blob: d5ba0378af99ea87a4f8f246540ea16f2ffe0d51 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package discovery
import (
"bytes"
"compress/flate"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"v.io/x/ref/lib/security/bcrypter"
)
// EncodingBuffer is used to encode and decode advertisements.
type EncodingBuffer struct {
buf *bytes.Buffer
}
// Write appends a byte slice to the buffer.
func (e *EncodingBuffer) Write(p []byte) {
e.buf.Write(p)
}
// Read reads the next len(p) bytes from the buffer. If the buffer has no
// enough data, io.EOF is returned.
func (e *EncodingBuffer) Read(p []byte) error {
n, err := e.buf.Read(p)
if err != nil {
return err
}
if n < len(p) {
return io.EOF
}
return nil
}
// WriteInt appends an integer to the buffer.
func (e *EncodingBuffer) WriteInt(x int) {
var p [binary.MaxVarintLen64]byte
n := binary.PutUvarint(p[:], uint64(x))
e.buf.Write(p[0:n])
}
// ReadInt reads an integer from the buffer.
func (e *EncodingBuffer) ReadInt() (int, error) {
x, err := binary.ReadUvarint(e.buf)
return int(x), err
}
// WriteBytes appends a byte slice to the buffer with its length.
func (e *EncodingBuffer) WriteBytes(p []byte) {
e.WriteInt(len(p))
e.buf.Write(p)
}
// ReadBytes reads a byte slice with its length from the buffer.
func (e *EncodingBuffer) ReadBytes() ([]byte, error) {
n, err := e.ReadInt()
if err != nil {
return nil, err
}
if n < 0 {
return nil, fmt.Errorf("invalid length %d", n)
}
p := e.buf.Next(n)
if len(p) < n {
return nil, io.EOF
}
return p, nil
}
// WriteString appends a string to the buffer.
func (e *EncodingBuffer) WriteString(s string) {
e.WriteInt(len(s))
e.buf.WriteString(s)
}
// ReadString reads a string from the buffer.
func (e *EncodingBuffer) ReadString() (string, error) {
p, err := e.ReadBytes()
if err != nil {
return "", err
}
return string(p), nil
}
// Len returns the number of bytes of the unread portion of the buffer.
func (e *EncodingBuffer) Len() int {
return e.buf.Len()
}
// Bytes returns a byte slice holding the unread portion of the buffer.
func (e *EncodingBuffer) Bytes() []byte {
return e.buf.Bytes()
}
// Next returns a slice containing the next n bytes from the buffer. If there
// are fewer than n bytes in the buffer, it returns the entire buffer.
func (e *EncodingBuffer) Next(n int) []byte {
return e.buf.Next(n)
}
// NewEncodingBuffer returns a new encoding buffer.
func NewEncodingBuffer(data []byte) *EncodingBuffer { return &EncodingBuffer{bytes.NewBuffer(data)} }
const (
addrsUncompressed = 0
addrsCompressed = 1
)
// PackAddresses packs addresses into a byte slice.
func PackAddresses(addrs []string) []byte {
if len(addrs) == 0 {
return nil
}
// Format: Trailing byte lists format (addrsCompressed or addrsUncompressed)
uncompressed := packAddressesUncompressed(addrs)
// Try to compress, if it fails or is too large, return uncompressed.
compressed := new(bytes.Buffer)
if w, err := flate.NewWriter(compressed, flate.DefaultCompression); err == nil {
if _, err := w.Write(uncompressed); err == nil && w.Close() == nil && compressed.Len() < len(uncompressed) {
return append(compressed.Bytes(), addrsCompressed)
}
}
return append(uncompressed, addrsUncompressed)
}
// UnpackAddresses unpacks addresses from a byte slice.
func UnpackAddresses(data []byte) ([]string, error) {
if len(data) == 0 {
return nil, nil
}
var uncompressed []byte
switch footer := data[len(data)-1]; footer {
case addrsUncompressed:
uncompressed = data[:len(data)-1]
case addrsCompressed:
r := flate.NewReader(bytes.NewBuffer(data[:len(data)-1]))
var err error
if uncompressed, err = ioutil.ReadAll(r); err != nil {
return nil, err
}
if err := r.Close(); err != nil {
return nil, err
}
}
return unpackAddressesUncompressed(uncompressed)
}
func packAddressesUncompressed(addrs []string) []byte {
buf := NewEncodingBuffer(nil)
for _, a := range addrs {
buf.WriteString(a)
}
return buf.Bytes()
}
func unpackAddressesUncompressed(data []byte) ([]string, error) {
buf := NewEncodingBuffer(data)
var addrs []string
for buf.Len() > 0 {
addr, err := buf.ReadString()
if err != nil {
return nil, err
}
addrs = append(addrs, addr)
}
return addrs, nil
}
// PackEncryptionKeys packs encryption algorithm and keys into a byte slice.
func PackEncryptionKeys(algo EncryptionAlgorithm, keys []EncryptionKey) []byte {
buf := NewEncodingBuffer(nil)
buf.WriteInt(int(algo))
for _, k := range keys {
buf.WriteBytes(k)
}
return buf.Bytes()
}
// UnpackEncryptionKeys unpacks encryption algorithm and keys from a byte slice.
func UnpackEncryptionKeys(data []byte) (EncryptionAlgorithm, []EncryptionKey, error) {
buf := NewEncodingBuffer(data)
algo, err := buf.ReadInt()
if err != nil {
return NoEncryption, nil, err
}
var keys []EncryptionKey
for buf.Len() > 0 {
key, err := buf.ReadBytes()
if err != nil {
return NoEncryption, nil, err
}
keys = append(keys, EncryptionKey(key))
}
return EncryptionAlgorithm(algo), keys, nil
}
// EncodeCiphertext encodes the cipher text into a byte slice.
func EncodeWireCiphertext(wctext *bcrypter.WireCiphertext) []byte {
buf := NewEncodingBuffer(nil)
buf.WriteString(wctext.PatternId)
for k, v := range wctext.Bytes {
buf.WriteString(k)
buf.WriteBytes(v)
}
return buf.Bytes()
}
// DecodeCiphertext decodes the cipher text from a byte slice.
func DecodeWireCiphertext(data []byte) (*bcrypter.WireCiphertext, error) {
buf := NewEncodingBuffer(data)
id, err := buf.ReadString()
if err != nil {
return nil, err
}
wctext := bcrypter.WireCiphertext{id, make(map[string][]byte)}
for buf.Len() > 0 {
k, err := buf.ReadString()
if err != nil {
return nil, err
}
v, err := buf.ReadBytes()
if err != nil {
return nil, err
}
wctext.Bytes[k] = v
}
return &wctext, nil
}
// EncodeTimestamp encodes the timestamp into a 8-byte byte slice.
func EncodeTimestamp(ts int64) []byte {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, uint64(ts))
return buf
}
// DecodeTimestamp decodes the timestamp from a 8-byte byte slice.
func DecodeTimestamp(data []byte) (int64, error) {
if len(data) != 8 {
return 0, errors.New("invalid length")
}
return int64(binary.LittleEndian.Uint64(data)), nil
}