Skip to content

Classic Protocols

UDP, TCP, Ethernet, and IPv4 are the foundation of internet networking. They are also excellent wirespec examples: small, well-understood, and each one demonstrates a distinct set of language features. Start here if you are new to wirespec.

UDP

User Datagram Protocol has the simplest possible fixed header: four 16-bit fields, a length constraint, and a variable-length payload whose size is derived from the header.

wire
module net.udp
@endian big

packet UdpDatagram {
    src_port: u16,
    dst_port: u16,
    length: u16,
    checksum: u16,
    require length >= 8,
    data: bytes[length: length - 8],
}

Features demonstrated:

FeatureWhere
packet definitionpacket UdpDatagram { ... }
Big-endian module default@endian big
u16 scalar fieldsrc_port, dst_port, length, checksum
Runtime validationrequire length >= 8
Length-prefixed bytesbytes[length: length - 8]
Expression arithmeticlength - 8 in the bytes spec

The require clause is checked at parse time and returns WIRESPEC_ERR_CONSTRAINT if the condition fails. The expression length - 8 subtracts the fixed header size (8 bytes) from the length field to give the payload byte count.

Compile:

bash
wirespec compile examples/net/udp.wspec -o build/

Generated C API:

c
wirespec_result_t net_udp_udp_datagram_parse(
    const uint8_t *buf, size_t len,
    net_udp_udp_datagram_t *out, size_t *consumed);

wirespec_result_t net_udp_udp_datagram_serialize(
    const net_udp_udp_datagram_t *in,
    uint8_t *buf, size_t cap, size_t *written);

The data field becomes a wirespec_bytes_t (pointer + length) — zero-copy, pointing into the original buffer.


TCP

TCP has a more complex header than UDP. The 4-bit data offset field, eight individual flag bits, and a variable-length options region show off wirespec's bits[N] and bit types.

wire
module net.tcp
@endian big

packet TcpSegment {
    src_port: u16,
    dst_port: u16,
    seq_num: u32,
    ack_num: u32,
    data_offset: bits[4],
    reserved: bits[4],
    cwr: bit,
    ece: bit,
    urg: bit,
    ack: bit,
    psh: bit,
    rst: bit,
    syn: bit,
    fin: bit,
    window: u16,
    checksum: u16,
    urgent_pointer: u16,
    require data_offset >= 5,
    options: bytes[length: data_offset * 4 - 20],
}

Features demonstrated:

FeatureWhere
bits[N] sub-byte fieldsdata_offset: bits[4], reserved: bits[4]
bit (single-bit flag)cwr, ece, urg, ack, psh, rst, syn, fin
BitGroup auto-groupingThe 8 flag bits are packed into one byte on the wire
u32 scalarseq_num, ack_num
Computed length bytesbytes[length: data_offset * 4 - 20]

BitGroup auto-grouping. Consecutive bits[N] and bit fields that together fill complete bytes are automatically grouped by the compiler. In the TCP definition, data_offset: bits[4] and reserved: bits[4] are grouped into one byte. The eight flag bits are grouped into a second byte. The compiler emits a single read for each group and uses shifts and masks to extract individual fields — no manual bit manipulation needed.

The data offset field encodes the header length in 32-bit words. The minimum value is 5 (20 bytes). The options field occupies data_offset * 4 - 20 bytes, which is zero when there are no options.

Compile:

bash
wirespec compile examples/net/tcp.wspec -o build/

Generated C struct (excerpt):

c
typedef struct {
    uint16_t src_port;
    uint16_t dst_port;
    uint32_t seq_num;
    uint32_t ack_num;
    uint8_t  data_offset;   /* bits[4] */
    uint8_t  reserved;      /* bits[4] */
    uint8_t  cwr;           /* bit */
    uint8_t  ece;
    uint8_t  urg;
    uint8_t  ack;
    uint8_t  psh;
    uint8_t  rst;
    uint8_t  syn;
    uint8_t  fin;
    uint16_t window;
    uint16_t checksum;
    uint16_t urgent_pointer;
    wirespec_bytes_t options;
} net_tcp_segment_t;

Ethernet

An Ethernet frame has two fixed-length MAC address fields (6 bytes each) and a payload whose size extends to the end of the input buffer. This demonstrates wirespec's zero-copy bytes[remaining].

wire
module net.ethernet
@endian big

packet EthernetFrame {
    dst_mac: bytes[6],
    src_mac: bytes[6],
    ether_type: u16,
    payload: bytes[remaining],
}

Features demonstrated:

FeatureWhere
Fixed-length bytesbytes[6] — exact byte count, zero-copy
bytes[remaining]payload consumes all bytes left in the buffer
Simple flat packetNo conditionals or expressions needed

bytes[remaining] is a terminal field: it must be the last wire field in its scope. The compiler enforces this statically and will reject any definition that places wire fields after it.

Both dst_mac, src_mac, and payload map to wirespec_bytes_t in the generated C — a { const uint8_t *ptr; size_t len; } pair pointing into the original buffer. No copying occurs.

Compile:

bash
wirespec compile examples/net/ethernet.wspec -o build/

IPv4

IPv4 uses bits[N] fields of varying widths and adds an @checksum(internet) annotation for automatic RFC 1071 checksum verification and serialization.

wire
module ip.v4
@endian big

packet IPv4Header {
    version: bits[4],
    ihl: bits[4],
    dscp: bits[6],
    ecn: bits[2],
    total_length: u16,
    identification: u16,
    flags: bits[3],
    fragment_offset: bits[13],
    ttl: u8,
    protocol: u8,
    @checksum(internet)
    header_checksum: u16,
    src_addr: u32,
    dst_addr: u32,
}

Features demonstrated:

FeatureWhere
bits[N] with varied widths4, 6, 2, 3, 13-bit fields
BitGroup auto-groupingversion+ihl → one byte; dscp+ecn → one byte; flags+fragment_offset → two bytes
@checksum(internet)Automatic RFC 1071 checksum on header_checksum

@checksum(internet) behavior:

  • On parse: The generated parser computes the one's complement sum over the entire header. If the result is not 0xFFFF, it returns WIRESPEC_ERR_CHECKSUM.
  • On serialize: The generated serializer zeros the checksum field, computes the checksum over the completed header, and writes the result back. You never touch the checksum field manually.

This matches the behavior specified in RFC 791 and verified against real IPv4 packets.

Compile:

bash
wirespec compile examples/ip/ipv4.wspec -o build/

Parsing with checksum verification:

c
ip_v4_ipv4_header_t hdr;
size_t consumed;
wirespec_result_t r = ip_v4_ipv4_header_parse(buf, len, &hdr, &consumed);
if (r == WIRESPEC_ERR_CHECKSUM) {
    /* header checksum failed — drop packet */
}

Serializing with automatic checksum:

c
ip_v4_ipv4_header_t hdr = {
    .version  = 4,
    .ihl      = 5,
    .protocol = 17,  /* UDP */
    /* ... other fields ... */
};
uint8_t buf[20];
size_t written;
ip_v4_ipv4_header_serialize(&hdr, buf, sizeof(buf), &written);
/* buf now contains a valid IPv4 header with correct checksum */

What Next

  • QUIC — variable-length integers, tagged frame unions, optional fields
  • BLE — little-endian protocols, type aliases, enums
  • Checksums — CRC32, CRC32C, Fletcher-16
  • Bit Fields — deeper dive into bits[N] and BitGroup packing