From b88bcf8339d92941d3e967ddc512a8fd752997ae Mon Sep 17 00:00:00 2001 From: Charles Nicholson Date: Sun, 4 Aug 2024 16:14:35 -0400 Subject: [PATCH 1/2] rename inplace to tinyframe --- Makefile | 4 +-- cobs.c | 12 ++++---- cobs.h | 10 +++---- ...place.cc => test_cobs_decode_tinyframe.cc} | 30 +++++++++---------- ...place.cc => test_cobs_encode_tinyframe.cc} | 24 +++++++-------- tests/test_wikipedia.cc | 8 ++--- 6 files changed, 44 insertions(+), 44 deletions(-) rename tests/{test_cobs_decode_inplace.cc => test_cobs_decode_tinyframe.cc} (84%) rename tests/{test_cobs_encode_inplace.cc => test_cobs_encode_tinyframe.cc} (87%) diff --git a/Makefile b/Makefile index 465a0d9..974405b 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,10 @@ SRCS := tests/cobs_encode_max_c.c \ tests/test_cobs_encode_max.cc \ tests/test_cobs_encode.cc \ tests/test_cobs_encode_inc.cc \ - tests/test_cobs_encode_inplace.cc \ + tests/test_cobs_encode_tinyframe.cc \ tests/test_cobs_decode.cc \ tests/test_cobs_decode_inc.cc \ - tests/test_cobs_decode_inplace.cc \ + tests/test_cobs_decode_tinyframe.cc \ tests/test_many_random_payloads.cc \ tests/test_paper_figures.cc \ tests/test_wikipedia.cc \ diff --git a/cobs.c b/cobs.c index a8e0554..98a3bb7 100644 --- a/cobs.c +++ b/cobs.c @@ -1,17 +1,17 @@ // SPDX-License-Identifier: Unlicense OR 0BSD #include "cobs.h" -#define COBS_ISV COBS_INPLACE_SENTINEL_VALUE +#define COBS_TFSV COBS_TINYFRAME_SENTINEL_VALUE typedef unsigned char cobs_byte_t; -cobs_ret_t cobs_encode_inplace(void *buf, size_t len) { +cobs_ret_t cobs_encode_tinyframe(void *buf, size_t len) { if (!buf || (len < 2)) { return COBS_RET_ERR_BAD_ARG; } cobs_byte_t *const src = (cobs_byte_t *)buf; - if ((src[0] != COBS_ISV) || (src[len - 1] != COBS_ISV)) { + if ((src[0] != COBS_TFSV) || (src[len - 1] != COBS_TFSV)) { return COBS_RET_ERR_BAD_PAYLOAD; } @@ -36,7 +36,7 @@ cobs_ret_t cobs_encode_inplace(void *buf, size_t len) { return COBS_RET_SUCCESS; } -cobs_ret_t cobs_decode_inplace(void *buf, size_t const len) { +cobs_ret_t cobs_decode_tinyframe(void *buf, size_t const len) { if (!buf || (len < 2)) { return COBS_RET_ERR_BAD_ARG; } @@ -56,8 +56,8 @@ cobs_ret_t cobs_decode_inplace(void *buf, size_t const len) { if (cur != len - 1) { return COBS_RET_ERR_BAD_PAYLOAD; } - src[0] = COBS_ISV; - src[len - 1] = COBS_ISV; + src[0] = COBS_TFSV; + src[len - 1] = COBS_TFSV; return COBS_RET_SUCCESS; } diff --git a/cobs.h b/cobs.h index 4d7192a..a73763f 100644 --- a/cobs.h +++ b/cobs.h @@ -23,7 +23,7 @@ enum { COBS_FRAME_DELIMITER = 0x00, // In-place encoding mandatory placeholder byte values. - COBS_INPLACE_SENTINEL_VALUE = 0x5A, + COBS_TINYFRAME_SENTINEL_VALUE = 0x5A, // In-place encodings that fit in a buffer of this size will always succeed. COBS_INPLACE_SAFE_BUFFER_SIZE = 256 @@ -45,7 +45,7 @@ inline constexpr size_t COBS_ENCODE_MAX(size_t DECODED_LEN) { (1 + (DECODED_LEN) + (((DECODED_LEN) + 253) / 254) + ((DECODED_LEN) == 0)) #endif -// cobs_encode_inplace +// cobs_encode_tinyframe // // Encode in-place the contents of the provided buffer |buf| of length |len|. Returns // COBS_RET_SUCCESS on successful encoding. @@ -64,9 +64,9 @@ inline constexpr size_t COBS_ENCODE_MAX(size_t DECODED_LEN) { // // If the function returns COBS_RET_ERR_BAD_PAYLOAD, the contents of |buf| are left // indeterminate and must not be relied on to be fully encoded or decoded. -cobs_ret_t cobs_encode_inplace(void *buf, size_t len); +cobs_ret_t cobs_encode_tinyframe(void *buf, size_t len); -// cobs_decode_inplace +// cobs_decode_tinyframe // // Decode in-place the contents of the provided buffer |buf| of length |len|. // Returns COBS_RET_SUCCESS on successful decoding. @@ -84,7 +84,7 @@ cobs_ret_t cobs_encode_inplace(void *buf, size_t len); // // If the function returns COBS_RET_ERR_BAD_PAYLOAD, the contents of |buf| are left // indeterminate and must not be relied on to be fully encoded or decoded. -cobs_ret_t cobs_decode_inplace(void *buf, size_t len); +cobs_ret_t cobs_decode_tinyframe(void *buf, size_t len); // cobs_decode // diff --git a/tests/test_cobs_decode_inplace.cc b/tests/test_cobs_decode_tinyframe.cc similarity index 84% rename from tests/test_cobs_decode_inplace.cc rename to tests/test_cobs_decode_tinyframe.cc index b690dbc..280c7f8 100644 --- a/tests/test_cobs_decode_inplace.cc +++ b/tests/test_cobs_decode_tinyframe.cc @@ -6,23 +6,23 @@ #include #include -static constexpr byte_t CSV = COBS_INPLACE_SENTINEL_VALUE; +static constexpr byte_t CSV = COBS_TINYFRAME_SENTINEL_VALUE; namespace { cobs_ret_t cobs_decode_vec(byte_vec_t &v) { - return cobs_decode_inplace(v.data(), static_cast(v.size())); + return cobs_decode_tinyframe(v.data(), static_cast(v.size())); } } // namespace TEST_CASE("Inplace decoding validation") { SUBCASE("Null buffer pointer") { - REQUIRE(cobs_decode_inplace(nullptr, 123) == COBS_RET_ERR_BAD_ARG); + REQUIRE(cobs_decode_tinyframe(nullptr, 123) == COBS_RET_ERR_BAD_ARG); } SUBCASE("Invalid buf_len") { char buf; - REQUIRE(cobs_decode_inplace(&buf, 0) == COBS_RET_ERR_BAD_ARG); - REQUIRE(cobs_decode_inplace(&buf, 1) == COBS_RET_ERR_BAD_ARG); + REQUIRE(cobs_decode_tinyframe(&buf, 0) == COBS_RET_ERR_BAD_ARG); + REQUIRE(cobs_decode_tinyframe(&buf, 1) == COBS_RET_ERR_BAD_ARG); } SUBCASE("Invalid payload") { @@ -123,16 +123,16 @@ void verify_decode_inplace(unsigned char *inplace, size_t payload_len) { payload_len); REQUIRE(external_len == payload_len); - REQUIRE(cobs_decode_inplace(inplace, payload_len + 2) == COBS_RET_SUCCESS); + REQUIRE(cobs_decode_tinyframe(inplace, payload_len + 2) == COBS_RET_SUCCESS); REQUIRE(byte_vec_t(inplace + 1, inplace + external_len + 1) == byte_vec_t(external.data(), external.data() + external_len)); } void fill_encode_inplace(byte_t *inplace, size_t payload_len, byte_t f) { - inplace[0] = COBS_INPLACE_SENTINEL_VALUE; + inplace[0] = COBS_TINYFRAME_SENTINEL_VALUE; memset(inplace + 1, f, payload_len); - inplace[payload_len + 1] = COBS_INPLACE_SENTINEL_VALUE; - REQUIRE_MESSAGE(cobs_encode_inplace(inplace, payload_len + 2) == COBS_RET_SUCCESS, + inplace[payload_len + 1] = COBS_TINYFRAME_SENTINEL_VALUE; + REQUIRE_MESSAGE(cobs_encode_tinyframe(inplace, payload_len + 2) == COBS_RET_SUCCESS, payload_len); } } // namespace @@ -163,24 +163,24 @@ TEST_CASE("Decode: Inplace == External") { SUBCASE("Fill with zero/one pattern") { for (auto i{ 0u }; i < inplace.size() - 2; ++i) { - inplace[0] = COBS_INPLACE_SENTINEL_VALUE; + inplace[0] = COBS_TINYFRAME_SENTINEL_VALUE; for (auto j{ 1u }; j < i; ++j) { inplace[j] = j & 1; } - inplace[i + 1] = COBS_INPLACE_SENTINEL_VALUE; - REQUIRE(cobs_encode_inplace(inplace.data(), i + 2) == COBS_RET_SUCCESS); + inplace[i + 1] = COBS_TINYFRAME_SENTINEL_VALUE; + REQUIRE(cobs_encode_tinyframe(inplace.data(), i + 2) == COBS_RET_SUCCESS); verify_decode_inplace(inplace.data(), i); } } SUBCASE("Fill with one/zero pattern") { for (auto i{ 0u }; i < inplace.size() - 2; ++i) { - inplace[0] = COBS_INPLACE_SENTINEL_VALUE; + inplace[0] = COBS_TINYFRAME_SENTINEL_VALUE; for (auto j = 1u; j < i; ++j) { inplace[j] = (j & 1) ^ 1; } - inplace[i + 1] = COBS_INPLACE_SENTINEL_VALUE; - REQUIRE(cobs_encode_inplace(inplace.data(), i + 2) == COBS_RET_SUCCESS); + inplace[i + 1] = COBS_TINYFRAME_SENTINEL_VALUE; + REQUIRE(cobs_encode_tinyframe(inplace.data(), i + 2) == COBS_RET_SUCCESS); verify_decode_inplace(inplace.data(), i); } } diff --git a/tests/test_cobs_encode_inplace.cc b/tests/test_cobs_encode_tinyframe.cc similarity index 87% rename from tests/test_cobs_encode_inplace.cc rename to tests/test_cobs_encode_tinyframe.cc index 490e34b..c97a540 100644 --- a/tests/test_cobs_encode_inplace.cc +++ b/tests/test_cobs_encode_tinyframe.cc @@ -5,23 +5,23 @@ #include #include -static constexpr byte_t CSV = COBS_INPLACE_SENTINEL_VALUE; +static constexpr byte_t CSV = COBS_TINYFRAME_SENTINEL_VALUE; namespace { cobs_ret_t cobs_encode_vec(byte_vec_t &v) { - return cobs_encode_inplace(v.data(), v.size()); + return cobs_encode_tinyframe(v.data(), v.size()); } } // namespace TEST_CASE("Inplace encoding validation") { SUBCASE("Null buffer pointer") { - REQUIRE(cobs_encode_inplace(nullptr, 123) == COBS_RET_ERR_BAD_ARG); + REQUIRE(cobs_encode_tinyframe(nullptr, 123) == COBS_RET_ERR_BAD_ARG); } SUBCASE("Invalid buf_len") { char buf; - REQUIRE(cobs_encode_inplace(&buf, 0) == COBS_RET_ERR_BAD_ARG); - REQUIRE(cobs_encode_inplace(&buf, 1) == COBS_RET_ERR_BAD_ARG); + REQUIRE(cobs_encode_tinyframe(&buf, 0) == COBS_RET_ERR_BAD_ARG); + REQUIRE(cobs_encode_tinyframe(&buf, 1) == COBS_RET_ERR_BAD_ARG); } SUBCASE("Invalid sentinel values") { @@ -133,14 +133,14 @@ void verify_encode_inplace(byte_t *inplace, size_t payload_len) { external.size(), &external_len) == COBS_RET_SUCCESS); - REQUIRE(cobs_encode_inplace(inplace, payload_len + 2) == COBS_RET_SUCCESS); + REQUIRE(cobs_encode_tinyframe(inplace, payload_len + 2) == COBS_RET_SUCCESS); REQUIRE(byte_vec_t(inplace, inplace + payload_len + 2) == external); } void fill_inplace(byte_t *inplace, size_t payload_len, byte_t f) { memset(inplace + 1, f, payload_len); - inplace[0] = COBS_INPLACE_SENTINEL_VALUE; - inplace[payload_len + 1] = COBS_INPLACE_SENTINEL_VALUE; + inplace[0] = COBS_TINYFRAME_SENTINEL_VALUE; + inplace[payload_len + 1] = COBS_TINYFRAME_SENTINEL_VALUE; } } // namespace @@ -170,22 +170,22 @@ TEST_CASE("Encode: Inplace == External") { SUBCASE("Fill with zero/one pattern") { for (auto i = 0u; i < sizeof(inplace) - 2; ++i) { - inplace[0] = COBS_INPLACE_SENTINEL_VALUE; + inplace[0] = COBS_TINYFRAME_SENTINEL_VALUE; for (auto j = 1u; j < i; ++j) { inplace[j] = j & 1; } - inplace[i + 1] = COBS_INPLACE_SENTINEL_VALUE; + inplace[i + 1] = COBS_TINYFRAME_SENTINEL_VALUE; verify_encode_inplace(inplace, i); } } SUBCASE("Fill with one/zero pattern") { for (auto i = 0u; i < sizeof(inplace) - 2; ++i) { - inplace[0] = COBS_INPLACE_SENTINEL_VALUE; + inplace[0] = COBS_TINYFRAME_SENTINEL_VALUE; for (auto j = 1u; j < i; ++j) { inplace[j] = (j & 1) ^ 1; } - inplace[i + 1] = COBS_INPLACE_SENTINEL_VALUE; + inplace[i + 1] = COBS_TINYFRAME_SENTINEL_VALUE; verify_encode_inplace(inplace, i); } } diff --git a/tests/test_wikipedia.cc b/tests/test_wikipedia.cc index 2a0a38a..01ec4fa 100644 --- a/tests/test_wikipedia.cc +++ b/tests/test_wikipedia.cc @@ -7,15 +7,15 @@ namespace { void round_trip_inplace(byte_vec_t const &decoded, byte_vec_t const &encoded) { - byte_vec_t decoded_inplace{ COBS_INPLACE_SENTINEL_VALUE }; + byte_vec_t decoded_inplace{ COBS_TINYFRAME_SENTINEL_VALUE }; decoded_inplace.insert(std::end(decoded_inplace), decoded.begin(), decoded.end()); - decoded_inplace.push_back(COBS_INPLACE_SENTINEL_VALUE); + decoded_inplace.push_back(COBS_TINYFRAME_SENTINEL_VALUE); byte_vec_t x(decoded_inplace); - REQUIRE(cobs_encode_inplace(x.data(), x.size()) == COBS_RET_SUCCESS); + REQUIRE(cobs_encode_tinyframe(x.data(), x.size()) == COBS_RET_SUCCESS); REQUIRE(x == encoded); - REQUIRE(cobs_decode_inplace(x.data(), x.size()) == COBS_RET_SUCCESS); + REQUIRE(cobs_decode_tinyframe(x.data(), x.size()) == COBS_RET_SUCCESS); REQUIRE(x == decoded_inplace); } From cf493a26e940cb46662cfcd42e65bff27a7657bd Mon Sep 17 00:00:00 2001 From: Charles Nicholson Date: Sun, 4 Aug 2024 16:45:13 -0400 Subject: [PATCH 2/2] update the comments, update windows --- README.md | 60 ++++++++++++++++++++++++++-------------------------- make-win.bat | 4 ++-- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 7429153..e37db0c 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,16 @@ Finally, I didn't see as many unit tests as I'd have liked in the other librarie ## Metrics -It's pretty small, and you probably need either `cobs_[en|de]code_inplace` _or_ `cobs_[en|de]code[_inc*]`, but not both. +It's pretty small, and you probably need either `cobs_[en|de]code_tinyframe` _or_ `cobs_[en|de]code[_inc*]`, but not both. ``` ❯ arm-none-eabi-gcc -mthumb -mcpu=cortex-m4 -Os -c cobs.c ❯ arm-none-eabi-nm --print-size --size-sort cobs.o 0000011c 0000001e T cobs_encode_inc_end (30 bytes) 0000007a 00000022 T cobs_encode_inc_begin (34 bytes) -00000048 00000032 T cobs_decode_inplace (50 bytes) +00000048 00000032 T cobs_decode_tinyframe (50 bytes) 0000013a 00000034 T cobs_encode (52 bytes) -00000000 00000048 T cobs_encode_inplace (72 bytes) +00000000 00000048 T cobs_encode_tinyframe (72 bytes) 0000009c 00000080 T cobs_encode_inc (128 bytes) 0000016e 00000090 T cobs_decode (144 bytes) Total 1fe (510 bytes) @@ -62,6 +62,26 @@ if (result == COBS_RET_SUCCESS) { } ``` +### Decoding + +Decoding works similarly; receive an encoded buffer from somewhere, prepare a buffer to hold the decoded data, and call `cobs_decode`. Decoding can always be performed in-place, since the encoded frames are always larger than the decoded data. Simply pass the same buffer to the `encoded` and `decoded` parameters and the frame will be decoded in-place. + +``` +char encoded[128]; +unsigned encoded_len; +get_encoded_data_from_somewhere(encoded, &encoded_len); + +char decoded[128]; +unsigned decoded_len; +cobs_ret_t const result = cobs_decode(encoded, encoded_len, decoded, sizeof(decoded), &decoded_len); + +if (result == COBS_RET_SUCCESS) { + // decoding succeeded, 'decoded' and 'decoded_len' hold details. +} else { + // decoding failed, look to 'result' for details. +} +``` + ### Incremental Encoding Sometimes it's helpful to be able to encode multiple separate buffers into one target. To do this, use the `cobs_encode_inc` family of functions: initialize a `cobx_enc_ctx_t` in `cobs_encode_inc_begin`, then call `cobs_encode_inc` multiple times, and finish encoding with `cobs_encode_inc_end`. @@ -90,9 +110,9 @@ if (r != COBS_RET_SUCCESS) { /* handle your error, return / assert, whatever */ |encoded_len| contains the length of the encoded buffer. */ ``` -### Encoding In-Place +### Encoding "Tiny Frames" -The COBS protocol requires an extra byte at the beginning and end of the payload. If encoding and decoding in-place, it becomes your responsibility to reserve these extra bytes. It's easy to mess this up and just put your own data at byte 0, but your data must start at byte 1. For safety and sanity, `cobs_encode_inplace` will error with `COBS_RET_ERR_BAD_PAYLOAD` if the first and last bytes aren't explicitly set to the sentinel value. You have to put them there. +If you can guarantee that your payloads are shorter than 254 bytes, then you can use the "tinyframe" API, which lets you both decode and encode in-place in a single buffer. The COBS protocol requires an extra byte at the beginning and end of the payload. If encoding and decoding in-place, it becomes your responsibility to reserve these extra bytes. It's easy to mess this up and just put your own data at byte 0, but your data must start at byte 1. For safety and sanity, `cobs_encode_tinyframe` will error with `COBS_RET_ERR_BAD_PAYLOAD` if the first and last bytes aren't explicitly set to the sentinel value. You have to put them there. (Note that `64` is an arbitrary size in this example, you can use any size you want up to `COBS_INPLACE_SAFE_BUFFER_SIZE`) @@ -103,7 +123,7 @@ buf[63] = COBS_SENTINEL_VALUE; // You have to do this. // Now, fill buf[1 .. 63] with whatever data you want. -cobs_ret_t const result = cobs_encode_inplace(buf, 64); +cobs_ret_t const result = cobs_encode_tinyframe(buf, 64); if (result == COBS_RET_SUCCESS) { // encoding succeeded, 'buf' now holds the encoded data. @@ -111,30 +131,9 @@ if (result == COBS_RET_SUCCESS) { // encoding failed, look to 'result' for details. } ``` -### Decoding With Separate Buffers +### Decoding "Tiny Frames" -Decoding works similarly; receive an encoded buffer from somewhere, prepare a buffer to hold the decoded data, and call `cobs_decode`. -``` -char encoded[128]; -unsigned encoded_len; -get_encoded_data_from_somewhere(encoded, &encoded_len); - -char decoded[128]; -unsigned decoded_len; -cobs_ret_t const result = cobs_decode(encoded, encoded_len, decoded, sizeof(decoded), &decoded_len); - -if (result == COBS_RET_SUCCESS) { - // decoding succeeded, 'decoded' and 'decoded_len' hold details. -} else { - // decoding failed, look to 'result' for details. -} -``` - -### Decoding In-Place - -There are two ways to decode COBS-encoded data in-place. One is simply by calling `cobs_decode` and pass your buffer to both the `enc` and `out_dec` parameters. Since COBS decoding never needs to revisit decoded bytes, and decoded payloads are always shorter than encoded payloads, this is guaranteed to work. - -`cobs_decode_inplace` is also provided and offers byte-layout-parity to `cobs_encode_inplace`. This lets you, for example, decode a payload, change some bytes, and re-encode it all in the same buffer: +`cobs_decode_tinyframe` is also provided and offers byte-layout-parity to `cobs_encode_tinyframe`. This lets you, for example, decode a payload, change some bytes, and re-encode it all in the same buffer: Accumulate data from your source until you encounter a COBS frame delimiter byte of `0x00`. Once you've got that, call `cobs_decode_inplace` on that region of a buffer to do an in-place decoding. The zeroth and final bytes of your payload will be replaced with the `COBS_SENTINEL_VALUE` bytes that, were you _encoding_ in-place, you would have had to place there anyway. @@ -144,7 +143,7 @@ char buf[64]; // You fill 'buf' with an encoded cobs frame (from uart, etc) that ends with 0x00. unsigned const length = you_fill_buf_with_data(buf); -cobs_ret_t const result = cobs_decode_inplace(buf, length); +cobs_ret_t const result = cobs_decode_tinyframe(buf, length); if (result == COBS_RET_SUCCESS) { // decoding succeeded, 'buf' bytes 0 and length-1 are COBS_SENTINEL_VALUE. // your data is in 'buf[1 ... length-2]' @@ -152,6 +151,7 @@ if (result == COBS_RET_SUCCESS) { // decoding failed, look to 'result' for details. } ``` + ## Developing `nanocobs` uses [doctest](https://github.com/onqtam/doctest) for unit and functional testing; its unified mega-header is checked in to the `tests` directory. To build and run all tests on macOS or Linux, run `make -j` from a terminal. To build + run all tests on Windows, run the `vsvarsXX.bat` of your choice to set up the VS environment, then run `make-win.bat` (if you want to make that part better, pull requests are very welcome). diff --git a/make-win.bat b/make-win.bat index 3878331..f35e89d 100644 --- a/make-win.bat +++ b/make-win.bat @@ -3,11 +3,11 @@ cl.exe /W4 /WX /MP /EHsc /std:c++20 ^ tests/cobs_encode_max_c.c ^ tests/test_cobs_decode.cc ^ tests/test_cobs_decode_inc.cc ^ - tests/test_cobs_decode_inplace.cc ^ + tests/test_cobs_decode_tinyframe.cc ^ tests/test_cobs_encode_max.cc ^ tests/test_cobs_encode.cc ^ tests/test_cobs_encode_inc.cc ^ - tests/test_cobs_encode_inplace.cc ^ + tests/test_cobs_encode_tinyframe.cc ^ tests/test_many_random_payloads.cc ^ tests/test_paper_figures.cc ^ tests/test_wikipedia.cc ^