mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-10 07:37:05 +01:00
c3f433ec66
It turns out we were heavily relying on the fact that after each message from the client there'd be a flush, and that there would not be anything after the JSON object we read. This will no longer be the case once we start streaming things or we are very quick in issuing the JSON-RPC requests. This just takes one of the error paths (incomplete read) and makes it into a successful path if we have indeed read a full root element.
456 lines
10 KiB
C
456 lines
10 KiB
C
/* JSON core and helpers */
|
|
#include "json.h"
|
|
#include "json_escaped.h"
|
|
#include <assert.h>
|
|
#include <ccan/build_assert/build_assert.h>
|
|
#include <ccan/str/hex/hex.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
struct json_result {
|
|
/* tal_arr of types we're enclosed in. */
|
|
jsmntype_t *wrapping;
|
|
|
|
/* 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_to_u64(const char *buffer, const jsmntok_t *tok,
|
|
uint64_t *num)
|
|
{
|
|
char *end;
|
|
unsigned long long l;
|
|
|
|
l = strtoull(buffer + tok->start, &end, 0);
|
|
if (end != buffer + tok->end)
|
|
return false;
|
|
|
|
BUILD_ASSERT(sizeof(l) >= sizeof(*num));
|
|
*num = l;
|
|
|
|
/* Check for overflow */
|
|
if (l == ULLONG_MAX && errno == ERANGE)
|
|
return false;
|
|
|
|
if (*num != l)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool json_to_double(const char *buffer, const jsmntok_t *tok, double *num)
|
|
{
|
|
char *end;
|
|
|
|
*num = strtod(buffer + tok->start, &end);
|
|
if (end != buffer + tok->end)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool json_to_number(const char *buffer, const jsmntok_t *tok,
|
|
unsigned int *num)
|
|
{
|
|
uint64_t u64;
|
|
|
|
if (!json_to_u64(buffer, tok, &u64))
|
|
return false;
|
|
*num = u64;
|
|
|
|
/* Just in case it doesn't fit. */
|
|
if (*num != u64)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool json_tok_bitcoin_amount(const char *buffer, const jsmntok_t *tok,
|
|
uint64_t *satoshi)
|
|
{
|
|
char *end;
|
|
unsigned long btc, sat;
|
|
|
|
btc = strtoul(buffer + tok->start, &end, 10);
|
|
if (btc == ULONG_MAX && errno == ERANGE)
|
|
return false;
|
|
if (end != buffer + tok->end) {
|
|
/* Expect always 8 decimal places. */
|
|
if (*end != '.' || buffer + tok->end - end != 9)
|
|
return false;
|
|
sat = strtoul(end+1, &end, 10);
|
|
if (sat == ULONG_MAX && errno == ERANGE)
|
|
return false;
|
|
if (end != buffer + tok->end)
|
|
return false;
|
|
} else
|
|
sat = 0;
|
|
|
|
*satoshi = btc * (uint64_t)100000000 + sat;
|
|
if (*satoshi != btc * (uint64_t)100000000 + sat)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool json_tok_is_num(const char *buffer, const jsmntok_t *tok)
|
|
{
|
|
if (tok->type != JSMN_PRIMITIVE)
|
|
return false;
|
|
|
|
for (int i = tok->start; i < tok->end; i++)
|
|
if (!cisdigit(buffer[i]))
|
|
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;
|
|
|
|
if (tok->type != JSMN_OBJECT)
|
|
return NULL;
|
|
|
|
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 jsmntok_t tok[], size_t index)
|
|
{
|
|
const jsmntok_t *t, *end;
|
|
|
|
if (tok->type != JSMN_ARRAY)
|
|
return NULL;
|
|
|
|
end = json_next(tok);
|
|
for (t = tok + 1; t < end; t = json_next(t)) {
|
|
if (index == 0)
|
|
return t;
|
|
index--;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
jsmntok_t *json_parse_input(const char *input, int len, bool *valid)
|
|
{
|
|
jsmn_parser parser;
|
|
jsmntok_t *toks;
|
|
int ret;
|
|
|
|
toks = tal_arr(input, jsmntok_t, 10);
|
|
toks[0].type = JSMN_UNDEFINED;
|
|
|
|
jsmn_init(&parser);
|
|
again:
|
|
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_NOMEM:
|
|
tal_resize(&toks, tal_count(toks) * 2);
|
|
goto again;
|
|
}
|
|
|
|
/* Check whether we read at least one full root element, i.e., root
|
|
* element has its end set. */
|
|
if (toks[0].type == JSMN_UNDEFINED || toks[0].end == -1) {
|
|
*valid = true;
|
|
return tal_free(toks);
|
|
}
|
|
|
|
/* If we read a partial element at the end of the stream we'll get a
|
|
* ret=JSMN_ERROR_PART, but due to the previous check we know we read at
|
|
* least one full element, so count tokens that are part of this root
|
|
* element. */
|
|
ret = json_next(toks) - toks;
|
|
|
|
/* Cut to length and return. */
|
|
*valid = true;
|
|
tal_resize(&toks, ret + 1);
|
|
/* Make sure last one is always referenceable. */
|
|
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 check_fieldname(const struct json_result *result,
|
|
const char *fieldname)
|
|
{
|
|
size_t n = tal_count(result->wrapping);
|
|
if (n == 0)
|
|
/* Can't have a fieldname if not in anything! */
|
|
assert(!fieldname);
|
|
else if (result->wrapping[n-1] == JSMN_ARRAY)
|
|
/* No fieldnames in arrays. */
|
|
assert(!fieldname);
|
|
else
|
|
/* Must have fieldnames in objects. */
|
|
assert(fieldname);
|
|
}
|
|
|
|
static void result_add_indent(struct json_result *result);
|
|
|
|
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, ", \n");
|
|
else
|
|
result_append(result, "\n");
|
|
|
|
result_add_indent(result);
|
|
|
|
check_fieldname(result, fieldname);
|
|
if (fieldname)
|
|
result_append_fmt(result, "\"%s\": ", fieldname);
|
|
}
|
|
|
|
static void result_add_indent(struct json_result *result)
|
|
{
|
|
size_t i, indent = tal_count(result->wrapping);
|
|
|
|
if (!indent)
|
|
return;
|
|
|
|
for (i = 0; i < indent; i++)
|
|
result_append(result, " ");
|
|
}
|
|
|
|
static void result_add_wrap(struct json_result *result, jsmntype_t type)
|
|
{
|
|
size_t indent = tal_count(result->wrapping);
|
|
|
|
tal_resize(&result->wrapping, indent+1);
|
|
result->wrapping[indent] = type;
|
|
}
|
|
|
|
static void result_pop_wrap(struct json_result *result, jsmntype_t type)
|
|
{
|
|
size_t indent = tal_count(result->wrapping);
|
|
|
|
assert(indent);
|
|
assert(result->wrapping[indent-1] == type);
|
|
tal_resize(&result->wrapping, indent-1);
|
|
}
|
|
|
|
void json_array_start(struct json_result *result, const char *fieldname)
|
|
{
|
|
json_start_member(result, fieldname);
|
|
result_append(result, "[");
|
|
result_add_wrap(result, JSMN_ARRAY);
|
|
}
|
|
|
|
void json_array_end(struct json_result *result)
|
|
{
|
|
result_append(result, "\n");
|
|
result_pop_wrap(result, JSMN_ARRAY);
|
|
result_add_indent(result);
|
|
result_append(result, "]");
|
|
}
|
|
|
|
void json_object_start(struct json_result *result, const char *fieldname)
|
|
{
|
|
json_start_member(result, fieldname);
|
|
result_append(result, "{");
|
|
result_add_wrap(result, JSMN_OBJECT);
|
|
}
|
|
|
|
void json_object_end(struct json_result *result)
|
|
{
|
|
result_append(result, "\n");
|
|
result_pop_wrap(result, JSMN_OBJECT);
|
|
result_add_indent(result);
|
|
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_double(struct json_result *result, const char *fieldname, double value)
|
|
{
|
|
json_start_member(result, fieldname);
|
|
result_append_fmt(result, "%f", value);
|
|
}
|
|
|
|
void json_add_u64(struct json_result *result, const char *fieldname,
|
|
uint64_t value)
|
|
{
|
|
json_start_member(result, fieldname);
|
|
result_append_fmt(result, "%"PRIu64, 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)
|
|
{
|
|
struct json_escaped *esc = json_partial_escape(NULL, value);
|
|
|
|
json_start_member(result, fieldname);
|
|
result_append_fmt(result, "\"%s\"", esc->s);
|
|
tal_free(esc);
|
|
}
|
|
|
|
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_hex(struct json_result *result, const char *fieldname,
|
|
const void *data, size_t len)
|
|
{
|
|
char *hex = tal_arr(NULL, char, hex_str_size(len));
|
|
|
|
hex_encode(data, len, hex, hex_str_size(len));
|
|
json_add_string(result, fieldname, hex);
|
|
tal_free(hex);
|
|
}
|
|
|
|
void json_add_hex_talarr(struct json_result *result,
|
|
const char *fieldname,
|
|
const tal_t *data)
|
|
{
|
|
json_add_hex(result, fieldname, data, tal_bytelen(data));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void json_add_escaped_string(struct json_result *result, const char *fieldname,
|
|
const struct json_escaped *esc TAKES)
|
|
{
|
|
json_start_member(result, fieldname);
|
|
result_append_fmt(result, "\"%s\"", esc->s);
|
|
if (taken(esc))
|
|
tal_free(esc);
|
|
}
|
|
|
|
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->wrapping = tal_arr(r, jsmntype_t, 0);
|
|
return r;
|
|
}
|
|
|
|
const char *json_result_string(const struct json_result *result)
|
|
{
|
|
assert(tal_count(result->wrapping) == 0);
|
|
assert(tal_count(result->s) == strlen(result->s) + 1);
|
|
return result->s;
|
|
}
|