lnd.xprv/tlv/record.go
Joost Jager 048971b40b
tlv+hop: contain odd/even logic in payload parsing
Tlv is used more widely in lnd than just for the onion payload. This
commit isolated the protocol-specific odd/even logic, so that tlv can be
used freely elsewhere. An example of this use is db serialization.
2019-11-12 16:18:25 +01:00

251 lines
6.3 KiB
Go

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
// TypeSet is an unordered set of Types. The map item boolean values indicate
// whether the type that we parsed was known.
type TypeSet map[Type]bool
// 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, error) {
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, nil
}
// 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()
})
}