lib: json: Add encoding support
Serializing an object in JSON is quite tricky to do by hand, and with an array of descriptor structs, there's enough information to do that programatically. The encoder takes a callback function, so that one can be written to write bytes to, for instance, a struct net_buf. This way, there's no guesswork to determine the buffer size, reducing the possibility of overflowing the stack. Jira: ZEP-1607 Change-Id: I5ccf1012e46c1db32fcfdf2ecee4a1ef44c927d5 Signed-off-by: Leandro Pereira <leandro.pereira@intel.com>
This commit is contained in:
parent
67ac6f6701
commit
b9b1c18cd7
2 changed files with 327 additions and 11 deletions
267
lib/json/json.c
267
lib/json/json.c
|
@ -8,6 +8,7 @@
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <misc/printk.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
@ -621,28 +622,76 @@ int json_obj_parse(char *payload, size_t len,
|
||||||
|
|
||||||
static const char escapable[] = "\"\\/\b\f\n\r\t";
|
static const char escapable[] = "\"\\/\b\f\n\r\t";
|
||||||
|
|
||||||
static int json_escape_internal(char *str, size_t *len, size_t buf_size)
|
static int json_escape_internal(const char *str,
|
||||||
|
json_append_bytes_t append_bytes,
|
||||||
|
void *data)
|
||||||
{
|
{
|
||||||
char tmp_buf[buf_size + 1];
|
const char *cur, *escape;
|
||||||
char *cur, *out = tmp_buf, *escape;
|
size_t out_len = 0;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
for (cur = str; *cur; cur++) {
|
for (cur = str; ret == 0 && *cur; cur++) {
|
||||||
escape = memchr(escapable, *cur, sizeof(escapable) - 1);
|
escape = memchr(escapable, *cur, sizeof(escapable) - 1);
|
||||||
|
|
||||||
if (escape) {
|
if (escape) {
|
||||||
*out++ = '\\';
|
uint8_t bytes[2] = {
|
||||||
*out++ = "\"\\/bfnrt"[escape - escapable];
|
'\\', "\"\\/bfnrt"[escape - escapable]
|
||||||
|
};
|
||||||
|
|
||||||
|
ret = append_bytes(bytes, 2, data);
|
||||||
|
out_len += 2;
|
||||||
} else {
|
} else {
|
||||||
*out++ = *cur;
|
ret = append_bytes(cur, 1, data);
|
||||||
|
out_len++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*out = '\0';
|
return ret;
|
||||||
*len = out - tmp_buf;
|
}
|
||||||
memcpy(str, tmp_buf, *len);
|
|
||||||
|
struct appender {
|
||||||
|
char *buffer;
|
||||||
|
size_t used;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int append_bytes_to_buf(const uint8_t *bytes, size_t len, void *data)
|
||||||
|
{
|
||||||
|
struct appender *appender = data;
|
||||||
|
|
||||||
|
if (len > appender->size - appender->used) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(appender->buffer + appender->used, bytes, len);
|
||||||
|
appender->used += len;
|
||||||
|
appender->buffer[appender->used] = '\0';
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int json_escape_buf(char *str, size_t *len, size_t buf_size)
|
||||||
|
{
|
||||||
|
char tmp_buf[buf_size + 1];
|
||||||
|
struct appender appender = { .buffer = tmp_buf, .size = buf_size };
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = json_escape_internal(str, append_bytes_to_buf, &appender);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append_bytes_to_buf("", 1, &appender);
|
||||||
|
if (!ret) {
|
||||||
|
memcpy(str, tmp_buf, appender.size);
|
||||||
|
if (len) {
|
||||||
|
*len = appender.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
size_t json_calc_escaped_len(const char *str, size_t len)
|
size_t json_calc_escaped_len(const char *str, size_t len)
|
||||||
{
|
{
|
||||||
size_t escaped_len = len;
|
size_t escaped_len = len;
|
||||||
|
@ -674,5 +723,201 @@ ssize_t json_escape(char *str, size_t *len, size_t buf_size)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
return json_escape_internal(str, len, escaped_len);
|
return json_escape_buf(str, len, escaped_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int encode(const struct json_obj_descr *descr, const void *val,
|
||||||
|
json_append_bytes_t append_bytes, void *data);
|
||||||
|
|
||||||
|
static int arr_encode(const struct json_obj_descr *descr, const void *field,
|
||||||
|
const void *val, json_append_bytes_t append_bytes,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
struct json_obj_descr elem_descr = { .type = descr->type };
|
||||||
|
ptrdiff_t elem_size = get_elem_size(descr);
|
||||||
|
size_t n_elem = *(size_t *)((char *)val + descr->offset);
|
||||||
|
size_t i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = append_bytes("[", 1, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < n_elem; i++) {
|
||||||
|
ret = encode(&elem_descr, field, append_bytes, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < n_elem - 1) {
|
||||||
|
ret = append_bytes(",", 1, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field = (char *)field + elem_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return append_bytes("]", 1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int str_encode(const char **str, json_append_bytes_t append_bytes,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = append_bytes("\"", 1, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = json_escape_internal(*str, append_bytes, data);
|
||||||
|
if (!ret) {
|
||||||
|
return append_bytes("\"", 1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int num_encode(const int32_t *num, json_append_bytes_t append_bytes,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
char buf[3 * sizeof(int32_t)];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = snprintk(buf, sizeof(buf), "%d", *num);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (ret >= (int)sizeof(buf)) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return append_bytes(buf, (size_t)ret, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bool_encode(const bool *value, json_append_bytes_t append_bytes,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
if (*value) {
|
||||||
|
return append_bytes("true", 4, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return append_bytes("false", 5, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int obj_encode(const struct json_obj_descr *descr, size_t descr_len,
|
||||||
|
const void *val, json_append_bytes_t append_bytes,
|
||||||
|
void *data);
|
||||||
|
|
||||||
|
static int encode(const struct json_obj_descr *descr, const void *val,
|
||||||
|
json_append_bytes_t append_bytes, void *data)
|
||||||
|
{
|
||||||
|
void *ptr = (char *)val + descr->offset;
|
||||||
|
|
||||||
|
switch (descr->type) {
|
||||||
|
case JSON_TOK_FALSE:
|
||||||
|
case JSON_TOK_TRUE:
|
||||||
|
return bool_encode(ptr, append_bytes, data);
|
||||||
|
case JSON_TOK_STRING:
|
||||||
|
return str_encode(ptr, append_bytes, data);
|
||||||
|
case JSON_TOK_LIST_START:
|
||||||
|
return arr_encode(descr->element_descr, ptr,
|
||||||
|
val, append_bytes, data);
|
||||||
|
case JSON_TOK_OBJECT_START:
|
||||||
|
return obj_encode(descr->sub_descr, descr->sub_descr_len,
|
||||||
|
ptr, append_bytes, data);
|
||||||
|
case JSON_TOK_NUMBER:
|
||||||
|
return num_encode(ptr, append_bytes, data);
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int obj_encode(const struct json_obj_descr *descr, size_t descr_len,
|
||||||
|
const void *val, json_append_bytes_t append_bytes,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = append_bytes("{", 1, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < descr_len; i++) {
|
||||||
|
ret = str_encode((const char **)&descr[i].field_name,
|
||||||
|
append_bytes, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = append_bytes(":", 1, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = encode(&descr[i], val, append_bytes, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < descr_len - 1) {
|
||||||
|
ret = append_bytes(",", 1, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return append_bytes("}", 1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
int json_obj_encode(const struct json_obj_descr *descr, size_t descr_len,
|
||||||
|
const void *val, json_append_bytes_t append_bytes,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = obj_encode(descr, descr_len, val, append_bytes, data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return append_bytes("", 1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
int json_obj_encode_buf(const struct json_obj_descr *descr, size_t descr_len,
|
||||||
|
const void *val, char *buffer, size_t buf_size)
|
||||||
|
{
|
||||||
|
struct appender appender = { .buffer = buffer, .size = buf_size };
|
||||||
|
|
||||||
|
return json_obj_encode(descr, descr_len, val, append_bytes_to_buf,
|
||||||
|
&appender);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int measure_bytes(const uint8_t *bytes, size_t len, void *data)
|
||||||
|
{
|
||||||
|
ssize_t *total = data;
|
||||||
|
|
||||||
|
*total += (ssize_t)len;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t json_calc_encoded_len(const struct json_obj_descr *descr,
|
||||||
|
size_t descr_len, const void *val)
|
||||||
|
{
|
||||||
|
ssize_t total = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = json_obj_encode(descr, descr_len, val, measure_bytes, &total);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,21 @@ struct json_obj_descr {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function pointer type to append bytes to a buffer while
|
||||||
|
* encoding JSON data.
|
||||||
|
*
|
||||||
|
* @param bytes Contents to write to the output
|
||||||
|
* @param len Number of bytes in @param bytes to append to output
|
||||||
|
* @param data User-provided pointer
|
||||||
|
*
|
||||||
|
* @return This callback function should return a negative number on
|
||||||
|
* error (which will be propagated to the return value of
|
||||||
|
* json_obj_encode()), or 0 on success.
|
||||||
|
*/
|
||||||
|
typedef int (*json_append_bytes_t)(const uint8_t *bytes, size_t len,
|
||||||
|
void *data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Helper macro to declare a descriptor for an object value
|
* @brief Helper macro to declare a descriptor for an object value
|
||||||
*
|
*
|
||||||
|
@ -196,4 +211,60 @@ ssize_t json_escape(char *str, size_t *len, size_t buf_size);
|
||||||
*/
|
*/
|
||||||
size_t json_calc_escaped_len(const char *str, size_t len);
|
size_t json_calc_escaped_len(const char *str, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the string length to fully encode an object
|
||||||
|
*
|
||||||
|
* @param descr Pointer to the descriptor array
|
||||||
|
*
|
||||||
|
* @param descr_len Number of elements in the descriptor array
|
||||||
|
*
|
||||||
|
* @param val Struct holding the values
|
||||||
|
*
|
||||||
|
* @return Number of bytes necessary to encode the values if >0,
|
||||||
|
* an error code is returned.
|
||||||
|
*/
|
||||||
|
ssize_t json_calc_encoded_len(const struct json_obj_descr *descr,
|
||||||
|
size_t descr_len, const void *val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encodes an object in a contiguous memory location
|
||||||
|
*
|
||||||
|
* @param descr Pointer to the descriptor array
|
||||||
|
*
|
||||||
|
* @param descr_len Number of elements in the descriptor array
|
||||||
|
*
|
||||||
|
* @param val Struct holding the values
|
||||||
|
*
|
||||||
|
* @param buffer Buffer to store the JSON data
|
||||||
|
*
|
||||||
|
* @param buf_size Size of buffer, in bytes, with space for the terminating
|
||||||
|
* NUL character
|
||||||
|
*
|
||||||
|
* @return 0 if object has been successfully encoded. A negative value
|
||||||
|
* indicates an error (as defined on errno.h).
|
||||||
|
*/
|
||||||
|
int json_obj_encode_buf(const struct json_obj_descr *descr, size_t descr_len,
|
||||||
|
const void *val, char *buffer, size_t buf_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encodes an object using an arbitrary writer function
|
||||||
|
*
|
||||||
|
* @param descr Pointer to the descriptor array
|
||||||
|
*
|
||||||
|
* @param descr_len Number of elements in the descriptor array
|
||||||
|
*
|
||||||
|
* @param val Struct holding the values
|
||||||
|
*
|
||||||
|
* @param append_bytes Function to append bytes to the output
|
||||||
|
*
|
||||||
|
* @param data Data pointer to be passed to the append_bytes callback
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* @return 0 if object has been successfully encoded. A negative value
|
||||||
|
* indicates an error.
|
||||||
|
*/
|
||||||
|
int json_obj_encode(const struct json_obj_descr *descr, size_t descr_len,
|
||||||
|
const void *val, json_append_bytes_t append_bytes,
|
||||||
|
void *data);
|
||||||
|
|
||||||
#endif /* __JSON_H */
|
#endif /* __JSON_H */
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue