Skip to content

Framing Details

This page covers the technical details of the framing system.

Frame Components

Frames consist of two parts:

  1. Header: Determines sync bytes and addressing
  2. Payload: Determines length encoding and checksum

Header Types

Basic Header (2 bytes)

[0x90] [0x70 + PayloadType]

Two-byte sync for reliable synchronization. Recommended for most applications.

Tiny Header (1 byte)

[0x70 + PayloadType]

Single-byte sync for bandwidth-limited scenarios.

None Header (0 bytes)

(no header)

For trusted links where synchronization isn’t needed.

Payload Types

Minimal

[MSG_ID] [PAYLOAD...]

1 byte overhead. Requires message length lookup. No checksum or length field.

Default

[LENGTH] [MSG_ID] [PAYLOAD...] [CRC_HI] [CRC_LO]

4 bytes overhead. Includes length (1 byte, max 255) and Fletcher-16 checksum.

Extended

[LENGTH_HI] [LENGTH_LO] [MSG_ID] [PAYLOAD...] [CRC_HI] [CRC_LO]

5 bytes overhead. 16-bit length field (max 65535 bytes).

ExtendedMultiSystemStream

[LENGTH_HI] [LENGTH_LO] [SRC] [DST] [SEQ] [MSG_ID] [PAYLOAD...] [CRC_HI] [CRC_LO]

8 bytes overhead. Adds source/destination addresses and sequence numbers for multi-node networks.

Frame Profiles

Profiles combine header + payload types:

ProfileHeaderPayloadTotal Overhead
StandardBasicDefault6 bytes
SensorTinyMinimal2 bytes
IPCNoneMinimal1 byte
BulkBasicExtended8 bytes
NetworkBasicExtendedMultiSystemStream11 bytes

Checksum (Fletcher-16)

The Default and Extended payload types use Fletcher-16 checksum.

Magic Numbers:

  • Basic frame start: 0x90 followed by 0x70 + PayloadType
  • Tiny frame start: 0x70 + PayloadType
  • Payload type base value: 0x70

The checksum algorithm:

For each byte in [LENGTH, MSG_ID, PAYLOAD]:
sum1 = (sum1 + byte) mod 255
sum2 = (sum2 + sum1) mod 255
checksum = (sum2 << 8) | sum1

Parser State Machine

The parser implements a state machine:

IDLE → wait for start byte(s)
HEADER → read header bytes
PAYLOAD → read length, msg_id, data
CHECKSUM → verify CRC
COMPLETE → return message

For Minimal payloads, the parser requires a message length callback to determine payload size.

Usage in C++

#include "frame_profiles.hpp"
using namespace FrameParsers;
// Using profiles
uint8_t buffer[1024];
ProfileStandardWriter writer(buffer, sizeof(buffer));
ProfileStandardAccumulatingReader reader;
// Encode
writer.write(msg);
send_data(writer.buffer(), writer.size());
// Decode (streaming)
while (receiving) {
if (auto result = reader.push_byte(read_byte())) {
handle_message(result.msg_id, result.msg_data, result.msg_len);
}
}
// Decode (buffer)
reader.add_data(buffer, buffer_size);
while (auto result = reader.next()) {
handle_message(result.msg_id, result.msg_data, result.msg_len);
}

Usage in Python

from frame_profiles import (
ProfileStandardWriter,
ProfileStandardAccumulatingReader,
)
from struct_frame.generated.messages import MyMessage, get_message_info
# Encode
msg = MyMessage(value=42)
writer = ProfileStandardWriter(1024)
writer.write(msg)
frame = writer.data()
# Decode (byte-by-byte streaming)
reader = ProfileStandardAccumulatingReader(get_message_info=get_message_info)
for byte in frame:
result = reader.push_byte(byte)
if result and result.valid:
handle_message(result)
# Decode (buffer mode)
reader2 = ProfileStandardAccumulatingReader(get_message_info=get_message_info)
reader2.add_data(frame)
result = reader2.next()
if result and result.valid:
handle_message(result)

Custom Profiles

Create custom frame formats by combining header and payload types:

// Custom: Tiny header with Extended payload
using CustomConfig = FrameConfig<
HeaderTiny,
PayloadExtended
>;
FrameEncoderWithCrc<CustomConfig> encoder;
BufferParserWithCrc<CustomConfig> parser;
from frame_profiles import ProfileConfig, HEADER_TINY_CONFIG, PAYLOAD_EXTENDED_CONFIG
from frame_profiles import BufferWriter, AccumulatingReader
custom_config = ProfileConfig(HEADER_TINY_CONFIG, PAYLOAD_EXTENDED_CONFIG, name="TinyExtended")
writer = BufferWriter(custom_config, 1024)
reader = AccumulatingReader(custom_config)