mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-24 07:07:46 +01:00
We want to extend it to plugins, and we want it to be allowed to be async for more power, so rather than not completing the cmd if we're checking, do it in command_check_done() and call it. This is cleaner than the special case we had before, and allows check to us all the normal jsonrpc mechanisms, especially async requests (which we'll need if we want to hand check requests to plugins!). Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
677 lines
19 KiB
C
677 lines
19 KiB
C
#include "config.h"
|
|
#include "../bech32.c"
|
|
#include "../json_filter.c"
|
|
#include "../json_parse.c"
|
|
#include "../json_parse_simple.c"
|
|
#include <assert.h>
|
|
#include <ccan/array_size/array_size.h>
|
|
#include <common/channel_type.h>
|
|
#include <common/setup.h>
|
|
#include <stdio.h>
|
|
|
|
/* We want to catch parameter checs for bad_programmer() */
|
|
#define paramcheck_assert save_paramcheck_assert
|
|
|
|
static bool paramcheck_assert_failed;
|
|
static void save_paramcheck_assert(bool cond)
|
|
{
|
|
if (!cond)
|
|
paramcheck_assert_failed = true;
|
|
}
|
|
|
|
#include "../json_param.c"
|
|
|
|
char *fail_msg = NULL;
|
|
bool failed = false;
|
|
|
|
static bool check_fail(void) {
|
|
if (!failed)
|
|
return false;
|
|
failed = false;
|
|
return true;
|
|
}
|
|
|
|
struct command *cmd;
|
|
|
|
struct command_result {
|
|
};
|
|
static struct command_result cmd_failed;
|
|
|
|
struct command_result *command_fail(struct command *cmd,
|
|
enum jsonrpc_errcode code, const char *fmt, ...)
|
|
{
|
|
failed = true;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
fail_msg = tal_vfmt(cmd, fmt, ap);
|
|
va_end(ap);
|
|
return &cmd_failed;
|
|
}
|
|
|
|
bool command_deprecated_in_ok(struct command *cmd,
|
|
const char *param,
|
|
const char *depr_start,
|
|
const char *depr_end)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/* AUTOGENERATED MOCKS START */
|
|
/* Generated stub for command_check_done */
|
|
struct command_result *command_check_done(struct command *cmd)
|
|
|
|
{ fprintf(stderr, "command_check_done called!\n"); abort(); }
|
|
/* Generated stub for command_dev_apis */
|
|
bool command_dev_apis(const struct command *cmd UNNEEDED)
|
|
{ fprintf(stderr, "command_dev_apis called!\n"); abort(); }
|
|
/* Generated stub for command_filter_ptr */
|
|
struct json_filter **command_filter_ptr(struct command *cmd UNNEEDED)
|
|
{ fprintf(stderr, "command_filter_ptr called!\n"); abort(); }
|
|
/* Generated stub for fromwire_tlv */
|
|
bool fromwire_tlv(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
|
|
const struct tlv_record_type *types UNNEEDED, size_t num_types UNNEEDED,
|
|
void *record UNNEEDED, struct tlv_field **fields UNNEEDED,
|
|
const u64 *extra_types UNNEEDED, size_t *err_off UNNEEDED, u64 *err_type UNNEEDED)
|
|
{ fprintf(stderr, "fromwire_tlv called!\n"); abort(); }
|
|
/* Generated stub for to_canonical_invstr */
|
|
const char *to_canonical_invstr(const tal_t *ctx UNNEEDED, const char *invstring UNNEEDED)
|
|
{ fprintf(stderr, "to_canonical_invstr called!\n"); abort(); }
|
|
/* Generated stub for towire_tlv */
|
|
void towire_tlv(u8 **pptr UNNEEDED,
|
|
const struct tlv_record_type *types UNNEEDED, size_t num_types UNNEEDED,
|
|
const void *record UNNEEDED)
|
|
{ fprintf(stderr, "towire_tlv called!\n"); abort(); }
|
|
/* AUTOGENERATED MOCKS END */
|
|
|
|
/* We do this lightningd-style: */
|
|
enum command_mode {
|
|
CMD_NORMAL,
|
|
CMD_USAGE,
|
|
CMD_CHECK
|
|
};
|
|
|
|
struct command {
|
|
enum command_mode mode;
|
|
bool deprecated_apis;
|
|
const char *usage;
|
|
};
|
|
|
|
void command_set_usage(struct command *cmd, const char *usage)
|
|
{
|
|
cmd->usage = usage;
|
|
}
|
|
|
|
bool command_usage_only(const struct command *cmd)
|
|
{
|
|
return cmd->mode == CMD_USAGE;
|
|
}
|
|
|
|
bool command_check_only(const struct command *cmd)
|
|
{
|
|
return cmd->mode == CMD_CHECK;
|
|
}
|
|
|
|
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);
|
|
}
|
|
return j;
|
|
}
|
|
|
|
static void zero_params(void)
|
|
{
|
|
struct json *j = json_parse(cmd, "{}");
|
|
assert(param(cmd, j->buffer, j->toks, NULL));
|
|
|
|
j = json_parse(cmd, "[]");
|
|
assert(param(cmd, j->buffer, j->toks, NULL));
|
|
}
|
|
|
|
struct sanity {
|
|
char *str;
|
|
bool failed;
|
|
int ival;
|
|
u64 fpval; /* floating-point, multiplied by 1000000 */
|
|
char *fail_str;
|
|
};
|
|
|
|
struct sanity buffers[] = {
|
|
// pass
|
|
{"['42', '3.15']", false, 42, 3150000, NULL},
|
|
{"{ 'u64' : '42', 'fp' : '3.15' }", false, 42, 3150000, NULL},
|
|
|
|
// fail
|
|
{"{'u64':'42', 'fp':'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', 'fp':'3.15'}", true, 0, 0, "be an unsigned 64"},
|
|
{"['3.15', '3.15', 'stuff']", true, 0, 0, "integer"},
|
|
};
|
|
|
|
static void stest(const struct json *j, struct sanity *b)
|
|
{
|
|
u64 *ival;
|
|
u64 *fpval;
|
|
if (!param(cmd, j->buffer, j->toks,
|
|
p_req("u64", param_u64, &ival),
|
|
p_req("fp", param_millionths, &fpval), NULL)) {
|
|
assert(check_fail());
|
|
assert(b->failed == true);
|
|
if (!strstr(fail_msg, b->fail_str)) {
|
|
printf("%s != %s\n", fail_msg, b->fail_str);
|
|
assert(false);
|
|
}
|
|
} else {
|
|
assert(!check_fail());
|
|
assert(b->failed == false);
|
|
assert(*ival == 42);
|
|
assert(*fpval > 3149900 && b->fpval < 3150100);
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
static struct command_result *param_tok(struct command *cmd, const char *name,
|
|
const char *buffer, const jsmntok_t * tok,
|
|
const jsmntok_t **out)
|
|
{
|
|
*out = tok;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* 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(cmd, j->buffer, j->toks,
|
|
p_req("satoshi", param_tok, &tok), NULL));
|
|
assert(tok);
|
|
assert(json_to_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(cmd, j->buffer, j->toks,
|
|
p_opt("satoshi", param_tok, &tok), NULL));
|
|
|
|
/* make sure it *is* NULL */
|
|
assert(tok == NULL);
|
|
}
|
|
}
|
|
|
|
/* check for valid but duplicate json name-value pairs */
|
|
static void dup_names(void)
|
|
{
|
|
struct json *j =
|
|
json_parse(cmd,
|
|
"{ 'u64' : '42', 'u64' : '43', 'fp' : '3.15' }");
|
|
|
|
u64 *i;
|
|
u64 *fp;
|
|
assert(!param(cmd, j->buffer, j->toks,
|
|
p_req("u64", param_u64, &i),
|
|
p_req("fp", param_millionths, &fp), NULL));
|
|
}
|
|
|
|
static void null_params(void)
|
|
{
|
|
uint64_t **intptrs = tal_arr(cmd, uint64_t *, 7);
|
|
/* no null params */
|
|
struct json *j =
|
|
json_parse(cmd, "[ '10', '11', '12', '13', '14', '15', '16']");
|
|
|
|
assert(param(cmd, j->buffer, j->toks,
|
|
p_req("0", param_u64, &intptrs[0]),
|
|
p_req("1", param_u64, &intptrs[1]),
|
|
p_req("2", param_u64, &intptrs[2]),
|
|
p_req("3", param_u64, &intptrs[3]),
|
|
p_opt_def("4", param_u64, &intptrs[4], 999),
|
|
p_opt("5", param_u64, &intptrs[5]),
|
|
p_opt("6", param_u64, &intptrs[6]),
|
|
NULL));
|
|
for (int i = 0; i < tal_count(intptrs); ++i) {
|
|
assert(intptrs[i]);
|
|
assert(*intptrs[i] == i + 10);
|
|
}
|
|
|
|
/* missing at end */
|
|
j = json_parse(cmd, "[ '10', '11', '12', '13', '14']");
|
|
assert(param(cmd, j->buffer, j->toks,
|
|
p_req("0", param_u64, &intptrs[0]),
|
|
p_req("1", param_u64, &intptrs[1]),
|
|
p_req("2", param_u64, &intptrs[2]),
|
|
p_req("3", param_u64, &intptrs[3]),
|
|
p_opt("4", param_u64, &intptrs[4]),
|
|
p_opt("5", param_u64, &intptrs[5]),
|
|
p_opt_def("6", param_u64, &intptrs[6], 888),
|
|
NULL));
|
|
assert(*intptrs[0] == 10);
|
|
assert(*intptrs[1] == 11);
|
|
assert(*intptrs[2] == 12);
|
|
assert(*intptrs[3] == 13);
|
|
assert(*intptrs[4] == 14);
|
|
assert(!intptrs[5]);
|
|
assert(*intptrs[6] == 888);
|
|
}
|
|
|
|
static void no_params(void)
|
|
{
|
|
struct json *j = json_parse(cmd, "[]");
|
|
assert(param(cmd, j->buffer, j->toks, NULL));
|
|
|
|
j = json_parse(cmd, "[ 'unexpected' ]");
|
|
assert(!param(cmd, j->buffer, j->toks, NULL));
|
|
}
|
|
|
|
/*
|
|
* Check to make sure there are no programming mistakes.
|
|
*/
|
|
static void bad_programmer(void)
|
|
{
|
|
u64 *ival;
|
|
u64 *ival2;
|
|
u64 *fpval;
|
|
struct json *j = json_parse(cmd, "[ '25', '546', '26' ]");
|
|
|
|
/* Usage mode makes it check parameters are sane */
|
|
cmd->mode = CMD_USAGE;
|
|
|
|
/* check for repeated names */
|
|
paramcheck_assert_failed = false;
|
|
param(cmd, j->buffer, j->toks,
|
|
p_req("repeat", param_u64, &ival),
|
|
p_req("fp", param_millionths, &fpval),
|
|
p_req("repeat", param_u64, &ival2), NULL);
|
|
assert(paramcheck_assert_failed);
|
|
|
|
paramcheck_assert_failed = false;
|
|
param(cmd, j->buffer, j->toks,
|
|
p_req("repeat", param_u64, &ival),
|
|
p_req("fp", param_millionths, &fpval),
|
|
p_req("repeat", param_u64, &ival), NULL);
|
|
assert(paramcheck_assert_failed);
|
|
|
|
paramcheck_assert_failed = false;
|
|
param(cmd, j->buffer, j->toks,
|
|
p_req("u64", param_u64, &ival),
|
|
p_req("repeat", param_millionths, &fpval),
|
|
p_req("repeat", param_millionths, &fpval), NULL);
|
|
assert(paramcheck_assert_failed);
|
|
|
|
paramcheck_assert_failed = false;
|
|
param(cmd, j->buffer, j->toks,
|
|
p_req("u64", (param_cbx) NULL, NULL), NULL);
|
|
assert(paramcheck_assert_failed);
|
|
|
|
/* Add required param after optional */
|
|
j = json_parse(cmd, "[ '25', '546', '26', '1.1' ]");
|
|
unsigned int *msatoshi;
|
|
u64 *riskfactor_millionths;
|
|
paramcheck_assert_failed = false;
|
|
param(
|
|
cmd, j->buffer, j->toks, p_req("u64", param_u64, &ival),
|
|
p_req("fp", param_millionths, &fpval),
|
|
p_opt_def("msatoshi", param_number, &msatoshi, 100),
|
|
p_req("riskfactor", param_millionths, &riskfactor_millionths),
|
|
NULL);
|
|
assert(*msatoshi);
|
|
assert(*msatoshi == 100);
|
|
assert(paramcheck_assert_failed);
|
|
|
|
cmd->mode = CMD_NORMAL;
|
|
}
|
|
|
|
static void add_members(struct param **params,
|
|
char **obj,
|
|
char **arr, unsigned int **ints)
|
|
{
|
|
for (int i = 0; i < tal_count(ints); ++i) {
|
|
const char *name = tal_fmt(*params, "%i", i);
|
|
if (i != 0) {
|
|
tal_append_fmt(obj, ", ");
|
|
tal_append_fmt(arr, ", ");
|
|
}
|
|
tal_append_fmt(obj, "\"%i\" : %i", i, i);
|
|
tal_append_fmt(arr, "%i", i);
|
|
param_add(params, name, true, NULL, NULL,
|
|
typesafe_cb_preargs(struct command_result *, void **,
|
|
param_number,
|
|
&ints[i],
|
|
struct command *,
|
|
const char *,
|
|
const char *,
|
|
const jsmntok_t *),
|
|
&ints[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, 0);
|
|
|
|
unsigned int **ints = tal_arr(params, unsigned int*, 500);
|
|
char *obj = tal_fmt(params, "{ ");
|
|
char *arr = tal_fmt(params, "[ ");
|
|
add_members(¶ms, &obj, &arr, ints);
|
|
tal_append_fmt(&obj, "}");
|
|
tal_append_fmt(&arr, "]");
|
|
|
|
/* first test object version */
|
|
struct json *j = json_parse(params, obj);
|
|
assert(param_arr(cmd, j->buffer, j->toks, params, false) == NULL);
|
|
for (int i = 0; i < tal_count(ints); ++i) {
|
|
assert(ints[i]);
|
|
assert(*ints[i] == i);
|
|
*ints[i] = 65535;
|
|
}
|
|
|
|
/* now test array */
|
|
j = json_parse(params, arr);
|
|
assert(param_arr(cmd, j->buffer, j->toks, params, false) == NULL);
|
|
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;
|
|
|
|
if (!param(cmd, j->buffer, j->toks,
|
|
p_req("route", param_tok, &routetok),
|
|
p_req("cltv", param_number, &cltv),
|
|
p_opt("note", param_tok, ¬e),
|
|
p_opt("msatoshi", param_u64, &msatoshi),
|
|
NULL))
|
|
assert(false);
|
|
|
|
assert(note);
|
|
assert(!strncmp("hello there", j->buffer + note->start,
|
|
note->end - note->start));
|
|
assert(msatoshi);
|
|
assert(*msatoshi == 547);
|
|
}
|
|
|
|
static void invalid_bech32m(void)
|
|
{
|
|
int wit_version;
|
|
uint8_t data_out[500];
|
|
size_t data_out_len;
|
|
|
|
/* Taken from BIP-350 */
|
|
|
|
/* Correct */
|
|
assert(segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "BC1SW50QGDZ25J"));
|
|
assert(wit_version == 16);
|
|
assert(data_out_len == 2);
|
|
|
|
/* Correct encoding, but expecting the wrong hrp*/
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "tb", "BC1SW50QGDZ25J"));
|
|
|
|
/* BIP350-valid, but fake HRP so was put in "invalid" section of BIP */
|
|
assert(segwit_addr_decode(&wit_version, data_out, &data_out_len, "tc", "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut"));
|
|
|
|
/* Incorrect for various reasons (including wrong checksum between bech32 <->bech32m */
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "tb", "tb1z0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqglt7rf"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "tb", "tb1q0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq24jc47"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "bc1p38j9r5y49hruaue7wxjce0updqjuyyx0kh56v8s25huc6995vvpql3jow4"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "BC130XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ7ZWS8R"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "bc1pw5dgrnzv"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v8n0nx0muaewav253zgeav"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "tb", "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq47Zagq"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7v07qwwzcrf"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "tb", "tb1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vpggkg4j"));
|
|
assert(!segwit_addr_decode(&wit_version, data_out, &data_out_len, "bc", "bc1gmk9yu"));
|
|
|
|
}
|
|
|
|
static void sendpay_nulltok(void)
|
|
{
|
|
struct json *j = json_parse(cmd, "[ 'A', '123']");
|
|
|
|
const jsmntok_t *routetok, *note = (void *) 65535;
|
|
u64 *msatoshi;
|
|
unsigned *cltv;
|
|
|
|
if (!param(cmd, j->buffer, j->toks,
|
|
p_req("route", param_tok, &routetok),
|
|
p_req("cltv", param_number, &cltv),
|
|
p_opt("note", param_tok, ¬e),
|
|
p_opt("msatoshi", param_u64, &msatoshi),
|
|
NULL))
|
|
assert(false);
|
|
|
|
assert(note == NULL);
|
|
assert(msatoshi == NULL);
|
|
}
|
|
|
|
static void advanced(void)
|
|
{
|
|
{
|
|
struct json *j = json_parse(cmd, "[ 'lightning', 24, 'tok', 543 ]");
|
|
|
|
struct json_escape *label;
|
|
u64 *msat;
|
|
u64 *msat_opt1, *msat_opt2;
|
|
const jsmntok_t *tok;
|
|
|
|
assert(param(cmd, j->buffer, j->toks,
|
|
p_req("description", param_label, &label),
|
|
p_req("msat", param_u64, &msat),
|
|
p_req("tok", param_tok, &tok),
|
|
p_opt("msat_opt1", param_u64, &msat_opt1),
|
|
p_opt("msat_opt2", param_u64, &msat_opt2),
|
|
NULL));
|
|
assert(label != NULL);
|
|
assert(streq(label->s, "lightning"));
|
|
assert(*msat == 24);
|
|
assert(tok);
|
|
assert(msat_opt1);
|
|
assert(*msat_opt1 == 543);
|
|
assert(msat_opt2 == NULL);
|
|
}
|
|
{
|
|
struct json *j = json_parse(cmd, "[ 3, 'foo' ]");
|
|
struct json_escape *label, *foo;
|
|
assert(param(cmd, j->buffer, j->toks,
|
|
p_req("label", param_label, &label),
|
|
p_opt("foo", param_label, &foo),
|
|
NULL));
|
|
assert(streq(label->s, "3"));
|
|
assert(streq(foo->s, "foo"));
|
|
}
|
|
{
|
|
u64 *msat;
|
|
u64 *msat2;
|
|
struct json *j = json_parse(cmd, "[ 3 ]");
|
|
assert(param(cmd, j->buffer, j->toks,
|
|
p_opt_def("msat", param_u64, &msat, 23),
|
|
p_opt_def("msat2", param_u64, &msat2, 53),
|
|
NULL));
|
|
assert(*msat == 3);
|
|
assert(msat2);
|
|
assert(*msat2 == 53);
|
|
}
|
|
}
|
|
|
|
static void advanced_fail(void)
|
|
{
|
|
{
|
|
struct json *j = json_parse(cmd, "[ 'anyx' ]");
|
|
u64 *msat;
|
|
assert(!param(cmd, j->buffer, j->toks,
|
|
p_req("msat", param_u64, &msat),
|
|
NULL));
|
|
assert(check_fail());
|
|
assert(strstr(fail_msg, "msat: should be an unsigned 64 bit integer: invalid token '\"anyx\"'"));
|
|
}
|
|
}
|
|
|
|
#define test_cb(cb, T, json_, value, pass) \
|
|
{ \
|
|
struct json *j = json_parse(cmd, json_); \
|
|
T *v; \
|
|
struct command_result *ret = cb(cmd, "name", j->buffer, j->toks + 1, &v); \
|
|
assert((ret == NULL) == pass); \
|
|
if (ret == NULL) { \
|
|
assert(v); \
|
|
assert(*v == value); \
|
|
} \
|
|
}
|
|
|
|
static void param_tests(void)
|
|
{
|
|
test_cb(param_bool, bool, "[ true ]", true, true);
|
|
test_cb(param_bool, bool, "[ false ]", false, true);
|
|
test_cb(param_bool, bool, "[ tru ]", false, false);
|
|
test_cb(param_bool, bool, "[ 1 ]", false, false);
|
|
|
|
test_cb(param_millionths, u64, "[ -0.01 ]", 0, false);
|
|
test_cb(param_millionths, u64, "[ 0.00 ]", 0, true);
|
|
test_cb(param_millionths, u64, "[ 1 ]", 1000000, true);
|
|
test_cb(param_millionths, u64, "[ 1.1 ]", 1100000, true);
|
|
test_cb(param_millionths, u64, "[ 1.01 ]", 1010000, true);
|
|
test_cb(param_millionths, u64, "[ 99.99 ]", 99990000, true);
|
|
test_cb(param_millionths, u64, "[ 100.0 ]", 100000000, true);
|
|
test_cb(param_millionths, u64, "[ 100.001 ]", 100001000, true);
|
|
test_cb(param_millionths, u64, "[ 1000 ]", 1000000000, true);
|
|
test_cb(param_millionths, u64, "[ 'wow' ]", 0, false);
|
|
}
|
|
|
|
static void test_invoice(struct command *cmd,
|
|
const char *buffer,
|
|
const jsmntok_t *obj UNNEEDED,
|
|
const jsmntok_t *params)
|
|
{
|
|
u64 *msatoshi_val;
|
|
struct json_escape *label_val;
|
|
const char *desc_val;
|
|
u64 *expiry;
|
|
const jsmntok_t *fallbacks;
|
|
const jsmntok_t *preimagetok;
|
|
|
|
assert(cmd->mode == CMD_USAGE);
|
|
if (!param(cmd, buffer, params,
|
|
p_req("msatoshi", param_u64, &msatoshi_val),
|
|
p_req("label", param_label, &label_val),
|
|
p_req("description", param_escaped_string, &desc_val),
|
|
p_opt("expiry", param_u64, &expiry),
|
|
p_opt("fallbacks", param_array, &fallbacks),
|
|
p_opt("preimage", param_tok, &preimagetok), NULL))
|
|
return;
|
|
|
|
/* should not be here since we are in the mode of CMD_USAGE
|
|
* and it always returns false. */
|
|
abort();
|
|
}
|
|
|
|
static void usage(void)
|
|
{
|
|
cmd->mode = CMD_USAGE;
|
|
|
|
test_invoice(cmd, NULL, NULL, NULL);
|
|
assert(streq(cmd->usage,
|
|
"msatoshi label description "
|
|
"[expiry] [fallbacks] [preimage]"));
|
|
|
|
cmd->mode = CMD_NORMAL;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
common_setup(argv[0]);
|
|
cmd = tal(tmpctx, struct command);
|
|
cmd->mode = CMD_NORMAL;
|
|
fail_msg = tal_arr(cmd, char, 10000);
|
|
|
|
zero_params();
|
|
sanity();
|
|
tok_tok();
|
|
null_params();
|
|
no_params();
|
|
bad_programmer();
|
|
dup_names();
|
|
five_hundred_params();
|
|
sendpay();
|
|
sendpay_nulltok();
|
|
advanced();
|
|
advanced_fail();
|
|
param_tests();
|
|
usage();
|
|
invalid_bech32m();
|
|
|
|
printf("run-params ok\n");
|
|
common_shutdown();
|
|
}
|