Skip to content

Commit

Permalink
two-buffer encode (#3)
Browse files Browse the repository at this point in the history
* add signatures for inplace vs external, break up tests

* make in parallel

* wip

* wip

* wip

* first external encoding

* const

* wip

* test before incrementing pointer

* fix linux
  • Loading branch information
charlesnicholson authored Apr 11, 2021
1 parent 199cb0e commit f56166a
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 37 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
SRCS := cobs.c \
tests/test_cobs_encode.cc \
tests/test_cobs_encode_inplace.cc \
tests/test_cobs_decode_inplace.cc \
tests/test_wikipedia.cc \
tests/unittest_main.cc

BUILD_DIR := ./build
BUILD_DIR := build
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
DEPS := $(OBJS:.o=.d)
OS := $(shell uname)
Expand Down
116 changes: 91 additions & 25 deletions cobs.c
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
#include "cobs.h"

#define COBS_ISV COBS_INPLACE_SENTINEL_VALUE
typedef unsigned char cobs_byte_t;

cobs_ret_t cobs_encode_inplace(void *buf, unsigned len) {
if (!buf || (len < 2)) {
return COBS_RET_ERR_BAD_ARG;
}

unsigned char * const src = (unsigned char *)buf;
unsigned cur = 0;
if ((src[cur] != COBS_INPLACE_SENTINEL_VALUE) ||
(src[len - 1] != COBS_INPLACE_SENTINEL_VALUE)) {
cobs_byte_t *cur = (cobs_byte_t *)buf;
cobs_byte_t const *const end = cur + len - 1;
if ((*cur != COBS_ISV) || (*end != COBS_ISV)) {
return COBS_RET_ERR_BAD_PAYLOAD;
}

unsigned patch = cur++;
while (cur != len - 1) {
if (src[cur] == 0) {
if (cur - patch >= 256) {
return COBS_RET_ERR_BAD_PAYLOAD;
}
src[patch] = (unsigned char)(cur - patch);
#define COBS__ENCODE_INPLACE_PATCH() \
do { \
unsigned const ofs = (unsigned)(cur - patch); \
if (ofs > 255) { return COBS_RET_ERR_BAD_PAYLOAD; } \
*patch = (cobs_byte_t)ofs; \
} while (0)

cobs_byte_t *patch = cur++;
while (cur != end) {
if (*cur == 0) {
COBS__ENCODE_INPLACE_PATCH();
patch = cur;
}
++cur;
}

if (cur - patch >= 256) {
return COBS_RET_ERR_BAD_PAYLOAD;
}
COBS__ENCODE_INPLACE_PATCH();
#undef COBS__ENCODE_INPLACE_PATCH

src[patch] = (unsigned char)(cur - patch);
src[cur] = 0;
*cur = 0;
return COBS_RET_SUCCESS;
}

Expand All @@ -38,22 +42,84 @@ cobs_ret_t cobs_decode_inplace(void *buf, unsigned len) {
return COBS_RET_ERR_BAD_ARG;
}

unsigned char * const src = (unsigned char *)buf;
unsigned cur = 0;
if ((src[cur] == 0) || (src[len - 1] != 0)) {
cobs_byte_t *cur = (cobs_byte_t *)buf;
cobs_byte_t const *const end = cur + len - 1;
if ((*cur == 0) || (*end != 0)) {
return COBS_RET_ERR_BAD_PAYLOAD;
}

while (src[cur]) {
unsigned const ofs = src[cur];
src[cur] = 0;
while (*cur) {
unsigned const ofs = *cur;
*cur = 0;
cur += ofs;
if (cur >= len) {
if (cur > end) {
return COBS_RET_ERR_BAD_PAYLOAD;
}
}

src[0] = COBS_INPLACE_SENTINEL_VALUE;
src[cur] = COBS_INPLACE_SENTINEL_VALUE;
*(cobs_byte_t *)buf = COBS_ISV;
*cur = COBS_ISV;
return COBS_RET_SUCCESS;
}

cobs_ret_t cobs_encode(void const *dec,
unsigned dec_len,
void *out_enc,
unsigned enc_max,
unsigned *out_enc_len) {
if (!dec || !out_enc || !out_enc_len) {
return COBS_RET_ERR_BAD_ARG;
}
if ((enc_max < 2) || (enc_max < dec_len)) {
return COBS_RET_ERR_BAD_ARG;
}

cobs_byte_t const *src = (cobs_byte_t const *)dec;
cobs_byte_t *dst = (cobs_byte_t *)out_enc;
cobs_byte_t *code_dst = dst++;
cobs_byte_t code = 1;
unsigned enc_len = 1;

while (dec_len--) {
cobs_byte_t const byte = *src;
if (byte) {
if (++enc_len > enc_max) { return COBS_RET_ERR_EXHAUSTED; }
*dst++ = byte;
++code;
}

if ((byte == 0) || (code == 0xFF)) {
*code_dst = code;
code_dst = dst;
code = 1;

if ((byte == 0) || dec_len) {
if (++enc_len > enc_max) { return COBS_RET_ERR_EXHAUSTED; }
++dst;
}
}
++src;
}

if (++enc_len > enc_max) { return COBS_RET_ERR_EXHAUSTED; }
*dst++ = 0;

*code_dst = code;
*out_enc_len = enc_len;
return COBS_RET_SUCCESS;
}


cobs_ret_t cobs_decode(void const *enc,
unsigned enc_len,
void *out_dec,
unsigned dec_max,
unsigned *out_dec_len) {
if (!enc || !out_dec || !out_dec_len) {
return COBS_RET_ERR_BAD_ARG;
}
if ((enc_len < 2) || (dec_max < enc_len)) {
return COBS_RET_ERR_BAD_ARG;
}
return COBS_RET_SUCCESS;
}
23 changes: 12 additions & 11 deletions cobs.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
typedef enum {
COBS_RET_SUCCESS = 0,
COBS_RET_ERR_BAD_ARG,
COBS_RET_ERR_BAD_PAYLOAD
COBS_RET_ERR_BAD_PAYLOAD,
COBS_RET_ERR_EXHAUSTED
} cobs_ret_t;

enum {
Expand All @@ -18,17 +19,17 @@ extern "C" {
cobs_ret_t cobs_encode_inplace(void *buf, unsigned len);
cobs_ret_t cobs_decode_inplace(void *buf, unsigned len);

cobs_ret_t cobs_encode(void const *src,
unsigned src_len,
void *dst,
unsigned dst_len,
unsigned *out_encoded_len);
cobs_ret_t cobs_encode(void const *dec,
unsigned dec_len,
void *out_enc,
unsigned enc_max,
unsigned *out_enc_len);

cobs_ret_t cobs_decode(void const *src,
unsigned src_len,
void *dst,
unsigned dst_len,
unsigned *out_decoded_len);
cobs_ret_t cobs_decode(void const *enc,
unsigned enc_len,
void *out_dec,
unsigned dec_max,
unsigned *out_dec_len);

#ifdef __cplusplus
}
Expand Down
122 changes: 122 additions & 0 deletions tests/test_cobs_encode.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#include "../cobs.h"
#include "catch.hpp"

#include <cstring>

using byte_vec_t = std::vector< unsigned char >;

TEST_CASE("Encoding validation", "[cobs_encode]") {
unsigned char enc[32], dec[32];
unsigned const enc_n = sizeof(enc);
unsigned const dec_n = sizeof(dec);
unsigned enc_len;

SECTION("Null buffer pointer") {
REQUIRE( cobs_encode(nullptr, dec_n, enc, enc_n, &enc_len) ==
COBS_RET_ERR_BAD_ARG );
REQUIRE( cobs_encode(dec, dec_n, nullptr, enc_n, &enc_len) ==
COBS_RET_ERR_BAD_ARG );
REQUIRE( cobs_encode(dec, dec_n, enc, enc_n, nullptr) ==
COBS_RET_ERR_BAD_ARG );
}

SECTION("Invalid enc_max") {
REQUIRE( cobs_encode(dec, dec_n, enc, 0, &enc_len) ==
COBS_RET_ERR_BAD_ARG );
REQUIRE( cobs_encode(dec, dec_n, enc, 1, &enc_len) ==
COBS_RET_ERR_BAD_ARG );
REQUIRE( cobs_encode(dec, dec_n, enc, dec_n - 2, &enc_len) ==
COBS_RET_ERR_BAD_ARG );
REQUIRE( cobs_encode(dec, dec_n, enc, dec_n - 1, &enc_len) ==
COBS_RET_ERR_BAD_ARG );
}
}

TEST_CASE("Simple encodings", "[cobs_encode]") {
unsigned char dec[16];
unsigned char enc[16];
unsigned enc_len;

SECTION("Empty") {
REQUIRE( cobs_encode(&dec, 0, enc, sizeof(enc), &enc_len) ==
COBS_RET_SUCCESS );
REQUIRE( enc_len == 2 );
REQUIRE( byte_vec_t(enc, enc + enc_len) == byte_vec_t{0x01, 0x00} );
}

SECTION("1 nonzero byte") {
dec[0] = 0x34;
REQUIRE( cobs_encode(&dec, 1, enc, sizeof(enc), &enc_len) ==
COBS_RET_SUCCESS );
REQUIRE( enc_len == 3 );
REQUIRE( byte_vec_t(enc, enc + enc_len) == byte_vec_t{0x02, 0x34, 0x00} );
}

SECTION("2 nonzero bytes") {
dec[0] = 0x34;
dec[1] = 0x56;
REQUIRE( cobs_encode(&dec, 2, enc, sizeof(enc), &enc_len) ==
COBS_RET_SUCCESS );
REQUIRE( enc_len == 4 );
REQUIRE( byte_vec_t(enc, enc + enc_len) ==
byte_vec_t{0x03, 0x34, 0x56, 0x00} );
}

SECTION("8 nonzero bytes") {
dec[0] = 0x12;
dec[1] = 0x34;
dec[2] = 0x56;
dec[3] = 0x78;
dec[4] = 0x9A;
dec[5] = 0xBC;
dec[6] = 0xDE;
dec[7] = 0xFF;
REQUIRE( cobs_encode(&dec, 8, enc, sizeof(enc), &enc_len) ==
COBS_RET_SUCCESS );
REQUIRE( enc_len == 10 );
REQUIRE( byte_vec_t(enc, enc + enc_len) ==
byte_vec_t{0x09,0x12,0x34,0x56,0x78,0x9A,0xBC,0xDE,0xFF,0x00} );
}

SECTION("1 zero byte") {
dec[0] = 0x00;
REQUIRE( cobs_encode(&dec, 1, enc, sizeof(enc), &enc_len) ==
COBS_RET_SUCCESS );
REQUIRE( enc_len == 3 );
REQUIRE( byte_vec_t(enc, enc + enc_len) == byte_vec_t{0x01, 0x01, 0x00} );
}

SECTION("2 zero bytes") {
dec[0] = 0x00;
dec[1] = 0x00;
REQUIRE( cobs_encode(&dec, 2, enc, sizeof(enc), &enc_len) ==
COBS_RET_SUCCESS );
REQUIRE( enc_len == 4 );
REQUIRE( byte_vec_t(enc, enc + enc_len) ==
byte_vec_t{0x01, 0x01, 0x01, 0x00} );
}

SECTION("8 nonzero bytes") {
memset(dec, 0, 8);
REQUIRE( cobs_encode(&dec, 8, enc, sizeof(enc), &enc_len) ==
COBS_RET_SUCCESS );
REQUIRE( enc_len == 10 );
REQUIRE( byte_vec_t(enc, enc + enc_len) ==
byte_vec_t{0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00} );
}

SECTION("4 alternating zero/nonzero bytes") {
dec[0] = 0x00;
dec[1] = 0x11;
dec[2] = 0x00;
dec[3] = 0x22;
REQUIRE( cobs_encode(&dec, 4, enc, sizeof(enc), &enc_len) ==
COBS_RET_SUCCESS );
REQUIRE( enc_len == 6 );
REQUIRE( byte_vec_t(enc, enc + enc_len) ==
byte_vec_t{0x01, 0x02, 0x11, 0x02, 0x22, 0x00} );
}
}

TEST_CASE("0xFE and 0xFF single code-block cases", "[cobs_encode]") {
}

0 comments on commit f56166a

Please sign in to comment.