https://github.com/firechip/cobs_codec_kt
Pure-Kotlin Consistent Overhead Byte Stuffing (COBS) and COBS/R for Android, packaged as an .aar. Kotlin sibling of the Dart cobs_codec package.
https://github.com/firechip/cobs_codec_kt
aar android byte-stuffing cobs cobs-r embedded framing kotlin serial uart
Last synced: about 7 hours ago
JSON representation
Pure-Kotlin Consistent Overhead Byte Stuffing (COBS) and COBS/R for Android, packaged as an .aar. Kotlin sibling of the Dart cobs_codec package.
- Host: GitHub
- URL: https://github.com/firechip/cobs_codec_kt
- Owner: firechip
- License: mit
- Created: 2026-07-04T11:03:53.000Z (about 20 hours ago)
- Default Branch: main
- Last Pushed: 2026-07-04T12:50:56.000Z (about 18 hours ago)
- Last Synced: 2026-07-04T13:10:20.882Z (about 18 hours ago)
- Topics: aar, android, byte-stuffing, cobs, cobs-r, embedded, framing, kotlin, serial, uart
- Language: Kotlin
- Size: 129 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# cobs_codec
_Kotlin / Android edition — Maven artifact `dev.firechip:cobs_codec`, repository
[`cobs_codec_kt`](https://github.com/firechip/cobs_codec_kt) (the `_kt` suffix is
just the repo slug)._
[](https://github.com/firechip/cobs_codec_kt/actions/workflows/ci.yml)
[](https://github.com/firechip/cobs_codec_kt/releases)
[](LICENSE)
Pure-Kotlin **Consistent Overhead Byte Stuffing (COBS)** and **COBS/R** for
Android, distributed as an `.aar`. It is the Kotlin/Android member of the
Firechip COBS family (alongside the Dart
[`cobs_codec`](https://pub.dev/packages/cobs_codec) on pub.dev) and produces
byte-identical output (validated against the shared conformance vectors).
COBS encodes an arbitrary `ByteArray` into one that contains no zero (`0x00`)
bytes, at a small, predictable cost (at most one extra byte per 254 bytes, plus
one). That lets a single `0x00` reliably delimit packets on a byte stream such
as a serial/UART, USB, or BLE link.
## Features
- **Basic COBS** and **COBS/R (Reduced)** encode/decode (`Cobs`, `Cobsr`).
- **Configurable sentinel** — encode/decode (both COBS and COBS/R) against any
delimiter byte instead of `0x00`, via `encodeWithSentinel` / `decodeWithSentinel`.
The encoded output never contains the sentinel byte, so a non-`0x00` byte can
delimit frames; a `sentinel` of `0` is byte-for-byte identical to the plain
codec.
- **In-place decode** (basic COBS) — `Cobs.decodeInPlace` decodes within the same
buffer with no second allocation, returning the decoded length. COBS never
expands on decode, so the decoded bytes occupy the front of the buffer.
- **Stream framing** for delimiter-framed links: `CobsFraming.frame` /
`unframe`, and the incremental `CobsStreamDecoder` (reassembles packets across
arbitrary chunk boundaries, with a `maxFrameLength` guard). Each takes a
`sentinel` byte (default `0x00`) so frames can be delimited by any chosen byte.
- **`java.io` stream adapters** — `CobsFramedOutputStream.writeFrame` /
`CobsFramedInputStream.readFrame` (plus `frames()` as a `Sequence`) wrap any
`OutputStream` / `InputStream` to write and read self-delimiting frames. They
use only `java.io`, so they add no dependency.
- **Coroutines `Flow` hook** — `Flow.cobsFrames()` reassembles a flow
of raw chunks into a flow of decoded packets. `kotlinx-coroutines` is a
`compileOnly` dependency, so the published artifact stays free of any runtime
dependency; the extension is available to consumers who already use coroutines.
- **Zero dependencies**, pure Kotlin, no Android framework APIs in the logic.
`minSdk 21`, `compileSdk 35`.
## Install
### Gradle (GitHub Packages)
The library is published to the GitHub Packages Maven registry as
`dev.firechip:cobs_codec`. Add the repository and the dependency:
```kotlin
repositories {
maven {
url = uri("https://maven.pkg.github.com/firechip/cobs_codec_kt")
credentials {
username = providers.gradleProperty("gpr.user").orNull
?: System.getenv("GITHUB_ACTOR")
password = providers.gradleProperty("gpr.key").orNull
?: System.getenv("GITHUB_TOKEN")
}
}
}
dependencies {
implementation("dev.firechip:cobs_codec:1.1.0")
}
```
> GitHub Packages requires authentication even for public packages. Use a GitHub
> [personal access token](https://github.com/settings/tokens) with the
> `read:packages` scope, set as `gpr.user` / `gpr.key` in
> `~/.gradle/gradle.properties` (or the `GITHUB_ACTOR` / `GITHUB_TOKEN`
> environment variables).
### Direct `.aar` download
Alternatively, the `.aar` is attached to every
[GitHub release](https://github.com/firechip/cobs_codec_kt/releases) and needs no
authentication:
```kotlin
dependencies {
implementation(files("libs/cobs_codec-1.1.0.aar"))
}
```
## Usage
```kotlin
import dev.firechip.cobs.Cobs
import dev.firechip.cobs.Cobsr
import dev.firechip.cobs.CobsFraming
import dev.firechip.cobs.CobsStreamDecoder
// Encode / decode a single packet.
val encoded = Cobs.encode(byteArrayOf(0x11, 0x00, 0x22)) // [0x02,0x11,0x02,0x22]
val decoded = Cobs.decode(encoded) // [0x11,0x00,0x22]
// COBS/R often avoids the trailing overhead byte for small messages.
Cobsr.encode("12345".toByteArray()) // "51234" bytes
// Frame a packet for a delimited link, then split a buffer back into packets.
val frame = CobsFraming.frame(byteArrayOf(0x11, 0x00, 0x22)) // ... trailing 0x00
val packets = CobsFraming.unframe(frame)
// Decode a live serial stream whose chunks do not align with frame boundaries.
val rx = CobsStreamDecoder(maxFrameLength = 4096)
serialPort.onBytes { chunk -> rx.feed(chunk).forEach(::handlePacket) }
// Configurable sentinel: encode/decode against any delimiter byte, not just 0x00.
// The output never contains the sentinel, so it can delimit frames instead of 0x00.
// A Byte literal above 0x7F needs `.toByte()`, e.g. 0xAA.toByte().
val s = 0xAA.toByte()
val stuffed = Cobs.encodeWithSentinel(byteArrayOf(0x11, 0x00, 0x22), s) // [0xA8,0xBB,0xA8,0x88], no 0xAA
Cobs.decodeWithSentinel(stuffed, s) // [0x11,0x00,0x22]
Cobsr.encodeWithSentinel(byteArrayOf(0x11, 0x00, 0x22), s) // [0xA8,0xBB,0x88], COBS/R variant
// In-place decode (basic COBS only): no second allocation; returns the decoded length.
val buf = Cobs.encode(byteArrayOf(0x11, 0x00, 0x22)) // [0x02,0x11,0x02,0x22]
val n = Cobs.decodeInPlace(buf) // n == 3; buf.copyOf(n) == [0x11,0x00,0x22]
// Cobs.decodeInPlace(buf, s) does the same for sentinel-encoded data.
// Sentinel-aware framing: frame/unframe and the stream decoder all take a sentinel.
val framed = CobsFraming.frame(byteArrayOf(0x11, 0x00, 0x22), sentinel = s) // ...trailing 0xAA
CobsFraming.unframe(framed, sentinel = s) // [[0x11,0x00,0x22]]
val rxAA = CobsStreamDecoder(maxFrameLength = 4096, sentinel = s)
```
### `java.io` stream adapters
Wrap any `OutputStream` / `InputStream` to write and read self-delimiting frames
(dependency-free — `java.io` only). `readFrame()` returns `null` at end of stream;
`frames()` exposes the same reads as a `Sequence`.
```kotlin
import dev.firechip.cobs.CobsFramedOutputStream
import dev.firechip.cobs.CobsFramedInputStream
CobsFramedOutputStream(socket.outputStream).use { out ->
out.writeFrame(byteArrayOf(0x11, 0x00, 0x22)) // encoded frame + 0x00 delimiter
}
val input = CobsFramedInputStream(socket.inputStream) // reduced / sentinel optional
for (packet in input.frames()) handlePacket(packet)
```
### Coroutines `Flow`
`Flow.cobsFrames()` reassembles a flow of raw chunks (however
misaligned) into a flow of decoded packets. `kotlinx-coroutines` is a
`compileOnly` dependency, so it adds nothing to the published artifact; add it to
your own build to use this extension.
```kotlin
import dev.firechip.cobs.cobsFrames
serialBytes // Flow of raw reads
.cobsFrames() // reduced / skipEmpty / sentinel optional
.collect { packet -> handlePacket(packet) }
```
Invalid encoded input throws `CobsDecodeException`.
## Benchmarks
Single-threaded JVM throughput on a 1 KiB payload (JDK 25, AMD Ryzen 7 3800XT
under WSL2) — ballpark micro-benchmark numbers:
| Operation | Throughput |
| --------- | ---------- |
| `Cobs.encode` | ~580 MB/s |
| `Cobs.decode` | ~850 MB/s |
| `Cobsr.encode` | ~600 MB/s |
Run with `COBS_BENCH=1 ./gradlew :cobs:testDebugUnitTest --tests '*BenchmarkTest*' --rerun-tasks`.
## Build
Requires JDK 17 or newer (CI builds on JDK 25) and the Android SDK
(`compileSdk 35`).
```console
./gradlew :cobs:assembleRelease # -> cobs/build/outputs/aar/cobs-release.aar
./gradlew :cobs:testDebugUnitTest # unit tests (golden vectors)
```
Pushing a `v*` tag builds the `.aar` in CI and attaches it to a GitHub release.
## License
MIT (c) 2026 Alexander Salas Bastidas ([Firechip](https://firechip.dev)). See
[LICENSE](LICENSE).