BitChat for systems that care about correctness.
A pure-Zig mesh server and protocol library with explicit bounds, no runtime heap growth in the core, no C or third-party dependencies.
Targets Windows and Linux. Embeddable by design.
Requires Zig 0.16.
zig build
zig build run -- help
zig build testRun a mesh server named alice, and greet the first peer once:
zitchat serve alice "hello world"Other tools
Encode an announce frame and immediately decode it:
zitchat encode-announce alice | xargs zitchat decodeRun a mesh server while capturing every frame:
zitchat serve --trace-file capture.hexreplay the capture later:
while read frame; do zitchat decode "$frame"; done < capture.hex| Command | Description |
|---|---|
help |
Show the command list, or zitchat help <command> for one command |
version |
Print semantic version and the git commit the binary was built from |
adapters |
List Bluetooth adapters and radios |
probe |
Check whether BLE hardware is present |
scan |
Watch for BLE advertisements for ten seconds |
serve |
Run the mesh server: announce, relay, and log peer traffic |
rotate-identity |
Replace the on-disk identity, keeping the previous one as <path>.bak |
encode-announce |
Print a hex-encoded announce frame |
encode-message |
Print a hex-encoded public message frame |
decode |
Decode a hex-encoded frame field by field |
zitchat <command> --help documents a single command, including its flags and examples.
Global flags (-v, -vv, --log-level <level>, --trace-file <path>, --version) are accepted
anywhere on the command line.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Unexpected internal error |
| 2 | Usage / invalid arguments / invalid content |
| 3 | probe: no Bluetooth radio |
| 4 | Transport / adapter / BLE operator failure |
| 5 | Protocol / identity / courier / undecodable frame |
Logging defaults to info.
--log-level <err|warn|info|debug>- set the runtime filter exactly-v/--verbose- adds debug logs-vv/--trace- additionally hex-dumps every raw frame as it crosses the transport (bounded to the first 256 bytes, liketcpdump -s)
--trace-file <path> writes every frame in full as a bare hex line, flushed immediately. A run that ends in a crash still leaves a complete capture, and each line feeds straight back into decode:
The Linux backend talks to BlueZ over the system D-Bus as an ordinary user. No root privileges are needed to run zitchat serve itself under the default BlueZ D-Bus policy.
Important! Before running against a real phone, disable bluetoothd's built-in battery plugin. It auto-reads a newly connected peer's Battery Level characteristic. iOS will answer with insufficient authentication which makes BlueZ start SMP pairing and raise a "Bluetooth Pairing Request" dialog on the phone which is not desirable. While that dialog is pending, the link never reaches connected.
Create /etc/systemd/system/bluetooth.service.d/10-no-battery-plugin.conf containing:
[Service]
ExecStart=
ExecStart=/usr/libexec/bluetooth/bluetoothd -P batteryNOTE: The bluetoothd path may differ per distribution. The example above is Debian/Raspberry Pi OS. Then apply it:
sudo systemctl daemon-reload && sudo systemctl restart bluetoothBlueZ 5.62 or newer is expected. Zitchat reads a characteristic's
GattCharacteristic1.MTU property, which older BlueZ releases do not expose.
serve can expose a localhost-only WebSocket control plane so an external client such as an LLM, web UI, robot bridge, smoke test, etc. sees every server event and can send commands back. docs/CONTROL_PROTOCOL.md contains the protocol.
zitchat serve alice --control-port 8737
zitchat serve alice --control-port 8737 --control-token s3cretTwo clients ship with the repo:
A simple zitchat smoke test that uses control plane
# connect, send one message, assert the ack
zig build ws-smoke -- 8737 A control plane HTML page with a connect form and live event log. Useful for observing traffic.
In addition to zig build test:
zig build fuzzis decoder-level fuzzing (generate-then-mutate).zig build dstis a TigerBeetle-VOPR-style simulator with many protocol-accurate simulated phones over a simulated BLE radio with no hardware required. It uses virtual time (ex: a 10 virtual-minute run takes a few wall-clock seconds) and is fully reproducible from a seed. The simulated radio injects loss, duplication, corruption, truncation, disconnect storms, address rotation, connection drops, and some malformed frames.
zig build fuzz -- smoke # run every fuzz target once, quickly
zig build fuzz -- packet 123 # one target, a fixed reproducible seed
zig build dst # run simulation index 0 from the embedded seed file
zig build dst -- --seed 42 # quick fuzz mode: any u64 is a new universe
zig build dst -Drelease -- --index 0 # ~6x faster, identical digest -- soak with thisThe full flag reference and how to read a DST report are in this Testing section. Soak scripts live under util/*_soak.sh.
Canonical service and characteristic UUIDs are taken from the upstream BLEService.swift:
| Purpose | UUID |
|---|---|
| Mainnet service | F47B5E2D-4A9E-4C5A-9B3F-8E1D2C3A4B5C |
| Characteristic | A1B2C3D4-E5F6-4A5B-8C9D-0E1F2A3B4C5D |
- Explicit bounds and assertions everywhere
- No runtime heap growth in the core paths
- Pure Zig with no hidden dependencies
- Provide a useful tool and a reference implementation
Bug reports, protocol questions, and carefully scoped patches are welcome.
Please keep changes in the spirit of Tiger Style.
MIT