Graven

Binary layout, by example

This page shows how each kind of value looks on the wire. Every example below was encoded by the reference implementation (gravenc from-json) when this site was built, then annotated from the layout in the lockfile. Point at a byte or a label to see what it encodes.

The examples are bare messages, without the envelope. Byte offsets start at 0. Pointers are relative to the position of the pointer itself, so a message can be copied or embedded anywhere without fixing them up.

Five colours are used throughout: structure (lengths, counts, presence bits, union tags), inline values, pointers, variable-size data, and absent fields.

Scalars, enums and presence

enum Unit : u8 { Celsius = 0, Fahrenheit = 1 }

table Reading {
  device  @0: u32;
  temp    @1: f32?;
  battery @2: u8?;
  unit    @3: Unit;
  online  @4: bool;
}

A table starts with a u16 giving the length of its fixed part. Scalars live in the fixed part, each in a slot at a position the compiler computes once and records in the lockfile. One presence bit per scalar tells a present zero from an absent field: here battery is absent, so its slot is ignored. A bool is two bits in the presence byte and has no slot.

The example needs JavaScript.

Strings and bytes

table Note {
  title @0: string;
  body  @1: string?;
  tag   @2: bytes?;
}

Variable-size data lives after the fixed part. The slot holds a 4-byte relative pointer; a zero pointer means absent. The data is a u32 length followed by the bytes, which a reader borrows straight from the buffer.

The example needs JavaScript.

Lists of scalars and structs

struct Point { x: i16, y: i16 }

table Path {
  ids    @0: [u16];
  points @1: [Point];
}

A list of fixed-size values is a u32 count followed by the elements, packed with no padding. Element i is at 4 + i × size, so indexing is a multiplication, not a scan.

The example needs JavaScript.

Lists of tables

table Product {
  name  @0: string;
  price @1: u32;
}

table Catalog {
  products @0: [Product];
}

This is the shape of a JSON array of objects, such as {"products": [{"name": "pen", "price": 150}, …]} (see Coming from JSON). The list starts with a u32 count and a u16 element length. The elements' fixed parts follow back to back, without a header each, so element i is at 6 + i × length. Each element can still have its own strings, lists and nested tables: their pointers lead to data after the list.

Storing the element length in the list, rather than taking it from the schema, is what lets a list written by a newer schema (with longer elements) be read by an older one, and the other way round.

The example needs JavaScript.

Lists of strings

table Tags {
  tags @0: [string];
}

A list of strings or byte arrays is a u32 count followed by one pointer per element.

The example needs JavaScript.

Nested tables

table Customer {
  name @0: string;
  home @1: Address?;
}

table Address {
  city @0: string;
  zip  @1: u32;
}

A table field is a pointer to another table, with its own length header and presence bits. Each table evolves on its own: adding a field to Address does not change Customer.

The example needs JavaScript.

Unions

table Card { last4 @0: u16; }
table Pix  { key @0: string; }

union Method { Card @1: Card, Pix @2: Pix }

table Payment {
  amount @0: u32;
  method @1: Method;
}

A union slot is a u16 tag followed by a pointer to the variant's table. Tag 0 means no value. A reader that does not know a tag reports it as unknown instead of failing, so new variants can be added.

The example needs JavaScript.

The normative rules for all of the above are in the specification.