canonical

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 2 Imported by: 0

Documentation

Overview

Package canonical owns the module's canonical encoding: the framing every identity digest in this module is computed over.

It exists because there is more than one thing worth digesting — a server's catalog (internal/catalog), a binding's configuration identity (pkg/harness) — and they must agree on how bytes are framed. Two encoders that start identical and drift apart are the specific hazard: the drift is invisible (each side's own golden test still passes) and its symptom is two digests that disagree about content they agree on, or worse, agree about content they differ on.

The framing gives every caller two properties that a naive hash of a struct does not have:

  • Determinism. The same content produces the same digest however it arrived. Nothing here iterates a map; ordering collections canonically before encoding them is the caller's job, because only the caller knows which orderings are content and which are incidental.
  • Unambiguity. No two different inputs encode to the same bytes. Every value is length-delimited or fixed-width, so fields cannot be slid into one another: Str("ab") + Str("") and Str("a") + Str("b") are distinct encodings where a naive concatenation would render both as "ab".

The scheme follows the fingerprint guidance in the session-versioning design: explicit domain, explicit schema version, stable field ordering, length-delimited values, deterministic collection ordering. This package supplies the framing; each caller supplies its own domain tag, its own schema version, and its own field order, because those are the parts that define what a particular digest means.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder writes the canonical encoding into a hash. Every method is length-delimited or fixed-width, so the concatenation of any sequence of calls is unambiguous.

It writes to a hash.Hash, which never errors (hash.Hash's Write contract forbids it), so no method here returns one. That is the reason this is not an io.Writer wrapper with error plumbing nobody could act on.

func NewEncoder

func NewEncoder(h hash.Hash) *Encoder

NewEncoder returns an Encoder that writes into h.

func (*Encoder) Bool

func (e *Encoder) Bool(b bool)

Bool writes a single byte.

func (*Encoder) Bytes

func (e *Encoder) Bytes(b []byte)

Bytes writes a length-delimited byte string.

func (*Encoder) Count

func (e *Encoder) Count(n int)

Count writes a collection's length.

func (*Encoder) Field

func (e *Encoder) Field(tag string)

Field writes a field tag. It is a length-delimited string like any other, which is enough to separate fields: the hash sees a different byte sequence for any different tag, and a tag can never be confused with a value because both are framed identically.

A field tag is not needed for unambiguity — the length delimiters already supply that — it is needed for meaning: it pins a value to the field it was read from, so that reordering two same-typed fields changes the digest.

func (*Encoder) Int

func (e *Encoder) Int(i int64)

Int writes a signed integer as a sign byte followed by a magnitude, rather than by casting a possibly-negative value straight to uint64.

The sign is written separately so that the magnitude is always a genuine count. A bare uint64(i) would encode -1 as 2^64-1, which is a legal encoding of a legal value — so a negative field and an enormous positive one would share a digest, and a configuration error would be indistinguishable from a configuration.

func (*Encoder) Str

func (e *Encoder) Str(s string)

Str writes a length-delimited string.

func (*Encoder) TriState

func (e *Encoder) TriState(p *bool)

TriState writes an optional bool in one byte, keeping "absent" distinct from "false" — which is the whole reason a protocol models an optional hint as a pointer.

func (*Encoder) Uint

func (e *Encoder) Uint(u uint64)

Uint writes a fixed-width unsigned integer. Fixed-width rather than varint: the width is then never a function of the value, so no encoding of one integer can be a prefix of the encoding of another.

Jump to

Keyboard shortcuts

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