Skip to content

Getting Started

wirespec is a DSL for describing network protocol wire formats. It compiles .wspec files into C or Rust parsers and serializers — no heap allocation, zero-copy where possible, and generated code that compiles clean with -Wall -Wextra -Werror.

Installation

Requires Rust (edition 2024).

bash
git clone https://github.com/wirespec-lang/wirespec.git
cd wirespec
cargo build --release
# Binary at target/release/wirespec

Your First .wspec File

The simplest real-world example is a UDP datagram header. Create examples/net/udp.wspec:

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],
}

This defines a UdpDatagram packet with:

  • Four u16 header fields (big-endian, per @endian big)
  • A runtime constraint: require length >= 8 (the header itself is 8 bytes)
  • A variable-length payload: bytes[length: length - 8] — a zero-copy slice of exactly length - 8 bytes

File Extension

wirespec uses .wspec as the file extension.

Compile to C

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

This generates two files:

  • build/net_udp.h — struct definitions and function declarations
  • build/net_udp.c — parse, serialize, and length functions

Compile to Rust

Pass -t rust to generate a Rust module instead:

bash
wirespec compile examples/net/udp.wspec -t rust -o build/
# Produces: build/net_udp.rs

The Rust output is a self-contained .rs file with struct definitions and parse / serialize functions that use no heap allocation.

Generated Code Overview

The generated header looks like this:

c
/* Auto-generated by wirespec compiler -- DO NOT EDIT */
#ifndef WIRESPEC_NET_UDP_H
#define WIRESPEC_NET_UDP_H

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

#include "wirespec_runtime.h"

typedef struct net_udp_udp_datagram net_udp_udp_datagram_t;

/* UdpDatagram */
struct net_udp_udp_datagram {
    uint16_t src_port;
    uint16_t dst_port;
    uint16_t length;
    uint16_t checksum;
    wirespec_bytes_t data;
};

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 *val,
    uint8_t *buf, size_t cap, size_t *written);

size_t net_udp_udp_datagram_serialized_len(const net_udp_udp_datagram_t *val);

#endif /* WIRESPEC_NET_UDP_H */

wirespec_bytes_t is a {const uint8_t *ptr; size_t len;} view into the input buffer — no copy, no allocation.

The 3 Generated Functions

Every wirespec type gets three functions:

_parse(buf, len, out, consumed) — Reads binary data into a struct. Returns WIRESPEC_OK on success or an error code (e.g. WIRESPEC_ERR_SHORT_BUFFER, WIRESPEC_ERR_CONSTRAINT) on failure. Sets *consumed to the number of bytes read.

_serialize(val, buf, cap, written) — Writes a struct back to binary. Returns WIRESPEC_OK or an error code. Sets *written to the number of bytes written.

_serialized_len(val) — Returns the exact byte count that _serialize will write. Use this to size your output buffer before calling serialize.

Build and Test

Include runtime/wirespec_runtime.h and compile the generated .c alongside your test code:

bash
cd build
gcc -Wall -Wextra -Werror -O2 -std=c11 -I../runtime \
    -o test_udp net_udp.c test_net_udp.c && ./test_udp

Key Concepts

  • Primitive types, bytes, arraysu8, u16le, bits[4], bytes[remaining], [T; count]Language Tour
  • Frames and capsules — Tagged unions (frame) and TLV containers (capsule) → Language Tour
  • State machines — Protocol state with typed transitions, guards, and actions → State Machines
  • Modules and imports — Multi-file projects with module and importModules

Next Steps

Continue to the Language Tour for a complete walkthrough of all wirespec features.