Md Imran Hossain bd7ac94e74 Add CoAP request/response test suite over UDP
Exercises every route against a running server using the bundled client,
so the request/response path can be checked without the HTTP bridge.

Covers /hello with and without a name, percent- and plus-encoded query
values, text and JSON POST bodies, and the 4.00/4.04/4.05 error paths.
/health is matched by shape rather than an exact string since uptime
moves. Exits non-zero on failure; COAP_HOST/COAP_PORT retarget it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 11:45:03 +06:00

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.

Check it in a browser

Browsers speak HTTP over TCP; this server speaks CoAP over UDP, so coap:// URLs cannot be opened directly. src/bin/bridge.rs is a small HTTP -> CoAP proxy that closes the gap. Run both:

cargo run --release --bin coap-hello-udp
cargo run --release --bin bridge

Then open http://localhost:8080/ for a test console with a name field and a button per route. Every response shows both the CoAP code and the HTTP status it was mapped to; the raw CoAP code is also returned in an X-Coap-Code header.

These are now ordinary URLs — clickable, curl-able, bookmarkable:

URL Response
http://localhost:8080/ the test console
http://localhost:8080/hello Hello, World!
http://localhost:8080/hello?name=Imran Hello, Imran!
http://localhost:8080/health {"status":"ok","uptime":12}

POST needs a body, so it is easiest from the console page or curl:

curl -X POST -d "Imran" http://localhost:8080/hello

The bridge is a development convenience: a deliberately small HTTP/1.1 subset with no keep-alive, no chunked bodies and no TLS. Do not expose it. Set HTTP_PORT if 8080 is taken.

Try it from the CLI

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/lib.rs Shared CoAP client used by the CLI and the bridge.
src/bin/client.rs CLI client for smoke tests.
src/bin/bridge.rs HTTP -> CoAP proxy so a browser can reach the server.
static/index.html The browser test console the bridge serves at /.
nodejs/ The earlier Node.js version, kept for reference.

Config

Env var Default Used by
COAP_PORT 5683 server, client, bridge
COAP_HOST 0.0.0.0 server, client, bridge
HTTP_PORT 8080 bridge

Design notes

  • One task, no per-request spawn. Handling a datagram is pure CPU work with no .await inside, 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_field handles a flat {"name":"..."} and nothing more; it exists to avoid a serde_json dependency 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 Imran already decoded. Not all clients do, so the server decodes again — meaning a literal % in a name would be misread. Drop percent_decode if your clients are known-conformant.

Next steps

  • DTLS. Plaintext CoAP has no encryption or authentication. Production uses coaps:// on port 5684 — see webrtc-dtls or a gateway like libcoap.
  • Block-wise transfer (RFC 7959) for payloads past ~1 KB; coap-lite ships a BlockHandler for this.
  • Observe (RFC 7641) for server-pushed updates; coap-lite provides Subject and create_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.
Description
No description provided
Readme 100 KiB
Languages
Rust 53.5%
HTML 19.8%
Python 18.7%
Shell 8%