Get started & guides#

pw_cobs: Consistent Overhead Byte Stuffing (COBS) codec and zero-copy frame streamer

This guide shows how to add pw_cobs to your build and use it to encode and decode COBS frames.

Build setup#

Add @pigweed//pw_cobs/cpp:pw_cobs to the deps list in your Bazel target:

cc_library(
    name = "my_transport",
    srcs = ["my_transport.cc"],
    deps = [
        "@pigweed//pw_cobs/cpp:pw_cobs",
    ],
)

Add $dir_pw_cobs/cpp:pw_cobs to the deps list in your pw_source_set() or pw_executable() build target:

pw_source_set("my_transport") {
  sources = [ "my_transport.cc" ]
  deps = [
    "$dir_pw_cobs/cpp:pw_cobs",
  ]
}

CMake is not supported.

Encoding in-place with EncodeBuffer#

EncodeBuffer is a fixed-capacity container that stores the unencoded payload and encodes it in-place when Encode() is called, avoiding a second buffer:

#include "pw_cobs/cobs.h"

void SendPacket(pw::ConstByteSpan payload) {
  pw::cobs::EncodeBuffer<256> buffer(payload);
  pw::ConstByteSpan encoded = buffer.Encode();
  WriteToSerial(encoded);
}

You can also populate EncodeBuffer incrementally using append(), push_back(), or resize():

#include "pw_cobs/cobs.h"

void BuildAndEncode(pw::ConstByteSpan header, pw::ConstByteSpan body) {
  pw::cobs::EncodeBuffer<256> buffer;
  buffer.append(header);
  buffer.append(body);
  pw::ConstByteSpan encoded = buffer.Encode();
  WriteToSerial(encoded);
}

One-shot encoding and decoding with spans#

Use Encode and Decode when working with separate input and output spans:

#include <array>

#include "pw_cobs/cobs.h"

void EncodeAndDecodeSpan(pw::ConstByteSpan payload) {
  std::array<std::byte, pw::cobs::MaxEncodedSize(128)> wire_buf{};
  pw::Result<pw::ByteSpan> encoded = pw::cobs::Encode(payload, wire_buf);
  if (!encoded.ok()) {
    return;
  }

  std::array<std::byte, 128> decoded_buf{};
  pw::Result<pw::ByteSpan> decoded = pw::cobs::Decode(*encoded, decoded_buf);
}

Streaming encoding with Streamer#

Streamer (and the owning convenience wrapper StreamerBuffer) incrementally COBS-encodes an arbitrarily large frame into a fixed-size buffer (at least 257 bytes). Each call to Flush() emits all completed COBS blocks currently in the buffer, and Finish() closes the final block and appends the trailing 0x00 delimiter:

#include "pw_cobs/cobs.h"

void StreamPayload(pw::ConstByteSpan header, pw::ConstByteSpan body) {
  pw::cobs::StreamerBuffer<> streamer;
  for (pw::ConstByteSpan seg : {header, body}) {
    while (!seg.empty()) {
      seg = streamer.Write(seg);
      if (!seg.empty()) {
        WriteToSerial(streamer.Flush());
      }
    }
  }
  WriteToSerial(streamer.Finish());
}

Streaming decode with DecoderBuffer#

Use DecoderBuffer (or Decoder with an external buffer) to decode incoming serial bytes incrementally:

#include "pw_cobs/cobs.h"

constexpr size_t kMaxPayloadBytes = 256;
pw::cobs::DecoderBuffer<kMaxPayloadBytes> decoder;

void OnSerialBytesReceived(pw::ConstByteSpan rx_bytes) {
  decoder.Process(rx_bytes, [](pw::Result<pw::ByteSpan> frame) {
    if (frame.ok()) {
      HandlePacket(*frame);
    }
  });
}

Or feed one byte at a time:

pw::Result<pw::ByteSpan> result = decoder.Process(rx_byte);
if (result.ok()) {
  HandlePacket(*result);
}