Skip to content

Commit 9dde403

Browse files
committed
docs: add framework integration recipes (tokio-util, embedded-io)
1 parent cf21256 commit 9dde403

2 files changed

Lines changed: 108 additions & 0 deletions

File tree

INTEGRATIONS.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Integrating with async & streaming frameworks
2+
3+
`cobs_codec_rs` deliberately keeps its core `#![no_std]` and dependency-free: it
4+
encodes, decodes, and frames over plain `&[u8]`, and nothing else. Adapters to a
5+
specific async runtime or I/O framework live in **your** project, built on the
6+
crate's public API — so the core never drags `tokio`, `bytes`, or an embedded
7+
HAL into everyone's dependency tree.
8+
9+
The recipes below are copy-paste starting points, **not** part of the published
10+
crate. Each is verified against the crate's real API.
11+
12+
## Tokio — a `tokio_util::codec` for `Framed` streams
13+
14+
A complete `Encoder` + `Decoder` that frames each packet as COBS plus a `0x00`
15+
delimiter, so it drops straight into `Framed` / `FramedRead` / `FramedWrite`
16+
over any `AsyncRead` / `AsyncWrite` (a TCP socket, or a serial port via
17+
`tokio-serial`, …).
18+
19+
In *your* `Cargo.toml`:
20+
21+
```toml
22+
[dependencies]
23+
cobs_codec_rs = "1"
24+
tokio-util = { version = "0.7", features = ["codec"] }
25+
bytes = "1"
26+
```
27+
28+
```rust
29+
use bytes::{Buf, BytesMut};
30+
use cobs_codec_rs::{cobs, max_encoded_len, DELIMITER};
31+
use tokio_util::codec::{Decoder, Encoder};
32+
33+
/// Frames each packet with COBS and a trailing `0x00` delimiter.
34+
#[derive(Default)]
35+
pub struct CobsFrameCodec;
36+
37+
impl Encoder<&[u8]> for CobsFrameCodec {
38+
type Error = std::io::Error;
39+
40+
fn encode(&mut self, item: &[u8], dst: &mut BytesMut) -> Result<(), Self::Error> {
41+
let start = dst.len();
42+
// Worst-case COBS output, plus one byte for the frame delimiter.
43+
dst.resize(start + max_encoded_len(item.len()) + 1, 0);
44+
let n = cobs::encode(item, &mut dst[start..]);
45+
dst[start + n] = DELIMITER;
46+
dst.truncate(start + n + 1);
47+
Ok(())
48+
}
49+
}
50+
51+
impl Decoder for CobsFrameCodec {
52+
type Item = Vec<u8>;
53+
type Error = std::io::Error;
54+
55+
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
56+
// A frame is everything up to the next delimiter; wait if it is absent.
57+
let Some(pos) = src.iter().position(|&b| b == DELIMITER) else {
58+
return Ok(None);
59+
};
60+
let frame = src.split_to(pos);
61+
src.advance(1); // discard the delimiter
62+
cobs::decode_to_vec(&frame)
63+
.map(Some)
64+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
65+
}
66+
}
67+
```
68+
69+
Then:
70+
71+
```rust
72+
use futures::{SinkExt, StreamExt};
73+
use tokio_util::codec::{FramedRead, FramedWrite};
74+
75+
// write side
76+
let mut w = FramedWrite::new(writer, CobsFrameCodec);
77+
w.send(&b"\x11\x00\x22"[..]).await?;
78+
79+
// read side — yields each decoded packet
80+
let mut r = FramedRead::new(reader, CobsFrameCodec);
81+
while let Some(packet) = r.next().await {
82+
let packet = packet?;
83+
// ...
84+
}
85+
```
86+
87+
## Embedded — `embedded-io` / `embedded-io-async`
88+
89+
The same shape works `no_std`. Scan an `embedded_io::Read` for the `0x00`
90+
delimiter into a fixed `[u8; N]`, then call `cobs::decode` into a decoded buffer
91+
— or `cobs::decode_in_place` to decode within the same buffer. Because the core
92+
needs no allocator, the whole path is allocation-free. The crate's
93+
`framing::FrameDecoder` already does the incremental delimiter scanning if you
94+
would rather reuse it than hand-roll the loop.
95+
96+
## What stays in the core
97+
98+
Everything you build on: `cobs` / `cobsr` encode/decode (with sentinel and
99+
in-place variants), `framing` for `0x00`-delimited streams, and the size helpers
100+
`max_encoded_len` / `encoding_overhead`. The adapters above are just the seam
101+
where your chosen framework meets that API.

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ Throughput on a 1 KiB payload (`cargo bench`, criterion), on an AMD Ryzen 7
170170
| COBS decode | ~1.40 GiB/s |
171171
| COBS/R encode | ~832 MiB/s |
172172

173+
## Integrations
174+
175+
The core stays `no_std` and dependency-free, so framework glue lives in your
176+
project rather than in the crate. [INTEGRATIONS.md](INTEGRATIONS.md) has verified
177+
copy-paste recipes — a `tokio_util::codec` for `Framed` streams, and the
178+
`embedded-io` shape for `no_std` targets — all built on the public API.
179+
173180
## Background
174181

175182
Stuart Cheshire and Mary Baker, "Consistent Overhead Byte Stuffing",

0 commit comments

Comments
 (0)