[ovs-dev] [PATCH] Implement JSON parsing and serialization.
Ben Pfaff
blp at nicira.com
Thu Sep 24 17:35:25 UTC 2009
This will be used by the upcoming Open vSwitch configuration database.
---
This is really a "pre-review", in that I am not proposing to push it to
"master" or "citrix" any time soon. Instead, it is a building block
for the database. Perhaps it would be appropriate to push it to a new
"ovsdb" topic branch.
lib/automake.mk | 4 +
lib/dynamic-string.c | 25 ++
lib/dynamic-string.h | 1 +
lib/json.c | 1043 ++++++++++++++++++++++++++++++++++++++++++++++++++
lib/json.h | 70 ++++
lib/shash.c | 6 +
lib/shash.h | 1 +
lib/unicode.c | 38 ++
lib/unicode.h | 53 +++
lib/util.c | 55 +++
lib/util.h | 4 +
tests/.gitignore | 1 +
tests/automake.mk | 5 +
tests/json.at | 211 ++++++++++
tests/test-json.c | 45 +++
tests/testsuite.at | 1 +
16 files changed, 1563 insertions(+), 0 deletions(-)
create mode 100644 lib/json.c
create mode 100644 lib/json.h
create mode 100644 lib/unicode.c
create mode 100644 lib/unicode.h
create mode 100644 tests/json.at
create mode 100644 tests/test-json.c
diff --git a/lib/automake.mk b/lib/automake.mk
index 9ba513a..2f7b27d 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -49,6 +49,8 @@ lib_libopenvswitch_a_SOURCES = \
lib/hash.h \
lib/hmap.c \
lib/hmap.h \
+ lib/json.c \
+ lib/json.h \
lib/leak-checker.c \
lib/leak-checker.h \
lib/learning-switch.c \
@@ -105,6 +107,8 @@ lib_libopenvswitch_a_SOURCES = \
lib/type-props.h \
lib/unixctl.c \
lib/unixctl.h \
+ lib/unicode.c \
+ lib/unicode.h \
lib/util.c \
lib/util.h \
lib/valgrind.h \
diff --git a/lib/dynamic-string.c b/lib/dynamic-string.c
index 9684ffa..531e53b 100644
--- a/lib/dynamic-string.c
+++ b/lib/dynamic-string.c
@@ -71,6 +71,31 @@ ds_put_char(struct ds *ds, char c)
*ds_put_uninit(ds, 1) = c;
}
+/* Appends unicode code point 'uc' to 'ds' in UTF-8 encoding. */
+void
+ds_put_utf8(struct ds *ds, int uc)
+{
+ if (uc <= 0x7f) {
+ ds_put_char(ds, uc);
+ } else if (uc <= 0x7ff) {
+ ds_put_char(ds, 0xc0 | (uc >> 6));
+ ds_put_char(ds, 0x80 | (uc & 0x3f));
+ } else if (uc <= 0xffff) {
+ ds_put_char(ds, 0xe0 | (uc >> 12));
+ ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+ ds_put_char(ds, 0x80 | (uc & 0x3f));
+ } else if (uc <= 0x10ffff) {
+ ds_put_char(ds, 0xf0 | (uc >> 18));
+ ds_put_char(ds, 0x80 | ((uc >> 12) & 0x3f));
+ ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+ ds_put_char(ds, 0x80 | (uc & 0x3f));
+ } else {
+ /* Invalid code point. Insert the Unicode general substitute
+ * REPLACEMENT CHARACTER. */
+ ds_put_utf8(ds, 0xfffd);
+ }
+}
+
void
ds_put_char_multiple(struct ds *ds, char c, size_t n)
{
diff --git a/lib/dynamic-string.h b/lib/dynamic-string.h
index a44e0b3..01b93c4 100644
--- a/lib/dynamic-string.h
+++ b/lib/dynamic-string.h
@@ -40,6 +40,7 @@ void ds_truncate(struct ds *, size_t new_length);
void ds_reserve(struct ds *, size_t min_length);
char *ds_put_uninit(struct ds *, size_t n);
void ds_put_char(struct ds *, char);
+void ds_put_utf8(struct ds *, int uc);
void ds_put_char_multiple(struct ds *, char, size_t n);
void ds_put_buffer(struct ds *, const char *, size_t n);
void ds_put_cstr(struct ds *, const char *);
diff --git a/lib/json.c b/lib/json.c
new file mode 100644
index 0000000..1f331f3
--- /dev/null
+++ b/lib/json.c
@@ -0,0 +1,1043 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <string.h>
+
+#include "dynamic-string.h"
+#include "shash.h"
+#include "unicode.h"
+#include "util.h"
+
+/* The type of a JSON token. */
+enum json_token_type {
+ T_EOF = 0,
+ T_BEGIN_ARRAY = '[',
+ T_END_ARRAY = ']',
+ T_BEGIN_OBJECT = '{',
+ T_END_OBJECT = '}',
+ T_NAME_SEPARATOR = ':',
+ T_VALUE_SEPARATOR = ',',
+ T_FALSE = UCHAR_MAX + 1,
+ T_NULL,
+ T_TRUE,
+ T_INTEGER,
+ T_REAL,
+ T_STRING
+};
+
+/* A JSON token.
+ *
+ * RFC 4627 doesn't define a lexical structure for JSON but I believe this to
+ * be compliant with the standard.
+ */
+struct json_token {
+ enum json_token_type type;
+ union {
+ double real;
+ long long int integer;
+ char *string;
+ } u;
+};
+
+/* A JSON parser. */
+struct json_parser {
+ /* Lexical analysis. */
+ struct json_token token; /* Token currently being parsed. */
+ const char *input; /* Beginning of current input chunk. */
+ const char *position; /* First unconsumed byte of input chunk. */
+ const char *end; /* Just past last byte of input chunk. */
+ int (*fill)(void *aux, const char **inputp); /* Call to get more input. */
+ void *fill_aux; /* Argument to fill function. */
+
+ /* Error handling. */
+ char *error; /* Error message, if any, null if none yet. */
+#define JSON_MAX_DEPTH 1000
+ int depth; /* Current depth, to avoid stack exhaustion. */
+#define JSON_MAX_SIZE 1000000
+ size_t size; /* Number of bytes lexed, to avoid heap
+ * exhaustion. */
+};
+
+static struct json *json_create(enum json_type type);
+
+static void json_error(struct json_parser *p, const char *format, ...)
+ PRINTF_FORMAT(2, 3);
+
+/* Functions for manipulating struct json. */
+
+static void json_destroy_object(struct shash *object);
+static void json_destroy_array(struct json_array *array);
+
+/* Frees 'json' and everything it points to, recursively. */
+void
+json_destroy(struct json *json)
+{
+ if (json) {
+ switch (json->type) {
+ case JSON_OBJECT:
+ json_destroy_object(json->u.object);
+ break;
+
+ case JSON_ARRAY:
+ json_destroy_array(&json->u.array);
+ break;
+
+ case JSON_STRING:
+ free(json->u.string);
+ break;
+
+ case JSON_NULL:
+ case JSON_FALSE:
+ case JSON_TRUE:
+ case JSON_INTEGER:
+ case JSON_REAL:
+ break;
+ }
+ free(json);
+ }
+}
+
+static void
+json_destroy_object(struct shash *object)
+{
+ struct shash_node *node, *next;
+
+ SHASH_FOR_EACH_SAFE (node, next, object) {
+ struct json *value = node->data;
+
+ json_destroy(value);
+ shash_delete(object, node);
+ }
+ free(object);
+}
+
+static void
+json_destroy_array(struct json_array *array)
+{
+ size_t i;
+
+ for (i = 0; i < array->n; i++) {
+ json_destroy(array->elems[i]);
+ }
+ free(array->elems);
+}
+
+/* Tokens. */
+
+static void
+json_token_init(struct json_token *token)
+{
+ token->type = T_EOF;
+}
+
+static void
+json_token_destroy(struct json_token *token)
+{
+ if (token->type == T_STRING) {
+ free(token->u.string);
+ }
+}
+
+static char *
+json_token_steal_string(struct json_token *token)
+{
+ assert(token->type == T_STRING);
+ token->type = T_EOF;
+ return token->u.string;
+}
+
+/* Lexical analysis. */
+
+/* Fills 'p''s input buffer with new input from our data source. Returns true
+ * if successful, false if we're out of input or an error occurs. */
+static bool
+json_fill(struct json_parser *p)
+{
+ const char *input;
+ int n;
+
+ n = p->fill(p->fill_aux, &input);
+ if (n > 0) {
+ p->input = p->position = input;
+ p->end = p->input + n;
+
+ p->size += n;
+ if (p->size < JSON_MAX_SIZE) {
+ return true;
+ } else {
+ json_error(p, "input exceeds max size %d", JSON_MAX_SIZE);
+ }
+ }
+ p->input = p->position = p->end = NULL;
+ return false;
+}
+
+/* Returns the next byte of input on 'p', or 0 at end of input. */
+static int
+json_getc(struct json_parser *p)
+{
+ if (p->position < p->end || (p->input && json_fill(p))) {
+ return *p->position++;
+ } else {
+ return 0;
+ }
+}
+
+/* Puts byte 'c' back onto the input queue. 'c' must be the previous byte
+ * read from 'p'. json_ungetc() may not be called twice for a given 'p'
+ * without an intervening call to json_getc(p). */
+static void
+json_ungetc(struct json_parser *p, int c)
+{
+ assert(p->position > p->input);
+ assert(p->position[-1] == c);
+ p->position--;
+}
+
+static bool
+json_lex_keyword(struct json_parser *p, const char *keyword,
+ enum json_token_type type, struct json_token *token)
+{
+ int c, i;
+
+ /* Make sure that all of the characters in 'keyword' are there. Skip
+ * keyword[0] since the caller already read that. */
+ for (i = 1; keyword[i]; i++) {
+ if (json_getc(p) != keyword[i]) {
+ goto error;
+ }
+ }
+
+ /* Make sure that 'keyword' is followed by a non-alphanumeric character.
+ * Strictly speaking this check is unnecessary, but it should make for
+ * better error messages. */
+ c = json_getc(p);
+ if (isalnum(c)) {
+ goto error;
+ }
+ json_ungetc(p, c);
+
+ token->type = type;
+ return true;
+
+error:
+ json_error(p, "invalid keyword");
+ return false;
+}
+
+static bool
+json_lex_digits(struct json_parser *p, struct ds *s, int *c)
+{
+ int n;
+ for (n = 0; isdigit(*c); n++) {
+ ds_put_char(s, *c);
+ *c = json_getc(p);
+ }
+ return n > 0;
+}
+
+static bool
+json_lex_number(struct json_parser *p, int c, struct json_token *token)
+{
+ struct ds s;
+ double d;
+
+ ds_init(&s);
+ ds_reserve(&s, 31);
+
+ /* Leading minus sign. */
+ if (c == '-') {
+ ds_put_char(&s, c);
+ c = json_getc(p);
+ }
+
+ /* At least one integer digit, but 0 may not be used as a leading digit for
+ * a longer number. */
+ if (c == '0') {
+ ds_put_char(&s, c);
+ c = json_getc(p);
+ if (isdigit(c)) {
+ json_error(p, "leading zeros not allowed");
+ goto error;
+
+ }
+ } else if (!json_lex_digits(p, &s, &c)) {
+ goto error;
+ }
+
+ /* Optional fraction. */
+ if (c == '.') {
+ ds_put_char(&s, c);
+ c = json_getc(p);
+ if (!json_lex_digits(p, &s, &c)) {
+ goto error;
+ }
+ }
+
+ /* Optional exponent. */
+ if (c == 'e' || c == 'E') {
+ ds_put_char(&s, c);
+ c = json_getc(p);
+ if (c == '+' || c == '-') {
+ ds_put_char(&s, c);
+ c = json_getc(p);
+ }
+ if (!json_lex_digits(p, &s, &c)) {
+ goto error;
+ }
+ }
+ json_ungetc(p, c);
+
+ /* Parse number. */
+ if (!str_to_double(ds_cstr(&s), &d)) {
+ json_error(p, "number \"%s\" outside valid range", ds_cstr(&s));
+ return false;
+ }
+ if (d >= LLONG_MIN && d <= LLONG_MAX && (long long int) d == d) {
+ token->type = T_INTEGER;
+ token->u.integer = d;
+ } else {
+ token->type = T_REAL;
+ token->u.real = d;
+ }
+
+ ds_destroy(&s);
+
+ return true;
+
+error:
+ json_error(p, "invalid numeric format");
+ ds_destroy(&s);
+ return false;
+}
+
+static int
+json_lex_4hex(struct json_parser *p)
+{
+ int value, i;
+
+ value = 0;
+ for (i = 0; i < 4; i++) {
+ int c = json_getc(p);
+ if (!isxdigit(c)) {
+ json_error(p, "malformed \\u escape");
+ return 0;
+ }
+ value = (value << 4) | hexit_value(c);
+ }
+ if (!value) {
+ json_error(p, "null bytes not supported in quoted strings");
+ return 0;
+ }
+ return value;
+}
+
+static int
+json_lex_unicode(struct json_parser *p)
+{
+ int c0, c1;
+
+ c0 = json_lex_4hex(p);
+ if (!uc_is_leading_surrogate(c0)) {
+ return c0;
+ }
+
+ if (json_getc(p) != '\\' || json_getc(p) != 'u') {
+ json_error(p, "malformed escaped surrogate pair");
+ return 0;
+ }
+
+ c1 = json_lex_4hex(p);
+ if (!uc_is_trailing_surrogate(c1)) {
+ json_error(p, "second half of escaped surrogate pair is not "
+ "trailing surrogate");
+ return 0;
+ }
+
+ return utf16_decode_surrogate_pair(c0, c1);
+}
+
+static bool
+json_lex_string(struct json_parser *p, struct json_token *token)
+{
+ struct ds s;
+ int c;
+
+ ds_init(&s);
+ for (c = json_getc(p); c != '"'; c = json_getc(p)) {
+ switch (c) {
+ case 0:
+ json_error(p, "unexpected end of input in quoted string");
+ goto error;
+
+ case '\\':
+ c = json_getc(p);
+ switch (c) {
+ case '"': case '\\': case '/':
+ ds_put_char(&s, c);
+ break;
+
+ case 'b':
+ ds_put_char(&s, '\b');
+ break;
+
+ case 'f':
+ ds_put_char(&s, '\f');
+ break;
+
+ case 'n':
+ ds_put_char(&s, '\n');
+ break;
+
+ case 'r':
+ ds_put_char(&s, '\r');
+ break;
+
+ case 't':
+ ds_put_char(&s, '\t');
+ break;
+
+ case 'u':
+ c = json_lex_unicode(p);
+ if (!c) {
+ goto error;
+ }
+ ds_put_utf8(&s, c);
+ break;
+
+ default:
+ json_error(p, "bad escape \\%c", c);
+ goto error;
+ }
+ break;
+
+ default:
+ if (c >= 0x20) {
+ ds_put_char(&s, c);
+ } else {
+ json_error(p, "U+%04X must be escaped in quoted string", c);
+ goto error;
+ }
+ break;
+ }
+ }
+
+ token->type = T_STRING;
+ token->u.string = ds_steal_cstr(&s);
+ return true;
+
+error:
+ ds_destroy(&s);
+ return false;
+}
+
+/* Retrieves the next token from 'p''s input stream into p->token. Returns
+ * true if successful, false on error or at end of input. */
+static bool
+json_get_token(struct json_parser *p)
+{
+ struct json_token *token = &p->token;
+
+ json_token_destroy(token);
+ json_token_init(token);
+ for (;;) {
+ int c = json_getc(p);
+
+ switch (c) {
+ case 0:
+ token->type = T_EOF;
+ return true;
+
+ case ' ': case '\t': case '\n': case '\r':
+ break;
+
+ case 'f':
+ return json_lex_keyword(p, "false", T_FALSE, token);
+
+ case 't':
+ return json_lex_keyword(p, "true", T_TRUE, token);
+
+ case 'n':
+ return json_lex_keyword(p, "null", T_NULL, token);
+
+ case '[': case '{': case ']': case '}': case ':': case ',':
+ token->type = c;
+ return true;
+
+ case '-':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return json_lex_number(p, c, token);
+
+ case '"':
+ return json_lex_string(p, token);
+
+ default:
+ if (isprint(c)) {
+ json_error(p, "invalid character '%c'", c);
+ } else {
+ json_error(p, "invalid character U+%04x", c);
+ }
+ return false;
+ }
+ }
+}
+
+/* Parsing. */
+
+static struct json *json_parse_object(struct json_parser *);
+static struct json *json_parse_array(struct json_parser *);
+static struct json *json_parse_value(struct json_parser *);
+
+static int
+json_string_cb(void *stringp_, const char **inputp)
+{
+ const char **stringp = stringp_;
+ if (*stringp) {
+ *inputp = *stringp;
+ *stringp = NULL;
+ return strlen(*inputp);
+ } else {
+ return 0;
+ }
+}
+
+/* Parses 'string' as JSON syntax and returns a newly allocated 'struct
+ * json'. The caller must free the returned structure with json_destroy()
+ * when it is no longer needed.
+ *
+ * 'string' must be encoded in UTF-8.
+ *
+ * If 'string' is valid JSON, then the returned 'struct json' will be either an
+ * object (JSON_OBJECT) or an array (JSON_ARRAY).
+ *
+ * If 'string' is not valid JSON, then the returned 'struct json' will be a
+ * string (JSON_STRING) that describes the particular error encountered during
+ * parsing. (This is an acceptable means of error reporting because at its top
+ * level JSON must be either an object or an array; a bare string is not
+ * valid.) */
+struct json *
+json_from_string(const char *string)
+{
+ return json_from_cb(json_string_cb, &string);
+}
+
+struct json_file_aux {
+ FILE *stream;
+ char buf[BUFSIZ];
+};
+
+static int
+json_file_cb(void *aux_, const char **inputp)
+{
+ struct json_file_aux *aux = aux_;
+ size_t n = fread(aux->buf, 1, sizeof aux->buf, aux->stream);
+ *inputp = aux->buf;
+ return n;
+}
+
+/* Reads the file named 'file_name', parses its contents as JSON syntax, and
+ * returns a newly allocated 'struct json'. The caller must free the returned
+ * structure with json_destroy() when it is no longer needed.
+ *
+ * The file must be encoded in UTF-8.
+ *
+ * See json_from_string() for return value semantics.
+ */
+struct json *
+json_from_file(const char *file_name)
+{
+ struct json_file_aux aux;
+ struct json *json;
+
+ aux.stream = fopen(file_name, "r");
+ if (!aux.stream) {
+ json = json_create(JSON_STRING);
+ json->u.string = xasprintf("%s: open failed: %s", file_name,
+ strerror(errno));
+ return json;
+ }
+
+ json = json_from_cb(json_file_cb, &aux);
+
+ /* XXX report file read errors */
+ fclose(aux.stream);
+
+ return json;
+}
+
+/* Parses data obtained in chunks through a callback as JSON and returns a
+ * newly allocated 'struct json'. The caller must free the returned structure
+ * with json_destroy() when it is no longer needed.
+ *
+ * The data is obtained by calling 'cb', passing 'aux' as auxiliary data. 'cb'
+ * should obtain more data to be parsed as JSON, store a pointer to it in
+ * '*inputp', and return the number of bytes of data. 'cb' may indicate end of
+ * input or an error by returning 0.
+ *
+ * 'cb' is responsible for freeing its own data; json_from_cb() will not do it.
+ *
+ * See json_from_string() for return value semantics.
+ */
+struct json *
+json_from_cb(int (*cb)(void *aux, const char **inputp), void *aux)
+{
+ struct json_parser p;
+ struct json *json;
+
+ memset(&p, 0, sizeof p);
+ json_token_init(&p.token);
+ p.fill = cb;
+ p.fill_aux = aux;
+
+ json = NULL;
+ if (!json_fill(&p) || !json_get_token(&p)) {
+ json_error(&p, "empty input stream");
+ } else if (p.token.type == '{') {
+ json = json_parse_object(&p);
+ } else if (p.token.type == '[') {
+ json = json_parse_array(&p);
+ } else {
+ json_error(&p, "syntax error at beginning of input");
+ }
+ if (p.token.type != T_EOF) {
+ json_error(&p, "trailing garbage at end of input");
+ }
+
+ if (p.error) {
+ json_destroy(json);
+ json = json_create(JSON_STRING);
+ json->u.string = p.error;
+ } else {
+ assert(json);
+ }
+
+ json_token_destroy(&p.token);
+ return json;
+}
+
+static bool
+json_parse_member(struct json_parser *p, void *object_)
+{
+ struct json *object = object_;
+ struct json *value;
+ char *name = NULL;
+
+ /* Parse name. */
+ if (p->token.type != T_STRING) {
+ json_error(p, "syntax error parsing object expecting string");
+ goto error;
+ }
+ name = json_token_steal_string(&p->token);
+
+ /* Parse ':'. */
+ json_get_token(p);
+ if (p->token.type != ':') {
+ json_error(p, "syntax error parsing object expecting ':'");
+ goto error;
+ }
+
+ /* Parse value. */
+ json_get_token(p);
+ value = json_parse_value(p);
+ if (!value) {
+ goto error;
+ }
+
+ /* Add value to object. */
+ if (!shash_find(object->u.object, name)) {
+ shash_add(object->u.object, name, value);
+ } else {
+ /* Duplicate name. */
+ json_destroy(value);
+ }
+ free(name);
+ return true;
+
+error:
+ free(name);
+ return false;
+}
+
+static bool
+json_parse_list(struct json_parser *p, int start_token, int end_token,
+ bool (*parse_cb)(struct json_parser *p, void *aux),
+ void *aux)
+{
+ /* Skip 'start_token'. */
+ assert(p->token.type == start_token);
+ json_get_token(p);
+
+ /* Parse list elements. */
+ if (p->token.type != end_token) {
+ for (;;) {
+ if (!parse_cb(p, aux)) {
+ return false;
+ }
+
+ if (p->token.type == ',') {
+ json_get_token(p);
+ } else if (p->token.type == end_token) {
+ break;
+ } else {
+ json_error(p, "syntax error expecting '%c' or ','", end_token);
+ return false;
+ }
+ }
+ }
+
+ /* Skip 'end_token'. */
+ json_get_token(p);
+
+ return true;
+}
+
+static struct json *
+json_parse_object(struct json_parser *p)
+{
+ struct json *object;
+
+ object = xmalloc(sizeof *object);
+ object->type = JSON_OBJECT;
+ object->u.object = xmalloc(sizeof *object->u.object);
+ shash_init(object->u.object);
+
+ if (!json_parse_list(p, '{', '}', json_parse_member, object)) {
+ json_destroy(object);
+ return NULL;
+ }
+ return object;
+}
+
+struct json_array_parser {
+ struct json *array;
+ size_t n_allocated;
+};
+
+static bool
+json_parse_element(struct json_parser *p, void *ap_)
+{
+ struct json_array_parser *ap = ap_;
+ struct json_array *array = &ap->array->u.array;
+ struct json *value;
+
+ value = json_parse_value(p);
+ if (!value) {
+ return false;
+ }
+
+ if (array->n >= ap->n_allocated) {
+ array->elems = x2nrealloc(array->elems, &ap->n_allocated,
+ sizeof *array->elems);
+ }
+ array->elems[array->n++] = value;
+ return true;
+}
+
+static struct json *
+json_parse_array(struct json_parser *p)
+{
+ struct json_array_parser ap;
+ struct json_array *array;
+
+ ap.array = xmalloc(sizeof *ap.array);
+ ap.array->type = JSON_ARRAY;
+ array = &ap.array->u.array;
+ array->n = 0;
+ array->elems = NULL;
+ ap.n_allocated = 0;
+
+ if (!json_parse_list(p, '[', ']', json_parse_element, &ap)) {
+ json_destroy(ap.array);
+ return NULL;
+ }
+ if (ap.n_allocated > array->n) {
+ array->elems = xrealloc(array->elems, sizeof *array->elems * array->n);
+ }
+ return ap.array;
+}
+
+static struct json *
+json_parse_keyword(struct json_parser *p, enum json_type type)
+{
+ json_get_token(p);
+ return json_create(type);
+}
+
+
+static struct json *
+json_parse_value(struct json_parser *p)
+{
+ struct json *json;
+
+ if (p->depth++ > JSON_MAX_DEPTH) {
+ json_error(p, "input exceeds maximum nesting depth %d",
+ JSON_MAX_DEPTH);
+ return NULL;
+ }
+
+ switch (p->token.type) {
+ case T_FALSE:
+ json = json_parse_keyword(p, JSON_FALSE);
+ break;
+
+ case T_NULL:
+ json = json_parse_keyword(p, JSON_NULL);
+ break;
+
+ case T_TRUE:
+ json = json_parse_keyword(p, JSON_TRUE);
+ break;
+
+ case '{':
+ json = json_parse_object(p);
+ break;
+
+ case '[':
+ json = json_parse_array(p);
+ break;
+
+ case T_INTEGER:
+ json = json_create(JSON_INTEGER);
+ json->u.integer = p->token.u.integer;
+ json_get_token(p);
+ break;
+
+ case T_REAL:
+ json = json_create(JSON_REAL);
+ json->u.real = p->token.u.real;
+ json_get_token(p);
+ break;
+
+ case T_STRING:
+ json = json_create(JSON_STRING);
+ json->u.string = json_token_steal_string(&p->token);
+ json_get_token(p);
+ break;
+
+ case T_EOF:
+ case '}':
+ case ']':
+ case ':':
+ case ',':
+ default:
+ json_error(p, "syntax error expecting value");
+ json = NULL;
+ break;
+ }
+
+ p->depth--;
+
+ return json;
+}
+
+static struct json *
+json_create(enum json_type type)
+{
+ struct json *json = xmalloc(sizeof *json);
+ json->type = type;
+ return json;
+}
+
+static void
+json_error(struct json_parser *p, const char *format, ...)
+{
+ if (!p->error) {
+ va_list args;
+
+ va_start(args, format);
+ p->error = xvasprintf(format, args);
+ va_end(args);
+ }
+}
+
+static void json_to_ds(const struct json *, struct ds *);
+static void json_object_to_ds(const struct shash *object, struct ds *);
+static void json_array_to_ds(const struct json_array *array, struct ds *);
+static void json_string_to_ds(const char *string, struct ds *);
+
+/* Converts 'json' to a string in JSON format, encoded in UTF-8, and returns
+ * that string. The caller is responsible for freeing the returned string,
+ * with free(), when it is no longer needed.
+ *
+ * The returned string is valid JSON only if 'json' represents an array or an
+ * object, since a bare literal does not satisfy the JSON grammar.
+ *
+ * Members of objects in the output are sorted in bytewise lexicographic order
+ * for reproducibility. */
+char *
+json_to_string(const struct json *json)
+{
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ json_to_ds(json, &ds);
+ return ds_steal_cstr(&ds);
+}
+
+static void
+json_to_ds(const struct json *json, struct ds *ds)
+{
+ switch (json->type) {
+ case JSON_NULL:
+ ds_put_cstr(ds, "null");
+ break;
+
+ case JSON_FALSE:
+ ds_put_cstr(ds, "false");
+ break;
+
+ case JSON_TRUE:
+ ds_put_cstr(ds, "true");
+ break;
+
+ case JSON_OBJECT:
+ json_object_to_ds(json->u.object, ds);
+ break;
+
+ case JSON_ARRAY:
+ json_array_to_ds(&json->u.array, ds);
+ break;
+
+ case JSON_INTEGER:
+ ds_put_format(ds, "%lld", json->u.integer);
+ break;
+
+ case JSON_REAL:
+ ds_put_format(ds, "%.*g", DBL_DIG, json->u.real);
+ break;
+
+ case JSON_STRING:
+ json_string_to_ds(json->u.string, ds);
+ break;
+
+ default:
+ NOT_REACHED();
+ }
+}
+
+static int
+compare_nodes_by_name(const void *a_, const void *b_)
+{
+ const struct shash_node *const *a = a_;
+ const struct shash_node *const *b = b_;
+ return strcmp((*a)->name, (*b)->name);
+}
+
+static void
+json_object_to_ds(const struct shash *object, struct ds *ds)
+{
+ size_t n = shash_count(object);
+ struct shash_node **nodes;
+ struct shash_node *node;
+ size_t i;
+
+ ds_put_char(ds, '{');
+
+ nodes = xmalloc(n * sizeof *nodes);
+ i = 0;
+ SHASH_FOR_EACH (node, object) {
+ nodes[i++] = node;
+ }
+ assert(i == n);
+
+ qsort(nodes, n, sizeof *nodes, compare_nodes_by_name);
+ for (i = 0; i < n; i++) {
+ node = nodes[i];
+
+ if (i) {
+ ds_put_char(ds, ',');
+ }
+
+ json_string_to_ds(node->name, ds);
+ ds_put_char(ds, ':');
+ json_to_ds(node->data, ds);
+ }
+
+ free(nodes);
+
+ ds_put_char(ds, '}');
+}
+
+static void
+json_array_to_ds(const struct json_array *array, struct ds *ds)
+{
+ size_t i;
+
+ ds_put_char(ds, '[');
+ for (i = 0; i < array->n; i++) {
+ if (i) {
+ ds_put_char(ds, ',');
+ }
+ json_to_ds(array->elems[i], ds);
+ }
+ ds_put_char(ds, ']');
+}
+
+static void
+json_string_to_ds(const char *string, struct ds *ds)
+{
+ uint8_t c;
+
+ ds_put_char(ds, '"');
+ while ((c = *string++) != '\0') {
+ switch (c) {
+ case '"':
+ ds_put_cstr(ds, "\\\"");
+ break;
+
+ case '\\':
+ ds_put_cstr(ds, "\\\\");
+ break;
+
+ case '\b':
+ ds_put_cstr(ds, "\\b");
+ break;
+
+ case '\f':
+ ds_put_cstr(ds, "\\f");
+ break;
+
+ case '\n':
+ ds_put_cstr(ds, "\\n");
+ break;
+
+ case '\r':
+ ds_put_cstr(ds, "\\r");
+ break;
+
+ case '\t':
+ ds_put_cstr(ds, "\\t");
+ break;
+
+ default:
+ if (c >= 32) {
+ ds_put_char(ds, c);
+ } else {
+ ds_put_format(ds, "\\u%04x", c);
+ }
+ break;
+ }
+ }
+ ds_put_char(ds, '"');
+}
diff --git a/lib/json.h b/lib/json.h
new file mode 100644
index 0000000..90657d9
--- /dev/null
+++ b/lib/json.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSON_H
+#define JSON_H 1
+
+/* This is an implementation of JavaScript Object Notation (JSON) as specified
+ * by RFC 4627. It is intended to fully comply with RFC 4627, with the
+ * following known exceptions and clarifications:
+ *
+ * - Null bytes (\u0000) are not allowed in strings.
+ *
+ * - Only UTF-8 encoding is supported (RFC 4627 allows for other Unicode
+ * encodings).
+ *
+ * - Names within an object must be unique (RFC 4627 says that they
+ * "should" be unique).
+ */
+
+#include "shash.h"
+
+enum json_type {
+ JSON_NULL, /* null */
+ JSON_FALSE, /* false */
+ JSON_TRUE, /* true */
+ JSON_OBJECT, /* {"a": b, "c": d, ...} */
+ JSON_ARRAY, /* [1, 2, 3, ...] */
+ JSON_INTEGER, /* 123. */
+ JSON_REAL, /* 123.456. */
+ JSON_STRING /* "..." */
+};
+
+struct json_array {
+ size_t n;
+ struct json **elems;
+};
+
+struct json {
+ enum json_type type;
+ union {
+ struct shash *object; /* Contains "struct json *"s. */
+ struct json_array array;
+ long long int integer;
+ double real;
+ char *string;
+ } u;
+};
+
+void json_destroy(struct json *);
+
+struct json *json_from_string(const char *string);
+struct json *json_from_file(const char *file_name);
+struct json *json_from_cb(int (*)(void *aux, const char **inputp), void *aux);
+
+char *json_to_string(const struct json *);
+
+#endif /* json.h */
diff --git a/lib/shash.c b/lib/shash.c
index 7bb8cd7..bdaa496 100644
--- a/lib/shash.c
+++ b/lib/shash.c
@@ -57,6 +57,12 @@ shash_is_empty(const struct shash *shash)
return hmap_is_empty(&shash->map);
}
+size_t
+shash_count(const struct shash *shash)
+{
+ return hmap_count(&shash->map);
+}
+
/* It is the caller's responsibility to avoid duplicate names, if that is
* desirable. */
struct shash_node *
diff --git a/lib/shash.h b/lib/shash.h
index 5794a20..0ce80c4 100644
--- a/lib/shash.h
+++ b/lib/shash.h
@@ -42,6 +42,7 @@ void shash_init(struct shash *);
void shash_destroy(struct shash *);
void shash_clear(struct shash *);
bool shash_is_empty(const struct shash *);
+size_t shash_count(const struct shash *);
struct shash_node *shash_add(struct shash *, const char *, void *);
void shash_delete(struct shash *, struct shash_node *);
struct shash_node *shash_find(const struct shash *, const char *);
diff --git a/lib/unicode.c b/lib/unicode.c
new file mode 100644
index 0000000..69ebcfc
--- /dev/null
+++ b/lib/unicode.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "unicode.h"
+
+/* Returns the unicode code point corresponding to leading surrogate 'leading'
+ * and trailing surrogate 'trailing'. The return value will not make any
+ * sense if 'leading' or 'trailing' are not in the correct ranges for leading
+ * or trailing surrogates. */
+int
+utf16_decode_surrogate_pair(int leading, int trailing)
+{
+ /*
+ * Leading surrogate: 110110wwwwxxxxxx
+ * Trailing surrogate: 110111xxxxxxxxxx
+ * Code point: 000uuuuuxxxxxxxxxxxxxxxx
+ */
+ int w = (leading >> 6) & 0xf;
+ int u = w + 1;
+ int x0 = leading & 0x3f;
+ int x1 = trailing & 0x3ff;
+ return (u << 16) | (x0 << 10) | x1;
+}
diff --git a/lib/unicode.h b/lib/unicode.h
new file mode 100644
index 0000000..0f20bdc
--- /dev/null
+++ b/lib/unicode.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UNICODE_H
+#define UNICODE_H 1
+
+#include <stdbool.h>
+
+/* Returns true if 'c' is a Unicode code point, otherwise false. */
+static inline bool
+uc_is_code_point(int c)
+{
+ return c >= 0 && c <= 0x10ffff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading surrogate. */
+static inline bool
+uc_is_leading_surrogate(int c)
+{
+ return c >= 0xd800 && c <= 0xdbff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a trailing surrogate. */
+static inline bool
+uc_is_trailing_surrogate(int c)
+{
+ return c >= 0xdc00 && c <= 0xdfff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading or trailing
+ * surrogate. */
+static inline bool
+uc_is_surrogate(int c)
+{
+ return c >= 0xd800 && c <= 0xdfff;
+}
+
+int utf16_decode_surrogate_pair(int leading, int trailing);
+
+#endif /* unicode.h */
diff --git a/lib/util.c b/lib/util.c
index f766d59..56f5199 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -294,3 +294,58 @@ str_to_ullong(const char *s, int base, unsigned long long *ull)
{
return str_to_llong(s, base, (long long *) ull);
}
+
+/* Converts floating-point string 's' into a double. If successful, stores
+ * the double in '*d' and returns true; on failure, stores 0 in '*d' and
+ * returns false.
+ *
+ * Underflow (e.g. "1e-9999") is not considered an error, but overflow
+ * (e.g. "1e9999)" is. */
+bool
+str_to_double(const char *s, double *d)
+{
+ int save_errno = errno;
+ char *tail;
+ errno = 0;
+ *d = strtod(s, &tail);
+ if (errno == EINVAL || (errno == ERANGE && *d != 0)
+ || tail == s || *tail != '\0') {
+ errno = save_errno;
+ *d = 0;
+ return false;
+ } else {
+ errno = save_errno;
+ return true;
+ }
+}
+
+/* Returns the value of 'c' as a hexadecimal digit. */
+int
+hexit_value(int c)
+{
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return c - '0';
+
+ case 'a': case 'A':
+ return 0xa;
+
+ case 'b': case 'B':
+ return 0xb;
+
+ case 'c': case 'C':
+ return 0xc;
+
+ case 'd': case 'D':
+ return 0xd;
+
+ case 'e': case 'E':
+ return 0xe;
+
+ case 'f': case 'F':
+ return 0xf;
+ }
+
+ NOT_REACHED();
+}
diff --git a/lib/util.h b/lib/util.h
index 962bad2..4c9ed6c 100644
--- a/lib/util.h
+++ b/lib/util.h
@@ -120,6 +120,10 @@ bool str_to_uint(const char *, int base, unsigned int *);
bool str_to_ulong(const char *, int base, unsigned long *);
bool str_to_ullong(const char *, int base, unsigned long long *);
+bool str_to_double(const char *, double *);
+
+int hexit_value(int c);
+
#ifdef __cplusplus
}
#endif
diff --git a/tests/.gitignore b/tests/.gitignore
index 706aa14..caa2db6 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -5,6 +5,7 @@
/test-flows
/test-hash
/test-hmap
+/test-json
/test-list
/test-stp
/test-type-props
diff --git a/tests/automake.mk b/tests/automake.mk
index 7659a26..cba914f 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -8,6 +8,7 @@ TESTSUITE_AT = \
tests/testsuite.at \
tests/lcov-pre.at \
tests/library.at \
+ tests/json.at \
tests/stp.at \
tests/ovs-vsctl.at \
tests/lcov-post.at
@@ -58,6 +59,10 @@ noinst_PROGRAMS += tests/test-hmap
tests_test_hmap_SOURCES = tests/test-hmap.c
tests_test_hmap_LDADD = lib/libopenvswitch.a
+noinst_PROGRAMS += tests/test-json
+tests_test_json_SOURCES = tests/test-json.c
+tests_test_json_LDADD = lib/libopenvswitch.a
+
noinst_PROGRAMS += tests/test-list
tests_test_list_SOURCES = tests/test-list.c
tests_test_list_LDADD = lib/libopenvswitch.a
diff --git a/tests/json.at b/tests/json.at
new file mode 100644
index 0000000..224086b
--- /dev/null
+++ b/tests/json.at
@@ -0,0 +1,211 @@
+m4_define([JSON_CHECK_POSITIVE],
+ [AT_SETUP([$1])
+ AT_KEYWORDS([json positive])
+ AT_DATA([input], [$2
+])
+ OVS_CHECK_LCOV([test-json input], [0], [$3
+], [])
+ AT_CLEANUP])
+
+m4_define([JSON_CHECK_NEGATIVE],
+ [AT_SETUP([$1])
+ AT_KEYWORDS([json negative])
+ AT_DATA([input], [$2
+])
+ OVS_CHECK_LCOV([test-json input], [1], [], [error: $3
+])
+ AT_CLEANUP])
+
+AT_BANNER([JSON -- arrays])
+
+JSON_CHECK_POSITIVE([empty array], [[ [ ] ]], [[[]]])
+JSON_CHECK_POSITIVE([single-element array], [[ [ 1 ] ]], [[[1]]])
+JSON_CHECK_POSITIVE([2-element array], [[ [ 1, 2 ] ]], [[[1,2]]])
+JSON_CHECK_POSITIVE([many-element array],
+ [[ [ 1, 2, 3, 4, 5 ] ]],
+ [[[1,2,3,4,5]]])
+JSON_CHECK_NEGATIVE([missing comma], [[ [ 1, 2, 3 4, 5 ] ]],
+ [syntax error expecting '@:>@' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed],
+ [[[1,2,]]], [syntax error expecting value])
+JSON_CHECK_NEGATIVE([doubled comma not allowed],
+ [[[1,,2]]], [syntax error expecting value])
+
+AT_BANNER([JSON -- strings])
+
+JSON_CHECK_POSITIVE([empty string], [[[ "" ]]], [[[""]]])
+JSON_CHECK_POSITIVE([1-character strings],
+ [[[ "a", "b", "c" ]]],
+ [[["a","b","c"]]])
+JSON_CHECK_POSITIVE([escape sequences],
+ [[[ " \" \\ \/ \b \f \n \r \t" ]]],
+ [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([Unicode escape sequences],
+ [[[ " \u0022 \u005c \u002F \u0008 \u000c \u000A \u000d \u0009" ]]],
+ [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([surrogate pairs],
+ [[["\ud834\udd1e"]]],
+ [[["ð"]]])
+JSON_CHECK_NEGATIVE([a string by itself is not valid JSON], ["xxx"],
+ [syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([end of line in quoted string],
+ [[["xxx
+"]]],
+ [U+000A must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([formfeed in quoted string],
+ [[["xxx"]]],
+ [U+000C must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([bad escape in quoted string],
+ [[["\x12"]]],
+ [bad escape \x])
+JSON_CHECK_NEGATIVE([\u must be followed by 4 hex digits],
+ [[["\u1x"]]],
+ [malformed \u escape])
+JSON_CHECK_NEGATIVE([isolated leading surrogate not allowed],
+ [[["\ud834xxx"]]],
+ [malformed escaped surrogate pair])
+JSON_CHECK_NEGATIVE([surrogatess must paired properly],
+ [[["\ud834\u1234"]]],
+ [second half of escaped surrogate pair is not trailing surrogate])
+JSON_CHECK_NEGATIVE([null bytes not allowed],
+ [[["\u0000"]]],
+ [null bytes not supported in quoted strings])
+
+AT_SETUP([end of input in quoted string])
+AT_KEYWORDS([json negative])
+AT_CHECK([printf '\"xxx' | test-json -], [1], [],
+ [error: unexpected end of input in quoted string
+])
+AT_CLEANUP
+
+AT_BANNER([JSON -- objects])
+
+JSON_CHECK_POSITIVE([empty object], [[{ }]], [[{}]])
+JSON_CHECK_POSITIVE([simple object],
+ [[{"b": 2, "a": 1, "c": 3}]],
+ [[{"a":1,"b":2,"c":3}]])
+JSON_CHECK_NEGATIVE([bad value], [[{"a": }, "b": 2]],
+ [syntax error expecting value])
+JSON_CHECK_NEGATIVE([missing colon], [[{"b": 2, "a" 1, "c": 3}]],
+ [syntax error parsing object expecting ':'])
+JSON_CHECK_NEGATIVE([missing comma], [[{"b": 2 "a" 1, "c": 3}]],
+ [syntax error expecting '}' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed],
+ [[{"b": 2, "a": 1, "c": 3, }]],
+ [[syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([doubled comma not allowed],
+ [[{"b": 2, "a": 1,, "c": 3}]],
+ [[syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([names must be strings],
+ [[{1: 2}]],
+ [[syntax error parsing object expecting string]])
+
+AT_BANNER([JSON -- literal names])
+
+JSON_CHECK_POSITIVE([null], [[[ null ]]], [[[null]]])
+JSON_CHECK_POSITIVE([false], [[[ false ]]], [[[false]]])
+JSON_CHECK_POSITIVE([true], [[[ true ]]], [[[true]]])
+JSON_CHECK_NEGATIVE([a literal by itself is not valid JSON], [null],
+ [syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([nullify is invalid], [[[ nullify ]]], [invalid keyword])
+JSON_CHECK_NEGATIVE([nubs is invalid], [[[ nubs ]]], [invalid keyword])
+JSON_CHECK_NEGATIVE([xxx is invalid], [[[ xxx ]]], [invalid character 'x'])
+
+AT_BANNER([JSON -- numbers])
+
+JSON_CHECK_POSITIVE(
+ [reals],
+ [[[0.0, 1.0, 2.0, 3.0, 3.5, 8.1250]]],
+ [[[0,1,2,3,3.5,8.125]]])
+JSON_CHECK_POSITIVE(
+ [scientific notation],
+ [[[1e3, 1E3, 2.5E2, 1e+3, 125e-3, 3.125e-2, 3125e-05, 1.525878906e-5]]],
+ [[[1000,1000,250,1000,0.125,0.03125,0.03125,1.525878906e-05]]])
+JSON_CHECK_POSITIVE(
+ [negative reals],
+ [[[-0, -1.0, -2.0, -3.0, -3.5, -8.1250]]],
+ [[[0,-1,-2,-3,-3.5,-8.125]]])
+JSON_CHECK_POSITIVE(
+ [negative scientific notation],
+ [[[-1e3, -1E3, -2.5E2, -1e+3, -125e-3, -3.125e-2, -3125e-05, -1.525878906e-5]]],
+ [[[-1000,-1000,-250,-1000,-0.125,-0.03125,-0.03125,-1.525878906e-05]]])
+JSON_CHECK_POSITIVE(
+ [1e-9999 underflows to 0],
+ [[[1e-9999]]],
+ [[[0]]])
+JSON_CHECK_NEGATIVE([a number by itself is not valid JSON], [1],
+ [syntax error at beginning of input])
+JSON_CHECK_NEGATIVE(
+ [leading zeros not allowed],
+ [[[0123]]],
+ [leading zeros not allowed])
+JSON_CHECK_NEGATIVE(
+ [1e9999 is too big],
+ [[[1e9999]]],
+ [number "1e9999" outside valid range])
+
+AT_BANNER([JSON -- RFC 4627 examples])
+
+JSON_CHECK_POSITIVE([RFC 4267 object example],
+[[{
+ "Image": {
+ "Width": 800,
+ "Height": 600,
+ "Title": "View from 15th Floor",
+ "Thumbnail": {
+ "Url": "http://www.example.com/image/481989943",
+ "Height": 125,
+ "Width": "100"
+ },
+ "IDs": [116, 943, 234, 38793]
+ }
+}]],
+[[{"Image":{"Height":600,"IDs":[116,943,234,38793],"Thumbnail":{"Height":125,"Url":"http://www.example.com/image/481989943","Width":"100"},"Title":"View from 15th Floor","Width":800}}]])
+
+JSON_CHECK_POSITIVE([RFC 4267 array example],
+[[[
+ {
+ "precision": "zip",
+ "Latitude": 37.7668,
+ "Longitude": -122.3959,
+ "Address": "",
+ "City": "SAN FRANCISCO",
+ "State": "CA",
+ "Zip": "94107",
+ "Country": "US"
+ },
+ {
+ "precision": "zip",
+ "Latitude": 37.371991,
+ "Longitude": -122.026020,
+ "Address": "",
+ "City": "SUNNYVALE",
+ "State": "CA",
+ "Zip": "94085",
+ "Country": "US"
+ }
+]]],
+[[[{"Address":"","City":"SAN FRANCISCO","Country":"US","Latitude":37.7668,"Longitude":-122.3959,"State":"CA","Zip":"94107","precision":"zip"},{"Address":"","City":"SUNNYVALE","Country":"US","Latitude":37.371991,"Longitude":-122.02602,"State":"CA","Zip":"94085","precision":"zip"}]]])
+
+AT_BANNER([JSON -- pathological cases])
+
+JSON_CHECK_NEGATIVE([trailing garbage], [[[1]null]],
+ [trailing garbage at end of input])
+JSON_CHECK_NEGATIVE([formfeeds are not valid white space],
+ [[[]]], [invalid character U+000c])
+JSON_CHECK_NEGATIVE([';' is not a valid token],
+ [;], [invalid character ';'])
+JSON_CHECK_NEGATIVE([arrays nesting too deep],
+ [m4_for([i], [0], [1002], [1], [@<:@])dnl
+ m4_for([i], [0], [1002], [1], [@:>@])],
+ [input exceeds maximum nesting depth 1000])
+JSON_CHECK_NEGATIVE([objects nesting too deep],
+ [m4_for([i], [0], [1002], [1], [{"x":])dnl
+ m4_for([i], [0], [1002], [1], [}])],
+ [input exceeds maximum nesting depth 1000])
+
+AT_SETUP([input may not be empty])
+AT_KEYWORDS([json negative])
+AT_CHECK([test-json /dev/null], [1], [], [error: empty input stream
+])
+AT_CLEANUP
diff --git a/tests/test-json.c b/tests/test-json.c
new file mode 100644
index 0000000..9f9a1eb
--- /dev/null
+++ b/tests/test-json.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <stdio.h>
+
+int
+main(int argc, char *argv[])
+{
+ struct json *json;
+ int exit_code;
+
+ if (argc != 2) {
+ ovs_fatal(0, "usage: %s INPUT.json", argv[0]);
+ }
+
+ json = json_from_file(!strcmp(argv[1], "-") ? "/dev/stdin" : argv[1]);
+ if (json->type == JSON_STRING) {
+ fprintf(stderr, "error: %s\n", json->u.string);
+ exit_code = 1;
+ } else {
+ char *s = json_to_string(json);
+ puts(s);
+ free(s);
+ exit_code = 0;
+ }
+ json_destroy(json);
+ return exit_code;
+}
diff --git a/tests/testsuite.at b/tests/testsuite.at
index c232a87..dba19af 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -19,6 +19,7 @@ AT_TESTED([ovs-vsctl])
m4_include([tests/lcov-pre.at])
m4_include([tests/library.at])
+m4_include([tests/json.at])
m4_include([tests/stp.at])
m4_include([tests/ovs-vsctl.at])
m4_include([tests/lcov-post.at])
--
1.6.3.3
More information about the dev
mailing list