Stefan Reinauer has uploaded this change for review.
Plain mjason 1.6
With whitespace fixes. Because flashrom doesn't like vanilla.
Change-Id: I9f770baec883f35507bf3b7b60ef2d581db27002
Signed-off-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
---
A mjson.c
A mjson.h
2 files changed, 1,016 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/47/75647/1
diff --git a/mjson.c b/mjson.c
new file mode 100644
index 0000000..ddb30d6
--- /dev/null
+++ b/mjson.c
@@ -0,0 +1,858 @@
+/****************************************************************************
+
+NAME
+ mjson.c - parse JSON into fixed-extent data structures
+
+DESCRIPTION
+ This module parses a large subset of JSON (JavaScript Object
+Notation). Unlike more general JSON parsers, it doesn't use malloc(3)
+and doesn't support polymorphism; you need to give it a set of
+template structures describing the expected shape of the incoming
+JSON, and it will error out if that shape is not matched. When the
+parse succeeds, attribute values will be extracted into static
+locations specified in the template structures.
+
+ The "shape" of a JSON object in the type signature of its
+attributes (and attribute values, and so on recursively down through
+all nestings of objects and arrays). This parser is indifferent to
+the order of attributes at any level, but you have to tell it in
+advance what the type of each attribute value will be and where the
+parsed value will be stored. The template structures may supply
+default values to be used when an expected attribute is omitted.
+
+ The preceding paragraph told one fib. A single attribute may
+actually have a span of multiple specifications with different
+syntactically distinguishable types (e.g. string vs. real vs. integer
+vs. boolean, but not signed integer vs. unsigned integer). The parser
+will match the right spec against the actual data.
+
+ The dialect this parses has some limitations. First, it cannot
+recognize the JSON "null" value. Second, all elements of an array must
+be of the same type. Third, characters may not be array elements (this
+restriction could be lifted)
+
+ There are separate entry points for beginning a parse of either
+JSON object or a JSON array. JSON "float" quantities are actually
+stored as doubles.
+
+ This parser processes object arrays in one of two different ways,
+defending on whether the array subtype is declared as object or
+structobject.
+
+ Object arrays take one base address per object subfield, and are
+mapped into parallel C arrays (one per subfield). Strings are not
+supported in this kind of array, as they don't have a "natural" size
+to use as an offset multiplier.
+
+ Structobjects arrays are a way to parse a list of objects to a set
+of modifications to a corresponding array of C structs. The trick is
+that the array object initialization has to specify both the C struct
+array's base address and the stride length (the size of the C struct).
+If you initialize the offset fields with the correct offsetof calls,
+everything will work. Strings are supported but all string storage
+has to be inline in the struct.
+
+PERMISSIONS
+ This file is Copyright (c) 2014 by Eric S. Raymond
+ SPDX-License-Identifier: BSD-2-Clause
+
+***************************************************************************/
+/* The strptime prototype is not provided unless explicitly requested.
+ * We also need to set the value high enough to signal inclusion of
+ * newer features (like clock_gettime). See the POSIX spec for more info:
+ * http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_02_01_02 */
+#define _XOPEN_SOURCE 600
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+#include <math.h> /* for HUGE_VAL */
+
+#include "mjson.h"
+
+#define str_starts_with(s, p) (strncmp(s, p, strlen(p)) == 0)
+
+#ifdef DEBUG_ENABLE
+static int debuglevel = 0;
+static FILE *debugfp;
+
+void json_enable_debug(int level, FILE * fp)
+/* control the level and destination of debug trace messages */
+{
+ debuglevel = level;
+ debugfp = fp;
+}
+
+static void json_trace(int errlevel, const char *fmt, ...)
+/* assemble command in printf(3) style */
+{
+ if (errlevel <= debuglevel) {
+ char buf[BUFSIZ];
+ va_list ap;
+
+ (void)strncpy(buf, "json: ", BUFSIZ-1);
+ buf[BUFSIZ-1] = '\0';
+ va_start(ap, fmt);
+ (void)vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt,
+ ap);
+ va_end(ap);
+
+ (void)fputs(buf, debugfp);
+ }
+}
+
+# define json_debug_trace(args) (void) json_trace args
+#else
+# define json_debug_trace(args) do { } while (0)
+#endif /* DEBUG_ENABLE */
+
+static char *json_target_address(const struct json_attr_t *cursor,
+ const struct json_array_t
+ *parent, int offset)
+{
+ char *targetaddr = NULL;
+ if (parent == NULL || parent->element_type != t_structobject) {
+ /* ordinary case - use the address in the cursor structure */
+ switch (cursor->type) {
+ case t_ignore:
+ targetaddr = NULL;
+ break;
+ case t_integer:
+ targetaddr = (char *)&cursor->addr.integer[offset];
+ break;
+ case t_uinteger:
+ targetaddr = (char *)&cursor->addr.uinteger[offset];
+ break;
+ case t_short:
+ targetaddr = (char *)&cursor->addr.shortint[offset];
+ break;
+ case t_ushort:
+ targetaddr = (char *)&cursor->addr.ushortint[offset];
+ break;
+ case t_time:
+ case t_real:
+ targetaddr = (char *)&cursor->addr.real[offset];
+ break;
+ case t_string:
+ targetaddr = cursor->addr.string;
+ break;
+ case t_boolean:
+ targetaddr = (char *)&cursor->addr.boolean[offset];
+ break;
+ case t_character:
+ targetaddr = (char *)&cursor->addr.character[offset];
+ break;
+ default:
+ targetaddr = NULL;
+ break;
+ }
+ } else
+ /* tricky case - hacking a member in an array of structures */
+ targetaddr =
+ parent->arr.objects.base + (offset * parent->arr.objects.stride) +
+ cursor->addr.offset;
+ json_debug_trace((1, "Target address for %s (offset %d) is %p\n",
+ cursor->attribute, offset, targetaddr));
+ return targetaddr;
+}
+
+#ifdef TIME_ENABLE
+static double iso8601_to_unix(char *isotime)
+/* ISO8601 UTC to Unix UTC */
+{
+ double usec;
+ struct tm tm;
+
+ char *dp = strptime(isotime, "%Y-%m-%dT%H:%M:%S", &tm);
+ if (dp == NULL)
+ return (double)HUGE_VAL;
+ if (*dp == '.')
+ usec = strtod(dp, NULL);
+ else
+ usec = 0;
+ return (double)timegm(&tm) + usec;
+}
+#endif /* TIME_ENABLE */
+
+static int json_internal_read_object(const char *cp,
+ const struct json_attr_t *attrs,
+ const struct json_array_t *parent,
+ int offset,
+ const char **end)
+{
+ enum
+ { init, await_attr, in_attr, await_value, in_val_string,
+ in_escape, in_val_token, post_val, post_element
+ } state = 0;
+#ifdef DEBUG_ENABLE
+ char *statenames[] = {
+ "init", "await_attr", "in_attr", "await_value", "in_val_string",
+ "in_escape", "in_val_token", "post_val", "post_element",
+ };
+#endif /* DEBUG_ENABLE */
+ char attrbuf[JSON_ATTR_MAX + 1], *pattr = NULL;
+ char valbuf[JSON_VAL_MAX + 1], *pval = NULL;
+ bool value_quoted = false;
+ char uescape[5]; /* enough space for 4 hex digits and a NUL */
+ const struct json_attr_t *cursor;
+ int substatus, n, maxlen = 0;
+ unsigned int u;
+ const struct json_enum_t *mp;
+ char *lptr;
+
+ if (end != NULL)
+ *end = NULL; /* give it a well-defined value on parse failure */
+
+ /* stuff fields with defaults in case they're omitted in the JSON input */
+ for (cursor = attrs; cursor->attribute != NULL; cursor++)
+ if (!cursor->nodefault) {
+ lptr = json_target_address(cursor, parent, offset);
+ if (lptr != NULL)
+ switch (cursor->type) {
+ case t_integer:
+ memcpy(lptr, &cursor->dflt.integer, sizeof(int));
+ break;
+ case t_uinteger:
+ memcpy(lptr, &cursor->dflt.uinteger, sizeof(unsigned int));
+ break;
+ case t_short:
+ memcpy(lptr, &cursor->dflt.shortint, sizeof(short));
+ break;
+ case t_ushort:
+ memcpy(lptr, &cursor->dflt.ushortint,
+ sizeof(unsigned short));
+ break;
+ case t_time:
+ case t_real:
+ memcpy(lptr, &cursor->dflt.real, sizeof(double));
+ break;
+ case t_string:
+ if (parent != NULL
+ && parent->element_type != t_structobject
+ && offset > 0)
+ return JSON_ERR_NOPARSTR;
+ lptr[0] = '\0';
+ break;
+ case t_boolean:
+ memcpy(lptr, &cursor->dflt.boolean, sizeof(bool));
+ break;
+ case t_character:
+ lptr[0] = cursor->dflt.character;
+ break;
+ case t_object: /* silences a compiler warning */
+ case t_structobject:
+ case t_array:
+ case t_check:
+ case t_ignore:
+ break;
+ }
+ }
+
+ json_debug_trace((1, "JSON parse of '%s' begins.\n", cp));
+
+ /* parse input JSON */
+ for (; *cp != '\0'; cp++) {
+ json_debug_trace((2, "State %-14s, looking at '%c' (%p)\n",
+ statenames[state], *cp, cp));
+ switch (state) {
+ case init:
+ if (isspace((unsigned char) *cp))
+ continue;
+ else if (*cp == '{')
+ state = await_attr;
+ else {
+ json_debug_trace((1,
+ "Non-WS when expecting object start.\n"));
+ if (end != NULL)
+ *end = cp;
+ return JSON_ERR_OBSTART;
+ }
+ break;
+ case await_attr:
+ if (isspace((unsigned char) *cp))
+ continue;
+ else if (*cp == '"') {
+ state = in_attr;
+ pattr = attrbuf;
+ if (end != NULL)
+ *end = cp;
+ } else if (*cp == '}')
+ break;
+ else {
+ json_debug_trace((1, "Non-WS when expecting attribute.\n"));
+ if (end != NULL)
+ *end = cp;
+ return JSON_ERR_ATTRSTART;
+ }
+ break;
+ case in_attr:
+ if (pattr == NULL)
+ /* don't update end here, leave at attribute start */
+ return JSON_ERR_NULLPTR;
+ if (*cp == '"') {
+ *pattr++ = '\0';
+ json_debug_trace((1, "Collected attribute name %s\n",
+ attrbuf));
+ for (cursor = attrs; cursor->attribute != NULL; cursor++) {
+ json_debug_trace((2, "Checking against %s\n",
+ cursor->attribute));
+ if (strcmp(cursor->attribute, attrbuf) == 0)
+ break;
+ if (strcmp(cursor->attribute, "") == 0 &&
+ cursor->type == t_ignore) {
+ break;
+ }
+ }
+ if (cursor->attribute == NULL) {
+ json_debug_trace((1,
+ "Unknown attribute name '%s'"
+ " (attributes begin with '%s').\n",
+ attrbuf, attrs->attribute));
+ /* don't update end here, leave at attribute start */
+ return JSON_ERR_BADATTR;
+ }
+ state = await_value;
+ if (cursor->type == t_string)
+ maxlen = (int)cursor->len - 1;
+ else if (cursor->type == t_check)
+ maxlen = (int)strlen(cursor->dflt.check);
+ else if (cursor->type == t_time || cursor->type == t_ignore)
+ maxlen = JSON_VAL_MAX;
+ else if (cursor->map != NULL)
+ maxlen = (int)sizeof(valbuf) - 1;
+ pval = valbuf;
+ } else if (pattr >= attrbuf + JSON_ATTR_MAX - 1) {
+ json_debug_trace((1, "Attribute name too long.\n"));
+ /* don't update end here, leave at attribute start */
+ return JSON_ERR_ATTRLEN;
+ } else
+ *pattr++ = *cp;
+ break;
+ case await_value:
+ if (isspace((unsigned char) *cp) || *cp == ':')
+ continue;
+ else if (*cp == '[') {
+ if (cursor->type != t_array) {
+ json_debug_trace((1,
+ "Saw [ when not expecting array.\n"));
+ if (end != NULL)
+ *end = cp;
+ return JSON_ERR_NOARRAY;
+ }
+ substatus = json_read_array(cp, &cursor->addr.array, &cp);
+ if (substatus != 0)
+ return substatus;
+ state = post_element;
+ } else if (cursor->type == t_array) {
+ json_debug_trace((1,
+ "Array element was specified, but no [.\n"));
+ if (end != NULL)
+ *end = cp;
+ return JSON_ERR_NOBRAK;
+ } else if (*cp == '{') {
+ if (cursor->type != t_object) {
+ json_debug_trace((1,
+ "Saw { when not expecting object.\n"));
+ if (end != NULL)
+ *end = cp;
+ return JSON_ERR_NOARRAY;
+ }
+ substatus = json_read_object(cp, cursor->addr.attrs, &cp);
+ if (substatus != 0)
+ return substatus;
+ --cp; // last } will be re-consumed by cp++ at end of loop
+ state = post_element;
+ } else if (cursor->type == t_object) {
+ json_debug_trace((1,
+ "Object element was specified, but no {.\n"));
+ if (end != NULL)
+ *end = cp;
+ return JSON_ERR_NOCURLY;
+ } else if (*cp == '"') {
+ value_quoted = true;
+ state = in_val_string;
+ pval = valbuf;
+ } else {
+ value_quoted = false;
+ state = in_val_token;
+ pval = valbuf;
+ *pval++ = *cp;
+ }
+ break;
+ case in_val_string:
+ if (pval == NULL)
+ /* don't update end here, leave at value start */
+ return JSON_ERR_NULLPTR;
+ if (*cp == '\\')
+ state = in_escape;
+ else if (*cp == '"') {
+ *pval++ = '\0';
+ json_debug_trace((1, "Collected string value %s\n", valbuf));
+ state = post_val;
+ } else if (pval > valbuf + JSON_VAL_MAX - 1
+ || pval > valbuf + maxlen) {
+ json_debug_trace((1, "String value too long.\n"));
+ /* don't update end here, leave at value start */
+ return JSON_ERR_STRLONG; /* */
+ } else
+ *pval++ = *cp;
+ break;
+ case in_escape:
+ if (pval == NULL)
+ /* don't update end here, leave at value start */
+ return JSON_ERR_NULLPTR;
+ else if (pval > valbuf + JSON_VAL_MAX - 1
+ || pval > valbuf + maxlen) {
+ json_debug_trace((1, "String value too long.\n"));
+ /* don't update end here, leave at value start */
+ return JSON_ERR_STRLONG; /* */
+ }
+ switch (*cp) {
+ case 'b':
+ *pval++ = '\b';
+ break;
+ case 'f':
+ *pval++ = '\f';
+ break;
+ case 'n':
+ *pval++ = '\n';
+ break;
+ case 'r':
+ *pval++ = '\r';
+ break;
+ case 't':
+ *pval++ = '\t';
+ break;
+ case 'u':
+ cp++; /* skip the 'u' */
+ for (n = 0; n < 4 && isxdigit(*cp); n++)
+ uescape[n] = *cp++;
+ uescape[n] = '\0'; /* terminate */
+ --cp;
+ /* ECMA-404 says JSON \u must have 4 hex digits */
+ if ((4 != n) || (1 != sscanf(uescape, "%4x", &u))) {
+ return JSON_ERR_BADSTRING;
+ }
+ *pval++ = (unsigned char)u; /* truncate values above 0xff */
+ break;
+ default: /* handles double quote and solidus */
+ *pval++ = *cp;
+ break;
+ }
+ state = in_val_string;
+ break;
+ case in_val_token:
+ if (pval == NULL)
+ /* don't update end here, leave at value start */
+ return JSON_ERR_NULLPTR;
+ if (isspace((unsigned char) *cp) || *cp == ',' || *cp == '}') {
+ *pval = '\0';
+ json_debug_trace((1, "Collected token value %s.\n", valbuf));
+ state = post_val;
+ if (*cp == '}' || *cp == ',')
+ --cp;
+ } else if (pval > valbuf + JSON_VAL_MAX - 1) {
+ json_debug_trace((1, "Token value too long.\n"));
+ /* don't update end here, leave at value start */
+ return JSON_ERR_TOKLONG;
+ } else
+ *pval++ = *cp;
+ break;
+ case post_val:
+ // Ignore whitespace after either string or token values.
+ if (isspace(*cp)) {
+ while (*cp != '\0' && isspace((unsigned char) *cp)) {
+ ++cp;
+ }
+ json_debug_trace((1, "Skipped trailing whitespace: value \"%s\"\n", valbuf));
+ }
+ /*
+ * We know that cursor points at the first spec matching
+ * the current attribute. We don't know that it's *the*
+ * correct spec; our dialect allows there to be any number
+ * of adjacent ones with the same attrname but different
+ * types. Here's where we try to seek forward for a
+ * matching type/attr pair if we're not looking at one.
+ */
+ for (;;) {
+ int seeking = cursor->type;
+ if (value_quoted && (cursor->type == t_string
+ || cursor->type == t_time))
+ break;
+ if ((strcmp(valbuf, "true")==0 || strcmp(valbuf, "false")==0
+ || isdigit((unsigned char) valbuf[0]))
+ && seeking == t_boolean)
+ break;
+ if (isdigit((unsigned char) valbuf[0])) {
+ bool decimal = strchr(valbuf, '.') != NULL;
+ if (decimal && seeking == t_real)
+ break;
+ if (!decimal && (seeking == t_integer
+ || seeking == t_uinteger))
+ break;
+ }
+ if (cursor[1].attribute==NULL) /* out of possiblities */
+ break;
+ if (strcmp(cursor[1].attribute, attrbuf)!=0)
+ break;
+ ++cursor;
+ }
+ if (value_quoted
+ && (cursor->type != t_string && cursor->type != t_character
+ && cursor->type != t_check && cursor->type != t_time
+ && cursor->type != t_ignore && cursor->map == 0)) {
+ json_debug_trace((1, "Saw quoted value when expecting"
+ " non-string.\n"));
+ return JSON_ERR_QNONSTRING;
+ }
+ if (!value_quoted
+ && (cursor->type == t_string || cursor->type == t_check
+ || cursor->type == t_time || cursor->map != 0)) {
+ json_debug_trace((1, "Didn't see quoted value when expecting"
+ " string.\n"));
+ return JSON_ERR_NONQSTRING;
+ }
+ if (cursor->map != 0) {
+ for (mp = cursor->map; mp->name != NULL; mp++)
+ if (strcmp(mp->name, valbuf) == 0) {
+ goto foundit;
+ }
+ json_debug_trace((1, "Invalid enumerated value string \"%s\".\n",
+ valbuf));
+ return JSON_ERR_BADENUM;
+ foundit:
+ (void)snprintf(valbuf, sizeof(valbuf), "%d", mp->value);
+ }
+ lptr = json_target_address(cursor, parent, offset);
+ if (lptr != NULL)
+ switch (cursor->type) {
+ case t_integer:
+ {
+ int tmp = atoi(valbuf);
+ memcpy(lptr, &tmp, sizeof(int));
+ }
+ break;
+ case t_uinteger:
+ {
+ unsigned int tmp = (unsigned int)atoi(valbuf);
+ memcpy(lptr, &tmp, sizeof(unsigned int));
+ }
+ break;
+ case t_short:
+ {
+ short tmp = atoi(valbuf);
+ memcpy(lptr, &tmp, sizeof(short));
+ }
+ break;
+ case t_ushort:
+ {
+ unsigned short tmp = (unsigned int)atoi(valbuf);
+ memcpy(lptr, &tmp, sizeof(unsigned short));
+ }
+ break;
+ case t_time:
+#ifdef TIME_ENABLE
+ {
+ double tmp = iso8601_to_unix(valbuf);
+ memcpy(lptr, &tmp, sizeof(double));
+ }
+#endif /* TIME_ENABLE */
+ break;
+ case t_real:
+ {
+ double tmp = atof(valbuf);
+ memcpy(lptr, &tmp, sizeof(double));
+ }
+ break;
+ case t_string:
+ if (parent != NULL
+ && parent->element_type != t_structobject
+ && offset > 0)
+ return JSON_ERR_NOPARSTR;
+ else {
+ size_t vl = strlen(valbuf), cl = cursor->len-1;
+ memset(lptr, '\0', cl);
+ memcpy(lptr, valbuf, vl < cl ? vl : cl);
+ }
+ break;
+ case t_boolean:
+ {
+ bool tmp = (strcmp(valbuf, "true") == 0 || strtol(valbuf, NULL, 0));
+ memcpy(lptr, &tmp, sizeof(bool));
+ }
+ break;
+ case t_character:
+ if (strlen(valbuf) > 1)
+ /* don't update end here, leave at value start */
+ return JSON_ERR_STRLONG;
+ else
+ lptr[0] = valbuf[0];
+ break;
+ case t_ignore: /* silences a compiler warning */
+ case t_object: /* silences a compiler warning */
+ case t_structobject:
+ case t_array:
+ break;
+ case t_check:
+ if (strcmp(cursor->dflt.check, valbuf) != 0) {
+ json_debug_trace((1, "Required attribute value %s"
+ " not present.\n",
+ cursor->dflt.check));
+ /* don't update end here, leave at start of attribute */
+ return JSON_ERR_CHECKFAIL;
+ }
+ break;
+ }
+ __attribute__ ((fallthrough));
+ case post_element:
+ if (isspace((unsigned char) *cp))
+ continue;
+ else if (*cp == ',')
+ state = await_attr;
+ else if (*cp == '}') {
+ ++cp;
+ goto good_parse;
+ } else {
+ json_debug_trace((1, "Garbage while expecting comma or }\n"));
+ if (end != NULL)
+ *end = cp;
+ return JSON_ERR_BADTRAIL;
+ }
+ break;
+ }
+ }
+
+ good_parse:
+ /* in case there's another object following, consume trailing WS */
+ while (*cp != '\0' && isspace((unsigned char) *cp))
+ ++cp;
+ if (end != NULL)
+ *end = cp;
+ json_debug_trace((1, "JSON parse ends.\n"));
+ return 0;
+}
+
+int json_read_array(const char *cp, const struct json_array_t *arr,
+ const char **end)
+{
+ int substatus, offset, arrcount;
+ char *tp;
+
+ if (end != NULL)
+ *end = NULL; /* give it a well-defined value on parse failure */
+
+ json_debug_trace((1, "Entered json_read_array()\n"));
+
+ while (isspace((unsigned char) *cp))
+ cp++;
+ if (*cp != '[') {
+ json_debug_trace((1, "Didn't find expected array start\n"));
+ return JSON_ERR_ARRAYSTART;
+ } else
+ cp++;
+
+ tp = arr->arr.strings.store;
+ arrcount = 0;
+
+ /* Check for empty array */
+ while (isspace((unsigned char) *cp))
+ cp++;
+ if (*cp == ']')
+ goto breakout;
+
+ for (offset = 0; offset < arr->maxlen; offset++) {
+ char *ep = NULL;
+ json_debug_trace((1, "Looking at %s\n", cp));
+ switch (arr->element_type) {
+ case t_string:
+ if (isspace((unsigned char) *cp))
+ cp++;
+ if (*cp != '"')
+ return JSON_ERR_BADSTRING;
+ else
+ ++cp;
+ arr->arr.strings.ptrs[offset] = tp;
+ for (; tp - arr->arr.strings.store < arr->arr.strings.storelen;
+ tp++)
+ if (*cp == '"') {
+ ++cp;
+ *tp++ = '\0';
+ goto stringend;
+ } else if (*cp == '\0') {
+ json_debug_trace((1,
+ "Bad string syntax in string list.\n"));
+ return JSON_ERR_BADSTRING;
+ } else {
+ *tp = *cp++;
+ }
+ json_debug_trace((1, "Bad string syntax in string list.\n"));
+ return JSON_ERR_BADSTRING;
+ stringend:
+ break;
+ case t_object:
+ case t_structobject:
+ substatus =
+ json_internal_read_object(cp, arr->arr.objects.subtype, arr,
+ offset, &cp);
+ if (substatus != 0) {
+ if (end != NULL)
+ end = &cp;
+ return substatus;
+ }
+ break;
+ case t_integer:
+ arr->arr.integers.store[offset] = (int)strtol(cp, &ep, 0);
+ if (ep == cp)
+ return JSON_ERR_BADNUM;
+ else
+ cp = ep;
+ break;
+ case t_uinteger:
+ arr->arr.uintegers.store[offset] = (unsigned int)strtoul(cp,
+ &ep, 0);
+ if (ep == cp)
+ return JSON_ERR_BADNUM;
+ else
+ cp = ep;
+ break;
+ case t_short:
+ arr->arr.shorts.store[offset] = (short)strtol(cp, &ep, 0);
+ if (ep == cp)
+ return JSON_ERR_BADNUM;
+ else
+ cp = ep;
+ break;
+ case t_ushort:
+ arr->arr.ushorts.store[offset] = (unsigned short)strtol(cp, &ep, 0);
+ if (ep == cp)
+ return JSON_ERR_BADNUM;
+ else
+ cp = ep;
+ break;
+#ifdef TIME_ENABLE
+ case t_time:
+ if (*cp != '"')
+ return JSON_ERR_BADSTRING;
+ else
+ ++cp;
+ arr->arr.reals.store[offset] = iso8601_to_unix((char *)cp);
+ if (arr->arr.reals.store[offset] >= HUGE_VAL)
+ return JSON_ERR_BADNUM;
+ while (*cp && *cp != '"')
+ cp++;
+ if (*cp != '"')
+ return JSON_ERR_BADSTRING;
+ else
+ ++cp;
+ break;
+#endif /* TIME_ENABLE */
+ case t_real:
+ arr->arr.reals.store[offset] = strtod(cp, &ep);
+ if (ep == cp)
+ return JSON_ERR_BADNUM;
+ else
+ cp = ep;
+ break;
+ case t_boolean:
+ if (str_starts_with(cp, "true")) {
+ arr->arr.booleans.store[offset] = true;
+ cp += 4;
+ }
+ else if (str_starts_with(cp, "false")) {
+ arr->arr.booleans.store[offset] = false;
+ cp += 5;
+ } else {
+ int val = strtol(cp, &ep, 0);
+ if (ep == cp)
+ return JSON_ERR_BADNUM;
+ else {
+ arr->arr.booleans.store[offset] = (bool) val;
+ cp = ep;
+ }
+ }
+ break;
+ case t_character:
+ case t_array:
+ case t_check:
+ case t_ignore:
+ json_debug_trace((1, "Invalid array subtype.\n"));
+ return JSON_ERR_SUBTYPE;
+ }
+ arrcount++;
+ if (isspace((unsigned char) *cp))
+ cp++;
+ if (*cp == ']') {
+ json_debug_trace((1, "End of array found.\n"));
+ goto breakout;
+ } else if (*cp == ',')
+ cp++;
+ else {
+ json_debug_trace((1, "Bad trailing syntax on array.\n"));
+ return JSON_ERR_BADSUBTRAIL;
+ }
+ }
+ json_debug_trace((1, "Too many elements in array.\n"));
+ if (end != NULL)
+ *end = cp;
+ return JSON_ERR_SUBTOOLONG;
+ breakout:
+ if (arr->count != NULL)
+ *(arr->count) = arrcount;
+ if (end != NULL)
+ *end = cp;
+ json_debug_trace((1, "leaving json_read_array() with %d elements\n",
+ arrcount));
+ return 0;
+}
+
+int json_read_object(const char *cp, const struct json_attr_t *attrs,
+ const char **end)
+{
+ int st;
+
+ json_debug_trace((1, "json_read_object() sees '%s'\n", cp));
+ st = json_internal_read_object(cp, attrs, NULL, 0, end);
+ return st;
+}
+
+const char *json_error_string(int err)
+{
+ const char *errors[] = {
+ "unknown error while parsing JSON",
+ "non-whitespace when expecting object start",
+ "non-whitespace when expecting attribute start",
+ "unknown attribute name",
+ "attribute name too long",
+ "saw [ when not expecting array",
+ "array element specified, but no [",
+ "string value too long",
+ "token value too long",
+ "garbage while expecting comma or } or ]",
+ "didn't find expected array start",
+ "error while parsing object array",
+ "too many array elements",
+ "garbage while expecting array comma",
+ "unsupported array element type",
+ "error while string parsing",
+ "check attribute not matched",
+ "can't support strings in parallel arrays",
+ "invalid enumerated value",
+ "saw quoted value when expecting nonstring",
+ "didn't see quoted value when expecting string",
+ "other data conversion error",
+ "unexpected null value or attribute pointer",
+ "object element specified, but no {",
+ };
+
+ if (err <= 0 || err >= (int)(sizeof(errors) / sizeof(errors[0])))
+ return errors[0];
+ else
+ return errors[err];
+}
+
+/* end */
diff --git a/mjson.h b/mjson.h
new file mode 100644
index 0000000..dd36e15
--- /dev/null
+++ b/mjson.h
@@ -0,0 +1,158 @@
+/* Structures for JSON parsing using only fixed-extent memory
+ *
+ * This file is Copyright (c) 2010 by the GPSD project
+ * SPDX-License-Identifier: BSD-2-clause
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <ctype.h>
+#ifdef TIME_ENABLE
+#include <time.h>
+#endif /* TIME_ENABLE */
+
+typedef enum {t_integer, t_uinteger, t_real,
+ t_string, t_boolean, t_character,
+ t_time,
+ t_object, t_structobject, t_array,
+ t_check, t_ignore,
+ t_short, t_ushort}
+ json_type;
+
+struct json_enum_t {
+ char *name;
+ int value;
+};
+
+struct json_array_t {
+ json_type element_type;
+ union {
+ struct {
+ const struct json_attr_t *subtype;
+ char *base;
+ size_t stride;
+ } objects;
+ struct {
+ char **ptrs;
+ char *store;
+ int storelen;
+ } strings;
+ struct {
+ int *store;
+ } integers;
+ struct {
+ unsigned int *store;
+ } uintegers;
+ struct {
+ short *store;
+ } shorts;
+ struct {
+ unsigned short *store;
+ } ushorts;
+ struct {
+ double *store;
+ } reals;
+ struct {
+ bool *store;
+ } booleans;
+ } arr;
+ int *count, maxlen;
+};
+
+struct json_attr_t {
+ char *attribute;
+ json_type type;
+ union {
+ int *integer;
+ unsigned int *uinteger;
+ short *shortint;
+ unsigned short *ushortint;
+ double *real;
+ char *string;
+ bool *boolean;
+ char *character;
+ const struct json_attr_t *attrs;
+ const struct json_array_t array;
+ size_t offset;
+ } addr;
+ union {
+ int integer;
+ unsigned int uinteger;
+ short shortint;
+ unsigned short ushortint;
+ double real;
+ bool boolean;
+ char character;
+ char *check;
+ } dflt;
+ size_t len;
+ const struct json_enum_t *map;
+ bool nodefault;
+};
+
+#define JSON_ATTR_MAX 31 /* max chars in JSON attribute name */
+#define JSON_VAL_MAX 512 /* max chars in JSON value part */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+int json_read_object(const char *, const struct json_attr_t *,
+ const char **);
+int json_read_array(const char *, const struct json_array_t *,
+ const char **);
+const char *json_error_string(int);
+
+#ifdef TIME_ENABLE
+extern time_t timegm(struct tm *tm);
+#endif /* TIME_ENABLE */
+
+void json_enable_debug(int, FILE *);
+#ifdef __cplusplus
+}
+#endif
+
+#define JSON_ERR_OBSTART 1 /* non-WS when expecting object start */
+#define JSON_ERR_ATTRSTART 2 /* non-WS when expecting attrib start */
+#define JSON_ERR_BADATTR 3 /* unknown attribute name */
+#define JSON_ERR_ATTRLEN 4 /* attribute name too long */
+#define JSON_ERR_NOARRAY 5 /* saw [ when not expecting array */
+#define JSON_ERR_NOBRAK 6 /* array element specified, but no [ */
+#define JSON_ERR_STRLONG 7 /* string value too long */
+#define JSON_ERR_TOKLONG 8 /* token value too long */
+#define JSON_ERR_BADTRAIL 9 /* garbage while expecting comma or } or ] */
+#define JSON_ERR_ARRAYSTART 10 /* didn't find expected array start */
+#define JSON_ERR_OBJARR 11 /* error while parsing object array */
+#define JSON_ERR_SUBTOOLONG 12 /* too many array elements */
+#define JSON_ERR_BADSUBTRAIL 13 /* garbage while expecting array comma */
+#define JSON_ERR_SUBTYPE 14 /* unsupported array element type */
+#define JSON_ERR_BADSTRING 15 /* error while string parsing */
+#define JSON_ERR_CHECKFAIL 16 /* check attribute not matched */
+#define JSON_ERR_NOPARSTR 17 /* can't support strings in parallel arrays */
+#define JSON_ERR_BADENUM 18 /* invalid enumerated value */
+#define JSON_ERR_QNONSTRING 19 /* saw quoted value when expecting nonstring */
+#define JSON_ERR_NONQSTRING 19 /* didn't see quoted value when expecting string */
+#define JSON_ERR_MISC 20 /* other data conversion error */
+#define JSON_ERR_BADNUM 21 /* error while parsing a numerical argument */
+#define JSON_ERR_NULLPTR 22 /* unexpected null value or attribute pointer */
+#define JSON_ERR_NOCURLY 23 /* object element specified, but no { */
+
+/*
+ * Use the following macros to declare template initializers for structobject
+ * arrays. Writing the equivalents out by hand is error-prone.
+ *
+ * STRUCTOBJECT takes a structure name s, and a fieldname f in s.
+ *
+ * STRUCTARRAY takes the name of a structure array, a pointer to a an
+ * initializer defining the subobject type, and the address of an integer to
+ * store the length in.
+ */
+#define STRUCTOBJECT(s, f) .addr.offset = offsetof(s, f)
+#define STRUCTARRAY(a, e, n) \
+ .addr.array.element_type = t_structobject, \
+ .addr.array.arr.objects.subtype = e, \
+ .addr.array.arr.objects.base = (char*)a, \
+ .addr.array.arr.objects.stride = sizeof(a[0]), \
+ .addr.array.count = n, \
+ .addr.array.maxlen = (int)(sizeof(a)/sizeof(a[0]))
+
+/* json.h ends here */
To view, visit change 75647. To unsubscribe, or for help writing mail filters, visit settings.