You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
6.4 KiB
251 lines
6.4 KiB
package tlv |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"io" |
|
"sort" |
|
|
|
"github.com/btcsuite/btcd/btcec" |
|
) |
|
|
|
// Type is an 64-bit identifier for a TLV Record. |
|
type Type uint64 |
|
|
|
// TypeMap is a map of parsed Types. The map values are byte slices. If the byte |
|
// slice is nil, the type was successfully parsed. Otherwise the value is byte |
|
// slice containing the encoded data. |
|
type TypeMap map[Type][]byte |
|
|
|
// Encoder is a signature for methods that can encode TLV values. An error |
|
// should be returned if the Encoder cannot support the underlying type of val. |
|
// The provided scratch buffer must be non-nil. |
|
type Encoder func(w io.Writer, val interface{}, buf *[8]byte) error |
|
|
|
// Decoder is a signature for methods that can decode TLV values. An error |
|
// should be returned if the Decoder cannot support the underlying type of val. |
|
// The provided scratch buffer must be non-nil. |
|
type Decoder func(r io.Reader, val interface{}, buf *[8]byte, l uint64) error |
|
|
|
// ENOP is an encoder that doesn't modify the io.Writer and never fails. |
|
func ENOP(io.Writer, interface{}, *[8]byte) error { return nil } |
|
|
|
// DNOP is an encoder that doesn't modify the io.Reader and never fails. |
|
func DNOP(io.Reader, interface{}, *[8]byte, uint64) error { return nil } |
|
|
|
// SizeFunc is a function that can compute the length of a given field. Since |
|
// the size of the underlying field can change, this allows the size of the |
|
// field to be evaluated at the time of encoding. |
|
type SizeFunc func() uint64 |
|
|
|
// SizeVarBytes returns a SizeFunc that can compute the length of a byte slice. |
|
func SizeVarBytes(e *[]byte) SizeFunc { |
|
return func() uint64 { |
|
return uint64(len(*e)) |
|
} |
|
} |
|
|
|
// RecorderProducer is an interface for objects that can produce a Record object |
|
// capable of encoding and/or decoding the RecordProducer as a Record. |
|
type RecordProducer interface { |
|
// Record returns a Record that can be used to encode or decode the |
|
// backing object. |
|
Record() Record |
|
} |
|
|
|
// Record holds the required information to encode or decode a TLV record. |
|
type Record struct { |
|
value interface{} |
|
typ Type |
|
staticSize uint64 |
|
sizeFunc SizeFunc |
|
encoder Encoder |
|
decoder Decoder |
|
} |
|
|
|
// Size returns the size of the Record's value. If no static size is known, the |
|
// dynamic size will be evaluated. |
|
func (f *Record) Size() uint64 { |
|
if f.sizeFunc == nil { |
|
return f.staticSize |
|
} |
|
|
|
return f.sizeFunc() |
|
} |
|
|
|
// Type returns the type of the underlying TLV record. |
|
func (f *Record) Type() Type { |
|
return f.typ |
|
} |
|
|
|
// Encode writes out the TLV record to the passed writer. This is useful when a |
|
// caller wants to obtain the raw encoding of a *single* TLV record, outside |
|
// the context of the Stream struct. |
|
func (f *Record) Encode(w io.Writer) error { |
|
var b [8]byte |
|
|
|
return f.encoder(w, f.value, &b) |
|
} |
|
|
|
// Decode read in the TLV record from the passed reader. This is useful when a |
|
// caller wants decode a *single* TLV record, outside the context of the Stream |
|
// struct. |
|
func (f *Record) Decode(r io.Reader, l uint64) error { |
|
var b [8]byte |
|
return f.decoder(r, f.value, &b, l) |
|
} |
|
|
|
// MakePrimitiveRecord creates a record for common types. |
|
func MakePrimitiveRecord(typ Type, val interface{}) Record { |
|
var ( |
|
staticSize uint64 |
|
sizeFunc SizeFunc |
|
encoder Encoder |
|
decoder Decoder |
|
) |
|
switch e := val.(type) { |
|
case *uint8: |
|
staticSize = 1 |
|
encoder = EUint8 |
|
decoder = DUint8 |
|
|
|
case *uint16: |
|
staticSize = 2 |
|
encoder = EUint16 |
|
decoder = DUint16 |
|
|
|
case *uint32: |
|
staticSize = 4 |
|
encoder = EUint32 |
|
decoder = DUint32 |
|
|
|
case *uint64: |
|
staticSize = 8 |
|
encoder = EUint64 |
|
decoder = DUint64 |
|
|
|
case *[32]byte: |
|
staticSize = 32 |
|
encoder = EBytes32 |
|
decoder = DBytes32 |
|
|
|
case *[33]byte: |
|
staticSize = 33 |
|
encoder = EBytes33 |
|
decoder = DBytes33 |
|
|
|
case **btcec.PublicKey: |
|
staticSize = 33 |
|
encoder = EPubKey |
|
decoder = DPubKey |
|
|
|
case *[64]byte: |
|
staticSize = 64 |
|
encoder = EBytes64 |
|
decoder = DBytes64 |
|
|
|
case *[]byte: |
|
sizeFunc = SizeVarBytes(e) |
|
encoder = EVarBytes |
|
decoder = DVarBytes |
|
|
|
default: |
|
panic(fmt.Sprintf("unknown primitive type: %T", val)) |
|
} |
|
|
|
return Record{ |
|
value: val, |
|
typ: typ, |
|
staticSize: staticSize, |
|
sizeFunc: sizeFunc, |
|
encoder: encoder, |
|
decoder: decoder, |
|
} |
|
} |
|
|
|
// MakeStaticRecord creates a record for a field of fixed-size |
|
func MakeStaticRecord(typ Type, val interface{}, size uint64, encoder Encoder, |
|
decoder Decoder) Record { |
|
|
|
return Record{ |
|
value: val, |
|
typ: typ, |
|
staticSize: size, |
|
encoder: encoder, |
|
decoder: decoder, |
|
} |
|
} |
|
|
|
// MakeDynamicRecord creates a record whose size may vary, and will be |
|
// determined at the time of encoding via sizeFunc. |
|
func MakeDynamicRecord(typ Type, val interface{}, sizeFunc SizeFunc, |
|
encoder Encoder, decoder Decoder) Record { |
|
|
|
return Record{ |
|
value: val, |
|
typ: typ, |
|
sizeFunc: sizeFunc, |
|
encoder: encoder, |
|
decoder: decoder, |
|
} |
|
} |
|
|
|
// RecordsToMap encodes a series of TLV records as raw key-value pairs in the |
|
// form of a map. |
|
func RecordsToMap(records []Record) (map[uint64][]byte, error) { |
|
tlvMap := make(map[uint64][]byte, len(records)) |
|
|
|
for _, record := range records { |
|
var b bytes.Buffer |
|
if err := record.Encode(&b); err != nil { |
|
return nil, err |
|
} |
|
|
|
tlvMap[uint64(record.Type())] = b.Bytes() |
|
} |
|
|
|
return tlvMap, nil |
|
} |
|
|
|
// StubEncoder is a factory function that makes a stub tlv.Encoder out of a raw |
|
// value. We can use this to make a record that can be encoded when we don't |
|
// actually know it's true underlying value, and only it serialization. |
|
func StubEncoder(v []byte) Encoder { |
|
return func(w io.Writer, val interface{}, buf *[8]byte) error { |
|
_, err := w.Write(v) |
|
return err |
|
} |
|
} |
|
|
|
// MapToRecords encodes the passed TLV map as a series of regular tlv.Record |
|
// instances. The resulting set of records will be returned in sorted order by |
|
// their type. |
|
func MapToRecords(tlvMap map[uint64][]byte) []Record { |
|
records := make([]Record, 0, len(tlvMap)) |
|
for k, v := range tlvMap { |
|
// We don't pass in a decoder here since we don't actually know |
|
// the type, and only expect this Record to be used for display |
|
// and encoding purposes. |
|
record := MakeStaticRecord( |
|
Type(k), nil, uint64(len(v)), StubEncoder(v), nil, |
|
) |
|
|
|
records = append(records, record) |
|
} |
|
|
|
SortRecords(records) |
|
|
|
return records |
|
} |
|
|
|
// SortRecords is a helper function that will sort a slice of records in place |
|
// according to their type. |
|
func SortRecords(records []Record) { |
|
if len(records) == 0 { |
|
return |
|
} |
|
|
|
sort.Slice(records, func(i, j int) bool { |
|
return records[i].Type() < records[j].Type() |
|
}) |
|
}
|
|
|