diff --git a/common/json.c b/common/json.c index 1c2467ea6..cb33f1586 100644 --- a/common/json.c +++ b/common/json.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -756,6 +755,190 @@ const jsmntok_t *json_delve(const char *buffer, return tok; } +/* talfmt take a ctx pointer and return NULL or a valid pointer. + * fmt takes the argument, and returns a bool. */ +static bool handle_percent(const char *buffer, + const jsmntok_t *tok, + va_list *ap) +{ + void *ctx; + + /* This is set to (dummy) json_scan if it's a non-tal fmt */ + ctx = va_arg(*ap, void *); + if (ctx != json_scan) { + void *(*talfmt)(void *, const char *, const jsmntok_t *); + void **p; + p = va_arg(*ap, void **); + talfmt = va_arg(*ap, void *(*)(void *, const char *, const jsmntok_t *)); + *p = talfmt(ctx, buffer, tok); + return *p != NULL; + } else { + bool (*fmt)(const char *, const jsmntok_t *, void *); + void *p; + + p = va_arg(*ap, void *); + fmt = va_arg(*ap, bool (*)(const char *, const jsmntok_t *, void *)); + return fmt(buffer, tok, p); + } +} + +/* GUIDE := OBJ | '%' + * OBJ := '{' FIELDLIST '}' + * FIELDLIST := FIELD [',' FIELD]* + * FIELD := LITERAL ':' FIELDVAL + * FIELDVAL := OBJ | LITERAL | '%' + */ + +/* Returns NULL on failure, or offset into guide */ +static const char *parse_literal(const char *guide, + const char **literal, + size_t *len) +{ + *literal = guide; + *len = strspn(guide, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-"); + return guide + *len; +} + +/* Recursion */ +static const char *parse_obj(const char *buffer, + const jsmntok_t *tok, + const char *guide, + va_list *ap); + +static const char *parse_guide(const char *buffer, + const jsmntok_t *tok, + const char *guide, + va_list *ap) +{ + if (*guide == '{') { + guide = parse_obj(buffer, tok, guide, ap); + if (!guide) + return NULL; + assert(*guide == '}'); + return guide + 1; + } else { + assert(*guide == '%'); + if (!handle_percent(buffer, tok, ap)) + return NULL; + return guide + 1; + } +} + +static const char *parse_fieldval(const char *buffer, + const jsmntok_t *tok, + const char *guide, + va_list *ap) +{ + if (*guide == '{') { + guide = parse_obj(buffer, tok, guide, ap); + if (!guide) + return NULL; + assert(*guide == '}'); + return guide + 1; + } else if (*guide == '%') { + if (!handle_percent(buffer, tok, ap)) + return NULL; + return guide + 1; + } else { + const char *literal; + size_t len; + + /* Literal must match exactly */ + guide = parse_literal(guide, &literal, &len); + if (!memeq(buffer + tok->start, tok->end - tok->start, + literal, len)) + return NULL; + return guide; + } +} + +static const char *parse_field(const char *buffer, + const jsmntok_t *tok, + const char *guide, + va_list *ap) +{ + const jsmntok_t *member; + size_t len; + const char *memname; + + guide = parse_literal(guide, &memname, &len); + assert(*guide == ':'); + + member = json_get_membern(buffer, tok, memname, guide - memname); + if (!member) + return NULL; + + return parse_fieldval(buffer, member, guide + 1, ap); +} + +static const char *parse_fieldlist(const char *buffer, + const jsmntok_t *tok, + const char *guide, + va_list *ap) +{ + for (;;) { + guide = parse_field(buffer, tok, guide, ap); + if (!guide) + return NULL; + if (*guide != ',') + break; + guide++; + } + return guide; +} + +static const char *parse_obj(const char *buffer, + const jsmntok_t *tok, + const char *guide, + va_list *ap) +{ + assert(*guide == '{'); + + if (tok->type != JSMN_OBJECT) + return NULL; + + guide = parse_fieldlist(buffer, tok, guide + 1, ap); + if (!guide) + return NULL; + return guide; +} + +bool json_scanv(const char *buffer, + const jsmntok_t *tok, + const char *guide, + va_list ap) +{ + va_list cpy; + + /* We need this, since &ap doesn't work on some platforms... */ + va_copy(cpy, ap); + guide = parse_guide(buffer, tok, guide, &cpy); + va_end(cpy); + + if (!guide) + return false; + assert(guide[0] == '\0'); + return true; +} + +bool json_scan(const char *buffer, + const jsmntok_t *tok, + const char *guide, + ...) +{ + va_list ap; + bool ret; + + va_start(ap, guide); + ret = json_scanv(buffer, tok, guide, ap); + va_end(ap); + return ret; +} + void json_add_num(struct json_stream *result, const char *fieldname, unsigned int value) { json_add_member(result, fieldname, false, "%u", value); diff --git a/common/json.h b/common/json.h index 6b2d9426b..b152e8a9e 100644 --- a/common/json.h +++ b/common/json.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,33 @@ const jsmntok_t *json_delve(const char *buffer, const jsmntok_t *tok, const char *guide); +/* Guide is % for a token: each must be followed by JSON_SCAN(). */ +bool json_scan(const char *buffer, + const jsmntok_t *tok, + const char *guide, + ...); + +/* eg. JSON_SCAN(json_to_bool, &boolvar) */ +#define JSON_SCAN(fmt, var) \ + json_scan, \ + ((var) + 0*sizeof(fmt((const char *)NULL, \ + (const jsmntok_t *)NULL, var) == true)), \ + (fmt) + +/* eg. JSON_SCAN_TAL(tmpctx, json_strdup, &charvar) */ +#define JSON_SCAN_TAL(ctx, fmt, var) \ + (ctx), \ + ((var) + 0*sizeof((*var) = fmt((ctx), \ + (const char *)NULL, \ + (const jsmntok_t *)NULL))), \ + (fmt) + +/* Already-have-varargs version */ +bool json_scanv(const char *buffer, + const jsmntok_t *tok, + const char *guide, + va_list ap); + /* Iterator macro for array: i is counter, t is token ptr, arr is JSMN_ARRAY */ #define json_for_each_arr(i, t, arr) \ for (i = 0, t = (arr) + 1; i < (arr)->size; t = json_next(t), i++) diff --git a/common/test/run-json_scan.c b/common/test/run-json_scan.c new file mode 100644 index 000000000..23de936d4 --- /dev/null +++ b/common/test/run-json_scan.c @@ -0,0 +1,181 @@ +#include "../json.c" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for amount_asset_is_main */ +bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } +/* Generated stub for amount_asset_to_sat */ +struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } +/* Generated stub for amount_sat */ +struct amount_sat amount_sat(u64 satoshis UNNEEDED) +{ fprintf(stderr, "amount_sat called!\n"); abort(); } +/* Generated stub for amount_sat_add */ + bool amount_sat_add(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } +/* Generated stub for amount_sat_eq */ +bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } +/* Generated stub for amount_sat_greater_eq */ +bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } +/* Generated stub for amount_sat_sub */ + bool amount_sat_sub(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } +/* Generated stub for amount_sat_to_asset */ +struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) +{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } +/* Generated stub for amount_tx_fee */ +struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for fromwire */ +const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) +{ fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_amount_sat */ +struct amount_sat fromwire_amount_sat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_amount_sat called!\n"); abort(); } +/* Generated stub for fromwire_bool */ +bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } +/* Generated stub for fromwire_fail */ +void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_secp256k1_ecdsa_signature */ +void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for fromwire_sha256 */ +void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } +/* Generated stub for fromwire_tal_arrn */ +u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } +/* Generated stub for fromwire_u16 */ +u16 fromwire_u16(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u16 called!\n"); abort(); } +/* Generated stub for fromwire_u32 */ +u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } +/* Generated stub for fromwire_u64 */ +u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } +/* Generated stub for fromwire_u8 */ +u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } +/* Generated stub for fromwire_u8_array */ +void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for json_add_member */ +void json_add_member(struct json_stream *js UNNEEDED, + const char *fieldname UNNEEDED, + bool quote UNNEEDED, + const char *fmt UNNEEDED, ...) +{ fprintf(stderr, "json_add_member called!\n"); abort(); } +/* Generated stub for json_member_direct */ +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 towire */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_amount_sat */ +void towire_amount_sat(u8 **pptr UNNEEDED, const struct amount_sat sat UNNEEDED) +{ fprintf(stderr, "towire_amount_sat called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_u16 */ +void towire_u16(u8 **pptr UNNEEDED, u16 v UNNEEDED) +{ fprintf(stderr, "towire_u16 called!\n"); abort(); } +/* Generated stub for towire_u32 */ +void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } +/* Generated stub for towire_u8 */ +void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) +{ fprintf(stderr, "towire_u8 called!\n"); abort(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +static bool json_to_tok(const char *buffer, const jsmntok_t *tok, + const jsmntok_t **t) +{ + *t = tok; + return true; +} + +int main(int argc, char *argv[]) +{ + const jsmntok_t *toks, *t; + char *buf; + char *s; + u32 u32val; + u8 *hex; + + common_setup(argv[0]); + + buf = tal_strdup(tmpctx, "{\"1\":\"one\", \"2\":\"two\", \"3\":{\"three\": {\"deeper\": 17}}}"); + toks = json_parse_simple(tmpctx, buf, strlen(buf)); + assert(toks); + assert(toks->size == 3); + + /* These are direct matches, and they work. */ + assert(json_scan(buf, toks, "{1:one}")); + assert(json_scan(buf, toks, "{1:one,2:two}")); + assert(json_scan(buf, toks, "{2:two,1:one}")); + assert(json_scan(buf, toks, "{2:two,1:one,3:{three:{deeper:17}}}")); + + /* These do not match */ + assert(!json_scan(buf, toks, "{2:one}")); + assert(!json_scan(buf, toks, "{1:one,2:tw}")); + assert(!json_scan(buf, toks, "{1:one,2:twoo}")); + assert(!json_scan(buf, toks, "{4:one}")); + assert(!json_scan(buf, toks, "{2:two,1:one,3:three}")); + assert(!json_scan(buf, toks, "{3:{three:deeper}}")); + assert(!json_scan(buf, toks, "{3:{three:{deeper:{}}}}")); + + /* These capture simple values. */ + assert(json_scan(buf, toks, "{3:{three:{deeper:%}}}", + JSON_SCAN(json_to_number, &u32val))); + assert(u32val == 17); + assert(!json_scan(buf, toks, "{1:%}", + JSON_SCAN(json_to_number, &u32val))); + assert(json_scan(buf, toks, "{1:%}", + JSON_SCAN_TAL(tmpctx, json_strdup, &s))); + assert(tal_parent(s) == tmpctx); + assert(streq(s, "one")); + + assert(!json_scan(buf, toks, "{1:%}", + JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &hex))); + + assert(json_scan(buf, toks, "{3:%}", JSON_SCAN(json_to_tok, &t))); + assert(t == &toks[6]); + + common_shutdown(); +}