diff --git a/common/json.c b/common/json.c index 4be49f04be14..5fb509b3dfdb 100644 --- a/common/json.c +++ b/common/json.c @@ -13,8 +13,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -97,13 +99,45 @@ bool json_to_s64(const char *buffer, const jsmntok_t *tok, s64 *num) return true; } -bool json_to_double(const char *buffer, const jsmntok_t *tok, double *num) -{ - char *end; +bool json_to_millionths(const char *buffer, const jsmntok_t *tok, + u64 *millionths) +{ + int decimal_places = -1; + bool has_digits = 0; + + *millionths = 0; + for (int i = tok->start; i < tok->end; i++) { + if (isdigit(buffer[i])) { + has_digits = true; + /* Ignore too much precision */ + if (decimal_places >= 0 && ++decimal_places > 6) + continue; + if (mul_overflows_u64(*millionths, 10)) + return false; + *millionths *= 10; + if (add_overflows_u64(*millionths, buffer[i] - '0')) + return false; + *millionths += buffer[i] - '0'; + } else if (buffer[i] == '.') { + if (decimal_places != -1) + return false; + decimal_places = 0; + } else + return false; + } - *num = strtod(buffer + tok->start, &end); - if (end != buffer + tok->end) + if (!has_digits) return false; + + if (decimal_places == -1) + decimal_places = 0; + + while (decimal_places < 6) { + if (mul_overflows_u64(*millionths, 10)) + return false; + *millionths *= 10; + decimal_places++; + } return true; } diff --git a/common/json.h b/common/json.h index 551d1b19816c..f92a634d9bb0 100644 --- a/common/json.h +++ b/common/json.h @@ -69,8 +69,13 @@ bool json_to_u32(const char *buffer, const jsmntok_t *tok, bool json_to_u16(const char *buffer, const jsmntok_t *tok, uint16_t *num); -/* Extract double from this (must be a number literal) */ -bool json_to_double(const char *buffer, const jsmntok_t *tok, double *num); +/* + * Extract a non-negative (either 0 or positive) floating-point number from this + * (must be a number literal), multiply it by 1 million and return it as an + * integer. Any fraction smaller than 0.000001 is ignored. + */ +bool json_to_millionths(const char *buffer, const jsmntok_t *tok, + u64 *millionths); /* Extract signed integer from this (may be a string, or a number literal) */ bool json_to_int(const char *buffer, const jsmntok_t *tok, int *num); diff --git a/common/json_tok.c b/common/json_tok.c index 730842d84048..a08de9e0e157 100644 --- a/common/json_tok.c +++ b/common/json_tok.c @@ -39,12 +39,9 @@ struct command_result *param_millionths(struct command *cmd, const char *name, const char *buffer, const jsmntok_t *tok, uint64_t **num) { - double d; - if (json_to_double(buffer, tok, &d) && d >= 0.0) { - *num = tal(cmd, uint64_t); - **num = (uint64_t)(d * 1000000); + *num = tal(cmd, uint64_t); + if (json_to_millionths(buffer, tok, *num)) return NULL; - } return command_fail( cmd, JSONRPC2_INVALID_PARAMS, diff --git a/common/test/run-json.c b/common/test/run-json.c index ed24dfd96359..04e583e7df5a 100644 --- a/common/test/run-json.c +++ b/common/test/run-json.c @@ -46,6 +46,62 @@ static int test_json_tok_bitcoin_amount(void) return 0; } +static void do_json_tok_millionths(const char *val, bool ok, uint64_t expected) +{ + uint64_t amount; + jsmntok_t tok; + + tok.start = 0; + tok.end = strlen(val); + + assert(json_to_millionths(val, &tok, &amount) == ok); + if (ok) + assert(amount == expected); +} + +static int test_json_tok_millionths(void) +{ + do_json_tok_millionths("", false, 0); + do_json_tok_millionths("0..0", false, 0); + do_json_tok_millionths("0.0.", false, 0); + do_json_tok_millionths(".", false, 0); + do_json_tok_millionths("..", false, 0); + + do_json_tok_millionths("0", true, 0); + do_json_tok_millionths(".0", true, 0); + do_json_tok_millionths("0.", true, 0); + do_json_tok_millionths("100", true, 100 * 1000000); + do_json_tok_millionths("100.0", true, 100 * 1000000); + do_json_tok_millionths("100.", true, 100 * 1000000); + do_json_tok_millionths("100.000001", true, 100 * 1000000 + 1); + do_json_tok_millionths("100.0000001", true, 100 * 1000000); + do_json_tok_millionths(".000009", true, 9); + do_json_tok_millionths(".0000099", true, 9); + do_json_tok_millionths("18446744073709.551615", true, + 18446744073709551615ULL); + do_json_tok_millionths("18446744073709.551616", false, 0); + do_json_tok_millionths("18446744073709.551625", false, 0); + do_json_tok_millionths("18446744073709.551715", false, 0); + do_json_tok_millionths("18446744073709.552615", false, 0); + do_json_tok_millionths("18446744073709.561615", false, 0); + do_json_tok_millionths("18446744073709.651615", false, 0); + do_json_tok_millionths("18446744073710.551615", false, 0); + do_json_tok_millionths("18446744073809.551615", false, 0); + do_json_tok_millionths("18446744074709.551615", false, 0); + do_json_tok_millionths("18446744083709.551615", false, 0); + do_json_tok_millionths("18446744173709.551615", false, 0); + do_json_tok_millionths("18446745073709.551615", false, 0); + do_json_tok_millionths("18446754073709.551615", false, 0); + do_json_tok_millionths("18446844073709.551615", false, 0); + do_json_tok_millionths("18447744073709.551615", false, 0); + do_json_tok_millionths("18456744073709.551615", false, 0); + do_json_tok_millionths("18546744073709.551615", false, 0); + do_json_tok_millionths("19446744073709.551615", false, 0); + do_json_tok_millionths("28446744073709.551615", false, 0); + + return 0; +} + static void test_json_tok_size(void) { const jsmntok_t *toks; @@ -190,6 +246,7 @@ int main(void) test_json_tok_size(); test_json_tok_bitcoin_amount(); + test_json_tok_millionths(); test_json_delve(); assert(!taken_any()); take_cleanup();