cobs: Introduce streaming
This commit does: - Introduce COBS streaming - Refactor custom delimiter with XOR'ed encoded data - Update tests Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
This commit is contained in:
committed by
Henrik Brix Andersen
parent
d4bf9ea2e8
commit
f344ab6b98
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Kelly Helmut Lord
|
||||
* Copyright (c) 2026 Basalte bv
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -16,20 +17,53 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* @name COBS Encoder/Decoder Flags
|
||||
* @anchor COBS_FLAGS
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Default COBS delimiter value
|
||||
*
|
||||
* The standard COBS delimiter is zero (0x00). This is the delimiter value
|
||||
* used when COBS_FLAG_CUSTOM_DELIMITER is not specified.
|
||||
*/
|
||||
#define COBS_DEFAULT_DELIMITER 0x00
|
||||
|
||||
/**
|
||||
* Flag indicating that encode and decode should include an implicit end delimiter
|
||||
* @brief Flag indicating that encode or decode should include a trailing delimiter
|
||||
*
|
||||
* When set, the encoder will append a delimiter byte after the encoded data,
|
||||
* and the decoder will accept a delimiter byte at the end of the encoded data.
|
||||
*/
|
||||
#define COBS_FLAG_TRAILING_DELIMITER BIT(8)
|
||||
|
||||
/**
|
||||
* Macro for extracting delimiter from flags. 8 LSB of "flags" is used for the delimiter
|
||||
* @brief Macro for setting a custom delimiter in flags
|
||||
*
|
||||
* The 8 LSB of "flags" is used for the delimiter value. When a custom delimiter is
|
||||
* configured, the implementation applies an XOR operation with the delimiter value
|
||||
* on the encoded data after encoding and before decoding. This allows COBS to work
|
||||
* with delimiters other than zero.
|
||||
*
|
||||
* @param x Custom delimiter value (0-255)
|
||||
*
|
||||
* @return Delimiter value masked to 8 bits
|
||||
*
|
||||
* Example usage:
|
||||
* @code{.c}
|
||||
* cobs_encode(src_buf, dst_buf, COBS_FLAG_TRAILING_DELIMITER | COBS_FLAG_CUSTOM_DELIMITER(0x7F));
|
||||
* @endcode
|
||||
*/
|
||||
#define COBS_FLAG_CUSTOM_DELIMITER(x) ((x) & 0xff)
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup cobs COBS (Consistent Overhead Byte Stuffing)
|
||||
* @ingroup utilities
|
||||
@@ -46,7 +80,7 @@ extern "C" {
|
||||
* @brief Calculate maximum encoded buffer size
|
||||
*
|
||||
* @param decoded_size Size of input data to be encoded
|
||||
* @param flags COBS_FLAG_TRAILING_DELIMITER to include termination byte in calculation
|
||||
* @param flags Encoding flags @ref COBS_FLAGS
|
||||
*
|
||||
* @return Required buffer size for worst-case encoding scenario
|
||||
*/
|
||||
@@ -60,11 +94,13 @@ static inline size_t cobs_max_encoded_len(size_t decoded_size, uint32_t flags)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Standard COBS encoding
|
||||
* @brief COBS encoding
|
||||
*
|
||||
* @param src Source buffer to decode
|
||||
* @param dst Destination buffer for decoded data
|
||||
* @param flags Decoding flags (reserved)
|
||||
* Encodes data from source buffer to destination buffer using COBS encoding.
|
||||
*
|
||||
* @param src Source buffer to encode
|
||||
* @param dst Destination buffer for encoded data
|
||||
* @param flags Encoding flags @ref COBS_FLAGS
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -ENOMEM Insufficient destination space
|
||||
@@ -74,11 +110,13 @@ static inline size_t cobs_max_encoded_len(size_t decoded_size, uint32_t flags)
|
||||
int cobs_encode(struct net_buf *src, struct net_buf *dst, uint32_t flags);
|
||||
|
||||
/**
|
||||
* @brief Standard COBS decoding
|
||||
* @brief COBS decoding
|
||||
*
|
||||
* Decodes COBS-encoded data from source buffer to destination buffer.
|
||||
*
|
||||
* @param src Source buffer to decode
|
||||
* @param dst Destination buffer for decoded data
|
||||
* @param flags Decoding flags (reserved)
|
||||
* @param flags Decoding flags @ref COBS_FLAGS
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -ENOMEM Insufficient destination space
|
||||
@@ -86,6 +124,166 @@ int cobs_encode(struct net_buf *src, struct net_buf *dst, uint32_t flags);
|
||||
*/
|
||||
int cobs_decode(struct net_buf *src, struct net_buf *dst, uint32_t flags);
|
||||
|
||||
/**
|
||||
* @brief Callback function type for streaming COBS encoder/decoder
|
||||
*
|
||||
* This callback is invoked by the streaming encoder/decoder to output
|
||||
* processed data chunks.
|
||||
* A decoder that allows trailing delimiters and encounters one will invoke
|
||||
* this callback with a NULL pointer and zero length indicating a completed frame.
|
||||
*
|
||||
* When this callback function returns a negative error value, the encoder or decoder
|
||||
* stream is aborted and the error will be propagated.
|
||||
*
|
||||
* @param buf Buffer containing processed data
|
||||
* @param len Length of data in buffer
|
||||
* @param user_data User-provided context pointer
|
||||
*
|
||||
* @return 0 on success, negative errno code on failure
|
||||
*/
|
||||
typedef int (*cobs_stream_cb)(const uint8_t *buf, size_t len, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief COBS streaming encoder state
|
||||
*
|
||||
* This structure maintains the state for incremental COBS encoding.
|
||||
* It should be initialized with cobs_encoder_init() before use.
|
||||
*/
|
||||
struct cobs_encoder {
|
||||
/** @cond INTERNAL_HIDDEN */
|
||||
/** Callback function to output encoded data */
|
||||
cobs_stream_cb cb;
|
||||
/** User data pointer passed to callback */
|
||||
void *cb_user_data;
|
||||
|
||||
/** Internal buffer for partial encoding */
|
||||
uint8_t fragment[255];
|
||||
/** Encoding flags @ref COBS_FLAGS */
|
||||
uint32_t flags;
|
||||
/** @endcond */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief COBS streaming decoder state
|
||||
*
|
||||
* This structure maintains the state for incremental COBS decoding.
|
||||
* It should be initialized with cobs_decoder_init() before use.
|
||||
*/
|
||||
struct cobs_decoder {
|
||||
/** @cond INTERNAL_HIDDEN */
|
||||
/** Callback function to output decoded data */
|
||||
cobs_stream_cb cb;
|
||||
/** User data pointer passed to callback */
|
||||
void *cb_user_data;
|
||||
|
||||
/** Current COBS code byte being processed */
|
||||
uint8_t code;
|
||||
/** Position within current code block */
|
||||
uint8_t code_index;
|
||||
/** Decoding flags @ref COBS_FLAGS */
|
||||
uint32_t flags;
|
||||
/** @endcond */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize COBS streaming encoder
|
||||
*
|
||||
* Initializes a COBS encoder for streaming operation. The encoder will call
|
||||
* the provided callback function to output encoded data chunks as they become
|
||||
* available.
|
||||
*
|
||||
* @param enc Pointer to encoder structure to initialize
|
||||
* @param cb Callback function for output data
|
||||
* @param user_data User data pointer passed to callback
|
||||
* @param flags Encoding flags @ref COBS_FLAGS
|
||||
*
|
||||
* @return 0 on success, negative errno code on failure
|
||||
*/
|
||||
int cobs_encoder_init(struct cobs_encoder *enc, cobs_stream_cb cb, void *user_data, uint32_t flags);
|
||||
|
||||
/**
|
||||
* @brief Finalize COBS streaming encoder
|
||||
*
|
||||
* Flushes any remaining data and optionally writes trailing delimiter if
|
||||
* COBS_FLAG_TRAILING_DELIMITER was set during initialization.
|
||||
*
|
||||
* The encoder state will be reset.
|
||||
*
|
||||
* @param enc Pointer to encoder structure
|
||||
*
|
||||
* @return 0 on success, negative errno code on failure
|
||||
*/
|
||||
int cobs_encoder_close(struct cobs_encoder *enc);
|
||||
|
||||
/**
|
||||
* @brief Write data to COBS streaming encoder
|
||||
*
|
||||
* Encodes the provided data and outputs encoded chunks via the registered
|
||||
* callback function. This function can be called multiple times to encode
|
||||
* data incrementally.
|
||||
*
|
||||
* In case an error is returned, the encoder state will be reset.
|
||||
*
|
||||
* @param enc Pointer to encoder structure
|
||||
* @param buf Buffer containing data to encode
|
||||
* @param len Length of data in buffer
|
||||
*
|
||||
* @return Number of bytes used from @p buf on success, negative errno code on failure
|
||||
*/
|
||||
int cobs_encoder_write(struct cobs_encoder *enc, const uint8_t *buf, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Initialize COBS streaming decoder
|
||||
*
|
||||
* Initializes a COBS decoder for streaming operation. The decoder will call
|
||||
* the provided callback function to output decoded data chunks as they become
|
||||
* available.
|
||||
*
|
||||
* @param dec Pointer to decoder structure to initialize
|
||||
* @param cb Callback function for output data
|
||||
* @param user_data User data pointer passed to callback
|
||||
* @param flags Decoding flags @ref COBS_FLAGS
|
||||
*
|
||||
* @return 0 on success, negative errno code on failure
|
||||
*/
|
||||
int cobs_decoder_init(struct cobs_decoder *dec, cobs_stream_cb cb, void *user_data, uint32_t flags);
|
||||
|
||||
/**
|
||||
* @brief Finalize COBS streaming decoder
|
||||
*
|
||||
* Completes the decoding process and verifies that the stream ended properly.
|
||||
* Should be called after all data has been written to the decoder.
|
||||
*
|
||||
* The decoder state will be reset.
|
||||
*
|
||||
* @param dec Pointer to decoder structure
|
||||
*
|
||||
* @retval 0 Success
|
||||
* @retval -EINVAL More data was expected before closing
|
||||
*/
|
||||
int cobs_decoder_close(struct cobs_decoder *dec);
|
||||
|
||||
/**
|
||||
* @brief Write data to COBS streaming decoder
|
||||
*
|
||||
* Decodes the provided encoded data and outputs decoded chunks via the
|
||||
* registered callback function. This function can be called multiple times
|
||||
* to decode data incrementally.
|
||||
*
|
||||
* In case an error is returned, the decoder state will be reset.
|
||||
*
|
||||
* @note If a delimiter is encountered, and the @ref COBS_FLAG_TRAILING_DELIMITER flag
|
||||
* is set, the registered callback function will be called with a NULL pointer
|
||||
* indicating a frame end.
|
||||
*
|
||||
* @param dec Pointer to decoder structure
|
||||
* @param buf Buffer containing encoded data
|
||||
* @param len Length of data in buffer
|
||||
*
|
||||
* @return Number of bytes used from @p buf on success, negative errno code on failure
|
||||
*/
|
||||
int cobs_decoder_write(struct cobs_decoder *dec, const uint8_t *buf, size_t len);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
290
lib/utils/cobs.c
290
lib/utils/cobs.c
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2024 Kelly Helmut Lord
|
||||
* Copyright (c) 2026 Basalte bv
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -8,97 +9,242 @@
|
||||
#include <stdint.h>
|
||||
#include <zephyr/data/cobs.h>
|
||||
|
||||
int cobs_encode(struct net_buf *src, struct net_buf *dst, uint32_t flags)
|
||||
static int cobs_net_buf_cb(const uint8_t *buf, size_t len, void *user_data)
|
||||
{
|
||||
uint8_t delimiter = COBS_FLAG_CUSTOM_DELIMITER(flags);
|
||||
struct net_buf *dst = user_data;
|
||||
|
||||
/* Calculate required space for worst case */
|
||||
size_t max_encoded_size = cobs_max_encoded_len(src->len, flags);
|
||||
|
||||
/* Check if destination has enough space */
|
||||
if (net_buf_tailroom(dst) < max_encoded_size) {
|
||||
if (net_buf_tailroom(dst) < len) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
uint8_t *code_ptr = net_buf_add(dst, 1);
|
||||
uint8_t code = 1;
|
||||
|
||||
/* Process all input bytes */
|
||||
uint8_t data = 0;
|
||||
|
||||
while (src->len > 0) {
|
||||
data = net_buf_pull_u8(src);
|
||||
if (data == delimiter) {
|
||||
/* Delimiter found - write current code and start new block */
|
||||
*code_ptr = code;
|
||||
code_ptr = net_buf_add(dst, 1);
|
||||
code = 1;
|
||||
} else {
|
||||
/* Add non-zero byte to output */
|
||||
net_buf_add_u8(dst, data);
|
||||
code++;
|
||||
|
||||
/* If we've reached maximum block size, start a new block */
|
||||
if (code == 0xFF && (src->len - 1 >= 0)) {
|
||||
*code_ptr = code;
|
||||
code_ptr = net_buf_add(dst, 1);
|
||||
code = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*code_ptr = code;
|
||||
|
||||
if (flags & COBS_FLAG_TRAILING_DELIMITER) {
|
||||
/* Add final delimiter */
|
||||
net_buf_add_u8(dst, delimiter);
|
||||
}
|
||||
(void)net_buf_add_mem(dst, buf, len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cobs_encode(struct net_buf *src, struct net_buf *dst, uint32_t flags)
|
||||
{
|
||||
struct cobs_encoder enc;
|
||||
size_t len = src->len;
|
||||
int ret;
|
||||
|
||||
(void)cobs_encoder_init(&enc, cobs_net_buf_cb, dst, flags);
|
||||
|
||||
ret = cobs_encoder_write(&enc, net_buf_pull_mem(src, len), len);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return cobs_encoder_close(&enc);
|
||||
}
|
||||
|
||||
int cobs_decode(struct net_buf *src, struct net_buf *dst, uint32_t flags)
|
||||
{
|
||||
uint8_t delimiter = COBS_FLAG_CUSTOM_DELIMITER(flags);
|
||||
struct cobs_decoder dec;
|
||||
size_t len = src->len;
|
||||
int ret;
|
||||
|
||||
if (flags & COBS_FLAG_TRAILING_DELIMITER) {
|
||||
uint8_t end_delim = net_buf_remove_u8(src);
|
||||
(void)cobs_decoder_init(&dec, cobs_net_buf_cb, dst, flags);
|
||||
|
||||
if (end_delim != delimiter) {
|
||||
return -EINVAL;
|
||||
ret = cobs_decoder_write(&dec, net_buf_pull_mem(src, len), len);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return cobs_decoder_close(&dec);
|
||||
}
|
||||
|
||||
static inline void cobs_encoder_reset(struct cobs_encoder *enc)
|
||||
{
|
||||
/* Reset buffer */
|
||||
enc->fragment[0] = 1;
|
||||
}
|
||||
|
||||
static int cobs_encoder_finish(struct cobs_encoder *enc, bool close)
|
||||
{
|
||||
uint8_t sentinel = COBS_FLAG_CUSTOM_DELIMITER(enc->flags);
|
||||
size_t len = enc->fragment[0];
|
||||
int ret;
|
||||
|
||||
if (sentinel != 0x00) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
enc->fragment[i] ^= sentinel;
|
||||
}
|
||||
}
|
||||
|
||||
while (src->len > 0) {
|
||||
/* Pull the COBS offset byte */
|
||||
uint8_t offset = net_buf_pull_u8(src);
|
||||
ret = enc->cb(enc->fragment, len, enc->cb_user_data);
|
||||
if (ret < 0) {
|
||||
cobs_encoder_reset(enc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (offset == delimiter && !(flags & COBS_FLAG_TRAILING_DELIMITER)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Verify we have enough data */
|
||||
if (src->len < (offset - 1)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Copy offset-1 bytes */
|
||||
for (uint8_t i = 0; i < offset - 1; i++) {
|
||||
uint8_t byte = net_buf_pull_u8(src);
|
||||
|
||||
if (byte == delimiter) {
|
||||
return -EINVAL;
|
||||
}
|
||||
net_buf_add_u8(dst, byte);
|
||||
}
|
||||
|
||||
/* If this wasn't a maximum offset and we have more data,
|
||||
* there was a delimiter here in the original data
|
||||
*/
|
||||
if (offset != 0xFF && src->len > 0) {
|
||||
net_buf_add_u8(dst, delimiter);
|
||||
if (close && (enc->flags & COBS_FLAG_TRAILING_DELIMITER) != 0U) {
|
||||
ret = enc->cb(&sentinel, 1, enc->cb_user_data);
|
||||
if (ret < 0) {
|
||||
cobs_encoder_reset(enc);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
cobs_encoder_reset(enc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cobs_encoder_init(struct cobs_encoder *enc, cobs_stream_cb cb, void *user_data, uint32_t flags)
|
||||
{
|
||||
if (cb == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
__ASSERT_NO_MSG(enc != NULL);
|
||||
|
||||
enc->cb = cb;
|
||||
enc->cb_user_data = user_data;
|
||||
enc->flags = flags;
|
||||
|
||||
cobs_encoder_reset(enc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cobs_encoder_close(struct cobs_encoder *enc)
|
||||
{
|
||||
__ASSERT_NO_MSG(enc != NULL);
|
||||
|
||||
return cobs_encoder_finish(enc, true);
|
||||
}
|
||||
|
||||
int cobs_encoder_write(struct cobs_encoder *enc, const uint8_t *buf, size_t len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
__ASSERT_NO_MSG(enc != NULL);
|
||||
__ASSERT_NO_MSG(len <= INT_MAX);
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
/* Finish if group is full */
|
||||
if (enc->fragment[0] == 0xff) {
|
||||
ret = cobs_encoder_finish(enc, false);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (buf[i] == 0x00) {
|
||||
ret = cobs_encoder_finish(enc, false);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
enc->fragment[enc->fragment[0]] = buf[i];
|
||||
enc->fragment[0]++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static inline void cobs_decoder_reset(struct cobs_decoder *dec)
|
||||
{
|
||||
dec->code = 0xff;
|
||||
dec->code_index = 0;
|
||||
}
|
||||
|
||||
static inline bool cobs_decoder_needs_more_data(struct cobs_decoder *dec)
|
||||
{
|
||||
return dec->code_index != 0;
|
||||
}
|
||||
|
||||
int cobs_decoder_init(struct cobs_decoder *dec, cobs_stream_cb cb, void *user_data, uint32_t flags)
|
||||
{
|
||||
if (cb == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
__ASSERT_NO_MSG(dec != NULL);
|
||||
|
||||
dec->cb = cb;
|
||||
dec->cb_user_data = user_data;
|
||||
dec->flags = flags;
|
||||
|
||||
cobs_decoder_reset(dec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cobs_decoder_close(struct cobs_decoder *dec)
|
||||
{
|
||||
int ret;
|
||||
|
||||
__ASSERT_NO_MSG(dec != NULL);
|
||||
|
||||
ret = cobs_decoder_needs_more_data(dec) ? -EINVAL : 0;
|
||||
cobs_decoder_reset(dec);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cobs_decoder_write(struct cobs_decoder *dec, const uint8_t *buf, size_t len)
|
||||
{
|
||||
uint8_t sentinel = COBS_FLAG_CUSTOM_DELIMITER(dec->flags);
|
||||
int ret;
|
||||
|
||||
__ASSERT_NO_MSG(dec != NULL);
|
||||
__ASSERT_NO_MSG(len <= INT_MAX);
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
uint8_t data = buf[i] ^ sentinel;
|
||||
|
||||
if (data == 0x00) {
|
||||
if ((dec->flags & COBS_FLAG_TRAILING_DELIMITER) == 0U ||
|
||||
cobs_decoder_needs_more_data(dec)) {
|
||||
/* Decoder shouldn't get delimiters or unexpected end of data */
|
||||
cobs_decoder_reset(dec);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Notify frame delimiter was seen */
|
||||
ret = dec->cb(NULL, 0, dec->cb_user_data);
|
||||
if (ret < 0) {
|
||||
cobs_decoder_reset(dec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Reset state */
|
||||
cobs_decoder_reset(dec);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dec->code_index > 0) {
|
||||
ret = dec->cb(&data, 1, dec->cb_user_data);
|
||||
if (ret < 0) {
|
||||
cobs_decoder_reset(dec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dec->code_index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
dec->code_index = data;
|
||||
|
||||
if (dec->code != 0xff) {
|
||||
/* Group finished, output zero byte */
|
||||
data = 0x00;
|
||||
|
||||
ret = dec->cb(&data, 1, dec->cb_user_data);
|
||||
if (ret < 0) {
|
||||
cobs_decoder_reset(dec);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
dec->code = dec->code_index;
|
||||
dec->code_index--;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -60,18 +60,20 @@ struct cobs_test_item {
|
||||
size_t decoded_len;
|
||||
const uint8_t *encoded;
|
||||
size_t encoded_len;
|
||||
uint8_t delimiter;
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
#define U8(...) (uint8_t[]) __VA_ARGS__
|
||||
|
||||
#define COBS_ITEM(d, e, del, n) \
|
||||
{.name = n, \
|
||||
.decoded = d, \
|
||||
.decoded_len = sizeof(d), \
|
||||
.encoded = e, \
|
||||
.encoded_len = sizeof(e), \
|
||||
.delimiter = del}
|
||||
#define COBS_ITEM(d, e, f, n) \
|
||||
{ \
|
||||
.name = n, \
|
||||
.decoded = d, \
|
||||
.decoded_len = sizeof(d), \
|
||||
.encoded = e, \
|
||||
.encoded_len = sizeof(e), \
|
||||
.flags = f, \
|
||||
}
|
||||
|
||||
static const struct cobs_test_item cobs_dataset[] = {
|
||||
COBS_ITEM(U8({}), U8({0x01}), COBS_DEFAULT_DELIMITER, "Empty"),
|
||||
@@ -91,12 +93,16 @@ static const struct cobs_test_item cobs_dataset[] = {
|
||||
COBS_ITEM(U8({'1', '2', '3', '4', '5', 0x00, '6', '7', '8', '9', 0x00}),
|
||||
U8({0x06, '1', '2', '3', '4', '5', 0x05, '6', '7', '8', '9', 0x01}),
|
||||
COBS_DEFAULT_DELIMITER, "Trailing zero"),
|
||||
COBS_ITEM(U8({}), U8({0x01}), 0x7F, "Empty with custom delimiter 0x7F"),
|
||||
COBS_ITEM(U8({'1'}), U8({0x02, '1'}), 0x7F, "One char with custom delimiter 0x7F"),
|
||||
COBS_ITEM(U8({0x7F}), U8({0x01, 0x01}), 0x7F, "One 0x7F delimiter"),
|
||||
COBS_ITEM(U8({0x7F, 0x7F}), U8({0x01, 0x01, 0x01}), 0x7F, "Two 0x7F delimiters"),
|
||||
COBS_ITEM(U8({0x7F, 0x7F, 0x7F}), U8({0x01, 0x01, 0x01, 0x01}), 0x7F,
|
||||
"Three 0x7F delimiters"),
|
||||
COBS_ITEM(U8({}), U8({0x01 ^ 0x7F}), COBS_FLAG_CUSTOM_DELIMITER(0x7F),
|
||||
"Empty with custom delimiter 0x7F"),
|
||||
COBS_ITEM(U8({'1'}), U8({0x7D, '1' ^ 0x7F}), COBS_FLAG_CUSTOM_DELIMITER(0x7F),
|
||||
"One char with custom delimiter 0x7F"),
|
||||
COBS_ITEM(U8({0x7F}), U8({0x7D, 0x00}), COBS_FLAG_CUSTOM_DELIMITER(0x7F),
|
||||
"One 0x7F delimiter"),
|
||||
COBS_ITEM(U8({0x7F, 0x7F}), U8({0x7C, 0x00, 0x00}), COBS_FLAG_CUSTOM_DELIMITER(0x7F),
|
||||
"Two 0x7F delimiters"),
|
||||
COBS_ITEM(U8({0x7F, 0x7F, 0x7F}), U8({0x7B, 0x00, 0x00, 0x00}),
|
||||
COBS_FLAG_CUSTOM_DELIMITER(0x7F), "Three 0x7F delimiters"),
|
||||
COBS_ITEM(
|
||||
U8({'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
||||
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'a', 'b',
|
||||
@@ -354,13 +360,12 @@ ZTEST_F(cobs_tests, test_encode)
|
||||
int ret;
|
||||
|
||||
ARRAY_FOR_EACH(cobs_dataset, idx) {
|
||||
uint8_t delimiter = cobs_dataset[idx].delimiter;
|
||||
uint32_t flags = cobs_dataset[idx].flags;
|
||||
|
||||
net_buf_add_mem(fixture->test_data, cobs_dataset[idx].decoded,
|
||||
cobs_dataset[idx].decoded_len);
|
||||
|
||||
ret = cobs_encode(fixture->test_data, fixture->encoded,
|
||||
COBS_FLAG_CUSTOM_DELIMITER(delimiter));
|
||||
ret = cobs_encode(fixture->test_data, fixture->encoded, flags);
|
||||
zassert_ok(ret, "COBS encoding failed for %s", cobs_dataset[idx].name);
|
||||
zassert_equal(cobs_dataset[idx].encoded_len, fixture->encoded->len,
|
||||
"Encoded length does not match expected for %s",
|
||||
@@ -383,13 +388,12 @@ ZTEST_F(cobs_tests, test_decode)
|
||||
int ret;
|
||||
|
||||
ARRAY_FOR_EACH(cobs_dataset, idx) {
|
||||
uint8_t delimiter = cobs_dataset[idx].delimiter;
|
||||
uint32_t flags = cobs_dataset[idx].flags;
|
||||
|
||||
net_buf_add_mem(fixture->test_data, cobs_dataset[idx].decoded,
|
||||
cobs_dataset[idx].decoded_len);
|
||||
|
||||
ret = cobs_decode(fixture->encoded, fixture->test_data,
|
||||
COBS_FLAG_CUSTOM_DELIMITER(delimiter));
|
||||
ret = cobs_decode(fixture->encoded, fixture->test_data, flags);
|
||||
zassert_ok(ret, "COBS decoding failed for %s", cobs_dataset[idx].name);
|
||||
zassert_equal(cobs_dataset[idx].decoded_len, fixture->test_data->len,
|
||||
"Decoded length does not match expected for %s",
|
||||
@@ -412,14 +416,14 @@ ZTEST_F(cobs_tests, test_encode_trailing_delimiter)
|
||||
int ret;
|
||||
|
||||
ARRAY_FOR_EACH(cobs_dataset, idx) {
|
||||
uint8_t delimiter = cobs_dataset[idx].delimiter;
|
||||
uint32_t flags = cobs_dataset[idx].flags;
|
||||
uint8_t delimiter = COBS_FLAG_CUSTOM_DELIMITER(flags);
|
||||
|
||||
net_buf_add_mem(fixture->test_data, cobs_dataset[idx].decoded,
|
||||
cobs_dataset[idx].decoded_len);
|
||||
|
||||
ret = cobs_encode(fixture->test_data, fixture->encoded,
|
||||
COBS_FLAG_TRAILING_DELIMITER |
|
||||
COBS_FLAG_CUSTOM_DELIMITER(delimiter));
|
||||
COBS_FLAG_TRAILING_DELIMITER | flags);
|
||||
zassert_ok(ret, "COBS encoding failed for %s", cobs_dataset[idx].name);
|
||||
zassert_equal(cobs_dataset[idx].encoded_len + 1, fixture->encoded->len,
|
||||
"Encoded length does not match expected for %s",
|
||||
@@ -445,7 +449,8 @@ ZTEST_F(cobs_tests, test_decode_trailing_delimiter)
|
||||
int ret;
|
||||
|
||||
ARRAY_FOR_EACH(cobs_dataset, idx) {
|
||||
uint8_t delimiter = cobs_dataset[idx].delimiter;
|
||||
uint32_t flags = cobs_dataset[idx].flags;
|
||||
uint8_t delimiter = COBS_FLAG_CUSTOM_DELIMITER(flags);
|
||||
|
||||
net_buf_add_mem(fixture->test_data, cobs_dataset[idx].decoded,
|
||||
cobs_dataset[idx].decoded_len);
|
||||
@@ -453,8 +458,7 @@ ZTEST_F(cobs_tests, test_decode_trailing_delimiter)
|
||||
net_buf_add_u8(fixture->encoded, delimiter);
|
||||
|
||||
ret = cobs_decode(fixture->encoded, fixture->test_data,
|
||||
COBS_FLAG_TRAILING_DELIMITER |
|
||||
COBS_FLAG_CUSTOM_DELIMITER(delimiter));
|
||||
COBS_FLAG_TRAILING_DELIMITER | flags);
|
||||
zassert_ok(ret, "COBS decoding failed for %s", cobs_dataset[idx].name);
|
||||
zassert_equal(cobs_dataset[idx].decoded_len, fixture->test_data->len,
|
||||
"Decoded length does not match expected for %s",
|
||||
@@ -479,7 +483,7 @@ ZTEST_F(cobs_tests, test_cobs_invalid_delim_pos)
|
||||
|
||||
net_buf_add_mem(fixture->encoded, data_enc, sizeof(data_enc));
|
||||
ret = cobs_decode(fixture->encoded, fixture->decoded, 0);
|
||||
zassert_true(ret == -EINVAL, "Decoding invalid delimiter caught");
|
||||
zassert_equal(ret, -EINVAL, "Decoding invalid delimiter caught");
|
||||
}
|
||||
|
||||
ZTEST_F(cobs_tests, test_cobs_consecutive_delims)
|
||||
@@ -489,7 +493,7 @@ ZTEST_F(cobs_tests, test_cobs_consecutive_delims)
|
||||
|
||||
net_buf_add_mem(fixture->encoded, data_enc, sizeof(data_enc));
|
||||
ret = cobs_decode(fixture->encoded, fixture->decoded, 0);
|
||||
zassert_true(ret == -EINVAL, "Decoding consecutive delimiters not caught");
|
||||
zassert_equal(ret, -EINVAL, "Decoding consecutive delimiters not caught");
|
||||
}
|
||||
|
||||
ZTEST_F(cobs_tests, test_cobs_invalid_overrun)
|
||||
@@ -499,5 +503,5 @@ ZTEST_F(cobs_tests, test_cobs_invalid_overrun)
|
||||
|
||||
net_buf_add_mem(fixture->encoded, data_enc, sizeof(data_enc));
|
||||
ret = cobs_decode(fixture->encoded, fixture->decoded, 0);
|
||||
zassert_true(ret == -EINVAL, "Decoding insufficient data not caught");
|
||||
zassert_equal(ret, -EINVAL, "Decoding insufficient data not caught");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user