A minimal CoAP (RFC 7252) server built on coap-lite for message encoding and a tokio UdpSocket for transport. The socket, read loop and routing are kept in src/main.rs rather than behind a framework. Routes: GET /hello[?name=X] -> 2.05 "Hello, X!" (defaults to World) POST /hello -> 2.01, name from a raw text or JSON body GET /health -> 2.05 status + uptime Unknown paths return 4.04, known paths with the wrong method 4.05. Includes a CLI client (src/bin/client.rs) for smoke tests and unit tests covering the greeting, payload parsing and query decoding. Verified against node-coap as an independent implementation to confirm wire compatibility. The earlier Node.js prototype is kept under nodejs/ for reference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.0 KiB
CoAP Hello (UDP) — Rust MVP
A minimal CoAP server over UDP in Rust, built on coap-lite
(the RFC 7252 binary codec) and a tokio UdpSocket.
CoAP is "HTTP semantics on a UDP datagram" — the same methods, paths and query
strings, but each exchange is one compact binary datagram. Response codes are
written class.detail: 2.05 Content, 2.01 Created, 4.00 Bad Request,
4.04 Not Found, 4.05 Method Not Allowed.
coap-lite does message encoding only — the socket, the read loop and the
routing are all in src/main.rs, so nothing about the UDP layer is hidden.
Run
cargo run --release
Listens on udp://0.0.0.0:5683 (CoAP's IANA-assigned port).
Routes
| Method | Path | Response |
|---|---|---|
| GET | /hello |
2.05 Hello, World! |
| GET | /hello?name=Imran |
2.05 Hello, Imran! |
| POST | /hello |
2.01 Hello, <payload>! |
| GET | /health |
2.05 {"status":"ok","uptime":12} |
POST /hello takes either a raw text payload (Imran) or JSON
({"name":"Imran"}). An empty payload returns 4.00. An unknown path returns
4.04; a known path with the wrong method returns 4.05.
Try it
With the bundled client:
cargo run --bin client -- get "/hello?name=Imran"
cargo run --bin client -- post /hello "Imran"
Or with any other CoAP client, e.g. coap-cli:
coap get "coap://localhost:5683/hello?name=Imran"
Test
cargo test
Five unit tests cover the greeting, payload parsing (text + JSON) and query decoding.
Layout
| Path | What it is |
|---|---|
src/main.rs |
The server: socket, read loop, route table, helpers. |
src/bin/client.rs |
CLI client for smoke tests. |
nodejs/ |
The earlier Node.js version, kept for reference. |
Config
| Env var | Default |
|---|---|
COAP_PORT |
5683 |
COAP_HOST |
0.0.0.0 |
Design notes
- One task, no per-request spawn. Handling a datagram is pure CPU work with
no
.awaitinside, so it runs inline on the receive loop — no task-spawn overhead, and replies keep their arrival order. Spawn per datagram once a handler does real I/O (a database, an upstream call). - A fixed 1152-byte receive buffer, reused across requests, so the steady state allocates only for the response body. 1152 is the RFC 7252 guidance for staying inside an unfragmented IPv6 datagram.
- Hand-rolled JSON field read.
json_string_fieldhandles a flat{"name":"..."}and nothing more; it exists to avoid aserde_jsondependency for one field. Swap it out the moment bodies get real structure. - Lenient percent-decoding. RFC 7252 §6.4 says the client decodes before
splitting a URI into options, so a strict client sends
name=Md Imranalready decoded. Not all clients do, so the server decodes again — meaning a literal%in a name would be misread. Droppercent_decodeif your clients are known-conformant.
Next steps
- DTLS. Plaintext CoAP has no encryption or authentication. Production uses
coaps://on port 5684 — seewebrtc-dtlsor a gateway likelibcoap. - Block-wise transfer (RFC 7959) for payloads past ~1 KB;
coap-liteships aBlockHandlerfor this. - Observe (RFC 7641) for server-pushed updates;
coap-liteprovidesSubjectandcreate_notification. - Deduplication. A CoAP server should track recent message IDs and replay the cached response, since UDP clients retransmit. Fine to skip for idempotent greetings, needed once a POST has side effects.