diff --git a/tlv/record.go b/tlv/record.go new file mode 100644 index 00000000..ae21c050 --- /dev/null +++ b/tlv/record.go @@ -0,0 +1,153 @@ +package tlv + +import ( + "io" + + "github.com/btcsuite/btcd/btcec" +) + +// Type is an 64-bit identifier for a TLV Record. +type Type uint64 + +// 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)) + } +} + +// 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() +} + +// 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("unknown primitive type") + } + + 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, + } +}