/* * Copyright (C) 2014 René Kijewski * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * @defgroup sys_ubjson Universal Binary JSON * @ingroup sys * @brief A library to read and write UBJSON serialized data. * @{ * * @file * @brief Headers for the UBJSON module * * @author René Kijewski */ #ifndef UBJSON_H #define UBJSON_H #include #include #include #include #if defined(MODULE_MSP430_COMMON) # include "msp430_types.h" #elif !defined(__linux__) # include #endif #ifdef __cplusplus extern "C" { #endif /* *************************************************************************** * READ FUNCTIONS / DEFINITIONS *************************************************************************** */ /** * @brief Status code of ubjson_read(), ubjson_read_array() and ubjson_read_object() callback * * When ubjson_read(), ubjson_read_array() and ubjson_read_object() iteratively invokes the gives callback function. * The callback function then has to invoke another function such as ubjson_get_i32(), depending on the parameter `type`. */ typedef enum { /** * @brief There is no such value. * * Only used in the callback of ubjson_read() for parameter `type2`. */ UBJSON_ABSENT, /** * @brief The next datum is a null value. * * As you might have already guessed: you cannot read a null value. */ UBJSON_TYPE_NULL, /** * @brief The next datum is a no-op value. * * As you might have already guessed: you cannot read a no-op value. */ UBJSON_TYPE_NOOP, /** * @brief The next datum is a boolean. * * The `content` is the boolean value. * Use ubjson_get_bool() or use `content` verbatim. */ UBJSON_TYPE_BOOL, /** * @brief The next datum is an integer that fits into an int32_t. * * Use ubjson_get_i32() to read the value. * `content` is one of ::ubjson_int32_type_t. */ UBJSON_TYPE_INT32, /** * @brief The next datum is an integer that fits into an int64_t. * * Use ubjson_get_i64() to read the value. */ UBJSON_TYPE_INT64, /** * @brief The next datum is a 32 bit floating point value. * * Use ubjson_get_float() to read the value. */ UBJSON_TYPE_FLOAT, /** * @brief The next datum is a 64 bit floating point value. * * Use ubjson_get_double() to read the value. */ UBJSON_TYPE_DOUBLE, /* NOTE: High-precision numbers are not implemented, yet. Implement if needed. */ /* UBJSON_TYPE_HP_NUMBER, */ /** * @brief The next datum is a string (blob). * * Use ubjson_get_string() to read the value. * `content` is the length of the blob. */ UBJSON_TYPE_STRING, /** * @brief The next datum is an array. * * Use ubjson_read_array() to read its contents. */ UBJSON_ENTER_ARRAY, /** * @brief The next datum is an object. * * Use ubjson_read_object() to read its contents. */ UBJSON_ENTER_OBJECT, /** * @brief The next datum is an array index. * * This value is emitted for every index in a call to ubjson_read_array(). * * `content1` is the array index. * `type2` and `content2` describe the value of the index. * * Arrays can be nested. */ UBJSON_INDEX, /** * @brief The next datum is an object key. * * This value is emitted for every index in a call to ubjson_read_object(). * * `content1` is the length of the key, invoke ubjson_get_string(). * `type2` and `content2` describe the value. * * Objects can be nested. */ UBJSON_KEY, } ubjson_type_t; /** * @brief Length of the UBJSON_TYPE_INT32 datum. */ typedef enum { UBJSON_INT32_INT8, /**< The stream contains an int8_t. */ UBJSON_INT32_UINT8, /**< The stream contains an uint8_t. */ UBJSON_INT32_INT16, /**< The stream contains an int16_t. */ UBJSON_INT32_INT32, /**< The stream contains an int32_t. */ } ubjson_int32_type_t; /** * @brief Return value of ::ubjson_read_callback_t and ubjson_read() * * The callback invoked by ubjson_read(), ubjson_read_array() or ubjson_read_object() can return an error value. * The error value is then returned by the read function. * * The values UBJSON_INVALID_DATA, UBJSON_PREMATURELY_ENDED, and UBJSON_SIZE_ERROR are returned on encoding errors, too. */ typedef enum { UBJSON_OKAY, /**< success / do continue */ UBJSON_ABORTED, /**< aborted / do abort */ UBJSON_INVALID_DATA, /**< invalid marker or type*/ UBJSON_PREMATURELY_ENDED, /**< the stream abruptly ended */ UBJSON_SIZE_ERROR, /**< the length of a field exceeded SSIZE_MAX */ } ubjson_read_callback_result_t; struct ubjson_cookie; /** * @brief A cookie passed between the read and write functions. * @details You probably want to wrap the cookie in some other data structure, * which you retrieve with container_of() in the callback. */ typedef struct ubjson_cookie ubjson_cookie_t; /** * @brief Method called by ubjson_read() to get more data. * @param[in] cookie The cookie that was passed to ubjson_read(). * @param[out] buf The buffer that should be written to. * @param[in] max_len The length of the buffer. Always `>= 1`. * @return @arg `< 0` on error. UBJSON_PREMATURELY_ENDED will be return by ubjson_read(). * @arg `> 0` the amount of read data, which must not exceed max_len. */ typedef ssize_t (*ubjson_read_t)(ubjson_cookie_t *__restrict cookie, void *buf, size_t max_len); /** * @brief Method called by ubjson_read() to denote the next element in the structure. * @details Depending on the value of type1 a different function, such as ubjson_get_i32(), * must be invoked by the callback function. * * With ubjson_read_array() or ubjson_read_object() the value of type1 is * UBJSON_INDEX or UBJSON_KEY, resp. * @param[in] cookie The cookie that was passed to ubjson_read(). * @param[in] type1 The type of the next datum. * @param[in] content1 The sub-type of the next datum. * @param[in] type2 The type of the value that belongs to the next key/index, or UBJSON_ABSENT. * @param[in] content2 The sub-type of the value that belongs to the next key/index. * @returns Either UBJSON_OKAY or UBJSON_ABORTED. */ typedef ubjson_read_callback_result_t (*ubjson_read_callback_t)(ubjson_cookie_t *__restrict cookie, ubjson_type_t type1, ssize_t content1, ubjson_type_t type2, ssize_t content2); /** * @brief Method called by ubjson_write_null() and friends. * @details The function in invoked multiple times per written value. * You should use some kind of buffer if you send the data over a stream. * * The function must write the whole buffer before returning. * @param[in] cookie The cookie that was passed to ubjson_write_init(). * @param[in] buf Data to write, never NULL. * @param[in] len Data to write, always >= 0. * @returns @arg `< 0` to indicate an error. * @arg `> 0` to indicate success. */ typedef ssize_t (*ubjson_write_t)(ubjson_cookie_t *__restrict cookie, const void *buf, size_t len); /** * @brief See @ref ubjson_cookie_t. */ struct ubjson_cookie { /** * @brief Read/write function * @internal */ union { ubjson_read_t read; /**< read function */ ubjson_write_t write; /**< write function */ } rw; /** * @brief Callback function * @internal */ union { ubjson_read_callback_t read; /**< Callback when a datum was read. */ } callback; /**< @internal */ /** * @brief One byte push-back buffer. * @internal */ char marker; }; /** * @brief Used to read with a setup cookie. * @details You need to use this function instead of ubjson_read() only if * your UBJSON data contains further UBJSON serialized data in a string. * * UBJSON data in a typed array may or may not work. * @param[in] cookie The cookie that is passed to the callback function. * @returns The same as ubjson_read(). */ ubjson_read_callback_result_t ubjson_read_next(ubjson_cookie_t *__restrict cookie); /** * @brief The entry function to read UBJSON serialized data. * @details This function invokes the callback function. * The value of type1 in the callback indicates which function * to call inside the callback function, e.g. ubjson_get_i32(). * * Nested calls to ubjson_read_array(), ubjson_read_object() or ubjson_read_next() * invoke the same callback function, possibly multiple times. * * You probably want to wrap the cookie in some other data structure, * which you retrieve with container_of() in the callback. * @param[in] cookie The cookie that is passed to the callback function. * @param[in] read The function that is called to receive more data. * @param[in] callback The callback function. * @returns See \ref ubjson_read_callback_result_t */ static inline ubjson_read_callback_result_t ubjson_read(ubjson_cookie_t *__restrict cookie, ubjson_read_t read, ubjson_read_callback_t callback) { cookie->rw.read = read; cookie->callback.read = callback; cookie->marker = 0; return ubjson_read_next(cookie); } /** * @brief Use in a callback if type1 is UBJSON_KEY or UBJSON_INDEX. * @details Call like ``ubjson_peek_value(cookie, &type2, &content2)``. * @param[in] cookie The cookie that was passed to the callback. * @param[in,out] type Pointer to a variable that was initialized with the value of type2, returns the new type1. * @param[in,out] content Pointer to a variable that was initialized with the value of content2, returns the new content1. * @returns The same as ubjson_read(). */ ubjson_read_callback_result_t ubjson_peek_value(ubjson_cookie_t *__restrict cookie, ubjson_type_t *type, ssize_t *content); /** * @brief Call if type1 of the callback was UBJSON_TYPE_INT32. * @details The value of content1 is one of ubjson_int32_type_t. * @param[in] cookie The cookie that was passed to the callback function. * @param[in] content The content1 that was passed to the callback function. * @param[out] dest The read datum. * @returns The result of the read callback, probably the amount of read bytes. */ ssize_t ubjson_get_i32(ubjson_cookie_t *__restrict cookie, ssize_t content, int32_t *dest); /** * @brief Call if type1 of the callback was UBJSON_TYPE_INT64. * @param[in] cookie The cookie that was passed to the callback function. * @param[in] content The content1 that was passed to the callback function. * @param[out] dest The read datum. * @returns The result of the read callback, probably the amount of read bytes. */ ssize_t ubjson_get_i64(ubjson_cookie_t *__restrict cookie, ssize_t content, int64_t *dest); /** * @brief Call if type1 of the callback was UBJSON_TYPE_STRING. * @details content1 is the length of the string/blob. * The result is not null-terminated! * @param[in] cookie The cookie that was passed to the callback function. * @param[in] content The content1 that was passed to the callback function. * @param[out] dest Buffer to read the string into. * @returns The result of the read callback, probably the amount of read bytes. */ ssize_t ubjson_get_string(ubjson_cookie_t *__restrict cookie, ssize_t content, void *dest); /** * @brief Call if type1 of the callback was UBJSON_TYPE_BOOL. * @details content1 is the value of the bool. The function only exists for symmetry. * @param[in] cookie The cookie that was passed to the callback function. * @param[in] content The content1 that was passed to the callback function. * @param[out] dest The read datum. * @returns `1`, the invocation cannot fail. */ static inline ssize_t ubjson_get_bool(ubjson_cookie_t *__restrict cookie, ssize_t content, bool *dest) { (void) cookie; *dest = content; return 1; } /** * @brief Call if type1 of the callback was UBJSON_TYPE_FLOAT. * @param[in] cookie The cookie that was passed to the callback function. * @param[in] content The content1 that was passed to the callback function. * @param[out] dest The read datum. * @returns The result of the read callback, probably the amount of read bytes. */ static inline ssize_t ubjson_get_float(ubjson_cookie_t *__restrict cookie, ssize_t content, float *dest) { (void) content; union { float f; int32_t i; } value; ubjson_read_callback_result_t result = ubjson_get_i32(cookie, UBJSON_INT32_INT32, &value.i); *dest = value.f; return result; } /** * @brief Call if type1 of the callback was UBJSON_TYPE_DOUBLE. * @param[in] cookie The cookie that was passed to the callback function. * @param[in] content The content1 that was passed to the callback function. * @param[out] dest The read datum. * @returns The result of the read callback, probably the amount of read bytes. */ static inline ssize_t ubjson_get_double(ubjson_cookie_t *__restrict cookie, ssize_t content, double *dest) { (void) content; union { double f; int64_t i; } value; ubjson_read_callback_result_t result = ubjson_get_i64(cookie, -1, &value.i); *dest = value.f; return result; } /** * @brief Call if type1 of the callback was UBJSON_ENTER_ARRAY. * @details Inside this call the callback function will be invoked multiple times, * once per array element, with type1=UBJSON_INDEX, * and content1=running index in the array. * * Use ubjson_peek_value() to determine the type of the element. * @param[in] cookie The cookie that was passed to the callback function. * @returns The same as ubjson_read(). */ ubjson_read_callback_result_t ubjson_read_array(ubjson_cookie_t *__restrict cookie); /** * @brief Call if type1 of the callback was UBJSON_ENTER_OBJECT. * @details Inside this call the callback function will be invoked multiple times, * once per object element, with type1=UBJSON_KEY, * and content1=length of the key string. * * First read the key with ubjson_get_string(), then * use ubjson_peek_value() to determine the type of the element. * @param[in] cookie The cookie that was passed to the callback function. * @returns The same as ubjson_read(). */ ubjson_read_callback_result_t ubjson_read_object(ubjson_cookie_t *__restrict cookie); /* *************************************************************************** * WRITE FUNCTIONS / DEFINITIONS *************************************************************************** */ /** * @brief The first call when you serialize data to UBJSON. * @details There is no corresponding "ubjson_write_finish" function. * The programmer needs to ensure that the API is used correctly. * The library won't complain if you write multiple values that are not * inside an array or object. The result will just not be properly serialized. * @param[out] cookie The cookie that will be passed to ubjson_write_null() and friends. * @param[in] write_fun The function that will be called to write data. */ static inline void ubjson_write_init(ubjson_cookie_t *__restrict cookie, ubjson_write_t write_fun) { cookie->rw.write = write_fun; } /** * @brief Write a null value. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). */ ssize_t ubjson_write_null(ubjson_cookie_t *__restrict cookie); /** * @brief Write a no-operation value. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_write_noop(ubjson_cookie_t *__restrict cookie); /** * @brief Write a boolean value. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @param[in] value The boolean value to write. * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_write_bool(ubjson_cookie_t *__restrict cookie, bool value); /** * @brief Write an integer value. * @details The library will determine the smallest serialization for the value itself. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @param[in] value The integer value to write. */ ssize_t ubjson_write_i32(ubjson_cookie_t *__restrict cookie, int32_t value); /** * @brief Write an integer value. * @details The library will determine the smallest serialization for the value itself. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @param[in] value The integer value to write. * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_write_i64(ubjson_cookie_t *__restrict cookie, int64_t value); /** * @brief Write a floating point value. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @param[in] value The integer value to write. * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_write_float(ubjson_cookie_t *__restrict cookie, float value); /** * @brief Write a floating point value. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @param[in] value The integer value to write. * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_write_double(ubjson_cookie_t *__restrict cookie, double value); /** * @brief Write a string or blob. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @param[in] value The string or blob to write. * @param[in] len The length of the string or blob. * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_write_string(ubjson_cookie_t *__restrict cookie, const void *value, size_t len); /** * @brief Open an array. * @details Write multiple elements inside this array. * Call ubjson_close_array() after the whole content was written. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_open_array(ubjson_cookie_t *__restrict cookie); /** * @brief Open an array with a known length. * @details Do not call ubjson_close_array(). * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @param[in] len Length of the array. * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_open_array_len(ubjson_cookie_t *__restrict cookie, size_t len); /** * @brief Close an array that was opened with ubjson_open_array(). * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_close_array(ubjson_cookie_t *__restrict cookie); /** * @brief Open an object. * @details Write multiple keys inside this object. * Call ubjson_close_object() after the whole content was written. * * For each element first write the key with ubjson_write_key(), * then invoke the function to write the value. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_open_object(ubjson_cookie_t *__restrict cookie); /** * @brief Open an object with a known length. * @details For each element first write the key with ubjson_write_key(), * then invoke the function to write the value. * * Do not call ubjson_close_object(). * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @param[in] len Number of keys inside the object. * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_open_object_len(ubjson_cookie_t *__restrict cookie, size_t len); /** * @brief Write a key inside an object. * @details For each element first write the key, * then invoke the function to write the value. * * It is up to the programmer to ensure that there are no duplicated keys. * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @param[in] value The key, should be a UTF-8 string. * @param[in] len The length of the key. * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_write_key(ubjson_cookie_t *__restrict cookie, const void *value, size_t len); /** * @brief Close an array that was opened with ubjson_open_object(). * @param[in] cookie The cookie that was initialized with ubjson_write_init(). * @returns The result of the supplied @ref ubjson_write_t function. */ ssize_t ubjson_close_object(ubjson_cookie_t *__restrict cookie); #ifdef __cplusplus } #endif #endif /* UBJSON_H */ /** @} */