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

606 lines
17 KiB

// Package codec implements the CBOR profile of the DateKeys protocol (spec
// §58, §58.1) without reflection and without dependencies.
//
// The profile is Deterministic CBOR (RFC 8949 §4.2.1) restricted to major
// types 0 (unsigned integer), 2 (byte string), 3 (text string), 4 (array) and
// 5 (map), with unsigned integer map keys in strictly ascending order,
// integers and lengths in their shortest form, definite lengths only and
// valid UTF-8 text. Negative integers, tags, floats, simple values (false,
// true, null, undefined), indefinite lengths and every other map key are
// rejected with ErrNonCanonicalCBOR.
//
// Each schema writes its own encoding with an Encoder and reads it with a
// Decoder, a strict cursor that reads exactly what the schema asks for.
// Unmarshal runs the decoder of a schema and then re-encodes what it decoded:
// the input must be reproduced byte for byte, or it is ErrNonCanonicalCBOR.
// The same principle as dk1_ canonicality (spec §19): canonicality does not
// depend on the decoder rejecting every non-canonical form.
//
// Peek reads the type tag and the schema version of an object before strict
// decoding (spec §70). Walk checks that bytes are one data item of the
// profile; it is a helper for vectors, fuzzing and diagnostics, and never
// decides whether an object of the protocol is valid.
package codec
import (
"bytes"
"encoding/binary"
"fmt"
"math"
"unicode/utf8"
datekeys "g.activething.com/go/DateKeys"
)
// 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
// MaxTypeTagLen bounds the type tag that Peek reads. Every type tag of V1 is
// at most 25 bytes, so a longer one is of no known schema; the bound also
// keeps an input-sized tag out of the errors. It is an implementation limit
// (spec §74).
const MaxTypeTagLen = 64
// Major types of the profile (spec §58).
const (
majorUint = 0
majorBytes = 2
majorText = 3
majorArray = 4
majorMap = 5
)
var majorNames = [8]string{
"an unsigned integer", "a negative integer", "a byte string", "a text string",
"an array", "a map", "a tag", "a float or simple value",
}
// errorf returns an error that wraps ErrNonCanonicalCBOR.
func errorf(format string, args ...any) error {
return fmt.Errorf("codec: "+format+": %w", append(args, datekeys.ErrNonCanonicalCBOR)...)
}
// ---------------------------------------------------------------------------
// Encoder
// Encoder writes the deterministic encoding of data items of the profile:
// every integer and length in its shortest form, definite lengths only. The
// first error is kept and later calls do nothing; Out returns it. The zero
// value is ready to use.
//
// An Encoder does not know the schema: the caller writes the map keys, as
// unsigned integers in ascending order, and as many entries and items as it
// announced. The decoder of the schema checks both on the output (spec §72).
//
// An encoding may hold secrets, such as I_PAYLOAD or access_material: the
// Encoder wipes every buffer it outgrows, so that the output is the only
// copy, and the caller wipes the output.
type Encoder struct {
buf []byte
err error
}
// grow makes room for n more bytes, wiping the buffer it outgrows.
func (e *Encoder) grow(n int) {
if cap(e.buf)-len(e.buf) >= n {
return
}
b := make([]byte, len(e.buf), 2*cap(e.buf)+n)
copy(b, e.buf)
clear(e.buf)
e.buf = b
}
// head appends the head of a data item: its major type and argument.
func (e *Encoder) head(major byte, arg uint64) {
if e.err != nil {
return
}
var h [9]byte
h[0] = major << 5
n := 1
switch {
case arg < 24:
h[0] |= byte(arg)
case arg <= math.MaxUint8:
h[0] |= 24
h[1] = byte(arg)
n = 2
case arg <= math.MaxUint16:
h[0] |= 25
binary.BigEndian.PutUint16(h[1:], uint16(arg))
n = 3
case arg <= math.MaxUint32:
h[0] |= 26
binary.BigEndian.PutUint32(h[1:], uint32(arg))
n = 5
default:
h[0] |= 27
binary.BigEndian.PutUint64(h[1:], arg)
n = 9
}
e.grow(n)
e.buf = append(e.buf, h[:n]...)
}
// Fail records err as the error of the encoding unless one is recorded
// already; a nil err is ignored. Every later call does nothing, and Out
// returns the first error. The encoder of a schema calls it when its value
// breaks a rule of the schema, so that bytes the decoder rejects are never
// returned.
func (e *Encoder) Fail(err error) {
if e.err == nil {
e.err = err
}
}
// Map writes the head of a map of the given number of entries. The caller
// then writes each key, with Uint, followed by its value.
func (e *Encoder) Map(pairs int) {
if pairs < 0 {
e.Fail(errorf("map of %d entries", pairs))
return
}
e.head(majorMap, uint64(pairs))
}
// Array writes the head of an array of the given number of items. The caller
// then writes each item.
func (e *Encoder) Array(items int) {
if items < 0 {
e.Fail(errorf("array of %d items", items))
return
}
e.head(majorArray, uint64(items))
}
// Uint writes an unsigned integer.
func (e *Encoder) Uint(v uint64) { e.head(majorUint, v) }
// Bstr writes a byte string. A nil slice is the empty byte string.
func (e *Encoder) Bstr(b []byte) {
e.head(majorBytes, uint64(len(b)))
if e.err == nil {
e.grow(len(b))
e.buf = append(e.buf, b...)
}
}
// Text writes a text string, which must be valid UTF-8.
func (e *Encoder) Text(s string) {
if !utf8.ValidString(s) {
e.Fail(errorf("text string %q is not valid UTF-8", s))
return
}
e.head(majorText, uint64(len(s)))
if e.err == nil {
e.grow(len(s))
e.buf = append(e.buf, s...)
}
}
// Out returns the encoding, or the first error. On error the partial output
// is wiped.
func (e *Encoder) Out() ([]byte, error) {
if e.err != nil {
clear(e.buf)
e.buf = nil
return nil, e.err
}
return e.buf, nil
}
// ---------------------------------------------------------------------------
// Decoder
// Decoder is a strict cursor over the encoding of one data item of the
// profile. Each method reads one data item, or one head, and rejects with
// ErrNonCanonicalCBOR a major type outside the profile, a major type other
// than the one asked for, an indefinite length, an integer or length not in
// its shortest form, a length beyond the remaining input and a value outside
// the bounds the caller gives. Within each open map the keys are unsigned
// integers in strictly ascending order.
//
// The first error is kept: every later call returns it.
type Decoder struct {
in []byte
off int
maps []openMap
err error
}
// openMap is the state of a map between Map and EndMap.
type openMap struct {
left uint64 // entries not read yet
last uint64 // last key read
started bool // at least one key was read
}
// NewDecoder returns a Decoder positioned at the start of in.
func NewDecoder(in []byte) *Decoder { return &Decoder{in: in} }
// fail records the first error, with the current offset, and returns it.
func (d *Decoder) fail(format string, args ...any) error {
if d.err == nil {
d.err = errorf("offset %d: "+format, append([]any{d.off}, args...)...)
}
return d.err
}
func (d *Decoder) remaining() int { return len(d.in) - d.off }
// head reads the head of the next data item and returns its major type and
// argument. It rejects the major types outside the profile, reserved values,
// indefinite lengths, arguments not in their shortest form and truncation.
func (d *Decoder) head() (byte, uint64, error) {
if d.err != nil {
return 0, 0, d.err
}
if d.off >= len(d.in) {
return 0, 0, d.fail("truncated input")
}
b := d.in[d.off]
major, info := b>>5, b&0x1f
switch major {
case 1, 6, 7:
return 0, 0, d.fail("%s (initial byte %#02x) is outside the CBOR profile", majorNames[major], b)
}
switch {
case info < 24:
d.off++
return major, uint64(info), nil
case info == 31:
return 0, 0, d.fail("indefinite length (initial byte %#02x)", b)
case info > 27:
return 0, 0, d.fail("reserved additional information (initial byte %#02x)", b)
}
n := 1 << (info - 24)
if d.remaining() < 1+n {
return 0, 0, d.fail("truncated input")
}
var arg, min uint64
p := d.in[d.off+1 : d.off+1+n]
switch n {
case 1:
arg, min = uint64(p[0]), 24
case 2:
arg, min = uint64(binary.BigEndian.Uint16(p)), math.MaxUint8+1
case 4:
arg, min = uint64(binary.BigEndian.Uint32(p)), math.MaxUint16+1
default:
arg, min = binary.BigEndian.Uint64(p), math.MaxUint32+1
}
if arg < min {
return 0, 0, d.fail("%d is not in its shortest form (initial byte %#02x)", arg, b)
}
d.off += 1 + n
return major, arg, nil
}
// expect reads the head of a data item of major type want.
func (d *Decoder) expect(want byte) (uint64, error) {
start := d.off
major, arg, err := d.head()
if err != nil {
return 0, err
}
if major != want {
d.off = start
return 0, d.fail("%s where %s was expected", majorNames[major], majorNames[want])
}
return arg, nil
}
// Map reads the head of a map of at most max entries and returns the number
// of entries. The caller reads each entry with Key and a value, then calls
// EndMap.
func (d *Decoder) Map(max int) (int, error) {
n, err := d.expect(majorMap)
if err != nil {
return 0, err
}
if max < 0 || n > uint64(max) {
return 0, d.fail("map of %d entries, at most %d", n, max)
}
if n > uint64(d.remaining())/2 {
return 0, d.fail("truncated input: map of %d entries", n)
}
d.maps = append(d.maps, openMap{left: n})
return int(n), nil
}
// Key reads the key of the next entry of the innermost open map: an unsigned
// integer greater than the previous key of that map.
func (d *Decoder) Key() (uint64, error) {
if d.err != nil {
return 0, d.err
}
if len(d.maps) == 0 {
return 0, d.fail("map key outside a map")
}
m := &d.maps[len(d.maps)-1]
if m.left == 0 {
return 0, d.fail("map key after the last entry")
}
start := d.off
k, err := d.expect(majorUint)
if err != nil {
return 0, err
}
if m.started && k <= m.last {
d.off = start
return 0, d.fail("map key %d after key %d: keys must be strictly ascending", k, m.last)
}
m.left--
m.last, m.started = k, true
return k, nil
}
// EndMap closes the innermost open map, all of whose entries must have been
// read.
func (d *Decoder) EndMap() error {
if d.err != nil {
return d.err
}
if len(d.maps) == 0 {
return d.fail("end of a map outside a map")
}
if left := d.maps[len(d.maps)-1].left; left != 0 {
return d.fail("%d map entries not read", left)
}
d.maps = d.maps[:len(d.maps)-1]
return nil
}
// Array reads the head of an array of at most max items and returns the
// number of items, which the caller then reads.
func (d *Decoder) Array(max int) (int, error) {
n, err := d.expect(majorArray)
if err != nil {
return 0, err
}
if max < 0 || n > uint64(max) {
return 0, d.fail("array of %d items, at most %d", n, max)
}
if n > uint64(d.remaining()) {
return 0, d.fail("truncated input: array of %d items", n)
}
return int(n), nil
}
// Uint reads an unsigned integer of at most max.
func (d *Decoder) Uint(max uint64) (uint64, error) {
v, err := d.expect(majorUint)
if err != nil {
return 0, err
}
if v > max {
return 0, d.fail("unsigned integer %d above %d", v, max)
}
return v, nil
}
// content reads a string of major type want and returns its content, a
// subslice of the input. The length is checked against the remaining input
// and then against min and max.
func (d *Decoder) content(want byte, min, max int) ([]byte, error) {
n, err := d.expect(want)
if err != nil {
return nil, err
}
if n > uint64(d.remaining()) {
return nil, d.fail("truncated input: %s of %d bytes", majorNames[want], n)
}
if int(n) < min || int(n) > max {
return nil, d.fail("%s of %d bytes outside %d..%d", majorNames[want], n, min, max)
}
b := d.in[d.off : d.off+int(n)]
d.off += int(n)
return b, nil
}
// Bstr reads a byte string of min to max bytes and returns a copy of its
// content. The length is checked before anything is copied.
func (d *Decoder) Bstr(min, max int) ([]byte, error) {
b, err := d.content(majorBytes, min, max)
if err != nil {
return nil, err
}
return bytes.Clone(b), nil
}
// Text reads a text string of at most max bytes of valid UTF-8.
func (d *Decoder) Text(max int) (string, error) {
start := d.off
b, err := d.content(majorText, 0, max)
if err != nil {
return "", err
}
if !utf8.Valid(b) {
d.off = start
return "", d.fail("text string is not valid UTF-8")
}
return string(b), nil
}
// Done checks that every map was closed and that no byte follows the data
// item.
func (d *Decoder) Done() error {
if d.err != nil {
return d.err
}
if len(d.maps) != 0 {
return d.fail("%d maps not closed", len(d.maps))
}
if d.off != len(d.in) {
return d.fail("%d trailing bytes", len(d.in)-d.off)
}
return nil
}
// next returns the major type of the next data item, or majorUint at the end
// of the input, where reading it reports the truncation.
func (d *Decoder) next() byte {
if d.off >= len(d.in) {
return majorUint
}
return d.in[d.off] >> 5
}
// ---------------------------------------------------------------------------
// Objects
// Unmarshal decodes one object from in with decode, checks that the whole
// input was read, and re-encodes the decoded value with encode: the result
// must reproduce in byte for byte. decode and encode are the two halves of
// one schema and work on the same value.
//
// Errors of the Decoder and of the re-encoding wrap ErrNonCanonicalCBOR; any
// other error of decode is returned as it is.
//
// The re-encoding equals in on success, so it may hold secrets such as
// I_PAYLOAD or access_material; it is wiped on every path, and so is every
// buffer the Encoder outgrows.
func Unmarshal(in []byte, decode func(*Decoder) error, encode func(*Encoder)) error {
d := NewDecoder(in)
if err := decode(d); err != nil {
return err
}
if err := d.Done(); err != nil {
return err
}
e := Encoder{buf: make([]byte, 0, len(in))}
encode(&e)
re, err := e.Out()
defer clear(re)
if err != nil || !bytes.Equal(re, in) {
return errorf("input is not the deterministic encoding of its value")
}
return nil
}
// Peek reads the type tag (key 0, a text string of at most MaxTypeTagLen
// bytes) and the schema version (key 1, an unsigned integer) of the map at the
// start of in, before strict decoding, so that an unknown schema version is
// reported as such (spec §70). The map must start with keys 0 and 1, in the
// profile; nothing after them is read. The result must never be used as the
// decoded object.
func Peek(in []byte) (typeTag string, version uint64, err error) {
d := NewDecoder(in)
// The input bounds the map.
pairs, err := d.Map(math.MaxInt)
if err != nil {
return "", 0, err
}
if pairs < 2 {
return "", 0, d.fail("map without a type tag and a schema version")
}
for want := range uint64(2) {
k, err := d.Key()
if err != nil {
return "", 0, err
}
if k != want {
return "", 0, d.fail("map key %d where key %d was expected", k, want)
}
if want == 0 {
typeTag, err = d.Text(MaxTypeTagLen)
} else {
version, err = d.Uint(MaxSafeUint)
}
if err != nil {
return "", 0, err
}
}
return typeTag, version, nil
}
// CheckSchema reads the type tag and the schema version of an object with
// Peek and requires the expected values. A different type tag is
// ErrNonCanonicalCBOR; a different version is ErrUnsupportedVersion.
func CheckSchema(in []byte, typeTag string, version uint64) error {
tag, v, err := Peek(in)
if err != nil {
return err
}
if tag != typeTag {
return errorf("type %q, want %q", tag, typeTag)
}
if v != version {
return fmt.Errorf("codec: %s schema version %d, want %d: %w", typeTag, v, version, datekeys.ErrUnsupportedVersion)
}
return nil
}
// Walk checks that in is exactly one data item of the profile, with
// containers nested at most maxDepth deep (a scalar has depth 0) and every
// string and container at most maxLen long. It reads iteratively, so deep
// input cannot exhaust the stack.
//
// Walk is a helper for vectors, fuzzing and diagnostics, and for registered
// extensions whose data is CBOR (spec §72). It never decides whether an
// object of the protocol is valid: the decoder of its schema does.
func Walk(in []byte, maxDepth, maxLen int) error {
d := NewDecoder(in)
var open []walkLevel
for first := true; first || len(open) > 0; first = false {
var err error
if n := len(open); n > 0 && open[n-1].left == 0 {
// The innermost container is complete.
if open[n-1].isMap {
err = d.EndMap()
}
open = open[:n-1]
} else {
if n > 0 {
open[n-1].left--
if open[n-1].isMap {
_, err = d.Key()
}
}
if err == nil {
open, err = d.walkItem(open, maxDepth, maxLen)
}
}
if err != nil {
return err
}
}
return d.Done()
}
// walkLevel is an open container of Walk.
type walkLevel struct {
left int // entries of a map or items of an array not read yet
isMap bool
}
// walkItem reads one data item for Walk: a scalar, or the head of a
// container, which it pushes on open.
func (d *Decoder) walkItem(open []walkLevel, maxDepth, maxLen int) ([]walkLevel, error) {
var err error
switch major := d.next(); major {
case majorMap, majorArray:
if len(open) >= maxDepth {
return open, d.fail("containers nested deeper than %d", maxDepth)
}
l := walkLevel{isMap: major == majorMap}
if l.isMap {
l.left, err = d.Map(maxLen)
} else {
l.left, err = d.Array(maxLen)
}
open = append(open, l)
case majorBytes:
_, err = d.content(majorBytes, 0, maxLen)
case majorText:
_, err = d.Text(maxLen)
default:
// An unsigned integer, or the error of whatever is there.
_, err = d.Uint(math.MaxUint64)
}
return open, err
}

Powered by TurnKey Linux.