diff --git a/apps/include/netutils/cJSON.h b/apps/include/netutils/cJSON.h index 6fc8474d60335565417a44eb658ed4f43d2d7474..c7cf9836fd896ed61583ad7deaba507b6ddb4dc6 100644 --- a/apps/include/netutils/cJSON.h +++ b/apps/include/netutils/cJSON.h @@ -99,26 +99,22 @@ typedef struct cJSON char *string; } cJSON; -typedef struct cJSON_Hooks -{ - void *(*malloc_fn)(size_t sz); - void (*free_fn)(void *ptr); -} cJSON_Hooks; - /**************************************************************************** * Public Functions ****************************************************************************/ -/* Supply malloc, realloc and free functions to cJSON */ - -void cJSON_InitHooks(cJSON_Hooks* hooks); - /* Supply a block of JSON, and this returns a cJSON object you can * interrogate. Call cJSON_Delete when finished. */ cJSON *cJSON_Parse(const char *value); +/* Supply a stream of JSON, and this returns a cJSON object you can + * interrogate. Call cJSON_Delete when finished. + */ + +cJSON *cJSON_Parse_Stream(char (*getc_fn)(void *priv), void *priv); + /* Render a cJSON entity to text for transfer/storage. Free the char* when * finished. */ @@ -166,6 +162,7 @@ cJSON *cJSON_CreateNumber(double num); cJSON *cJSON_CreateString(const char *string); cJSON *cJSON_CreateArray(); cJSON *cJSON_CreateObject(); +cJSON *cJSON_New_Item(void); /* These utilities create an Array of count items. */ diff --git a/apps/netutils/json/Makefile b/apps/netutils/json/Makefile index 44237d2382035363cd0b581a652d25cb8119cd4f..daa96ceea2350cbdc7ada16b03dbb37dec10b317 100644 --- a/apps/netutils/json/Makefile +++ b/apps/netutils/json/Makefile @@ -38,7 +38,7 @@ include $(APPDIR)/Make.defs ASRCS = -CSRCS = cJSON.c +CSRCS = cJSON.c cJSON_stream_parse.c AOBJS = $(ASRCS:.S=$(OBJEXT)) COBJS = $(CSRCS:.c=$(OBJEXT)) diff --git a/apps/netutils/json/cJSON.c b/apps/netutils/json/cJSON.c index 6a76f1e79b04b98ce4e240646f455fa804601bb2..3f6b200b8e17777d9b725756b7b0a448218c021b 100644 --- a/apps/netutils/json/cJSON.c +++ b/apps/netutils/json/cJSON.c @@ -49,6 +49,11 @@ * Pre-processor Definitions ****************************************************************************/ +#define cJSON_malloc malloc +#define cJSON_free free +#define cJSON_realloc realloc +#define cJSON_strdup strdup + /**************************************************************************** * Private Data ****************************************************************************/ @@ -58,9 +63,6 @@ static const char *ep; static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc }; -static void *(*cJSON_malloc)(size_t sz) = malloc; -static void (*cJSON_free)(void *ptr) = free; - /**************************************************************************** * Private Prototypes ****************************************************************************/ @@ -76,34 +78,6 @@ static char *print_object(cJSON *item, int depth, int fmt); * Private Functions ****************************************************************************/ -static char *cJSON_strdup(const char *str) -{ - size_t len; - char *copy; - - len = strlen(str) + 1; - if (!(copy = (char *)cJSON_malloc(len))) - { - return 0; - } - - memcpy(copy, str, len); - return copy; -} - -/* Internal constructor. */ - -static cJSON *cJSON_New_Item(void) -{ - cJSON *node = (cJSON *) cJSON_malloc(sizeof(cJSON)); - if (node) - { - memset(node, 0, sizeof(cJSON)); - } - - return node; -} - static int cJSON_strcasecmp(const char *s1, const char *s2) { if (!s1) @@ -1130,24 +1104,14 @@ static cJSON *create_reference(cJSON *item) * Public Functions ****************************************************************************/ -const char *cJSON_GetErrorPtr(void) +cJSON *cJSON_New_Item(void) { - return ep; + return calloc(1, sizeof(cJSON)); } -void cJSON_InitHooks(cJSON_Hooks *hooks) +const char *cJSON_GetErrorPtr(void) { - if (!hooks) - { - /* Reset hooks */ - - cJSON_malloc = malloc; - cJSON_free = free; - return; - } - - cJSON_malloc = (hooks->malloc_fn) ? hooks->malloc_fn : malloc; - cJSON_free = (hooks->free_fn) ? hooks->free_fn : free; + return ep; } /* Delete a cJSON structure. */ @@ -1180,7 +1144,7 @@ void cJSON_Delete(cJSON *c) /* Parse an object - create a new root, and populate. */ -cJSON *cJSON_Parse(const char *value) +cJSON *cJSON_Parse_Old(const char *value) { cJSON *c = cJSON_New_Item(); ep = 0; diff --git a/apps/netutils/json/cJSON_stream_parse.c b/apps/netutils/json/cJSON_stream_parse.c new file mode 100644 index 0000000000000000000000000000000000000000..4bea826c3ac4c39ca3ddff612a455c5a93a0f125 --- /dev/null +++ b/apps/netutils/json/cJSON_stream_parse.c @@ -0,0 +1,920 @@ +/**************************************************************************** + * apps/netutils/json/cJSON_stream_parse.c + * + * This file is a part of NuttX: + * + * Copyright (c) 2011 Gregory Nutt. All rights reserved. + * Copyright (c) 2015 Haltian Ltd. + * Author: Jussi Kivilinna <jussi.kivilinna@haltian.com> + * + * And derives from the cJSON Project which has an MIT license: + * + * Copyright (c) 2009 Dave Gamble + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <stdlib.h> +#include <float.h> +#include <limits.h> +#include <ctype.h> +#include <unistd.h> +#include <errno.h> + +#include <apps/netutils/cJSON.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define FD_PARSER_CACHE_SIZE 256 + +#define cJSON_malloc malloc +#define cJSON_free free +#define cJSON_realloc realloc +#define cJSON_strdup strdup + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef struct cJSON_instream_s +{ + char (*getc_fn)(void *priv); + void *fn_priv; + int curr; +} cJSON_instream; + +struct parse_fd_priv_s +{ + int fd; + ssize_t max_readlen; + size_t nread; + char *buf; + size_t buflen; + size_t bufpos; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const unsigned char firstByteMark[7] = + { 0x00, 0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc }; + +/**************************************************************************** + * Private Prototypes + ****************************************************************************/ + +static cJSON_instream *stream_parse_value(cJSON *item, cJSON_instream *in); +static cJSON_instream *stream_parse_array(cJSON *item, cJSON_instream *in); +static cJSON_instream *stream_parse_object(cJSON *item, cJSON_instream *in); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline char stream_get(cJSON_instream *in) +{ + if (in->curr >= 0) + { + char curr = in->curr; + in->curr = -1; + return curr; + } + + return in->getc_fn(in->fn_priv); +} + +static inline char stream_peek(cJSON_instream *in) +{ + if (in->curr >= 0) + { + return in->curr; + } + + in->curr = (unsigned int)in->getc_fn(in->fn_priv) & 0xFF; + return in->curr; +} + +/* Parse the input text to generate a number, and populate the result into item. */ + +static cJSON_instream *stream_parse_number(cJSON *item, cJSON_instream *in) +{ + double n = 0, sign = 1, scale = 0; + int subscale = 0, signsubscale = 1; + + /* Has sign? */ + + if (stream_peek(in) == '-') + { + sign = -1; + (void)stream_get(in); + } + + /* is zero */ + + if (stream_peek(in) == '0') + { + (void)stream_get(in); + + if (stream_peek(in) != '.' && stream_peek(in) != 'e' && + stream_peek(in) != 'E') + { + n = sign * 0.0; + item->valuedouble = n; + item->valueint = (int)n; + item->type = cJSON_Number; + return in; + } + } + + /* Number? */ + + if (stream_peek(in) >= '1' && stream_peek(in) <= '9') + { + do + { + n = (n * 10.0) + (stream_get(in) - '0'); + } + while (stream_peek(in) >= '0' && stream_peek(in) <= '9'); + } + + /* Fractional part? */ + + if (stream_peek(in) == '.') + { + (void)stream_get(in); + + if (stream_peek(in) >= '0' && stream_peek(in) <= '9') + { + do + { + n = (n * 10.0) + (stream_get(in) - '0'), scale--; + } + while (stream_peek(in)>= '0' && stream_peek(in)<= '9'); + } + } + + /* Exponent? */ + + if (stream_peek(in) == 'e' || stream_peek(in) == 'E') + { + (void)stream_get(in); + + if (stream_peek(in) == '+') + { + (void)stream_get(in); + } + + /* With sign? */ + + else if (stream_peek(in) == '-') + { + signsubscale = -1; + (void)stream_get(in); + } + + /* Number? */ + + while (stream_peek(in) >= '0' && stream_peek(in) <= '9') + { + subscale = (subscale * 10) + (stream_get(in) - '0'); + } + } + + /* number = +/- number.fraction * 10^+/-exponent */ + + n = sign * n * pow(10.0, (scale + subscale * signsubscale)); + item->valuedouble = n; + item->valueint = (int)n; + item->type = cJSON_Number; + return in; +} + +static bool parse_realloc(char **out, int *outlen, char **out2, int inclen) +{ + char *newp; + + *outlen += inclen; + newp = cJSON_realloc(*out, *outlen); + if (!newp) + { + free(*out); + *out = NULL; + *out2 = NULL; + *outlen = 0; + return false; + } + + *out2 += newp - *out; + *out = newp; + return true; +} + +static int stream_parse_hex(cJSON_instream *in) +{ + char ascii = stream_peek(in); + unsigned h; + + if (ascii >= '0' && ascii <= '9') + { + h = ascii - '0'; + } + else if (ascii >= 'A' && ascii <= 'F') + { + h = 0xA + ascii - 'A'; + } + else if (ascii >= 'a' && ascii <= 'f') + { + h = 0xa + ascii - 'a'; + } + else + { + return -1; + } + + (void)stream_get(in); + return h; +} + +static unsigned stream_parse_hex4(cJSON_instream *in) +{ + int val; + unsigned h = 0; + + val = stream_parse_hex(in); + if (val < 0) + return 0; + + h += val; + h <<= 4; + + val = stream_parse_hex(in); + if (val < 0) + return 0; + + h += val; + h <<= 4; + + val = stream_parse_hex(in); + if (val < 0) + return 0; + + h += val; + h <<= 4; + + val = stream_parse_hex(in); + if (val < 0) + return 0; + + h += val; + return h; +} + +/* Parse the input text into an unescaped cstring, and populate item. */ + +static cJSON_instream *stream_parse_string(cJSON *item, cJSON_instream *in) +{ + char *ptr2; + char *out; + int len; + int outlen; + unsigned uc; + unsigned uc2; + + if (stream_peek(in) != '\"') + { + /* not a string! */ + + return NULL; + } + + outlen = 1; + out = (char *)cJSON_malloc(outlen + 7); + if (!out) + { + return NULL; + } + + (void)stream_get(in); + ptr2 = out; + while (stream_peek(in) != '\"' && stream_peek(in)) + { + if (stream_peek(in) != '\\') + { + if (!parse_realloc(&out, &outlen, &ptr2, 1)) + { + /* not enough memory */ + + return NULL; + } + + *ptr2++ = stream_get(in); + } + else + { + char val; + + (void)stream_get(in); + +next_escape: + val = stream_peek(in); + switch (val) + { + case 'b': + val = '\b'; + break; + + case 'f': + val = '\f'; + break; + + case 'n': + val = '\n'; + break; + + case 'r': + val = '\r'; + break; + + case 't': + val = '\t'; + break; + } + + (void)stream_get(in); + + switch (val) + { + default: + if (!parse_realloc(&out, &outlen, &ptr2, 1)) + { + /* not enough memory */ + + return NULL; + } + + *ptr2++ = val; + break; + + case 'u': + /* Transcode utf16 to utf8. */ + /* Get the unicode char. */ + + uc = stream_parse_hex4(in); + + /* Check for invalid. */ + + if ((uc >= 0xdc00 && uc <= 0xdfff) || uc == 0) + { + break; + } + + if (uc >= 0xd800 && uc <= 0xdbff) /* UTF16 surrogate pairs. */ + { + /* missing second-half of surrogate. */ + + if (stream_peek(in) != '\\') + { + break; + } + + (void)stream_get(in); + + if (stream_peek(in) != 'u') + { + goto next_escape; + } + + (void)stream_get(in); + + uc2 = stream_parse_hex4(in); + if (uc2 < 0xdc00 || uc2 > 0xdfff) + { + /* Invalid second-half of surrogate. */ + + break; + } + + uc = 0x10000 | ((uc & 0x3ff) << 10) | (uc2 & 0x3ff); + } + + len = 4; + if (uc < 0x80) + { + len = 1; + } + else if (uc < 0x800) + { + len = 2; + } + else if (uc < 0x10000) + { + len = 3; + } + + if (!parse_realloc(&out, &outlen, &ptr2, len)) + { + /* not enough memory */ + + return NULL; + } + + ptr2 += len; + + switch (len) + { + case 4: + *--ptr2 = ((uc | 0x80) & 0xbf); + uc >>= 6; + /* no break */ + case 3: + *--ptr2 = ((uc | 0x80) & 0xbf); + uc >>= 6; + /* no break */ + case 2: + *--ptr2 = ((uc | 0x80) & 0xbf); + uc >>= 6; + /* no break */ + case 1: + *--ptr2 = (uc | firstByteMark[len]); + break; + } + + ptr2 += len; + break; + } + } + } + + *ptr2 = 0; + + if (stream_peek(in) == '\"') + { + (void)stream_get(in); + } + + item->valuestring = out; + item->type = cJSON_String; + return in; +} + +/* Utility to jump whitespace and cr/lf */ + +static cJSON_instream *skip(cJSON_instream *in) +{ + while (in && stream_peek(in) && (unsigned char)stream_peek(in) <= 32) + { + (void)stream_get(in); + } + + return in; +} + +/* Parser core - when encountering text, process appropriately. */ + +static cJSON_instream *stream_parse_value(cJSON *item, cJSON_instream *in) +{ + static const struct + { + const char *name; + char type; + char value; + } match_strings[] = + { + { "null", cJSON_NULL, 0 }, + { "false", cJSON_False, 0 }, + { "true", cJSON_True, 1 }, + { } + }; + int midx; + + if (!in) + { + /* Fail on null. */ + + return NULL; + } + + for (midx = 0; match_strings[midx].name; midx++) + { + const char *match = match_strings[midx].name; + + if (stream_peek(in) == *match) + { + while (*match && stream_get(in) == *match) + { + match++; + } + + if (*match != '\0') + { + /* Failure. */ + + return NULL; + } + + item->type = match_strings[midx].type; + item->valueint = match_strings[midx].value; + return in; + } + } + + if (stream_peek(in) == '\"') + { + return stream_parse_string(item, in); + } + + if (stream_peek(in) == '-' || + (stream_peek(in) >= '0' && stream_peek(in) <= '9')) + { + return stream_parse_number(item, in); + } + + if (stream_peek(in) == '[') + { + return stream_parse_array(item, in); + } + + if (stream_peek(in) == '{') + { + return stream_parse_object(item, in); + } + + /* Failure. */ + + return NULL; +} + +/* Build an array from input text. */ + +static cJSON_instream *stream_parse_array(cJSON *item, cJSON_instream *in) +{ + cJSON *child; + + if (stream_peek(in) != '[') + { + /* not an array! */ + + return NULL; + } + + item->type = cJSON_Array; + (void)stream_get(in); + in = skip(in); + if (stream_peek(in) == ']') + { + /* Empty array. */ + + (void)stream_get(in); + return in; + } + + item->child = child = cJSON_New_Item(); + if (!item->child) + { + /* Memory fail */ + + return NULL; + } + + /* Skip any spacing, get the value. */ + + in = skip(stream_parse_value(child, skip(in))); + if (!in) + { + return NULL; + } + + while (stream_peek(in) == ',') + { + cJSON *new_item; + if (!(new_item = cJSON_New_Item())) + { + /* memory fail */ + + return NULL; + } + + child->next = new_item; + new_item->prev = child; + child = new_item; + (void)stream_get(in); + in = skip(stream_parse_value(child, skip(in))); + if (!in) + { + /* Memory fail */ + + return NULL; + } + } + + if (stream_peek(in) == ']') + { + /* End of array */ + + (void)stream_get(in); + return in; + } + + /* Malformed */ + + return NULL; +} + +/* Build an object from the text. */ + +static cJSON_instream *stream_parse_object(cJSON *item, cJSON_instream *in) +{ + cJSON *child; + if (stream_peek(in) != '{') + { + /* Not an object! */ + + return NULL; + } + + item->type = cJSON_Object; + (void)stream_get(in); + in = skip(in); + if (stream_peek(in) == '}') + { + /* Empty array. */ + + (void)stream_get(in); + return in; + } + + item->child = child = cJSON_New_Item(); + if (!item->child) + { + return NULL; + } + + in = skip(stream_parse_string(child, skip(in))); + if (!in) + { + return NULL; + } + + child->string = child->valuestring; + child->valuestring = 0; + if (stream_peek(in) != ':') + { + return NULL; + } + + /* Skip any spacing, get the value. */ + + (void)stream_get(in); + in = skip(stream_parse_value(child, skip(in))); + if (!in) + { + return NULL; + } + + while (stream_peek(in) == ',') + { + cJSON *new_item; + if (!(new_item = cJSON_New_Item())) + { + /* Memory fail */ + + return NULL; + } + + child->next = new_item; + new_item->prev = child; + child = new_item; + (void)stream_get(in); + in = skip(stream_parse_string(child, skip(in))); + if (!in) + { + return NULL; + } + + child->string = child->valuestring; + child->valuestring = 0; + if (stream_peek(in) != ':') + { + return NULL; + } + + /* Skip any spacing, get the value. */ + + (void)stream_get(in); + in = skip(stream_parse_value(child, skip(in))); + if (!in) + { + return NULL; + } + } + + if (stream_peek(in) == '}') + { + /* End of array */ + + (void)stream_get(in); + return in; + } + + /* Malformed */ + + return NULL; +} + +static char getc_string(void *priv) +{ + char **pvalue = priv; + char *value = *pvalue; + char val = *value; + + if (val) + { + value++; + *pvalue = value; + } + + return val; +} + +static char getc_fd(void *priv) +{ + struct parse_fd_priv_s *parse = priv; + ssize_t ret; + char val; + + if (parse->max_readlen > 0 && parse->nread == parse->max_readlen) + { + /* End of fd-buffer reached, give NULL char. */ + + return '\0'; + } + + if (parse->buf && parse->bufpos < parse->buflen) + { + /* Load next from buffer. */ + + return parse->buf[parse->bufpos++]; + } + + if (parse->max_readlen > 0 && !parse->buf) + { + /* Allocate read buffer. */ + + parse->buf = malloc(FD_PARSER_CACHE_SIZE); + } + + if (parse->buf) + { + size_t maxread = parse->max_readlen - parse->nread; + + if (maxread > FD_PARSER_CACHE_SIZE) + { + maxread = FD_PARSER_CACHE_SIZE; + } + + /* Read buffer full or remaining bytes. */ + + while ((ret = read(parse->fd, parse->buf, maxread)) < 0) + { + if (errno == EINTR || errno == EAGAIN) + { + continue; + } + + break; + } + + if (ret <= 0) + { + return '\0'; + } + + parse->nread += ret; + parse->bufpos = 0; + parse->buflen = ret; + + return parse->buf[parse->bufpos++]; + } + + /* Buffer not used, either allocation failed or do not know input buffer + * length. */ + + while ((ret = read(parse->fd, &val, 1)) < 0) + { + if (errno == EINTR || errno == EAGAIN) + { + continue; + } + + break; + } + + if (ret == 1) + { + parse->nread++; + return val; + } + + return '\0'; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/* Parse an object from stream - create a new root, and populate. */ + +cJSON *cJSON_Parse_Stream(char (*getc_fn)(void *priv), void *priv) +{ + cJSON_instream stream = {}; + cJSON *c; + + c = cJSON_New_Item(); + if (!c) + { + /* Memory fail */ + + return NULL; + } + + stream.getc_fn = getc_fn; + stream.fn_priv = priv; + stream.curr = -1; + + if (!stream_parse_value(c, skip(&stream))) + { + cJSON_Delete(c); + return NULL; + } + + skip(&stream); + if (stream_get(&stream)) + { + /* Malformed at end. */ + + cJSON_Delete(c); + return NULL; + } + + return c; +} + +/* Parse an object from input string - create a new root, and populate. */ + +cJSON *cJSON_Parse(const char *value) +{ + char **pvalue = (void *)&value; + return cJSON_Parse_Stream(getc_string, (void *)pvalue); +} + +/* Parse an object from file-descriptor - create a new root, and populate. */ + +cJSON *cJSON_Parse_fd(int fd, ssize_t max_readlen, size_t *nread) +{ + struct parse_fd_priv_s parse = + { + .fd = fd, + .max_readlen = max_readlen, + .buf = NULL + }; + cJSON *obj; + + obj = cJSON_Parse_Stream(getc_fd, &parse); + if (nread) + { + *nread = parse.nread; + } + + free(parse.buf); + + return obj; +} diff --git a/apps/netutils/json_gtest/Makefile b/apps/netutils/json_gtest/Makefile index 8c73c0884257c3a446e6dea9deed92a3ee8fa8f1..9dc8b5eb25d98e041b72fedfbfac3973b0350159 100644 --- a/apps/netutils/json_gtest/Makefile +++ b/apps/netutils/json_gtest/Makefile @@ -4,9 +4,11 @@ include $(APPDIR)/Make.defs HOSTOBJEXT ?= .hobj -HOSTCSRCS := ../json/cJSON.c +HOSTCSRCS := ../json/cJSON.c ../json/cJSON_stream_parse.c HOSTCXXSRCS := cJSON_test.cc empty_arrays.cc empty_objects.cc +HOSTCSRCS += nuttx_glue.c + HOSTCOBJS = $(HOSTCSRCS:.c=$(HOSTOBJEXT)) HOSTCXXOBJS = $(HOSTCXXSRCS:.cc=$(HOSTOBJEXT)) diff --git a/apps/netutils/json_gtest/nuttx_glue.c b/apps/netutils/json_gtest/nuttx_glue.c new file mode 100644 index 0000000000000000000000000000000000000000..1a41fcc20e5ef4f9dfcd57d91126e938ba54c794 --- /dev/null +++ b/apps/netutils/json_gtest/nuttx_glue.c @@ -0,0 +1,10 @@ +int *get_errno_ptr(void) +{ + static int err = -1; + return &err; +} +void up_assert(char *s) +{ + extern void exit(int); + exit(1); +}