Schema Reference

A schema tells EByte what a binary format looks like: the fields in order, how wide each one is, and how they depend on each other. This page covers version 1 of the schema format, the one EByte reads. The repeat type needs EByte 0.2.0 or later. Worked schemas for real formats are in the schema library. The full contract, with every edge case, is schema-v1.md on GitHub.

The File

A schema is a UTF-8 JSON file with exactly four members:

{
  "schema_version": 1,
  "name": "ebyte_packet",
  "endian": "little",
  "fields": [ ... ]
}

endian is little or big, and integers inherit it unless they set their own. Names are ASCII letters, digits and underscores, up to 64 characters, starting with a letter or underscore. EByte is strict about the JSON: unknown members, duplicate members, null values and invalid Unicode are all rejected before any binary input is opened.

A schema’s identity is the SHA-256 of its exact bytes, whitespace included. Documents carry that hash, so a document decoded with one schema can’t be encoded with another by accident. Reformatting a schema file changes its identity.

Field Types

Every field has a name and a type.

Type Width In the JSON document
u8, u16, u32, u64 1, 2, 4, 8 bytes, unsigned Decimal string, such as "300"
i8, i16, i32, i64 1, 2, 4, 8 bytes, two’s complement Decimal string, such as "-2"
bytes length bytes Lowercase hex, two characters per byte
string length bytes, encoding is ascii or utf8 The exact characters, NULs and spaces kept
array count elements, each an object of fields JSON array of objects
repeat Elements of fields until a stop rule, no count JSON array of objects

Integers are strings in the document so that 64-bit values never pass through floating point.

An integer can set its own endian. A length or count is either a literal number or a reference to an earlier integer in the same object, written as {"field": "data_size"}. Strings are exact-width: EByte doesn’t trim, pad or look for a terminator. If a format pads a field, declare the padding as its own field and it’ll be kept.

Repeating Blocks

Many formats are a run of blocks with no count anywhere: RIFF chunks, ZIP entries, PNG chunks. A repeat field reads them, with until saying when to stop.

"until": "end_of_input" keeps reading elements while any byte is left. It has to be the last field of the schema and can’t have when.

{"name": "chunks", "type": "repeat", "until": "end_of_input", "fields": [
  {"name": "id", "type": "string", "encoding": "ascii", "length": 4},
  {"name": "size", "type": "u32"},
  {"name": "body", "type": "bytes", "length": {"field": "size"}}
]}

"until": "tag_mismatch" keeps reading while the next bytes equal a tag. The element’s first field is the tag: an unconditional bytes field of 1 to 16 bytes with expect. When the next bytes differ, or the input runs out, the repeat ends and the following fields carry on from there.

{"name": "entries", "type": "repeat", "until": "tag_mismatch", "fields": [
  {"name": "signature", "type": "bytes", "length": 4, "expect": "504b0304"},
  ...
]}

In the document a repeat is an array of objects, like an array, and you can add, remove or reorder elements freely. Every element has to take at least one byte. On encode, EByte refuses output whose bytes right after a tag repeat start with the tag, because reading the file back would take them for another element.

Checks, Conditions and Derived Counts

expect checks a fixed value. On an integer it’s a decimal string, on a fixed-length bytes field it’s lowercase hex. A magic number is the usual case:

{"name": "magic", "type": "bytes", "length": 2, "expect": "4542"}

A mismatch is an error on decode and on encode. EByte never corrects a value to make it pass.

when makes a field optional. The field is present only if an earlier integer in the same object equals a value:

{"name": "note", "type": "bytes", "length": 2, "when": {"field": "flags", "equals": "1"}}

Equality is the only test. When it’s false, the field takes no bytes and must be absent from the document.

derive lets the encoder work out a count. An unsigned integer marked {"count_of": "records"} is left out of the editable document, and on encode EByte writes the length of the records array into it. The array’s count must point back at that integer. A count field without derive stays in the document, and encode fails if it doesn’t match the array, rather than guessing which one is right.

Rules That Keep It Bounded

  • References only point backwards, to an earlier integer in the same object that has no when. The one forward link allowed is a derived count to its own array.
  • Arrays and repeats can nest up to 8 levels deep. A repeat looks ahead at most 16 bytes.
  • There are no expressions, no arithmetic between fields, no seeking to an offset, no recursion and no plugins.
  • Decoding must use every byte. Data left over after the last field is reported as trailing_data, not ignored.

Limits

These are the default ceilings. A program or a command-line flag can lower any of them, but never raise one.

Limit Default
Schema file size 1 MiB
Field declarations in a schema 1,024
Array nesting depth 8
Binary input or output 16 MiB
One bytes or string field 16 MiB
Elements in one array or repeat 65,536
Values in a document 100,000
Entries in a trace 100,000
Document or trace JSON 64 MiB

Sizes are checked before EByte reads or allocates for a field, so a header that claims a 2^64-byte field is rejected after reading just that header.

Not in Version 1

Floating point, bit fields, offsets and seeks, checksums, compression and structural diff between two files. If your format needs one of these, say which: it helps decide what comes next.