Stefan Reinauer has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/75647?usp=email )
Change subject: Plain mjason 1.6 ......................................................................
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... */ +#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 */