COBS Start Byte Stuffing Code:First, we'll go over how to stuff a packet based on the packet start byte (as a transmitter) and then how to "unpack" the same packet (as a receiver).
Assume we're using the data payload as described in Reply #2: [2, 5, 8, 1]. Note that this array only represents the payload portion of the packet we need to send. Next, let's also assume our start byte is 0x7E ('~'). In order to determine the packet's COBS byte we need to find the address of the first instance of the start byte in the payload. This is easy for this particular example since the start byte ('~') isn't present at all in the payload. Because of this, the COBS byte for this particular packet will be 255.
You might be tempted to say 0 is also a good default value, but this won't work. If the COBS byte is 0, this means the first payload byte is equal to the start byte, which is false for this example. Because of this, the max payload size for our serial protocol is 255. With this systematic limitation, we can use 255 as a COBS "error code" since the payload will never have element 255. (I hope that explanation makes sense

)
However, let's say our next packet's payload is now [1, 126, 126, 0] while using the same start byte as the previous packet. In this case, the COBS byte will not be 255 since the start byte is present in the second and third elements of the payload array (126 = 0x7E). This means the packet's COBS byte will now be 1 (remember indexing starts at 0).
Here is a C++ function that can be used to determine the packet's COBS byte:
/*
byte calcOverhead(uint8_t arr[], uint8_t len)
Description:
------------
* Calculates the COBS (Consistent Overhead Stuffing) Overhead
byte and stores it in the class's overheadByte variable. This
variable holds the byte position (within the payload) of the
first payload byte equal to that of START_BYTE
Inputs:
-------
* uint8_t arr[] - Array of values the overhead is to be calculated
over
* uint8_t len - Number of elements in arr[]
Return:
-------
* byte overheadByte - Payload COBS overhead byte
*/
byte calcOverhead(uint8_t arr[], uint8_t len)
{
byte overheadByte = 0xFF;
for (uint8_t i = 0; i < len; i++)
if (arr[i] == START_BYTE)
overheadByte = i;
return overheadByte;
}
Here is the Python function that mirrors the above C++ function:
def calc_overhead(txBuff, pay_len):
'''
Description:
------------
Calculates the COBS (Consistent Overhead Stuffing) Overhead
byte and stores it in the class's overheadByte variable. This
variable holds the byte position (within the payload) of the
first payload byte equal to that of START_BYTE
:param pay_len: int - number of bytes in the payload
:return: overheadByte
'''
overheadByte = 0xFF
for i in range(pay_len):
if txBuff[i] == START_BYTE:
overheadByte = i
break
return overheadByte
After finding the packet's COBS byte, we then need to stuff the rest of the payload before transmission.
Let's continue the last example with payload [1, 126, 126, 0] and COBS byte of 1. Since the COBS byte is 1, we then find the distance between the second element with the next element that holds the value of the start byte. In this case, that value is 1 since the third element also holds 126. We then replace the first element with this "distance". The payload now looks like [1, 1, 126, 0]. Be careful, the job isn't over! We have one last instance of the start byte in the payload (third element). Since this is the last element needed to be stuffed, we replace it with 0 like so: [1, 1, 0, 0]. Now we're done!
Let's look at the fully stuffed packet:
0x7E 0x01 0x01 0x01 0x00 0x00 0x81 (start byte, COBS byte, 1st payload byte, 2nd payload byte, 3rd payload byte, 4th payload byte, end byte)
Here's a C++ function to stuff the payload:
/*
void stuffPacket(uint8_t arr[], uint8_t len)
Description:
------------
* Enforces the COBS (Consistent Overhead Stuffing) ruleset across
all bytes in the packet against the value of START_BYTE
Inputs:
-------
* uint8_t arr[] - Array of values to stuff
* uint8_t len - Number of elements in arr[]
Return:
-------
* void
*/
void stuffPacket(uint8_t arr[], uint8_t len)
{
int16_t refByte = findLast(arr, len);
if (refByte != -1)
{
for (uint8_t i = (len - 1); i != 0xFF; i--)
{
if (arr[i] == START_BYTE)
{
arr[i] = refByte - i;
refByte = i;
}
}
}
}
where findLast() is defined as:
/*
int16_t packetStuffing(uint8_t arr[], uint8_t len)
Description:
------------
* Finds last instance of the value START_BYTE within the given
packet array
Inputs:
-------
* uint8_t arr[] - Packet array
* uint8_t len - Number of elements in arr[]
Return:
-------
* int16_t - Index value of the last instance of START_BYTE in the given packet array (-1 if not found)
*/
int16_t findLast(uint8_t arr[], uint8_t len)
{
for (uint8_t i = (len - 1); i != 0xFF; i--)
if (arr[i] == START_BYTE)
return i;
return -1;
}
The Python mirror of the above functions are:
def find_last(txBuff, pay_len):
'''
Description:
------------
Finds last instance of the value START_BYTE within the given
packet array
:param txBuff: list - payload
:param pay_len: int - number of bytes in the payload
:return: int - location of the last instance of the value START_BYTE
within the given packet array
'''
if pay_len <= MAX_PACKET_SIZE:
for i in range(pay_len - 1, 0, -1):
if txBuff[i] == START_BYTE:
return i
return -1
def stuff_packet(txBuff, pay_len):
'''
Description:
------------
Enforces the COBS (Consistent Overhead Stuffing) ruleset across
all bytes in the packet against the value of START_BYTE
:param txBuff: list - payload
:param pay_len: int - number of bytes in the payload
:return: void
'''
refByte = find_last(txBuff, pay_len)
if (not refByte == -1) and (refByte <= MAX_PACKET_SIZE):
for i in range(pay_len - 1, 0, -1):
if txBuff[i] == START_BYTE:
txBuff[i] = refByte - i
refByte = i
This post is getting long, so I'll go over how a receiver can "unpack" COBS-stuffed packets in the next post.