const jsmntok_t *json_delve(const char *buffer, const jsmntok_t *tok, const char *guide) { while (*guide) { const char *key; size_t len = strcspn(guide+1, ".[]"); key = tal_strndup(tmpctx, guide+1, len); switch (guide[0]) { case '.': if (tok->type != JSMN_OBJECT) return NULL; tok = json_get_member(buffer, tok, key); if (!tok) return NULL; break; case '[': if (tok->type != JSMN_ARRAY) return NULL; tok = json_get_arr(tok, atol(key)); if (!tok) return NULL; /* Must be terminated */ assert(guide[1+strlen(key)] == ']'); len++; break; default: abort(); } guide += len + 1; } return tok; }
json_for_each_arr(i, t, route) { u64 val; json_to_u64(buf, json_get_member(buf, t, fieldname), &val); if (worst == NULL || val - prev > worstval) { worst = t; worstval = val - prev; } prev = val; }
static struct command_result *waitsendpay_error(struct command *cmd, const char *buf, const jsmntok_t *error, struct pay_command *pc) { const jsmntok_t *codetok, *scidtok, *dirtok; int code; attempt_failed_tok(pc, "waitsendpay", buf, error); codetok = json_get_member(buf, error, "code"); if (!json_to_int(buf, codetok, &code)) plugin_err("waitsendpay error gave no 'code'? '%.*s'", error->end - error->start, buf + error->start); /* FIXME: Handle PAY_UNPARSEABLE_ONION! */ /* Many error codes are final. */ if (code != PAY_TRY_OTHER_ROUTE) { return forward_error(cmd, buf, error, pc); } scidtok = json_delve(buf, error, ".data.erring_channel"); if (!scidtok) plugin_err("waitsendpay error no erring_channel '%.*s'", error->end - error->start, buf + error->start); dirtok = json_delve(buf, error, ".data.erring_direction"); if (!dirtok) plugin_err("waitsendpay error no erring_direction '%.*s'", error->end - error->start, buf + error->start); if (time_after(time_now(), pc->stoptime)) { return waitsendpay_expired(cmd, pc); } /* If failure is in routehint part, try next one */ if (channel_in_routehint(pc->current_routehint, buf, scidtok)) return next_routehint(cmd, pc); /* Otherwise, add erring channel to exclusion list. */ tal_arr_expand(&pc->excludes, tal_fmt(pc->excludes, "%.*s/%c", scidtok->end - scidtok->start, buf + scidtok->start, buf[dirtok->start])); /* Try again. */ return start_pay_attempt(cmd, pc, "Excluded channel %s", pc->excludes[tal_count(pc->excludes)-1]); }
static void attempt_failed_tok(struct pay_command *pc, const char *method, const char *buf, const jsmntok_t *errtok) { const jsmntok_t *msg = json_get_member(buf, errtok, "message"); if (msg) attempt_failed_fmt(pc, "%.*sCall to %s:%.*s", msg->start - errtok->start, buf + errtok->start, method, errtok->end - msg->start, buf + msg->start); else attempt_failed_fmt(pc, "{ 'message': 'Call to %s failed', %.*s", method, errtok->end - errtok->start - 1, buf + errtok->start + 1); }
void json_get_params(const char *buffer, const jsmntok_t param[], ...) { va_list ap; const char *name; const jsmntok_t **tokptr, *p, *end; if (param->type == JSMN_ARRAY) { if (param->size == 0) p = NULL; else p = param + 1; end = json_next(param); } else assert(param->type == JSMN_OBJECT); va_start(ap, param); while ((name = va_arg(ap, const char *)) != NULL) { tokptr = va_arg(ap, const jsmntok_t **); if (param->type == JSMN_ARRAY) { *tokptr = p; if (p) { p = json_next(p); if (p == end) p = NULL; } } else { *tokptr = json_get_member(buffer, param, name); } /* Convert 'null' to NULL */ if (*tokptr && (*tokptr)->type == JSMN_PRIMITIVE && buffer[(*tokptr)->start] == 'n') { *tokptr = NULL; } } va_end(ap); }
static void parse_request(struct json_connection *jcon, const jsmntok_t tok[]) { const jsmntok_t *method, *id, *params; const struct json_command *cmd; assert(!jcon->current); if (tok[0].type != JSMN_OBJECT) { json_command_malformed(jcon, "null", "Expected {} for json command"); return; } 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) { json_command_malformed(jcon, "null", "No id"); return; } if (id->type != JSMN_STRING && id->type != JSMN_PRIMITIVE) { json_command_malformed(jcon, "null", "Expected string/primitive for id"); return; } /* This is a convenient tal parent for durarion of command * (which may outlive the conn!). */ jcon->current = tal(jcon->dstate, struct command); jcon->current->jcon = jcon; jcon->current->dstate = jcon->dstate; jcon->current->id = tal_strndup(jcon->current, json_tok_contents(jcon->buffer, id), json_tok_len(id)); if (!method || !params) { command_fail(jcon->current, method ? "No params" : "No method"); return; } if (method->type != JSMN_STRING) { command_fail(jcon->current, "Expected string for method"); return; } cmd = find_cmd(jcon->buffer, method); if (!cmd) { command_fail(jcon->current, "Unknown command '%.*s'", (int)(method->end - method->start), jcon->buffer + method->start); return; } if (params->type != JSMN_ARRAY && params->type != JSMN_OBJECT) { command_fail(jcon->current, "Expected array or object for params"); return; } cmd->dispatch(jcon->current, jcon->buffer, params); }
JSON *json_get_array(JSON *j, const char *name) { JSON *v = json_get_member(j, name); if(v) return json_as_array(v); return NULL; }
JSON *json_get_object(JSON *j, const char *name) { JSON *v = json_get_member(j, name); if(v) return json_as_object(v); return NULL; }
const char *json_get_string(JSON *j, const char *name) { JSON *v = json_get_member(j, name); if(v) return json_as_string(v); return NULL; }
double json_get_number(JSON *j, const char *name) { JSON *v = json_get_member(j, name); if(v) return json_as_number(v); return 0.0; }
/* Simple test code to create a gateway transaction */ int main(int argc, char *argv[]) { int fd, i, off; const char *method; char *cmd, *resp, *idstr, *rpc_filename; char *result_end; struct sockaddr_un addr; jsmntok_t *toks; const jsmntok_t *result, *error, *id; char *pettycoin_dir; const tal_t *ctx = tal(NULL, char); size_t num_opens, num_closes; bool valid; err_set_progname(argv[0]); opt_set_alloc(opt_allocfn, tal_reallocfn, tal_freefn); pettycoin_dir_register_opts(ctx, &pettycoin_dir, &rpc_filename); opt_register_noarg("--help|-h", opt_usage_and_exit, "<command> [<params>...]", "Show this message"); opt_register_noarg("--version|-V", opt_version_and_exit, VERSION, "Display version and exit"); opt_early_parse(argc, argv, opt_log_stderr_exit); opt_parse(&argc, argv, opt_log_stderr_exit); method = argv[1]; if (!method) errx(ERROR_USAGE, "Need at least one argument\n%s", opt_usage(argv[0], NULL)); if (chdir(pettycoin_dir) != 0) err(ERROR_TALKING_TO_PETTYCOIN, "Moving into '%s'", pettycoin_dir); fd = socket(AF_UNIX, SOCK_STREAM, 0); if (strlen(rpc_filename) + 1 > sizeof(addr.sun_path)) errx(ERROR_USAGE, "rpc filename '%s' too long", rpc_filename); strcpy(addr.sun_path, rpc_filename); addr.sun_family = AF_UNIX; if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) err(ERROR_TALKING_TO_PETTYCOIN, "Connecting to '%s'", rpc_filename); idstr = tal_fmt(ctx, "pettycoin_query-%i", getpid()); cmd = tal_fmt(ctx, "{ \"method\" : \"%s\", \"id\" : \"%s\", \"params\" : [ ", method, idstr); for (i = 2; i < argc; i++) { /* Numbers are left unquoted, and quoted things left alone. */ if (strspn(argv[i], "0123456789") == strlen(argv[i]) || argv[i][0] == '"') tal_append_fmt(&cmd, "%s", argv[i]); else tal_append_fmt(&cmd, "\"%s\"", argv[i]); if (i != argc - 1) tal_append_fmt(&cmd, ", "); } tal_append_fmt(&cmd, "] }"); if (!write_all(fd, cmd, strlen(cmd))) err(ERROR_TALKING_TO_PETTYCOIN, "Writing command"); resp = tal_arr(cmd, char, 100); off = 0; num_opens = num_closes = 0; while ((i = read(fd, resp + off, tal_count(resp) - 1 - off)) > 0) { resp[off + i] = '\0'; num_opens += strcount(resp + off, "{"); num_closes += strcount(resp + off, "}"); off += i; if (off == tal_count(resp) - 1) tal_resize(&resp, tal_count(resp) * 2); /* parsing huge outputs is slow: do quick check first. */ if (num_opens == num_closes && strstr(resp, "\"result\"")) break; } if (i < 0) err(ERROR_TALKING_TO_PETTYCOIN, "reading response"); /* Parsing huge results is too slow, so hack fastpath common case */ result_end = tal_fmt(ctx, ", \"error\" : null, \"id\" : \"%s\" }\n", idstr); if (strstarts(resp, "{ \"result\" : ") && strends(resp, result_end)) { /* Result is OK, so dump it */ resp += strlen("{ \"result\" : "); printf("%.*s\n", (int)(strlen(resp) - strlen(result_end)), resp); tal_free(ctx); return 0; } toks = json_parse_input(resp, off, &valid); if (!toks || !valid) errx(ERROR_TALKING_TO_PETTYCOIN, "Malformed response '%s'", resp); result = json_get_member(resp, toks, "result"); if (!result) errx(ERROR_TALKING_TO_PETTYCOIN, "Missing 'result' in response '%s'", resp); error = json_get_member(resp, toks, "error"); if (!error) errx(ERROR_TALKING_TO_PETTYCOIN, "Missing 'error' in response '%s'", resp); id = json_get_member(resp, toks, "id"); if (!id) errx(ERROR_TALKING_TO_PETTYCOIN, "Missing 'id' in response '%s'", resp); if (!json_tok_streq(resp, id, idstr)) errx(ERROR_TALKING_TO_PETTYCOIN, "Incorrect 'id' in response: %.*s", json_tok_len(id), json_tok_contents(resp, id)); if (json_tok_is_null(resp, error)) { printf("%.*s\n", json_tok_len(result), json_tok_contents(resp, result)); tal_free(ctx); return 0; } printf("%.*s\n", json_tok_len(error), json_tok_contents(resp, error)); tal_free(ctx); return 1; }
int main(void) { jsmntok_t *toks_arr, *toks_obj, *arg1, *arg2, *arg3, *arg4, *arg5; const jsmntok_t *arr_params, *obj_params; void *ctx; bool valid; char *cmd_arr, *cmd_obj; struct protocol_double_sha sha; ctx = tal(NULL, char); cmd_arr = tal_strdup(ctx, "{ \"method\" : \"dev-echo\", " "\"params\" : [ null, [ 1, 2, 3 ], { \"one\" : 1 }, \"four\" ], " "\"id\" : \"1\" }"); cmd_obj = tal_strdup(ctx, "{ \"method\" : \"dev-echo\", " "\"params\" : { \"arg2\" : [ 1, 2, 3 ]," " \"arg3\" : { \"one\" : 1 }," " \"arg4\" : \"four\" }, " "\"id\" : \"1\" }"); /* Partial id we skip } */ toks_arr = json_parse_input(cmd_arr, strlen(cmd_arr) - 1, &valid); assert(!toks_arr); assert(valid); toks_obj = json_parse_input(cmd_obj, strlen(cmd_obj) - 1, &valid); assert(!toks_obj); assert(valid); /* This should work */ toks_arr = json_parse_input(cmd_arr, strlen(cmd_arr), &valid); assert(toks_arr); assert(tal_count(toks_arr) == 17); assert(valid); toks_obj = json_parse_input(cmd_obj, strlen(cmd_obj), &valid); assert(toks_obj); assert(tal_count(toks_obj) == 19); assert(valid); assert(toks_arr[0].type == JSMN_OBJECT); assert(json_tok_len(toks_arr) == strlen(cmd_arr)); assert(strncmp(json_tok_contents(cmd_arr, toks_arr), cmd_arr, json_tok_len(toks_arr)) == 0); assert(toks_obj[0].type == JSMN_OBJECT); assert(json_tok_len(toks_obj) == strlen(cmd_obj)); assert(strncmp(json_tok_contents(cmd_obj, toks_obj), cmd_obj, json_tok_len(toks_obj)) == 0); /* It's not a string, so this will fail. */ assert(!json_tok_streq(cmd_arr, toks_arr, cmd_obj)); assert(json_tok_streq(cmd_arr, toks_arr+1, "method")); assert(json_tok_streq(cmd_obj, toks_obj+1, "method")); assert(json_tok_is_null(cmd_arr, toks_arr + 5)); assert(!json_tok_is_null(cmd_arr, toks_arr + 6)); assert(!json_tok_is_null(cmd_arr, toks_arr + 7)); assert(json_get_member(cmd_arr, toks_arr, "method") == toks_arr+2); assert(json_get_member(cmd_obj, toks_obj, "method") == toks_obj+2); assert(!json_get_member(cmd_arr, toks_arr, "dev-echo")); assert(!json_get_member(cmd_obj, toks_obj, "arg2")); arr_params = json_get_member(cmd_arr, toks_arr, "params"); assert(arr_params == toks_arr+4); assert(arr_params->type == JSMN_ARRAY); obj_params = json_get_member(cmd_obj, toks_obj, "params"); assert(obj_params == toks_obj+4); assert(obj_params->type == JSMN_OBJECT); assert(json_get_member(cmd_arr, toks_arr, "id") == toks_arr+15); assert(json_get_member(cmd_obj, toks_obj, "id") == toks_obj+17); /* get_member works in sub objects */ assert(json_get_member(cmd_obj, obj_params, "arg4") == toks_obj + 15); json_get_params(cmd_arr, arr_params, "arg1", &arg1, "arg2", &arg2, "arg3", &arg3, "arg4", &arg4, "arg5", &arg5, NULL); assert(arg1 == NULL); assert(arg2 == toks_arr + 6); assert(arg2->type == JSMN_ARRAY); assert(arg3 == toks_arr + 10); assert(arg3->type == JSMN_OBJECT); assert(arg4 == toks_arr + 13); assert(arg4->type == JSMN_STRING); assert(arg5 == NULL); json_get_params(cmd_obj, obj_params, "arg1", &arg1, "arg2", &arg2, "arg3", &arg3, "arg4", &arg4, "arg5", &arg5, NULL); assert(arg1 == NULL); assert(arg2 == toks_obj + 6); assert(arg2->type == JSMN_ARRAY); assert(arg3 == toks_obj + 11); assert(arg3->type == JSMN_OBJECT); assert(arg4 == toks_obj + 15); assert(arg4->type == JSMN_STRING); assert(arg5 == NULL); /* Test json_delve() */ assert(json_delve(cmd_arr, toks_arr, ".method") == toks_arr + 2); assert(json_delve(cmd_arr, toks_arr, ".params[0]") == toks_arr + 5); assert(json_delve(cmd_arr, toks_arr, ".params[1]") == toks_arr + 6); assert(json_delve(cmd_arr, toks_arr, ".params[2]") == toks_arr + 10); assert(json_delve(cmd_arr, toks_arr, ".params[1][2]") == toks_arr + 9); assert(json_delve(cmd_arr, toks_arr, ".params[4]") == NULL); assert(json_delve(cmd_arr, toks_arr, ".params[1][4]") == NULL); assert(json_delve(cmd_arr, toks_arr, ".params[3][4]") == NULL); assert(json_delve(cmd_arr, toks_arr, ".unknown") == NULL); assert(json_delve(cmd_arr, toks_arr, ".unknown[1]") == NULL); assert(json_delve(cmd_arr, toks_arr, ".param") == NULL); assert(json_delve(cmd_arr, toks_arr, ".params\"") == NULL); assert(json_delve(cmd_arr, toks_arr, ".dev-echo") == NULL); assert(json_delve(cmd_arr, toks_arr, ".id[0]") == NULL); assert(json_delve(cmd_obj, toks_obj, ".params.arg3.one") == toks_obj + 13); /* More exotic object creation */ cmd_arr = tal_arr(ctx, char, 0); json_add_object(&cmd_arr, "arg2", JSMN_ARRAY, "[ 1, 2, 3 ]", "arg3", JSMN_OBJECT, "{ \"one\" : 1 }", "arg4", JSMN_STRING, "four", NULL); assert(streq(cmd_arr, "{ \"arg2\" : [ 1, 2, 3 ]," " \"arg3\" : { \"one\" : 1 }," " \"arg4\" : \"four\" }")); cmd_arr = tal_arr(ctx, char, 0); json_object_start(&cmd_arr, NULL); json_add_pubkey(&cmd_arr, "key", helper_public_key(0)); memset(&sha, 42, sizeof(sha)); json_add_double_sha(&cmd_arr, "sha", &sha); json_add_address(&cmd_arr, "test-address", true, helper_addr(0)); json_add_address(&cmd_arr, "address", false, helper_addr(0)); json_object_end(&cmd_arr); assert(streq(cmd_arr, "{ \"key\" : \"0214f24666a59e62c8b92a0b4b58f2a1cdeb573ea377e42f411be028292ff81926\"," " \"sha\" : \"2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\"," " \"test-address\" : \"qKCafy33t92L9Nmoxx8H6NHDuiyGViqWBZ\"," " \"address\" : \"PZZyf1xcSbNFodrGQ6ot4LrsdSUu1bgmkc\" }")); tal_free(ctx); return 0; }