xrpld
Loading...
Searching...
No Matches
ProtocolMessage_test.cpp
1#include <xrpld/overlay/Compression.h>
2#include <xrpld/overlay/Message.h>
3#include <xrpld/overlay/detail/ProtocolMessage.h>
4
5#include <xrpl/beast/unit_test/suite.h>
6
7#include <boost/asio/buffer.hpp>
8#include <boost/system/errc.hpp>
9
10#include <xrpl.pb.h>
11
12#include <array>
13#include <cstddef>
14#include <cstdint>
15#include <limits>
16#include <memory>
17#include <optional>
18#include <string>
19#include <utility>
20#include <vector>
21
22namespace xrpl::test {
23
25{
27 {
28 bool compression = false;
29 int beginCount = 0;
30 int messageCount = 0;
31 int endCount = 0;
32 int unknownCount = 0;
34
35 [[nodiscard]] bool
37 {
38 return compression;
39 }
40
41 void
43 {
45 lastType = type;
46 }
47
48 void
59
60 template <class T>
61 void
63 {
65 }
66
67 void
72
73 [[nodiscard]] static std::size_t
78 };
79
80 // Wire bytes: `type` (2 bytes) + unknown field tag (2 bytes) + length varint (2 bytes, as these
81 // tests all use unknownFieldSize >= 128).
87
89 makePingBuffer(std::size_t unknownFieldSize, bool compressed = false)
90 {
91 auto ping = protocol::TMPing{};
92 ping.set_type(protocol::TMPing::ptPING);
93 if (unknownFieldSize > 0)
94 {
95 ping.mutable_unknown_fields()->AddLengthDelimited(
96 42, std::string(unknownFieldSize, 'A'));
97 }
98
99 if (!compressed)
100 {
101 auto m = Message{ping, protocol::mtPING};
103 }
104
105 // Message::compress() refuses to compress pings (mtPING is not in its
106 // allow-list), so getBuffer(Compressed::On) would just return the
107 // uncompressed bytes. Roll it by hand here to get a compressed
108 // ping message on the wire.
109 auto payload = std::string{};
110 ping.SerializeToString(&payload);
111
112 auto deflated = std::vector<std::uint8_t>{};
113 auto const deflatedSize = compression::compress(
114 payload.data(),
115 payload.size(),
116 [&](std::size_t sz) {
117 deflated.resize(sz);
118 return deflated.data();
119 },
121 deflated.resize(deflatedSize);
122
123 auto const type = static_cast<std::uint16_t>(protocol::mtPING);
124 auto buffer = std::vector<std::uint8_t>{};
125 auto pack = [&buffer](std::uint32_t value) {
126 buffer.push_back(static_cast<std::uint8_t>((value >> 24) & 0x0F));
127 buffer.push_back(static_cast<std::uint8_t>((value >> 16) & 0xFF));
128 buffer.push_back(static_cast<std::uint8_t>((value >> 8) & 0xFF));
129 buffer.push_back(static_cast<std::uint8_t>(value & 0xFF));
130 };
131
132 pack(static_cast<std::uint32_t>(deflated.size())); // compressed payload size
133 buffer.push_back(static_cast<std::uint8_t>((type >> 8) & 0xFF));
134 buffer.push_back(static_cast<std::uint8_t>(type & 0xFF));
135 pack(static_cast<std::uint32_t>(payload.size())); // uncompressed size
136 buffer[0] |= static_cast<std::uint8_t>(compression::Algorithm::LZ4);
137
138 buffer.insert(buffer.end(), deflated.begin(), deflated.end());
139 return buffer;
140 }
141
144 {
145 auto ec = boost::system::error_code{};
146 auto const seq = std::array<boost::asio::const_buffer, 1>{boost::asio::buffer(buffer)};
147 if (auto const header = xrpl::detail::parseMessageHeader(ec, seq, buffer.size()))
148 {
149 return header->uncompressedSize + header->headerSize;
150 }
151 return std::nullopt;
152 }
153
156 {
157 auto const seq = std::array<boost::asio::const_buffer, 1>{boost::asio::buffer(buffer)};
158 auto hint = 0uz;
159 return invokeProtocolMessage(seq, handler, hint);
160 }
161
162 void
164 {
165 testcase("oversized ping rejected before dispatch");
166
167 auto runLocalTest = [&](std::size_t size, bool compressed = false) {
168 auto const buffer = makePingBuffer(size, compressed);
169 auto const declared = declaredPingSize(buffer);
170 if (BEAST_EXPECT(declared.has_value()))
171 BEAST_EXPECT(*declared > kMaximumPingMessageSize);
172 BEAST_EXPECT(buffer.size() < kMaximumMessageSize);
173
174 auto handler = TestHandler{};
175 handler.compression = compressed;
176 auto const [bytes, ec] = invoke(buffer, handler);
177
178 BEAST_EXPECT(ec == make_error_code(boost::system::errc::message_size));
179 BEAST_EXPECT(bytes == 0);
180 BEAST_EXPECT(handler.beginCount == 0);
181 BEAST_EXPECT(handler.messageCount == 0);
182 BEAST_EXPECT(handler.endCount == 0);
183 };
184 // Just over the cap, and comfortably over it.
187 runLocalTest(
189 runLocalTest(
191 }
192
193 void
195 {
196 testcase("oversized ping rejected from header alone");
197
198 auto runLocalTest = [&](std::size_t size, bool compressed = false) {
199 auto const full = makePingBuffer(size, compressed);
200 auto const headerSize =
202
203 // Only the header has arrived; the declared payload is still in flight.
204 auto const headerOnly =
205 std::vector<std::uint8_t>{full.begin(), full.begin() + headerSize};
206 BEAST_EXPECT(headerOnly.size() < full.size());
207
208 auto handler = TestHandler{};
209 handler.compression = compressed;
210 auto const [bytes, ec] = invoke(headerOnly, handler);
211
212 BEAST_EXPECT(ec == make_error_code(boost::system::errc::message_size));
213 BEAST_EXPECT(bytes == 0);
214 BEAST_EXPECT(handler.beginCount == 0);
215 BEAST_EXPECT(handler.messageCount == 0);
216 BEAST_EXPECT(handler.endCount == 0);
217 };
220 runLocalTest(
222 runLocalTest(
224 }
225
226 void
228 {
229 testcase("normal ping dispatched");
230
231 auto runLocalTest = [&](std::size_t size, bool compressed = false) {
232 auto const buffer = makePingBuffer(size, compressed);
233 auto const declared = declaredPingSize(buffer);
234 if (BEAST_EXPECT(declared.has_value()))
235 BEAST_EXPECT(*declared <= kMaximumPingMessageSize);
236
237 auto handler = TestHandler{};
238 handler.compression = compressed;
239 auto const [bytes, ec] = invoke(buffer, handler);
240
241 BEAST_EXPECT(!ec);
242 BEAST_EXPECT(bytes == buffer.size());
243 BEAST_EXPECT(handler.beginCount == 1);
244 BEAST_EXPECT(handler.messageCount == 1);
245 BEAST_EXPECT(handler.endCount == 1);
246 };
247 runLocalTest(0);
248 runLocalTest(0, true);
249 }
250
251 void
253 {
254 testcase("ping with small unknown field still dispatched");
255
256 auto runLocalTest = [&](std::size_t size, bool compressed = false) {
257 auto const buffer = makePingBuffer(size, compressed);
258 auto const declared = declaredPingSize(buffer);
259 if (BEAST_EXPECT(declared.has_value()))
260 BEAST_EXPECT(*declared <= kMaximumPingMessageSize);
261
262 auto handler = TestHandler{};
263 handler.compression = compressed;
264 auto const [bytes, ec] = invoke(buffer, handler);
265
266 BEAST_EXPECT(!ec);
267 BEAST_EXPECT(bytes == buffer.size());
268 BEAST_EXPECT(handler.beginCount == 1);
269 BEAST_EXPECT(handler.messageCount == 1);
270 BEAST_EXPECT(handler.endCount == 1);
271 };
272 // Well under the cap, one byte under it, and exactly at it.
276 runLocalTest(
278 runLocalTest(
280 runLocalTest(
282 }
283
284 void
292};
293
294BEAST_DEFINE_TESTSUITE(ProtocolMessage, overlay, xrpl);
295
296} // namespace xrpl::test
A testsuite class.
Definition suite.h:52
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
std::vector< uint8_t > const & getBuffer(Compressed tryCompressed)
Retrieve the packed message data.
Definition Message.cpp:201
static constexpr std::size_t kPingProtoOverheadWithUnknownLen
static constexpr std::size_t kMinimumPingSizeCompressedWithEmptyUnknownField
static std::pair< std::size_t, boost::system::error_code > invoke(std::vector< std::uint8_t > const &buffer, TestHandler &handler)
static std::optional< std::size_t > declaredPingSize(std::vector< std::uint8_t > const &buffer)
static constexpr std::size_t kMinimumPingSizeWithEmptyUnknownField
static std::vector< std::uint8_t > makePingBuffer(std::size_t unknownFieldSize, bool compressed=false)
void run() override
Runs the suite.
T max(T... args)
std::size_t compress(void const *in, std::size_t inSize, BufferFactory &&bf, Algorithm algorithm=Algorithm::LZ4)
Compress input data.
Definition Compression.h:73
constexpr std::size_t kHeaderBytesCompressed
Definition Compression.h:13
constexpr std::size_t kHeaderBytes
Definition Compression.h:12
std::optional< MessageHeader > parseMessageHeader(boost::system::error_code &ec, BufferSequence const &bufs, std::size_t size)
Parse a message header.
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr std::size_t kMaximumMessageSize
Definition Message.h:22
constexpr std::size_t kMaximumPingMessageSize
Definition Message.h:25
std::error_code make_error_code(xrpl::TokenCodecErrc e)
std::pair< std::size_t, boost::system::error_code > invokeProtocolMessage(Buffers const &buffers, Handler &handler, std::size_t &hint)
Calls the handler for up to one protocol message in the passed buffers.
T size(T... args)
void onMessageEnd(std::uint16_t, std::shared_ptr<::google::protobuf::Message > const &)
void onMessageBegin(std::uint16_t type, std::shared_ptr<::google::protobuf::Message > const &, std::size_t, std::size_t, bool)