core-lightning/lightningd/params.c
Rusty Russell 6ff901d7b0 params: simplify lifetimes of params.
@wythe points out we don't need to keep the around now param_is_set()
is removed.  We can in fact go further and avoid marshalling them into
temporary objects at the caller altogether.

This means internally we have an array of struct param, rather than an
array of 'struct param *', which causes most of the noise in this
patch.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2018-07-05 00:19:16 +00:00

308 lines
7.4 KiB
C

#include <ccan/asort/asort.h>
#include <ccan/tal/str/str.h>
#include <common/utils.h>
#include <lightningd/json.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/jsonrpc_errors.h>
#include <lightningd/lightningd.h>
#include <lightningd/params.h>
struct param {
const tal_t *ctx;
const char *name;
bool is_set;
param_cb cb;
void *arg;
size_t argsize;
};
static void param_add(struct param **params,
const char *name, param_cb cb, void *arg,
const tal_t *ctx, size_t argsize)
{
#if DEVELOPER
assert(name);
assert(cb);
assert(arg);
#endif
struct param *last;
tal_resize(params, tal_count(*params) + 1);
last = &(*params)[tal_count(*params) - 1];
last->ctx = ctx;
last->is_set = false;
last->name = name;
last->cb = cb;
last->arg = arg;
last->argsize = argsize;
/* Non-NULL means we are supposed to allocate iff found */
if (last->ctx)
*(void **)last->arg = NULL;
}
struct fail_format {
void *cb;
const char *format;
};
static struct fail_format fail_formats[] = {
{json_tok_bool, "'%s' should be 'true' or 'false', not '%.*s'"},
{json_tok_double, "'%s' should be a double, not '%.*s'"},
{json_tok_u64, "'%s' should be an unsigned 64 bit integer, not '%.*s'"},
{json_tok_number, "'%s' should be an integer, not '%.*s'"},
{json_tok_wtx,
"'%s' should be 'all' or a positive integer greater than "
"545, not '%.*s'"},
{NULL, "'%s' of '%.*s' is invalid'"}
};
static const char *find_fail_format(param_cb cb)
{
struct fail_format *fmt = fail_formats;
while (fmt->cb != NULL) {
if (fmt->cb == cb)
break;
fmt++;
}
return fmt->format;
}
static bool make_callback(struct command *cmd,
struct param *def,
const char *buffer, const jsmntok_t * tok)
{
void *arg;
def->is_set = true;
if (def->argsize && def->cb != (param_cb)json_tok_tok) {
*(void **)def->arg
= arg
= tal_alloc_(def->ctx, def->argsize, false, false,
"param");
} else
arg = def->arg;
if (!def->cb(buffer, tok, arg)) {
struct json_result *data = new_json_result(cmd);
const char *val = tal_fmt(cmd, "%.*s", tok->end - tok->start,
buffer + tok->start);
json_object_start(data, NULL);
json_add_string(data, def->name, val);
json_object_end(data);
command_fail_detailed(cmd, JSONRPC2_INVALID_PARAMS, data,
find_fail_format(def->cb), def->name,
tok->end - tok->start,
buffer + tok->start);
return false;
}
return true;
}
static struct param *post_check(struct command *cmd, struct param *params)
{
struct param *first = params;
struct param *last = first + tal_count(params);
/* Make sure required params were provided. */
while (first != last && first->argsize == 0) {
if (!first->is_set) {
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"missing required parameter: '%s'",
first->name);
return NULL;
}
first++;
}
return params;
}
static bool parse_by_position(struct command *cmd,
struct param *params,
const char *buffer,
const jsmntok_t tokens[])
{
const jsmntok_t *tok = tokens + 1;
const jsmntok_t *end = json_next(tokens);
struct param *first = params;
struct param *last = first + tal_count(params);
while (first != last && tok != end) {
if (!json_tok_is_null(buffer, tok))
if (!make_callback(cmd, first, buffer, tok))
return NULL;
tok = json_next(tok);
first++;
}
/* check for unexpected trailing params */
if (tok != end) {
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"too many parameters:"
" got %u, expected %zu",
tokens->size, tal_count(params));
return false;
}
return post_check(cmd, params);
}
static struct param *find_param(struct param *params, const char *start,
size_t n)
{
struct param *first = params;
struct param *last = first + tal_count(params);
while (first != last) {
if (strncmp(first->name, start, n) == 0)
if (strlen(first->name) == n)
return first;
first++;
}
return NULL;
}
static bool parse_by_name(struct command *cmd,
struct param *params,
const char *buffer,
const jsmntok_t tokens[])
{
const jsmntok_t *first = tokens + 1;
const jsmntok_t *last = json_next(tokens);
while (first != last) {
struct param *p = find_param(params, buffer + first->start,
first->end - first->start);
if (!p) {
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"unknown parameter: '%.*s'",
first->end - first->start,
buffer + first->start);
return false;
}
if (p->is_set) {
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"duplicate json names: '%s'", p->name);
return false;
}
if (!make_callback(cmd, p, buffer, first + 1))
return false;
first = json_next(first + 1);
}
return post_check(cmd, params);
}
#if DEVELOPER
static int comp_by_name(const struct param *a, const struct param *b,
void *unused)
{
return strcmp(a->name, b->name);
}
static int comp_by_arg(const struct param *a, const struct param *b,
void *unused)
{
/* size_t could be larger than int: don't turn a 4bn difference into 0 */
if (a->arg > b->arg)
return 1;
else if (a->arg < b->arg)
return -1;
return 0;
}
/* This comparator is a bit different, but works well.
* Return 0 if @a is optional and @b is required. Otherwise return 1.
*/
static int comp_req_order(const struct param *a, const struct param *b,
void *unused)
{
if (a->argsize != 0 && b->argsize == 0)
return 0;
return 1;
}
/*
* Make sure 2 sequential items in @params are not equal (based on
* provided comparator).
*/
static void check_distinct(struct param *params,
int (*compar) (const struct param *a,
const struct param *b, void *unused))
{
struct param *first = params;
struct param *last = first + tal_count(params);
first++;
while (first != last) {
assert(compar(first - 1, first, NULL) != 0);
first++;
}
}
static void check_unique(struct param *copy,
int (*compar) (const struct param *a,
const struct param *b, void *unused))
{
asort(copy, tal_count(copy), compar, NULL);
check_distinct(copy, compar);
}
/*
* Verify consistent internal state.
*/
static void check_params(struct param *params)
{
if (tal_count(params) < 2)
return;
/* make sure there are no required params following optional */
check_distinct(params, comp_req_order);
/* duplicate so we can sort */
struct param *copy = tal_dup_arr(params, struct param,
params, tal_count(params), 0);
/* check for repeated names and args */
check_unique(copy, comp_by_name);
check_unique(copy, comp_by_arg);
tal_free(copy);
}
#endif
static bool param_parse_arr(struct command *cmd,
const char *buffer,
const jsmntok_t tokens[],
struct param *params)
{
#if DEVELOPER
check_params(params);
#endif
if (tokens->type == JSMN_ARRAY)
return parse_by_position(cmd, params, buffer, tokens);
else if (tokens->type == JSMN_OBJECT)
return parse_by_name(cmd, params, buffer, tokens);
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Expected array or object for params");
return false;
}
bool param_parse(struct command *cmd, const char *buffer,
const jsmntok_t tokens[], ...)
{
struct param *params = tal_arr(tmpctx, struct param, 0);
const char *name;
va_list ap;
va_start(ap, tokens);
while ((name = va_arg(ap, const char *)) != NULL) {
param_cb cb = va_arg(ap, param_cb);
void *arg = va_arg(ap, void *);
const tal_t *ctx = va_arg(ap, const tal_t *);
size_t argsize = va_arg(ap, size_t);
param_add(&params, name, cb, arg, ctx, argsize);
}
va_end(ap);
return param_parse_arr(cmd, buffer, tokens, params);
}