Skip to content

Commit 02fcee7

Browse files
committed
Initial release: cobs_codec 1.0.0
COBS and COBS/R byte-stuffing codecs for Dart and Flutter, with dart:convert integration and 0x00-delimited stream framing for serial/UART links. - Basic COBS + COBS/R encode/decode, byte-identical to the reference implementations (differential-tested against cobs-python on 20k vectors). - Codec/Converter API with chunked conversion; fuses with json/utf8/base64. - CobsFrameEncoder/CobsFrameDecoder stream transformers: reassemble packets across arbitrary chunk boundaries, propagate pause/resume/cancel, and bound buffering via maxFrameLength. - 45 tests, strict analysis, pana 160/160. Signed-off-by: Alexander Salas Bastidas <ajsb85@firechip.dev>
0 parents  commit 02fcee7

20 files changed

Lines changed: 1908 additions & 0 deletions

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Dart / Pub
2+
.dart_tool/
3+
.packages
4+
build/
5+
6+
# Library packages should not commit the lockfile.
7+
pubspec.lock
8+
9+
# Coverage
10+
coverage/
11+
.test_coverage.json

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Changelog
2+
3+
All notable changes to this package are documented here. This project adheres
4+
to [Semantic Versioning](https://semver.org).
5+
6+
## 1.0.0
7+
8+
Initial release.
9+
10+
### Added
11+
12+
- **Basic COBS** encoding and decoding: the [cobs] codec plus the `cobsEncode`
13+
and `cobsDecode` functions.
14+
- **COBS/R (Reduced)** encoding and decoding: the `cobsr` codec plus the
15+
`cobsrEncode` and `cobsrDecode` functions. COBS/R often removes the trailing
16+
overhead byte for smaller messages.
17+
- **`dart:convert` integration**: `CobsCodec` / `CobsrCodec` extend `Codec`, and
18+
the encoders/decoders extend `Converter` with chunked-conversion support, so
19+
they compose with `fuse`, `Stream.transform`, and other codecs.
20+
- **Stream framing** for `0x00`-delimited links (serial, UART, USB-CDC):
21+
`cobsFrame`, `cobsUnframe`, and the `CobsFrameEncoder` / `CobsFrameDecoder`
22+
stream transformers. `CobsFrameDecoder` reassembles packets across arbitrary
23+
chunk boundaries, propagates pause/resume/cancel to the source (even during a
24+
delimiter-less run), supports a `maxFrameLength` bound against unbounded
25+
buffering on a noisy or malicious link, and offers configurable handling of
26+
empty and malformed frames.
27+
- **Size helpers**: `encodingOverhead` and `maxEncodedLength` for buffer
28+
pre-allocation.
29+
- Extensive test suite, including the golden vectors from the reference COBS and
30+
COBS/R implementations.
31+
32+
[cobs]: https://pub.dev/documentation/cobs_codec/latest/cobs_codec/cobs-constant.html

CONTRIBUTING.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Contributing to cobs_codec
2+
3+
Thanks for your interest in improving `cobs_codec`! This document explains how to
4+
build, test, and submit changes.
5+
6+
## Code of conduct
7+
8+
Be respectful and constructive. By participating you agree to keep discussions
9+
professional and welcoming.
10+
11+
## Getting started
12+
13+
You need the [Dart SDK](https://dart.dev/get-dart) `>= 3.5.0` (the package is
14+
pure Dart and works with Flutter too).
15+
16+
```console
17+
git clone https://github.com/firechip/cobs_codec.git
18+
cd cobs_codec
19+
dart pub get
20+
```
21+
22+
## Development workflow
23+
24+
Before opening a pull request, make sure all of the following pass — this is the
25+
exact bar CI and `pub.dev` enforce:
26+
27+
```console
28+
dart format . # formatting (must produce no changes)
29+
dart analyze # static analysis (must report no issues)
30+
dart test # unit tests (must be all green)
31+
dart run example/cobs_codec_example.dart # example still runs
32+
```
33+
34+
For a release-readiness check, run the pub scorer:
35+
36+
```console
37+
dart pub global activate pana
38+
dart pub global run pana --no-warning .
39+
```
40+
41+
The package targets a perfect **160/160** pana score; please don't regress it.
42+
43+
## Correctness bar
44+
45+
COBS and COBS/R are exact, well-specified algorithms, so correctness is
46+
non-negotiable:
47+
48+
- Any change to `lib/src/cobs.dart` or `lib/src/cobsr.dart` must keep the golden
49+
vectors in `test/` passing. Those vectors are ported from the reference
50+
implementations and must **not** be changed to make new code pass.
51+
- New behaviour needs new tests. For encode/decode changes, add a golden vector;
52+
for streaming/framing changes, add a test under `test/framing_test.dart`.
53+
- The implementation is validated by differential testing against the original
54+
Python reference (`cobs-python`). If you change the algorithms, run a
55+
byte-for-byte comparison against that reference before submitting.
56+
57+
## Style
58+
59+
- Follow the analyzer/`dart format` output — no manual style debates.
60+
- Every public API member must have a dartdoc comment
61+
(`public_member_api_docs` is enforced).
62+
- Keep the public surface minimal; add internal code under `lib/src/` and export
63+
deliberately from `lib/cobs_codec.dart`.
64+
65+
## Commits and pull requests
66+
67+
- Write clear, imperative commit messages (e.g. "Fix decoder backpressure on
68+
delimiter-less runs").
69+
- Commits and tags in this repository are **SSH-signed**; please sign your
70+
commits (`git config gpg.format ssh` and set `user.signingkey`) and include a
71+
sign-off (`git commit -s`) certifying the [DCO](https://developercertificate.org/).
72+
- Keep pull requests focused; update `CHANGELOG.md` under an `## Unreleased`
73+
heading for user-visible changes.
74+
75+
## Reporting bugs
76+
77+
Open an issue at <https://github.com/firechip/cobs_codec/issues> with a minimal
78+
reproduction: the input bytes, what you expected, and what you got.
79+
80+
## License
81+
82+
By contributing, you agree that your contributions are licensed under the
83+
project's [MIT License](LICENSE).

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Alexander Salas Bastidas <ajsb85@firechip.dev> (Firechip)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# cobs_codec
2+
3+
[![pub package](https://img.shields.io/pub/v/cobs_codec.svg)](https://pub.dev/packages/cobs_codec)
4+
[![pub points](https://img.shields.io/pub/points/cobs_codec)](https://pub.dev/packages/cobs_codec/score)
5+
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6+
7+
Fast, dependency-free **Consistent Overhead Byte Stuffing (COBS)** and **COBS/R**
8+
codecs for Dart and Flutter, with first-class `dart:convert` integration and
9+
stream framing for serial links.
10+
11+
COBS encodes an arbitrary byte sequence into one that contains **no zero
12+
(`0x00`) bytes**, at a small and *predictable* cost — at most **one extra byte
13+
per 254 bytes**, plus one. That lets a single `0x00` reliably delimit packets on
14+
a byte stream (serial/UART, USB-CDC, TCP, BLE, …), so a receiver can always
15+
resynchronise on the next `0x00` even after a corrupt packet. Unlike escape-based
16+
schemes such as PPP byte stuffing, COBS never doubles a packet's size.
17+
18+
## Features
19+
20+
-**Basic COBS** and **COBS/R (Reduced)** — pick the standard scheme, or COBS/R
21+
to shave the trailing overhead byte off small messages.
22+
- 🧩 **`dart:convert` native**`cobs` and `cobsr` are `Codec`s, so they
23+
`fuse`, `transform` streams, and compose like `json`/`utf8`/`base64`.
24+
- 🔌 **Stream framing built in** — turn a raw serial byte stream into a stream of
25+
decoded packets with `CobsFrameDecoder`; chunk boundaries don't have to align
26+
with frames.
27+
- 🎯 **Zero dependencies, all platforms** — pure Dart (`dart:typed_data`), works
28+
on mobile, desktop, web, server and CLI. Uses `Uint8List` throughout.
29+
- 📏 **Predictable sizing**`maxEncodedLength` / `encodingOverhead` for buffer
30+
pre-allocation.
31+
-**Reference-verified** — tested against the golden vectors from the original
32+
COBS and COBS/R implementations.
33+
34+
## Install
35+
36+
```console
37+
dart pub add cobs_codec
38+
```
39+
40+
```yaml
41+
dependencies:
42+
cobs_codec: ^1.0.0
43+
```
44+
45+
## Usage
46+
47+
### Encode and decode
48+
49+
```dart
50+
import 'package:cobs_codec/cobs_codec.dart';
51+
52+
void main() {
53+
final data = [0x11, 0x22, 0x00, 0x33];
54+
55+
final encoded = cobs.encode(data); // [0x03, 0x11, 0x22, 0x02, 0x33] — no 0x00
56+
final decoded = cobs.decode(encoded); // [0x11, 0x22, 0x00, 0x33]
57+
}
58+
```
59+
60+
`cobs.encode` returns a `Uint8List` and never fails — any input is encodable.
61+
`cobs.decode` throws a `CobsDecodeException` (a `FormatException`) if the input
62+
is not valid COBS.
63+
64+
### COBS/R — save a byte
65+
66+
COBS always adds exactly one byte to messages of 254 bytes or fewer. COBS/R
67+
opportunistically avoids that byte when the final data byte allows it:
68+
69+
```dart
70+
cobs.encode([0x31, 0x32, 0x33, 0x34, 0x35]); // [0x06, 0x31, 0x32, 0x33, 0x34, 0x35]
71+
cobsr.encode([0x31, 0x32, 0x33, 0x34, 0x35]); // [0x35, 0x31, 0x32, 0x33, 0x34] (same size!)
72+
```
73+
74+
Both round-trip losslessly; just decode with the matching codec.
75+
76+
### Framing a packet stream
77+
78+
COBS output has no `0x00`, so append one to delimit frames:
79+
80+
```dart
81+
final frame = cobsFrame([0x11, 0x00, 0x22]); // [0x02, 0x11, 0x02, 0x22, 0x00]
82+
83+
final packets = cobsUnframe(buffer); // List<Uint8List>, one per 0x00-delimited frame
84+
```
85+
86+
### Reading packets from a serial stream
87+
88+
`CobsFrameDecoder` is a `StreamTransformer` that buffers bytes across arbitrarily
89+
chunked reads and emits one decoded packet per completed frame — exactly what you
90+
want on a UART:
91+
92+
```dart
93+
import 'package:cobs_codec/cobs_codec.dart';
94+
95+
// `serialPort` is any Stream<List<int>> of incoming bytes.
96+
serialPort
97+
.transform(CobsFrameDecoder(
98+
// Keep receiving even if a frame is corrupted on a noisy link.
99+
onInvalidFrame: (error, rawFrame) => print('dropped bad frame: $error'),
100+
// Bound memory if a peer never sends the 0x00 delimiter.
101+
maxFrameLength: 4096,
102+
))
103+
.listen((packet) => handlePacket(packet));
104+
105+
// Sending: encode + delimit each outgoing packet.
106+
outgoingPackets
107+
.transform(const CobsFrameEncoder())
108+
.listen(serialPort.add);
109+
```
110+
111+
### Composing with other codecs
112+
113+
Because they are `Codec`s, you can `fuse` COBS with anything:
114+
115+
```dart
116+
// Encode a Dart object to JSON, to UTF-8 bytes, then COBS-frame it.
117+
final pipeline = json.fuse(utf8).fuse(cobs);
118+
final wire = pipeline.encode({'id': 7, 'ok': true});
119+
final obj = pipeline.decode(wire);
120+
```
121+
122+
### Sizing buffers
123+
124+
```dart
125+
encodingOverhead(0); // 1
126+
encodingOverhead(254); // 1
127+
encodingOverhead(255); // 2
128+
maxEncodedLength(1000); // 1004
129+
```
130+
131+
## How much overhead?
132+
133+
| Input length *n* | Max encoded length | Overhead |
134+
| --------------------- | ------------------ | ------------- |
135+
| 0 (empty) | 1 | +1 byte |
136+
| 1 – 254 | *n* + 1 | +1 byte |
137+
| 255 – 508 | *n* + 2 | +2 bytes |
138+
| *n* | *n* + ⌈*n* / 254⌉ |~0.4% |
139+
140+
The overhead is *data-independent*: worst case and average case are almost the
141+
same. Compare PPP/SLIP escape stuffing, whose worst case **doubles** the packet.
142+
143+
## API overview
144+
145+
| Symbol | Description |
146+
| ------ | ----------- |
147+
| `cobs` / `cobsr` | Shared `Codec` instances (basic COBS and COBS/R). |
148+
| `cobsEncode` / `cobsDecode` | Direct basic-COBS functions. |
149+
| `cobsrEncode` / `cobsrDecode` | Direct COBS/R functions. |
150+
| `CobsCodec`, `CobsEncoder`, `CobsDecoder` | `dart:convert` classes for basic COBS. |
151+
| `CobsrCodec`, `CobsrEncoder`, `CobsrDecoder` | `dart:convert` classes for COBS/R. |
152+
| `cobsFrame` / `cobsUnframe` | Add / split the `0x00` frame delimiter. |
153+
| `CobsFrameEncoder` / `CobsFrameDecoder` | Stream transformers for framed links. |
154+
| `cobsDelimiter` | The frame delimiter byte (`0x00`). |
155+
| `encodingOverhead` / `maxEncodedLength` | Size bounds. |
156+
| `cobsMaxBlockLength` | Max data bytes per COBS block (`254`). |
157+
| `CobsDecodeException` | Thrown on invalid encoded input. |
158+
159+
> **Note on decoding.** `decode` expects a *single* encoded packet with **no**
160+
> surrounding `0x00` delimiter — split a delimited stream into frames first (with
161+
> `cobsUnframe` or `CobsFrameDecoder`). This matches the reference COBS
162+
> implementations, where framing is the application's responsibility.
163+
164+
## Background
165+
166+
COBS is described in:
167+
168+
> Stuart Cheshire and Mary Baker, "Consistent Overhead Byte Stuffing,"
169+
> *IEEE/ACM Transactions on Networking*, Vol. 7, No. 2, April 1999.
170+
171+
**COBS/R** ("Reduced") was devised by **Craig McQueen**, whose C and Python
172+
reference implementations were used to validate this package's test vectors. The
173+
COBS/ZPE and COBS/ZRE variants are not implemented.
174+
175+
## Contributing
176+
177+
Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
178+
179+
## License
180+
181+
MIT © 2026 Alexander Salas Bastidas ([Firechip](https://firechip.dev)). See
182+
[LICENSE](LICENSE).

analysis_options.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Strict static analysis for a published, well-documented library.
2+
include: package:lints/recommended.yaml
3+
4+
analyzer:
5+
language:
6+
strict-casts: true
7+
strict-inference: true
8+
strict-raw-types: true
9+
10+
linter:
11+
rules:
12+
# Public API quality.
13+
- public_member_api_docs
14+
- directives_ordering
15+
- sort_pub_dependencies
16+
# Consistency / safety.
17+
- prefer_final_locals
18+
- prefer_single_quotes
19+
- prefer_relative_imports
20+
- unawaited_futures
21+
- avoid_redundant_argument_values
22+
- unnecessary_lambdas

0 commit comments

Comments
 (0)