Deterministic binary data format. Schema | Compact Encoding | Binary Data
hypha-spec encoding types.md
6.1 kB · 145 lines
Markdown
at dev

Type Encoding #

Every value is encoded recursively. The integer encoding is a fundamental building block — it's used for length prefixes, indices, and option selectors.

Each type is defined by its schema declaration. Bounds from the schema determine how the value is encoded and validated.

Null #

Null is encoded as a null integer value (see [Integer Encoding: Null](integer.md#null)).

A null value can only appear for a nullable type. The null is encoded as the corresponding null wire value for the type's integer mode.

The null type itself does not produce any bytes independently — it only appears through nullable types.

Opt #

Encoding: int[0...N-1]

An option selects one value from a fixed set of named options. It is encoded as an integer with bounds int[0...N-1] where N is the number of options.

The value is the zero-based index of the selected option.

Schema Value Encoding (hex)
opt{"a","b","c","d"} "b" (index 1) 01

Blob #

Encoding: int[max(0, a)...b] + raw bytes

A blob is a sequence of raw bytes. It is encoded as:

  1. An integer length prefix with bounds derived from the blob's content bounds.
  2. The raw bytes themselves.

The blob's schema bounds [a:b] (or [:b] where a defaults to 0) define the minimum and maximum byte length of the content. The length prefix integer is int[max(0,a)...b]:

  • Both bounds present → unsigned mode: f(e) = max(0,a) + e
  • The decoded value f(e) is the content length, which must satisfy max(0,a) ≤ f(e) ≤ b
  • The encoded wire value is e = actual_length - max(0,a)
Schema Content length Wire (e) Encoding (hex)
blob[:20] 2 bytes 2 02 [2 bytes]
blob[4:20] 5 bytes 1 01 [5 bytes] (5 - 4 = 1)

Constraint: Blob must have an upper bound defined (b).

String #

Encoding: int[max(0, a)...b] + UTF-8 bytes

A string is UTF-8 encoded text. It follows the exact same encoding as blob: an integer length prefix followed by the UTF-8 bytes.

The length is the byte length of the UTF-8 encoding (not character count).

Schema Value Byte length Wire (e) Encoding (hex)
string[:30] "Alice" 5 5 05 41 6C 69 63 65
string[3:30] "Alice" 5 2 02 41 6C 69 63 65 (5 - 3 = 2)

Constraint: String must have an upper bound defined (b).

Ref #

Encoding: determined by the referenced schema's root type

A reference points to another schema. The encoded value is produced by encoding the reference's data according to the root type of the referenced schema.

There is no length prefix — the referenced schema's root type determines how many bytes the ref consumes.

Example: ref(abc123def...) → look up schema with ID abc123def...
           → encode ref data using that schema's root type

Validation: If there is an infinite loop of references (schema A references schema B which references schema A), the schema is invalid.

Array #

Encoding: int[max(0, a)...b] + concatenated elements

An array is an ordered list of elements of the same type. It is encoded as:

  1. An integer element count with bounds int[max(0, a)...b] where a is the minimum and b the maximum element count.
  2. Each element encoded recursively, concatenated in order.
Schema Elements Count Encoding (hex)
array{int}[:3] [10, 20] 2 02 0A 14

Constraint: Array must have an upper bound defined (b).

Elements have no separator or length prefix between them — each element's encoding is self-delimiting (either fixed-size or length-prefixed).

Struct #

Encoding: concatenated fields sorted by field name

A structure is an ordered collection of named fields. Fields are sorted before encoding:

  1. Shorter field names first.
  2. Same-length names sorted lexicographically by UTF-8 big-endian byte value.

This sorting is the same as the canonicalization sorting used for schema IDs. The order in the schema file does not affect encoding.

There is no length prefix — the schema defines exactly which fields exist and their types.

Schema Encoding (hex)
struct{y: int, x: int} with x=10, y=20 0A 14

In this example, x and y are both 1 byte. x (0x78) < y (0x79), so x (10 = 0x0A) is encoded before y (20 = 0x14), even though the schema declares y first.

Constraint: struct cannot be nullable.

Nullable Types (?) #

When a type is declared as nullable (prefix ?), the encoding changes:

  • If the value is null: encode the corresponding null integer for the underlying integer mode.
  • If the value is present: encode normally, but apply the null offset to the integer portion.

For length-prefixed types (string, blob), null is encoded as the null integer value with no trailing data. The decoder reads the null integer and knows not to expect any content bytes.

Schema Value Encoding (hex) Notes
?string[:300] null 00 unsigned null
?string[:300] "hi" 03 68 69 length=2, offset: wire=(2-0)+1=3
?int null 7F two's comp null
?int -5 7A wire = -5 - 1 = -6