Files
rippled/src/test/basics/base_uint_test.cpp
2026-04-21 15:32:51 +00:00

359 lines
12 KiB
C++

#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/hardened_hash.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Zero.h>
#include <boost/endian/detail/order.hpp>
#include <array>
#include <cassert>
#include <complex>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <stdexcept>
#include <string_view>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <vector>
namespace xrpl::test {
// a non-hashing Hasher that just copies the bytes.
// Used to test hash_append in base_uint
template <std::size_t Bits>
struct nonhash
{
static constexpr auto const endian = boost::endian::order::big;
static constexpr std::size_t WIDTH = Bits / 8;
std::array<std::uint8_t, WIDTH> data_;
nonhash() = default;
void
operator()(void const* key, std::size_t len) noexcept
{
assert(len == WIDTH);
memcpy(data_.data(), key, len);
}
explicit
operator std::size_t() noexcept
{
return WIDTH;
}
};
struct base_uint_test : beast::unit_test::suite
{
using test96 = base_uint<96>;
static_assert(std::is_copy_constructible_v<test96>);
static_assert(std::is_copy_assignable_v<test96>);
void
testComparisons()
{
{
static constexpr std::array<std::pair<std::string_view, std::string_view>, 6> test_args{
{{"0000000000000000", "0000000000000001"},
{"0000000000000000", "ffffffffffffffff"},
{"1234567812345678", "2345678923456789"},
{"8000000000000000", "8000000000000001"},
{"aaaaaaaaaaaaaaa9", "aaaaaaaaaaaaaaaa"},
{"fffffffffffffffe", "ffffffffffffffff"}}};
for (auto const& arg : test_args)
{
xrpl::base_uint<64> const u{arg.first}, v{arg.second};
BEAST_EXPECT(u < v);
BEAST_EXPECT(u <= v);
BEAST_EXPECT(u != v);
BEAST_EXPECT(!(u == v));
BEAST_EXPECT(!(u > v));
BEAST_EXPECT(!(u >= v));
BEAST_EXPECT(!(v < u));
BEAST_EXPECT(!(v <= u));
BEAST_EXPECT(v != u);
BEAST_EXPECT(!(v == u));
BEAST_EXPECT(v > u);
BEAST_EXPECT(v >= u);
BEAST_EXPECT(u == u);
BEAST_EXPECT(v == v);
}
}
{
static constexpr std::array<std::pair<std::string_view, std::string_view>, 6> test_args{
{
{"000000000000000000000000", "000000000000000000000001"},
{"000000000000000000000000", "ffffffffffffffffffffffff"},
{"0123456789ab0123456789ab", "123456789abc123456789abc"},
{"555555555555555555555555", "55555555555a555555555555"},
{"aaaaaaaaaaaaaaa9aaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaa"},
{"fffffffffffffffffffffffe", "ffffffffffffffffffffffff"},
}};
for (auto const& arg : test_args)
{
xrpl::base_uint<96> const u{arg.first}, v{arg.second};
BEAST_EXPECT(u < v);
BEAST_EXPECT(u <= v);
BEAST_EXPECT(u != v);
BEAST_EXPECT(!(u == v));
BEAST_EXPECT(!(u > v));
BEAST_EXPECT(!(u >= v));
BEAST_EXPECT(!(v < u));
BEAST_EXPECT(!(v <= u));
BEAST_EXPECT(v != u);
BEAST_EXPECT(!(v == u));
BEAST_EXPECT(v > u);
BEAST_EXPECT(v >= u);
BEAST_EXPECT(u == u);
BEAST_EXPECT(v == v);
}
}
}
void
run() override
{
testcase("base_uint: general purpose tests");
static_assert(!std::is_constructible_v<test96, std::complex<double>>);
static_assert(!std::is_assignable_v<test96&, std::complex<double>>);
testComparisons();
// used to verify set insertion (hashing required)
std::unordered_set<test96, hardened_hash<>> uset;
Blob const raw{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
BEAST_EXPECT(test96::bytes == raw.size());
test96 u{raw};
uset.insert(u);
BEAST_EXPECT(raw.size() == u.size());
BEAST_EXPECT(to_string(u) == "0102030405060708090A0B0C");
BEAST_EXPECT(to_short_string(u) == "01020304...");
BEAST_EXPECT(*u.data() == 1);
BEAST_EXPECT(u.signum() == 1);
BEAST_EXPECT(!!u);
BEAST_EXPECT(!u.isZero());
BEAST_EXPECT(u.isNonZero());
unsigned char t = 0;
for (auto& d : u)
{
BEAST_EXPECT(d == ++t);
}
// Test hash_append by "hashing" with a no-op hasher (h)
// and then extracting the bytes that were written during hashing
// back into another base_uint (w) for comparison with the original
nonhash<96> h{};
hash_append(h, u);
test96 const w{std::vector<std::uint8_t>(h.data_.begin(), h.data_.end())};
BEAST_EXPECT(w == u);
test96 v{~u};
uset.insert(v);
BEAST_EXPECT(to_string(v) == "FEFDFCFBFAF9F8F7F6F5F4F3");
BEAST_EXPECT(to_short_string(v) == "FEFDFCFB...");
BEAST_EXPECT(*v.data() == 0xfe);
BEAST_EXPECT(v.signum() == 1);
BEAST_EXPECT(!!v);
BEAST_EXPECT(!v.isZero());
BEAST_EXPECT(v.isNonZero());
t = 0xff;
for (auto& d : v)
{
BEAST_EXPECT(d == --t);
}
BEAST_EXPECT(u < v);
BEAST_EXPECT(v > u);
v = u;
BEAST_EXPECT(v == u);
test96 z{beast::zero};
uset.insert(z);
BEAST_EXPECT(to_string(z) == "000000000000000000000000");
BEAST_EXPECT(to_short_string(z) == "00000000...");
BEAST_EXPECT(*z.data() == 0);
BEAST_EXPECT(*z.begin() == 0);
BEAST_EXPECT(*std::prev(z.end(), 1) == 0);
BEAST_EXPECT(z.signum() == 0);
BEAST_EXPECT(!z);
BEAST_EXPECT(z.isZero());
BEAST_EXPECT(!z.isNonZero());
for (auto& d : z)
{
BEAST_EXPECT(d == 0);
}
test96 n{z};
n++;
BEAST_EXPECT(n == test96(1));
n--;
BEAST_EXPECT(n == beast::zero);
BEAST_EXPECT(n == z);
n--;
BEAST_EXPECT(to_string(n) == "FFFFFFFFFFFFFFFFFFFFFFFF");
BEAST_EXPECT(to_short_string(n) == "FFFFFFFF...");
n = beast::zero;
BEAST_EXPECT(n == z);
test96 zp1{z};
zp1++;
test96 zm1{z};
zm1--;
test96 const x{zm1 ^ zp1};
uset.insert(x);
BEAST_EXPECTS(to_string(x) == "FFFFFFFFFFFFFFFFFFFFFFFE", to_string(x));
BEAST_EXPECTS(to_short_string(x) == "FFFFFFFF...", to_short_string(x));
BEAST_EXPECT(uset.size() == 4);
test96 tmp;
BEAST_EXPECT(tmp.parseHex(to_string(u)));
BEAST_EXPECT(tmp == u);
tmp = z;
// fails with extra char
BEAST_EXPECT(!tmp.parseHex("A" + to_string(u)));
tmp = z;
// fails with extra char at end
BEAST_EXPECT(!tmp.parseHex(to_string(u) + "A"));
// fails with a non-hex character at some point in the string:
tmp = z;
for (std::size_t i = 0; i != 24; ++i)
{
std::string x = to_string(z);
x[i] = ('G' + (i % 10));
BEAST_EXPECT(!tmp.parseHex(x));
}
// Walking 1s:
for (std::size_t i = 0; i != 24; ++i)
{
std::string s1 = "000000000000000000000000";
s1[i] = '1';
BEAST_EXPECT(tmp.parseHex(s1));
BEAST_EXPECT(to_string(tmp) == s1);
}
// Walking 0s:
for (std::size_t i = 0; i != 24; ++i)
{
std::string s1 = "111111111111111111111111";
s1[i] = '0';
BEAST_EXPECT(tmp.parseHex(s1));
BEAST_EXPECT(to_string(tmp) == s1);
}
// Constexpr constructors
{
static_assert(test96{}.signum() == 0);
static_assert(test96("0").signum() == 0);
static_assert(test96("000000000000000000000000").signum() == 0);
static_assert(test96("000000000000000000000001").signum() == 1);
static_assert(test96("800000000000000000000000").signum() == 1);
// Everything within the #if should fail during compilation.
#if 0
// Too few characters
static_assert(test96("00000000000000000000000").signum() == 0);
// Too many characters
static_assert(test96("0000000000000000000000000").signum() == 0);
// Non-hex characters
static_assert(test96("00000000000000000000000 ").signum() == 1);
static_assert(test96("00000000000000000000000/").signum() == 1);
static_assert(test96("00000000000000000000000:").signum() == 1);
static_assert(test96("00000000000000000000000@").signum() == 1);
static_assert(test96("00000000000000000000000G").signum() == 1);
static_assert(test96("00000000000000000000000`").signum() == 1);
static_assert(test96("00000000000000000000000g").signum() == 1);
static_assert(test96("00000000000000000000000~").signum() == 1);
#endif // 0
// Using the constexpr constructor in a non-constexpr context
// with an error in the parsing throws an exception.
{
// Invalid length for string.
bool caught = false;
try
{
// Try to prevent constant evaluation.
std::vector<char> str(23, '7');
std::string_view const sView(str.data(), str.size());
[[maybe_unused]] test96 const t96(sView);
}
catch (std::invalid_argument const& e)
{
BEAST_EXPECT(e.what() == std::string("invalid length for hex string"));
caught = true;
}
BEAST_EXPECT(caught);
}
{
// Invalid character in string.
bool caught = false;
try
{
// Try to prevent constant evaluation.
std::vector<char> str(23, '7');
str.push_back('G');
std::string_view const sView(str.data(), str.size());
[[maybe_unused]] test96 const t96(sView);
}
catch (std::range_error const& e)
{
BEAST_EXPECT(e.what() == std::string("invalid hex character"));
caught = true;
}
BEAST_EXPECT(caught);
}
// Verify that constexpr base_uints interpret a string the same
// way parseHex() does.
struct StrBaseUint
{
char const* const str;
test96 tst;
constexpr StrBaseUint(char const* s) : str(s), tst(s)
{
}
};
constexpr StrBaseUint testCases[] = {
"000000000000000000000000",
"000000000000000000000001",
"fedcba9876543210ABCDEF91",
"19FEDCBA0123456789abcdef",
"800000000000000000000000",
"fFfFfFfFfFfFfFfFfFfFfFfF"};
for (StrBaseUint const& t : testCases)
{
test96 t96;
BEAST_EXPECT(t96.parseHex(t.str));
BEAST_EXPECT(t96 == t.tst);
}
}
}
};
BEAST_DEFINE_TESTSUITE(base_uint, basics, xrpl);
} // namespace xrpl::test