Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ coverage/
# Dependency cache
deps/
vendor/

# Blog drafts (not part of the repo)
.blog/
32 changes: 32 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
FROM alpine:3.21 AS builder

# Install Zig 0.15.2 and build dependencies.
RUN apk add --no-cache curl xz && \
curl -L https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz | \
tar -xJ -C /opt && \
mv /opt/zig-x86_64-linux-0.15.2 /opt/zig
ENV PATH="/opt/zig:${PATH}"

WORKDIR /src
COPY build.zig build.zig.zon ./
COPY src/ src/
COPY schemas/ schemas/

RUN zig build -Doptimize=ReleaseSafe -Dtarget=x86_64-linux

# --- Runtime stage ---
FROM alpine:3.21

RUN addgroup -S protomq && adduser -S protomq -G protomq

WORKDIR /opt/protomq

COPY --from=builder /src/zig-out/bin/protomq-server ./bin/protomq-server
COPY --from=builder /src/zig-out/bin/protomq-cli ./bin/protomq-cli
COPY schemas/ ./schemas/

USER protomq

EXPOSE 1883

ENTRYPOINT ["./bin/protomq-server"]
69 changes: 69 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Features

This document covers ProtoMQ's features in more depth than the README. If you're looking for deployment guides and configuration, check [FAQ.md](FAQ.md).

---

## Service Discovery

ProtoMQ includes a built-in Service Discovery mechanism. Clients can discover available topics and their associated Protobuf schemas — including the full `.proto` source code — by querying the `$SYS/discovery/request` topic.

This lets new clients bootstrap themselves in a single round-trip without any out-of-band configuration or pre-shared schema files.

**Using the CLI:**

```bash
protomq-cli discover --proto-dir schemas
```

Under the hood, the client subscribes to `$SYS/discovery/request` and receives a `ServiceDiscoveryResponse` message containing every registered topic-schema mapping. The response includes the raw `.proto` source so clients can dynamically configure their own decoding logic.

---

## Admin Server

An optional HTTP server for runtime schema management and telemetry. Disabled by default — when the build flag is off, the HTTP code is **completely stripped from the binary** (zero overhead, not just disabled).

### Enabling it

```bash
zig build -Dadmin_server=true run-server
```

### Build comparison

| Build | Memory baseline | Admin API |
|---|---|---|
| `zig build` | ~2.6 MB | ✗ |
| `zig build -Dadmin_server=true` | ~4.0 MB | ✓ |

The Admin Server runs cooperatively on the same event loop as the MQTT broker — enabling it does **not** degrade per-message MQTT performance.

### Endpoints

All endpoints require `Authorization: Bearer <ADMIN_TOKEN>` (defaults to `admin_secret`, configurable via the `ADMIN_TOKEN` environment variable).

| Method | Path | Description |
|---|---|---|
| `GET` | `/metrics` | Active connections, message throughput, loaded schemas |
| `GET` | `/api/v1/schemas` | Current topic-to-schema mappings |
| `POST` | `/api/v1/schemas` | Register a new `.proto` schema and map it to a topic at runtime |

### Dynamic schema registration

With the Admin Server enabled, you can register schemas at runtime without restarting the broker:

```bash
curl -X POST http://127.0.0.1:8080/api/v1/schemas \
-H "Authorization: Bearer ${ADMIN_TOKEN:-admin_secret}" \
-H "Content-Type: application/json" \
-d '{
"topic": "telemetry/gps",
"message_type": "GpsCoordinate",
"proto_file_content": "syntax = \"proto3\";\nmessage GpsCoordinate { float lat = 1; float lon = 2; }"
}'
```

The schema is parsed in-process and persisted to disk as `schemas/<MessageType>.proto`. The mapping is live immediately — no restart needed.

> **Security note**: The Admin Server binds to `127.0.0.1:8080` only. If you need remote access, use an SSH tunnel or reverse proxy.
149 changes: 86 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,100 +2,123 @@
<h1 align="center">ProtoMQ</h1>

<p align="center">
<img src="assets/mascot.png" alt="ProtoMQ Mascot" width="300px" />
<img src="assets/mascot.png" alt="ProtoMQ Mascot" width="280px" />
<br />
<b>Type-safe, bandwidth-efficient MQTT for the rest of us.</b>
<b>MQTT's simplicity. Protobuf's efficiency. Zig's bare-metal performance.</b>
<br />
<i>Stop sending bloated JSON over the wire.</i>
Built for IoT and edge computing.
</p>

<p align="center">
<a href="#quick-start">Quick Start</a> •
<a href="#why-protomq">Why ProtoMQ</a> •
<a href="#performance">Performance</a> •
<a href="FEATURES.md">Features</a> •
<a href="FAQ.md">FAQ</a>
</p>

---
- MQTT v3.1.1 packet parsing (CONNECT, PUBLISH, SUBSCRIBE, etc.)
- Thread-safe Topic Broker with wildcard support (`+`, `#`)
- Custom Protobuf Engine with runtime `.proto` schema parsing
- Topic-based Protobuf schema routing
- Service Discovery & Schema Registry
- CLI with automatic JSON-to-Protobuf encoding
- Structured diagnostic output for Protobuf payloads

### Building
ProtoMQ is an MQTT broker that enforces **Protobuf schemas at the broker level**. All messages on the wire are Protobuf — the broker validates incoming payloads against registered `.proto` schemas and rejects anything that doesn't conform. The bundled CLI can accept JSON and encode it to Protobuf client-side for convenience.

One have to have Zig 0.15.2 or later installed. Please download it from [here](https://ziglang.org/download/).
<p align="center">
<img src="assets/terminal_demo.svg" alt="ProtoMQ terminal demo" width="780px" />
</p>

```bash
# Build server and client
zig build
- **Schema-enforced messaging** — `.proto` files define the contract. Malformed payloads get rejected *before* they reach subscribers.
- **Custom Protobuf engine** — written from scratch in Zig. Runtime `.proto` parsing, zero external dependencies.
- **Wildcard topic routing** — full MQTT `+` and `#` wildcard support via a trie-based topic broker.
- **Service Discovery** — clients query `$SYS/discovery/request` to discover topics and download schemas on the fly. No pre-shared `.proto` files needed.
- **Optional Admin HTTP API** — register new schemas and topic mappings at runtime, monitor connections and throughput. Disabled by default, zero overhead when off. See [FEATURES.md](FEATURES.md) for details.
- **Runs in 2.6 MB** — the entire broker with 100 active connections fits in under 3 MB of memory.

# Build and run server
zig build run-server
---

# Build and run client
zig build run-client
### Why ProtoMQ

# Run tests
zig build test
If you've worked with IoT sensor fleets, you've probably been through this: you start with JSON over MQTT because it's easy to debug, every language has a parser, and `mosquitto_sub` lets you eyeball what's going on. It works fine... until you start caring about bandwidth.

# Run all integration tests
./tests/run_all.sh
```
A 12-field sensor reading weighs around 310 bytes in JSON. The same data in Protobuf: 82 bytes. On a cellular-connected device pushing telemetry every 5 seconds, that gap adds up to roughly 1.6 MB/day per device — multiply by a few thousand devices and the data bill starts hurting.

### Limitations
<p align="center">
<img src="assets/payload_comparison.svg" alt="JSON vs Protobuf payload size comparison" width="680px" />
</p>

For the initial release, we support:
- QoS 0 only (at most once delivery)
- No persistent sessions
- No retained messages
- Single-node deployment
But switching to Protobuf usually means code generation per language, keeping stubs in sync across firmware versions, and losing the ability to just read your payloads. ProtoMQ takes a different approach: the broker owns the `.proto` schemas and validates every message against them. The CLI can accept JSON and encode it to Protobuf before publishing, so you get a human-friendly workflow without sacrificing wire efficiency.

### Service Discovery
| | Plain MQTT + JSON | ProtoMQ |
|---|---|---|
| Schema enforcement | None — anything goes | Validated at every `PUBLISH` |
| Payload format | JSON (~170 bytes, 8 fields) | Protobuf (~48 bytes) |
| Client bootstrap | Pre-shared docs | Built-in Service Discovery |
| Code generation | Required per language | CLI encodes JSON → Protobuf for you |

ProtoMQ includes a built-in Service Discovery mechanism. Clients can discover available topics and their associated Protobuf schemas (including the full source code) by querying the `$SYS/discovery/request` topic.
---

**Using the CLI for discovery:**
```bash
# Verify schemas are loaded and available
protomq-cli discover --proto-dir schemas
```
This allows clients to "bootstrap" themselves without needing pre-shared `.proto` files.
### Under the Hood

### Admin Server
ProtoMQ is not a wrapper around an existing broker — it's a ground-up implementation. Here's what makes it tick:

ProtoMQ includes an optional HTTP Admin Server for runtime observability and dynamic schema management without polluting the core MQTT hot-paths.
- **`epoll` / `kqueue` event loop** — single-threaded, no abstraction layer. The network layer talks directly to the OS kernel I/O primitives. On Linux that's `epoll`, on macOS `kqueue`. No libuv, no tokio, no hidden threads.
- **One allocator, full control** — every allocation goes through Zig's `std.mem.Allocator`. No GC, no hidden heap churn, no runtime. You can trace every byte the broker touches.
- **Zero third-party dependencies** — the MQTT parser, TCP connection handler, Protobuf wire format encoder, `.proto` file parser — all written in Zig using only the standard library. `build.zig.zon` has an empty `dependencies` block.
- **Runtime schema registry** — `.proto` files are parsed at startup and mapped to MQTT topics. With the Admin Server enabled, you can register new schemas and mappings at runtime over HTTP without restarting the broker.
- **Comptime-generated lookup tables** — the MQTT packet parser uses Zig's `comptime` to build dispatch tables at compile time. No branching, no hash maps — just array indexing.
- **Cross-compilation** — `zig build -Dtarget=aarch64-linux` produces a Raspberry Pi binary from a Mac. One command, no toolchain headaches.

- **Dynamic Schema Registration**: Register `.proto` files at runtime via `POST /api/v1/schemas`.
- **Telemetry**: Monitor active connections, message throughput, and schemas via `GET /metrics`.
- **Zero Overhead Footprint**: The Admin Server is disabled by default to preserve the absolute minimum memory footprint for embedded devices. It is strictly conditionally compiled via the `zig build -Dadmin_server=true` flag. Enabling it moderately increases the initial static memory baseline (e.g., from ~2.6 MB to ~4.0 MB) by safely running a parallel HTTP listener, but it executes cooperatively on the same event loop ensuring zero degradation to per-message MQTT performance. When the flag is deactivated, it incurs **zero overhead footprint**.
---

### Performance Results
### Quick Start

ProtoMQ delivers high performance across both high-end and edge hardware:
**Docker:**

| Scenario | Apple M2 Pro | Raspberry Pi 5 |
|----------|--------------|----------------|
| Latency (p99, 100 clients) | 0.44 ms | 0.13 ms |
| Concurrent clients | 10,000 | 10,000 |
| Sustained throughput | 9k msg/s | 9k msg/s |
| Message throughput (small) | 208k msg/s | 147k msg/s |
| Memory (100 clients) | 2.6 MB | 2.5 MB |
```bash
docker compose up
```

Handles 100,000 connection cycles with zero memory leaks and sub-millisecond latency.
The server starts on port `1883` with the schemas from `schemas/`. Connect with any MQTT client.

For detailed methodology and full results, see [ProtoMQ Benchmarking Suite](benchmarks/README.md).
**From source** (requires [Zig 0.15.2+](https://ziglang.org/download/)):

### Contributing
```bash
git clone https://github.com/electricalgorithm/protomq.git
cd protomq
zig build run-server
```

This is currently a learning/development project. Contributions will be welcome after the MVP is complete.
```bash
# In another terminal — publish (CLI encodes JSON to Protobuf for you)
zig build run-client -- publish --topic sensors/temp \
--json '{"device_id":"s-042","temperature":22.5,"humidity":61,"timestamp":1706140800}'

### License
# In another terminal — subscribe
zig build run-client -- subscribe --topic "sensors/#"
```

The project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
---

### Resources
### Performance

- [Zig Documentation](https://ziglang.org/documentation/master/)
- [MQTT v3.1.1 Specification](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html)
- [Protocol Buffers](https://protobuf.dev/)
ProtoMQ handles **208,000 messages/second** on an Apple M2 Pro and **147,000 msg/s** on a Raspberry Pi 5 — with sub-millisecond p99 latency and no memory leaks across 100,000 connection cycles.

| Scenario | Apple M2 Pro | Raspberry Pi 5 |
|----------|--------------|----------------|
| **p99 latency** (100 clients) | 0.44 ms | 0.13 ms |
| **Throughput** (10-byte msgs) | 208k msg/s | 147k msg/s |
| **Throughput** (64 KB msgs) | 39k msg/s | 27k msg/s |
| **Sustained load** (10 min) | 8,981 msg/s | 9,012 msg/s |
| **Memory** (100 connections) | 2.6 MB | 2.5 MB |
| **Connection churn** (100k cycles) | 1,496 conn/s | 1,548 conn/s |
| **Memory leaks** | 0 MB | 0 MB |

All benchmarks run on loopback, `ReleaseSafe` mode, Zig 0.15.2. Methodology and raw results: [`benchmarks/`](benchmarks/README.md).

---

**Note**: This project is under active development. The API and architecture may change significantly.
### Current Limitations

QoS 0 only (at most once delivery), no persistent sessions, no retained messages, single-node deployment. These are scope decisions for the initial release — multi-node and QoS 1/2 are on the roadmap.

### Contributing

Contributions are welcome. If you're interested in MQTT internals, Protobuf wire format, or systems programming in Zig, there's plenty to dig into. See [FEATURES.md](FEATURES.md) for the full feature set and [FAQ.md](FAQ.md) for deployment and configuration guides.
71 changes: 71 additions & 0 deletions assets/payload_comparison.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading