diff --git a/encoding/cbor/cbor.go b/encoding/cbor/cbor.go new file mode 100644 index 000000000..fe8449a82 --- /dev/null +++ b/encoding/cbor/cbor.go @@ -0,0 +1,139 @@ +// Package cbor implements partial encoding/decoding of concise binary object +// representation (CBOR) described in [RFC 8949]. +// +// This package is intended for use only by the smithy client runtime. The +// exported API therein is not considered stable and is subject to breaking +// changes without notice. More specifically, this package implements a subset +// of the RFC 8949 specification required to support the Smithy RPCv2-CBOR +// protocol and is NOT suitable for general application use. +// +// The following principal restrictions apply: +// - Map (major type 5) keys can only be strings. +// - Float16 (major type 7, 25) values can be read but not encoded. Any +// float16 encountered during decode is converted to float32. +// - Indefinite-length values can be read but not encoded. Since the encoding +// API operates strictly off of a constructed syntax tree, the length of each +// data item in a Value will always be known and the encoder will always +// generate definite-length variants. +// +// It is the responsibility of the caller to determine whether a decoded CBOR +// integral or floating-point Value is suitable for its target (e.g. whether +// the value of a CBOR Uint fits into a field modeled as a Smithy short). +// +// All CBOR tags (major type 6) are implicitly supported since the +// encoder/decoder does not attempt to interpret a tag's contents. It is the +// responsibility of the caller to both provide valid Tag values to encode and +// to assert that a decoded Tag's contents are valid for its tag ID (e.g. +// ensuring whether a Tag with ID 1, indicating an enclosed epoch timestamp, +// actually contains a valid integral or floating-point CBOR Value). +// +// [RFC 8949]: https://www.rfc-editor.org/rfc/rfc8949.html +package cbor + +// Value describes a CBOR data item. +// +// The following types implement Value: +// - [Uint] +// - [NegInt] +// - [Slice] +// - [String] +// - [List] +// - [Map] +// - [Tag] +// - [Bool] +// - [Nil] +// - [Undefined] +// - [Float32] +// - [Float64] +type Value interface { + len() int + encode(p []byte) int +} + +var ( + _ Value = Uint(0) + _ Value = NegInt(0) + _ Value = Slice(nil) + _ Value = String("") + _ Value = List(nil) + _ Value = Map(nil) + _ Value = (*Tag)(nil) + _ Value = Bool(false) + _ Value = (*Nil)(nil) + _ Value = (*Undefined)(nil) + _ Value = Float32(0) + _ Value = Float64(0) +) + +// Uint describes a CBOR uint (major type 0) in the range [0, 2^64-1]. +type Uint uint64 + +// NegInt describes a CBOR negative int (major type 1) in the range [-2^64, -1]. +// +// The "true negative" value of a type 1 is specified by RFC 8949 to be -1 +// minus the encoded value. The encoder/decoder applies this bias +// automatically, e.g. the integral -100 is represented as NegInt(100), which +// will which encode to/from hex 3863 (major 1, minor 24, argument 99). +// +// This implicitly means that the lower bound of this type -2^64 is represented +// as the wraparound value NegInt(0). Deserializer implementations should take +// care to guard against this case when deriving a value for a signed integral +// type which was encoded as NegInt. +type NegInt uint64 + +// Slice describes a CBOR byte slice (major type 2). +type Slice []byte + +// String describes a CBOR text string (major type 3). +type String string + +// List describes a CBOR list (major type 4). +type List []Value + +// Map describes a CBOR map (major type 5). +// +// The type signature of the map's key is restricted to string as it is in +// Smithy. +type Map map[string]Value + +// Tag describes a CBOR-tagged value (major type 6). +type Tag struct { + ID uint64 + Value Value +} + +// Bool describes a boolean value (major type 7, argument 20/21). +type Bool bool + +// Nil is the `nil` / `null` literal (major type 7, argument 22). +type Nil struct{} + +// Undefined is the `undefined` literal (major type 7, argument 23). +type Undefined struct{} + +// Float32 describes an IEEE 754 single-precision floating-point number +// (major type 7, argument 26). +// +// Go does not natively support float16, all values encoded as such (major type +// 7, argument 25) must be represented by this variant instead. +type Float32 float32 + +// Float64 describes an IEEE 754 double-precision floating-point number +// (major type 7, argument 27). +type Float64 float64 + +// Encode returns a byte slice that encodes the given Value. +func Encode(v Value) []byte { + p := make([]byte, v.len()) + v.encode(p) + return p +} + +// Decode returns the Value encoded in the given byte slice. +func Decode(p []byte) (Value, error) { + v, _, err := decode(p) + if err != nil { + return nil, err + } + return v, nil +} diff --git a/encoding/cbor/const.go b/encoding/cbor/const.go new file mode 100644 index 000000000..353471557 --- /dev/null +++ b/encoding/cbor/const.go @@ -0,0 +1,41 @@ +package cbor + +// major type in LSB position +type majorType byte + +const ( + majorTypeUint majorType = iota + majorTypeNegInt + majorTypeSlice + majorTypeString + majorTypeList + majorTypeMap + majorTypeTag + majorType7 +) + +// masks for major/minor component in encoded head +const ( + maskMajor = 0b111 << 5 + maskMinor = 0b11111 +) + +// minor value encodings to represent arg bit length (and indefinite) +const ( + minorArg1 = 24 + minorArg2 = 25 + minorArg4 = 26 + minorArg8 = 27 + minorIndefinite = 31 +) + +// minor sentinels for everything in major 7 +const ( + major7False = 20 + major7True = 21 + major7Nil = 22 + major7Undefined = 23 + major7Float16 = minorArg2 + major7Float32 = minorArg4 + major7Float64 = minorArg8 +) diff --git a/encoding/cbor/decode.go b/encoding/cbor/decode.go new file mode 100644 index 000000000..036f3ac34 --- /dev/null +++ b/encoding/cbor/decode.go @@ -0,0 +1,320 @@ +package cbor + +import ( + "encoding/binary" + "fmt" + "math" +) + +func decode(p []byte) (Value, int, error) { + if len(p) == 0 { + return nil, 0, fmt.Errorf("unexpected end of payload") + } + + switch peekMajor(p) { + case majorTypeUint: + return decodeUint(p) + case majorTypeNegInt: + return decodeNegInt(p) + case majorTypeSlice: + return decodeSlice(p, majorTypeSlice) + case majorTypeString: + s, n, err := decodeSlice(p, majorTypeString) + return String(s), n, err + case majorTypeList: + return decodeList(p) + case majorTypeMap: + return decodeMap(p) + case majorTypeTag: + return decodeTag(p) + default: // majorType7 + return decodeMajor7(p) + } +} + +func decodeUint(p []byte) (Uint, int, error) { + i, off, err := decodeArgument(p) + if err != nil { + return 0, 0, fmt.Errorf("decode argument: %w", err) + } + + return Uint(i), off, nil +} + +func decodeNegInt(p []byte) (NegInt, int, error) { + i, off, err := decodeArgument(p) + if err != nil { + return 0, 0, fmt.Errorf("decode argument: %w", err) + } + + return NegInt(i + 1), off, nil +} + +// this routine is used for both string and slice major types, the value of +// inner specifies which context we're in (needed for validating subsegments +// inside indefinite encodings) +func decodeSlice(p []byte, inner majorType) (Slice, int, error) { + minor := peekMinor(p) + if minor == minorIndefinite { + return decodeSliceIndefinite(p, inner) + } + + slen, off, err := decodeArgument(p) + if err != nil { + return nil, 0, fmt.Errorf("decode argument: %w", err) + } + + p = p[off:] + if uint64(len(p)) < slen { + return nil, 0, fmt.Errorf("slice len %d greater than remaining buf len", slen) + } + + return Slice(p[:slen]), off + int(slen), nil +} + +func decodeSliceIndefinite(p []byte, inner majorType) (Slice, int, error) { + p = p[1:] + + s := Slice{} + for off := 0; len(p) > 0; { + if p[0] == 0xff { + return s, off + 2, nil + } + + if major := peekMajor(p); major != inner { + return nil, 0, fmt.Errorf("unexpected major type %d in indefinite slice", major) + } + if peekMinor(p) == minorIndefinite { + return nil, 0, fmt.Errorf("nested indefinite slice") + } + + ss, n, err := decodeSlice(p, inner) + if err != nil { + return nil, 0, fmt.Errorf("decode subslice: %w", err) + } + p = p[n:] + + s = append(s, ss...) + off += n + } + return nil, 0, fmt.Errorf("expected break marker") +} + +func decodeList(p []byte) (List, int, error) { + minor := peekMinor(p) + if minor == minorIndefinite { + return decodeListIndefinite(p) + } + + alen, off, err := decodeArgument(p) + if err != nil { + return nil, 0, fmt.Errorf("decode argument: %w", err) + } + p = p[off:] + + l := List{} + for i := 0; i < int(alen); i++ { + item, n, err := decode(p) + if err != nil { + return nil, 0, fmt.Errorf("decode item: %w", err) + } + p = p[n:] + + l = append(l, item) + off += n + } + + return l, off, nil +} + +func decodeListIndefinite(p []byte) (List, int, error) { + p = p[1:] + + l := List{} + for off := 0; len(p) > 0; { + if p[0] == 0xff { + return l, off + 2, nil + } + + item, n, err := decode(p) + if err != nil { + return nil, 0, fmt.Errorf("decode item: %w", err) + } + p = p[n:] + + l = append(l, item) + off += n + } + return nil, 0, fmt.Errorf("expected break marker") +} + +func decodeMap(p []byte) (Map, int, error) { + minor := peekMinor(p) + if minor == minorIndefinite { + return decodeMapIndefinite(p) + } + + maplen, off, err := decodeArgument(p) + if err != nil { + return nil, 0, fmt.Errorf("decode argument: %w", err) + } + p = p[off:] + + mp := Map{} + for i := 0; i < int(maplen); i++ { + if len(p) == 0 { + return nil, 0, fmt.Errorf("unexpected end of payload") + } + + if major := peekMajor(p); major != majorTypeString { + return nil, 0, fmt.Errorf("unexpected major type %d for map key", major) + } + + key, kn, err := decodeSlice(p, majorTypeString) + if err != nil { + return nil, 0, fmt.Errorf("decode key: %w", err) + } + p = p[kn:] + + value, vn, err := decode(p) + if err != nil { + return nil, 0, fmt.Errorf("decode value: %w", err) + } + p = p[vn:] + + mp[string(key)] = value + off += kn + vn + } + + return mp, off, nil +} + +func decodeMapIndefinite(p []byte) (Map, int, error) { + p = p[1:] + + mp := Map{} + for off := 0; len(p) > 0; { + if p[0] == 0xff { + return mp, off + 2, nil + } + + if major := peekMajor(p); major != majorTypeString { + return nil, 0, fmt.Errorf("unexpected major type %d for map key", major) + } + + key, kn, err := decodeSlice(p, majorTypeString) + if err != nil { + return nil, 0, fmt.Errorf("decode key: %w", err) + } + p = p[kn:] + + value, vn, err := decode(p) + if err != nil { + return nil, 0, fmt.Errorf("decode value: %w", err) + } + p = p[vn:] + + mp[string(key)] = value + off += kn + vn + } + return nil, 0, fmt.Errorf("expected break marker") +} + +func decodeTag(p []byte) (*Tag, int, error) { + id, off, err := decodeArgument(p) + if err != nil { + return nil, 0, fmt.Errorf("decode argument: %w", err) + } + p = p[off:] + + v, n, err := decode(p) + if err != nil { + return nil, 0, fmt.Errorf("decode value: %w", err) + } + + return &Tag{ID: id, Value: v}, off + n, nil +} + +func decodeMajor7(p []byte) (Value, int, error) { + switch m := peekMinor(p); m { + case major7True, major7False: + return Bool(m == major7True), 1, nil + case major7Nil: + return &Nil{}, 1, nil + case major7Undefined: + return &Undefined{}, 1, nil + case major7Float16: + if len(p) < 3 { + return nil, 0, fmt.Errorf("incomplete float16 at end of buf") + } + b := binary.BigEndian.Uint16(p[1:]) + return Float32(math.Float32frombits(float16to32(b))), 3, nil + case major7Float32: + if len(p) < 5 { + return nil, 0, fmt.Errorf("incomplete float32 at end of buf") + } + b := binary.BigEndian.Uint32(p[1:]) + return Float32(math.Float32frombits(b)), 5, nil + case major7Float64: + if len(p) < 9 { + return nil, 0, fmt.Errorf("incomplete float64 at end of buf") + } + b := binary.BigEndian.Uint64(p[1:]) + return Float64(math.Float64frombits(b)), 9, nil + default: + return nil, 0, fmt.Errorf("unexpected minor value %d", m) + } +} + +func peekMajor(p []byte) majorType { + return majorType(p[0] & maskMajor >> 5) +} + +func peekMinor(p []byte) byte { + return p[0] & maskMinor +} + +// pulls the next argument out of the buffer +// +// expects one of the sized arguments and will error otherwise - callers that +// need to check for the indefinite flag must do so externally +func decodeArgument(p []byte) (uint64, int, error) { + minor := peekMinor(p) + if minor < minorArg1 { + return uint64(minor), 1, nil + } + + switch minor { + case minorArg1, minorArg2, minorArg4, minorArg8: + argLen := mtol(minor) + if len(p) < argLen+1 { + return 0, 0, fmt.Errorf("arg len %d greater than remaining buf len", argLen) + } + return readArgument(p[1:], argLen), argLen + 1, nil + default: + return 0, 0, fmt.Errorf("unexpected minor value %d", minor) + } +} + +// minor value to arg len in bytes, assumes minor was checked to be in [24,27] +func mtol(minor byte) int { + if minor == minorArg1 { + return 1 + } else if minor == minorArg2 { + return 2 + } else if minor == minorArg4 { + return 4 + } + return 8 +} + +func readArgument(p []byte, len int) uint64 { + if len == 1 { + return uint64(p[0]) + } else if len == 2 { + return uint64(binary.BigEndian.Uint16(p)) + } else if len == 4 { + return uint64(binary.BigEndian.Uint32(p)) + } + return uint64(binary.BigEndian.Uint64(p)) +} diff --git a/encoding/cbor/decode_test.go b/encoding/cbor/decode_test.go new file mode 100644 index 000000000..c0ac01182 --- /dev/null +++ b/encoding/cbor/decode_test.go @@ -0,0 +1,1334 @@ +package cbor + +import ( + "math" + "reflect" + "strings" + "testing" +) + +func TestDecode_InvalidArgument(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Err string + }{ + "uint/1": { + []byte{0<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "uint/2": { + []byte{0<<5 | 25, 0}, + "arg len 2 greater than remaining buf len", + }, + "uint/4": { + []byte{0<<5 | 26, 0, 0, 0}, + "arg len 4 greater than remaining buf len", + }, + "uint/8": { + []byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, + "arg len 8 greater than remaining buf len", + }, + "uint/?": { + []byte{0<<5 | 31}, + "unexpected minor value 31", + }, + "negint/1": { + []byte{1<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "negint/2": { + []byte{1<<5 | 25, 0}, + "arg len 2 greater than remaining buf len", + }, + "negint/4": { + []byte{1<<5 | 26, 0, 0, 0}, + "arg len 4 greater than remaining buf len", + }, + "negint/8": { + []byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, + "arg len 8 greater than remaining buf len", + }, + "negint/?": { + []byte{1<<5 | 31}, + "unexpected minor value 31", + }, + "slice/1": { + []byte{2<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "slice/2": { + []byte{2<<5 | 25, 0}, + "arg len 2 greater than remaining buf len", + }, + "slice/4": { + []byte{2<<5 | 26, 0, 0, 0}, + "arg len 4 greater than remaining buf len", + }, + "slice/8": { + []byte{2<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, + "arg len 8 greater than remaining buf len", + }, + "string/1": { + []byte{3<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "string/2": { + []byte{3<<5 | 25, 0}, + "arg len 2 greater than remaining buf len", + }, + "string/4": { + []byte{3<<5 | 26, 0, 0, 0}, + "arg len 4 greater than remaining buf len", + }, + "string/8": { + []byte{3<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, + "arg len 8 greater than remaining buf len", + }, + "list/1": { + []byte{4<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "list/2": { + []byte{4<<5 | 25, 0}, + "arg len 2 greater than remaining buf len", + }, + "list/4": { + []byte{4<<5 | 26, 0, 0, 0}, + "arg len 4 greater than remaining buf len", + }, + "list/8": { + []byte{4<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, + "arg len 8 greater than remaining buf len", + }, + "map/1": { + []byte{5<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "map/2": { + []byte{5<<5 | 25, 0}, + "arg len 2 greater than remaining buf len", + }, + "map/4": { + []byte{5<<5 | 26, 0, 0, 0}, + "arg len 4 greater than remaining buf len", + }, + "map/8": { + []byte{5<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, + "arg len 8 greater than remaining buf len", + }, + "tag/1": { + []byte{6<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "tag/2": { + []byte{6<<5 | 25, 0}, + "arg len 2 greater than remaining buf len", + }, + "tag/4": { + []byte{6<<5 | 26, 0, 0, 0}, + "arg len 4 greater than remaining buf len", + }, + "tag/8": { + []byte{6<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, + "arg len 8 greater than remaining buf len", + }, + "tag/?": { + []byte{6<<5 | 31}, + "unexpected minor value 31", + }, + "major7/float16": { + []byte{7<<5 | 25, 0}, + "incomplete float16 at end of buf", + }, + "major7/float32": { + []byte{7<<5 | 26, 0, 0, 0}, + "incomplete float32 at end of buf", + }, + "major7/float64": { + []byte{7<<5 | 27, 0, 0, 0, 0, 0, 0, 0}, + "incomplete float64 at end of buf", + }, + "major7/?": { + []byte{7<<5 | 31}, + "unexpected minor value 31", + }, + } { + t.Run(name, func(t *testing.T) { + _, _, err := decode(c.In) + if err == nil { + t.Errorf("expect err %s", c.Err) + } + if aerr := err.Error(); !strings.Contains(aerr, c.Err) { + t.Errorf("expect err %s, got %s", c.Err, aerr) + } + }) + } +} + +func TestDecode_InvalidSlice(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Err string + }{ + "slice/1, not enough bytes": { + []byte{2<<5 | 24, 1}, + "slice len 1 greater than remaining buf len", + }, + "slice/?, no break": { + []byte{2<<5 | 31}, + "expected break marker", + }, + "slice/?, invalid nested major": { + []byte{2<<5 | 31, 3<<5 | 0}, + "unexpected major type 3 in indefinite slice", + }, + "slice/?, nested indefinite": { + []byte{2<<5 | 31, 2<<5 | 31}, + "nested indefinite slice", + }, + "slice/?, invalid nested definite": { + []byte{2<<5 | 31, 2<<5 | 24, 1}, + "decode subslice: slice len 1 greater than remaining buf len", + }, + "string/1, not enough bytes": { + []byte{3<<5 | 24, 1}, + "slice len 1 greater than remaining buf len", + }, + "string/?, no break": { + []byte{3<<5 | 31}, + "expected break marker", + }, + "string/?, invalid nested major": { + []byte{3<<5 | 31, 2<<5 | 0}, + "unexpected major type 2 in indefinite slice", + }, + "string/?, nested indefinite": { + []byte{3<<5 | 31, 3<<5 | 31}, + "nested indefinite slice", + }, + "string/?, invalid nested definite": { + []byte{3<<5 | 31, 3<<5 | 24, 1}, + "decode subslice: slice len 1 greater than remaining buf len", + }, + } { + t.Run(name, func(t *testing.T) { + _, _, err := decode(c.In) + if err == nil { + t.Errorf("expect err %s", c.Err) + } + if aerr := err.Error(); !strings.Contains(aerr, c.Err) { + t.Errorf("expect err %s, got %s", c.Err, aerr) + } + }) + } +} + +func TestDecode_InvalidList(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Err string + }{ + "[] / eof after head": { + []byte{4<<5 | 1}, + "unexpected end of payload", + }, + "[] / invalid item": { + []byte{4<<5 | 1, 0<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "[_ ] / no break": { + []byte{4<<5 | 31}, + "expected break marker", + }, + "[_ ] / invalid item": { + []byte{4<<5 | 31, 0<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + } { + t.Run(name, func(t *testing.T) { + _, _, err := decode(c.In) + if err == nil { + t.Errorf("expect err %s", c.Err) + } + if aerr := err.Error(); !strings.Contains(aerr, c.Err) { + t.Errorf("expect err %s, got %s", c.Err, aerr) + } + }) + } +} + +func TestDecode_InvalidMap(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Err string + }{ + "{} / eof after head": { + []byte{5<<5 | 1}, + "unexpected end of payload", + }, + "{} / non-string key": { + []byte{5<<5 | 1, 0}, + "unexpected major type 0 for map key", + }, + "{} / invalid key": { + []byte{5<<5 | 1, 3<<5 | 24, 1}, + "slice len 1 greater than remaining buf len", + }, + "{} / invalid value": { + []byte{5<<5 | 1, 3<<5 | 3, 0x66, 0x6f, 0x6f, 0<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "{_ } / no break": { + []byte{5<<5 | 31}, + "expected break marker", + }, + "{_ } / non-string key": { + []byte{5<<5 | 31, 0}, + "unexpected major type 0 for map key", + }, + "{_ } / invalid key": { + []byte{5<<5 | 31, 3<<5 | 24, 1}, + "slice len 1 greater than remaining buf len", + }, + "{_ } / invalid value": { + []byte{5<<5 | 31, 3<<5 | 3, 0x66, 0x6f, 0x6f, 0<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + } { + t.Run(name, func(t *testing.T) { + _, _, err := decode(c.In) + if err == nil { + t.Errorf("expect err %s", c.Err) + } + if aerr := err.Error(); !strings.Contains(aerr, c.Err) { + t.Errorf("expect err %s, got %s", c.Err, aerr) + } + }) + } +} + +func TestDecode_InvalidTag(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Err string + }{ + "invalid value": { + []byte{6<<5 | 1, 0<<5 | 24}, + "arg len 1 greater than remaining buf len", + }, + "eof": { + []byte{6<<5 | 1}, + "unexpected end of payload", + }, + } { + t.Run(name, func(t *testing.T) { + _, _, err := decode(c.In) + if err == nil { + t.Errorf("expect err %s", c.Err) + } + if aerr := err.Error(); !strings.Contains(aerr, c.Err) { + t.Errorf("expect err %s, got %s", c.Err, aerr) + } + }) + } +} + +func TestDecode_Atomic(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Expect Value + }{ + "uint/0/min": { + []byte{0<<5 | 0}, + Uint(0), + }, + "uint/0/max": { + []byte{0<<5 | 23}, + Uint(23), + }, + "uint/1/min": { + []byte{0<<5 | 24, 0}, + Uint(0), + }, + "uint/1/max": { + []byte{0<<5 | 24, 0xff}, + Uint(0xff), + }, + "uint/2/min": { + []byte{0<<5 | 25, 0, 0}, + Uint(0), + }, + "uint/2/max": { + []byte{0<<5 | 25, 0xff, 0xff}, + Uint(0xffff), + }, + "uint/4/min": { + []byte{0<<5 | 26, 0, 0, 0, 0}, + Uint(0), + }, + "uint/4/max": { + []byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}, + Uint(0xffffffff), + }, + "uint/8/min": { + []byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}, + Uint(0), + }, + "uint/8/max": { + []byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + Uint(0xffffffff_ffffffff), + }, + "negint/0/min": { + []byte{1<<5 | 0}, + NegInt(1), + }, + "negint/0/max": { + []byte{1<<5 | 23}, + NegInt(24), + }, + "negint/1/min": { + []byte{1<<5 | 24, 0}, + NegInt(1), + }, + "negint/1/max": { + []byte{1<<5 | 24, 0xff}, + NegInt(0x100), + }, + "negint/2/min": { + []byte{1<<5 | 25, 0, 0}, + NegInt(1), + }, + "negint/2/max": { + []byte{1<<5 | 25, 0xff, 0xff}, + NegInt(0x10000), + }, + "negint/4/min": { + []byte{1<<5 | 26, 0, 0, 0, 0}, + NegInt(1), + }, + "negint/4/max": { + []byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}, + NegInt(0x100000000), + }, + "negint/8/min": { + []byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}, + NegInt(1), + }, + "negint/8/max": { + []byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}, + NegInt(0xffffffff_ffffffff), + }, + "true": { + []byte{7<<5 | major7True}, + Bool(true), + }, + "false": { + []byte{7<<5 | major7False}, + Bool(false), + }, + "null": { + []byte{7<<5 | major7Nil}, + &Nil{}, + }, + "undefined": { + []byte{7<<5 | major7Undefined}, + &Undefined{}, + }, + "float16/+Inf": { + []byte{7<<5 | major7Float16, 0x7c, 0}, + Float32(math.Float32frombits(0x7f800000)), + }, + "float16/-Inf": { + []byte{7<<5 | major7Float16, 0xfc, 0}, + Float32(math.Float32frombits(0xff800000)), + }, + "float16/NaN/MSB": { + []byte{7<<5 | major7Float16, 0x7e, 0}, + Float32(math.Float32frombits(0x7fc00000)), + }, + "float16/NaN/LSB": { + []byte{7<<5 | major7Float16, 0x7c, 1}, + Float32(math.Float32frombits(0x7f802000)), + }, + "float32": { + []byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}, + Float32(math.Float32frombits(0x7f800000)), + }, + "float64": { + []byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}, + Float64(math.Float64frombits(0x7ff00000_00000000)), + }, + } { + t.Run(name, func(t *testing.T) { + actual, n, err := decode(c.In) + if err != nil { + t.Errorf("expect no err, got %v", err) + } + if n != len(c.In) { + t.Errorf("didn't decode whole buffer") + } + assertValue(t, c.Expect, actual) + }) + } +} + +func TestDecode_DefiniteSlice(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Expect Value + }{ + "len = 0": { + []byte{2<<5 | 0}, + Slice{}, + }, + "len > 0": { + []byte{2<<5 | 3, 0x66, 0x6f, 0x6f}, + Slice{0x66, 0x6f, 0x6f}, + }, + } { + t.Run(name, func(t *testing.T) { + actual, n, err := decode(c.In) + if err != nil { + t.Errorf("expect no err, got %v", err) + } + if n != len(c.In) { + t.Errorf("didn't decode whole buffer") + } + assertValue(t, c.Expect, actual) + }) + } +} + +func TestDecode_IndefiniteSlice(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Expect Value + }{ + "len = 0": { + []byte{2<<5 | 31, 0xff}, + Slice{}, + }, + "len = 0, explicit": { + []byte{2<<5 | 31, 2<<5 | 0, 0xff}, + Slice{}, + }, + "len = 0, len > 0": { + []byte{ + 2<<5 | 31, + 2<<5 | 0, + 2<<5 | 3, 0x66, 0x6f, 0x6f, + 0xff, + }, + Slice{0x66, 0x6f, 0x6f}, + }, + "len > 0, len = 0": { + []byte{ + 2<<5 | 31, + 2<<5 | 3, 0x66, 0x6f, 0x6f, + 2<<5 | 0, + 0xff, + }, + Slice{0x66, 0x6f, 0x6f}, + }, + "len > 0, len > 0": { + []byte{ + 2<<5 | 31, + 2<<5 | 3, 0x66, 0x6f, 0x6f, + 2<<5 | 3, 0x66, 0x6f, 0x6f, + 0xff, + }, + Slice{0x66, 0x6f, 0x6f, 0x66, 0x6f, 0x6f}, + }, + } { + t.Run(name, func(t *testing.T) { + actual, n, err := decode(c.In) + if err != nil { + t.Errorf("expect no err, got %v", err) + } + if n != len(c.In) { + t.Errorf("didn't decode whole buffer") + } + assertValue(t, c.Expect, actual) + }) + } +} + +func TestDecode_DefiniteString(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Expect Value + }{ + "len = 0": { + []byte{3<<5 | 0}, + String(""), + }, + "len > 0": { + []byte{3<<5 | 3, 0x66, 0x6f, 0x6f}, + String("foo"), + }, + } { + t.Run(name, func(t *testing.T) { + actual, n, err := decode(c.In) + if err != nil { + t.Errorf("expect no err, got %v", err) + } + if n != len(c.In) { + t.Errorf("didn't decode whole buffer") + } + assertValue(t, c.Expect, actual) + }) + } +} + +func TestDecode_IndefiniteString(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Expect Value + }{ + "len = 0": { + []byte{3<<5 | 31, 0xff}, + String(""), + }, + "len = 0, explicit": { + []byte{3<<5 | 31, 3<<5 | 0, 0xff}, + String(""), + }, + "len = 0, len > 0": { + []byte{ + 3<<5 | 31, + 3<<5 | 0, + 3<<5 | 3, 0x66, 0x6f, 0x6f, + 0xff, + }, + String("foo"), + }, + "len > 0, len = 0": { + []byte{ + 3<<5 | 31, + 3<<5 | 3, 0x66, 0x6f, 0x6f, + 3<<5 | 0, + 0xff, + }, + String("foo"), + }, + "len > 0, len > 0": { + []byte{ + 3<<5 | 31, + 3<<5 | 3, 0x66, 0x6f, 0x6f, + 3<<5 | 3, 0x66, 0x6f, 0x6f, + 0xff, + }, + String("foofoo"), + }, + } { + t.Run(name, func(t *testing.T) { + actual, n, err := decode(c.In) + if err != nil { + t.Errorf("expect no err, got %v", err) + } + if n != len(c.In) { + t.Errorf("didn't decode whole buffer") + } + assertValue(t, c.Expect, actual) + }) + } +} + +func TestDecode_List(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Expect Value + }{ + "[uint/0/min]": { + In: withDefiniteList([]byte{0<<5 | 0}), + Expect: List{Uint(0)}, + }, + "[uint/0/max]": { + In: withDefiniteList([]byte{0<<5 | 23}), + Expect: List{Uint(23)}, + }, + "[uint/1/min]": { + In: withDefiniteList([]byte{0<<5 | 24, 0}), + Expect: List{Uint(0)}, + }, + "[uint/1/max]": { + In: withDefiniteList([]byte{0<<5 | 24, 0xff}), + Expect: List{Uint(0xff)}, + }, + "[uint/2/min]": { + In: withDefiniteList([]byte{0<<5 | 25, 0, 0}), + Expect: List{Uint(0)}, + }, + "[uint/2/max]": { + In: withDefiniteList([]byte{0<<5 | 25, 0xff, 0xff}), + Expect: List{Uint(0xffff)}, + }, + "[uint/4/min]": { + In: withDefiniteList([]byte{0<<5 | 26, 0, 0, 0, 0}), + Expect: List{Uint(0)}, + }, + "[uint/4/max]": { + In: withDefiniteList([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Expect: List{Uint(0xffffffff)}, + }, + "[uint/8/min]": { + In: withDefiniteList([]byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), + Expect: List{Uint(0)}, + }, + "[uint/8/max]": { + In: withDefiniteList([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), + Expect: List{Uint(0xffffffff_ffffffff)}, + }, + "[negint/0/min]": { + In: withDefiniteList([]byte{1<<5 | 0}), + Expect: List{NegInt(1)}, + }, + "[negint/0/max]": { + In: withDefiniteList([]byte{1<<5 | 23}), + Expect: List{NegInt(24)}, + }, + "[negint/1/min]": { + In: withDefiniteList([]byte{1<<5 | 24, 0}), + Expect: List{NegInt(1)}, + }, + "[negint/1/max]": { + In: withDefiniteList([]byte{1<<5 | 24, 0xff}), + Expect: List{NegInt(0x100)}, + }, + "[negint/2/min]": { + In: withDefiniteList([]byte{1<<5 | 25, 0, 0}), + Expect: List{NegInt(1)}, + }, + "[negint/2/max]": { + In: withDefiniteList([]byte{1<<5 | 25, 0xff, 0xff}), + Expect: List{NegInt(0x10000)}, + }, + "[negint/4/min]": { + In: withDefiniteList([]byte{1<<5 | 26, 0, 0, 0, 0}), + Expect: List{NegInt(1)}, + }, + "[negint/4/max]": { + In: withDefiniteList([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Expect: List{NegInt(0x100000000)}, + }, + "[negint/8/min]": { + In: withDefiniteList([]byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), + Expect: List{NegInt(1)}, + }, + "[negint/8/max]": { + In: withDefiniteList([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), + Expect: List{NegInt(0xffffffff_ffffffff)}, + }, + "[true]": { + In: withDefiniteList([]byte{7<<5 | major7True}), + Expect: List{Bool(true)}, + }, + "[false]": { + In: withDefiniteList([]byte{7<<5 | major7False}), + Expect: List{Bool(false)}, + }, + "[null]": { + In: withDefiniteList([]byte{7<<5 | major7Nil}), + Expect: List{&Nil{}}, + }, + "[undefined]": { + In: withDefiniteList([]byte{7<<5 | major7Undefined}), + Expect: List{&Undefined{}}, + }, + "[float16/+Inf]": { + In: withDefiniteList([]byte{7<<5 | major7Float16, 0x7c, 0}), + Expect: List{Float32(math.Float32frombits(0x7f800000))}, + }, + "[float16/-Inf]": { + In: withDefiniteList([]byte{7<<5 | major7Float16, 0xfc, 0}), + Expect: List{Float32(math.Float32frombits(0xff800000))}, + }, + "[float16/NaN/MSB]": { + In: withDefiniteList([]byte{7<<5 | major7Float16, 0x7e, 0}), + Expect: List{Float32(math.Float32frombits(0x7fc00000))}, + }, + "[float16/NaN/LSB]": { + In: withDefiniteList([]byte{7<<5 | major7Float16, 0x7c, 1}), + Expect: List{Float32(math.Float32frombits(0x7f802000))}, + }, + "[float32]": { + In: withDefiniteList([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), + Expect: List{Float32(math.Float32frombits(0x7f800000))}, + }, + "[float64]": { + In: withDefiniteList([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), + Expect: List{Float64(math.Float64frombits(0x7ff00000_00000000))}, + }, + "[_ uint/0/min]": { + In: withIndefiniteList([]byte{0<<5 | 0}), + Expect: List{Uint(0)}, + }, + "[_ uint/0/max]": { + In: withIndefiniteList([]byte{0<<5 | 23}), + Expect: List{Uint(23)}, + }, + "[_ uint/1/min]": { + In: withIndefiniteList([]byte{0<<5 | 24, 0}), + Expect: List{Uint(0)}, + }, + "[_ uint/1/max]": { + In: withIndefiniteList([]byte{0<<5 | 24, 0xff}), + Expect: List{Uint(0xff)}, + }, + "[_ uint/2/min]": { + In: withIndefiniteList([]byte{0<<5 | 25, 0, 0}), + Expect: List{Uint(0)}, + }, + "[_ uint/2/max]": { + In: withIndefiniteList([]byte{0<<5 | 25, 0xff, 0xff}), + Expect: List{Uint(0xffff)}, + }, + "[_ uint/4/min]": { + In: withIndefiniteList([]byte{0<<5 | 26, 0, 0, 0, 0}), + Expect: List{Uint(0)}, + }, + "[_ uint/4/max]": { + In: withIndefiniteList([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Expect: List{Uint(0xffffffff)}, + }, + "[_ uint/8/min]": { + In: withIndefiniteList([]byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), + Expect: List{Uint(0)}, + }, + "[_ uint/8/max]": { + In: withIndefiniteList([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), + Expect: List{Uint(0xffffffff_ffffffff)}, + }, + "[_ negint/0/min]": { + In: withIndefiniteList([]byte{1<<5 | 0}), + Expect: List{NegInt(1)}, + }, + "[_ negint/0/max]": { + In: withIndefiniteList([]byte{1<<5 | 23}), + Expect: List{NegInt(24)}, + }, + "[_ negint/1/min]": { + In: withIndefiniteList([]byte{1<<5 | 24, 0}), + Expect: List{NegInt(1)}, + }, + "[_ negint/1/max]": { + In: withIndefiniteList([]byte{1<<5 | 24, 0xff}), + Expect: List{NegInt(0x100)}, + }, + "[_ negint/2/min]": { + In: withIndefiniteList([]byte{1<<5 | 25, 0, 0}), + Expect: List{NegInt(1)}, + }, + "[_ negint/2/max]": { + In: withIndefiniteList([]byte{1<<5 | 25, 0xff, 0xff}), + Expect: List{NegInt(0x10000)}, + }, + "[_ negint/4/min]": { + In: withIndefiniteList([]byte{1<<5 | 26, 0, 0, 0, 0}), + Expect: List{NegInt(1)}, + }, + "[_ negint/4/max]": { + In: withIndefiniteList([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Expect: List{NegInt(0x100000000)}, + }, + "[_ negint/8/min]": { + In: withIndefiniteList([]byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), + Expect: List{NegInt(1)}, + }, + "[_ negint/8/max]": { + In: withIndefiniteList([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), + Expect: List{NegInt(0xffffffff_ffffffff)}, + }, + "[_ true]": { + In: withIndefiniteList([]byte{7<<5 | major7True}), + Expect: List{Bool(true)}, + }, + "[_ false]": { + In: withIndefiniteList([]byte{7<<5 | major7False}), + Expect: List{Bool(false)}, + }, + "[_ null]": { + In: withIndefiniteList([]byte{7<<5 | major7Nil}), + Expect: List{&Nil{}}, + }, + "[_ undefined]": { + In: withIndefiniteList([]byte{7<<5 | major7Undefined}), + Expect: List{&Undefined{}}, + }, + "[_ float16/+Inf]": { + In: withIndefiniteList([]byte{7<<5 | major7Float16, 0x7c, 0}), + Expect: List{Float32(math.Float32frombits(0x7f800000))}, + }, + "[_ float16/-Inf]": { + In: withIndefiniteList([]byte{7<<5 | major7Float16, 0xfc, 0}), + Expect: List{Float32(math.Float32frombits(0xff800000))}, + }, + "[_ float16/NaN/MSB]": { + In: withIndefiniteList([]byte{7<<5 | major7Float16, 0x7e, 0}), + Expect: List{Float32(math.Float32frombits(0x7fc00000))}, + }, + "[_ float16/NaN/LSB]": { + In: withIndefiniteList([]byte{7<<5 | major7Float16, 0x7c, 1}), + Expect: List{Float32(math.Float32frombits(0x7f802000))}, + }, + "[_ float32]": { + In: withIndefiniteList([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), + Expect: List{Float32(math.Float32frombits(0x7f800000))}, + }, + "[_ float64]": { + In: withIndefiniteList([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), + Expect: List{Float64(math.Float64frombits(0x7ff00000_00000000))}, + }, + } { + t.Run(name, func(t *testing.T) { + actual, n, err := decode(c.In) + if err != nil { + t.Errorf("expect no err, got %v", err) + } + if n != len(c.In) { + t.Errorf("didn't decode whole buffer (decoded %d of %d)", n, len(c.In)) + } + assertValue(t, c.Expect, actual) + }) + } +} + +func TestDecode_Map(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Expect Value + }{ + "{uint/0/min}": { + In: withDefiniteMap([]byte{0<<5 | 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{uint/0/max}": { + In: withDefiniteMap([]byte{0<<5 | 23}), + Expect: Map{"foo": Uint(23)}, + }, + "{uint/1/min}": { + In: withDefiniteMap([]byte{0<<5 | 24, 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{uint/1/max}": { + In: withDefiniteMap([]byte{0<<5 | 24, 0xff}), + Expect: Map{"foo": Uint(0xff)}, + }, + "{uint/2/min}": { + In: withDefiniteMap([]byte{0<<5 | 25, 0, 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{uint/2/max}": { + In: withDefiniteMap([]byte{0<<5 | 25, 0xff, 0xff}), + Expect: Map{"foo": Uint(0xffff)}, + }, + "{uint/4/min}": { + In: withDefiniteMap([]byte{0<<5 | 26, 0, 0, 0, 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{uint/4/max}": { + In: withDefiniteMap([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Expect: Map{"foo": Uint(0xffffffff)}, + }, + "{uint/8/min}": { + In: withDefiniteMap([]byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{uint/8/max}": { + In: withDefiniteMap([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), + Expect: Map{"foo": Uint(0xffffffff_ffffffff)}, + }, + "{negint/0/min}": { + In: withDefiniteMap([]byte{1<<5 | 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{negint/0/max}": { + In: withDefiniteMap([]byte{1<<5 | 23}), + Expect: Map{"foo": NegInt(24)}, + }, + "{negint/1/min}": { + In: withDefiniteMap([]byte{1<<5 | 24, 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{negint/1/max}": { + In: withDefiniteMap([]byte{1<<5 | 24, 0xff}), + Expect: Map{"foo": NegInt(0x100)}, + }, + "{negint/2/min}": { + In: withDefiniteMap([]byte{1<<5 | 25, 0, 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{negint/2/max}": { + In: withDefiniteMap([]byte{1<<5 | 25, 0xff, 0xff}), + Expect: Map{"foo": NegInt(0x10000)}, + }, + "{negint/4/min}": { + In: withDefiniteMap([]byte{1<<5 | 26, 0, 0, 0, 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{negint/4/max}": { + In: withDefiniteMap([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Expect: Map{"foo": NegInt(0x100000000)}, + }, + "{negint/8/min}": { + In: withDefiniteMap([]byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{negint/8/max}": { + In: withDefiniteMap([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), + Expect: Map{"foo": NegInt(0xffffffff_ffffffff)}, + }, + "{true}": { + In: withDefiniteMap([]byte{7<<5 | major7True}), + Expect: Map{"foo": Bool(true)}, + }, + "{false}": { + In: withDefiniteMap([]byte{7<<5 | major7False}), + Expect: Map{"foo": Bool(false)}, + }, + "{null}": { + In: withDefiniteMap([]byte{7<<5 | major7Nil}), + Expect: Map{"foo": &Nil{}}, + }, + "{undefined}": { + In: withDefiniteMap([]byte{7<<5 | major7Undefined}), + Expect: Map{"foo": &Undefined{}}, + }, + "{float16/+Inf}": { + In: withDefiniteMap([]byte{7<<5 | major7Float16, 0x7c, 0}), + Expect: Map{"foo": Float32(math.Float32frombits(0x7f800000))}, + }, + "{float16/-Inf}": { + In: withDefiniteMap([]byte{7<<5 | major7Float16, 0xfc, 0}), + Expect: Map{"foo": Float32(math.Float32frombits(0xff800000))}, + }, + "{float16/NaN/MSB}": { + In: withDefiniteMap([]byte{7<<5 | major7Float16, 0x7e, 0}), + Expect: Map{"foo": Float32(math.Float32frombits(0x7fc00000))}, + }, + "{float16/NaN/LSB}": { + In: withDefiniteMap([]byte{7<<5 | major7Float16, 0x7c, 1}), + Expect: Map{"foo": Float32(math.Float32frombits(0x7f802000))}, + }, + "{float32}": { + In: withDefiniteMap([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), + Expect: Map{"foo": Float32(math.Float32frombits(0x7f800000))}, + }, + "{float64}": { + In: withDefiniteMap([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), + Expect: Map{"foo": Float64(math.Float64frombits(0x7ff00000_00000000))}, + }, + "{_ uint/0/min}": { + In: withIndefiniteMap([]byte{0<<5 | 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{_ uint/0/max}": { + In: withIndefiniteMap([]byte{0<<5 | 23}), + Expect: Map{"foo": Uint(23)}, + }, + "{_ uint/1/min}": { + In: withIndefiniteMap([]byte{0<<5 | 24, 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{_ uint/1/max}": { + In: withIndefiniteMap([]byte{0<<5 | 24, 0xff}), + Expect: Map{"foo": Uint(0xff)}, + }, + "{_ uint/2/min}": { + In: withIndefiniteMap([]byte{0<<5 | 25, 0, 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{_ uint/2/max}": { + In: withIndefiniteMap([]byte{0<<5 | 25, 0xff, 0xff}), + Expect: Map{"foo": Uint(0xffff)}, + }, + "{_ uint/4/min}": { + In: withIndefiniteMap([]byte{0<<5 | 26, 0, 0, 0, 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{_ uint/4/max}": { + In: withIndefiniteMap([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Expect: Map{"foo": Uint(0xffffffff)}, + }, + "{_ uint/8/min}": { + In: withIndefiniteMap([]byte{0<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), + Expect: Map{"foo": Uint(0)}, + }, + "{_ uint/8/max}": { + In: withIndefiniteMap([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), + Expect: Map{"foo": Uint(0xffffffff_ffffffff)}, + }, + "{_ negint/0/min}": { + In: withIndefiniteMap([]byte{1<<5 | 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{_ negint/0/max}": { + In: withIndefiniteMap([]byte{1<<5 | 23}), + Expect: Map{"foo": NegInt(24)}, + }, + "{_ negint/1/min}": { + In: withIndefiniteMap([]byte{1<<5 | 24, 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{_ negint/1/max}": { + In: withIndefiniteMap([]byte{1<<5 | 24, 0xff}), + Expect: Map{"foo": NegInt(0x100)}, + }, + "{_ negint/2/min}": { + In: withIndefiniteMap([]byte{1<<5 | 25, 0, 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{_ negint/2/max}": { + In: withIndefiniteMap([]byte{1<<5 | 25, 0xff, 0xff}), + Expect: Map{"foo": NegInt(0x10000)}, + }, + "{_ negint/4/min}": { + In: withIndefiniteMap([]byte{1<<5 | 26, 0, 0, 0, 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{_ negint/4/max}": { + In: withIndefiniteMap([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Expect: Map{"foo": NegInt(0x100000000)}, + }, + "{_ negint/8/min}": { + In: withIndefiniteMap([]byte{1<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0}), + Expect: Map{"foo": NegInt(1)}, + }, + "{_ negint/8/max}": { + In: withIndefiniteMap([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), + Expect: Map{"foo": NegInt(0xffffffff_ffffffff)}, + }, + "{_ true}": { + In: withIndefiniteMap([]byte{7<<5 | major7True}), + Expect: Map{"foo": Bool(true)}, + }, + "{_ false}": { + In: withIndefiniteMap([]byte{7<<5 | major7False}), + Expect: Map{"foo": Bool(false)}, + }, + "{_ null}": { + In: withIndefiniteMap([]byte{7<<5 | major7Nil}), + Expect: Map{"foo": &Nil{}}, + }, + "{_ undefined}": { + In: withIndefiniteMap([]byte{7<<5 | major7Undefined}), + Expect: Map{"foo": &Undefined{}}, + }, + "{_ float16/+Inf}": { + In: withIndefiniteMap([]byte{7<<5 | major7Float16, 0x7c, 0}), + Expect: Map{"foo": Float32(math.Float32frombits(0x7f800000))}, + }, + "{_ float16/-Inf}": { + In: withIndefiniteMap([]byte{7<<5 | major7Float16, 0xfc, 0}), + Expect: Map{"foo": Float32(math.Float32frombits(0xff800000))}, + }, + "{_ float16/NaN/MSB}": { + In: withIndefiniteMap([]byte{7<<5 | major7Float16, 0x7e, 0}), + Expect: Map{"foo": Float32(math.Float32frombits(0x7fc00000))}, + }, + "{_ float16/NaN/LSB}": { + In: withIndefiniteMap([]byte{7<<5 | major7Float16, 0x7c, 1}), + Expect: Map{"foo": Float32(math.Float32frombits(0x7f802000))}, + }, + "{_ float32}": { + In: withIndefiniteMap([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), + Expect: Map{"foo": Float32(math.Float32frombits(0x7f800000))}, + }, + "{_ float64}": { + In: withIndefiniteMap([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), + Expect: Map{"foo": Float64(math.Float64frombits(0x7ff00000_00000000))}, + }, + } { + t.Run(name, func(t *testing.T) { + actual, n, err := decode(c.In) + if err != nil { + t.Errorf("expect no err, got %v", err) + } + if n != len(c.In) { + t.Errorf("didn't decode whole buffer (decoded %d of %d)", n, len(c.In)) + } + assertValue(t, c.Expect, actual) + }) + } +} + +func TestDecode_Tag(t *testing.T) { + for name, c := range map[string]struct { + In []byte + Expect Value + }{ + "0/min": { + In: []byte{6<<5 | 0, 1}, + Expect: &Tag{0, Uint(1)}, + }, + "0/max": { + In: []byte{6<<5 | 23, 1}, + Expect: &Tag{23, Uint(1)}, + }, + "1/min": { + In: []byte{6<<5 | 24, 0, 1}, + Expect: &Tag{0, Uint(1)}, + }, + "1/max": { + In: []byte{6<<5 | 24, 0xff, 1}, + Expect: &Tag{0xff, Uint(1)}, + }, + "2/min": { + In: []byte{6<<5 | 25, 0, 0, 1}, + Expect: &Tag{0, Uint(1)}, + }, + "2/max": { + In: []byte{6<<5 | 25, 0xff, 0xff, 1}, + Expect: &Tag{0xffff, Uint(1)}, + }, + "4/min": { + In: []byte{6<<5 | 26, 0, 0, 0, 0, 1}, + Expect: &Tag{0, Uint(1)}, + }, + "4/max": { + In: []byte{6<<5 | 26, 0xff, 0xff, 0xff, 0xff, 1}, + Expect: &Tag{0xffffffff, Uint(1)}, + }, + "8/min": { + In: []byte{6<<5 | 27, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + Expect: &Tag{0, Uint(1)}, + }, + "8/max": { + In: []byte{6<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1}, + Expect: &Tag{0xffffffff_ffffffff, Uint(1)}, + }, + } { + t.Run(name, func(t *testing.T) { + actual, n, err := decode(c.In) + if err != nil { + t.Errorf("expect no err, got %v", err) + } + if n != len(c.In) { + t.Errorf("didn't decode whole buffer (decoded %d of %d)", n, len(c.In)) + } + assertValue(t, c.Expect, actual) + }) + } +} + +func assertValue(t *testing.T, e, a Value) { + switch v := e.(type) { + case Uint, NegInt, Slice, String, Bool, *Nil, *Undefined: + if !reflect.DeepEqual(e, a) { + t.Errorf("%v != %v", e, a) + } + case List: + assertList(t, v, a) + case Map: + assertMap(t, v, a) + case *Tag: + assertTag(t, v, a) + case Float32: + assertMajor7Float32(t, v, a) + case Float64: + assertMajor7Float64(t, v, a) + default: + t.Errorf("unrecognized variant %T", e) + } +} + +func assertList(t *testing.T, e List, a Value) { + av, ok := a.(List) + if !ok { + t.Errorf("%T != %T", e, a) + return + } + + if len(e) != len(av) { + t.Errorf("length %d != %d", len(e), len(av)) + return + } + + for i := 0; i < len(e); i++ { + assertValue(t, e[i], av[i]) + } +} + +func assertMap(t *testing.T, e Map, a Value) { + av, ok := a.(Map) + if !ok { + t.Errorf("%T != %T", e, a) + return + } + + if len(e) != len(av) { + t.Errorf("length %d != %d", len(e), len(av)) + return + } + + for k, ev := range e { + avv, ok := av[k] + if !ok { + t.Errorf("missing key %s", k) + return + } + + assertValue(t, ev, avv) + } +} + +func assertTag(t *testing.T, e *Tag, a Value) { + av, ok := a.(*Tag) + if !ok { + t.Errorf("%T != %T", e, a) + return + } + + if e.ID != av.ID { + t.Errorf("tag ID %d != %d", e.ID, av.ID) + return + } + + assertValue(t, e.Value, av.Value) +} + +func assertMajor7Float32(t *testing.T, e Float32, a Value) { + av, ok := a.(Float32) + if !ok { + t.Errorf("%T != %T", e, a) + return + } + + if math.Float32bits(float32(e)) != math.Float32bits(float32(av)) { + t.Errorf("float32(%x) != float32(%x)", e, av) + } +} + +func assertMajor7Float64(t *testing.T, e Float64, a Value) { + av, ok := a.(Float64) + if !ok { + t.Errorf("%T != %T", e, a) + return + } + + if math.Float64bits(float64(e)) != math.Float64bits(float64(av)) { + t.Errorf("float64(%x) != float64(%x)", e, av) + } +} + +var mapKeyFoo = []byte{0x63, 0x66, 0x6f, 0x6f} + +func withDefiniteList(p []byte) []byte { + return append([]byte{4<<5 | 1}, p...) +} + +func withIndefiniteList(p []byte) []byte { + p = append([]byte{4<<5 | 31}, p...) + return append(p, 0xff) +} + +func withDefiniteMap(p []byte) []byte { + head := append([]byte{5<<5 | 1}, mapKeyFoo...) + return append(head, p...) +} + +func withIndefiniteMap(p []byte) []byte { + head := append([]byte{5<<5 | 31}, mapKeyFoo...) + p = append(head, p...) + return append(p, 0xff) +} diff --git a/encoding/cbor/encode.go b/encoding/cbor/encode.go new file mode 100644 index 000000000..2cbf077cf --- /dev/null +++ b/encoding/cbor/encode.go @@ -0,0 +1,175 @@ +package cbor + +import ( + "encoding/binary" + "math" +) + +func (i Uint) len() int { + return itoarglen(uint64(i)) +} + +func (i Uint) encode(p []byte) int { + return encodeArg(majorTypeUint, uint64(i), p) +} + +func (i NegInt) len() int { + return itoarglen(uint64(i) - 1) +} + +func (i NegInt) encode(p []byte) int { + return encodeArg(majorTypeNegInt, uint64(i-1), p) +} + +func (s Slice) len() int { + return itoarglen(len(s)) + len(s) +} + +func (s Slice) encode(p []byte) int { + off := encodeArg(majorTypeSlice, len(s), p) + copy(p[off:], []byte(s)) + return off + len(s) +} + +func (s String) len() int { + return itoarglen(len(s)) + len(s) +} + +func (s String) encode(p []byte) int { + off := encodeArg(majorTypeString, len(s), p) + copy(p[off:], []byte(s)) + return off + len(s) +} + +func (l List) len() int { + total := itoarglen(len(l)) + for _, v := range l { + total += v.len() + } + return total +} + +func (l List) encode(p []byte) int { + off := encodeArg(majorTypeList, len(l), p) + for _, v := range l { + off += v.encode(p[off:]) + } + return off +} + +func (m Map) len() int { + total := itoarglen(len(m)) + for k, v := range m { + total += String(k).len() + v.len() + } + return total +} + +func (m Map) encode(p []byte) int { + off := encodeArg(majorTypeMap, len(m), p) + for k, v := range m { + off += String(k).encode(p[off:]) + off += v.encode(p[off:]) + } + return off +} + +func (t Tag) len() int { + return itoarglen(t.ID) + t.Value.len() +} + +func (t Tag) encode(p []byte) int { + off := encodeArg(majorTypeTag, t.ID, p) + return off + t.Value.encode(p[off:]) +} + +func (b Bool) len() int { + return 1 +} + +func (b Bool) encode(p []byte) int { + if b { + p[0] = compose(majorType7, major7True) + } else { + p[0] = compose(majorType7, major7False) + } + return 1 +} + +func (*Nil) len() int { + return 1 +} + +func (*Nil) encode(p []byte) int { + p[0] = compose(majorType7, major7Nil) + return 1 +} + +func (*Undefined) len() int { + return 1 +} + +func (*Undefined) encode(p []byte) int { + p[0] = compose(majorType7, major7Undefined) + return 1 +} + +func (f Float32) len() int { + return 5 +} + +func (f Float32) encode(p []byte) int { + p[0] = compose(majorType7, major7Float32) + binary.BigEndian.PutUint32(p[1:], math.Float32bits(float32(f))) + return 5 +} + +func (f Float64) len() int { + return 9 +} + +func (f Float64) encode(p []byte) int { + p[0] = compose(majorType7, major7Float64) + binary.BigEndian.PutUint64(p[1:], math.Float64bits(float64(f))) + return 5 +} + +func compose(major majorType, minor byte) byte { + return byte(major)<<5 | minor +} + +func itoarglen[I int | uint64](v I) int { + if v < 24 { + return 1 // type and len in single byte + } else if v < 0x100 { + return 2 // type + 1-byte len + } else if v < 0x10000 { + return 3 // type + 2-byte len + } else if v < 0x100000000 { + return 5 // type + 4-byte len + } + return 9 // type + 8-byte len +} + +func encodeArg[I int | uint64](t majorType, arg I, p []byte) int { + if arg < 24 { + p[0] = byte(t)<<5 | byte(arg) + return 1 + } else if arg < 0x100 { + p[0] = compose(t, minorArg1) + p[1] = byte(arg) + return 2 + } else if arg < 0x10000 { + p[0] = compose(t, minorArg2) + binary.BigEndian.PutUint16(p[1:], uint16(arg)) + return 3 + } else if arg < 0x100000000 { + p[0] = compose(t, minorArg4) + binary.BigEndian.PutUint32(p[1:], uint32(arg)) + return 5 + } + + p[0] = compose(t, minorArg8) + binary.BigEndian.PutUint64(p[1:], uint64(arg)) + return 9 +} diff --git a/encoding/cbor/encode_test.go b/encoding/cbor/encode_test.go new file mode 100644 index 000000000..204ee26d0 --- /dev/null +++ b/encoding/cbor/encode_test.go @@ -0,0 +1,466 @@ +package cbor + +import ( + "bytes" + "encoding/hex" + "math" + "testing" +) + +func TestEncode_Atomic(t *testing.T) { + for name, c := range map[string]struct { + Expect []byte + In Value + }{ + "uint/0/min": { + []byte{0<<5 | 0}, + Uint(0), + }, + "uint/0/max": { + []byte{0<<5 | 23}, + Uint(23), + }, + "uint/1/min": { + []byte{0<<5 | 24, 24}, + Uint(24), + }, + "uint/1/max": { + []byte{0<<5 | 24, 0xff}, + Uint(0xff), + }, + "uint/2/min": { + []byte{0<<5 | 25, 1, 0}, + Uint(0x100), + }, + "uint/2/max": { + []byte{0<<5 | 25, 0xff, 0xff}, + Uint(0xffff), + }, + "uint/4/min": { + []byte{0<<5 | 26, 1, 0, 0, 0}, + Uint(0x1000000), + }, + "uint/4/max": { + []byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}, + Uint(0xffffffff), + }, + "uint/8/min": { + []byte{0<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}, + Uint(0x1000000_00000000), + }, + "uint/8/max": { + []byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + Uint(0xffffffff_ffffffff), + }, + "negint/0/min": { + []byte{1<<5 | 0}, + NegInt(1), + }, + "negint/0/max": { + []byte{1<<5 | 23}, + NegInt(24), + }, + "negint/1/min": { + []byte{1<<5 | 24, 24}, + NegInt(25), + }, + "negint/1/max": { + []byte{1<<5 | 24, 0xff}, + NegInt(0x100), + }, + "negint/2/min": { + []byte{1<<5 | 25, 1, 0}, + NegInt(0x101), + }, + "negint/2/max": { + []byte{1<<5 | 25, 0xff, 0xff}, + NegInt(0x10000), + }, + "negint/4/min": { + []byte{1<<5 | 26, 1, 0, 0, 0}, + NegInt(0x1000001), + }, + "negint/4/max": { + []byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}, + NegInt(0x100000000), + }, + "negint/8/min": { + []byte{1<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}, + NegInt(0x1000000_00000001), + }, + "negint/8/max": { + []byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}, + NegInt(0xffffffff_ffffffff), + }, + "true": { + []byte{7<<5 | major7True}, + Bool(true), + }, + "false": { + []byte{7<<5 | major7False}, + Bool(false), + }, + "null": { + []byte{7<<5 | major7Nil}, + &Nil{}, + }, + "undefined": { + []byte{7<<5 | major7Undefined}, + &Undefined{}, + }, + "float32": { + []byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}, + Float32(math.Float32frombits(0x7f800000)), + }, + "float64": { + []byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}, + Float64(math.Float64frombits(0x7ff00000_00000000)), + }, + } { + t.Run(name, func(t *testing.T) { + actual := Encode(c.In) + if !bytes.Equal(c.Expect, actual) { + t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) + } + }) + } +} + +func TestEncode_Slice(t *testing.T) { + for name, c := range map[string]struct { + Expect []byte + In Value + }{ + "len = 0": { + []byte{2<<5 | 0}, + Slice{}, + }, + "len > 0": { + []byte{2<<5 | 3, 0x66, 0x6f, 0x6f}, + Slice{0x66, 0x6f, 0x6f}, + }, + } { + t.Run(name, func(t *testing.T) { + actual := Encode(c.In) + if !bytes.Equal(c.Expect, actual) { + t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) + } + }) + } +} + +func TestEncode_String(t *testing.T) { + for name, c := range map[string]struct { + Expect []byte + In Value + }{ + "len = 0": { + []byte{3<<5 | 0}, + String(""), + }, + "len > 0": { + []byte{3<<5 | 3, 0x66, 0x6f, 0x6f}, + String("foo"), + }, + } { + t.Run(name, func(t *testing.T) { + actual := Encode(c.In) + if !bytes.Equal(c.Expect, actual) { + t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) + } + }) + } +} + +func TestEncode_List(t *testing.T) { + for name, c := range map[string]struct { + Expect []byte + In Value + }{ + "[uint/0/min]": { + withDefiniteList([]byte{0<<5 | 0}), + List{Uint(0)}, + }, + "[uint/0/max]": { + withDefiniteList([]byte{0<<5 | 23}), + List{Uint(23)}, + }, + "[uint/1/min]": { + withDefiniteList([]byte{0<<5 | 24, 24}), + List{Uint(24)}, + }, + "[uint/1/max]": { + withDefiniteList([]byte{0<<5 | 24, 0xff}), + List{Uint(0xff)}, + }, + "[uint/2/min]": { + withDefiniteList([]byte{0<<5 | 25, 1, 0}), + List{Uint(0x100)}, + }, + "[uint/2/max]": { + withDefiniteList([]byte{0<<5 | 25, 0xff, 0xff}), + List{Uint(0xffff)}, + }, + "[uint/4/min]": { + withDefiniteList([]byte{0<<5 | 26, 1, 0, 0, 0}), + List{Uint(0x1000000)}, + }, + "[uint/4/max]": { + withDefiniteList([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + List{Uint(0xffffffff)}, + }, + "[uint/8/min]": { + withDefiniteList([]byte{0<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}), + List{Uint(0x1000000_00000000)}, + }, + "[uint/8/max]": { + withDefiniteList([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), + List{Uint(0xffffffff_ffffffff)}, + }, + "[negint/0/min]": { + withDefiniteList([]byte{1<<5 | 0}), + List{NegInt(1)}, + }, + "[negint/0/max]": { + withDefiniteList([]byte{1<<5 | 23}), + List{NegInt(24)}, + }, + "[negint/1/min]": { + withDefiniteList([]byte{1<<5 | 24, 24}), + List{NegInt(25)}, + }, + "[negint/1/max]": { + withDefiniteList([]byte{1<<5 | 24, 0xff}), + List{NegInt(0x100)}, + }, + "[negint/2/min]": { + withDefiniteList([]byte{1<<5 | 25, 1, 0}), + List{NegInt(0x101)}, + }, + "[negint/2/max]": { + withDefiniteList([]byte{1<<5 | 25, 0xff, 0xff}), + List{NegInt(0x10000)}, + }, + "[negint/4/min]": { + withDefiniteList([]byte{1<<5 | 26, 1, 0, 0, 0}), + List{NegInt(0x1000001)}, + }, + "[negint/4/max]": { + withDefiniteList([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + List{NegInt(0x100000000)}, + }, + "[negint/8/min]": { + withDefiniteList([]byte{1<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}), + List{NegInt(0x1000000_00000001)}, + }, + "[negint/8/max]": { + withDefiniteList([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), + List{NegInt(0xffffffff_ffffffff)}, + }, + "[true]": { + withDefiniteList([]byte{7<<5 | major7True}), + List{Bool(true)}, + }, + "[false]": { + withDefiniteList([]byte{7<<5 | major7False}), + List{Bool(false)}, + }, + "[null]": { + withDefiniteList([]byte{7<<5 | major7Nil}), + List{&Nil{}}, + }, + "[undefined]": { + withDefiniteList([]byte{7<<5 | major7Undefined}), + List{&Undefined{}}, + }, + "[float32]": { + withDefiniteList([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), + List{Float32(math.Float32frombits(0x7f800000))}, + }, + "[float64]": { + withDefiniteList([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), + List{Float64(math.Float64frombits(0x7ff00000_00000000))}, + }, + } { + t.Run(name, func(t *testing.T) { + actual := Encode(c.In) + if !bytes.Equal(c.Expect, actual) { + t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) + } + }) + } +} + +func TestEncode_Map(t *testing.T) { + for name, c := range map[string]struct { + Expect []byte + In Value + }{ + "{uint/0/min}": { + withDefiniteMap([]byte{0<<5 | 0}), + Map{"foo": Uint(0)}, + }, + "{uint/0/max}": { + withDefiniteMap([]byte{0<<5 | 23}), + Map{"foo": Uint(23)}, + }, + "{uint/1/min}": { + withDefiniteMap([]byte{0<<5 | 24, 24}), + Map{"foo": Uint(24)}, + }, + "{uint/1/max}": { + withDefiniteMap([]byte{0<<5 | 24, 0xff}), + Map{"foo": Uint(0xff)}, + }, + "{uint/2/min}": { + withDefiniteMap([]byte{0<<5 | 25, 1, 0}), + Map{"foo": Uint(0x100)}, + }, + "{uint/2/max}": { + withDefiniteMap([]byte{0<<5 | 25, 0xff, 0xff}), + Map{"foo": Uint(0xffff)}, + }, + "{uint/4/min}": { + withDefiniteMap([]byte{0<<5 | 26, 1, 0, 0, 0}), + Map{"foo": Uint(0x1000000)}, + }, + "{uint/4/max}": { + withDefiniteMap([]byte{0<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Map{"foo": Uint(0xffffffff)}, + }, + "{uint/8/min}": { + withDefiniteMap([]byte{0<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}), + Map{"foo": Uint(0x1000000_00000000)}, + }, + "{uint/8/max}": { + withDefiniteMap([]byte{0<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}), + Map{"foo": Uint(0xffffffff_ffffffff)}, + }, + "{negint/0/min}": { + withDefiniteMap([]byte{1<<5 | 0}), + Map{"foo": NegInt(1)}, + }, + "{negint/0/max}": { + withDefiniteMap([]byte{1<<5 | 23}), + Map{"foo": NegInt(24)}, + }, + "{negint/1/min}": { + withDefiniteMap([]byte{1<<5 | 24, 24}), + Map{"foo": NegInt(25)}, + }, + "{negint/1/max}": { + withDefiniteMap([]byte{1<<5 | 24, 0xff}), + Map{"foo": NegInt(0x100)}, + }, + "{negint/2/min}": { + withDefiniteMap([]byte{1<<5 | 25, 1, 0}), + Map{"foo": NegInt(0x101)}, + }, + "{negint/2/max}": { + withDefiniteMap([]byte{1<<5 | 25, 0xff, 0xff}), + Map{"foo": NegInt(0x10000)}, + }, + "{negint/4/min}": { + withDefiniteMap([]byte{1<<5 | 26, 1, 0, 0, 0}), + Map{"foo": NegInt(0x1000001)}, + }, + "{negint/4/max}": { + withDefiniteMap([]byte{1<<5 | 26, 0xff, 0xff, 0xff, 0xff}), + Map{"foo": NegInt(0x100000000)}, + }, + "{negint/8/min}": { + withDefiniteMap([]byte{1<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0}), + Map{"foo": NegInt(0x1000000_00000001)}, + }, + "{negint/8/max}": { + withDefiniteMap([]byte{1<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}), + Map{"foo": NegInt(0xffffffff_ffffffff)}, + }, + "{true}": { + withDefiniteMap([]byte{7<<5 | major7True}), + Map{"foo": Bool(true)}, + }, + "{false}": { + withDefiniteMap([]byte{7<<5 | major7False}), + Map{"foo": Bool(false)}, + }, + "{null}": { + withDefiniteMap([]byte{7<<5 | major7Nil}), + Map{"foo": &Nil{}}, + }, + "{undefined}": { + withDefiniteMap([]byte{7<<5 | major7Undefined}), + Map{"foo": &Undefined{}}, + }, + "{float32}": { + withDefiniteMap([]byte{7<<5 | major7Float32, 0x7f, 0x80, 0, 0}), + Map{"foo": Float32(math.Float32frombits(0x7f800000))}, + }, + "{float64}": { + withDefiniteMap([]byte{7<<5 | major7Float64, 0x7f, 0xf0, 0, 0, 0, 0, 0, 0}), + Map{"foo": Float64(math.Float64frombits(0x7ff00000_00000000))}, + }, + } { + t.Run(name, func(t *testing.T) { + actual := Encode(c.In) + if !bytes.Equal(c.Expect, actual) { + t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) + } + }) + } +} + +func TestEncode_Tag(t *testing.T) { + for name, c := range map[string]struct { + Expect []byte + In Value + }{ + "0/min": { + []byte{6<<5 | 0, 1}, + &Tag{0, Uint(1)}, + }, + "0/max": { + []byte{6<<5 | 23, 1}, + &Tag{23, Uint(1)}, + }, + "1/min": { + []byte{6<<5 | 24, 24, 1}, + &Tag{24, Uint(1)}, + }, + "1/max": { + []byte{6<<5 | 24, 0xff, 1}, + &Tag{0xff, Uint(1)}, + }, + "2/min": { + []byte{6<<5 | 25, 1, 0, 1}, + &Tag{0x100, Uint(1)}, + }, + "2/max": { + []byte{6<<5 | 25, 0xff, 0xff, 1}, + &Tag{0xffff, Uint(1)}, + }, + "4/min": { + []byte{6<<5 | 26, 1, 0, 0, 0, 1}, + &Tag{0x1000000, Uint(1)}, + }, + "4/max": { + []byte{6<<5 | 26, 0xff, 0xff, 0xff, 0xff, 1}, + &Tag{0xffffffff, Uint(1)}, + }, + "8/min": { + []byte{6<<5 | 27, 1, 0, 0, 0, 0, 0, 0, 0, 1}, + &Tag{0x1000000_00000000, Uint(1)}, + }, + "8/max": { + []byte{6<<5 | 27, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1}, + &Tag{0xffffffff_ffffffff, Uint(1)}, + }, + } { + t.Run(name, func(t *testing.T) { + actual := Encode(c.In) + if !bytes.Equal(c.Expect, actual) { + t.Errorf("bytes not equal (%s != %s)", hex.EncodeToString(c.Expect), hex.EncodeToString(actual)) + } + }) + } +} diff --git a/encoding/cbor/float16.go b/encoding/cbor/float16.go new file mode 100644 index 000000000..eea42eed4 --- /dev/null +++ b/encoding/cbor/float16.go @@ -0,0 +1,45 @@ +package cbor + +func float16to32(f uint16) uint32 { + sign, exp, mant := splitf16(f) + if exp == 0x1f { + return sign | 0xff<<23 | mant // infinity/NaN + } + + if exp == 0 { // subnormal + if mant == 0 { + return sign + } + return normalize(sign, mant) + } + + return sign | (exp+127-15)<<23 | mant // rebias exp by the difference between the two +} + +func splitf16(f uint16) (sign, exp, mantissa uint32) { + const smask = 0x1 << 15 // put sign in float32 position + const emask = 0x1f << 10 // pull exponent as a number (for bias shift) + const mmask = 0x3ff // put mantissa in float32 position + + return uint32(f&smask) << 16, uint32(f&emask) >> 10, uint32(f&mmask) << 13 +} + +// moves a float16 normal into normal float32 space +// to do this we must re-express the float16 mantissa in terms of a normal +// float32 where the hidden bit is 1, e.g. +// +// f16: 0 00000 0001010000 = 0.000101 * 2^(-14), which is equal to +// f32: 0 01101101 01000000000000000000000 = 1.01 * 2^(-18) +// +// this is achieved by shifting the mantissa to the right until the leading bit +// that == 1 reaches position 24, then the number of positions shifted over is +// equal to the offset from the subnormal exponent +func normalize(sign, mant uint32) uint32 { + exp := (uint32(-14 + 127)) // f16 subnormal exp, with f32 bias + for mant&0x800000 == 0 { // repeat until bit 24 ("hidden" mantissa) is 1 + mant <<= 1 + exp-- // tracking the offset + } + mant &= 0x7fffff // remask to 23bit + return sign | exp<<23 | mant +} diff --git a/encoding/cbor/float16_test.go b/encoding/cbor/float16_test.go new file mode 100644 index 000000000..ef97f3acc --- /dev/null +++ b/encoding/cbor/float16_test.go @@ -0,0 +1,41 @@ +package cbor + +import ( + "testing" +) + +func TestFloat16To32(t *testing.T) { + for name, c := range map[string]struct { + In uint16 + Expect uint32 + }{ + "+infinity": { + 0b0_11111_0000000000, + 0b0_11111111_00000000000000000000000, + }, + "-infinity": { + 0b1_11111_0000000000, + 0b1_11111111_00000000000000000000000, + }, + "NaN": { + 0b0_11111_0101010101, + 0b0_11111111_01010101010000000000000, + }, + "absolute zero": {0, 0}, + "subnormal": { + 0b0_00000_0001010000, + 0b0_01101101_01000000000000000000000, + }, + "normal": { + 0b0_00001_0001010000, + 0b0_0001110001_00010100000000000000000, + }, + } { + t.Run(name, func(t *testing.T) { + if actual := float16to32(c.In); c.Expect != actual { + t.Errorf("%x != %x", c.Expect, actual) + } + }) + } + +} diff --git a/encoding/cbor/fuzz_test.go b/encoding/cbor/fuzz_test.go new file mode 100644 index 000000000..03dd1e231 --- /dev/null +++ b/encoding/cbor/fuzz_test.go @@ -0,0 +1,114 @@ +//go:build fuzz +// +build fuzz + +package cbor + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "testing" +) + +// caught by fuzz: +// - broken typecast from uint64 to int when checking encoded string(mt2,3) length vs buflen +// - huge encoded list/map sizes would cause panics on make() +// - map declaration at end of buffer would attempt to peek p[0] when len(p) == 0 + +func TestDecode_Fuzz(t *testing.T) { + const runs = 1_000_000 + const buflen = 512 + + p := make([]byte, buflen) + + defer func() { + if err := recover(); err != nil { + fmt.Println(hex.EncodeToString(p)) + dump(p) + + t.Fatalf("decode panic: %v\n", err) + } + }() + + for i := 0; i < runs; i++ { + if _, err := rand.Read(p); err != nil { + t.Fatalf("create randbuf: %v", err) + } + + decode(p) + } +} + +func dump(p []byte) { + for len(p) > 0 { + var off int + + major, minor := peekMajor(p), peekMinor(p) + switch major { + case majorTypeUint, majorTypeNegInt, majorType7: + if minor > 27 { + fmt.Printf("%d, %d (invalid)\n", major, minor) + return + } + + arg, n, err := decodeArgument(p) + if err != nil { + panic(err) + } + + fmt.Printf("%d, %d\n", major, arg) + off = n + case majorTypeSlice, majorTypeString: + if minor == 31 { + panic("todo") + } else if minor > 27 { + fmt.Printf("%d, %d (invalid)\n", major, minor) + return + } + + arg, n, err := decodeArgument(p) + if err != nil { + panic(err) + } + + fmt.Printf("str(%d), len %d\n", major, arg) + off = n + int(arg) + case majorTypeList, majorTypeMap: + if minor == 31 { + panic("todo") + } else if minor > 27 { + fmt.Printf("%d, %d (invalid)\n", major, minor) + return + } + + arg, n, err := decodeArgument(p) + if err != nil { + panic(err) + } + + fmt.Printf("container(%d), len %d\n", major, arg) + off = n + case majorTypeTag: + if minor > 27 { + fmt.Printf("tag, %d (invalid)\n", minor) + return + } + + arg, n, err := decodeArgument(p) + if err != nil { + panic(err) + } + + fmt.Printf("tag, %d\n", arg) + off = n + } + + if off > len(p) { + fmt.Println("overflow, stop") + return + } + p = p[off:] + } + + fmt.Println("EOF") +}