|
| 1 | +# COBS (Consistent Overhead Byte Stuffing) Component |
| 2 | + |
| 3 | +[](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) |
0 commit comments