diff --git a/encoding/cbor/cbor.go b/encoding/cbor/cbor.go index e7973ad9e..fe8449a82 100644 --- a/encoding/cbor/cbor.go +++ b/encoding/cbor/cbor.go @@ -1,34 +1,50 @@ // Package cbor implements partial encoding/decoding of concise binary object -// representation (CBOR) described in RFC 8949. +// representation (CBOR) described in [RFC 8949]. // -// This package implements a subset of the specification required to support -// the Smithy RPCv2-CBOR protocol and is NOT suitable for general application -// use. +// 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. // -// As 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 encodings of container types (byte/text -// string, list, map). +// 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. // -// Conversely, the decoding API will handle both definite and indefinite -// variations of encoded containers. +// 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 +// - [Uint] +// - [NegInt] +// - [Slice] +// - [String] +// - [List] +// - [Map] +// - [Tag] +// - [Bool] +// - [Nil] +// - [Undefined] +// - [Float32] +// - [Float64] type Value interface { len() int encode(p []byte) int @@ -49,10 +65,20 @@ var ( _ Value = Float64(0) ) -// Uint describes a CBOR uint (major type 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). +// 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). diff --git a/encoding/cbor/float16.go b/encoding/cbor/float16.go index 8c3ae2caf..eea42eed4 100644 --- a/encoding/cbor/float16.go +++ b/encoding/cbor/float16.go @@ -29,7 +29,7 @@ func splitf16(f uint16) (sign, exp, mantissa uint32) { // float32 where the hidden bit is 1, e.g. // // f16: 0 00000 0001010000 = 0.000101 * 2^(-14), which is equal to -// f32: 0 00000000 00100000000000000000000 = 1.01 * 2^(-18) +// 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 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) + } + }) + } + +}