From aeb161f8f70ef2fa0cc2b78200986ad10d1fcf2c Mon Sep 17 00:00:00 2001 From: Nick Krecklow Date: Tue, 19 Dec 2023 18:14:44 -0600 Subject: [PATCH] v3.0.0 (Incompatible with v2.x usage, although the underlying changes are minor.) The biggest change is new function/struct names. The snakecase has been replaced to reduce long lines of code when using the library. Each struct has its name prefixing the function name, followed by _read. This makes a clean assocation between each type (e.g. `TFHeader` and `TFHeader_read`). `enum tf_err_t` -> `TFError` `tf_err_str` -> `TFError_string` `struct tf_var_header_t` -> `TFVarHeader` `tf_read_var_header` -> `TFVarHeader_read` `struct tf_file_header_t` -> `TFHeader` `tf_read_file_header` -> `TFHeader_read` ... Ignoring the new typedefs, the struct names are mostly unchanged with the exception of `struct tf_file_header_t` being shortened to `struct tf_header_t`. There were multiple errors describing undersized buffers provided to _read functions. They have been unified into a single `TF_EINVALID_BUFFER_SIZE` error value. The previously definable macro, TINYFSEQ_MEMCPY, was replaced with usage of `__builtin_memcpy`. --- .clang-format | 12 +- README.md | 36 ++---- example.c | 25 ++-- tinyfseq.h | 320 +++++++++++++++++++++----------------------------- 4 files changed, 166 insertions(+), 227 deletions(-) diff --git a/.clang-format b/.clang-format index b2a6c79..26d2648 100644 --- a/.clang-format +++ b/.clang-format @@ -2,20 +2,22 @@ BasedOnStyle: LLVM AccessModifierOffset: -4 AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: AcrossComments -AlignConsecutiveDeclarations: AcrossComments +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveMacros: Consecutive AlignOperands: Align AllowAllArgumentsOnNextLine: false AllowAllConstructorInitializersOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: Always AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: Never +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Always AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: true AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: false BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: false @@ -34,7 +36,7 @@ BreakBeforeBinaryOperators: None BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakInheritanceList: BeforeColon -ColumnLimit: 0 +ColumnLimit: 80 CompactNamespaces: false ContinuationIndentWidth: 8 IndentCaseLabels: true diff --git a/README.md b/README.md index 6c0f7b9..72c10e1 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,15 @@ # libtinyfseq -A single-file library (~150 LOC) for decoding FSEQ (.fseq) v2.0+ sequence files developed and popularized by -the [fpp](https://github.com/FalconChristmas/fpp) and [xLights](https://github.com/smeighan/xLights) programs. -Additional documentation for the file format is available -at [Cryptkeeper/fseq-file-format](https://github.com/Cryptkeeper/fseq-file-format). +A single-file library (~150 LOC) for decoding FSEQ (.fseq) v2.0+ sequence files developed and popularized by the [fpp](https://github.com/FalconChristmas/fpp) and [xLights](https://github.com/smeighan/xLights) programs. Additional documentation for the file format is available at [Cryptkeeper/fseq-file-format](https://github.com/Cryptkeeper/fseq-file-format). ## Installation - Download and copy `tinyfseq.h` into your project locally, or to your toolchain's include paths - `#include "tinyfseq.h"` as expected (you may need to modify the path). -- Define `TINYFSEQ_IMPLEMENTATION` in a single C/C++ source code - file ([more info on using single-file libraries](https://github.com/nothings/stb#how-do-i-use-these-libraries)) +- Define `TINYFSEQ_IMPLEMENTATION` in a single C/C++ source code file ([more info on using single-file libraries](https://github.com/nothings/stb#how-do-i-use-these-libraries)) A short example of including libtinyfseq and decoding a file header is available in [`example.c`](example.c) -## Library Configuration - -Prior to including `tinyfseq.h`, two definition based options are available: - -1. `TINYFSEQ_MEMCPY` allows you to override the selected `memcpy` function with whatever is best for your platform ( - currently a basic freestanding implementation, `tf_memcpy_impl`) -2. `TINYFSEQ_STRIP_ERR_STRINGS` replaces all literal strings returned by `tf_err_str` with `"NULL"` (as a string) to - reduce the compiled binary size - ## Compatibility - libtinyfseq uses `stdint.h` for fixed-size int types @@ -31,19 +18,16 @@ Prior to including `tinyfseq.h`, two definition based options are available: ## Usage -libtinyfseq only defines three functions for reading the various components of a FSEQ file. See [tinyfseq.h](tinyfseq.h) -for comments describing their usage. - -| Function | Schema | -| ----------------------- | ------------------------------------------------------------ | -| `tf_read_file_header` | https://github.com/Cryptkeeper/fseq-file-format#header | -| `tf_read_var_header` | https://github.com/Cryptkeeper/fseq-file-format#variable | -| `tf_read_channel_range` | https://github.com/Cryptkeeper/fseq-file-format#sparse-range | +libtinyfseq provides decoding functions for the following components of a FSEQ file. See [tinyfseq.h](tinyfseq.h) for comments describing their specific usage. -Two additional utility functions are provided: +| Function | Schema | Type | +| ------------------------- | ----------------------------------------------------------------- | -------------------- | +| `TFHeader_read` | https://github.com/Cryptkeeper/fseq-file-format#header | `TFHeader` | +| `TFVarHeader_read` | https://github.com/Cryptkeeper/fseq-file-format#variable | `TFVarHeader` | +| `TFChannelRange_read` | https://github.com/Cryptkeeper/fseq-file-format#sparse-range | `TFChannelRange` | +| `TFCompressionBlock_read` | https://github.com/Cryptkeeper/fseq-file-format#compression-block | `TFCompressionBlock` | -1. `tf_sequence_duration_seconds` for calculating the duration of a given sequence in seconds -2. `tf_err_str` for mapping `enum tf_err_t` values into their string names +All decoding functions return a `TFError` value, with `TF_OK` indicating success. If an error occurs, the value will be non-zero. An error string can be retrieved via `TFError_string` (and should _not_ be freed by the caller). ## License diff --git a/example.c b/example.c index 108131b..12bebfb 100644 --- a/example.c +++ b/example.c @@ -42,26 +42,33 @@ static uint8_t FILE_DATA[] = { 'U', }; -int main() { +int main(const int argc, char **const argv) { + (void) argc; + (void) argv; + printf("using tinyfseq v%s\n", TINYFSEQ_VERSION); - struct tf_file_header_t header; + TFHeader header; + TFError err; // read the "embedded" file, FILE_DATA - enum tf_err_t err; - if ((err = tf_read_file_header(FILE_DATA, sizeof(FILE_DATA), &header, NULL))) { - return err; + if ((err = TFHeader_read(FILE_DATA, sizeof(FILE_DATA), &header, NULL))) { + printf("libtinyfseq error: %s\n", TFError_string(err)); + + return 1; } + // TODO: use header fields, read variables, etc. + // sequenceUid is an uint64_t that normally stores a timestamp // instead it carries an 8-byte string message // this assert call validates the decoded header prior to printing the char values assert(header.sequenceUid == 6147230170719669321); - int bit_idx; - for (bit_idx = 0; bit_idx < 64; bit_idx += 8) { - uint64_t bit_mask = ((uint64_t) 0xFFu) << bit_idx; - printf("%c", (uint8_t) ((header.sequenceUid & bit_mask) >> bit_idx)); + for (int i = 0; i < 64; i += 8) { + const uint64_t mask = (uint64_t) 0xFFu << i; + + printf("%c", (uint8_t) ((header.sequenceUid & mask) >> i)); } printf("\n"); diff --git a/tinyfseq.h b/tinyfseq.h index 0f9674d..40b60c8 100644 --- a/tinyfseq.h +++ b/tinyfseq.h @@ -1,37 +1,7 @@ -/* - * MIT License - * - * Copyright (c) 2022 Nick Krecklow - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ /** - * libtinyfseq - * v2.1.0 + * libtinyfseq v3.0.0 (2023-12-19) * https://github.com/Cryptkeeper/libtinyfseq - * - * Prior to including `tinyfseq.h`, two definition based options are available: - * - * 1. `TINYFSEQ_MEMCPY` allows you to override the selected `memcpy` function with whatever is best for your platform (currently a basic freestanding - * implementation, `tf_memcpy_impl`) - * 2. `TINYFSEQ_STRIP_ERR_STRINGS` replaces all literal strings returned by `tf_err_str` with `"NULL"` (as a string) to - * reduce the compiled binary size + * MIT License * * `TINYFSEQ_IMPLEMENTATION` should be defined in a SINGLE C/C++ source code file to include the function implementations. * More information on using single-file libraries: https://github.com/nothings/stb#how-do-i-use-these-libraries @@ -41,156 +11,146 @@ #include -#define TINYFSEQ_VERSION "2.1.1" +#define TINYFSEQ_VERSION "3.0.0" -enum tf_err_t { +typedef enum tf_err_t { TF_OK = 0, - TF_EINVALID_HEADER_SIZE, TF_EINVALID_MAGIC, TF_EINVALID_COMPRESSION_TYPE, - TF_EINVALID_VAR_HEADER_SIZE, - TF_EINVALID_VAR_VALUE_SIZE, - TF_EINVALID_CHANNEL_RANGE_SIZE, - TF_EINVALID_COMPRESSION_BLOCK_SIZE, -}; + TF_EINVALID_BUFFER_SIZE, +} TFError; /** - * `tf_err_str` returns a string version of the enum name, including a comment with additional error context. - * `tf_err_str` does not allocate, its return values should not be freed. `tf_err_str` will never return a NULL value. + * `TFError_string` returns a string version of the enum name, including a comment with additional error context. + * `TFError_string` does not allocate, its return values should not be freed. * - * @param err Unknown/invalid values will default to returning "unknown `tf_err_t` value" - * @return A string version of the given value, or "NULL" (as a string) if the library is built with `TF_STRIP_ERR_STRINGS` defined + * @param err Unknown/invalid values will default to returning "unknown TFError value" + * @return A string version of the given value, never NULL */ -const char *tf_err_str(enum tf_err_t err); +const char *TFError_string(TFError err); -enum tf_ctype_t { +typedef enum tf_compression_type_t { TF_COMPRESSION_NONE, TF_COMPRESSION_ZSTD, TF_COMPRESSION_ZLIB, -}; - -struct tf_file_header_t { - uint16_t channelDataOffset; - uint8_t minorVersion; - uint8_t majorVersion; - uint16_t variableDataOffset; - uint32_t channelCount; - uint32_t frameCount; - uint8_t frameStepTimeMillis; - enum tf_ctype_t compressionType; - uint8_t compressionBlockCount; - uint8_t channelRangeCount; - uint64_t sequenceUid; -}; +} TFCompressionType; + +typedef struct tf_header_t { + uint16_t channelDataOffset; + uint8_t minorVersion; + uint8_t majorVersion; + uint16_t variableDataOffset; + uint32_t channelCount; + uint32_t frameCount; + uint8_t frameStepTimeMillis; + enum tf_compression_type_t compressionType; + uint8_t compressionBlockCount; + uint8_t channelRangeCount; + uint64_t sequenceUid; +} TFHeader; /** - * `tf_read_file_header` selectively decodes the fields available within `struct tf_file_header_t` from a given data buffer. + * `TFHeader_read` selectively decodes the fields available within `TFHeader` from a given data buffer. * The buffer should be at least 32 bytes in length (the minimum size of a FSEQ v2.0+ header). * - * @param bd Data buffer to decode into the given `struct tf_file_header_t` pointer + * @param bd Data buffer to decode into the given `TFHeader` pointer * @param bs Size of the data buffer (>=32) - * @param header `struct tf_file_header_t` instance to decode data buffer fields into + * @param header `TFHeader` instance to decode data buffer fields into * @param ep End pointer which upon a successful return will point to the end of the structure within `bd` (may be NULL) - * @return A `tf_err_t` value indicating an error, if any, otherwise `TF_OK` + * @return A `TFError` value indicating an error, if any, otherwise `TF_OK` */ -enum tf_err_t tf_read_file_header(const uint8_t *bd, int bs, struct tf_file_header_t *header, uint8_t **ep); +TFError TFHeader_read(const uint8_t *bd, + int bs, + TFHeader *header, + uint8_t **ep); -struct tf_compression_block_t { +typedef struct tf_compression_block_t { uint32_t firstFrameId; uint32_t size; -}; +} TFCompressionBlock; -enum tf_err_t tf_read_compression_block(const uint8_t *bd, int bs, struct tf_compression_block_t *block, uint8_t **ep); +/** + * `TFCompressionBlock_read` decodes the fields available within `TFCompressionBlock` from a given data buffer. + * The buffer should be at least 8 bytes in length (the fixed size of an encoded compression block). + * + * @param bd Data buffer to decode into the given `TFCompressionBlock` pointer + * @param bs Size of the data buffer (>=8) + * @param block `TFCompressionBlock` instance to decode data buffer fields into + * @param ep End pointer which upon a successful return will point to the end of the structure within `bd` (may be NULL) + * @return A `TFError` value indicating an error, if any, otherwise `TF_OK` + */ +TFError TFCompressionBlock_read(const uint8_t *bd, + int bs, + TFCompressionBlock *block, + uint8_t **ep); -struct tf_var_header_t { +typedef struct tf_var_header_t { uint16_t size; - uint8_t id[2]; -}; + uint8_t id[2]; +} TFVarHeader; /** - * `tf_read_var_header` decodes the fields available within `struct tf_var_header_t` from a given data buffer. + * `TFVarHeader_read` decodes the fields available within `TFVarHeader` from a given data buffer. * The buffer should be at least 5 bytes in length (the minimum size of an encoded variable header). * - * An additional data buffer (`vd`) is used to store the decoded variable value (compared to `struct tf_var_header_t` which + * An additional data buffer (`vd`) is used to store the decoded variable value (compared to `TFVarHeader` which * only contains the header fields). A NULL `vd` value will skip this decoding step. * - * @param bd Data buffer to decode into the given `struct tf_var_header_t` pointer + * @param bd Data buffer to decode into the given `TFVarHeader` pointer * @param bs Size of the data buffer (>=6) - * @param varHeader `struct tf_var_header_t` instance to decode data buffer fields into + * @param varHeader `TFVarHeader` instance to decode data buffer fields into * @param vd Optional data buffer for storing the corresponding value of the variable header (may be NULL) * @param vs Size of the optional data buffer * @param ep End pointer which upon a successful return will point to the end of the structure within `bd` (may be NULL) - * @return A `tf_err_t` value indicating an error, if any, otherwise `TF_OK` + * @return A `TFError` value indicating an error, if any, otherwise `TF_OK` */ -enum tf_err_t tf_read_var_header(const uint8_t *bd, int bs, struct tf_var_header_t *varHeader, uint8_t *vd, int vs, uint8_t **ep); - -struct tf_channel_range_t { +TFError TFVarHeader_read(const uint8_t *bd, + int bs, + TFVarHeader *varHeader, + uint8_t *vd, + int vs, + uint8_t **ep); + +typedef struct tf_channel_range_t { uint32_t firstChannelNumber; uint32_t channelCount; -}; +} TFChannelRange; /** - * `tf_read_channel_range` decodes the fields available within `struct tf_channel_range_t` from a given data buffer. + * `TFChannelRange_Read` decodes the fields available within `TFChannelRange` from a given data buffer. * The buffer should be at least 6 bytes in length (the fixed size of an encoded channel range). * - * @param bd Data buffer to decode into the given `struct tf_channel_range_t` pointer + * @param bd Data buffer to decode into the given `TFChannelRange` pointer * @param bs Size of the data buffer (>=6) - * @param channelRange `struct tf_channel_range_t` instance to decode data buffer fields into + * @param channelRange `TFChannelRange` instance to decode data buffer fields into * @param ep End pointer which upon a successful return will point to the end of the structure within `bd` (may be NULL) - * @return A `tf_err_t` value indicating an error, if any, otherwise `TF_OK` + * @return A `TFError` value indicating an error, if any, otherwise `TF_OK` */ -enum tf_err_t tf_read_channel_range(const uint8_t *bd, int bs, struct tf_channel_range_t *channelRange, uint8_t **ep); - -/** - * Calculates the seconds duration of a sequence in seconds given its frame step time and frame count. - * `frameCount` and `frameStepTimeMillis` are available via a `struct tf_file_header_t` value. - * - * @param frameCount The number of frames within a sequence - * @param frameStepTimeMillis The duration (in milliseconds) of each frame - * @return The duration of the sequence in seconds - */ -float tf_sequence_duration_seconds(uint32_t frameCount, uint8_t frameStepTimeMillis); +TFError TFChannelRange_read(const uint8_t *bd, + int bs, + TFChannelRange *channelRange, + uint8_t **ep); #endif//TINYFSEQ_H #ifdef TINYFSEQ_IMPLEMENTATION -static inline void tf_memcpy_impl(uint8_t *dst, const uint8_t *src, int count) { - for (; count > 0; count--) { - (*dst++) = (*src++); - } -} - -#define TINYFSEQ_MEMCPY tf_memcpy_impl - -const char *tf_err_str(enum tf_err_t err) { -#ifndef TINYFSEQ_STRIP_ERR_STRINGS +const char *TFError_string(const TFError err) { switch (err) { case TF_OK: return "TF_OK (ok)"; - case TF_EINVALID_HEADER_SIZE: - return "TF_EINVALID_HEADER_SIZE (undersized `tf_file_header_t` data decoding buffer)"; case TF_EINVALID_MAGIC: return "TF_EINVALID_MAGIC (invalid magic file signature)"; case TF_EINVALID_COMPRESSION_TYPE: - return "TF_EINVALID_COMPRESSION_TYPE (unknown compression format/unmapped `tf_ctype_t` value)"; - case TF_EINVALID_VAR_HEADER_SIZE: - return "TF_EINVALID_VAR_HEADER_SIZE (undersized `tf_var_header_t` data decoding buffer)"; - case TF_EINVALID_VAR_VALUE_SIZE: - return "TF_EINVALID_VAR_VALUE_SIZE (undersized variable value data decoding buffer)"; - case TF_EINVALID_CHANNEL_RANGE_SIZE: - return "TF_EINVALID_CHANNEL_RANGE_SIZE (undersized `tf_channel_range_t` data decoding buffer)"; - case TF_EINVALID_COMPRESSION_BLOCK_SIZE: - return "TF_EINVALID_COMPRESSION_BLOCK_SIZE (undersized `tf_compression_block_t` data decoding buffer)"; + return "TF_EINVALID_COMPRESSION_TYPE (unknown compression identifier)"; + case TF_EINVALID_BUFFER_SIZE: + return "TF_EINVALID_BUFFER_SIZE (undersized data decoding buffer argument)"; default: - return "unknown `tf_err_t` value"; + return "unknown TFError value"; } -#else - return "NULL"; -#endif } -static inline int tf_is_known_ctype(uint8_t b) { +static int TFCompressionType_valid(const uint8_t b) { switch (b) { case TF_COMPRESSION_NONE: case TF_COMPRESSION_ZSTD: @@ -201,18 +161,17 @@ static inline int tf_is_known_ctype(uint8_t b) { } } -enum tf_err_t tf_read_file_header(const uint8_t *bd, int bs, struct tf_file_header_t *header, uint8_t **ep) { +TFError TFHeader_read(const uint8_t *const bd, + const int bs, + TFHeader *const header, + uint8_t **const ep) { // header structure is a fixed 32 byte size according to schema // https://github.com/Cryptkeeper/fseq-file-format#header - const int FILE_HEADER_SIZE = 32; + const int HEADER_SIZE = 32; - if (bs < FILE_HEADER_SIZE) { - return TF_EINVALID_HEADER_SIZE; - } + if (bs < HEADER_SIZE) return TF_EINVALID_BUFFER_SIZE; - if (bd[0] != 'P' || bd[1] != 'S' || bd[2] != 'E' || bd[3] != 'Q') { - return TF_EINVALID_MAGIC; - } + if (bd[0] != 'P' || bd[1] != 'S' || bd[2] != 'E' || bd[3] != 'Q') return TF_EINVALID_MAGIC; header->channelDataOffset = ((uint16_t *) &bd[4])[0]; header->minorVersion = bd[6]; @@ -226,97 +185,84 @@ enum tf_err_t tf_read_file_header(const uint8_t *bd, int bs, struct tf_file_head // mask to lower 4 bits to filter only the compression type field const uint8_t compressionType = bd[20] & 0xF; - if (!tf_is_known_ctype(compressionType)) { - return TF_EINVALID_COMPRESSION_TYPE; - } + if (!TFCompressionType_valid(compressionType)) return TF_EINVALID_COMPRESSION_TYPE; - header->compressionType = (enum tf_ctype_t) compressionType; + header->compressionType = (TFCompressionType) compressionType; header->compressionBlockCount = bd[21]; header->channelRangeCount = bd[22]; header->sequenceUid = ((uint64_t *) &bd[24])[0]; - if (ep) { - *ep = ((uint8_t *) bd) + FILE_HEADER_SIZE; - } + if (ep) *ep = (uint8_t *) bd + HEADER_SIZE; return TF_OK; } -enum tf_err_t tf_read_compression_block(const uint8_t *bd, int bs, struct tf_compression_block_t *block, uint8_t **ep) { +TFError TFCompressionBlock_read(const uint8_t *const bd, + const int bs, + TFCompressionBlock *const block, + uint8_t **const ep) { const int COMPRESSION_BLOCK_SIZE = 8; - if (bs < COMPRESSION_BLOCK_SIZE) { - return TF_EINVALID_COMPRESSION_BLOCK_SIZE; - } + if (bs < COMPRESSION_BLOCK_SIZE) return TF_EINVALID_BUFFER_SIZE; block->firstFrameId = ((uint32_t *) &bd[0])[0]; block->size = ((uint32_t *) &bd[4])[0]; - if (ep) { - *ep = ((uint8_t *) bd) + COMPRESSION_BLOCK_SIZE; - } + if (ep) *ep = (uint8_t *) bd + COMPRESSION_BLOCK_SIZE; return TF_OK; } -enum tf_err_t tf_read_var_header(const uint8_t *bd, int bs, struct tf_var_header_t *varHeader, uint8_t *vd, int vs, uint8_t **ep) { +TFError TFVarHeader_read(const uint8_t *const bd, + const int bs, + TFVarHeader *const varHeader, + uint8_t *const vd, + const int vs, + uint8_t **const ep) { const int VAR_HEADER_SIZE = 4; // variable header requires 4 bytes and is NULL terminated // an empty variable should be at least 5 bytes - if (bs <= VAR_HEADER_SIZE) { - return TF_EINVALID_VAR_HEADER_SIZE; - } else { - varHeader->size = ((uint16_t *) &bd[0])[0]; - - TINYFSEQ_MEMCPY(varHeader->id, &bd[2], sizeof(varHeader->id)); - - // only attempt to read variable value if a decoding buffer (`vd`) is provided - // `.size` already includes the 4 bytes the header consumes - if (vd) { - const int valueSize = varHeader->size - VAR_HEADER_SIZE; - - if (vs < valueSize) { - return TF_EINVALID_VAR_VALUE_SIZE; - } else { - TINYFSEQ_MEMCPY(vd, &bd[VAR_HEADER_SIZE], valueSize); - } - } - - if (ep) { - *ep = ((uint8_t *) bd) + varHeader->size; - } - - return TF_OK; + if (bs <= VAR_HEADER_SIZE) return TF_EINVALID_BUFFER_SIZE; + + varHeader->size = ((uint16_t *) &bd[0])[0]; + + __builtin_memcpy(varHeader->id, &bd[2], sizeof(varHeader->id)); + + // only attempt to read variable value if a decoding buffer (`vd`) is provided + // `.size` already includes the 4 bytes the header consumes + if (vd) { + const int valueSize = varHeader->size - VAR_HEADER_SIZE; + + if (vs < valueSize) return TF_EINVALID_BUFFER_SIZE; + + __builtin_memcpy(vd, &bd[VAR_HEADER_SIZE], valueSize); } + + if (ep) *ep = (uint8_t *) bd + varHeader->size; + + return TF_OK; } -static inline uint32_t tf_read_uint24(const uint8_t *bd) { - return (uint32_t) (bd[0] | (bd[1] << 8) | (bd[2] << 16)); +static uint32_t TFUint24_read(const uint8_t *const bd) { + return bd[0] | bd[1] << 8 | bd[2] << 16; } -enum tf_err_t tf_read_channel_range(const uint8_t *bd, int bs, struct tf_channel_range_t *channelRange, uint8_t **ep) { +TFError TFChannelRange_read(const uint8_t *const bd, + const int bs, + TFChannelRange *const channelRange, + uint8_t **const ep) { const int CHANNEL_RANGE_SIZE = 6; - if (bs < CHANNEL_RANGE_SIZE) { - return TF_EINVALID_CHANNEL_RANGE_SIZE; - } else { - channelRange->firstChannelNumber = tf_read_uint24(&bd[0]); - channelRange->channelCount = tf_read_uint24(&bd[3]); + if (bs < CHANNEL_RANGE_SIZE) return TF_EINVALID_BUFFER_SIZE; - if (ep) { - *ep = ((uint8_t *) bd) + CHANNEL_RANGE_SIZE; - } + channelRange->firstChannelNumber = TFUint24_read(&bd[0]); + channelRange->channelCount = TFUint24_read(&bd[3]); - return TF_OK; - } -} + if (ep) *ep = (uint8_t *) bd + CHANNEL_RANGE_SIZE; -float tf_sequence_duration_seconds(uint32_t frameCount, uint8_t frameStepTimeMillis) { - const unsigned int millis = frameCount * frameStepTimeMillis; - - return ((float) millis) / 1000.0F; + return TF_OK; } #endif//TINYFSEQ_IMPLEMENTATION