Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notification support #4046

Merged
merged 13 commits into from
Oct 23, 2020
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
2 changes: 1 addition & 1 deletion ccan/README
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
CCAN imported from http://ccodearchive.net.

CCAN version: init-2500-gcbc7cbf1
CCAN version: init-2502-gb45a3266
3 changes: 2 additions & 1 deletion ccan/ccan/json_out/json_out.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,11 +337,12 @@ char *json_out_direct(struct json_out *jout, size_t len)
return p;
}

void json_out_finished(const struct json_out *jout)
void json_out_finished(struct json_out *jout)
{
#ifdef CCAN_JSON_OUT_DEBUG
assert(tal_count(jout->wrapping) == 0);
#endif
jout->empty = true;
}

const char *json_out_contents(const struct json_out *jout, size_t *len)
Expand Down
7 changes: 5 additions & 2 deletions ccan/ccan/json_out/json_out.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,12 @@ bool json_out_add_splice(struct json_out *jout,
* @jout: the json_out object written to.
*
* This simply causes internal assertions that all arrays and objects are
* finished. It needs CCAN_JSON_OUT_DEBUG defined to have any effect.
* finished. If CCAN_JSON_OUT_DEBUG is defined, it does sanity checks.
*
* This also resets the empty flag, so there will be no comma added if
* another JSON object is written.
*/
void json_out_finished(const struct json_out *jout);
void json_out_finished(struct json_out *jout);

/**
* json_out_contents - read contents from json_out stream.
Expand Down
43 changes: 43 additions & 0 deletions ccan/ccan/opt/test/run-unregister.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include <ccan/tap/tap.h>
#include <stdlib.h>
#include <ccan/opt/opt.c>
#include <ccan/opt/usage.c>
#include <ccan/opt/helpers.c>
#include <ccan/opt/parse.c>
#include "utils.h"

int main(int argc, char *argv[])
{
const char *myname = argv[0];

plan_tests(15);

opt_register_noarg("--aaa|-a", test_noarg, NULL, "AAAAAAll");
opt_register_arg("-b", test_arg, NULL, "bbb", "b");

/* We can't unregister wrong ones, but can unregister correct one */
ok1(!opt_unregister("--aaa"));
ok1(!opt_unregister("-a"));
ok1(opt_unregister("--aaa|-a"));

/* Arg parsing works as if we'd never registered it */
ok1(parse_args(&argc, &argv, "-bbbb", NULL));
ok1(argc == 1);
ok1(argv[0] == myname);
ok1(argv[1] == NULL);
ok1(test_cb_called == 1);

ok1(!parse_args(&argc, &argv, "--aaa", NULL));

/* We can still add another one OK. */
opt_register_noarg("-c", test_noarg, NULL, "AAAAAAll");
ok1(parse_args(&argc, &argv, "-c", NULL));
ok1(argc == 1);
ok1(argv[0] == myname);
ok1(argv[1] == NULL);
ok1(test_cb_called == 2);

/* parse_args allocates argv */
free(argv);
return exit_status();
}
1 change: 1 addition & 0 deletions cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ LIGHTNING_CLI_COMMON_OBJS := \
common/configdir.o \
common/json.o \
common/json_stream.o \
common/status_levels.o \
common/utils.o \
common/version.o

Expand Down
174 changes: 166 additions & 8 deletions cli/lightning-cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <common/json.h>
#include <common/json_command.h>
#include <common/memleak.h>
#include <common/status_levels.h>
#include <common/utils.h>
#include <common/version.h>
#include <libgen.h>
Expand Down Expand Up @@ -470,6 +471,135 @@ static enum format choose_format(const char *resp,
return format;
}

static bool handle_notify(const char *buf, jsmntok_t *toks,
enum log_level notification_level,
bool *last_was_progress)
{
const jsmntok_t *id, *method, *params;

if (toks->type != JSMN_OBJECT)
return false;

id = json_get_member(buf, toks, "id");
if (id)
return false;

method = json_get_member(buf, toks, "method");
if (!method)
return false;

params = json_get_member(buf, toks, "params");
if (!params)
return false;

/* Print nothing if --notifications=none */
if (notification_level == LOG_LEVEL_MAX + 1)
return true;

/* We try to be robust if malformed */
if (json_tok_streq(buf, method, "message")) {
const jsmntok_t *message, *leveltok;
enum log_level level;

leveltok = json_get_member(buf, params, "level");
if (!leveltok
|| !log_level_parse(buf + leveltok->start,
leveltok->end - leveltok->start,
&level)
|| level < notification_level)
return true;

if (*last_was_progress)
printf("\n");
*last_was_progress = false;
message = json_get_member(buf, params, "message");
if (!message)
return true;

printf("# %.*s\n",
message->end - message->start,
buf + message->start);
} else if (json_tok_streq(buf, method, "progress")) {
const jsmntok_t *num, *total, *stage;
u32 n, tot;
char bar[60 + 1];
char totstr[STR_MAX_CHARS(u32)];

num = json_get_member(buf, params, "num");
total = json_get_member(buf, params, "total");
if (!num || !total)
return true;
if (!json_to_u32(buf, num, &n)
|| !json_to_u32(buf, total, &tot))
return true;

/* Ph3ar my gui skillz! */
printf("\r# ");
stage = json_get_member(buf, params, "stage");
if (stage) {
u32 stage_num, stage_total;
json_to_u32(buf, json_get_member(buf, stage, "num"),
&stage_num);
json_to_u32(buf, json_get_member(buf, stage, "total"),
&stage_total);
snprintf(totstr, sizeof(totstr), "%u", stage_total);
printf("Stage %*u/%s ",
(int)strlen(totstr), stage_num+1, totstr);
}
snprintf(totstr, sizeof(totstr), "%u", tot);
printf("%*u/%s ", (int)strlen(totstr), n+1, totstr);
memset(bar, ' ', sizeof(bar)-1);
memset(bar, '=', (double)strlen(bar) / (tot-1) * n);
bar[sizeof(bar)-1] = '\0';
printf("|%s|", bar);
/* Leave bar there if it's finished. */
if (n+1 == tot) {
printf("\n");
*last_was_progress = false;
} else {
fflush(stdout);
*last_was_progress = true;
}
}

return true;
}

static void enable_notifications(int fd)
{
const char *enable = "{ \"jsonrpc\": \"2.0\", \"method\": \"notifications\", \"id\": 0, \"params\": { \"enable\": true } }";
char rbuf[100];

if (!write_all(fd, enable, strlen(enable)))
err(ERROR_TALKING_TO_LIGHTNINGD, "Writing enable command");

/* We get a very simple response, ending in \n\n. */
memset(rbuf, 0, sizeof(rbuf));
while (!strends(rbuf, "\n\n")) {
size_t len = strlen(rbuf);
if (read(fd, rbuf + len, sizeof(rbuf) - len) < 0)
err(ERROR_TALKING_TO_LIGHTNINGD,
"Reading enable response");
}
}

static char *opt_set_level(const char *arg, enum log_level *level)
{
if (streq(arg, "none"))
*level = LOG_LEVEL_MAX + 1;
else if (!log_level_parse(arg, strlen(arg), level))
return "Invalid level";
return NULL;
}

static void opt_show_level(char buf[OPT_SHOW_LEN], const enum log_level *level)
{
if (*level == LOG_LEVEL_MAX + 1)
strncpy(buf, "none", OPT_SHOW_LEN-1);
else
strncpy(buf, log_level_name(*level), OPT_SHOW_LEN-1);
}

int main(int argc, char *argv[])
{
setup_locale();
Expand All @@ -487,6 +617,8 @@ int main(int argc, char *argv[])
int parserr;
enum format format = DEFAULT_FORMAT;
enum input input = DEFAULT_INPUT;
enum log_level notification_level = LOG_INFORM;
bool last_was_progress = false;
char *command = NULL;

err_set_progname(argv[0]);
Expand Down Expand Up @@ -514,6 +646,9 @@ int main(int argc, char *argv[])
"Use format key=value for <params>");
opt_register_noarg("-o|--order", opt_set_ordered, &input,
"Use params in order for <params>");
opt_register_arg("-N|--notifications", opt_set_level,
opt_show_level, &notification_level,
"Set notification level, or none");

opt_register_version();

Expand Down Expand Up @@ -567,6 +702,10 @@ int main(int argc, char *argv[])
"Connecting to '%s'", rpc_filename);

idstr = tal_fmt(ctx, "lightning-cli-%i", getpid());

if (notification_level <= LOG_LEVEL_MAX)
enable_notifications(fd);

cmd = tal_fmt(ctx,
"{ \"jsonrpc\" : \"2.0\", \"method\" : \"%s\", \"id\" : \"%s\", \"params\" :",
json_escape(ctx, method)->s, idstr);
Expand Down Expand Up @@ -607,6 +746,7 @@ int main(int argc, char *argv[])
/* Start with 1000 characters, 100 tokens. */
resp = tal_arr(ctx, char, 1000);
toks = tal_arr(ctx, jsmntok_t, 100);
toks[0].type = JSMN_UNDEFINED;

off = 0;
parserr = 0;
Expand All @@ -632,27 +772,45 @@ int main(int argc, char *argv[])
case JSMN_ERROR_INVAL:
errx(ERROR_TALKING_TO_LIGHTNINGD,
"Malformed response '%s'", resp);
case JSMN_ERROR_NOMEM: {
case JSMN_ERROR_NOMEM:
/* Need more tokens, double it */
if (!tal_resize(&toks, tal_count(toks) * 2))
oom_dump(fd, resp, off);
break;
}
case JSMN_ERROR_PART:
/* Need more data: make room if necessary */
if (off == tal_bytelen(resp) - 1) {
if (!tal_resize(&resp, tal_count(resp) * 2))
oom_dump(fd, resp, off);
/* We may actually have a complete token! */
if (toks[0].type == JSMN_UNDEFINED || toks[0].end == -1) {
/* Need more data: make room if necessary */
if (off == tal_bytelen(resp) - 1) {
if (!tal_resize(&resp, tal_count(resp) * 2))
oom_dump(fd, resp, off);
}
break;
}
/* Otherwise fall through... */
default:
if (handle_notify(resp, toks, notification_level,
&last_was_progress)) {
/* +2 for \n\n */
size_t len = toks[0].end - toks[0].start + 2;
memmove(resp, resp + len, off - len);
off -= len;
jsmn_init(&parser);
toks[0].type = JSMN_UNDEFINED;
/* Don't force another read! */
parserr = JSMN_ERROR_NOMEM;
}
break;
}
}

if (toks->type != JSMN_OBJECT)
errx(ERROR_TALKING_TO_LIGHTNINGD,
"Non-object response '%s'", resp);

/* This can rellocate toks, so call before getting pointers to tokens */
if (last_was_progress)
printf("\n");

/* This can reallocate toks, so call before getting pointers to tokens */
format = choose_format(resp, &toks, method, command, format);
result = json_get_member(resp, toks, "result");
error = json_get_member(resp, toks, "error");
Expand Down
11 changes: 9 additions & 2 deletions cli/test/run-human-mode.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ void json_add_member(struct json_stream *js UNNEEDED,
char *json_member_direct(struct json_stream *js UNNEEDED,
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
{ fprintf(stderr, "json_member_direct called!\n"); abort(); }
/* Generated stub for log_level_name */
const char *log_level_name(enum log_level level UNNEEDED)
{ fprintf(stderr, "log_level_name called!\n"); abort(); }
/* Generated stub for log_level_parse */
bool log_level_parse(const char *levelstr UNNEEDED, size_t len UNNEEDED,
enum log_level *level UNNEEDED)
{ fprintf(stderr, "log_level_parse called!\n"); abort(); }
/* Generated stub for towire_amount_msat */
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
Expand Down Expand Up @@ -162,11 +169,11 @@ int main(int argc UNUSED, char *argv[])
{
setup_locale();

char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "-H", "listconfigs", NULL };
char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "-H", "listconfigs", "-N", "none", NULL };

response_off = 0;
max_read_return = -1;
assert(test_main(4, fake_argv) == 0);
assert(test_main(6, fake_argv) == 0);
assert(!taken_any());
take_cleanup();
return 0;
Expand Down
11 changes: 9 additions & 2 deletions cli/test/run-large-input.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ void json_add_member(struct json_stream *js UNNEEDED,
char *json_member_direct(struct json_stream *js UNNEEDED,
const char *fieldname UNNEEDED, size_t extra UNNEEDED)
{ fprintf(stderr, "json_member_direct called!\n"); abort(); }
/* Generated stub for log_level_name */
const char *log_level_name(enum log_level level UNNEEDED)
{ fprintf(stderr, "log_level_name called!\n"); abort(); }
/* Generated stub for log_level_parse */
bool log_level_parse(const char *levelstr UNNEEDED, size_t len UNNEEDED,
enum log_level *level UNNEEDED)
{ fprintf(stderr, "log_level_parse called!\n"); abort(); }
/* Generated stub for towire_amount_msat */
void towire_amount_msat(u8 **pptr UNNEEDED, const struct amount_msat msat UNNEEDED)
{ fprintf(stderr, "towire_amount_msat called!\n"); abort(); }
Expand Down Expand Up @@ -171,7 +178,7 @@ int main(int argc UNUSED, char *argv[])
{
setup_locale();

char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "test", NULL };
char *fake_argv[] = { argv[0], "--lightning-dir=/tmp/", "test", "-N", "none", NULL };

/* sizeof() is an overestimate, but we don't care. */
response = tal_arr(NULL, char,
Expand All @@ -196,7 +203,7 @@ int main(int argc UNUSED, char *argv[])

response_off = 0;
max_read_return = -1;
assert(test_main(3, fake_argv) == 0);
assert(test_main(5, fake_argv) == 0);
tal_free(response);
assert(!taken_any());
take_cleanup();
Expand Down
Loading