# CoAP Hello (UDP) — Rust MVP A minimal CoAP server over UDP in Rust, built on [`coap-lite`](https://crates.io/crates/coap-lite) (the RFC 7252 binary codec) and a [`tokio`](https://tokio.rs) `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 ```bash 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, !` | | 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: ```bash cargo run --release --bin coap-hello-udp ``` ```bash cargo run --release --bin bridge ``` Then open 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: ```bash 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: ```bash cargo run --bin client -- get "/hello?name=Imran" ``` ```bash cargo run --bin client -- post /hello "Imran" ``` Or with any other CoAP client, e.g. [`coap-cli`](https://www.npmjs.com/package/coap-cli): ```bash coap get "coap://localhost:5683/hello?name=Imran" ``` ## Test ```bash 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.