From 46004158a2fc48c9451ddf9ea663c617e983f70e Mon Sep 17 00:00:00 2001 From: Edward Hennis Date: Wed, 5 Apr 2017 17:12:12 -0400 Subject: [PATCH] Allow Json parser understand TER strings where appropriate --- Builds/VisualStudio2015/RippleD.vcxproj | 4 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/ripple/protocol/TER.h | 5 + src/ripple/protocol/impl/STParsedJSON.cpp | 41 +++++- src/ripple/protocol/impl/TER.cpp | 48 ++++++- src/test/protocol/STObject_test.cpp | 135 ++++++++++++++++++ src/test/protocol/TER_test.cpp | 62 ++++++++ src/test/unity/protocol_test_unity.cpp | 1 + 8 files changed, 293 insertions(+), 6 deletions(-) create mode 100644 src/test/protocol/TER_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 7fd435d34..0e806b05c 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4849,6 +4849,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index c33bafc99..fc091b25c 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5556,6 +5556,9 @@ test\protocol + + test\protocol + test\protocol diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index 811de43e9..45cd2903f 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_PROTOCOL_TER_H_INCLUDED #define RIPPLE_PROTOCOL_TER_H_INCLUDED +#include #include namespace ripple { @@ -254,6 +255,10 @@ extern std::string transHuman (TER code); +extern +boost::optional +transCode(std::string const& token); + } // ripple #endif diff --git a/src/ripple/protocol/impl/STParsedJSON.cpp b/src/ripple/protocol/impl/STParsedJSON.cpp index 5aa7374dd..603324884 100644 --- a/src/ripple/protocol/impl/STParsedJSON.cpp +++ b/src/ripple/protocol/impl/STParsedJSON.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -189,31 +190,61 @@ static boost::optional parseLeaf ( case STI_UINT8: try { + constexpr auto minValue = std::numeric_limits::min(); + constexpr auto maxValue = std::numeric_limits::max(); if (value.isString ()) { - // VFALCO TODO wtf? + std::string const strValue = value.asString(); + + if (!strValue.empty() && + ((strValue[0] < '0') || (strValue[0] > '9'))) + { + if (field == sfTransactionResult) + { + auto ter = transCode(strValue); + + if (!ter || *ter < minValue || *ter > maxValue) + { + error = out_of_range(json_name, fieldName); + return ret; + } + + ret = detail::make_stvar(field, + static_cast(*ter)); + } + else + { + error = bad_type(json_name, fieldName); + return ret; + } + } + else + { + ret = detail::make_stvar (field, + beast::lexicalCastThrow (strValue)); + } } else if (value.isInt ()) { - if (value.asInt () < 0 || value.asInt () > 255) + if (value.asInt () < minValue || value.asInt () > maxValue) { error = out_of_range (json_name, fieldName); return ret; } ret = detail::make_stvar (field, - static_cast (value.asInt ())); + static_cast (value.asInt ())); } else if (value.isUInt ()) { - if (value.asUInt () > 255) + if (value.asUInt () > maxValue) { error = out_of_range (json_name, fieldName); return ret; } ret = detail::make_stvar (field, - static_cast (value.asUInt ())); + static_cast (value.asUInt ())); } else { diff --git a/src/ripple/protocol/impl/TER.cpp b/src/ripple/protocol/impl/TER.cpp index b4674923e..0ee0bc27e 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/ripple/protocol/impl/TER.cpp @@ -19,12 +19,19 @@ #include #include +#include #include #include namespace ripple { -bool transResultInfo (TER code, std::string& token, std::string& text) +namespace detail { + +static +std::unordered_map< + std::underlying_type_t, + std::pair> const& +transResults() { static std::unordered_map< @@ -141,6 +148,14 @@ bool transResultInfo (TER code, std::string& token, std::string& text) { tesSUCCESS, { "tesSUCCESS", "The transaction was applied. Only final in a validated ledger." } }, }; + return results; +} + +} + +bool transResultInfo (TER code, std::string& token, std::string& text) +{ + auto& results = detail::transResults(); auto const r = results.find ( static_cast> (code)); @@ -169,4 +184,35 @@ std::string transHuman (TER code) return transResultInfo (code, token, text) ? text : "-"; } +boost::optional +transCode(std::string const& token) +{ + static + auto const results = [] + { + auto& byTer = detail::transResults(); + auto range = boost::make_iterator_range(byTer.begin(), + byTer.end()); + auto tRange = boost::adaptors::transform( + range, + [](auto const& r) + { + return std::make_pair(r.second.first, r.first); + } + ); + std::unordered_map< + std::string, + std::underlying_type_t> const + byToken(tRange.begin(), tRange.end()); + return byToken; + }(); + + auto const r = results.find(token); + + if (r == results.end()) + return boost::none; + + return static_cast(r->second); +} + } // ripple diff --git a/src/test/protocol/STObject_test.cpp b/src/test/protocol/STObject_test.cpp index 6dd569d96..7c7d4305f 100644 --- a/src/test/protocol/STObject_test.cpp +++ b/src/test/protocol/STObject_test.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -88,6 +89,7 @@ public: if (parsedOK) { STParsedJSONObject parsed ("test", jsonObject); + BEAST_EXPECT(parsed.object); std::string const& serialized ( to_string (parsed.object->getJson(0))); BEAST_EXPECT(serialized == json); @@ -98,6 +100,138 @@ public: } } + void testParseJSONEdgeCases() + { + testcase("parse json object"); + + { + std::string const goodJson( + R"({"CloseResolution":19,"Method":250,)" + R"("TransactionResult":"tecFROZEN"})"); + + Json::Value jv; + if (BEAST_EXPECT(parseJSONString(goodJson, jv))) + { + STParsedJSONObject parsed("test", jv); + if (BEAST_EXPECT(parsed.object)) + { + std::string const& serialized( + to_string(parsed.object->getJson(0))); + BEAST_EXPECT(serialized == goodJson); + } + } + } + + { + std::string const goodJson( + R"({"CloseResolution":19,"Method":"250",)" + R"("TransactionResult":"tecFROZEN"})"); + std::string const expectedJson( + R"({"CloseResolution":19,"Method":250,)" + R"("TransactionResult":"tecFROZEN"})"); + + Json::Value jv; + if (BEAST_EXPECT(parseJSONString(goodJson, jv))) + { + // Integer values are always parsed as int, + // unless they're too big. We want a small uint. + jv["CloseResolution"] = Json::UInt(19); + STParsedJSONObject parsed("test", jv); + if (BEAST_EXPECT(parsed.object)) + { + std::string const& serialized( + to_string(parsed.object->getJson(0))); + BEAST_EXPECT(serialized == expectedJson); + } + } + } + + { + std::string const json( + R"({"CloseResolution":19,"Method":250,)" + R"("TransactionResult":"terQUEUED"})"); + + Json::Value jv; + if (BEAST_EXPECT(parseJSONString(json, jv))) + { + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT(parsed.error[jss::error_message] == + "Field 'test.TransactionResult' is out of range."); + } + } + + { + std::string const json( + R"({"CloseResolution":19,"Method":"pony",)" + R"("TransactionResult":"tesSUCCESS"})"); + + Json::Value jv; + if (BEAST_EXPECT(parseJSONString(json, jv))) + { + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT(parsed.error[jss::error_message] == + "Field 'test.Method' has bad type."); + } + } + + { + std::string const json( + R"({"CloseResolution":19,"Method":3294967296,)" + R"("TransactionResult":"tesSUCCESS"})"); + + Json::Value jv; + if (BEAST_EXPECT(parseJSONString(json, jv))) + { + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT(parsed.error[jss::error_message] == + "Field 'test.Method' is out of range."); + } + } + + { + std::string const json( + R"({"CloseResolution":-10,"Method":42,)" + R"("TransactionResult":"tesSUCCESS"})"); + + Json::Value jv; + if (BEAST_EXPECT(parseJSONString(json, jv))) + { + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT(parsed.error[jss::error_message] == + "Field 'test.CloseResolution' is out of range."); + } + } + + { + std::string const json( + R"({"CloseResolution":19,"Method":3.141592653,)" + R"("TransactionResult":"tesSUCCESS"})"); + + Json::Value jv; + if (BEAST_EXPECT(parseJSONString(json, jv))) + { + STParsedJSONObject parsed("test", jv); + BEAST_EXPECT(!parsed.object); + BEAST_EXPECT(parsed.error); + BEAST_EXPECT(parsed.error[jss::error] == "invalidParams"); + BEAST_EXPECT(parsed.error[jss::error_message] == + "Field 'test.Method' has bad type."); + } + } + } + void testSerialization () { testcase ("serialization"); @@ -510,6 +644,7 @@ public: testSerialization(); testParseJSONArray(); testParseJSONArrayWithInvalidChildrenObjects(); + testParseJSONEdgeCases(); } }; diff --git a/src/test/protocol/TER_test.cpp b/src/test/protocol/TER_test.cpp new file mode 100644 index 000000000..77949d0c3 --- /dev/null +++ b/src/test/protocol/TER_test.cpp @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include + +namespace ripple { + +struct TER_test : public beast::unit_test::suite +{ + void + testTransResultInfo() + { + for (auto i = -400; i < 400; ++i) + { + TER t = static_cast(i); + auto inRange = isTelLocal(t) || + isTemMalformed(t) || + isTefFailure(t) || + isTerRetry(t) || + isTesSuccess(t) || + isTecClaim(t); + + std::string token, text; + auto good = transResultInfo(t, token, text); + BEAST_EXPECT(inRange || !good); + BEAST_EXPECT(transToken(t) == (good ? token : "-")); + BEAST_EXPECT(transHuman(t) == (good ? text : "-")); + + auto code = transCode(token); + BEAST_EXPECT(good == !!code); + BEAST_EXPECT(!code || *code == t); + } + } + + void + run() override + { + testTransResultInfo(); + } +}; + +BEAST_DEFINE_TESTSUITE(TER,protocol,ripple); + +} diff --git a/src/test/unity/protocol_test_unity.cpp b/src/test/unity/protocol_test_unity.cpp index 363d8b7eb..a971e34f6 100644 --- a/src/test/unity/protocol_test_unity.cpp +++ b/src/test/unity/protocol_test_unity.cpp @@ -31,5 +31,6 @@ #include #include #include +#include #include #include \ No newline at end of file