Skip to main content

Crate cobs_io_async

Crate cobs_io_async 

Source
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

ItemRole
TransportPoll-based byte-stream trait, both for the underlying link you supply and for Reliable itself.
TimerMonotonic clock and wakeup source you supply for retransmission deadlines.
ReliableThe connection. Wraps a Transport and a Timer.
ConfigSession identifier, retransmission timeout and retry limit.
syncIn-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

OperationBehavior
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 bufReturns Ready(Ok(0)) and creates no packet.
Corrupted or malformed frameDiscarded silently. Retransmission provides recovery.
Incoming payload slot fullNew DATA is discarded without acknowledgment, so the peer retransmits it. Acknowledgments and duplicates are still processed.
Transport error, EOF, retries exhausted, sequence numbers exhaustedThe connection fails permanently. See ConnectionError.
Dropping a future awaiting a poll methodProtocol 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:

  1. Record which operation, as a PendingOperation, returned Pending.
  2. If its future is dropped, obtain exclusive access to the Reliable, serialized with polling.
  3. Call Reliable::cancel_pending for 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_read returning Ok(0) for a nonempty buffer means end of stream. Temporary lack of input must return Pending.
  • poll_write returning Ok(0) for a nonempty buffer is an error.
  • A returned Pending must 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) | 0

with big-endian integers:

FieldSizeMeaning
kind11 for DATA, 2 for ACK.
session8Session identifier from Config::session.
sequence4DATA sequence number, starting at 0. An ACK echoes it.
payload1 or more for DATA, absent for ACKApplication bytes. The length is implied by the frame boundary.
checksum4CRC-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 with ConnectionError::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.

FeatureEffect
serdeSerialization and deserialization of Config, ConfigError, ConnectionError, DecodeError, SeekableError and DecodeProgress, subject to generic parameter bounds. Live connection state is not serializable.
defmtCompact 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 Reliable connection.
DecodeProgress
Progress reported with DecodeError::InvalidFrame.
Reliable
A reliable, ordered byte stream over an unreliable Transport.

Enums§

ConfigError
An invalid Reliable configuration, rejected at construction.
ConnectionError
A terminal failure of a Reliable connection.
DecodeError
A failure to decode a frame.
PendingOperation
An operation of a Reliable connection that can be pending.
SeekableError
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_len payload bytes.
max_encoding_overhead
Returns the maximum additional bytes needed for an undelimited encoding.