From 230d43fdbc41b356700b0d8a6984d69e00279ade Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 30 Jul 2019 14:53:05 -0700 Subject: [PATCH 1/3] Abstract out some of the descriptor Span-parsing helpers --- src/Makefile.am | 2 ++ src/script/descriptor.cpp | 66 ++++++-------------------------------- src/util/spanparsing.cpp | 67 +++++++++++++++++++++++++++++++++++++++ src/util/spanparsing.h | 29 +++++++++++++++++ 4 files changed, 107 insertions(+), 57 deletions(-) create mode 100644 src/util/spanparsing.cpp create mode 100644 src/util/spanparsing.h diff --git a/src/Makefile.am b/src/Makefile.am index 8fc7f61d4b7..ca349eac2a8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -208,6 +208,7 @@ BITCOIN_CORE_H = \ util/bytevectorhash.h \ util/error.h \ util/fees.h \ + util/spanparsing.h \ util/system.h \ util/memory.h \ util/moneystr.h \ @@ -503,6 +504,7 @@ libbitcoin_util_a_SOURCES = \ util/moneystr.cpp \ util/rbf.cpp \ util/threadnames.cpp \ + util/spanparsing.cpp \ util/strencodings.cpp \ util/string.cpp \ util/time.cpp \ diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index b782ebbd1f3..2d056b5616a 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -632,63 +633,6 @@ enum class ParseScriptContext { P2WSH, }; -/** Parse a constant. If successful, sp is updated to skip the constant and return true. */ -bool Const(const std::string& str, Span& sp) -{ - if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) { - sp = sp.subspan(str.size()); - return true; - } - return false; -} - -/** Parse a function call. If successful, sp is updated to be the function's argument(s). */ -bool Func(const std::string& str, Span& sp) -{ - if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) { - sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2); - return true; - } - return false; -} - -/** Return the expression that sp begins with, and update sp to skip it. */ -Span Expr(Span& sp) -{ - int level = 0; - auto it = sp.begin(); - while (it != sp.end()) { - if (*it == '(') { - ++level; - } else if (level && *it == ')') { - --level; - } else if (level == 0 && (*it == ')' || *it == ',')) { - break; - } - ++it; - } - Span ret = sp.first(it - sp.begin()); - sp = sp.subspan(it - sp.begin()); - return ret; -} - -/** Split a string on every instance of sep, returning a vector. */ -std::vector> Split(const Span& sp, char sep) -{ - std::vector> ret; - auto it = sp.begin(); - auto start = it; - while (it != sp.end()) { - if (*it == sep) { - ret.emplace_back(start, it); - start = it + 1; - } - ++it; - } - ret.emplace_back(start, it); - return ret; -} - /** Parse a key path, being passed a split list of elements (the first element is ignored). */ NODISCARD bool ParseKeyPath(const std::vector>& split, KeyPath& out, std::string& error) { @@ -715,6 +659,8 @@ NODISCARD bool ParseKeyPath(const std::vector>& split, KeyPath& /** Parse a public key that excludes origin information. */ std::unique_ptr ParsePubkeyInner(const Span& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error) { + using namespace spanparsing; + auto split = Split(sp, '/'); std::string str(split[0].begin(), split[0].end()); if (str.size() == 0) { @@ -774,6 +720,8 @@ std::unique_ptr ParsePubkeyInner(const Span& sp, boo /** Parse a public key including origin information (if enabled). */ std::unique_ptr ParsePubkey(const Span& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error) { + using namespace spanparsing; + auto origin_split = Split(sp, ']'); if (origin_split.size() > 2) { error = "Multiple ']' characters found for a single pubkey"; @@ -808,6 +756,8 @@ std::unique_ptr ParsePubkey(const Span& sp, bool per /** Parse a script in a particular context. */ std::unique_ptr ParseScript(Span& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error) { + using namespace spanparsing; + auto expr = Expr(sp); if (Func("pk", expr)) { auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2WSH, out, error); @@ -1003,6 +953,8 @@ std::unique_ptr InferScript(const CScript& script, ParseScriptCo /** Check a descriptor checksum, and update desc to be the checksum-less part. */ bool CheckChecksum(Span& sp, bool require_checksum, std::string& error, std::string* out_checksum = nullptr) { + using namespace spanparsing; + auto check_split = Split(sp, '#'); if (check_split.size() > 2) { error = "Multiple '#' symbols"; diff --git a/src/util/spanparsing.cpp b/src/util/spanparsing.cpp new file mode 100644 index 00000000000..0c8575399aa --- /dev/null +++ b/src/util/spanparsing.cpp @@ -0,0 +1,67 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include + +#include +#include + +namespace spanparsing { + +bool Const(const std::string& str, Span& sp) +{ + if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) { + sp = sp.subspan(str.size()); + return true; + } + return false; +} + +bool Func(const std::string& str, Span& sp) +{ + if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) { + sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2); + return true; + } + return false; +} + +Span Expr(Span& sp) +{ + int level = 0; + auto it = sp.begin(); + while (it != sp.end()) { + if (*it == '(') { + ++level; + } else if (level && *it == ')') { + --level; + } else if (level == 0 && (*it == ')' || *it == ',')) { + break; + } + ++it; + } + Span ret = sp.first(it - sp.begin()); + sp = sp.subspan(it - sp.begin()); + return ret; +} + +std::vector> Split(const Span& sp, char sep) +{ + std::vector> ret; + auto it = sp.begin(); + auto start = it; + while (it != sp.end()) { + if (*it == sep) { + ret.emplace_back(start, it); + start = it + 1; + } + ++it; + } + ret.emplace_back(start, it); + return ret; +} + +} // namespace spanparsing diff --git a/src/util/spanparsing.h b/src/util/spanparsing.h new file mode 100644 index 00000000000..a2eb24b1fb7 --- /dev/null +++ b/src/util/spanparsing.h @@ -0,0 +1,29 @@ +// Copyright (c) 2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_SPANPARSING_H +#define BITCOIN_UTIL_SPANPARSING_H + +#include + +#include +#include + +namespace spanparsing { + +/** Parse a constant. If successful, sp is updated to skip the constant and return true. */ +bool Const(const std::string& str, Span& sp); + +/** Parse a function call. If successful, sp is updated to be the function's argument(s). */ +bool Func(const std::string& str, Span& sp); + +/** Return the expression that sp begins with, and update sp to skip it. */ +Span Expr(Span& sp); + +/** Split a string on every instance of sep, returning a vector. */ +std::vector> Split(const Span& sp, char sep); + +} // namespace spanparsing + +#endif // BITCOIN_UTIL_SPANPARSING_H From 5e69aeec3f2a0fafd5e591b7222716f00145761d Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 18 Sep 2019 12:25:55 -0700 Subject: [PATCH 2/3] Add documenting comments to spanparsing.h --- src/util/spanparsing.h | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/util/spanparsing.h b/src/util/spanparsing.h index a2eb24b1fb7..63f54758bd2 100644 --- a/src/util/spanparsing.h +++ b/src/util/spanparsing.h @@ -12,16 +12,37 @@ namespace spanparsing { -/** Parse a constant. If successful, sp is updated to skip the constant and return true. */ +/** Parse a constant. + * + * If sp's initial part matches str, sp is updated to skip that part, and true is returned. + * Otherwise sp is unmodified and false is returned. + */ bool Const(const std::string& str, Span& sp); -/** Parse a function call. If successful, sp is updated to be the function's argument(s). */ +/** Parse a function call. + * + * If sp's initial part matches str + "(", and sp ends with ")", sp is updated to be the + * section between the braces, and true is returned. Otherwise sp is unmodified and false + * is returned. + */ bool Func(const std::string& str, Span& sp); -/** Return the expression that sp begins with, and update sp to skip it. */ +/** Extract the expression that sp begins with. + * + * This function will return the initial part of sp, up to (but not including) the first + * comma or closing brace, skipping ones that are surrounded by braces. So for example, + * for "foo(bar(1),2),3" the initial part "foo(bar(1),2)" will be returned. sp will be + * updated to skip the initial part that is returned. + */ Span Expr(Span& sp); -/** Split a string on every instance of sep, returning a vector. */ +/** Split a string on every instance of sep, returning a vector. + * + * If sep does not occur in sp, a singleton with the entirety of sp is returned. + * + * Note that this function does not care about braces, so splitting + * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}. + */ std::vector> Split(const Span& sp, char sep); } // namespace spanparsing From bb36372b8f2bd675313ae8553ceb61f28c2c1afd Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Wed, 25 Sep 2019 09:10:28 +0200 Subject: [PATCH 3/3] test: add unit tests for Span-parsing helpers tests the following four functions: - Const() [parse constant] - Func() [parse function] - Expr() [parse expression] - Split() [split up a string] --- src/test/util_tests.cpp | 124 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index d0cd4b0a03d..31a66b6fa9d 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -1572,4 +1573,127 @@ BOOST_AUTO_TEST_CASE(test_Capitalize) BOOST_CHECK_EQUAL(Capitalize("\x00\xfe\xff"), "\x00\xfe\xff"); } +static std::string SpanToStr(Span& span) +{ + return std::string(span.begin(), span.end()); +} + +BOOST_AUTO_TEST_CASE(test_spanparsing) +{ + using namespace spanparsing; + std::string input; + Span sp; + bool success; + + // Const(...): parse a constant, update span to skip it if successful + input = "MilkToastHoney"; + sp = MakeSpan(input); + success = Const("", sp); // empty + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "MilkToastHoney"); + + success = Const("Milk", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "ToastHoney"); + + success = Const("Bread", sp); + BOOST_CHECK(!success); + + success = Const("Toast", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "Honey"); + + success = Const("Honeybadger", sp); + BOOST_CHECK(!success); + + success = Const("Honey", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), ""); + + // Func(...): parse a function call, update span to argument if successful + input = "Foo(Bar(xy,z()))"; + sp = MakeSpan(input); + + success = Func("FooBar", sp); + BOOST_CHECK(!success); + + success = Func("Foo(", sp); + BOOST_CHECK(!success); + + success = Func("Foo", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "Bar(xy,z())"); + + success = Func("Bar", sp); + BOOST_CHECK(success); + BOOST_CHECK_EQUAL(SpanToStr(sp), "xy,z()"); + + success = Func("xy", sp); + BOOST_CHECK(!success); + + // Expr(...): return expression that span begins with, update span to skip it + Span result; + + input = "(n*(n-1))/2"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "(n*(n-1))/2"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ""); + + input = "foo,bar"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "foo"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ",bar"); + + input = "(aaaaa,bbbbb()),c"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "(aaaaa,bbbbb())"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ",c"); + + input = "xyz)foo"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "xyz"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ")foo"); + + input = "((a),(b),(c)),xxx"; + sp = MakeSpan(input); + result = Expr(sp); + BOOST_CHECK_EQUAL(SpanToStr(result), "((a),(b),(c))"); + BOOST_CHECK_EQUAL(SpanToStr(sp), ",xxx"); + + // Split(...): split a string on every instance of sep, return vector + std::vector> results; + + input = "xxx"; + results = Split(MakeSpan(input), 'x'); + BOOST_CHECK_EQUAL(results.size(), 4); + BOOST_CHECK_EQUAL(SpanToStr(results[0]), ""); + BOOST_CHECK_EQUAL(SpanToStr(results[1]), ""); + BOOST_CHECK_EQUAL(SpanToStr(results[2]), ""); + BOOST_CHECK_EQUAL(SpanToStr(results[3]), ""); + + input = "one#two#three"; + results = Split(MakeSpan(input), '-'); + BOOST_CHECK_EQUAL(results.size(), 1); + BOOST_CHECK_EQUAL(SpanToStr(results[0]), "one#two#three"); + + input = "one#two#three"; + results = Split(MakeSpan(input), '#'); + BOOST_CHECK_EQUAL(results.size(), 3); + BOOST_CHECK_EQUAL(SpanToStr(results[0]), "one"); + BOOST_CHECK_EQUAL(SpanToStr(results[1]), "two"); + BOOST_CHECK_EQUAL(SpanToStr(results[2]), "three"); + + input = "*foo*bar*"; + results = Split(MakeSpan(input), '*'); + BOOST_CHECK_EQUAL(results.size(), 4); + BOOST_CHECK_EQUAL(SpanToStr(results[0]), ""); + BOOST_CHECK_EQUAL(SpanToStr(results[1]), "foo"); + BOOST_CHECK_EQUAL(SpanToStr(results[2]), "bar"); + BOOST_CHECK_EQUAL(SpanToStr(results[3]), ""); +} + BOOST_AUTO_TEST_SUITE_END()