[ovs-dev] [PATCH] Implement JSON parsing and serialization.

Justin Pettit jpettit at nicira.com
Thu Sep 24 17:51:05 UTC 2009


Yes, please put it in a topic branch.  All new features should be in  
topic branches, so that we can make the release more palatable to  
Citrix.  If we can get through their Midnight Ride QA process soon, I  
think we'll be able to swing getting other features in.  If we start  
piling on features before they're comfortable with what they have now,  
we'll be more likely to scare them off integrating.

I'll take a look at the patch when I can do it a bit more at my  
leisure.  Perhaps this evening, when I'm sitting in front of the fire  
in my smoking jacket with a snifter of cognac.

--Justin


On Sep 24, 2009, at 10:35 AM, Ben Pfaff wrote:

> 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
>
>
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> http://openvswitch.org/mailman/listinfo/dev_openvswitch.org





More information about the dev mailing list