record

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

Documentation

Overview

Package record is the bounded, ordered reading of one application record: a canonical CBOR map with unsigned-integer keys, whose key 0 is the record's magic and whose keys above the last one a version defines are preserved and ignored.

A record is refused for the first rule it breaks, in one order, so that two implementations refuse the same bytes for the same reason:

  1. ErrTooLarge: the bytes exceed the record's bound, checked before anything is decoded;
  2. ErrCBOR: not one canonical CBOR item (package cbor), or not a map;
  3. ErrTooLarge: more than MaxKeys entries;
  4. ErrKeyType: a key that is not an unsigned integer;
  5. ErrMagic: key 0 is not the byte string the caller names;
  6. then each defined key as the caller reads it, in the caller's order: ErrMissing, ErrType, and ErrRange or ErrList for the key's own rule.

The application supplies the bound, the last defined key and the magic, and reads its own fields. Nothing here names a record.

Index

Examples

Constants

View Source
const MaxKeys = 64

MaxKeys bounds the entries of a record map, unknown keys included.

Variables

View Source
var (
	ErrTooLarge = errors.New("record: record exceeds its bound")
	ErrCBOR     = errors.New("record: not a canonical CBOR map")
	ErrKeyType  = errors.New("record: record key is not an unsigned integer")
	ErrMagic    = errors.New("record: wrong magic")
	ErrMissing  = errors.New("record: required key missing")
	ErrType     = errors.New("record: field has the wrong type")
	ErrRange    = errors.New("record: field out of range")
	ErrList     = errors.New("record: list is over its bound, or not distinct and ascending")
)

The refusals of the record steps. An application counts them by Reason, and wraps nothing around them: errors.Is finds each through the detail a step adds.

Functions

func Ascending32

func Ascending32(a [][32]byte) error

Ascending32 holds a list of 32-byte values to strictly ascending byte order, or ErrList.

func CheckExtra

func CheckExtra(extra cbor.Map, last uint64, defined int) error

CheckExtra is CheckExtraKeys and the bound on the whole map: the preserved pairs and the defined keys the record writes are at most MaxKeys together.

func CheckExtraKeys

func CheckExtraKeys(extra cbor.Map, last uint64) error

CheckExtraKeys holds preserved pairs to the rule that their keys are unsigned integers above last, so re-encoding cannot shadow a defined field.

func Claims

func Claims(payload, magic []byte) bool

Claims reports whether payload claims to be a record under magic, reading only its head: a definite-length map head (0xa0 to 0xbb), key 0, then a byte-string head of four bytes and magic itself. Key 0 always sorts first in a canonical map with unsigned-integer keys. It allocates nothing and decides nothing else: a payload that claims a record is then held to Decode.

func DecodeMap

func DecodeMap(b []byte, max int) (cbor.Map, error)

DecodeMap is steps 1 to 3: the bound, one canonical CBOR item that is a map, and at most MaxKeys entries. The bound is checked before the decoder runs, and the decoder bounds every declared length against the bytes present before it allocates.

func Encode

func Encode(m cbor.Map, extra cbor.Map, max int) ([]byte, error)

Encode writes the defined pairs then the preserved ones as one canonical CBOR map, and holds the result to max. The encoder sorts the keys, so the order of m does not matter.

func InRange

func InRange(n, lo, hi uint64, what string) error

InRange holds n to lo through hi, or ErrRange naming what.

func Reason

func Reason(err error) (string, bool)

Reason is the fixed label of a record refusal, and false for any other error.

Example

A record is refused for the first rule it breaks, and each refusal has the fixed label a host counts it by.

package main

import (
	"fmt"

	"github.com/lightwebinc/bcommon/cbor"
	"github.com/lightwebinc/bcommon/record"
)

// The sample record's magic and bounds: the test prefix, a letter and a
// version byte. An application registers its own.
var (
	sampleMagic = []byte("vxr\x01")
	sampleLast  = uint64(2)
	sampleMax   = 256
)

func main() {
	enc := func(m cbor.Map) []byte {
		b, err := cbor.Encode(m)
		if err != nil {
			panic(err)
		}
		return b
	}
	array, err := cbor.Encode([]cbor.Value{sampleMagic, "a name"})
	if err != nil {
		panic(err)
	}
	good := cbor.Map{{Key: uint64(0), Val: sampleMagic}, {Key: uint64(1), Val: "a name"}, {Key: uint64(2), Val: uint64(7)}}
	read := func(b []byte) error {
		f, err := record.Decode(b, sampleMax, sampleLast, sampleMagic)
		if err != nil {
			return err
		}
		if _, err := f.Text(1); err != nil {
			return err
		}
		_, err = f.Uint(2, 1, 100)
		return err
	}
	for _, c := range []struct {
		name string
		b    []byte
	}{
		{"over the bound, whatever it is", make([]byte, sampleMax+1)},
		{"not CBOR", []byte{0xff}},
		{"an array", array},
		{"a text key", enc(append(cbor.Map{{Key: "name", Val: uint64(1)}}, good...))},
		{"another magic", enc(cbor.Map{{Key: uint64(0), Val: []byte("vxq\x01")}})},
		{"key 1 missing", enc(cbor.Map{good[0], good[2]})},
		{"key 1 a number", enc(cbor.Map{good[0], {Key: uint64(1), Val: uint64(1)}, good[2]})},
		{"key 2 out of range", enc(cbor.Map{good[0], good[1], {Key: uint64(2), Val: uint64(101)}})},
	} {
		reason, _ := record.Reason(read(c.b))
		fmt.Printf("%s: %s\n", c.name, reason)
	}
}
Output:
over the bound, whatever it is: too-large
not CBOR: cbor
an array: cbor
a text key: key-type
another magic: magic
key 1 missing: missing
key 1 a number: type
key 2 out of range: range

func Values32

func Values32(a [][32]byte) []cbor.Value

Values32 is a list of 32-byte values as the CBOR array Encode writes, each element a copy.

Types

type Fields

type Fields struct {

	// Extra holds the pairs whose keys are above the last defined key, in
	// the record's order, exactly as decoded.
	Extra cbor.Map
	// contains filtered or unexported fields
}

Fields is a decoded record: its known integer keys, and the unknown ones above the last defined key, which are preserved.

func Decode

func Decode(b []byte, max int, last uint64, magic []byte) (*Fields, error)

Decode is steps 1 to 5 in order: DecodeMap, Split and CheckMagic.

Example

An application's decoder reads its own keys, in its own order, from a record the package has held to the steps every record shares. A key above the last defined one is preserved, so a reader of this version writes back what a later version added.

package main

import (
	"fmt"

	"github.com/lightwebinc/bcommon/cbor"
	"github.com/lightwebinc/bcommon/record"
)

// The sample record's magic and bounds: the test prefix, a letter and a
// version byte. An application registers its own.
var (
	sampleMagic = []byte("vxr\x01")
	sampleLast  = uint64(2)
	sampleMax   = 256
)

func main() {
	b, err := record.Encode(cbor.Map{
		{Key: uint64(0), Val: sampleMagic},
		{Key: uint64(1), Val: "a name"},
		{Key: uint64(2), Val: uint64(7)},
	}, cbor.Map{{Key: uint64(9), Val: "from a later version"}}, sampleMax)
	if err != nil {
		fmt.Println(err)
		return
	}

	f, err := record.Decode(b, sampleMax, sampleLast, sampleMagic)
	if err != nil {
		fmt.Println(err)
		return
	}
	name, err := f.Text(1)
	fmt.Println("name:", name, err)
	count, err := f.Uint(2, 1, 100)
	fmt.Println("count:", count, err)
	fmt.Println("preserved:", len(f.Extra), "pair, key", f.Extra[0].Key)

	again, err := record.Encode(cbor.Map{
		{Key: uint64(0), Val: sampleMagic},
		{Key: uint64(1), Val: name},
		{Key: uint64(2), Val: count},
	}, f.Extra, sampleMax)
	fmt.Println("re-encodes to the same bytes:", string(again) == string(b), err)
	fmt.Println("claims the record:", record.Claims(b, sampleMagic), record.Claims(b, []byte("vxq\x01")))
}
Output:
name: a name <nil>
count: 7 <nil>
preserved: 1 pair, key 9
re-encodes to the same bytes: true <nil>
claims the record: true false

func Split

func Split(m cbor.Map, last uint64) (*Fields, error)

Split is step 4: it splits a record map into its known integer keys and the unknown ones above last, which are preserved. Any key that is not an unsigned integer refuses the record.

func (*Fields) Array

func (f *Fields) Array(k uint64, max int) ([]cbor.Value, error)

Array returns key k as an array of at most max elements. The length is checked before the caller allocates anything for the elements.

func (*Fields) Bool

func (f *Fields) Bool(k uint64) (bool, error)

Bool returns key k as a boolean.

func (*Fields) Bytes

func (f *Fields) Bytes(k uint64) ([]byte, error)

Bytes returns key k as a byte string of any length.

func (*Fields) BytesN

func (f *Fields) BytesN(k uint64, n int) ([]byte, error)

BytesN returns key k as a byte string of exactly n bytes; a negative n takes any length.

func (*Fields) BytesRange

func (f *Fields) BytesRange(k uint64, lo, hi int) ([]byte, error)

BytesRange returns key k as a byte string of lo to hi bytes.

func (*Fields) CheckMagic

func (f *Fields) CheckMagic(magic []byte) error

CheckMagic is step 5: key 0 present, a byte string, and equal to magic.

func (*Fields) Get

func (f *Fields) Get(k uint64) (cbor.Value, bool)

Get returns key k's value and whether the record carries it.

func (*Fields) Has

func (f *Fields) Has(k uint64) bool

Has reports whether the record carries defined key k.

func (*Fields) List32

func (f *Fields) List32(k uint64, max int) ([][32]byte, error)

List32 returns key k as an array of at most max 32-byte strings in strictly ascending byte order, so one set has one encoding.

func (*Fields) Need

func (f *Fields) Need(k uint64) (cbor.Value, error)

Need returns key k's value, or ErrMissing.

func (*Fields) Text

func (f *Fields) Text(k uint64) (string, error)

Text returns key k as a text string. The decoder has already held it to valid UTF-8.

func (*Fields) Uint

func (f *Fields) Uint(k uint64, lo, hi uint64) (uint64, error)

Uint returns key k as an unsigned integer within lo to hi.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL