Browse Source
This is part of #1464 and incorporates Rusty's suggested updates from #1569. See comment in param.h for description, here's the basics: unsigned cltv; const jsmntok_t *note; u64 msatoshi; struct param * mp; if (!param_parse(cmd, buffer, tokens, param_req("cltv", json_tok_number, &cltv), param_opt("note", json_tok_tok, ¬e), mp = param_opt("msatoshi", json_tok_u64, &msatoshi), NULL)) return; if (param_is_set(mp)) do_something() There is a lot of developer mode code to make sure we don't make mistakes, like trying to unmarshal into the same variable twice or adding a required param after optional. During testing, I found a bug (of sorts) in the current system. It allows you to provide two named parameters with the same name without error; e.g.: # cli/lightning-cli -k newaddr addresstype=p2sh-segwit addresstype=bech32 { "address": "2N3r6fT65PhfhE1mcMS6TtcdaEurud6M7pA" } It just takes the first and ignores the second. The new system reports this as an error for now. We can always change this later.ppa-0.6.1
Mark Beckwith
7 years ago
committed by
Rusty Russell
6 changed files with 851 additions and 0 deletions
@ -0,0 +1,297 @@ |
|||||
|
#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 { |
||||
|
char *name; |
||||
|
bool required; |
||||
|
bool is_set; |
||||
|
param_cb cb; |
||||
|
void *arg; |
||||
|
}; |
||||
|
|
||||
|
void *param_is_set(struct param *def) |
||||
|
{ |
||||
|
return def->is_set ? def->arg : NULL; |
||||
|
} |
||||
|
|
||||
|
struct param *param_add_(bool required, char *name, param_cb cb, void *arg) |
||||
|
{ |
||||
|
#if DEVELOPER |
||||
|
assert(name); |
||||
|
assert(cb); |
||||
|
assert(arg); |
||||
|
#endif |
||||
|
struct param *last = tal(tmpctx, struct param); |
||||
|
last->is_set = false; |
||||
|
last->name = tal_strdup(last, name); |
||||
|
last->cb = cb; |
||||
|
last->arg = arg; |
||||
|
last->required = required; |
||||
|
return last; |
||||
|
} |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
def->is_set = true; |
||||
|
if (!def->cb(buffer, tok, def->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)->required) { |
||||
|
if (!(*first)->is_set) { |
||||
|
command_fail(cmd, JSONRPC2_INVALID_PARAMS, |
||||
|
"missing required parameter: '%s'", |
||||
|
(*first)->name); |
||||
|
return NULL; |
||||
|
} |
||||
|
first++; |
||||
|
} |
||||
|
|
||||
|
/* Set optional missing jsmntok_t args to NULL. */ |
||||
|
while (first != last) { |
||||
|
struct param *p = *first; |
||||
|
if (!p->is_set && (p->cb == (param_cb) json_tok_tok)) { |
||||
|
jsmntok_t **tok = p->arg; |
||||
|
*tok = NULL; |
||||
|
} |
||||
|
first++; |
||||
|
} |
||||
|
return params; |
||||
|
} |
||||
|
|
||||
|
static struct param **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 NULL; |
||||
|
} |
||||
|
|
||||
|
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 param **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 NULL; |
||||
|
} |
||||
|
|
||||
|
if (p->is_set) { |
||||
|
command_fail(cmd, JSONRPC2_INVALID_PARAMS, |
||||
|
"duplicate json names: '%s'", p->name); |
||||
|
return NULL; |
||||
|
} |
||||
|
|
||||
|
if (!make_callback(cmd, p, buffer, first + 1)) |
||||
|
return NULL; |
||||
|
first = json_next(first + 1); |
||||
|
} |
||||
|
return post_check(cmd, params); |
||||
|
} |
||||
|
|
||||
|
#if DEVELOPER |
||||
|
static int comp_by_name(const void *a, const void *b) |
||||
|
{ |
||||
|
const char *x = (*(const struct param **) a)->name; |
||||
|
const char *y = (*(const struct param **) b)->name; |
||||
|
return strcmp(x, y); |
||||
|
} |
||||
|
|
||||
|
static int comp_by_arg(const void *a, const void *b) |
||||
|
{ |
||||
|
size_t x = (size_t) ((*(const struct param **) a)->arg); |
||||
|
size_t y = (size_t) ((*(const struct param **) b)->arg); |
||||
|
return x - y; |
||||
|
} |
||||
|
|
||||
|
/* 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 void *a, const void *b) |
||||
|
{ |
||||
|
bool x = (bool) ((*(const struct param **) a)->required); |
||||
|
bool y = (bool) ((*(const struct param **) b)->required); |
||||
|
if (!x && y) |
||||
|
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 void *, const void *)) |
||||
|
{ |
||||
|
struct param **first = params; |
||||
|
struct param **last = first + tal_count(params); |
||||
|
first++; |
||||
|
while (first != last) { |
||||
|
assert(compar(first - 1, first) != 0); |
||||
|
first++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static void check_unique(struct param **copy, |
||||
|
int (*compar) (const void *, const void *)) |
||||
|
{ |
||||
|
qsort(copy, tal_count(copy), sizeof(struct param *), compar); |
||||
|
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 struct param **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 NULL; |
||||
|
} |
||||
|
|
||||
|
struct param **param_parse(struct command *cmd, const char *buffer, |
||||
|
const jsmntok_t tokens[], ...) |
||||
|
{ |
||||
|
struct param *def; |
||||
|
struct param **params = tal_arr(cmd, struct param *, 0); |
||||
|
va_list ap; |
||||
|
va_start(ap, tokens); |
||||
|
while ((def = va_arg(ap, struct param *)) != NULL) { |
||||
|
tal_steal(params, def); |
||||
|
tal_resize(¶ms, tal_count(params) + 1); |
||||
|
params[tal_count(params) - 1] = def; |
||||
|
} |
||||
|
va_end(ap); |
||||
|
|
||||
|
return param_parse_arr(cmd, buffer, tokens, params); |
||||
|
} |
@ -0,0 +1,93 @@ |
|||||
|
#ifndef LIGHTNING_LIGHTNINGD_PARAMS_H |
||||
|
#define LIGHTNING_LIGHTNINGD_PARAMS_H |
||||
|
#include "config.h" |
||||
|
#include <ccan/ccan/typesafe_cb/typesafe_cb.h> |
||||
|
|
||||
|
struct param; |
||||
|
|
||||
|
/*
|
||||
|
Typesafe callback system for unmarshalling and validating json parameters. |
||||
|
|
||||
|
Typical usage: |
||||
|
unsigned cltv; |
||||
|
const jsmntok_t *note; |
||||
|
u64 msatoshi; |
||||
|
struct param * mp; |
||||
|
|
||||
|
if (!param_parse(cmd, buffer, tokens, |
||||
|
param_req("cltv", json_tok_number, &cltv), |
||||
|
param_opt("note", json_tok_tok, ¬e), |
||||
|
mp = param_opt("msatoshi", json_tok_u64, &msatoshi), |
||||
|
NULL)) |
||||
|
return; |
||||
|
|
||||
|
At this point in the code you can be assured the json tokens were successfully |
||||
|
parsed. If not, param_parse() returned NULL, having already called |
||||
|
command_fail() with a descriptive error message. The data section of the json |
||||
|
result contains the offending parameter and its value. |
||||
|
|
||||
|
cltv is a required parameter, and is set correctly. |
||||
|
|
||||
|
note and msatoshi are optional parameters. You can see if they have been set |
||||
|
by calling param_is_set(); e.g.: |
||||
|
|
||||
|
if (param_is_set(mp)) |
||||
|
do_something() |
||||
|
|
||||
|
The note parameter uses a special callback, json_tok_tok(). It |
||||
|
simply sets seedtok to the appropriate value and lets the handler do the |
||||
|
validating. It has the added feature of setting seedtok to NULL if it is null |
||||
|
or not specified. |
||||
|
|
||||
|
There are canned failure messages for common callbacks. An example: |
||||
|
|
||||
|
'msatoshi' should be an unsigned 64 bit integer, not '123z' |
||||
|
|
||||
|
Otherwise a generic message is provided. |
||||
|
*/ |
||||
|
struct param **param_parse(struct command *cmd, const char *buffer, |
||||
|
const jsmntok_t params[], ...); |
||||
|
|
||||
|
/*
|
||||
|
* This callback provided must follow this signature; e.g., |
||||
|
* bool json_tok_double(const char *buffer, const jsmntok_t *tok, double *arg) |
||||
|
*/ |
||||
|
typedef bool(*param_cb)(const char *buffer, const jsmntok_t *tok, void *arg); |
||||
|
|
||||
|
/*
|
||||
|
* Add a handler to unmarshal a required json token into @arg. The handler must |
||||
|
* return true on success and false on failure. Upon failure, command_fail will be |
||||
|
* called with a descriptive error message. |
||||
|
* |
||||
|
* This operation is typesafe; i.e., a compilation error will occur if the types |
||||
|
* of @arg and the last parameter of @cb do not match. |
||||
|
* |
||||
|
* Returns an opaque pointer that can be later used in param_is_set(). |
||||
|
*/ |
||||
|
#define param_req(name, cb, arg) \ |
||||
|
param_add_(true, name, \ |
||||
|
typesafe_cb_preargs(bool, void *, \ |
||||
|
(cb), (arg), \ |
||||
|
const char *, \ |
||||
|
const jsmntok_t *), \ |
||||
|
(arg)) |
||||
|
/*
|
||||
|
* Same as above but for optional parameters. |
||||
|
*/ |
||||
|
#define param_opt(name, cb, arg) \ |
||||
|
param_add_(false, name, \ |
||||
|
typesafe_cb_preargs(bool, void *, \ |
||||
|
(cb), (arg), \ |
||||
|
const char *, \ |
||||
|
const jsmntok_t *), \ |
||||
|
(arg)) |
||||
|
struct param * param_add_(bool required, char *name, param_cb cb, void *arg); |
||||
|
|
||||
|
/*
|
||||
|
* Check to see if an optional parameter was set during parsing (although it |
||||
|
* works for all parameters). |
||||
|
* Returns the @arg if set, otherwise NULL. |
||||
|
*/ |
||||
|
void * param_is_set(struct param *p); |
||||
|
|
||||
|
#endif /* LIGHTNING_LIGHTNINGD_PARAMS_H */ |
@ -0,0 +1,446 @@ |
|||||
|
#include <signal.h> |
||||
|
#include <setjmp.h> |
||||
|
#include <lightningd/jsonrpc.h> |
||||
|
|
||||
|
#include <lightningd/params.c> |
||||
|
#include <common/json.c> |
||||
|
#include <common/json_escaped.c> |
||||
|
#include <ccan/array_size/array_size.h> |
||||
|
|
||||
|
bool failed; |
||||
|
char *fail_msg; |
||||
|
|
||||
|
struct command *cmd; |
||||
|
|
||||
|
void command_fail(struct command *cmd, int code, const char *fmt, ...) |
||||
|
{ |
||||
|
failed = true; |
||||
|
va_list ap; |
||||
|
va_start(ap, fmt); |
||||
|
fail_msg = tal_vfmt(cmd, fmt, ap); |
||||
|
va_end(ap); |
||||
|
} |
||||
|
|
||||
|
void command_fail_detailed(struct command *cmd, int code, |
||||
|
const struct json_result *data, const char *fmt, ...) |
||||
|
{ |
||||
|
failed = true; |
||||
|
va_list ap; |
||||
|
va_start(ap, fmt); |
||||
|
fail_msg = tal_vfmt(cmd, fmt, ap); |
||||
|
fail_msg = |
||||
|
tal_fmt(cmd, "%s data: %s", fail_msg, json_result_string(data)); |
||||
|
va_end(ap); |
||||
|
} |
||||
|
|
||||
|
/* AUTOGENERATED MOCKS START */ |
||||
|
/* Generated stub for json_tok_wtx */ |
||||
|
bool json_tok_wtx(struct wallet_tx *tx UNNEEDED, const char *buffer UNNEEDED, |
||||
|
const jsmntok_t * sattok UNNEEDED) |
||||
|
{ |
||||
|
abort(); |
||||
|
} |
||||
|
|
||||
|
/* AUTOGENERATED MOCKS END */ |
||||
|
|
||||
|
struct json { |
||||
|
jsmntok_t *toks; |
||||
|
char *buffer; |
||||
|
}; |
||||
|
|
||||
|
static void convert_quotes(char *first) |
||||
|
{ |
||||
|
while (*first != '\0') { |
||||
|
if (*first == '\'') |
||||
|
*first = '"'; |
||||
|
first++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static struct json *json_parse(const tal_t * ctx, const char *str) |
||||
|
{ |
||||
|
struct json *j = tal(ctx, struct json); |
||||
|
j->buffer = tal_strdup(j, str); |
||||
|
convert_quotes(j->buffer); |
||||
|
|
||||
|
j->toks = tal_arr(j, jsmntok_t, 50); |
||||
|
assert(j->toks); |
||||
|
jsmn_parser parser; |
||||
|
|
||||
|
again: |
||||
|
jsmn_init(&parser); |
||||
|
int ret = jsmn_parse(&parser, j->buffer, strlen(j->buffer), j->toks, |
||||
|
tal_count(j->toks)); |
||||
|
if (ret == JSMN_ERROR_NOMEM) { |
||||
|
tal_resize(&j->toks, tal_count(j->toks) * 2); |
||||
|
goto again; |
||||
|
} |
||||
|
|
||||
|
if (ret <= 0) { |
||||
|
assert(0); |
||||
|
} |
||||
|
failed = false; |
||||
|
return j; |
||||
|
} |
||||
|
|
||||
|
static void zero_params(void) |
||||
|
{ |
||||
|
struct json *j = json_parse(cmd, "{}"); |
||||
|
assert(param_parse(cmd, j->buffer, j->toks, NULL)); |
||||
|
|
||||
|
j = json_parse(cmd, "[]"); |
||||
|
assert(param_parse(cmd, j->buffer, j->toks, NULL)); |
||||
|
} |
||||
|
|
||||
|
struct sanity { |
||||
|
char *str; |
||||
|
bool failed; |
||||
|
int ival; |
||||
|
double dval; |
||||
|
char *fail_str; |
||||
|
}; |
||||
|
|
||||
|
struct sanity buffers[] = { |
||||
|
// pass
|
||||
|
{"['42', '3.15']", false, 42, 3.15, NULL}, |
||||
|
{"{ 'u64' : '42', 'double' : '3.15' }", false, 42, 3.15, NULL}, |
||||
|
|
||||
|
// fail
|
||||
|
{"{'u64':'42', 'double':'3.15', 'extra':'stuff'}", true, 0, 0, |
||||
|
"unknown parameter"}, |
||||
|
{"['42', '3.15', 'stuff']", true, 0, 0, "too many"}, |
||||
|
{"['42', '3.15', 'null']", true, 0, 0, "too many"}, |
||||
|
|
||||
|
// not enough
|
||||
|
{"{'u64':'42'}", true, 0, 0, "missing required"}, |
||||
|
{"['42']", true, 0, 0, "missing required"}, |
||||
|
|
||||
|
// fail wrong type
|
||||
|
{"{'u64':'hello', 'double':'3.15'}", true, 0, 0, "\"u64\": \"hello\""}, |
||||
|
{"['3.15', '3.15', 'stuff']", true, 0, 0, "integer"}, |
||||
|
}; |
||||
|
|
||||
|
static void stest(const struct json *j, struct sanity *b) |
||||
|
{ |
||||
|
u64 ival; |
||||
|
double dval; |
||||
|
if (!param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("u64", json_tok_u64, &ival), |
||||
|
param_req("double", json_tok_double, &dval), NULL)) { |
||||
|
assert(failed == true); |
||||
|
assert(b->failed == true); |
||||
|
assert(strstr(fail_msg, b->fail_str)); |
||||
|
} else { |
||||
|
assert(b->failed == false); |
||||
|
assert(ival == 42); |
||||
|
assert(dval > 3.1499 && b->dval < 3.1501); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static void sanity(void) |
||||
|
{ |
||||
|
for (int i = 0; i < ARRAY_SIZE(buffers); ++i) { |
||||
|
struct json *j = json_parse(cmd, buffers[i].str); |
||||
|
assert(j->toks->type == JSMN_OBJECT |
||||
|
|| j->toks->type == JSMN_ARRAY); |
||||
|
stest(j, &buffers[i]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* Make sure toks are passed through correctly, and also make sure |
||||
|
* optional missing toks are set to NULL. |
||||
|
*/ |
||||
|
static void tok_tok(void) |
||||
|
{ |
||||
|
{ |
||||
|
unsigned int n; |
||||
|
const jsmntok_t *tok = NULL; |
||||
|
struct json *j = json_parse(cmd, "{ 'satoshi', '546' }"); |
||||
|
|
||||
|
assert(param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("satoshi", json_tok_tok, |
||||
|
&tok), NULL)); |
||||
|
assert(tok); |
||||
|
assert(json_tok_number(j->buffer, tok, &n)); |
||||
|
assert(n == 546); |
||||
|
} |
||||
|
// again with missing optional parameter
|
||||
|
{ |
||||
|
/* make sure it is *not* NULL */ |
||||
|
const jsmntok_t *tok = (const jsmntok_t *) 65535; |
||||
|
|
||||
|
struct json *j = json_parse(cmd, "{}"); |
||||
|
assert(param_parse(cmd, j->buffer, j->toks, |
||||
|
param_opt("satoshi", json_tok_tok, |
||||
|
&tok), NULL)); |
||||
|
|
||||
|
/* make sure it *is* NULL */ |
||||
|
assert(tok == NULL); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* check for valid but duplicate json name-value pairs */ |
||||
|
static void dup(void) |
||||
|
{ |
||||
|
struct json *j = |
||||
|
json_parse(cmd, |
||||
|
"{ 'u64' : '42', 'u64' : '43', 'double' : '3.15' }"); |
||||
|
|
||||
|
u64 i; |
||||
|
double d; |
||||
|
assert(!param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("u64", json_tok_u64, &i), |
||||
|
param_req("double", json_tok_double, &d), NULL)); |
||||
|
} |
||||
|
|
||||
|
static void null_params(void) |
||||
|
{ |
||||
|
uint64_t *ints = tal_arr(cmd, uint64_t, 7); |
||||
|
/* no null params */ |
||||
|
struct json *j = |
||||
|
json_parse(cmd, "[ '10', '11', '12', '13', '14', '15', '16']"); |
||||
|
for (int i = 0; i < tal_count(ints); ++i) |
||||
|
ints[i] = i; |
||||
|
|
||||
|
assert(param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("0", json_tok_u64, &ints[0]), |
||||
|
param_req("1", json_tok_u64, &ints[1]), |
||||
|
param_req("2", json_tok_u64, &ints[2]), |
||||
|
param_req("3", json_tok_u64, &ints[3]), |
||||
|
param_opt("4", json_tok_u64, &ints[4]), |
||||
|
param_opt("5", json_tok_u64, &ints[5]), |
||||
|
param_opt("6", json_tok_u64, &ints[6]), NULL)); |
||||
|
for (int i = 0; i < tal_count(ints); ++i) |
||||
|
assert(ints[i] == i + 10); |
||||
|
|
||||
|
/* missing at end */ |
||||
|
for (int i = 0; i < tal_count(ints); ++i) |
||||
|
ints[i] = 42; |
||||
|
|
||||
|
j = json_parse(cmd, "[ '10', '11', '12', '13', '14']"); |
||||
|
struct param *four, *five; |
||||
|
assert(param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("0", json_tok_u64, &ints[0]), |
||||
|
param_req("1", json_tok_u64, &ints[1]), |
||||
|
param_req("2", json_tok_u64, &ints[2]), |
||||
|
param_req("3", json_tok_u64, &ints[3]), |
||||
|
four = param_opt("4", json_tok_u64, &ints[4]), |
||||
|
five = param_opt("5", json_tok_u64, &ints[5]), |
||||
|
param_opt("6", json_tok_u64, &ints[6]), NULL)); |
||||
|
assert(ints[4] == 14); |
||||
|
assert(param_is_set(four) == &ints[4]); |
||||
|
assert(!param_is_set(five)); |
||||
|
assert(ints[5] == 42); |
||||
|
assert(ints[6] == 42); |
||||
|
} |
||||
|
|
||||
|
#if DEVELOPER |
||||
|
jmp_buf jump; |
||||
|
static void handle_abort(int sig) |
||||
|
{ |
||||
|
longjmp(jump, 1); |
||||
|
} |
||||
|
|
||||
|
static void set_assert(void) |
||||
|
{ |
||||
|
struct sigaction act; |
||||
|
memset(&act, '\0', sizeof(act)); |
||||
|
act.sa_handler = &handle_abort; |
||||
|
if (sigaction(SIGABRT, &act, NULL) < 0) { |
||||
|
perror("set_assert"); |
||||
|
exit(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static void restore_assert(void) |
||||
|
{ |
||||
|
struct sigaction act; |
||||
|
memset(&act, '\0', sizeof(act)); |
||||
|
act.sa_handler = SIG_DFL; |
||||
|
if (sigaction(SIGABRT, &act, NULL) < 0) { |
||||
|
perror("reset_assert"); |
||||
|
exit(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* Check to make sure there are no programming mistakes. |
||||
|
*/ |
||||
|
static void bad_programmer(void) |
||||
|
{ |
||||
|
u64 ival; |
||||
|
u64 ival2; |
||||
|
double dval; |
||||
|
set_assert(); |
||||
|
struct json *j = json_parse(cmd, "[ '25', '546', '26' ]"); |
||||
|
|
||||
|
/* check for repeated names */ |
||||
|
if (setjmp(jump) == 0) { |
||||
|
param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("repeat", json_tok_u64, &ival), |
||||
|
param_req("double", json_tok_double, &dval), |
||||
|
param_req("repeat", json_tok_u64, &ival2), NULL); |
||||
|
/* shouldn't get here */ |
||||
|
restore_assert(); |
||||
|
assert(false); |
||||
|
} |
||||
|
|
||||
|
if (setjmp(jump) == 0) { |
||||
|
param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("repeat", json_tok_u64, &ival), |
||||
|
param_req("double", json_tok_double, &dval), |
||||
|
param_req("repeat", json_tok_u64, &ival), NULL); |
||||
|
restore_assert(); |
||||
|
assert(false); |
||||
|
} |
||||
|
|
||||
|
if (setjmp(jump) == 0) { |
||||
|
param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("u64", json_tok_u64, &ival), |
||||
|
param_req("repeat", json_tok_double, &dval), |
||||
|
param_req("repeat", json_tok_double, &dval), NULL); |
||||
|
restore_assert(); |
||||
|
assert(false); |
||||
|
} |
||||
|
|
||||
|
/* check for repeated arguments */ |
||||
|
if (setjmp(jump) == 0) { |
||||
|
param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("u64", json_tok_u64, &ival), |
||||
|
param_req("repeated-arg", json_tok_u64, &ival), |
||||
|
NULL); |
||||
|
restore_assert(); |
||||
|
assert(false); |
||||
|
} |
||||
|
|
||||
|
/* check for NULL input */ |
||||
|
if (setjmp(jump) == 0) { |
||||
|
param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req(NULL, json_tok_u64, &ival), NULL); |
||||
|
restore_assert(); |
||||
|
assert(false); |
||||
|
} |
||||
|
|
||||
|
if (setjmp(jump) == 0) { |
||||
|
param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req(NULL, json_tok_u64, &ival), NULL); |
||||
|
restore_assert(); |
||||
|
assert(false); |
||||
|
} |
||||
|
|
||||
|
if (setjmp(jump) == 0) { |
||||
|
param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("u64", NULL, &ival), NULL); |
||||
|
restore_assert(); |
||||
|
assert(false); |
||||
|
} |
||||
|
|
||||
|
if (setjmp(jump) == 0) { |
||||
|
/* Add required param after optional */ |
||||
|
struct json *j = |
||||
|
json_parse(cmd, "[ '25', '546', '26', '1.1' ]"); |
||||
|
unsigned int msatoshi; |
||||
|
double riskfactor; |
||||
|
param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("u64", json_tok_u64, &ival), |
||||
|
param_req("double", json_tok_double, &dval), |
||||
|
param_opt("msatoshi", json_tok_number, &msatoshi), |
||||
|
param_req("riskfactor", json_tok_double, |
||||
|
&riskfactor), NULL); |
||||
|
restore_assert(); |
||||
|
assert(false); |
||||
|
} |
||||
|
restore_assert(); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
static void add_members(struct param **params, |
||||
|
struct json_result *obj, |
||||
|
struct json_result *arr, unsigned int *ints) |
||||
|
{ |
||||
|
char name[256]; |
||||
|
for (int i = 0; i < tal_count(ints); ++i) { |
||||
|
sprintf(name, "%d", i); |
||||
|
json_add_num(obj, name, i); |
||||
|
json_add_num(arr, NULL, i); |
||||
|
params[i] = param_req(name, json_tok_number, &ints[i]); |
||||
|
tal_steal(params, params[i]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* A roundabout way of initializing an array of ints to: |
||||
|
* ints[0] = 0, ints[1] = 1, ... ints[499] = 499 |
||||
|
*/ |
||||
|
static void five_hundred_params(void) |
||||
|
{ |
||||
|
struct param **params = tal_arr(NULL, struct param *, 500); |
||||
|
|
||||
|
unsigned int *ints = tal_arr(params, unsigned int, 500); |
||||
|
struct json_result *obj = new_json_result(params); |
||||
|
struct json_result *arr = new_json_result(params); |
||||
|
json_object_start(obj, NULL); |
||||
|
json_array_start(arr, NULL); |
||||
|
add_members(params, obj, arr, ints); |
||||
|
json_object_end(obj); |
||||
|
json_array_end(arr); |
||||
|
|
||||
|
/* first test object version */ |
||||
|
struct json *j = json_parse(params, obj->s); |
||||
|
assert(param_parse_arr(cmd, j->buffer, j->toks, params)); |
||||
|
for (int i = 0; i < tal_count(ints); ++i) { |
||||
|
assert(ints[i] == i); |
||||
|
ints[i] = 65535; |
||||
|
} |
||||
|
|
||||
|
/* now test array */ |
||||
|
j = json_parse(params, arr->s); |
||||
|
assert(param_parse_arr(cmd, j->buffer, j->toks, params)); |
||||
|
for (int i = 0; i < tal_count(ints); ++i) { |
||||
|
assert(ints[i] == i); |
||||
|
} |
||||
|
|
||||
|
tal_free(params); |
||||
|
} |
||||
|
|
||||
|
static void sendpay(void) |
||||
|
{ |
||||
|
struct json *j = json_parse(cmd, "[ 'A', '123', 'hello there' '547']"); |
||||
|
|
||||
|
const jsmntok_t *routetok, *note; |
||||
|
u64 msatoshi; |
||||
|
unsigned cltv; |
||||
|
struct param * mp; |
||||
|
|
||||
|
if (!param_parse(cmd, j->buffer, j->toks, |
||||
|
param_req("route", json_tok_tok, &routetok), |
||||
|
param_req("cltv", json_tok_number, &cltv), |
||||
|
param_opt("note", json_tok_tok, ¬e), |
||||
|
mp = param_opt("msatoshi", json_tok_u64, &msatoshi), |
||||
|
NULL)) |
||||
|
assert(false); |
||||
|
|
||||
|
assert(param_is_set(mp)); |
||||
|
assert(msatoshi == 547); |
||||
|
} |
||||
|
|
||||
|
int main(void) |
||||
|
{ |
||||
|
setup_locale(); |
||||
|
cmd = tal(NULL, struct command); |
||||
|
fail_msg = tal_arr(cmd, char, 10000); |
||||
|
|
||||
|
zero_params(); |
||||
|
sanity(); |
||||
|
tok_tok(); |
||||
|
null_params(); |
||||
|
#if DEVELOPER |
||||
|
bad_programmer(); |
||||
|
#endif |
||||
|
dup(); |
||||
|
five_hundred_params(); |
||||
|
sendpay(); |
||||
|
tal_free(cmd); |
||||
|
printf("run-params ok\n"); |
||||
|
} |
Loading…
Reference in new issue