# framed-codec

**Cancel-safe, zero-allocation-per-frame length-prefixed framing for tokio byte streams.**

A small, self-contained building block for streaming binary telemetry
(drone/robot links, sensor feeds, any binary wire protocol) over
anything that is `AsyncRead + AsyncWrite` — TCP, a Unix socket, or an
in-process pipe.

## The problem

Two bugs recur in hand-rolled async framing code:

1. **Torn frames across packet boundaries.** A `read()` returns whatever
   bytes happened to arrive — frequently *half* a frame. Code that
   assumes "one read = one frame" silently corrupts the stream.
2. **Lost data on cancellation.** `read_exact()` into a stack buffer
   inside a `tokio::select!` is **not cancel-safe**: if another branch
   wins, the partially-read bytes on the stack are discarded and the
   stream is desynchronised permanently.

## The approach

A length-prefixed [`tokio_util::codec`] `Decoder`/`Encoder`. All
partial-read state lives inside the `Framed` buffer, never on the stack,
so the read future (`StreamExt::next`) **is** cancel-safe — a `select!`
can drop it mid-frame and the next poll resumes exactly where it left
off. The decoder validates the declared length *before* indexing, so a
hostile or corrupt length is a clean protocol error, never a panic or an
OOM. Decoding a frame produces a `Copy` value with no per-frame heap
allocation.

`run_link` is the production-shaped read loop: decode frames, reset a
liveness heartbeat on every frame, and race the read against a heartbeat
deadline and a `CancellationToken` — mapping silence to a safe-state
transition instead of an indefinite hang.

## Run it

```bash
cargo run --bin demo     # end-to-end: split frame + heartbeat → safe state
cargo test               # unit + integration + property tests
```

The demo uses an in-process `tokio::io::duplex` pipe (no sockets, no
ports — runs anywhere). It deliberately splits one frame across two
writes with a stall between them to prove the receiver reconstructs it
intact, then goes silent to show the heartbeat tripping a safe state.

## What the tests prove

- `decodes_only_when_complete_even_byte_by_byte` — a frame fed one byte
  at a time decodes exactly once, on the final byte.
- `many_frames_arbitrary_chunking` — 50 frames fed in 7-byte chunks all
  reconstruct in order.
- `rejects_out_of_bounds_length_without_panic` — a ~4 GiB declared
  length is a clean `InvalidData` error.
- `no_torn_frame_across_select_cancellation` — the headline guarantee: a
  frame split across the wire, with a `select!` that cancels the read
  mid-frame, still decodes intact.
- `roundtrip_any_split` (proptest) — any frame, split at any boundary,
  round-trips bit-exact.

## License

MIT OR Apache-2.0.
