core-lightning/common/param.c

304 lines
7.0 KiB
C
Raw Normal View History

#include <ccan/asort/asort.h>
#include <ccan/tal/str/str.h>
#include <common/json_command.h>
#include <common/jsonrpc_errors.h>
#include <common/param.h>
#include <common/utils.h>
struct param {
const char *name;
bool is_set;
bool required;
param_cbx cbx;
void *arg;
};
static bool param_add(struct param **params,
const char *name, bool required,
param_cbx cbx, void *arg)
{
#if DEVELOPER
if (!(name && cbx && arg))
return false;
#endif
struct param last;
last.is_set = false;
last.name = name;
last.required = required;
last.cbx = cbx;
last.arg = arg;
tal_arr_expand(params, last);
return true;
}
static struct command_result *make_callback(struct command *cmd,
struct param *def,
const char *buffer,
const jsmntok_t *tok)
{
def->is_set = true;
return def->cbx(cmd, def->name, buffer, tok, def->arg);
}
static struct command_result *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->required) {
if (!first->is_set) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"missing required parameter: %s",
first->name);
}
first++;
}
return NULL;
}
static struct command_result *parse_by_position(struct command *cmd,
struct param *params,
const char *buffer,
const jsmntok_t tokens[],
bool allow_extra)
{
struct command_result *res;
const jsmntok_t *tok;
size_t i;
json_for_each_arr(i, tok, tokens) {
/* check for unexpected trailing params */
if (i == tal_count(params)) {
if (!allow_extra) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"too many parameters:"
" got %u, expected %zu",
tokens->size,
tal_count(params));
}
break;
}
if (!json_tok_is_null(buffer, tok)) {
res = make_callback(cmd, params+i, buffer, tok);
if (res)
return res;
}
}
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 struct command_result *parse_by_name(struct command *cmd,
struct param *params,
const char *buffer,
const jsmntok_t tokens[],
bool allow_extra)
{
size_t i;
const jsmntok_t *t;
json_for_each_obj(i, t, tokens) {
struct param *p = find_param(params, buffer + t->start,
t->end - t->start);
if (!p) {
if (!allow_extra) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"unknown parameter: %.*s",
t->end - t->start,
buffer + t->start);
}
} else {
struct command_result *res;
if (p->is_set) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"duplicate json names: %s",
p->name);
}
res = make_callback(cmd, p, buffer, t + 1);
if (res)
return res;
}
}
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->required && b->required)
return 0;
return 1;
}
/*
* Make sure 2 sequential items in @params are not equal (based on
* provided comparator).
*/
static bool 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) {
if (compar(first - 1, first, NULL) == 0)
return false;
first++;
}
return true;
}
static bool check_unique(struct param *copy,
int (*compar) (const struct param *a,
const struct param *b, void *unused))
{
asort(copy, tal_count(copy), compar, NULL);
return check_distinct(copy, compar);
}
/*
* Verify consistent internal state.
*/
static bool check_params(struct param *params)
{
if (tal_count(params) < 2)
return true;
/* make sure there are no required params following optional */
if (!check_distinct(params, comp_req_order))
return false;
/* 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 */
if (!check_unique(copy, comp_by_name))
return false;
if (!check_unique(copy, comp_by_arg))
return false;
tal_free(copy);
return true;
}
#endif
static char *param_usage(const tal_t *ctx,
const struct param *params)
{
char *usage = tal_strdup(ctx, "");
for (size_t i = 0; i < tal_count(params); i++) {
if (i != 0)
tal_append_fmt(&usage, " ");
if (params[i].required)
tal_append_fmt(&usage, "%s", params[i].name);
else
tal_append_fmt(&usage, "[%s]", params[i].name);
}
return usage;
}
static struct command_result *param_arr(struct command *cmd, const char *buffer,
const jsmntok_t tokens[],
struct param *params,
bool allow_extra)
{
#if DEVELOPER
if (!check_params(params)) {
return command_fail(cmd, PARAM_DEV_ERROR,
"developer error: check_params");
}
#endif
if (tokens->type == JSMN_ARRAY)
return parse_by_position(cmd, params, buffer, tokens, allow_extra);
else if (tokens->type == JSMN_OBJECT)
return parse_by_name(cmd, params, buffer, tokens, allow_extra);
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Expected array or object for params");
}
bool param(struct command *cmd, const char *buffer,
const jsmntok_t tokens[], ...)
{
struct param *params = tal_arr(cmd, struct param, 0);
const char *name;
va_list ap;
bool allow_extra = false;
va_start(ap, tokens);
while ((name = va_arg(ap, const char *)) != NULL) {
bool required = va_arg(ap, int);
param_cbx cbx = va_arg(ap, param_cbx);
void *arg = va_arg(ap, void *);
if (streq(name, "")) {
allow_extra = true;
continue;
}
if (!param_add(&params, name, required, cbx, arg)) {
/* We really do ignore this return! */
if (command_fail(cmd, PARAM_DEV_ERROR,
"developer error: param_add %s", name))
;
va_end(ap);
return false;
}
}
va_end(ap);
if (command_usage_only(cmd)) {
command_set_usage(cmd, param_usage(cmd, params));
return false;
}
/* Always return false if we're simply checking command parameters;
* normally this returns true if all parameters are valid. */
return param_arr(cmd, buffer, tokens, params, allow_extra) == NULL
&& !command_check_only(cmd);
}