diff --git a/daemon/Makefile b/daemon/Makefile index 863c220b5..166c22618 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -8,6 +8,7 @@ daemon-all: daemon/lightningd DAEMON_SRC := \ daemon/configdir.c \ + daemon/json.c \ daemon/lightningd.c \ daemon/log.c \ daemon/pseudorand.c @@ -18,6 +19,7 @@ DAEMON_JSMN_HEADERS := daemon/jsmn/jsmn.h DAEMON_HEADERS := \ daemon/configdir.h \ + daemon/json.h \ daemon/lightningd.h \ daemon/log.h \ daemon/pseudorand.h diff --git a/daemon/json.c b/daemon/json.c new file mode 100644 index 000000000..e878611da --- /dev/null +++ b/daemon/json.c @@ -0,0 +1,383 @@ +/* JSON core and helpers */ +#include "json.h" +#include +#include +#include +#include +#include +#include +#include +#include + +struct json_result { + unsigned int indent; + /* tal_count() of this is strlen() + 1 */ + char *s; +}; + +const char *json_tok_contents(const char *buffer, const jsmntok_t *t) +{ + if (t->type == JSMN_STRING) + return buffer + t->start - 1; + return buffer + t->start; +} + +/* Include " if it's a string. */ +int json_tok_len(const jsmntok_t *t) +{ + if (t->type == JSMN_STRING) + return t->end - t->start + 2; + return t->end - t->start; +} + +bool json_tok_streq(const char *buffer, const jsmntok_t *tok, const char *str) +{ + if (tok->type != JSMN_STRING) + return false; + if (tok->end - tok->start != strlen(str)) + return false; + return strncmp(buffer + tok->start, str, tok->end - tok->start) == 0; +} + +bool json_tok_number(const char *buffer, const jsmntok_t *tok, + unsigned int *num) +{ + char *end; + unsigned long l; + + l = strtoul(buffer + tok->start, &end, 0); + if (end != buffer + tok->end) + return false; + + *num = l; + + /* Check for overflow */ + if (l == ULONG_MAX && errno == ERANGE) + return false; + + if (*num != l) + return false; + + return true; +} + +bool json_tok_is_null(const char *buffer, const jsmntok_t *tok) +{ + if (tok->type != JSMN_PRIMITIVE) + return false; + return buffer[tok->start] == 'n'; +} + +const jsmntok_t *json_next(const jsmntok_t *tok) +{ + const jsmntok_t *t; + size_t i; + + for (t = tok + 1, i = 0; i < tok->size; i++) + t = json_next(t); + + return t; +} + +const jsmntok_t *json_get_member(const char *buffer, const jsmntok_t tok[], + const char *label) +{ + const jsmntok_t *t, *end; + + assert(tok->type == JSMN_OBJECT); + + end = json_next(tok); + for (t = tok + 1; t < end; t = json_next(t+1)) + if (json_tok_streq(buffer, t, label)) + return t + 1; + + return NULL; +} + +const jsmntok_t *json_get_arr(const char *buffer, const jsmntok_t tok[], + size_t index) +{ + const jsmntok_t *t, *end; + + assert(tok->type == JSMN_ARRAY); + + end = json_next(tok); + for (t = tok + 1; t < end; t = json_next(t)) { + if (index == 0) + return t; + index--; + } + + return NULL; +} + +/* Guide is a string with . for members, [] around indexes. */ +const jsmntok_t *json_delve(const char *buffer, + const jsmntok_t *tok, + const char *guide) +{ + while (*guide) { + const char *key; + size_t len = strcspn(guide+1, ".[]"); + + key = tal_strndup(NULL, guide+1, len); + switch (guide[0]) { + case '.': + if (tok->type != JSMN_OBJECT) + return tal_free(key); + tok = json_get_member(buffer, tok, key); + if (!tok) + return tal_free(key); + break; + case '[': + if (tok->type != JSMN_ARRAY) + return tal_free(key); + tok = json_get_arr(buffer, tok, atol(key)); + if (!tok) + return tal_free(key); + /* Must be terminated */ + assert(guide[1+strlen(key)] == ']'); + len++; + break; + default: + abort(); + } + tal_free(key); + guide += len + 1; + } + + return tok; +} + +void json_get_params(const char *buffer, const jsmntok_t param[], ...) +{ + va_list ap; + const char *name; + const jsmntok_t **tokptr, *p, *end; + + if (param->type == JSMN_ARRAY) { + if (param->size == 0) + p = NULL; + else + p = param + 1; + end = json_next(param); + } else + assert(param->type == JSMN_OBJECT); + + va_start(ap, param); + while ((name = va_arg(ap, const char *)) != NULL) { + tokptr = va_arg(ap, const jsmntok_t **); + if (param->type == JSMN_ARRAY) { + *tokptr = p; + if (p) { + p = json_next(p); + if (p == end) + p = NULL; + } + } else { + *tokptr = json_get_member(buffer, param, name); + } + /* Convert 'null' to NULL */ + if (*tokptr + && (*tokptr)->type == JSMN_PRIMITIVE + && buffer[(*tokptr)->start] == 'n') { + *tokptr = NULL; + } + } + + va_end(ap); +} + +jsmntok_t *json_parse_input(const char *input, int len, bool *valid) +{ + jsmn_parser parser; + jsmntok_t *toks; + jsmnerr_t ret; + + toks = tal_arr(input, jsmntok_t, 10); + +again: + jsmn_init(&parser); + ret = jsmn_parse(&parser, input, len, toks, tal_count(toks) - 1); + + switch (ret) { + case JSMN_ERROR_INVAL: + *valid = false; + return tal_free(toks); + case JSMN_ERROR_PART: + *valid = true; + return tal_free(toks); + case JSMN_ERROR_NOMEM: + tal_resize(&toks, tal_count(toks) * 2); + goto again; + } + + /* Cut to length and return. */ + *valid = true; + tal_resize(&toks, ret + 1); + /* Make sure last one is always referencable. */ + toks[ret].type = -1; + toks[ret].start = toks[ret].end = toks[ret].size = 0; + + return toks; +} + +static void result_append(struct json_result *res, const char *str) +{ + size_t len = tal_count(res->s) - 1; + + tal_resize(&res->s, len + strlen(str) + 1); + strcpy(res->s + len, str); +} + +static void PRINTF_FMT(2,3) +result_append_fmt(struct json_result *res, const char *fmt, ...) +{ + size_t len = tal_count(res->s) - 1, fmtlen; + va_list ap; + + va_start(ap, fmt); + fmtlen = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + + tal_resize(&res->s, len + fmtlen + 1); + va_start(ap, fmt); + vsprintf(res->s + len, fmt, ap); + va_end(ap); +} + +static bool result_ends_with(struct json_result *res, const char *str) +{ + size_t len = tal_count(res->s) - 1; + + if (strlen(str) > len) + return false; + return streq(res->s + len - strlen(str), str); +} + +static void json_start_member(struct json_result *result, const char *fieldname) +{ + /* Prepend comma if required. */ + if (result->s[0] + && !result_ends_with(result, "{ ") + && !result_ends_with(result, "[ ")) + result_append(result, ", "); + if (fieldname) + result_append_fmt(result, "\"%s\" : ", fieldname); +} + +void json_array_start(struct json_result *result, const char *fieldname) +{ + json_start_member(result, fieldname); + if (result->indent) { + unsigned int i; + result_append(result, "\n"); + for (i = 0; i < result->indent; i++) + result_append(result, "\t"); + } + result_append(result, "[ "); + result->indent++; +} + +void json_array_end(struct json_result *result) +{ + assert(result->indent); + result->indent--; + result_append(result, " ]"); +} + +void json_object_start(struct json_result *result, const char *fieldname) +{ + json_start_member(result, fieldname); + if (result->indent) { + unsigned int i; + result_append(result, "\n"); + for (i = 0; i < result->indent; i++) + result_append(result, "\t"); + } + result_append(result, "{ "); + result->indent++; +} + +void json_object_end(struct json_result *result) +{ + assert(result->indent); + result->indent--; + result_append(result, " }"); +} + +void json_add_num(struct json_result *result, const char *fieldname, unsigned int value) +{ + json_start_member(result, fieldname); + result_append_fmt(result, "%u", value); +} + +void json_add_literal(struct json_result *result, const char *fieldname, + const char *literal, int len) +{ + json_start_member(result, fieldname); + result_append_fmt(result, "%.*s", len, literal); +} + +void json_add_string(struct json_result *result, const char *fieldname, const char *value) +{ + json_start_member(result, fieldname); + result_append_fmt(result, "\"%s\"", value); +} + +void json_add_bool(struct json_result *result, const char *fieldname, bool value) +{ + json_start_member(result, fieldname); + result_append(result, value ? "true" : "false"); +} + +void json_add_null(struct json_result *result, const char *fieldname) +{ + json_start_member(result, fieldname); + result_append(result, "null"); +} + +void json_add_hex(struct json_result *result, const char *fieldname, + const void *data, size_t len) +{ + char hex[hex_str_size(len)]; + + hex_encode(data, len, hex, sizeof(hex)); + json_add_string(result, fieldname, hex); +} + +void json_add_object(struct json_result *result, ...) +{ + va_list ap; + const char *field; + + va_start(ap, result); + json_object_start(result, NULL); + while ((field = va_arg(ap, const char *)) != NULL) { + jsmntype_t type = va_arg(ap, jsmntype_t); + const char *value = va_arg(ap, const char *); + if (type == JSMN_STRING) + json_add_string(result, field, value); + else + json_add_literal(result, field, value, strlen(value)); + } + json_object_end(result); + va_end(ap); +} + +struct json_result *new_json_result(const tal_t *ctx) +{ + struct json_result *r = tal(ctx, struct json_result); + + /* Using tal_arr means that it has a valid count. */ + r->s = tal_arrz(r, char, 1); + r->indent = 0; + return r; +} + +const char *json_result_string(const struct json_result *result) +{ + assert(!result->indent); + assert(tal_count(result->s) == strlen(result->s) + 1); + return result->s; +} diff --git a/daemon/json.h b/daemon/json.h new file mode 100644 index 000000000..c333803fc --- /dev/null +++ b/daemon/json.h @@ -0,0 +1,87 @@ +#ifndef LIGHTNING_DAEMON_JSON_H +#define LIGHTNING_DAEMON_JSON_H +#include "config.h" +#include "stdbool.h" +#include "stdlib.h" +#include + +#define JSMN_STRICT 1 +# include "jsmn/jsmn.h" + +struct json_result; + +/* Include " if it's a string. */ +const char *json_tok_contents(const char *buffer, const jsmntok_t *t); + +/* Include " if it's a string. */ +int json_tok_len(const jsmntok_t *t); + +/* Is this a string equal to str? */ +bool json_tok_streq(const char *buffer, const jsmntok_t *tok, const char *str); + +/* Extract number from this (may be a string, or a number literal) */ +bool json_tok_number(const char *buffer, const jsmntok_t *tok, + unsigned int *num); + +/* Is this the null primitive? */ +bool json_tok_is_null(const char *buffer, const jsmntok_t *tok); + +/* Returns next token with same parent. */ +const jsmntok_t *json_next(const jsmntok_t *tok); + +/* Get the parameters (by position or name). Followed by pairs + * of const char *name, const jsmntok_t **ret_ptr, then NULL. + * *ret_ptr will be set to NULL if it's a literal 'null' or not present. + */ +void json_get_params(const char *buffer, const jsmntok_t param[], ...); + +/* Get top-level member. */ +const jsmntok_t *json_get_member(const char *buffer, const jsmntok_t tok[], + const char *label); + +/* Get index'th array member. */ +const jsmntok_t *json_get_arr(const char *buffer, const jsmntok_t tok[], + size_t index); + +/* Guide is a string with . for members, [] around indexes. */ +const jsmntok_t *json_delve(const char *buffer, + const jsmntok_t *tok, + const char *guide); + +/* If input is complete and valid, return tokens. */ +jsmntok_t *json_parse_input(const char *input, int len, bool *valid); + +/* Creating JSON strings */ + +/* '"fieldname" : [ ' or '[ ' if fieldname is NULL */ +void json_array_start(struct json_result *ptr, const char *fieldname); +/* '"fieldname" : { ' or '{ ' if fieldname is NULL */ +void json_object_start(struct json_result *ptr, const char *fieldname); +/* ' ], ' */ +void json_array_end(struct json_result *ptr); +/* ' }, ' */ +void json_object_end(struct json_result *ptr); + +struct json_result *new_json_result(const tal_t *ctx); + +/* '"fieldname" : "value"' or '"value"' if fieldname is NULL*/ +void json_add_string(struct json_result *result, const char *fieldname, const char *value); +/* '"fieldname" : literal' or 'literal' if fieldname is NULL*/ +void json_add_literal(struct json_result *result, const char *fieldname, + const char *literal, int len); +/* '"fieldname" : value' or 'value' if fieldname is NULL */ +void json_add_num(struct json_result *result, const char *fieldname, + unsigned int value); +/* '"fieldname" : true|false' or 'true|false' if fieldname is NULL */ +void json_add_bool(struct json_result *result, const char *fieldname, + bool value); +/* '"fieldname" : null' or 'null' if fieldname is NULL */ +void json_add_null(struct json_result *result, const char *fieldname); +/* '"fieldname" : "0189abcdef..."' or "0189abcdef..." if fieldname is NULL */ +void json_add_hex(struct json_result *result, const char *fieldname, + const void *data, size_t len); + +void json_add_object(struct json_result *result, ...); + +const char *json_result_string(const struct json_result *result); +#endif /* LIGHTNING_DAEMON_JSON_H */