fix: Workaround large number validation and parsing (#2608)

Fixes #2586
This commit is contained in:
Alex Kremer
2025-09-17 16:12:32 +01:00
committed by GitHub
parent e78ff5c442
commit b66d13bc74
40 changed files with 357 additions and 107 deletions

View File

@@ -20,8 +20,13 @@
#include "util/JsonUtils.hpp"
#include <boost/json/parse.hpp>
#include <boost/json/value.hpp>
#include <gtest/gtest.h>
#include <cstdint>
#include <limits>
#include <stdexcept>
TEST(JsonUtils, RemoveSecrets)
{
auto json = boost::json::parse(R"JSON({
@@ -60,3 +65,26 @@ TEST(JsonUtils, RemoveSecrets)
EXPECT_EQ(json2.at("seed_hex").as_string(), "*");
EXPECT_EQ(json2.at("passphrase").as_string(), "*");
}
TEST(JsonUtils, integralValueAs)
{
auto const expectedResultUint64 = static_cast<uint64_t>(std::numeric_limits<int32_t>::max()) + 1u;
auto const uint64Json = boost::json::value(expectedResultUint64);
EXPECT_EQ(util::integralValueAs<int32_t>(uint64Json), std::numeric_limits<int32_t>::min());
EXPECT_EQ(util::integralValueAs<uint32_t>(uint64Json), expectedResultUint64);
EXPECT_EQ(util::integralValueAs<int64_t>(uint64Json), expectedResultUint64);
EXPECT_EQ(util::integralValueAs<uint64_t>(uint64Json), expectedResultUint64);
auto const expectedResultInt64 = static_cast<int64_t>(std::numeric_limits<int32_t>::max()) + 1u;
auto const int64Json = boost::json::value(expectedResultInt64);
EXPECT_EQ(util::integralValueAs<int32_t>(int64Json), std::numeric_limits<int32_t>::min());
EXPECT_EQ(util::integralValueAs<uint32_t>(int64Json), expectedResultInt64);
EXPECT_EQ(util::integralValueAs<int64_t>(int64Json), expectedResultInt64);
EXPECT_EQ(util::integralValueAs<uint64_t>(int64Json), expectedResultInt64);
auto const doubleJson = boost::json::value(3.14);
EXPECT_THROW(util::integralValueAs<int>(doubleJson), std::logic_error);
auto const stringJson = boost::json::value("not a number");
EXPECT_THROW(util::integralValueAs<int>(stringJson), std::logic_error);
}

View File

@@ -36,6 +36,7 @@
#include <xrpl/protocol/ErrorCodes.h>
#include <cstdint>
#include <limits>
#include <string>
#include <string_view>
@@ -50,39 +51,97 @@ namespace json = boost::json;
class RPCBaseTest : public virtual ::testing::Test {};
TEST_F(RPCBaseTest, CheckType)
TEST_F(RPCBaseTest, CheckTypeString)
{
auto const jstr = json::value("a string");
ASSERT_TRUE(checkType<string>(jstr));
ASSERT_FALSE(checkType<int>(jstr));
auto const jString = json::value("a string");
ASSERT_TRUE(checkType<string>(jString));
ASSERT_FALSE(checkType<int>(jString));
}
auto const juint = json::value(123u);
ASSERT_TRUE(checkType<uint32_t>(juint));
ASSERT_TRUE(checkType<int32_t>(juint));
ASSERT_FALSE(checkType<bool>(juint));
TEST_F(RPCBaseTest, CheckTypeUint)
{
auto const jUint = json::value(123u);
ASSERT_TRUE(checkType<uint32_t>(jUint));
ASSERT_TRUE(checkType<int32_t>(jUint));
ASSERT_FALSE(checkType<bool>(jUint));
}
auto jint = json::value(123);
ASSERT_TRUE(checkType<int32_t>(jint));
ASSERT_TRUE(checkType<uint32_t>(jint));
ASSERT_FALSE(checkType<bool>(jint));
TEST_F(RPCBaseTest, CheckTypeInt)
{
auto jInt = json::value(123);
ASSERT_TRUE(checkType<int32_t>(jInt));
ASSERT_TRUE(checkType<uint32_t>(jInt));
ASSERT_FALSE(checkType<bool>(jInt));
jint = json::value(-123);
ASSERT_TRUE(checkType<int32_t>(jint));
ASSERT_FALSE(checkType<uint32_t>(jint));
ASSERT_FALSE(checkType<bool>(jint));
jInt = json::value(-123);
ASSERT_TRUE(checkType<int32_t>(jInt));
ASSERT_FALSE(checkType<uint32_t>(jInt)); // Unsigned can't be negative
ASSERT_FALSE(checkType<bool>(jInt));
}
auto const jbool = json::value(true);
ASSERT_TRUE(checkType<bool>(jbool));
ASSERT_FALSE(checkType<int>(jbool));
TEST_F(RPCBaseTest, CheckTypeBool)
{
auto const jBool = json::value(true);
ASSERT_TRUE(checkType<bool>(jBool));
ASSERT_FALSE(checkType<int>(jBool));
}
auto const jdouble = json::value(0.123);
ASSERT_TRUE(checkType<double>(jdouble));
ASSERT_TRUE(checkType<float>(jdouble));
ASSERT_FALSE(checkType<bool>(jdouble));
TEST_F(RPCBaseTest, CheckTypeDouble)
{
auto const jDouble = json::value(0.123);
ASSERT_TRUE(checkType<double>(jDouble));
ASSERT_TRUE(checkType<float>(jDouble));
ASSERT_FALSE(checkType<bool>(jDouble));
}
auto const jarr = json::value({1, 2, 3});
ASSERT_TRUE(checkType<json::array>(jarr));
ASSERT_FALSE(checkType<int>(jarr));
TEST_F(RPCBaseTest, CheckTypeArray)
{
auto const jArr = json::value({1, 2, 3});
ASSERT_TRUE(checkType<json::array>(jArr));
ASSERT_FALSE(checkType<int>(jArr));
}
TEST_F(RPCBaseTest, CheckTypeAndClampValueUnchanged)
{
auto jUint = json::value(123u);
ASSERT_TRUE(checkTypeAndClamp<uint32_t>(jUint));
ASSERT_EQ(jUint.as_uint64(), 123u);
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jUint));
ASSERT_EQ(jUint.as_uint64(), 123u);
auto jInt = json::value(123);
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jInt));
ASSERT_EQ(jInt.as_int64(), 123);
ASSERT_TRUE(checkTypeAndClamp<uint32_t>(jInt));
ASSERT_EQ(jInt.as_int64(), 123);
jInt = json::value(-123);
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jInt));
ASSERT_EQ(jInt.as_int64(), -123);
}
TEST_F(RPCBaseTest, CheckTypeAndClampInvalidValues)
{
auto jInt = json::value(-123);
ASSERT_FALSE(checkTypeAndClamp<uint32_t>(jInt)); // Unsigned can't be negative
}
TEST_F(RPCBaseTest, CheckTypeAndClampOverflow)
{
auto jBigUint = json::value(std::numeric_limits<uint64_t>::max());
ASSERT_TRUE(checkTypeAndClamp<uint32_t>(jBigUint));
ASSERT_EQ(jBigUint.as_uint64(), std::numeric_limits<uint32_t>::max());
auto jBigInt = json::value(std::numeric_limits<int64_t>::max());
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jBigInt));
ASSERT_EQ(jBigInt.as_int64(), std::numeric_limits<int32_t>::max());
}
TEST_F(RPCBaseTest, CheckTypeAndClampUnderflow)
{
auto jLowInt = json::value(std::numeric_limits<int64_t>::min());
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jLowInt));
ASSERT_EQ(jLowInt.as_int64(), std::numeric_limits<int32_t>::min());
}
TEST_F(RPCBaseTest, TypeValidator)
@@ -203,6 +262,18 @@ TEST_F(RPCBaseTest, MinValidator)
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, MinValidatorAfterType)
{
auto spec = RpcSpec{
{"amount", Type<std::uint32_t>{}, Min{std::numeric_limits<uint32_t>::max()}},
{"amount2", Type<std::int32_t>{}, Min{std::numeric_limits<int32_t>::max()}},
{"amount3", Type<std::int32_t>{}, Min{std::numeric_limits<int32_t>::min()}},
};
auto bigInput = json::parse(R"JSON({ "amount": 9999999999, "amount2": 9999999999, "amount3": -9999999999 })JSON");
ASSERT_TRUE(spec.process(bigInput)); // type check clamps to type's max/min value
}
TEST_F(RPCBaseTest, MaxValidator)
{
auto spec = RpcSpec{
@@ -219,6 +290,18 @@ TEST_F(RPCBaseTest, MaxValidator)
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, MaxValidatorAfterType)
{
auto spec = RpcSpec{
{"amount", Type<std::uint32_t>{}, Max{std::numeric_limits<uint32_t>::max()}},
{"amount2", Type<std::int32_t>{}, Max{std::numeric_limits<int32_t>::max()}},
{"amount3", Type<std::int32_t>{}, Max{std::numeric_limits<int32_t>::min()}},
};
auto bigInput = json::parse(R"JSON({ "amount": 9999999999, "amount2": 9999999999, "amount3": -9999999999 })JSON");
ASSERT_TRUE(spec.process(bigInput)); // type check clamps to type's min/max value
}
TEST_F(RPCBaseTest, OneOfValidator)
{
auto spec = RpcSpec{