// Package codec implements the Deterministic CBOR rules of spec §58 and §58.1. // // Encoding uses RFC 8949 §4.2.1 Core Deterministic Encoding. Decoding is // strict (no indefinite lengths, no tags, no duplicate keys, bounded depth and // sizes, valid UTF-8, no unknown struct fields) and is always followed by a // re-encoding that must reproduce the input byte for byte. Any difference is // ErrNonCanonicalCBOR. The same principle as dk1_ canonicality (spec §19): // canonicality does not depend on a library promising to reject every // non-canonical form. package codec import ( "bytes" "fmt" "github.com/fxamacker/cbor/v2" datekeys "g.activething.com/go/DateKeys" ) // Decoding limits. Structural sizes are additionally bounded by the framing // limits of spec §57 before any CBOR is decoded. const ( MaxNestedLevels = 16 MaxArrayElements = 65536 MaxMapPairs = 65536 ) var ( encMode = must(cbor.CoreDetEncOptions().EncMode()) decMode = must(decOptions(true).DecMode()) peekMode = must(decOptions(false).DecMode()) ) // decOptions returns the strict decoding options. Peek mode ignores unknown // map keys; the canonical mode reports them. func decOptions(strict bool) cbor.DecOptions { o := cbor.DecOptions{ DupMapKey: cbor.DupMapKeyEnforcedAPF, IndefLength: cbor.IndefLengthForbidden, TagsMd: cbor.TagsForbidden, MaxNestedLevels: MaxNestedLevels, MaxArrayElements: MaxArrayElements, MaxMapPairs: MaxMapPairs, UTF8: cbor.UTF8RejectInvalid, MapKeyByteString: cbor.MapKeyByteStringAllowed, } if strict { o.ExtraReturnErrors = cbor.ExtraDecErrorUnknownField } return o } // must accepts only the static options above, which cannot be invalid. func must[T any](m T, err error) T { if err != nil { panic("codec: invalid static options: " + err.Error()) } return m } // Marshal returns the core deterministic CBOR encoding of v. func Marshal(v any) ([]byte, error) { b, err := encMode.Marshal(v) if err != nil { return nil, fmt.Errorf("codec: encode: %w", err) } return b, nil } // Unmarshal decodes exactly one CBOR data item from data into v, which must be // a pointer, and then requires that re-encoding v reproduces data exactly. // Every failure wraps datekeys.ErrNonCanonicalCBOR. // // Fields of type cbor.RawMessage are copied verbatim and are NOT covered by the // re-encoding check; callers must validate them with Valid. func Unmarshal(data []byte, v any) error { if err := decMode.Unmarshal(data, v); err != nil { return fmt.Errorf("codec: decode: %v: %w", err, datekeys.ErrNonCanonicalCBOR) } if re, err := encMode.Marshal(v); err != nil || !bytes.Equal(re, data) { return fmt.Errorf("codec: input is not the deterministic encoding of its value: %w", datekeys.ErrNonCanonicalCBOR) } return nil } // Peek decodes selected fields of a CBOR map, ignoring every other key and // without the canonicality check. It exists only to read a type tag and a // schema version before strict decoding, so that an unknown major version is // reported as such (spec §70). Its result must never be used as the decoded // object. func Peek(data []byte, v any) error { if err := peekMode.Unmarshal(data, v); err != nil { return fmt.Errorf("codec: decode: %v: %w", err, datekeys.ErrNonCanonicalCBOR) } return nil } // CheckSchema reads key 0 (type tag) and key 1 (schema version) of a CBOR map // and requires the expected values. A different type tag is // ErrNonCanonicalCBOR; a different version is ErrUnsupportedVersion. func CheckSchema(data []byte, typeTag string, version uint64) error { var h struct { Type string `cbor:"0,keyasint"` Version uint64 `cbor:"1,keyasint"` } if err := Peek(data, &h); err != nil { return err } if h.Type != typeTag { return fmt.Errorf("codec: type %q, want %q: %w", h.Type, typeTag, datekeys.ErrNonCanonicalCBOR) } if h.Version != version { return fmt.Errorf("codec: %s schema version %d, want %d: %w", typeTag, h.Version, version, datekeys.ErrUnsupportedVersion) } return nil } // Valid reports whether data is exactly one well-formed CBOR data item in core // deterministic encoding. It is used for opaque values the protocol does not // interpret, such as extension data (spec §54). Tags, the simple value // undefined and map keys that are arrays or maps are rejected. func Valid(data []byte) error { var v any return Unmarshal(data, &v) }