From a764aefa31282193e8ea119ad86edd8efa3ad13a Mon Sep 17 00:00:00 2001
From: Jussi Kivilinna <jussi.kivilinna@iki.fi>
Date: Tue, 29 Sep 2015 21:11:03 +0300
Subject: [PATCH] netutils/cJSON: add input stream JSON parser

Patch provides new stream parsing engine. Benefits are: parsing input
string using user supplied getc function, which allows reading input
directly from files or socket streams (thus avoid temporary buffer
allocation). Parser has also been fixed to pass all tests in 'json_gtest'
folder.

From github:thingsee/thingsee-sdk pull request:

 "Improve cJSON to allow parse/print from/to stream (caller supplied getc/putc functions) for reducing peak memory usage"

 https://github.com/thingsee/thingsee-sdk/pull/6

Signed-off-by: Jussi Kivilinna <jussi.kivilinna@haltian.com>
(cherry picked from commit ed7c67771fcc97ffc9726241d6db70b24b0c0995)
---
 apps/include/netutils/cJSON.h           |  17 +-
 apps/netutils/json/Makefile             |   2 +-
 apps/netutils/json/cJSON.c              |  56 +-
 apps/netutils/json/cJSON_stream_parse.c | 920 ++++++++++++++++++++++++
 apps/netutils/json_gtest/Makefile       |   4 +-
 apps/netutils/json_gtest/nuttx_glue.c   |  10 +
 6 files changed, 951 insertions(+), 58 deletions(-)
 create mode 100644 apps/netutils/json/cJSON_stream_parse.c
 create mode 100644 apps/netutils/json_gtest/nuttx_glue.c

diff --git a/apps/include/netutils/cJSON.h b/apps/include/netutils/cJSON.h
index 6fc8474d..c7cf9836 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 44237d23..daa96cee 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 6a76f1e7..3f6b200b 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 00000000..4bea826c
--- /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 8c73c088..9dc8b5eb 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 00000000..1a41fcc2
--- /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);
+}
-- 
GitLab