Browse Source
Also taken from pettycoin. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>ppa-0.6.1
Rusty Russell
9 years ago
4 changed files with 523 additions and 0 deletions
@ -0,0 +1,456 @@ |
|||
/* Code for JSON_RPC API */ |
|||
/* eg: { "method" : "dev-echo", "params" : [ "hello", "Arabella!" ], "id" : "1" } */ |
|||
#include "json.h" |
|||
#include "jsonrpc.h" |
|||
#include "lightningd.h" |
|||
#include "log.h" |
|||
#include <ccan/array_size/array_size.h> |
|||
#include <ccan/err/err.h> |
|||
#include <ccan/io/io.h> |
|||
#include <ccan/tal/str/str.h> |
|||
#include <errno.h> |
|||
#include <fcntl.h> |
|||
#include <stdio.h> |
|||
#include <sys/socket.h> |
|||
#include <sys/stat.h> |
|||
#include <sys/types.h> |
|||
#include <sys/un.h> |
|||
|
|||
struct json_output { |
|||
struct list_node list; |
|||
const char *json; |
|||
}; |
|||
|
|||
static void finish_jcon(struct io_conn *conn, struct json_connection *jcon) |
|||
{ |
|||
log_info(jcon->log, "Closing (%s)", strerror(errno)); |
|||
} |
|||
|
|||
static char *json_help(struct json_connection *jcon, |
|||
const jsmntok_t *params, |
|||
struct json_result *response); |
|||
|
|||
static const struct json_command help_command = { |
|||
"help", |
|||
json_help, |
|||
"describe commands", |
|||
"[<command>] if specified gives details about a single command." |
|||
}; |
|||
|
|||
static char *json_echo(struct json_connection *jcon, |
|||
const jsmntok_t *params, |
|||
struct json_result *response) |
|||
{ |
|||
json_object_start(response, NULL); |
|||
json_add_num(response, "num", params->size); |
|||
json_add_literal(response, "echo", |
|||
json_tok_contents(jcon->buffer, params), |
|||
json_tok_len(params)); |
|||
json_object_end(response); |
|||
return NULL; |
|||
} |
|||
|
|||
static const struct json_command echo_command = { |
|||
"dev-echo", |
|||
json_echo, |
|||
"echo parameters", |
|||
"Simple echo test for developers" |
|||
}; |
|||
|
|||
static char *json_stop(struct json_connection *jcon, |
|||
const jsmntok_t *params, |
|||
struct json_result *response) |
|||
{ |
|||
jcon->stop = true; |
|||
json_add_string(response, NULL, "Shutting down"); |
|||
return NULL; |
|||
} |
|||
|
|||
static const struct json_command stop_command = { |
|||
"stop", |
|||
json_stop, |
|||
"Shutdown the lightningd process", |
|||
"What part of shutdown wasn't clear?" |
|||
}; |
|||
|
|||
struct log_info { |
|||
enum log_level level; |
|||
struct json_result *response; |
|||
unsigned int num_skipped; |
|||
}; |
|||
|
|||
static void add_skipped(struct log_info *info) |
|||
{ |
|||
if (info->num_skipped) { |
|||
json_array_start(info->response, NULL); |
|||
json_add_string(info->response, "type", "SKIPPED"); |
|||
json_add_num(info->response, "num_skipped", info->num_skipped); |
|||
json_array_end(info->response); |
|||
info->num_skipped = 0; |
|||
} |
|||
} |
|||
|
|||
static void json_add_time(struct json_result *result, const char *fieldname, |
|||
struct timespec ts) |
|||
{ |
|||
char timebuf[100]; |
|||
|
|||
sprintf(timebuf, "%lu.%09u", |
|||
(unsigned long)ts.tv_sec, |
|||
(unsigned)ts.tv_nsec); |
|||
json_add_string(result, fieldname, timebuf); |
|||
} |
|||
|
|||
static void log_to_json(unsigned int skipped, |
|||
struct timerel diff, |
|||
enum log_level level, |
|||
const char *prefix, |
|||
const char *log, |
|||
struct log_info *info) |
|||
{ |
|||
info->num_skipped += skipped; |
|||
|
|||
if (level < info->level) { |
|||
info->num_skipped++; |
|||
return; |
|||
} |
|||
|
|||
add_skipped(info); |
|||
|
|||
json_array_start(info->response, NULL); |
|||
json_add_string(info->response, "type", |
|||
level == LOG_BROKEN ? "BROKEN" |
|||
: level == LOG_UNUSUAL ? "UNUSUAL" |
|||
: level == LOG_INFORM ? "INFO" |
|||
: level == LOG_DBG ? "DEBUG" |
|||
: level == LOG_IO ? "IO" |
|||
: "UNKNOWN"); |
|||
json_add_time(info->response, "time", diff.ts); |
|||
json_add_string(info->response, "source", prefix); |
|||
if (level == LOG_IO) { |
|||
if (log[0]) |
|||
json_add_string(info->response, "direction", "IN"); |
|||
else |
|||
json_add_string(info->response, "direction", "OUT"); |
|||
|
|||
json_add_hex(info->response, "data", log+1, tal_count(log)-1); |
|||
} else |
|||
json_add_string(info->response, "log", log); |
|||
|
|||
json_array_end(info->response); |
|||
} |
|||
|
|||
static char *json_getlog(struct json_connection *jcon, |
|||
const jsmntok_t *params, |
|||
struct json_result *response) |
|||
{ |
|||
struct log_info info; |
|||
struct log_record *lr = jcon->state->log_record; |
|||
jsmntok_t *level; |
|||
|
|||
json_get_params(jcon->buffer, params, "level", &level, NULL); |
|||
|
|||
info.response = response; |
|||
info.num_skipped = 0; |
|||
|
|||
if (!level) |
|||
info.level = LOG_INFORM; |
|||
else if (json_tok_streq(jcon->buffer, level, "io")) |
|||
info.level = LOG_IO; |
|||
else if (json_tok_streq(jcon->buffer, level, "debug")) |
|||
info.level = LOG_DBG; |
|||
else if (json_tok_streq(jcon->buffer, level, "info")) |
|||
info.level = LOG_INFORM; |
|||
else if (json_tok_streq(jcon->buffer, level, "unusual")) |
|||
info.level = LOG_UNUSUAL; |
|||
else |
|||
return "Invalid level param"; |
|||
|
|||
json_object_start(response, NULL); |
|||
json_add_time(response, "creation_time", log_init_time(lr)->ts); |
|||
json_add_num(response, "bytes_used", (unsigned int)log_used(lr)); |
|||
json_add_num(response, "bytes_max", (unsigned int)log_max_mem(lr)); |
|||
json_object_start(response, "log"); |
|||
log_each_line(lr, log_to_json, &info); |
|||
json_object_end(response); |
|||
json_object_end(response); |
|||
return NULL; |
|||
} |
|||
|
|||
static const struct json_command getlog_command = { |
|||
"getlog", |
|||
json_getlog, |
|||
"Get logs, with optional level: [io|debug|info|unusual]", |
|||
"Returns log array" |
|||
}; |
|||
|
|||
static const struct json_command *cmdlist[] = { |
|||
&help_command, |
|||
&stop_command, |
|||
&getlog_command, |
|||
/* Developer/debugging options. */ |
|||
&echo_command, |
|||
}; |
|||
|
|||
static char *json_help(struct json_connection *jcon, |
|||
const jsmntok_t *params, |
|||
struct json_result *response) |
|||
{ |
|||
unsigned int i; |
|||
|
|||
json_array_start(response, NULL); |
|||
for (i = 0; i < ARRAY_SIZE(cmdlist); i++) { |
|||
json_add_object(response, |
|||
"command", JSMN_STRING, |
|||
cmdlist[i]->name, |
|||
"description", JSMN_STRING, |
|||
cmdlist[i]->description, |
|||
NULL); |
|||
} |
|||
json_array_end(response); |
|||
return NULL; |
|||
} |
|||
|
|||
static const struct json_command *find_cmd(const char *buffer, |
|||
const jsmntok_t *tok) |
|||
{ |
|||
unsigned int i; |
|||
|
|||
/* cmdlist[i]->name can be NULL in test code. */ |
|||
for (i = 0; i < ARRAY_SIZE(cmdlist); i++) |
|||
if (cmdlist[i]->name |
|||
&& json_tok_streq(buffer, tok, cmdlist[i]->name)) |
|||
return cmdlist[i]; |
|||
return NULL; |
|||
} |
|||
|
|||
/* Returns NULL if it's a fatal error. */ |
|||
static char *parse_request(struct json_connection *jcon, const jsmntok_t tok[]) |
|||
{ |
|||
const jsmntok_t *method, *id, *params; |
|||
const struct json_command *cmd; |
|||
char *error; |
|||
struct json_result *result; |
|||
|
|||
if (tok[0].type != JSMN_OBJECT) { |
|||
log_unusual(jcon->log, "Expected {} for json command"); |
|||
return NULL; |
|||
} |
|||
|
|||
method = json_get_member(jcon->buffer, tok, "method"); |
|||
params = json_get_member(jcon->buffer, tok, "params"); |
|||
id = json_get_member(jcon->buffer, tok, "id"); |
|||
|
|||
if (!id || !method || !params) { |
|||
log_unusual(jcon->log, "json: No %s", |
|||
!id ? "id" : (!method ? "method" : "params")); |
|||
return NULL; |
|||
} |
|||
|
|||
if (id->type != JSMN_STRING && id->type != JSMN_PRIMITIVE) { |
|||
log_unusual(jcon->log, "Expected string/primitive for id"); |
|||
return NULL; |
|||
} |
|||
|
|||
if (method->type != JSMN_STRING) { |
|||
log_unusual(jcon->log, "Expected string for method"); |
|||
return NULL; |
|||
} |
|||
|
|||
cmd = find_cmd(jcon->buffer, method); |
|||
if (!cmd) { |
|||
return tal_fmt(jcon, |
|||
"{ \"result\" : null," |
|||
" \"error\" : \"Unknown command '%.*s'\"," |
|||
" \"id\" : %.*s }\n", |
|||
(int)(method->end - method->start), |
|||
jcon->buffer + method->start, |
|||
json_tok_len(id), |
|||
json_tok_contents(jcon->buffer, id)); |
|||
} |
|||
|
|||
if (params->type != JSMN_ARRAY && params->type != JSMN_OBJECT) { |
|||
log_unusual(jcon->log, "Expected array or object for params"); |
|||
return NULL; |
|||
} |
|||
|
|||
result = new_json_result(jcon); |
|||
error = cmd->dispatch(jcon, params, result); |
|||
if (error) { |
|||
char *quote; |
|||
|
|||
/* Remove " */ |
|||
while ((quote = strchr(error, '"')) != NULL) |
|||
*quote = '\''; |
|||
|
|||
return tal_fmt(jcon, |
|||
"{ \"result\" : null," |
|||
" \"error\" : \"%s\"," |
|||
" \"id\" : %.*s }\n", |
|||
error, |
|||
json_tok_len(id), |
|||
json_tok_contents(jcon->buffer, id)); |
|||
} |
|||
return tal_fmt(jcon, |
|||
"{ \"result\" : %s," |
|||
" \"error\" : null," |
|||
" \"id\" : %.*s }\n", |
|||
json_result_string(result), |
|||
json_tok_len(id), |
|||
json_tok_contents(jcon->buffer, id)); |
|||
} |
|||
|
|||
static struct io_plan *write_json(struct io_conn *conn, |
|||
struct json_connection *jcon) |
|||
{ |
|||
struct json_output *out; |
|||
|
|||
out = list_pop(&jcon->output, struct json_output, list); |
|||
if (!out) { |
|||
if (jcon->stop) { |
|||
log_unusual(jcon->log, "JSON-RPC shutdown"); |
|||
/* Return us to toplevel lightningd.c */ |
|||
io_break(jcon->state); |
|||
return io_close(conn); |
|||
} |
|||
|
|||
/* Reader can go again now. */ |
|||
io_wake(jcon); |
|||
return io_out_wait(conn, jcon, write_json, jcon); |
|||
} |
|||
|
|||
jcon->outbuf = tal_steal(jcon, out->json); |
|||
tal_free(out); |
|||
|
|||
log_io(jcon->log, false, jcon->outbuf, strlen(jcon->outbuf)); |
|||
return io_write(conn, |
|||
jcon->outbuf, strlen(jcon->outbuf), write_json, jcon); |
|||
} |
|||
|
|||
static struct io_plan *read_json(struct io_conn *conn, |
|||
struct json_connection *jcon) |
|||
{ |
|||
jsmntok_t *toks; |
|||
bool valid; |
|||
struct json_output *out; |
|||
|
|||
log_io(jcon->log, true, jcon->buffer + jcon->used, jcon->len_read); |
|||
|
|||
/* Resize larger if we're full. */ |
|||
jcon->used += jcon->len_read; |
|||
if (jcon->used == tal_count(jcon->buffer)) |
|||
tal_resize(&jcon->buffer, jcon->used * 2); |
|||
|
|||
again: |
|||
toks = json_parse_input(jcon->buffer, jcon->used, &valid); |
|||
if (!toks) { |
|||
if (!valid) { |
|||
log_unusual(jcon->state->base_log, |
|||
"Invalid token in json input: '%.*s'", |
|||
(int)jcon->used, jcon->buffer); |
|||
return io_close(conn); |
|||
} |
|||
/* We need more. */ |
|||
goto read_more; |
|||
} |
|||
|
|||
/* Empty buffer? (eg. just whitespace). */ |
|||
if (tal_count(toks) == 1) { |
|||
jcon->used = 0; |
|||
goto read_more; |
|||
} |
|||
|
|||
out = tal(jcon, struct json_output); |
|||
out->json = parse_request(jcon, toks); |
|||
if (!out->json) |
|||
return io_close(conn); |
|||
|
|||
/* Remove first {}. */ |
|||
memmove(jcon->buffer, jcon->buffer + toks[0].end, |
|||
tal_count(jcon->buffer) - toks[0].end); |
|||
jcon->used -= toks[0].end; |
|||
tal_free(toks); |
|||
|
|||
/* Queue for writing, and wake writer. */ |
|||
list_add_tail(&jcon->output, &out->list); |
|||
io_wake(jcon); |
|||
|
|||
/* See if we can parse the rest. */ |
|||
goto again; |
|||
|
|||
read_more: |
|||
tal_free(toks); |
|||
return io_read_partial(conn, jcon->buffer + jcon->used, |
|||
tal_count(jcon->buffer) - jcon->used, |
|||
&jcon->len_read, read_json, jcon); |
|||
} |
|||
|
|||
static struct io_plan *jcon_connected(struct io_conn *conn, |
|||
struct lightningd_state *state) |
|||
{ |
|||
struct json_connection *jcon; |
|||
|
|||
jcon = tal(state, struct json_connection); |
|||
jcon->state = state; |
|||
jcon->used = 0; |
|||
jcon->len_read = 64; |
|||
jcon->buffer = tal_arr(jcon, char, jcon->len_read); |
|||
jcon->stop = false; |
|||
jcon->log = new_log(jcon, state->log_record, "%sjcon fd %i:", |
|||
log_prefix(state->base_log), io_conn_fd(conn)); |
|||
list_head_init(&jcon->output); |
|||
|
|||
io_set_finish(conn, finish_jcon, jcon); |
|||
|
|||
return io_duplex(conn, |
|||
io_read_partial(conn, jcon->buffer, |
|||
tal_count(jcon->buffer), |
|||
&jcon->len_read, read_json, jcon), |
|||
write_json(conn, jcon)); |
|||
} |
|||
|
|||
static struct io_plan *incoming_jcon_connected(struct io_conn *conn, |
|||
struct lightningd_state *state) |
|||
{ |
|||
log_info(state->base_log, "Connected json input"); |
|||
return jcon_connected(conn, state); |
|||
} |
|||
|
|||
void setup_jsonrpc(struct lightningd_state *state, const char *rpc_filename) |
|||
{ |
|||
struct sockaddr_un addr; |
|||
int fd, old_umask; |
|||
|
|||
if (streq(rpc_filename, "")) |
|||
return; |
|||
|
|||
if (streq(rpc_filename, "/dev/tty")) { |
|||
fd = open(rpc_filename, O_RDWR); |
|||
if (fd == -1) |
|||
err(1, "Opening %s", rpc_filename); |
|||
io_new_conn(state, fd, jcon_connected, state); |
|||
return; |
|||
} |
|||
|
|||
fd = socket(AF_UNIX, SOCK_STREAM, 0); |
|||
if (strlen(rpc_filename) + 1 > sizeof(addr.sun_path)) |
|||
errx(1, "rpc filename '%s' too long", rpc_filename); |
|||
strcpy(addr.sun_path, rpc_filename); |
|||
addr.sun_family = AF_UNIX; |
|||
|
|||
/* Of course, this is racy! */ |
|||
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) |
|||
errx(1, "rpc filename '%s' in use", rpc_filename); |
|||
unlink(rpc_filename); |
|||
|
|||
/* This file is only rw by us! */ |
|||
old_umask = umask(0177); |
|||
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr))) |
|||
err(1, "Binding rpc socket to '%s'", rpc_filename); |
|||
umask(old_umask); |
|||
|
|||
if (listen(fd, 1) != 0) |
|||
err(1, "Listening on '%s'", rpc_filename); |
|||
|
|||
io_new_listener(state, fd, incoming_jcon_connected, state); |
|||
} |
@ -0,0 +1,47 @@ |
|||
#ifndef LIGHTNING_DAEMON_JSONRPC_H |
|||
#define LIGHTNING_DAEMON_JSONRPC_H |
|||
#include "config.h" |
|||
#include "json.h" |
|||
#include <ccan/list/list.h> |
|||
|
|||
struct json_connection { |
|||
/* The global state */ |
|||
struct lightningd_state *state; |
|||
|
|||
/* Logging for this json connection. */ |
|||
struct log *log; |
|||
|
|||
/* The buffer (required to interpret tokens). */ |
|||
char *buffer; |
|||
|
|||
/* Internal state: */ |
|||
/* How much is already filled. */ |
|||
size_t used; |
|||
/* How much has just been filled. */ |
|||
size_t len_read; |
|||
|
|||
/* We've been told to stop. */ |
|||
bool stop; |
|||
|
|||
struct list_head output; |
|||
const char *outbuf; |
|||
}; |
|||
|
|||
struct json_command { |
|||
const char *name; |
|||
char *(*dispatch)(struct json_connection *jcon, |
|||
const jsmntok_t *params, |
|||
struct json_result *result); |
|||
const char *description; |
|||
const char *help; |
|||
}; |
|||
|
|||
/* Add notification about something. */ |
|||
void json_notify(struct json_connection *jcon, const char *result); |
|||
|
|||
/* For initialization */ |
|||
void setup_jsonrpc(struct lightningd_state *state, const char *rpc_filename); |
|||
|
|||
/* Commands (from other files) */ |
|||
|
|||
#endif /* LIGHTNING_DAEMON_JSONRPC_H */ |
Loading…
Reference in new issue