mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 09:54:16 +01:00
daemon/json: helpers for using jsmn.
Also taken from pettycoin. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
0c81b0918a
commit
ac4491909c
@ -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
|
||||
|
383
daemon/json.c
Normal file
383
daemon/json.c
Normal file
@ -0,0 +1,383 @@
|
||||
/* JSON core and helpers */
|
||||
#include "json.h"
|
||||
#include <assert.h>
|
||||
#include <ccan/str/hex/hex.h>
|
||||
#include <ccan/tal/str/str.h>
|
||||
#include <ccan/tal/tal.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
87
daemon/json.h
Normal file
87
daemon/json.h
Normal file
@ -0,0 +1,87 @@
|
||||
#ifndef LIGHTNING_DAEMON_JSON_H
|
||||
#define LIGHTNING_DAEMON_JSON_H
|
||||
#include "config.h"
|
||||
#include "stdbool.h"
|
||||
#include "stdlib.h"
|
||||
#include <ccan/tal/tal.h>
|
||||
|
||||
#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 */
|
Loading…
Reference in New Issue
Block a user