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).
git clone https://github.com/wirespec-lang/wirespec.git
cd wirespec
cargo build --release
# Binary at target/release/wirespecYour First .wspec File
The simplest real-world example is a UDP datagram header. Create examples/net/udp.wspec:
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
u16header 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 exactlylength - 8bytes
File Extension
wirespec uses .wspec as the file extension.
Compile to C
wirespec compile examples/net/udp.wspec -t c -o build/This generates two files:
build/net_udp.h— struct definitions and function declarationsbuild/net_udp.c— parse, serialize, and length functions
Compile to Rust
Pass -t rust to generate a Rust module instead:
wirespec compile examples/net/udp.wspec -t rust -o build/
# Produces: build/net_udp.rsThe 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:
/* 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:
cd build
gcc -Wall -Wextra -Werror -O2 -std=c11 -I../runtime \
-o test_udp net_udp.c test_net_udp.c && ./test_udpKey Concepts
- Primitive types, bytes, arrays —
u8,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
moduleandimport→ Modules
Next Steps
Continue to the Language Tour for a complete walkthrough of all wirespec features.