You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/codec/codec.go

145 lines
5.1 KiB

// 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.
//
// The CBOR profile of the protocol (spec §58: major types 0, 2, 3, 4 and 5
// only, unsigned integer map keys) is enforced by decoding into the typed
// schemas of each package: their fields are unsigned integers, byte strings,
// text strings, arrays and maps, so negative integers, floats and simple
// values fail to decode, and null fails the re-encoding check.
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
)
// MaxSafeUint is 2^53-1, the largest unsigned integer any schema of the
// protocol allows, so that every integer is exact as an IEEE 754 double
// (spec §58).
const MaxSafeUint = 1<<53 - 1
var (
encMode = must(encOptions().EncMode())
decMode = must(decOptions(true).DecMode())
peekMode = must(decOptions(false).DecMode())
)
// encOptions returns Core Deterministic Encoding options in which a nil byte
// string, array or map encodes as an empty one, never as null: null is outside
// the profile of spec §58, so an input null never survives the re-encoding
// check.
func encOptions() cbor.EncOptions {
o := cbor.CoreDetEncOptions()
o.NilContainers = cbor.NilContainerAsEmpty
return o
}
// 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; the caller must validate them.
//
// The re-encoding equals data on success, so it may hold secrets such as
// I_PAYLOAD or access_material; it is wiped on every path. This is best
// effort: the encoder's internal buffer may keep a copy.
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)
}
re, err := encMode.Marshal(v)
defer clear(re)
if 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
}

Powered by TurnKey Linux.