Skip to content

Commit 68f6a1a

Browse files
committed
common/jsonrpc_io: helper routines for reading JSON from sockets.
The efficient way to do this is to use membuf, which handles the buffer control (only using memmove when necessary). We have multiple places where we opencoded this, some of which did not use membuf at all. So now we create common infrastructure. I tried making it a single function but the various users are quite different, so instead I opted for a toolbox approach. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
1 parent dd7cc71 commit 68f6a1a

File tree

5 files changed

+510
-0
lines changed

5 files changed

+510
-0
lines changed

common/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ COMMON_SRC_NOGEN := \
6262
common/json_parse.c \
6363
common/json_parse_simple.c \
6464
common/json_stream.c \
65+
common/jsonrpc_io.c \
6566
common/key_derive.c \
6667
common/keyset.c \
6768
common/lease_rates.c \

common/jsonrpc_io.c

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#include "config.h"
2+
3+
#include <ccan/io/io.h>
4+
#include <ccan/membuf/membuf.h>
5+
#include <ccan/tal/str/str.h>
6+
#include <common/jsonrpc_io.h>
7+
#include <common/utils.h>
8+
9+
#define READ_CHUNKSIZE 64
10+
11+
struct jsonrpc_io {
12+
MEMBUF(char) membuf;
13+
jsmn_parser parser;
14+
jsmntok_t *toks;
15+
16+
/* Amount just read by io_read_partial */
17+
size_t bytes_read;
18+
};
19+
20+
struct jsonrpc_io *jsonrpc_io_new(const tal_t *ctx)
21+
{
22+
struct jsonrpc_io *json_in;
23+
24+
json_in = tal(ctx, struct jsonrpc_io);
25+
json_in->bytes_read = 0;
26+
27+
membuf_init(&json_in->membuf,
28+
tal_arr(json_in, char, READ_CHUNKSIZE),
29+
READ_CHUNKSIZE, membuf_tal_resize);
30+
json_in->toks = toks_alloc(json_in);
31+
jsmn_init(&json_in->parser);
32+
33+
return json_in;
34+
}
35+
36+
/* Empty new bytes read into our unparsed buffer */
37+
static void add_newly_read(struct jsonrpc_io *json_in)
38+
{
39+
/* Now added it to our unparsed buffer */
40+
assert(json_in->bytes_read <= membuf_num_space(&json_in->membuf));
41+
membuf_added(&json_in->membuf, json_in->bytes_read);
42+
json_in->bytes_read = 0;
43+
}
44+
45+
const char *jsonrpc_newly_read(struct jsonrpc_io *json_in,
46+
size_t *len)
47+
{
48+
*len = json_in->bytes_read;
49+
50+
add_newly_read(json_in);
51+
52+
return membuf_space(&json_in->membuf) - *len;
53+
}
54+
55+
const char *jsonrpc_io_parse(const tal_t *ctx,
56+
struct jsonrpc_io *json_in,
57+
const jsmntok_t **toks,
58+
const char **buf)
59+
{
60+
bool complete;
61+
62+
/* If we're read any more, add that */
63+
add_newly_read(json_in);
64+
*toks = NULL;
65+
*buf = NULL;
66+
67+
if (!json_parse_input(&json_in->parser, &json_in->toks,
68+
membuf_elems(&json_in->membuf),
69+
membuf_num_elems(&json_in->membuf),
70+
&complete)) {
71+
return tal_fmt(ctx, "Failed to parse RPC JSON response '%.*s'",
72+
(int)membuf_num_elems(&json_in->membuf),
73+
membuf_elems(&json_in->membuf));
74+
}
75+
76+
if (!complete)
77+
return NULL;
78+
79+
/* Must have jsonrpc to be valid! */
80+
if (!json_get_member(membuf_elems(&json_in->membuf),
81+
json_in->toks,
82+
"jsonrpc")) {
83+
return tal_fmt(ctx,
84+
"JSON-RPC message does not contain \"jsonrpc\" field: '%.*s'",
85+
(int)membuf_num_elems(&json_in->membuf),
86+
membuf_elems(&json_in->membuf));
87+
}
88+
89+
*toks = json_in->toks;
90+
*buf = membuf_elems(&json_in->membuf);
91+
return NULL;
92+
}
93+
94+
void jsonrpc_io_parse_done(struct jsonrpc_io *json_in)
95+
{
96+
size_t bytes_parsed = json_in->toks[0].end;
97+
membuf_consume(&json_in->membuf, bytes_parsed);
98+
99+
jsmn_init(&json_in->parser);
100+
toks_reset(json_in->toks);
101+
}
102+
103+
struct io_plan *jsonrpc_io_read_(struct io_conn *conn,
104+
struct jsonrpc_io *json_in,
105+
struct io_plan *(*next)(struct io_conn *,
106+
void *),
107+
void *arg)
108+
{
109+
/* Make sure there's more room */
110+
membuf_prepare_space(&json_in->membuf, READ_CHUNKSIZE);
111+
112+
/* Try to read more. */
113+
json_in->bytes_read = 0;
114+
return io_read_partial(conn,
115+
membuf_space(&json_in->membuf),
116+
membuf_num_space(&json_in->membuf),
117+
&json_in->bytes_read,
118+
next, arg);
119+
}

common/jsonrpc_io.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/* Low-level helper library for C plugins using ccan/io and jsonrpc socket. */
2+
#ifndef LIGHTNING_COMMON_JSONRPC_IO_H
3+
#define LIGHTNING_COMMON_JSONRPC_IO_H
4+
#include "config.h"
5+
#include <ccan/tal/tal.h>
6+
#include <ccan/typesafe_cb/typesafe_cb.h>
7+
#include <common/json_parse_simple.h>
8+
9+
struct io_conn;
10+
struct plugin;
11+
12+
/**
13+
* jsonrpc_io_new: allocate a fresh jsonrpc_io
14+
*/
15+
struct jsonrpc_io *jsonrpc_io_new(const tal_t *ctx);
16+
17+
18+
/**
19+
* jsonrpc_io_read: set io_plan for reading more into buffer.
20+
* @conn: the io_conn to read.
21+
* @json_in: the jsonrpc_io.
22+
* @next: the callback once a read is done.
23+
* @arg: the argument for @next (typesafe).
24+
*/
25+
struct io_plan *jsonrpc_io_read_(struct io_conn *conn,
26+
struct jsonrpc_io *json_in,
27+
struct io_plan *(*next)(struct io_conn *,
28+
void *),
29+
void *arg);
30+
#define jsonrpc_io_read(ctx, json_in, next, arg) \
31+
jsonrpc_io_read_((ctx), (json_in), \
32+
typesafe_cb_preargs(struct io_plan *, void *, \
33+
(next), (arg), \
34+
struct io_conn *), \
35+
(arg))
36+
37+
/**
38+
* jsonrpc_newly_read: how much did we read into the buffer?
39+
*
40+
* Returns the buffer and sets *len to the bytes just read. After
41+
* that it will return *len == 0.
42+
*/
43+
const char *jsonrpc_newly_read(struct jsonrpc_io *json_in,
44+
size_t *len);
45+
46+
/**
47+
* jsonrpc_io_parse: try to parse more of the buffer.
48+
* @ctx: context to allocate error message off.
49+
* @json_in: json_in after jsonrpc_io_read.
50+
* @toks: returned non-NULL if there's a whole valid json object.
51+
* @buf: returned non-NULL as above.
52+
*
53+
* On error, a message is returned. On incomplete, *@toks and *@buf
54+
* are NULL. Usually you call this, the use the result and call
55+
* jsonrpc_io_parse_done(), then call it again.
56+
*/
57+
const char *jsonrpc_io_parse(const tal_t *ctx,
58+
struct jsonrpc_io *json_in,
59+
const jsmntok_t **toks,
60+
const char **buf);
61+
62+
/**
63+
* jsonrpc_io_parse_done: call aftr using toks from jsonrpc_io_parse.
64+
* @json_in: json_in after jsonrpc_io_parse.
65+
*
66+
* You must call this if jsonrpc_io_parse() sets *toks non-NULL
67+
* (i.e. complete, and no error).
68+
*/
69+
void jsonrpc_io_parse_done(struct jsonrpc_io *json_in);
70+
71+
#endif /* LIGHTNING_COMMON_JSONRPC_IO_H */

common/test/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,6 @@ common/test/run-shutdown_scriptpubkey: wire/towire.o wire/fromwire.o
130130

131131
common/test/run-wireaddr: wire/towire.o wire/fromwire.o
132132

133+
common/test/run-jsonrpc_io: common/json_parse_simple.o
134+
133135
check-units: $(COMMON_TEST_PROGRAMS:%=unittest/%)

0 commit comments

Comments
 (0)