Skip to content

Commit c93082c

Browse files
Add COBS (Consistent Overhead Byte Stuffing) Component (#513)
* COBS implementation and example/test code * checking the max size is superfluous since the max size is 254 (so block max is 255) but it's a uint8 * make const reference and add mutex. * static test failures * fix idf_component * Docs * Add to lib * pre-commit hooks * autogenerate bindings * formatting consistency * include for lib build * add another autogenerate * add cpp files * python tests * Update cobs_test.py * copilot comment suggestions * add example to build * Improvements based on feedback * Move docs * Add vector input API with move semantics * autogenerate * also test content, not just packet count. * Invalid packet should still remove content * fix links * improve doc with warnings * update using new vector based API * instead of copying a vector over, we insert the encoded data directly to the buffer. Also for extract_data * improve python to be more demo and less test. highlight potential differences to other libraries to help cross-communication * run pre-commit * autogenerate bindings * Switch to using std::span * cobs.hpp move to using span * pre-commit and autogenerate * Update pybind_espp.cpp * Add max_encoded_size/max_decoded_size and use span for output buffers Expand tests to make sure all API is covered. * Pre-commit and autogenerate * missing string * static analysis fix
1 parent 7fa5cc0 commit c93082c

26 files changed

+2204
-1
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ jobs:
5151
target: esp32s3
5252
- path: 'components/cli/example'
5353
target: esp32
54+
- path: 'components/cobs/example'
55+
target: esp32s3
5456
- path: 'components/color/example'
5557
target: esp32
5658
- path: 'components/controller/example'

.github/workflows/upload_components.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ jobs:
4646
components/byte90
4747
components/chsc6x
4848
components/cli
49+
components/cobs
4950
components/codec
5051
components/color
5152
components/controller

components/cobs/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
idf_component_register(
2+
INCLUDE_DIRS "include"
3+
SRC_DIRS "src")

components/cobs/README.md

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# COBS (Consistent Overhead Byte Stuffing) Component
2+
3+
[![Badge](https://components.espressif.com/components/espp/cobs/badge.svg)](https://components.espressif.com/components/espp/cobs)
4+
5+
The COBS component provides efficient encoding and decoding for the Consistent Overhead Byte Stuffing algorithm. COBS is a packet framing protocol that ensures reliable packet boundaries in communication streams by eliminating zero bytes from the data and using them as packet delimiters.
6+
7+
## Features
8+
9+
- **Zero-byte elimination**: Removes all zero bytes from data, using them as packet delimiters
10+
- **Consistent overhead**: Maximum overhead of ⌈n/254⌉ + 1 bytes per packet
11+
- **Simple framing**: Uses zero bytes as reliable packet delimiters
12+
- **Streaming support**: Handle fragmented data streams and batch multiple packets
13+
- **Thread-safe**: Streaming classes are thread-safe with internal locking
14+
- **Memory efficient**: Minimal overhead with RAII memory management
15+
- **Real-time friendly**: O(n) complexity with predictable performance
16+
17+
## Design Differences from Other COBS Libraries
18+
19+
This ESPP COBS implementation has some design choices that may differ from other COBS libraries:
20+
21+
### **Delimiter Handling**
22+
- **ESPP COBS**: Automatically adds a `0x00` delimiter at the end of encoded packets
23+
- **Other libraries**: May not add delimiters, requiring manual framing
24+
- **Impact**: ESPP encoded data includes framing, making it ready for transmission
25+
26+
### **Empty Packet Handling**
27+
- **ESPP COBS**: Ignores empty packets (length = 0) for performance optimization
28+
- **Other libraries**: May encode empty packets as a single `0x01` byte
29+
- **Impact**: Empty packets are filtered out during encoding, reducing overhead
30+
31+
### **Compatibility Notes**
32+
When integrating with other COBS libraries:
33+
- **Decoding**: ESPP can decode data from other libraries by adding the missing `0x00` delimiter
34+
- **Encoding**: Other libraries may need to add `0x00` delimiters to decode ESPP-encoded data
35+
- **Empty packets**: Handle empty packets separately if compatibility is required
36+
37+
### **Example: Design Differences**
38+
39+
```cpp
40+
// Input data with zeros
41+
std::vector<uint8_t> data = {0x01, 0x02, 0x00, 0x03, 0x04};
42+
43+
// ESPP COBS encoding (includes delimiter)
44+
std::vector<uint8_t> espp_encoded = Cobs::encode_packet(std::span{data});
45+
// Result: {0x03, 0x01, 0x02, 0x03, 0x03, 0x04, 0x00}
46+
47+
// Other COBS libraries (no delimiter)
48+
// Result: {0x03, 0x01, 0x02, 0x03, 0x03, 0x04}
49+
50+
// Empty packet handling
51+
std::vector<uint8_t> empty_data = {};
52+
espp_encoded = Cobs::encode_packet(std::span{empty_data});
53+
// ESPP Result: {} (ignored)
54+
// Other libraries: {0x01} (encoded as single byte)
55+
```
56+
57+
## API Design
58+
59+
The COBS implementation provides two levels of API:
60+
61+
### 1. Single Packet API (`cobs.hpp`)
62+
- **`Cobs::encode_packet()`** - Encode a single packet with COBS
63+
- **`Cobs::decode_packet()`** - Decode a single COBS-encoded packet
64+
- **`Cobs::max_encoded_size()`** - Calculate required buffer size for encoding
65+
- **`Cobs::max_decoded_size()`** - Calculate required buffer size for decoding
66+
- Simple, stateless functions for basic encoding/decoding with buffer size calculation
67+
68+
### 2. Streaming API (`cobs_stream.hpp`)
69+
- **`CobsStreamEncoder`** - Batch multiple packets for transmission
70+
- **`CobsStreamDecoder`** - Handle fragmented incoming data streams
71+
- Stateful classes for complex communication scenarios
72+
73+
## Usage
74+
75+
### Basic Single Packet Encoding/Decoding
76+
77+
```cpp
78+
#include "cobs.hpp"
79+
#include <span>
80+
81+
// Encode a packet (returns vector)
82+
std::vector<uint8_t> data = {0x01, 0x02, 0x00, 0x03, 0x04};
83+
std::vector<uint8_t> encoded = Cobs::encode_packet(std::span{data});
84+
85+
// Decode a packet (returns vector)
86+
std::vector<uint8_t> decoded = Cobs::decode_packet(std::span{encoded});
87+
88+
// Buffer-based encoding (for embedded systems with limited memory)
89+
uint8_t input_data[] = {0x01, 0x02, 0x00, 0x03, 0x04};
90+
size_t required_size = Cobs::max_encoded_size(sizeof(input_data));
91+
uint8_t output_buffer[100]; // Must be >= required_size
92+
93+
size_t bytes_written = Cobs::encode_packet(std::span{input_data}, std::span{output_buffer});
94+
if (bytes_written > 0) {
95+
// Successfully encoded, output_buffer contains encoded data
96+
}
97+
98+
// Buffer-based decoding
99+
size_t decoded_size = Cobs::max_decoded_size(encoded.size());
100+
uint8_t decode_buffer[100]; // Must be >= decoded_size
101+
102+
size_t bytes_decoded = Cobs::decode_packet(std::span{encoded}, std::span{decode_buffer});
103+
if (bytes_decoded > 0) {
104+
// Successfully decoded, decode_buffer contains original data
105+
}
106+
```
107+
108+
### Streaming Encoder for Batching
109+
110+
```cpp
111+
#include "cobs_stream.hpp"
112+
#include <span>
113+
114+
// Create streaming encoder
115+
CobsStreamEncoder encoder;
116+
117+
// Add multiple packets using std::span (zero-copy for contiguous data)
118+
for (auto& packet : packets) {
119+
encoder.add_packet(std::span{packet}); // Zero-copy from vector
120+
}
121+
122+
// Or with arrays (zero-copy)
123+
uint8_t data[] = {0x01, 0x02, 0x00, 0x03};
124+
encoder.add_packet(std::span{data});
125+
126+
// Extract data in chunks suitable for transmission
127+
while (encoder.buffer_size() > 0) {
128+
auto chunk = encoder.extract_data(max_chunk_size);
129+
send_over_bus(chunk);
130+
}
131+
132+
// Or extract directly to a buffer
133+
uint8_t tx_buffer[256];
134+
size_t bytes_extracted = encoder.extract_data(tx_buffer, sizeof(tx_buffer));
135+
if (bytes_extracted > 0) {
136+
send_over_bus(std::span{tx_buffer, bytes_extracted});
137+
}
138+
```
139+
140+
### Streaming Decoder for Fragmented Data
141+
142+
```cpp
143+
#include "cobs_stream.hpp"
144+
#include <span>
145+
146+
// Create streaming decoder
147+
CobsStreamDecoder decoder;
148+
149+
// Add received data (may be fragmented) using std::span
150+
decoder.add_data(std::span{received_data, received_length});
151+
152+
// Or with vectors (zero-copy)
153+
std::vector<uint8_t> received_vec = get_received_data();
154+
decoder.add_data(std::span{received_vec}); // Zero-copy from vector
155+
156+
// Extract complete packets
157+
while (auto packet = decoder.extract_packet()) {
158+
process_packet(*packet);
159+
}
160+
161+
// Check remaining data for debugging
162+
if (decoder.buffer_size() > 0) {
163+
const auto& remaining = decoder.remaining_data();
164+
// Handle incomplete data or debug buffer contents
165+
}
166+
167+
// Clear buffer if needed
168+
decoder.clear();
169+
```
170+
171+
172+
**Buffer Size Formulas:**
173+
- **`max_encoded_size(payload_len)`**: `payload_len + ⌈payload_len/254⌉ + 2`
174+
- **`max_decoded_size(encoded_len)`**: `encoded_len - 1` (accounts for delimiter)
175+
176+
## COBS Algorithm Details
177+
178+
COBS works by:
179+
1. **Eliminating zeros**: All zero bytes are removed from the data
180+
2. **Adding overhead bytes**: Each block of non-zero data gets a length prefix
181+
3. **Using zero as delimiter**: A single zero byte marks the end of each packet
182+
4. **Consistent overhead**: Maximum overhead is ⌈n/254⌉ + 1 bytes per packet
183+
184+
### Example Encoding
185+
```
186+
Input: [0x01, 0x02, 0x00, 0x03, 0x04]
187+
Output: [0x03, 0x01, 0x02, 0x02, 0x03, 0x04, 0x00]
188+
^ ^ ^ ^ ^ ^ ^
189+
| | | | | | delimiter
190+
| | | | | data
191+
| | | | data
192+
| | | length (2 bytes follow)
193+
| | data
194+
| data
195+
length (3 bytes follow)
196+
```
197+
198+
## Performance Characteristics
199+
200+
- **Encoding overhead**: At most ⌈n/254⌉ + 1 bytes per packet
201+
- **Decoding complexity**: O(n) where n is packet size
202+
- **Speed**: Optimized for embedded systems with minimal overhead
203+
- **Large packet support**: Tested and verified with packets up to 1000+ bytes
204+
- **Memory efficiency**: Uses RAII for automatic memory management
205+
- **Zero-copy API**: `std::span` interface eliminates unnecessary data copies
206+
- **Direct buffer encoding**: Packets are encoded directly into internal buffers
207+
- **Buffer size calculation**: Static methods provide compile-time buffer size estimation
208+
- **Embedded-friendly**: Buffer-based APIs for systems with limited heap allocation
209+
210+
## Thread Safety
211+
212+
The streaming classes (`CobsStreamEncoder` and `CobsStreamDecoder`) are **thread-safe** with internal locking:
213+
214+
- **Internal mutexes**: All buffer operations are protected by mutexes
215+
- **Atomic operations**: Each method call is atomic and thread-safe
216+
- **No external synchronization needed**: Safe to use from multiple threads
217+
218+
**Note**: The single packet API (`Cobs::encode_packet` and `Cobs::decode_packet`) is stateless and inherently thread-safe.
219+
220+
## Example
221+
222+
The [example](./example) demonstrates comprehensive COBS usage including:
223+
- Single packet encoding and decoding with various data patterns using `std::span` API
224+
- Buffer-based encoding/decoding for embedded systems with limited memory
225+
- Static buffer size calculation API usage
226+
- Streaming encoder for batching multiple packets with zero-copy efficiency
227+
- Streaming decoder for processing fragmented data with buffer management
228+
- Edge cases and error handling (empty packets, large packets, alternating patterns)
229+
- Buffer size validation and overflow protection
230+
- Comprehensive test suite with test cases covering all functionality
231+
- Performance characteristics for various packet sizes (up to 1000+ bytes)
232+
233+
## References
234+
235+
- [COBS Wikipedia Article](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing)
236+
- [COBS C Implementation](https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing#Implementation)
237+
- [HDLC Framing Comparison](https://en.wikipedia.org/wiki/High-Level_Data_Link_Control)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# The following lines of boilerplate have to be in your project's CMakeLists
2+
# in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.20)
4+
5+
set(ENV{IDF_COMPONENT_MANAGER} "0")
6+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
7+
8+
# add the component directories that we want to use
9+
set(EXTRA_COMPONENT_DIRS
10+
"../../../components/"
11+
)
12+
13+
set(
14+
COMPONENTS
15+
"main esptool_py cobs logger"
16+
CACHE STRING
17+
"List of components to include"
18+
)
19+
20+
project(cobs_example)
21+
22+
set(CMAKE_CXX_STANDARD 20)

0 commit comments

Comments
 (0)