Skip to content

Commit

Permalink
feat: rpc2: cbor codec (#492)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucix-aws committed Mar 1, 2024
1 parent 5893827 commit 009f4b4
Show file tree
Hide file tree
Showing 9 changed files with 2,675 additions and 0 deletions.
139 changes: 139 additions & 0 deletions encoding/cbor/cbor.go
Original file line number Diff line number Diff line change
@@ -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
}
41 changes: 41 additions & 0 deletions encoding/cbor/const.go
Original file line number Diff line number Diff line change
@@ -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
)
Loading

0 comments on commit 009f4b4

Please sign in to comment.