xrpld
Loading...
Searching...
No Matches
Codec.cpp
1#include <xrpl/nodestore/detail/codec.h>
2
3#include <xrpl/nodestore/NodeObject.h>
4#include <xrpl/protocol/HashPrefix.h>
5
6#include <gtest/gtest.h>
7#include <nudb/detail/buffer.hpp>
8#include <nudb/detail/stream.hpp>
9
10#include <array>
11#include <cstddef>
12#include <cstdint>
13#include <cstring>
14#include <utility>
15#include <vector>
16
17using namespace xrpl;
18using namespace xrpl::node_store;
19
20namespace {
21
22// v1 inner-node layout: 16 hashes of 32 bytes each
23constexpr std::size_t kHashCount = 16;
24constexpr std::size_t kHashSize = 32;
25
27makeInnerNode(std::size_t nonEmptySlots)
28{
29 using namespace nudb::detail;
30
31 static constexpr std::size_t kInnerNodeSize = 525;
32
34 for (auto slot = 0uz; slot < nonEmptySlots; ++slot)
35 {
36 for (auto byte = 0uz; byte < kHashSize; ++byte)
37 {
38 std::size_t const offset = (slot * kHashSize) + byte;
39 hashes[offset] = static_cast<std::uint8_t>((offset % 255) + 1);
40 }
41 }
42
43 std::vector<std::uint8_t> blob(kInnerNodeSize);
44 ostream os(blob.data(), blob.size());
45 write<std::uint32_t>(os, 0); // index
46 write<std::uint32_t>(os, 0); // unused
49 write(os, hashes.data(), hashes.size());
50
51 return blob;
52}
53
55codecType(std::pair<void const*, std::size_t> const& compressed)
56{
57 return static_cast<std::uint8_t const*>(compressed.first)[0];
58}
59
60} // namespace
61
62// All 16 hash slots populated - "full v1 inner node"
63TEST(Codec, inner_node_full_roundtrip)
64{
65 static constexpr std::uint8_t kTypeInnerNodeFull = 3;
66
67 auto const blob = makeInnerNode(kHashCount);
68
69 nudb::detail::buffer compressBuf;
70 auto const compressed = nodeobjectCompress(blob.data(), blob.size(), compressBuf);
71
72 EXPECT_EQ(codecType(compressed), kTypeInnerNodeFull);
73 EXPECT_EQ(compressed.second, sizeVarint(kTypeInnerNodeFull) + (kHashCount * kHashSize));
74
75 nudb::detail::buffer decompressBuf;
76 auto const restored = nodeobjectDecompress(compressed.first, compressed.second, decompressBuf);
77
78 EXPECT_EQ(restored.second, blob.size());
79 EXPECT_EQ(std::memcmp(restored.first, blob.data(), blob.size()), 0);
80}
81
82// Some hash slots empty - "compressed v1 inner node"
83TEST(Codec, inner_node_compressed_roundtrip)
84{
85 static constexpr std::uint8_t kTypeInnerNodeCompressed = 2;
86 static constexpr std::size_t kNonEmpty = 5;
87 auto const blob = makeInnerNode(kNonEmpty);
88
89 nudb::detail::buffer compressBuf;
90 auto const compressed = nodeobjectCompress(blob.data(), blob.size(), compressBuf);
91
92 EXPECT_EQ(codecType(compressed), kTypeInnerNodeCompressed);
93 EXPECT_EQ(
94 compressed.second,
95 sizeVarint(kTypeInnerNodeCompressed) + sizeof(std::uint16_t) + (kNonEmpty * kHashSize));
96 EXPECT_LT(compressed.second, blob.size());
97
98 nudb::detail::buffer decompressBuf;
99 auto const restored = nodeobjectDecompress(compressed.first, compressed.second, decompressBuf);
100
101 EXPECT_EQ(restored.second, blob.size());
102 EXPECT_EQ(std::memcmp(restored.first, blob.data(), blob.size()), 0);
103}
104
105// Anything that is not a v1 inner node - lz4 compressed
106TEST(Codec, lz4_roundtrip)
107{
108 // A payload that is deliberately not a v1 inner node (any size other than 525), filled with a
109 // short repeating pattern so lz4 actually shrinks it.
110 static constexpr std::size_t kNonInnerNodeSize = 1000;
111 static constexpr std::size_t kBytePatternPeriod = 7;
112 static constexpr std::uint8_t kTypeLz4 = 1;
113
114 std::vector<std::uint8_t> blob(kNonInnerNodeSize);
115 for (auto i = 0uz; i < blob.size(); ++i)
116 blob[i] = static_cast<std::uint8_t>(i % kBytePatternPeriod);
117
118 nudb::detail::buffer compressBuf;
119 auto const compressed = nodeobjectCompress(blob.data(), blob.size(), compressBuf);
120
121 EXPECT_EQ(codecType(compressed), kTypeLz4);
122
123 nudb::detail::buffer decompressBuf;
124 auto const restored = nodeobjectDecompress(compressed.first, compressed.second, decompressBuf);
125
126 EXPECT_EQ(restored.second, blob.size());
127 EXPECT_EQ(std::memcmp(restored.first, blob.data(), blob.size()), 0);
128}
129
130// An uncompressed blob is never produced by the compressor but must still decode: leading varint 0
131// followed by the raw payload.
132TEST(Codec, uncompressed_passthrough)
133{
134 static constexpr std::uint8_t kTypeUncompressed = 0;
135 static constexpr auto payload = std::to_array<std::uint8_t>({0xde, 0xad, 0xbe, 0xef, 0x2a});
136
138 blob.push_back(kTypeUncompressed); // leading varint type tag
139 blob.insert(blob.end(), payload.begin(), payload.end());
140
141 nudb::detail::buffer decompressBuf;
142 auto const restored = nodeobjectDecompress(blob.data(), blob.size(), decompressBuf);
143
144 EXPECT_EQ(restored.second, payload.size());
145 EXPECT_EQ(std::memcmp(restored.first, payload.data(), payload.size()), 0);
146}
T data(T... args)
T memcmp(T... args)
std::pair< void const *, std::size_t > nodeobjectCompress(void const *in, std::size_t inSize, BufferFactory &&bf)
Definition codec.h:201
std::size_t sizeVarint(T v)
Definition Varint.h:73
void write(nudb::detail::ostream &os, std::size_t t)
Definition Varint.h:120
std::pair< void const *, std::size_t > nodeobjectDecompress(void const *in, std::size_t inSize, BufferFactory &&bf)
Definition codec.h:89
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
TEST(FileUtilitiesTest, get_file_contents)
@ InnerNode
inner node in V1 tree
Definition HashPrefix.h:54
T push_back(T... args)
T size(T... args)