Files
coapwithudp/README.md
Md Imran Hossain 137b9d9547 Add CoAP-over-UDP hello server in Rust
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>
2026-09-03 10:28:24 +06:00

106 lines
4.0 KiB
Markdown

# 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, <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:
```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/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 `.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.