Expand description
§Reliable byte streams over COBS framing
A runtime-independent, no_std transport for an unreliable point-to-point
link, such as a serial line. Reliable implements Transport, a
poll-based byte-stream interface. It splits written bytes into packets,
frames them with Consistent Overhead Byte Stuffing (COBS), verifies them
with a checksum, acknowledges them, and retransmits lost ones. Packet
boundaries are internal: applications write and read bytes.
The Cargo package is cobs-io-async; the Rust import name is
cobs_io_async.
§Overview
| Item | Role |
|---|---|
Transport | Poll-based byte-stream trait, both for the underlying link you supply and for Reliable itself. |
Timer | Monotonic clock and wakeup source you supply for retransmission deadlines. |
Reliable | The connection. Wraps a Transport and a Timer. |
Config | Session identifier, retransmission timeout and retry limit. |
sync | In-memory COBS encoding and decoding between slices. |
This crate provides no adapters for any runtime and spawns no tasks. You
implement Transport for your serial port, socket or driver, and
Timer for your clock, and wrap them in whatever your runtime needs.
Neither trait requires Send, 'static or pinning.
Nothing happens unless the connection is polled. Retransmissions,
acknowledgments and incoming data are processed only inside
Transport::poll_read, Transport::poll_write,
Transport::poll_flush and Reliable::poll_progress. While none of
these is pending, call Reliable::poll_progress from a task of your own
to keep the connection alive.
§Behavior
| Operation | Behavior |
|---|---|
poll_write(buf) | Copies a nonempty prefix of buf into the outgoing packet and returns the number of bytes accepted, at most Reliable::MAX_PAYLOAD. Returns Pending while the previous packet is unacknowledged. |
poll_read(buf) | Copies verified payload bytes into buf. Packet boundaries are invisible. Returns Pending while no payload is available. |
poll_flush() | Completes when all accepted data is acknowledged by the peer and the underlying transport has been flushed. |
Empty buf | Returns Ready(Ok(0)) and creates no packet. |
| Corrupted or malformed frame | Discarded silently. Retransmission provides recovery. |
| Incoming payload slot full | New DATA is discarded without acknowledgment, so the peer retransmits it. Acknowledgments and duplicates are still processed. |
| Transport error, EOF, retries exhausted, sequence numbers exhausted | The connection fails permanently. See ConnectionError. |
| Dropping a future awaiting a poll method | Protocol state and accepted data are unaffected. Multi-task adapters must call Reliable::cancel_pending to preserve the other operations’ wakeups. |
Every poll method drives both directions, performing a bounded amount of
work per call. If work remains when the budget is spent, the task is woken
before Pending is returned, so an always-ready transport cannot
monopolize the executor.
§Cancellation
The connection owns all protocol state, so dropping a future never rolls
it back or corrupts accepted data. It does not detect the drop, however.
The underlying Transport and Timer wake only their latest poller,
so if the task that polled them last is canceled, another pending task may
never be woken.
An adapter that polls operations from separate tasks must therefore:
- Record which operation, as a
PendingOperation, returnedPending. - If its future is dropped, obtain exclusive access to the
Reliable, serialized with polling. - Call
Reliable::cancel_pendingfor that operation before abandoning it.
The call wakes each other stored waiter once, and those tasks poll again and re-register. Only one waiter is stored per operation, so concurrent futures for the same operation are not tracked separately, and a stale future must not cancel a newer one. Without the call, the stall remains possible.
§Underlying transport requirements
The link underneath Reliable must behave as a byte stream that may
lose, corrupt, insert or delete bytes, but must not reorder or delay them
beyond the retransmission timeout:
poll_readreturningOk(0)for a nonempty buffer means end of stream. Temporary lack of input must returnPending.poll_writereturningOk(0)for a nonempty buffer is an error.- A returned
Pendingmust have registered a wakeup.
§Protocol
Reliability is stop-and-wait in each direction independently: one unacknowledged outgoing packet and one retained incoming payload. This favors small, fixed memory over throughput.
Each packet is
0 | COBS(kind | session | sequence | payload | checksum) | 0with big-endian integers:
| Field | Size | Meaning |
|---|---|---|
kind | 1 | 1 for DATA, 2 for ACK. |
session | 8 | Session identifier from Config::session. |
sequence | 4 | DATA sequence number, starting at 0. An ACK echoes it. |
payload | 1 or more for DATA, absent for ACK | Application bytes. The length is implied by the frame boundary. |
checksum | 4 | CRC-32/ISO-HDLC over all preceding decoded fields. |
A receiver accepts DATA only if the session matches, the checksum is valid and the sequence is the next expected one. It acknowledges data once it has retained it, not once the application has read it. A repeat of the previous DATA is acknowledged again but never delivered twice. Any other DATA is dropped. A sender releases its packet only for an ACK with a matching session and sequence.
The receive path collects bytes until a zero delimiter and decodes only delimiter-terminated frames. Repeated zeros are padding. A frame longer than the connection’s buffer is discarded through its delimiter, and the following frame is processed normally.
§Limitations
- Both peers must be constructed with the same session identifier, chosen by the caller and fresh for each session. It distinguishes sessions; it is not authentication.
- There is no handshake or reconnection. Peer restarts, and packets delayed beyond a session, are outside the failure model. Sequence numbers alone do not handle them.
- Sequence numbers do not wrap. After 232 packets in one
direction the connection fails with
ConnectionError::SequenceExhausted; start a new session. - A peer that stops reading for longer than the retransmission budget
(
retransmit_timeout * (max_retries + 1)) causes the sender to fail withConnectionError::Timeout. - Each accepted write becomes its own packet. Small writes are not coalesced.
- There is no sliding window, adaptive timeout or fragmentation metadata.
§Example
The stubs below stand in for a real link and clock. A real
Timer::poll_deadline must register cx.waker() when the deadline is
in the future.
use cobs_io_async::{Config, Reliable, Timer, Transport};
let mut connection: Reliable<Link, Clock> =
Reliable::new(Link, Clock(0), Config::new(0xC0B5)).unwrap();
let mut cx = Context::from_waker(Waker::noop());
assert!(matches!(connection.poll_write(&mut cx, b"hello"), Poll::Ready(Ok(5))));
// The peer has not acknowledged the packet, so the flush stays pending.
assert!(connection.poll_flush(&mut cx).is_pending());§In-memory encoding and decoding
The sync module exposes the underlying COBS codec between slices. It
needs no allocation and is independent of Reliable.
COBS transforms a payload into an encoded body containing no zero bytes,
so zero can delimit frames. Payload zeros are encoded as data. An empty
payload encodes as [1], or [0, 1, 0] with surrounding delimiters. A
terminal full block of 254 nonzero payload bytes does not receive a
redundant trailing code byte.
Use max_encoding_length to size a buffer for an undelimited body, and
reserve two more bytes for delimiters, using checked arithmetic when sizes
come from untrusted or potentially large lengths.
COBS alone provides framing, not integrity or authenticity.
§Features
No features are enabled by default.
| Feature | Effect |
|---|---|
serde | Serialization and deserialization of Config, ConfigError, ConnectionError, DecodeError, SeekableError and DecodeProgress, subject to generic parameter bounds. Live connection state is not serializable. |
defmt | Compact diagnostic formatting for the same types. |
The crate is no_std and never allocates.
Modules§
- sync
- Synchronous COBS encoding and decoding between in-memory slices.
Structs§
- Config
- Parameters of a
Reliableconnection. - Decode
Progress - Progress reported with
DecodeError::InvalidFrame. - Reliable
- A reliable, ordered byte stream over an unreliable
Transport.
Enums§
- Config
Error - An invalid
Reliableconfiguration, rejected at construction. - Connection
Error - A terminal failure of a
Reliableconnection. - Decode
Error - A failure to decode a frame.
- Pending
Operation - An operation of a
Reliableconnection that can be pending. - Seekable
Error - A bounds error involving a slice-backed buffer.
Traits§
- Timer
- A monotonic clock and wakeup source for retransmission deadlines.
- Transport
- A poll-based, runtime-independent byte-stream interface.
Functions§
- max_
encoding_ length - Returns the maximum encoded body length for
source_lenpayload bytes. - max_
encoding_ overhead - Returns the maximum additional bytes needed for an undelimited encoding.