diff --git a/sys/include/ubjson.h b/sys/include/ubjson.h new file mode 100644 index 0000000000..f0b2f7b7cc --- /dev/null +++ b/sys/include/ubjson.h @@ -0,0 +1,585 @@ +/* + * 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(CPU_NATIVE) +# 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 /* ifndef UBJSON_H__ */ +/** @} */ diff --git a/sys/ubjson/Makefile b/sys/ubjson/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/ubjson/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/ubjson/ubjson-internal.h b/sys/ubjson/ubjson-internal.h new file mode 100644 index 0000000000..641bd9bf64 --- /dev/null +++ b/sys/ubjson/ubjson-internal.h @@ -0,0 +1,62 @@ +/* + * 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 + */ + +#ifndef UBJSON__INTERNAL_H__ +#define UBJSON__INTERNAL_H__ + +/* compare http://ubjson.org/type-reference/ */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + UBJSON_MARKER_NULL = 'Z', + UBJSON_MARKER_NOOP = 'N', + + UBJSON_MARKER_TRUE = 'T', + UBJSON_MARKER_FALSE = 'F', + + UBJSON_MARKER_INT8 = 'i', + UBJSON_MARKER_UINT8 = 'U', + UBJSON_MARKER_INT16 = 'I', + UBJSON_MARKER_INT32 = 'l', + UBJSON_MARKER_INT64 = 'L', + + UBJSON_MARKER_FLOAT32 = 'd', + UBJSON_MARKER_FLOAT64 = 'D', + UBJSON_MARKER_HP_NUMBER = 'H', + + UBJSON_MARKER_CHAR = 'C', + UBJSON_MARKER_STRING = 'S', + + UBJSON_MARKER_ARRAY_START = '[', + UBJSON_MARKER_ARRAY_END = ']', + + UBJSON_MARKER_OBJECT_START = '{', + UBJSON_MARKER_OBJECT_END = '}', + + UBJSON_MARKER_COUNT = '#', + UBJSON_MARKER_TYPE = '$', +} ubjson_marker_t; + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef UBJSON__INTERNAL_H__ */ diff --git a/sys/ubjson/ubjson-read.c b/sys/ubjson/ubjson-read.c new file mode 100644 index 0000000000..d2886526a4 --- /dev/null +++ b/sys/ubjson/ubjson-read.c @@ -0,0 +1,388 @@ +/* + * 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 + */ + +/** + * @ingroup sys + * @{ + * @file + * @brief Universal Binary JSON deserializer + * @author René Kijewski + * @} + */ + +#include "ubjson-internal.h" +#include "ubjson.h" +#include "byteorder.h" + +#define READ_MARKER() \ + do { \ + result = _ubjson_read_marker(cookie, &marker); \ + if (result != UBJSON_OKAY) { \ + return result; \ + } \ + } while (0) + +static ubjson_read_callback_result_t _ubjson_read_marker(ubjson_cookie_t *restrict cookie, + char *marker) +{ + while (1) { + ssize_t bytes_read; + + *marker = cookie->marker; + if (!*marker) { + bytes_read = cookie->rw.read(cookie, marker, 1); + } + else { + cookie->marker = 0; + bytes_read = 1; + } + + if (bytes_read == 1) { + return UBJSON_OKAY; + } + else if (bytes_read != 0) { + return UBJSON_PREMATURELY_ENDED; + } + } +} + +ssize_t ubjson_get_string(ubjson_cookie_t *restrict cookie, ssize_t length, void *dest_) +{ + ssize_t total = 0; + char *dest = dest_; + while (total < length) { + ssize_t read = cookie->rw.read(cookie, dest, length - total); + if (read < 0) { + return read; + } + total += read; + dest += read; + } + return total; +} + +ssize_t ubjson_get_i32(ubjson_cookie_t *restrict cookie, ssize_t content, int32_t *dest) +{ + static const int8_t LENGHTS[] = { 1, 1, 2, 4 }; + + union { + int8_t i8; + uint8_t u8; + network_uint16_t i16; + network_uint32_t i32; + } value; + + int type = content; + + ssize_t result = ubjson_get_string(cookie, LENGHTS[type], &value); + if (result > 0) { + switch (type) { + case UBJSON_INT32_INT8: + *dest = value.i8; + break; + case UBJSON_INT32_UINT8: + *dest = value.u8; + break; + case UBJSON_INT32_INT16: + *dest = (int16_t) byteorder_ntohs(value.i16); + break; + case UBJSON_INT32_INT32: + *dest = (int32_t) byteorder_ntohl(value.i32); + break; + } + } + return result; +} + +ssize_t ubjson_get_i64(ubjson_cookie_t *restrict cookie, ssize_t content, int64_t *dest) +{ + (void) content; + + network_uint64_t buf; + ssize_t result = ubjson_get_string(cookie, 8, &buf); + *dest = byteorder_ntohll(buf); + return result; +} + +static ubjson_read_callback_result_t _ubjson_read_length(ubjson_cookie_t *restrict cookie, + ssize_t *len) +{ + ubjson_type_t type; + ssize_t content = 0; + ubjson_read_callback_result_t result = ubjson_peek_value(cookie, &type, &content); + if (result != UBJSON_OKAY) { + return result; + } + + int64_t len64; + ssize_t read; + if (type == UBJSON_TYPE_INT32) { + int32_t len32; + read = ubjson_get_i32(cookie, content, &len32); + len64 = len32; + } + else if (type == UBJSON_TYPE_INT64) { + read = ubjson_get_i64(cookie, content, &len64); + } + else { + return UBJSON_INVALID_DATA; + } + + if (read <= 0) { + return UBJSON_PREMATURELY_ENDED; + } + else if ((ssize_t) len64 < 0) { + return UBJSON_SIZE_ERROR; + } + else if (len64 < 0) { + return UBJSON_INVALID_DATA; + } + + *len = len64; + return UBJSON_OKAY; +} + +static ubjson_read_callback_result_t _ubjson_get_call(ubjson_cookie_t *restrict cookie, + char marker, + ubjson_type_t *type, ssize_t *content) +{ + *content = -1; + + switch (marker) { + case UBJSON_MARKER_NULL: + *type = UBJSON_TYPE_NULL; + break; + + case UBJSON_MARKER_NOOP: + *type = UBJSON_TYPE_NOOP; + break; + + case UBJSON_MARKER_TRUE: + case UBJSON_MARKER_FALSE: + *type = UBJSON_TYPE_BOOL, + *content = (marker == UBJSON_MARKER_TRUE); + break; + + case UBJSON_MARKER_INT8: + *content = UBJSON_INT32_INT8; + *type = UBJSON_TYPE_INT32; + break; + + case UBJSON_MARKER_UINT8: + *content = UBJSON_INT32_UINT8; + *type = UBJSON_TYPE_INT32; + break; + + case UBJSON_MARKER_INT16: + *content = UBJSON_INT32_INT16; + *type = UBJSON_TYPE_INT32; + break; + + case UBJSON_MARKER_INT32: + *content = UBJSON_INT32_INT32; + *type = UBJSON_TYPE_INT32; + break; + + case UBJSON_MARKER_INT64: + *type = UBJSON_TYPE_INT64; + break; + + case UBJSON_MARKER_CHAR: + *type = UBJSON_TYPE_STRING; + break; + + case UBJSON_MARKER_FLOAT32: + *type = UBJSON_TYPE_FLOAT; + break; + + case UBJSON_MARKER_FLOAT64: + *type = UBJSON_TYPE_DOUBLE; + break; + + case UBJSON_MARKER_STRING: { + ubjson_read_callback_result_t result = _ubjson_read_length(cookie, content); + if (result != UBJSON_OKAY) { + return result; + } + *type = UBJSON_TYPE_STRING; + break; + } + + case UBJSON_MARKER_ARRAY_START: + *type = UBJSON_ENTER_ARRAY; + break; + + case UBJSON_MARKER_OBJECT_START: + *type = UBJSON_ENTER_OBJECT; + break; + + /* NOTE: High-precision numbers are not implemented, yet. Implement if needed. + * + * case UBJSON_MARKER_HP_NUMBER: + * ... + * break; + */ + + case UBJSON_MARKER_ARRAY_END: + case UBJSON_MARKER_OBJECT_END: + case UBJSON_MARKER_COUNT: + case UBJSON_MARKER_TYPE: + default: + return UBJSON_INVALID_DATA; + } + + return UBJSON_OKAY; +} + +typedef bool (*_ubjson_read_struct_continue)(ubjson_cookie_t *restrict cookie, char marker, + ubjson_read_callback_result_t *result, + ssize_t count, ssize_t index, + ubjson_type_t *type1, ssize_t *content1); + +static ubjson_read_callback_result_t _ubjson_read_struct(ubjson_cookie_t *restrict cookie, + _ubjson_read_struct_continue get_continue) +{ + ubjson_read_callback_result_t result; + ssize_t count = -1; + char marker, type_marker = 0; + + READ_MARKER(); + + if (marker == UBJSON_MARKER_TYPE) { + READ_MARKER(); + if (marker == 0) { + return UBJSON_INVALID_DATA; + } + type_marker = marker; + READ_MARKER(); + } + + if (marker == UBJSON_MARKER_COUNT) { + result = _ubjson_read_length(cookie, &count); + if (result != UBJSON_OKAY) { + return result; + } + READ_MARKER(); + } + + cookie->marker = marker; + + if ((type_marker != 0) && (count < 0)) { + /* If a type is specified, a count must be specified as well. + * Otherwise a ']' could either be data (e.g. the character ']'), + * or be meant to close the array. + */ + return UBJSON_INVALID_DATA; + } + + for (ssize_t index = 0; (count < 0) || (index < count); ++index) { + ubjson_type_t type1; + ssize_t content1; + + READ_MARKER(); + if (!get_continue(cookie, marker, &result, count, index, &type1, &content1) + || (result != UBJSON_OKAY)) { + break; + } + + result = cookie->callback.read(cookie, type1, content1, + UBJSON_ABSENT, (unsigned char) type_marker); + if (result != UBJSON_OKAY) { + break; + } + } + + return result; +} + +static bool _ubjson_read_array_continue(ubjson_cookie_t *restrict cookie, char marker, + ubjson_read_callback_result_t *result, + ssize_t count, ssize_t index, + ubjson_type_t *type1, ssize_t *content1) +{ + if (marker == UBJSON_MARKER_ARRAY_END) { + if (count >= 0) { + *result = UBJSON_INVALID_DATA; + } + return false; + } + + cookie->marker = marker; + + *type1 = UBJSON_INDEX; + *content1 = index; + return true; +} + +static bool _ubjson_read_object_continue(ubjson_cookie_t *restrict cookie, char marker, + ubjson_read_callback_result_t *result, + ssize_t count, ssize_t index, + ubjson_type_t *type1, ssize_t *content1) +{ + (void) index; + + if (marker == UBJSON_MARKER_OBJECT_END) { + if (count >= 0) { + *result = UBJSON_INVALID_DATA; + } + return false; + } + + cookie->marker = marker; + + *type1 = UBJSON_KEY; + *result = _ubjson_read_length(cookie, content1); + return true; +} + +ubjson_read_callback_result_t ubjson_read_array(ubjson_cookie_t *restrict cookie) +{ + return _ubjson_read_struct(cookie, _ubjson_read_array_continue); +} + +ubjson_read_callback_result_t ubjson_read_object(ubjson_cookie_t *restrict cookie) +{ + return _ubjson_read_struct(cookie, _ubjson_read_object_continue); +} + +ubjson_read_callback_result_t ubjson_read_next(ubjson_cookie_t *restrict cookie) +{ + char marker; + ubjson_read_callback_result_t result; + READ_MARKER(); + + ubjson_type_t type; + ssize_t content; + result = _ubjson_get_call(cookie, marker, &type, &content); + if (result != UBJSON_OKAY) { + return result; + } + + return cookie->callback.read(cookie, type, content, UBJSON_ABSENT, 0); +} + +ubjson_read_callback_result_t ubjson_peek_value(ubjson_cookie_t *restrict cookie, + ubjson_type_t *type, ssize_t *content) +{ + char marker = (char) *content; + if (marker == 0) { + ubjson_read_callback_result_t result; + READ_MARKER(); + } + return _ubjson_get_call(cookie, marker, type, content); +} diff --git a/sys/ubjson/ubjson-write.c b/sys/ubjson/ubjson-write.c new file mode 100644 index 0000000000..9a05539fa5 --- /dev/null +++ b/sys/ubjson/ubjson-write.c @@ -0,0 +1,188 @@ +/* + * 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 + */ + +/** + * @ingroup sys + * @{ + * @file + * @brief Universal Binary JSON deserializer + * @author René Kijewski + * @} + */ + +#include "ubjson-internal.h" +#include "ubjson.h" +#include "byteorder.h" + +#include + +#define WRITE_CALL(FUN, ...) \ + do { \ + ssize_t wrote = (FUN)(__VA_ARGS__); \ + if (wrote < 0) { \ + return wrote; \ + } \ + result += wrote; \ + } while (0) + +#define WRITE_BUF(BUF, COUNT) \ + WRITE_CALL(cookie->rw.write, cookie, (BUF), (COUNT)) + +#define WRITE_MARKER(MARKER) \ + do { \ + char marker_buf[] = { (char) (MARKER) }; \ + WRITE_BUF(marker_buf, 1); \ + } while (0) + +#define MARKER_FUN(NAME, MARKER) \ + ssize_t NAME(ubjson_cookie_t *restrict cookie) \ + { \ + ssize_t result = 0; \ + WRITE_MARKER((MARKER)); \ + return result; \ + } + +MARKER_FUN(ubjson_write_null, UBJSON_MARKER_NULL) +MARKER_FUN(ubjson_write_noop, UBJSON_MARKER_NOOP) + +MARKER_FUN(ubjson_open_array, UBJSON_MARKER_ARRAY_START) +MARKER_FUN(ubjson_close_array, UBJSON_MARKER_ARRAY_END) + +MARKER_FUN(ubjson_open_object, UBJSON_MARKER_OBJECT_START) +MARKER_FUN(ubjson_close_object, UBJSON_MARKER_OBJECT_END) + +ssize_t ubjson_write_bool(ubjson_cookie_t *restrict cookie, bool value) +{ + static const char marker_false[] = { UBJSON_MARKER_FALSE }; + static const char marker_true[] = { UBJSON_MARKER_TRUE }; + return cookie->rw.write(cookie, value ? &marker_false : &marker_true, 1); +} + +ssize_t ubjson_write_i32(ubjson_cookie_t *restrict cookie, int32_t value) +{ + ssize_t result = 0; + if ((INT8_MIN <= value) && (value <= INT8_MAX)) { + WRITE_MARKER(UBJSON_MARKER_INT8); + WRITE_MARKER((uint8_t) value); + } + else if ((0 <= value) && (value <= UINT8_MAX)) { + WRITE_MARKER(UBJSON_MARKER_UINT8); + WRITE_MARKER((uint8_t) value); + } + else if ((INT16_MIN <= value) && (value <= UINT16_MAX)) { + WRITE_MARKER(UBJSON_MARKER_INT16); + network_uint16_t buf = byteorder_htons((uint16_t) value); + WRITE_BUF(&buf, sizeof(buf)); + } + else { + WRITE_MARKER(UBJSON_MARKER_INT32); + network_uint32_t buf = byteorder_htonl((uint32_t) value); + WRITE_BUF(&buf, sizeof(buf)); + } + return result; +} + +ssize_t ubjson_write_i64(ubjson_cookie_t *restrict cookie, int64_t value) +{ + if ((INT32_MIN <= value) && (value <= INT32_MAX)) { + return ubjson_write_i32(cookie, (int32_t) value); + } + + ssize_t result = 0; + network_uint64_t buf = byteorder_htonll((uint64_t) value); + WRITE_BUF(&buf, sizeof(buf)); + return result; +} + +ssize_t ubjson_write_float(ubjson_cookie_t *restrict cookie, float value) +{ + union { + float f; + uint32_t i; + } v = { .f = value }; + + ssize_t result = 0; + WRITE_MARKER(UBJSON_MARKER_FLOAT32); + network_uint32_t buf = byteorder_htonl(v.i); + WRITE_BUF(&buf, sizeof(buf)); + return result; +} + +ssize_t ubjson_write_double(ubjson_cookie_t *restrict cookie, double value) +{ + union { + double f; + uint64_t i; + } v = { .f = value }; + + ssize_t result = 0; + WRITE_MARKER(UBJSON_MARKER_FLOAT64); + network_uint64_t buf = byteorder_htonll(v.i); + WRITE_BUF(&buf, sizeof(buf)); + return result; +} + +ssize_t ubjson_write_string(ubjson_cookie_t *restrict cookie, const void *value, size_t len) +{ + ssize_t result = 0; + WRITE_MARKER(UBJSON_MARKER_STRING); + WRITE_CALL(ubjson_write_key, cookie, value, len); + return result; +} + +static ssize_t _ubjson_write_length(ubjson_cookie_t *restrict cookie, size_t len) +{ + ssize_t result = 0; + WRITE_MARKER(UBJSON_MARKER_COUNT); + WRITE_CALL(ubjson_write_i64, cookie, (int64_t) len); + return result; +} + +ssize_t ubjson_open_array_len(ubjson_cookie_t *restrict cookie, size_t len) +{ + ssize_t result = 0; + WRITE_CALL(ubjson_open_array, cookie); + if (len > 0) { + WRITE_CALL(_ubjson_write_length, cookie, len); + } + else { + WRITE_CALL(ubjson_close_array, cookie); + } + return result; +} + +ssize_t ubjson_open_object_len(ubjson_cookie_t *restrict cookie, size_t len) +{ + ssize_t result = 0; + WRITE_CALL(ubjson_open_object, cookie); + if (len > 0) { + WRITE_CALL(_ubjson_write_length, cookie, len); + } + else { + WRITE_CALL(ubjson_close_object, cookie); + } + return result; +} + +ssize_t ubjson_write_key(ubjson_cookie_t *restrict cookie, const void *value, size_t len) +{ + ssize_t result = 0; + WRITE_CALL(ubjson_write_i64, cookie, (int64_t) len); + WRITE_BUF(value, len); + return result; +}