Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions common/json_tok.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ struct command_result *param_string(struct command *cmd, const char *name,
return NULL;
}

struct command_result *param_ignore(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
const void *unused)
{
return NULL;
}

struct command_result *param_label(struct command *cmd, const char *name,
const char * buffer, const jsmntok_t *tok,
struct json_escape **label)
Expand Down
4 changes: 4 additions & 0 deletions common/json_tok.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,8 @@ struct command_result *param_tok(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t * tok,
const jsmntok_t **out);

/* Ignore the token. Not usually used. */
struct command_result *param_ignore(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
const void *unused);
#endif /* LIGHTNING_COMMON_JSON_TOK_H */
44 changes: 44 additions & 0 deletions common/param.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,50 @@ static struct command_result *param_arr(struct command *cmd, const char *buffer,
"Expected array or object for params");
}

#include <stdio.h>

const char *param_subcommand(struct command *cmd, const char *buffer,
const jsmntok_t tokens[],
const char *name, ...)
{
va_list ap;
struct param *params = tal_arr(cmd, struct param, 0);
const char *arg, **names = tal_arr(tmpctx, const char *, 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mildly disagree with allocating a names array but that is just me being too much of a premature optimizer.

const char *subcmd;

param_add(&params, "subcommand", true, (void *)param_string, &subcmd);
names[0] = name;
va_start(ap, name);
while ((arg = va_arg(ap, const char *)) != NULL)
tal_arr_expand(&names, arg);
va_end(ap);

if (command_usage_only(cmd)) {
char *usage = tal_strdup(cmd, "subcommand");
for (size_t i = 0; i < tal_count(names); i++)
tal_append_fmt(&usage, "%c%s",
i == 0 ? '=' : '|', names[i]);
command_set_usage(cmd, usage);
return NULL;
}

/* Check it's valid */
if (param_arr(cmd, buffer, tokens, params, true) != NULL) {
return NULL;
}

/* Check it's one of the known ones. */
for (size_t i = 0; i < tal_count(names); i++)
if (streq(subcmd, names[i]))
return subcmd;

/* We really do ignore this. */
if (command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Unknown subcommand '%s'", subcmd))
;
return NULL;
}

bool param(struct command *cmd, const char *buffer,
const jsmntok_t tokens[], ...)
{
Expand Down
18 changes: 15 additions & 3 deletions common/param.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,25 @@ typedef struct command_result *(*param_cbx)(struct command *cmd,
const jsmntok_t *tok,
void **arg);

/**
* Parse the first json value.
*
* name...: NULL-terminated array of valid values.
*
* Returns subcommand: if it returns NULL if you should return
* command_param_failed() immediately.
*/
const char *param_subcommand(struct command *cmd, const char *buffer,
const jsmntok_t tokens[],
const char *name, ...) LAST_ARG_NULL;

/*
* Add a required parameter.
*/
#define p_req(name, cbx, arg) \
name"", \
true, \
(cbx), \
(param_cbx)(cbx), \
(arg) + 0*sizeof((cbx)((struct command *)NULL, \
(const char *)NULL, \
(const char *)NULL, \
Expand All @@ -77,7 +89,7 @@ typedef struct command_result *(*param_cbx)(struct command *cmd,
#define p_opt(name, cbx, arg) \
name"", \
false, \
(cbx), \
(param_cbx)(cbx), \
({ *arg = NULL; \
(arg) + 0*sizeof((cbx)((struct command *)NULL, \
(const char *)NULL, \
Expand All @@ -91,7 +103,7 @@ typedef struct command_result *(*param_cbx)(struct command *cmd,
#define p_opt_def(name, cbx, arg, def) \
name"", \
false, \
(cbx), \
(param_cbx)(cbx), \
({ (*arg) = tal((cmd), typeof(**arg)); \
(**arg) = (def); \
(arg) + 0*sizeof((cbx)((struct command *)NULL, \
Expand Down
84 changes: 49 additions & 35 deletions lightningd/jsonrpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,49 +269,63 @@ static void slowcmd_start(struct slowcmd *sc)
slowcmd_finish, sc);
}

static struct command_result *json_slowcmd(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNUSED,
const jsmntok_t *params)
static struct command_result *json_dev(struct command *cmd UNUSED,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct slowcmd *sc = tal(cmd, struct slowcmd);

sc->cmd = cmd;
if (!param(cmd, buffer, params,
p_opt_def("msec", param_number, &sc->msec, 1000),
NULL))
return command_param_failed();

new_reltimer(cmd->ld->timers, sc, time_from_msec(0), slowcmd_start, sc);
return command_still_pending(cmd);
}

static const struct json_command dev_slowcmd_command = {
"dev-slowcmd",
"developer",
json_slowcmd,
"Torture test for slow commands, optional {msec}"
};
AUTODATA(json_command, &dev_slowcmd_command);
const char *subcmd;

static struct command_result *json_crash(struct command *cmd UNUSED,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
if (!param(cmd, buffer, params, NULL))
subcmd = param_subcommand(cmd, buffer, params,
"crash", "rhash", "slowcmd", NULL);
if (!subcmd)
return command_param_failed();

fatal("Crash at user request");
if (streq(subcmd, "crash")) {
if (!param(cmd, buffer, params,
p_req("subcommand", param_ignore, cmd),
NULL))
return command_param_failed();
fatal("Crash at user request");
} else if (streq(subcmd, "slowcmd")) {
struct slowcmd *sc = tal(cmd, struct slowcmd);

sc->cmd = cmd;
if (!param(cmd, buffer, params,
p_req("subcommand", param_ignore, cmd),
p_opt_def("msec", param_number, &sc->msec, 1000),
NULL))
return command_param_failed();

new_reltimer(cmd->ld->timers, sc, time_from_msec(0),
slowcmd_start, sc);
return command_still_pending(cmd);
} else {
assert(streq(subcmd, "rhash"));
struct json_stream *response;
struct sha256 *secret;

if (!param(cmd, buffer, params,
p_req("subcommand", param_ignore, cmd),
p_req("secret", param_sha256, &secret),
NULL))
return command_param_failed();

/* Hash in place. */
sha256(secret, secret, sizeof(*secret));
response = json_stream_success(cmd);
json_add_hex(response, "rhash", secret, sizeof(*secret));
return command_success(cmd, response);
}
}

static const struct json_command dev_crash_command = {
"dev-crash",
static const struct json_command dev_command = {
"dev",
"developer",
json_crash,
"Crash lightningd by calling fatal()"
json_dev,
"Developer command test multiplexer"
};
AUTODATA(json_command, &dev_crash_command);
AUTODATA(json_command, &dev_command);
#endif /* DEVELOPER */

static size_t num_cmdlist;
Expand Down
10 changes: 10 additions & 0 deletions lightningd/test/run-jsonrpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ struct command_result *param_feerate_estimate(struct command *cmd UNNEEDED,
u32 **feerate_per_kw UNNEEDED,
enum feerate feerate UNNEEDED)
{ fprintf(stderr, "param_feerate_estimate called!\n"); abort(); }
/* Generated stub for param_ignore */
struct command_result *param_ignore(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
const void *unused UNNEEDED)
{ fprintf(stderr, "param_ignore called!\n"); abort(); }
/* Generated stub for param_number */
struct command_result *param_number(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
Expand All @@ -80,6 +85,11 @@ struct command_result *param_sha256(struct command *cmd UNNEEDED, const char *na
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
struct sha256 **hash UNNEEDED)
{ fprintf(stderr, "param_sha256 called!\n"); abort(); }
/* Generated stub for param_subcommand */
const char *param_subcommand(struct command *cmd UNNEEDED, const char *buffer UNNEEDED,
const jsmntok_t tokens[] UNNEEDED,
const char *name UNNEEDED, ...)
{ fprintf(stderr, "param_subcommand called!\n"); abort(); }
/* Generated stub for param_tok */
struct command_result *param_tok(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char *buffer UNNEEDED, const jsmntok_t * tok UNNEEDED,
Expand Down
67 changes: 59 additions & 8 deletions tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,14 +506,14 @@ def test_multiplexed_rpc(node_factory):

# Neighbouring ones may be in or out of order.
commands = [
b'{"id":1,"jsonrpc":"2.0","method":"dev-slowcmd","params":[2000]}',
b'{"id":1,"jsonrpc":"2.0","method":"dev-slowcmd","params":[2000]}',
b'{"id":2,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1500]}',
b'{"id":2,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1500]}',
b'{"id":3,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1000]}',
b'{"id":3,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1000]}',
b'{"id":4,"jsonrpc":"2.0","method":"dev-slowcmd","params":[500]}',
b'{"id":4,"jsonrpc":"2.0","method":"dev-slowcmd","params":[500]}'
b'{"id":1,"jsonrpc":"2.0","method":"dev","params":["slowcmd",2000]}',
b'{"id":1,"jsonrpc":"2.0","method":"dev","params":["slowcmd",2000]}',
b'{"id":2,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1500]}',
b'{"id":2,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1500]}',
b'{"id":3,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1000]}',
b'{"id":3,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1000]}',
b'{"id":4,"jsonrpc":"2.0","method":"dev","params":["slowcmd",500]}',
b'{"id":4,"jsonrpc":"2.0","method":"dev","params":["slowcmd",500]}'
]

sock.sendall(b'\n'.join(commands))
Expand Down Expand Up @@ -1260,3 +1260,54 @@ def mock_fail(*args):
l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', None)

f.result()


@unittest.skipIf(not DEVELOPER, "needs dev command")
def test_dev_demux(node_factory):
l1 = node_factory.get_node(may_fail=True, allow_broken_log=True)

# Check should work.
l1.rpc.check(command_to_check='dev', subcommand='crash')
l1.rpc.check(command_to_check='dev', subcommand='slowcmd', msec=1000)
l1.rpc.check(command_to_check='dev', subcommand='rhash', secret='00' * 32)
with pytest.raises(RpcError, match=r'Unknown subcommand'):
l1.rpc.check(command_to_check='dev', subcommand='foobar')
with pytest.raises(RpcError, match=r'unknown parameter'):
l1.rpc.check(command_to_check='dev', subcommand='crash', unk=1)
with pytest.raises(RpcError, match=r"'msec' should be an integer"):
l1.rpc.check(command_to_check='dev', subcommand='slowcmd', msec='aaa')
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.check(command_to_check='dev', subcommand='rhash')
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.check(command_to_check='dev')

# Non-check failures should fail, in both object and array form.
with pytest.raises(RpcError, match=r'Unknown subcommand'):
l1.rpc.call('dev', {'subcommand': 'foobar'})
with pytest.raises(RpcError, match=r'Unknown subcommand'):
l1.rpc.call('dev', ['foobar'])
with pytest.raises(RpcError, match=r'unknown parameter'):
l1.rpc.call('dev', {'subcommand': 'crash', 'unk': 1})
with pytest.raises(RpcError, match=r'too many parameters'):
l1.rpc.call('dev', ['crash', 1])
with pytest.raises(RpcError, match=r"'msec' should be an integer"):
l1.rpc.call('dev', {'subcommand': 'slowcmd', 'msec': 'aaa'})
with pytest.raises(RpcError, match=r"'msec' should be an integer"):
l1.rpc.call('dev', ['slowcmd', 'aaa'])
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.call('dev', {'subcommand': 'rhash'})
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.call('dev', ['rhash'])
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.call('dev')

# Help should list them all.
assert 'subcommand=crash|rhash|slowcmd' in l1.rpc.help('dev')['help'][0]['command']

# These work
assert l1.rpc.call('dev', ['slowcmd', '7'])['msec'] == 7
assert l1.rpc.call('dev', {'subcommand': 'slowcmd', 'msec': '7'})['msec'] == 7
assert l1.rpc.call('dev', {'subcommand': 'rhash', 'secret': '00' * 32})['rhash'] == '66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925'

with pytest.raises(RpcError):
l1.rpc.call('dev', {'subcommand': 'crash'})