From 0a5c787992901b0847af013554c99d12612a0463 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 30 Jun 2021 16:20:10 +1000 Subject: [PATCH 1/6] cborparser: Document `cbor_parser_init_reader`. Describe the input parameters for the function and how they are used as best we understand from on-paper analysis of the C code. --- src/cborparser.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/cborparser.c b/src/cborparser.c index 31c8d8bf..0c7f6407 100644 --- a/src/cborparser.c +++ b/src/cborparser.c @@ -345,6 +345,23 @@ CborError cbor_parser_init(const uint8_t *buffer, size_t size, uint32_t flags, C return preparse_value(it); } +/** + * Initializes the CBOR parser for parsing a document that is read by an + * abstract reader interface defined by \a ops. The iterator to the first + * element is returned in \a it. + * + * The \a parser structure needs to remain valid throughout the decoding + * process. It is not thread-safe to share one CborParser among multiple + * threads iterating at the same time, but the object can be copied so multiple + * threads can iterate. + * + * The \a ops structure defines functions that implement the read process from + * the buffer given, see \ref CborParserOperations for further details. + * + * The \a token is passed as the first argument to all + * \ref CborParserOperations methods, and may be used to pass additional + * context information to the reader implementation. + */ CborError cbor_parser_init_reader(const struct CborParserOperations *ops, CborParser *parser, CborValue *it, void *token) { memset(parser, 0, sizeof(*parser)); From 6820d654d5b5cbcc80df0281c25ecc8b7ddbaeed Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 30 Jun 2021 17:41:45 +1000 Subject: [PATCH 2/6] cbor: Document the reader interface. --- src/cbor.h | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/cbor.h b/src/cbor.h index d5570265..aae547c1 100644 --- a/src/cbor.h +++ b/src/cbor.h @@ -319,11 +319,80 @@ enum CborParserIteratorFlags }; struct CborValue; + +/** + * Defines an interface for abstract document readers. This structure is used + * in conjunction with \ref cbor_parser_init_reader to define how the various + * required operations are to be implemented. + */ struct CborParserOperations { + /** + * Determines whether \a len bytes may be read from the reader. This is + * called before \ref read_bytes and \ref transfer_bytes to ensure it is safe + * to read the requested number of bytes from the reader. + * + * \param token An opaque object passed to \ref cbor_parser_init_reader + * that may be used to pass context information between the + * \ref CborParserOperations methods. + * + * \param len The number of bytes sought. + * + * \retval true \a len bytes may be read from the reader. + * \retval false Insufficient data is available to be read at this time. + */ bool (*can_read_bytes)(void *token, size_t len); + + /** + * Reads \a len bytes from the reader starting at \a offset bytes from + * the current read position and copies them to \a dst. The read pointer + * is *NOT* modified by this operation. + * + * \param token An opaque object passed to \ref cbor_parser_init_reader + * that may be used to pass context information between the + * \ref CborParserOperations methods. + * + * \param dst The buffer the read bytes will be copied to. + * + * \param offset The starting position for the read relative to the + * current read position. + * + * \param len The number of bytes sought. + */ void *(*read_bytes)(void *token, void *dst, size_t offset, size_t len); + + /** + * Skips past \a len bytes from the reader without reading them. The read + * pointer is advanced in the process. + * + * \param token An opaque object passed to \ref cbor_parser_init_reader + * that may be used to pass context information between the + * \ref CborParserOperations methods. + * + * \param len The number of bytes skipped. + */ void (*advance_bytes)(void *token, size_t len); + + /** + * Overwrite the user-supplied pointer \a userptr with the address where the + * data indicated by \a offset is located, then advance the read pointer + * \a len bytes beyond that point. + * + * This routine is used for accessing strings embedded in CBOR documents + * (both text and binary strings). + * + * \param token An opaque object passed to \ref cbor_parser_init_reader + * that may be used to pass context information between the + * \ref CborParserOperations methods. + * + * \param userptr The pointer that will be updated to reference the location + * of the data in the buffer. + * + * \param offset The starting position for the read relative to the + * current read position. + * + * \param len The number of bytes sought. + */ CborError (*transfer_string)(void *token, const void **userptr, size_t offset, size_t len); }; From cacf527341fdebf40afe811d468b5bbd8a8854e4 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Sat, 3 Jul 2021 11:56:13 +1000 Subject: [PATCH 3/6] cborencoder: Document the write callback function. What is not known, is what the significance is of `CborEncoderAppendType`. It basically tells the writer the nature of the data being written, but the default implementation ignores this and just blindly appends it no matter what. That raises the question of why it's important enough that the writer function needs to know about it. --- src/cbor.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/cbor.h b/src/cbor.h index aae547c1..491932ab 100644 --- a/src/cbor.h +++ b/src/cbor.h @@ -214,7 +214,20 @@ typedef enum CborEncoderAppendType CborEncoderAppendRawData = 2 } CborEncoderAppendType; -typedef CborError (*CborEncoderWriteFunction)(void *, const void *, size_t, CborEncoderAppendType); +/** + * Writer interface call-back function. When there is data to be written to + * the CBOR document, this routine will be called. The \a token parameter is + * taken from the \a token argument provided to \ref cbor_encoder_init_writer + * and may be used in any way the writer function sees fit. + * + * The \a data parameter contains a pointer to the raw bytes to be copied to + * the output buffer, with \a len specifying how long the payload is, which + * can be as small as a single byte or an entire (byte or text) string. + * + * The \a append parameter informs the writer function whether it is writing + * a string or general CBOR data. + */ +typedef CborError (*CborEncoderWriteFunction)(void *token, const void *data, size_t len, CborEncoderAppendType append); enum CborEncoderFlags { From 134864402ce792330bf8d67acf526d0e36726db9 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 8 Sep 2021 08:22:53 +1000 Subject: [PATCH 4/6] cbor.h, cborparser.c: Migrate parser documentation. Not 100% sure of the syntax for documenting struct-members outside of the struct as I'm too used to doing it inline, hopefully this works as expected. :-) --- src/cbor.h | 69 +------------------------------------------ src/cborparser.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 68 deletions(-) diff --git a/src/cbor.h b/src/cbor.h index 491932ab..1ccf974f 100644 --- a/src/cbor.h +++ b/src/cbor.h @@ -333,79 +333,12 @@ enum CborParserIteratorFlags struct CborValue; -/** - * Defines an interface for abstract document readers. This structure is used - * in conjunction with \ref cbor_parser_init_reader to define how the various - * required operations are to be implemented. - */ + struct CborParserOperations { - /** - * Determines whether \a len bytes may be read from the reader. This is - * called before \ref read_bytes and \ref transfer_bytes to ensure it is safe - * to read the requested number of bytes from the reader. - * - * \param token An opaque object passed to \ref cbor_parser_init_reader - * that may be used to pass context information between the - * \ref CborParserOperations methods. - * - * \param len The number of bytes sought. - * - * \retval true \a len bytes may be read from the reader. - * \retval false Insufficient data is available to be read at this time. - */ bool (*can_read_bytes)(void *token, size_t len); - - /** - * Reads \a len bytes from the reader starting at \a offset bytes from - * the current read position and copies them to \a dst. The read pointer - * is *NOT* modified by this operation. - * - * \param token An opaque object passed to \ref cbor_parser_init_reader - * that may be used to pass context information between the - * \ref CborParserOperations methods. - * - * \param dst The buffer the read bytes will be copied to. - * - * \param offset The starting position for the read relative to the - * current read position. - * - * \param len The number of bytes sought. - */ void *(*read_bytes)(void *token, void *dst, size_t offset, size_t len); - - /** - * Skips past \a len bytes from the reader without reading them. The read - * pointer is advanced in the process. - * - * \param token An opaque object passed to \ref cbor_parser_init_reader - * that may be used to pass context information between the - * \ref CborParserOperations methods. - * - * \param len The number of bytes skipped. - */ void (*advance_bytes)(void *token, size_t len); - - /** - * Overwrite the user-supplied pointer \a userptr with the address where the - * data indicated by \a offset is located, then advance the read pointer - * \a len bytes beyond that point. - * - * This routine is used for accessing strings embedded in CBOR documents - * (both text and binary strings). - * - * \param token An opaque object passed to \ref cbor_parser_init_reader - * that may be used to pass context information between the - * \ref CborParserOperations methods. - * - * \param userptr The pointer that will be updated to reference the location - * of the data in the buffer. - * - * \param offset The starting position for the read relative to the - * current read position. - * - * \param len The number of bytes sought. - */ CborError (*transfer_string)(void *token, const void **userptr, size_t offset, size_t len); }; diff --git a/src/cborparser.c b/src/cborparser.c index 0c7f6407..a796bd0b 100644 --- a/src/cborparser.c +++ b/src/cborparser.c @@ -132,6 +132,83 @@ * \endif */ +/** + * \struct CborParserOperations + * + * Defines an interface for abstract document readers. This structure is used + * in conjunction with \ref cbor_parser_init_reader to define how the various + * required operations are to be implemented. + * + * + * \var CborParserOperations::can_read_bytes + * + * Determines whether \a len bytes may be read from the reader. This is + * called before \ref read_bytes and \ref transfer_bytes to ensure it is safe + * to read the requested number of bytes from the reader. + * + * \param token An opaque object passed to \ref cbor_parser_init_reader + * that may be used to pass context information between the + * \ref CborParserOperations methods. + * + * \param len The number of bytes sought. + * + * \retval true \a len bytes may be read from the reader. + * \retval false Insufficient data is available to be read at this time. + * + * + * \var CborParserOperations::read_bytes + * + * Reads \a len bytes from the reader starting at \a offset bytes from + * the current read position and copies them to \a dst. The read pointer + * is *NOT* modified by this operation. + * + * \param token An opaque object passed to \ref cbor_parser_init_reader + * that may be used to pass context information between the + * \ref CborParserOperations methods. + * + * \param dst The buffer the read bytes will be copied to. + * + * \param offset The starting position for the read relative to the + * current read position. + * + * \param len The number of bytes sought. + * + * + * \var CborParserOperations::advance_bytes + * + * Skips past \a len bytes from the reader without reading them. The read + * pointer is advanced in the process. + * + * \param token An opaque object passed to \ref cbor_parser_init_reader + * that may be used to pass context information between the + * \ref CborParserOperations methods. + * + * \param len The number of bytes skipped. + * + * + * \var CborParserOperations::transfer_string + * + * Overwrite the user-supplied pointer \a userptr with the address where the + * data indicated by \a offset is located, then advance the read pointer + * \a len bytes beyond that point. + * + * This routine is used for accessing strings embedded in CBOR documents + * (both text and binary strings). + * + * \param token An opaque object passed to \ref cbor_parser_init_reader + * that may be used to pass context information between the + * \ref CborParserOperations methods. + * + * \param userptr The pointer that will be updated to reference the location + * of the data in the buffer. + * + * \param offset The starting position for the read relative to the + * current read position. + * + * \param len The number of bytes sought. + */ + + static uint64_t extract_number_and_advance(CborValue *it) { /* This function is only called after we've verified that the number From a02319b0e01b0b7fce725c5cb644b3297ab260fc Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 8 Sep 2021 08:11:20 +1000 Subject: [PATCH 5/6] cbor.h, cborencoder.c: Migrate documentation for encoder functions --- src/cbor.h | 13 ------------- src/cborencoder.c | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/cbor.h b/src/cbor.h index 1ccf974f..6d34847a 100644 --- a/src/cbor.h +++ b/src/cbor.h @@ -214,19 +214,6 @@ typedef enum CborEncoderAppendType CborEncoderAppendRawData = 2 } CborEncoderAppendType; -/** - * Writer interface call-back function. When there is data to be written to - * the CBOR document, this routine will be called. The \a token parameter is - * taken from the \a token argument provided to \ref cbor_encoder_init_writer - * and may be used in any way the writer function sees fit. - * - * The \a data parameter contains a pointer to the raw bytes to be copied to - * the output buffer, with \a len specifying how long the payload is, which - * can be as small as a single byte or an entire (byte or text) string. - * - * The \a append parameter informs the writer function whether it is writing - * a string or general CBOR data. - */ typedef CborError (*CborEncoderWriteFunction)(void *token, const void *data, size_t len, CborEncoderAppendType append); enum CborEncoderFlags diff --git a/src/cborencoder.c b/src/cborencoder.c index b21c1da8..0f4bf03e 100644 --- a/src/cborencoder.c +++ b/src/cborencoder.c @@ -187,6 +187,23 @@ * Structure used to encode to CBOR. */ +/** + * \file cbor.h + * \typedef CborEncoderWriteFunction + * + * Writer interface call-back function. When there is data to be written to + * the CBOR document, this routine will be called. The \a token parameter is + * taken from the \a token argument provided to \ref cbor_encoder_init_writer + * and may be used in any way the writer function sees fit. + * + * The \a data parameter contains a pointer to the raw bytes to be copied to + * the output buffer, with \a len specifying how long the payload is, which + * can be as small as a single byte or an entire (byte or text) string. + * + * The \a append parameter informs the writer function whether it is writing + * a string or general CBOR data. + */ + /** * Initializes a CborEncoder structure \a encoder by pointing it to buffer \a * buffer of size \a size. The \a flags field is currently unused and must be From 34042b9ba98b3b601fe5badd7d4396be222224e8 Mon Sep 17 00:00:00 2001 From: Stuart Longland Date: Wed, 7 Jul 2021 13:35:01 +1000 Subject: [PATCH 6/6] examples: Add buffered writer example. --- examples/bufferedwriter.c | 735 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 735 insertions(+) create mode 100644 examples/bufferedwriter.c diff --git a/examples/bufferedwriter.c b/examples/bufferedwriter.c new file mode 100644 index 00000000..307b0d3f --- /dev/null +++ b/examples/bufferedwriter.c @@ -0,0 +1,735 @@ +/* vim: set sw=4 ts=4 et tw=80: */ + +/**************************************************************************** +** +** Copyright © 2021 VRT Systems Pty Ltd. +** +** 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. +** +****************************************************************************/ + +/** + * \brief An example of a buffered CBOR file writer using low-level + * POSIX file I/O as might be implemented in a microcontroller + * RTOS. + * + * \author Stuart Longland + * + * \copyright VRT Systems Pty Ltd + * + * \file bufferedwriter.c + */ + +/* Includes for POSIX low-level file I/O */ +#include +#include +#include + +/* Pull in "standard" integer types */ +#include + +/* Pull in definitions for printf and errno */ +#include +#include +#include + +/* For example usage */ +#include + +#include "../src/cbor.h" + +/** + * File writer buffer size. This should be tuned to balance memory usage and + * performance. Most interfaces, bigger writes are more efficient, but on a + * small MCU, memory may be tight. + * + * We're using `uint8_t` to represent our buffer position, so this must be + * strictly less than 256 bytes unless you change it in \ref filewriter (and + * in \ref filewriter_writer_impl) below. + */ +#define FILEWRITER_BUFFER_SZ (64) + +/** + * Context for the file writer. This stores the file descriptor, the write + * buffer, and a counter indicating our position within it. + */ +struct filewriter +{ + /** + * Write buffer. `tinycbor` writes will be initially buffered here, and + * the buffer will automatically be flushed: + * - when the buffer position counter reaches \ref FILEWRITER_BUFFER_SZ + * - when the file is closed with \ref filewriter_close + */ + uint8_t buffer[FILEWRITER_BUFFER_SZ]; + + /** + * File descriptor, returned by the `open` system call. + */ + int fd; + + /** + * Position within the buffer. When less than \ref FILEWRITER_BUFFER_SZ + * this indicates the position for new data. If new data arrives and + * this value is equal to \ref FILEWRITER_BUFFER_SZ, the buffer will be + * flushed first before writing. + */ + uint8_t pos; +}; + +/* Forward declaration, we'll cover this later */ +static CborError filewriter_writer_impl( + void* token, const void* data, size_t len, CborEncoderAppendType append +); + +/** + * Open a CBOR file for writing. + * + * \param[inout] encoder CBOR encoder object to initialise. + * + * \param[inout] context The file writer context. This must exist + * for the duration the file is open. + * + * \param[in] path The path to the file being written. + * + * \param[in] flags `open` flags. `O_WRONLY` is logic-ORed + * with this value, but the user may provide + * other options here. + * + * \param[in] mode Mode bits to set on the created file. + * + * \retval CborErrorIO The `open` call failed for some reason, + * see the POSIX standard `errno` variable + * for why. + * + * \retval CborNoError CBOR encoder initialised successfully. + */ +CborError filewriter_open( + CborEncoder * const encoder, + struct filewriter * const context, + const char* path, + int flags, + mode_t mode +) +{ + CborError error = CborNoError; + + context->fd = open(path, O_WRONLY | flags, mode); + if (context->fd < 0) { + /* Open fails */ + error = CborErrorIO; + } else { + /* Initialise structure */ + context->pos = 0; + + /* Initialise the CBOR encoder */ + cbor_encoder_init_writer(encoder, filewriter_writer_impl, context); + } + + return error; +} + +/** + * Explicitly flush the content of the buffer. This is called automatically + * when the file is closed or when we run out of buffer space. Is a no-op if + * there is nothing to flush. + * + * \param[inout] context File writer context to flush. + * + * \retval CborErrorIO The `write` call failed for some reason, + * see the POSIX standard `errno` variable + * for why. + * + * \retval CborNoError Buffer flushed, position reset. + */ +CborError filewriter_flush(struct filewriter * const context) +{ + CborError error = CborNoError; + + if (context->pos > 0) { + if (write(context->fd, context->buffer, context->pos) < context->pos) { + error = CborErrorIO; + } else { + /* Success */ + context->pos = 0; + } + } + + return error; +} + +/** + * Close the file writer, flushing any remaining data and finishing the write. + * It is assumed that relevant CBOR containers have been closed first. + * + * \param[inout] context File writer context to flush. + * + * \retval CborErrorIO The `write` or `close` call failed for + * some reason, see the POSIX standard + * `errno` variable for why. (CBOR file + * should be considered invalid in this + * case.) *The file may still be open!* + * + * \retval CborNoError File closed, `fd` should be set to -1. + */ +CborError filewriter_close(struct filewriter * const context) +{ + CborError error = filewriter_flush(context); + + if (error == CborNoError) { + int res = close(context->fd); + if (res < 0) { + /* Close failed */ + error = CborErrorIO; + } else { + /* Mark context as closed */ + context->fd = -1; + } + } + + return error; +} + +/** + * CBOR Writer implementation. This function implements the necessary + * interface expected by `tinycbor` to perform synchronous writes to a + * file arbitrarily. Flushing is automatically handled. + */ +static CborError filewriter_writer_impl( + void* token, const void* data, size_t len, CborEncoderAppendType append +) +{ + struct filewriter* context = (struct filewriter*)token; + const uint8_t* rptr = (const uint8_t*)data; + CborError error = CborNoError; + + (void)append; /* We don't use the `append` argument */ + + while ((len > 0) && (error == CborNoError)) { + /* How much space is left? */ + uint8_t rem = FILEWRITER_BUFFER_SZ - context->pos; + + /* Is there any space? */ + if (rem > 0) { + /* Where is our write pointer at? */ + uint8_t* wptr = &(context->buffer[context->pos]); + + /* How much can we write? */ + uint8_t sz = rem; + if (sz > len) { + /* Clamp to amount of data available */ + sz = len; + } + + /* Copy that into the buffer */ + memcpy(wptr, rptr, sz); + context->pos += sz; + rptr += sz; + len -= sz; + rem -= sz; + } + + /* Are we full yet? */ + if (rem == 0) { + error = filewriter_flush(context); + } + } + + return error; +} + +/* --- Example usage of the above writer --- */ + +/** + * Print the error encountered. If the error is `CborErrorIO`, also check + * the global `errno` variable and print the resultant error seen. + * + * \param[in] error CBORError constant + */ +void print_err(CborError error) +{ + if (error == CborErrorIO) { + printf("IO: %s\n", strerror(errno)); + } else { + printf("%s\n", cbor_error_string(error)); + } +} + +/* Forward declarations */ +int exec_arg_array(CborEncoder * const encoder, size_t len, int argc, char **argv); +int exec_arg_map(CborEncoder * const encoder, size_t len, int argc, char **argv); + +/** + * Interpret the arguments given and execute one of the `tinycbor` routines. + * + * \param[inout] encoder CBOREncoder instance + * \param[in] argc Number of command line arguments remaining + * \param[in] argv Command line arguments' values + * + * \retval ≤0 CBOR error occurred, stop here. + * \retval >0 Number of arguments consumed. + */ +int exec_arg(CborEncoder * const encoder, int argc, char **argv) +{ + if (argc > 1) { + CborError error; + int consumed; + int len = strlen(argv[0]); + + printf("Command: %s (%d bytes)\n", argv[0], len); + if (len == 1) { + /* Single-character commands */ + switch (argv[0][0]) { + case '{': /* Begin unknown-length map */ + consumed = exec_arg_map( + encoder, CborIndefiniteLength, + argc - 1, argv + 1 + ); + if (consumed > 0) { + consumed++; + } + return consumed; + case '[': /* Begin unknown-length array */ + consumed = exec_arg_array( + encoder, CborIndefiniteLength, + argc - 1, argv + 1 + ); + if (consumed > 0) { + consumed++; + } + return consumed; + + case 'N': /* Null */ + case 'n': + error = cbor_encode_null(encoder); + if (error != CborNoError) { + printf("Failed at null: "); + print_err(error); + return -1; + } + return 1; + case 'U': /* Undefined */ + case 'u': + error = cbor_encode_undefined(encoder); + if (error != CborNoError) { + printf("Failed at undefined: "); + print_err(error); + return -1; + } + return 1; + case 'F': /* False */ + case 'f': + error = cbor_encode_boolean(encoder, false); + if (error != CborNoError) { + printf("Failed at false: "); + print_err(error); + return -1; + } + return 1; + case 'T': /* True */ + case 't': + error = cbor_encode_boolean(encoder, true); + if (error != CborNoError) { + printf("Failed at true: "); + print_err(error); + return -1; + } + return 1; + default: + printf("Unknown single-character command: %s", argv[0]); + return -1; + } + } else if (strncmp(argv[0], "map(", 4) == 0) { + /* Fixed-size map */ + char *endptr = NULL; + unsigned long maplen = strtoul(&(argv[0][4]), &endptr, 0); + + if (!endptr || (*endptr != ')')) { + /* Not a valid length */ + printf("Invalid length for map: %s\n", argv[0]); + return -1; + } else { + consumed = exec_arg_map(encoder, maplen, argc - 1, argv + 1); + if (consumed > 0) { + consumed++; + } + return consumed; + } + } else if (strncmp(argv[0], "array(", 6) == 0) { + /* Fixed-size array */ + char *endptr = NULL; + unsigned long arraylen = strtoul(&(argv[0][4]), &endptr, 0); + + if (!endptr || (*endptr != ')')) { + /* Not a valid length */ + printf("Invalid length for array: %s\n", argv[0]); + return -1; + } else { + consumed = exec_arg_array( + encoder, arraylen, argc - 1, argv + 1 + ); + if (consumed > 0) { + consumed++; + } + return consumed; + } + } else if (argv[0][0] == 's') { + /* Text string */ + error = cbor_encode_text_string(encoder, &(argv[0][1]), len - 1); + if (error != CborNoError) { + printf( + "Failed at text string (%s): ", + argv[0] + ); + print_err(error); + return -1; + } else { + return 1; + } + } else if (argv[0][0] == 'x') { + /* Byte string, total length must be odd with 'x' prefix */ + if ((len % 2) == 0) { + printf("Byte string must be an even number of hex digits.\n"); + return -1; + } + + uint8_t bytes[len / 2]; + int i; + for (i = 1; i < len; i++) { + char nybble = argv[0][i]; + if ((nybble >= '0') && (nybble <= '9')) { + nybble -= '0'; + } else if ((nybble >= 'A') && (nybble <= 'F')) { + nybble -= 'A'; + nybble += 10; + } else if ((nybble >= 'a') && (nybble <= 'f')) { + nybble -= 'a'; + nybble += 10; + } else { + printf("Unsupported character '%c' in byte string at %d\n", + nybble, i); + return -1; + } + + if ((i % 2) == 0) { + /* Even numbered: lower nybble */ + bytes[(i - 1)/2] |= nybble; + } else { + /* Odd numbered: upper nybble */ + bytes[i/2] = nybble << 4; + } + } + + error = cbor_encode_byte_string(encoder, bytes, len / 2); + if (error != CborNoError) { + printf( + "Failed at byte string (%s): ", + argv[0] + ); + print_err(error); + return -1; + } else { + return 1; + } + } else if (argv[0][0] == 'd') { + /* 32-bit float number */ + char *endptr = NULL; + double d = strtod(&(argv[0][1]), &endptr); + + if (!endptr || (*endptr)) { + /* Invalid number */ + printf("Invalid double %s\n", argv[0]); + return -1; + } + + error = cbor_encode_double(encoder, d); + if (error != CborNoError) { + printf( + "Failed at double (%s): ", + argv[0] + ); + print_err(error); + return -1; + } else { + return 1; + } + } else if (argv[0][0] == 'f') { + /* 32-bit float number */ + char *endptr = NULL; + float f = strtof(&(argv[0][1]), &endptr); + + if (!endptr || (*endptr)) { + /* Invalid number */ + printf("Invalid float %s\n", argv[0]); + return -1; + } + + error = cbor_encode_float(encoder, f); + if (error != CborNoError) { + printf( + "Failed at float (%s): ", + argv[0] + ); + print_err(error); + return -1; + } else { + return 1; + } + } else if (argv[0][0] == 'u') { + /* Unsigned integer (positive) number */ + char *endptr = NULL; + unsigned long long ull = strtoull(&(argv[0][1]), &endptr, 0); + + if (!endptr || (*endptr)) { + /* Invalid number */ + printf("Invalid unsigned integer %s\n", argv[0]); + return -1; + } + + error = cbor_encode_uint(encoder, ull); + if (error != CborNoError) { + printf( + "Failed at unsigned integer (%s): ", + argv[0] + ); + print_err(error); + return -1; + } else { + return 1; + } + } else if (argv[0][0] == '-') { + /* Unsigned integer (negative) number */ + char *endptr = NULL; + unsigned long long ull = strtoull(&(argv[0][1]), &endptr, 0); + + if (!endptr || (*endptr)) { + /* Invalid number */ + printf("Invalid negative unsigned integer %s\n", argv[0]); + return -1; + } + + error = cbor_encode_negative_int(encoder, ull); + if (error != CborNoError) { + printf( + "Failed at negative unsigned integer (%s): ", + argv[0] + ); + print_err(error); + return -1; + } else { + return 1; + } + } else { + printf("Unknown command: %s", argv[0]); + return -1; + } + } else { + /* No arguments to consume. */ + printf("End of arguments.\n"); + return 0; + } +} + +/** + * Interpret the arguments given and execute one of the `tinycbor` routines, + * in an array context. + * + * \param[inout] encoder CBOREncoder instance + * \param[in] len Length of the array. + * \param[in] argc Number of command line arguments remaining + * \param[in] argv Command line arguments' values + * + * \retval ≤0 CBOR error occurred, stop here. + * \retval >0 Number of arguments consumed. + */ +int exec_arg_array(CborEncoder * const encoder, size_t len, int argc, char **argv) { + int consumed = 0; + CborEncoder container; + CborError error; + + error = cbor_encoder_create_array(encoder, &container, len); + if (error != CborNoError) { + printf("Failed to create array (length=%lu): ", len); + print_err(error); + consumed = -1; + } else { + while ((consumed >= 0) && (argc > 0) && (argv[0][0] != ']')) { + int arg_consumed = exec_arg(&container, argc, argv); + + if (arg_consumed > 0) { + consumed += arg_consumed; + argc -= arg_consumed; + argv += arg_consumed; + } else { + /* Error condition */ + printf( + "Failed inside array context (after %d arguments).\n", + consumed + ); + consumed = -1; + } + } + + if (consumed >= 0) { + printf("Close array after %d arguments\n", consumed); + + /* Count end-of-array */ + consumed++; + + error = cbor_encoder_close_container(encoder, &container); + if (error != CborNoError) { + printf("Failed to finish array (length=%lu): ", len); + print_err(error); + consumed = -1; + } + } + } + + return consumed; +} + +/** + * Interpret the arguments given and execute one of the `tinycbor` routines, + * in a map context. + * + * \param[inout] encoder CBOREncoder instance + * \param[in] len Length of the map. + * \param[in] argc Number of command line arguments remaining + * \param[in] argv Command line arguments' values + * + * \retval ≤0 CBOR error occurred, stop here. + * \retval >0 Number of arguments consumed. + */ +int exec_arg_map(CborEncoder * const encoder, size_t len, int argc, char **argv) { + int consumed = 0; + CborEncoder container; + CborError error; + + error = cbor_encoder_create_map(encoder, &container, len); + if (error != CborNoError) { + printf("Failed to create map (length=%lu): ", len); + print_err(error); + consumed = -1; + } else { + while ((consumed >= 0) && (argc > 0) && (argv[0][0] != '}')) { + int arg_consumed = exec_arg(&container, argc, argv); + + if (arg_consumed > 0) { + consumed += arg_consumed; + argc -= arg_consumed; + argv += arg_consumed; + } else { + /* Error condition */ + printf( + "Failed inside map context (after %d arguments).\n", + consumed + ); + consumed = -1; + } + } + + if (consumed >= 0) { + printf("Close map after %d arguments\n", consumed); + + /* Count end-of-map */ + consumed++; + + error = cbor_encoder_close_container(encoder, &container); + if (error != CborNoError) { + printf("Failed to finish map (length=%lu): ", len); + print_err(error); + consumed = -1; + } + } + } + + return consumed; +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + printf( + "Usage: %s ...\n" + "Valid commands:\n" + "\t{\tStart an unknown-length map\n" + "\t[\tStart an unknown-length array\n" + "\tmap() {\tStart a map of length \n" + "\tarray() [\tStart an array of length \n" + "\ts\tInsert a text string\n" + "\tx\tInsert a byte string\n" + "\tu\tInsert an unsigned positive integer\n" + "\t-\tInsert an unsigned negative integer\n" + "\td\tInsert a 64-bit float\n" + "\tf\tInsert a 32-bit float\n" + "\tf, t\tInsert FALSE or TRUE (case insensitive)\n" + "\tn, u\tInsert NULL or UNDEFINED (case insensitive)\n" + "\nInside maps:\n" + "\t}\tEnd the current map\n" + "\nInside arrays:\n" + "\t]\tEnd the current array\n", + argv[0] + ); + return 1; + } else { + struct filewriter context; + CborEncoder encoder; + CborError error; + + /* Open the file for writing, create if needed */ + error = filewriter_open( + &encoder, /* CBOR context */ + &context, /* Writer context */ + argv[1], /* File name */ + O_CREAT, /* Open flags */ + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH /* File permissions */ + ); + + if (error != CborNoError) { + printf("Failed to open %s for writing: ", argv[1]); + print_err(error); + } else { + argv += 2; + argc -= 2; + + while (argc > 0) { + int consumed = exec_arg(&encoder, argc, argv); + + if (consumed > 0) { + argc -= consumed; + argv += consumed; + } else { + break; + } + } + + error = filewriter_close(&context); + if (error != CborNoError) { + printf("Failed to close file: "); + print_err(error); + } + } + + if (error != CborNoError) { + return 2; + } else { + return 0; + } + } +}