diff --git a/Makefile b/Makefile index 17dc65d..df61f43 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ MODULES += mod conf MODULES += bfcp MODULES += aes srtp MODULES += odict +MODULES += json INSTALL := install ifeq ($(DESTDIR),) diff --git a/docs/README b/docs/README index 5c63b32..dded0f6 100644 --- a/docs/README +++ b/docs/README @@ -33,6 +33,7 @@ Modules: * httpauth testing HTTP-based Authentication (RFC 2617) * ice unstable Interactive Connectivity Establishment (ICE) * jbuf testing Jitter buffer +* json unstable JavaScript Object Notation (JSON) * list stable Sortable doubly-linked list handling * lock testing Resource locking functions * main testing Main poll loop diff --git a/include/re.h b/include/re.h index df8cf80..d557440 100644 --- a/include/re.h +++ b/include/re.h @@ -40,6 +40,7 @@ extern "C" { #include "re_mqueue.h" #include "re_net.h" #include "re_odict.h" +#include "re_json.h" #include "re_rtp.h" #include "re_sdp.h" #include "re_uri.h" diff --git a/include/re_json.h b/include/re_json.h new file mode 100644 index 0000000..0cc43db --- /dev/null +++ b/include/re_json.h @@ -0,0 +1,50 @@ +/** + * @file re_json.h Interface to JavaScript Object Notation (JSON) -- RFC 7159 + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + +enum json_typ { + JSON_STRING, + JSON_INT, + JSON_DOUBLE, + JSON_BOOL, + JSON_NULL, +}; + +struct json_value { + union { + char *str; + int64_t integer; + double dbl; + bool boolean; + } v; + enum json_typ type; +}; + +struct json_handlers; + +typedef int (json_object_entry_h)(const char *name, + const struct json_value *value, void *arg); +typedef int (json_array_entry_h)(unsigned idx, + const struct json_value *value, void *arg); +typedef int (json_object_h)(const char *name, unsigned idx, + struct json_handlers *h); +typedef int (json_array_h)(const char *name, unsigned idx, + struct json_handlers *h); + +struct json_handlers { + json_object_h *oh; + json_array_h *ah; + json_object_entry_h *oeh; + json_array_entry_h *aeh; + void *arg; +}; + +int json_decode(const char *str, size_t len, unsigned maxdepth, + json_object_h *oh, json_array_h *ah, + json_object_entry_h *oeh, json_array_entry_h *aeh, void *arg); + +int json_decode_odict(struct odict **op, uint32_t hash_size, const char *str, + size_t len, unsigned maxdepth); +int json_encode_odict(struct re_printf *pf, const struct odict *o); diff --git a/src/json/decode.c b/src/json/decode.c new file mode 100644 index 0000000..488f2f7 --- /dev/null +++ b/src/json/decode.c @@ -0,0 +1,464 @@ +/** + * @file json/decode.c JSON decoder + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + +#include +#include +#include +#include +#include +#include +#include + + +static inline uint64_t mypower10(uint64_t e) +{ + uint64_t i, n = 1; + + for (i=0; il < 2) + return false; + + if (pl->p[0] != '"'|| pl->p[pl->l-1] != '"') + return false; + + c->p = pl->p + 1; + c->l = pl->l - 2; + + return true; +} + + +static bool is_number(long double *d, bool *isfloat, const struct pl *pl) +{ + bool neg = false, pos = false, frac = false, exp = false; + long double v = 0, mul = 1; + const char *p; + int64_t e = 0; + + if (!pl->l) + return false; + + p = &pl->p[pl->l]; + + while (p > pl->p) { + + const char ch = *--p; + + if (ch == 'e' || ch == 'E') { + + if (exp || frac) + return false; + + exp = true; + e = neg ? -v : v; + v = 0; + mul = 1; + neg = false; + pos = false; + } + else if (pos || neg) { + return false; + } + else if (ch == '.') { + + if (frac) + return false; + + frac = true; + v /= mul; + mul = 1; + } + else if ('0' <= ch && ch <= '9') { + v += mul * (ch - '0'); + mul *= 10; + } + else if (ch == '-') { + neg = true; + } + else if (ch == '+') { + pos = true; + } + else { + return false; + } + } + + *isfloat = (frac || (exp && e < 0)); + + if (exp) { + if (e < 0) + v /= mypower10(-e); + else + v *= mypower10(e); + } + + if (neg) + v = -v; + + *d = v; + + return true; +} + + +static int decode_name(char **str, const struct pl *pl) +{ + struct pl pls; + + if (!pl->p) + return EBADMSG; + + if (!is_string(&pls, pl)) + return EBADMSG; + + return re_sdprintf(str, "%H", utf8_decode, &pls); +} + + +static int decode_value(struct json_value *val, const struct pl *pl) +{ + long double dbl; + struct pl pls; + bool isfloat; + int err = 0; + + if (!pl->p) + return EBADMSG; + + if (is_string(&pls, pl)) { + + err = re_sdprintf(&val->v.str, "%H", utf8_decode, &pls); + val->type = JSON_STRING; + } + else if (is_number(&dbl, &isfloat, pl)) { + + if (isfloat) { + val->type = JSON_DOUBLE; + val->v.dbl = dbl; + } + else { + val->type = JSON_INT; + val->v.integer = dbl; + } + } + else if (!pl_strcasecmp(pl, "false")) { + + val->v.boolean = false; + val->type = JSON_BOOL; + } + else if (!pl_strcasecmp(pl, "true")) { + + val->v.boolean = true; + val->type = JSON_BOOL; + } + else if (!pl_strcasecmp(pl, "null")) { + + val->type = JSON_NULL; + } + else { + re_printf("json: value of unkown type: <%r>\n", pl); + err = EBADMSG; + } + + return err; +} + + +static int object_entry(const struct pl *pl_name, const struct pl *pl_val, + json_object_entry_h *oeh, void *arg) +{ + struct json_value val; + char *name; + int err; + + err = decode_name(&name, pl_name); + if (err) + return err; + + err = decode_value(&val, pl_val); + if (err) + goto out; + + if (oeh) + err = oeh(name, &val, arg); + + if (val.type == JSON_STRING) + mem_deref(val.v.str); + + out: + mem_deref(name); + + return err; +} + + +static int array_entry(unsigned idx, const struct pl *pl_val, + json_array_entry_h *aeh, void *arg) +{ + struct json_value val; + int err; + + err = decode_value(&val, pl_val); + if (err) + return err; + + if (aeh) + err = aeh(idx, &val, arg); + + if (val.type == JSON_STRING) + mem_deref(val.v.str); + + return err; +} + + +static int object_start(const struct pl *pl_name, unsigned idx, + struct json_handlers *h) +{ + char *name = NULL; + int err; + + if (pl_name->p) { + + err = decode_name(&name, pl_name); + if (err) + return err; + } + + if (h->oh) + err = h->oh(name, idx, h); + + mem_deref(name); + + return err; +} + + +static int array_start(const struct pl *pl_name, unsigned idx, + struct json_handlers *h) +{ + char *name = NULL; + int err; + + if (pl_name->p) { + + err = decode_name(&name, pl_name); + if (err) + return err; + } + + if (h->ah) + err = h->ah(name, idx, h); + + mem_deref(name); + + return err; +} + + +static inline int chkval(struct pl *val, const char *p) +{ + if (!val->p || pp) + return EINVAL; + + val->l = p - val->p; + + return 0; +} + + +static int _json_decode(const char **str, size_t *len, + unsigned depth, unsigned maxdepth, + json_object_h *oh, json_array_h *ah, + json_object_entry_h *oeh, json_array_entry_h *aeh, + void *arg) +{ + bool esc = false, inquot = false, inobj = false, inarray = false; + struct pl name = PL_INIT, val = PL_INIT; + size_t ws = 0; + unsigned idx = 0; + int err; + + for (; *len>0; ++(*str), --(*len)) { + + if (inquot) { + if (esc) + esc = false; + else if (**str == '\"') + inquot = false; + else if (**str == '\\') + esc = true; + + continue; + } + + switch (**str) { + + case ':': + if (!inobj || name.p || chkval(&val, *str - ws)) + return EBADMSG; + + name = val; + val = pl_null; + break; + + case ',': + if (chkval(&val, *str - ws)) + break; + + if (inobj) { + + if (!name.p) + return EBADMSG; + + err = object_entry(&name, &val, oeh, arg); + if (err) + return err; + } + else if (inarray) { + + err = array_entry(idx, &val, aeh, arg); + if (err) + return err; + + ++idx; + } + else + return EBADMSG; + + name = pl_null; + val = pl_null; + break; + + case '{': + if (inobj || inarray) { + + struct json_handlers h = {oh,ah,oeh,aeh,arg}; + + if (depth >= maxdepth) + return EOVERFLOW; + + if (inobj && !name.p) + return EBADMSG; + + err = object_start(&name, idx, &h); + if (err) + return err; + + name = pl_null; + + err = _json_decode(str, len, depth + 1, + maxdepth, h.oh, h.ah, + h.oeh, h.aeh, h.arg); + if (err) + return err; + + if (inarray) + ++idx; + } + else { + inobj = true; + } + break; + + case '[': + if (inobj || inarray) { + + struct json_handlers h = {oh,ah,oeh,aeh,arg}; + + if (depth >= maxdepth) + return EOVERFLOW; + + if (inobj && !name.p) + return EBADMSG; + + err = array_start(&name, idx, &h); + if (err) + return err; + + name = pl_null; + + err = _json_decode(str, len, depth + 1, + maxdepth, h.oh, h.ah, + h.oeh, h.aeh, h.arg); + if (err) + return err; + + if (inarray) + ++idx; + } + else { + inarray = true; + idx = 0; + } + break; + + case '}': + if (!inobj) + return EBADMSG; + + if (chkval(&val, *str - ws)) + return 0; + + if (!name.p) + return EBADMSG; + + return object_entry(&name, &val, oeh, arg); + + case ']': + if (!inarray) + return EBADMSG; + + if (chkval(&val, *str - ws)) + return 0; + + return array_entry(idx, &val, aeh, arg); + + case ' ': + case '\t': + case '\r': + case '\n': + ++ws; + break; + + default: + if (val.p) + break; + + if (**str == '\"') + inquot = true; + + val.p = *str; + val.l = 0; + ws = 0; + break; + } + } + + if (inobj || inarray) + return EBADMSG; + + return 0; +} + + +int json_decode(const char *str, size_t len, unsigned maxdepth, + json_object_h *oh, json_array_h *ah, + json_object_entry_h *oeh, json_array_entry_h *aeh, void *arg) +{ + if (!str) + return EINVAL; + + return _json_decode(&str, &len, 0, maxdepth, oh, ah, oeh, aeh, arg); +} diff --git a/src/json/decode_odict.c b/src/json/decode_odict.c new file mode 100644 index 0000000..cd64aff --- /dev/null +++ b/src/json/decode_odict.c @@ -0,0 +1,125 @@ +/** + * @file json/decode_odict.c JSON odict decode + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ + +#include +#include +#include +#include +#include +#include +#include + + +static int container_add(const char *name, unsigned idx, + enum odict_type type, struct json_handlers *h) +{ + struct odict *o = h->arg, *oc; + char index[64]; + int err; + + if (!name) { + if (re_snprintf(index, sizeof(index), "%u", idx) < 0) + return ENOMEM; + + name = index; + } + + err = odict_alloc(&oc, hash_bsize(o->ht)); + if (err) + return err; + + err = odict_entry_add(o, name, type, oc); + mem_deref(oc); + h->arg = oc; + + return err; +} + + +static int object_handler(const char *name, unsigned idx, + struct json_handlers *h) +{ + return container_add(name, idx, ODICT_OBJECT, h); +} + + +static int array_handler(const char *name, unsigned idx, + struct json_handlers *h) +{ + return container_add(name, idx, ODICT_ARRAY, h); +} + + +static int entry_add(struct odict *o, const char *name, + const struct json_value *val) +{ + switch (val->type) { + + case JSON_STRING: + return odict_entry_add(o, name, ODICT_STRING, val->v.str); + + case JSON_INT: + return odict_entry_add(o, name, ODICT_INT, val->v.integer); + + case JSON_DOUBLE: + return odict_entry_add(o, name, ODICT_DOUBLE, val->v.dbl); + + case JSON_BOOL: + return odict_entry_add(o, name, ODICT_BOOL, val->v.boolean); + + case JSON_NULL: + return odict_entry_add(o, name, ODICT_NULL); + + default: + return ENOSYS; + } +} + + +static int object_entry_handler(const char *name, const struct json_value *val, + void *arg) +{ + struct odict *o = arg; + + return entry_add(o, name, val); +} + + +static int array_entry_handler(unsigned idx, const struct json_value *val, + void *arg) +{ + struct odict *o = arg; + char index[64]; + + if (re_snprintf(index, sizeof(index), "%u", idx) < 0) + return ENOMEM; + + return entry_add(o, index, val); +} + + +int json_decode_odict(struct odict **op, uint32_t hash_size, const char *str, + size_t len, unsigned maxdepth) +{ + struct odict *o; + int err; + + if (!op || !str) + return EINVAL; + + err = odict_alloc(&o, hash_size); + if (err) + return err; + + err = json_decode(str, len, maxdepth, object_handler, array_handler, + object_entry_handler, array_entry_handler, o); + if (err) + mem_deref(o); + else + *op = o; + + return err; +} diff --git a/src/json/encode.c b/src/json/encode.c new file mode 100644 index 0000000..fa56de3 --- /dev/null +++ b/src/json/encode.c @@ -0,0 +1,99 @@ +/** + * @file json/encode.c JSON encoder + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include +#include +#include +#include +#include + + +static int encode_entry(struct re_printf *pf, const struct odict_entry *e) +{ + struct odict *array; + struct le *le; + int err; + + if (!e) + return 0; + + switch (e->type) { + + case ODICT_OBJECT: + err = json_encode_odict(pf, e->u.odict); + break; + + case ODICT_ARRAY: + array = e->u.odict; + if (!array) + return 0; + + err = re_hprintf(pf, "["); + + for (le=array->lst.head; le; le=le->next) { + + const struct odict_entry *ae = le->data; + + err |= re_hprintf(pf, "%H%s", + encode_entry, ae, + le->next ? "," : ""); + } + + err |= re_hprintf(pf, "]"); + break; + + case ODICT_INT: + err = re_hprintf(pf, "%lld", e->u.integer); + break; + + case ODICT_DOUBLE: + err = re_hprintf(pf, "%f", e->u.dbl); + break; + + case ODICT_STRING: + err = re_hprintf(pf, "\"%H\"", utf8_encode, e->u.str); + break; + + case ODICT_BOOL: + err = re_hprintf(pf, "%s", e->u.boolean ? "true" : "false"); + break; + + case ODICT_NULL: + err = re_hprintf(pf, "null"); + break; + + default: + re_fprintf(stderr, "json: unsupported type %d\n", e->type); + err = EINVAL; + } + + return err; +} + + +int json_encode_odict(struct re_printf *pf, const struct odict *o) +{ + struct le *le; + int err; + + if (!o) + return 0; + + err = re_hprintf(pf, "{"); + + for (le=o->lst.head; le; le=le->next) { + + const struct odict_entry *e = le->data; + + err |= re_hprintf(pf, "\"%H\":%H%s", + utf8_encode, e->key, + encode_entry, e, + le->next ? "," : ""); + } + + err |= re_hprintf(pf, "}"); + + return err; +} diff --git a/src/json/mod.mk b/src/json/mod.mk new file mode 100644 index 0000000..731ddc5 --- /dev/null +++ b/src/json/mod.mk @@ -0,0 +1,9 @@ +# +# mod.mk +# +# Copyright (C) 2010 - 2015 Creytiv.com +# + +SRCS += json/decode.c +SRCS += json/decode_odict.c +SRCS += json/encode.c