Improve compression support:

* Optimize parsing of compressed message headers
* Enforce protocol-defined message size maxima
* Update comments
This commit is contained in:
Nik Bougalis
2020-04-13 10:26:14 -04:00
parent 362a017eee
commit fe9922d654
7 changed files with 138 additions and 68 deletions

View File

@@ -31,7 +31,9 @@ namespace compression {
std::size_t constexpr headerBytes = 6; std::size_t constexpr headerBytes = 6;
std::size_t constexpr headerBytesCompressed = 10; std::size_t constexpr headerBytesCompressed = 10;
enum class Algorithm : std::uint8_t { None = 0x00, LZ4 = 0x01 }; // All values other than 'none' must have the high bit. The low order four bits
// must be 0.
enum class Algorithm : std::uint8_t { None = 0x00, LZ4 = 0x90 };
enum class Compressed : std::uint8_t { On, Off }; enum class Compressed : std::uint8_t { On, Off };

View File

@@ -84,7 +84,7 @@ private:
* @param in Pointer to the payload * @param in Pointer to the payload
* @param payloadBytes Size of the payload excluding the header size * @param payloadBytes Size of the payload excluding the header size
* @param type Protocol message type * @param type Protocol message type
* @param comprAlgorithm Compression algorithm used in compression, * @param compression Compression algorithm used in compression,
* currently LZ4 only. If None then the message is uncompressed. * currently LZ4 only. If None then the message is uncompressed.
* @param uncompressedBytes Size of the uncompressed message * @param uncompressedBytes Size of the uncompressed message
*/ */
@@ -93,7 +93,7 @@ private:
std::uint8_t* in, std::uint8_t* in,
std::uint32_t payloadBytes, std::uint32_t payloadBytes,
int type, int type,
Algorithm comprAlgorithm, Algorithm compression,
std::uint32_t uncompressedBytes); std::uint32_t uncompressedBytes);
/** Try to compress the payload. /** Try to compress the payload.

View File

@@ -118,6 +118,9 @@ public:
cycleStatus() = 0; cycleStatus() = 0;
virtual bool virtual bool
hasRange(std::uint32_t uMin, std::uint32_t uMax) = 0; hasRange(std::uint32_t uMin, std::uint32_t uMax) = 0;
virtual bool
compressionEnabled() const = 0;
}; };
} // namespace ripple } // namespace ripple

View File

@@ -109,22 +109,46 @@ Message::compress()
} }
/** Set payload header /** Set payload header
* Uncompressed message header
* 47-42 Set to 0 The header is a variable-sized structure that contains information about
* 41-16 Payload size the type of the message and the length and encoding of the payload.
* 15-0 Message Type
* Compressed message header The first bit determines whether a message is compressed or uncompressed;
* 79 Set to 0, indicates the message is compressed for compressed messages, the next three bits identify the compression
* 78-76 Compression algorithm, value 1-7. Set to 1 to indicate LZ4 algorithm.
* compression 75-74 Set to 0 73-48 Payload size 47-32 Message Type
* 31-0 Uncompressed message size All multi-byte values are represented in big endian.
*/
For uncompressed messages (6 bytes), numbering bits from left to right:
- The first 6 bits are set to 0.
- The next 26 bits represent the payload size.
- The remaining 16 bits represent the message type.
For compressed messages (10 bytes), numbering bits from left to right:
- The first 32 bits, together, represent the compression algorithm
and payload size:
- The first bit is set to 1 to indicate the message is compressed.
- The next 3 bits indicate the compression algorithm.
- The next 2 bits are reserved at this time and set to 0.
- The remaining 26 bits represent the payload size.
- The next 16 bits represent the message type.
- The remaining 32 bits are the uncompressed message size.
The maximum size of a message at this time is 64 MB. Messages larger than
this will be dropped and the recipient may, at its option, sever the link.
@note While nominally a part of the wire protocol, the framing is subject
to change; future versions of the code may negotiate the use of
substantially different framing.
*/
void void
Message::setHeader( Message::setHeader(
std::uint8_t* in, std::uint8_t* in,
std::uint32_t payloadBytes, std::uint32_t payloadBytes,
int type, int type,
Algorithm comprAlgorithm, Algorithm compression,
std::uint32_t uncompressedBytes) std::uint32_t uncompressedBytes)
{ {
auto h = in; auto h = in;
@@ -142,10 +166,10 @@ Message::setHeader(
*in++ = static_cast<std::uint8_t>((type >> 8) & 0xFF); *in++ = static_cast<std::uint8_t>((type >> 8) & 0xFF);
*in++ = static_cast<std::uint8_t>(type & 0xFF); *in++ = static_cast<std::uint8_t>(type & 0xFF);
if (comprAlgorithm != Algorithm::None) if (compression != Algorithm::None)
{ {
pack(in, uncompressedBytes); pack(in, uncompressedBytes);
*h |= 0x80 | (static_cast<uint8_t>(comprAlgorithm) << 4); *h |= static_cast<std::uint8_t>(compression);
} }
} }

View File

@@ -428,6 +428,12 @@ public:
boost::optional<hash_map<PublicKey, ShardInfo>> boost::optional<hash_map<PublicKey, ShardInfo>>
getPeerShardInfo() const; getPeerShardInfo() const;
bool
compressionEnabled() const override
{
return compressionEnabled_ == Compressed::On;
}
private: private:
void void
close(); close();

View File

@@ -120,51 +120,94 @@ buffersBegin(BufferSequence const& bufs)
bufs); bufs);
} }
/** Parse a message header
* @return a seated optional if the message header was successfully
* parsed. An unseated optional otherwise, in which case
* @param ec contains more information:
* - set to `errc::success` if not enough bytes were present
* - set to `errc::no_message` if a valid header was not present
*/
template <class BufferSequence> template <class BufferSequence>
boost::optional<MessageHeader> boost::optional<MessageHeader>
parseMessageHeader(BufferSequence const& bufs, std::size_t size) parseMessageHeader(
boost::system::error_code& ec,
BufferSequence const& bufs,
std::size_t size)
{ {
using namespace ripple::compression; using namespace ripple::compression;
auto iter = buffersBegin(bufs);
MessageHeader hdr; MessageHeader hdr;
auto const compressed = (*iter & 0x80) == 0x80; auto iter = buffersBegin(bufs);
// Check valid header // Check valid header
if ((*iter & 0xFC) == 0 || compressed) if (*iter & 0x80)
{ {
hdr.header_size = compressed ? headerBytesCompressed : headerBytes; hdr.header_size = headerBytesCompressed;
// not enough bytes to parse the header
if (size < hdr.header_size) if (size < hdr.header_size)
return {};
if (compressed)
{ {
uint8_t algorithm = (*iter & 0x70) >> 4; ec = make_error_code(boost::system::errc::success);
if (algorithm != return boost::none;
static_cast<std::uint8_t>(compression::Algorithm::LZ4)) }
return {};
hdr.algorithm = compression::Algorithm::LZ4; if (*iter & 0x0C)
{
ec = make_error_code(boost::system::errc::protocol_error);
return boost::none;
}
hdr.algorithm = static_cast<compression::Algorithm>(*iter);
if (hdr.algorithm != compression::Algorithm::LZ4)
{
ec = make_error_code(boost::system::errc::protocol_error);
return boost::none;
} }
for (int i = 0; i != 4; ++i) for (int i = 0; i != 4; ++i)
hdr.payload_wire_size = (hdr.payload_wire_size << 8) + *iter++; hdr.payload_wire_size = (hdr.payload_wire_size << 8) + *iter++;
// clear the compression bits
hdr.payload_wire_size &= 0x03FFFFFF; // clear the top four bits (the compression bits).
hdr.payload_wire_size &= 0x0FFFFFFF;
hdr.total_wire_size = hdr.header_size + hdr.payload_wire_size; hdr.total_wire_size = hdr.header_size + hdr.payload_wire_size;
for (int i = 0; i != 2; ++i) for (int i = 0; i != 2; ++i)
hdr.message_type = (hdr.message_type << 8) + *iter++; hdr.message_type = (hdr.message_type << 8) + *iter++;
if (compressed) for (int i = 0; i != 4; ++i)
for (int i = 0; i != 4; ++i) hdr.uncompressed_size = (hdr.uncompressed_size << 8) + *iter++;
hdr.uncompressed_size = (hdr.uncompressed_size << 8) + *iter++;
return hdr; return hdr;
} }
return {}; if ((*iter & 0xFC) == 0)
{
hdr.header_size = headerBytes;
if (size < hdr.header_size)
{
ec = make_error_code(boost::system::errc::success);
return boost::none;
}
hdr.algorithm = Algorithm::None;
for (int i = 0; i != 4; ++i)
hdr.payload_wire_size = (hdr.payload_wire_size << 8) + *iter++;
hdr.uncompressed_size = hdr.payload_wire_size;
hdr.total_wire_size = hdr.header_size + hdr.payload_wire_size;
for (int i = 0; i != 2; ++i)
hdr.message_type = (hdr.message_type << 8) + *iter++;
return hdr;
}
ec = make_error_code(boost::system::errc::no_message);
return boost::none;
} }
template < template <
@@ -186,7 +229,7 @@ invoke(MessageHeader const& header, Buffers const& buffers, Handler& handler)
std::vector<std::uint8_t> payload; std::vector<std::uint8_t> payload;
payload.resize(header.uncompressed_size); payload.resize(header.uncompressed_size);
auto payloadSize = ripple::compression::decompress( auto const payloadSize = ripple::compression::decompress(
stream, stream,
header.payload_wire_size, header.payload_wire_size,
payload.data(), payload.data(),
@@ -226,10 +269,13 @@ invokeProtocolMessage(Buffers const& buffers, Handler& handler)
if (size == 0) if (size == 0)
return result; return result;
auto header = detail::parseMessageHeader(buffers, size); auto header = detail::parseMessageHeader(result.second, buffers, size);
// If we can't parse the header then it may be that we don't have enough // If we can't parse the header then it may be that we don't have enough
// bytes yet, or because the message was cut off. // bytes yet, or because the message was cut off (if error_code is success).
// Otherwise we failed to match the header's marker (error_code is set to
// no_message) or the compression algorithm is invalid (error_code is
// protocol_error) and signal an error.
if (!header) if (!header)
return result; return result;
@@ -237,12 +283,21 @@ invokeProtocolMessage(Buffers const& buffers, Handler& handler)
// whose size exceeds this may result in the connection being dropped. A // whose size exceeds this may result in the connection being dropped. A
// larger message size may be supported in the future or negotiated as // larger message size may be supported in the future or negotiated as
// part of a protocol upgrade. // part of a protocol upgrade.
if (header->payload_wire_size > megabytes(64)) if (header->payload_wire_size > megabytes(64) ||
header->uncompressed_size > megabytes(64))
{ {
result.second = make_error_code(boost::system::errc::message_size); result.second = make_error_code(boost::system::errc::message_size);
return result; return result;
} }
// We requested uncompressed messages from the peer but received compressed.
if (!handler.compressionEnabled() &&
header->algorithm != compression::Algorithm::None)
{
result.second = make_error_code(boost::system::errc::protocol_error);
return result;
}
// We don't have the whole message yet. This isn't an error but we have // We don't have the whole message yet. This isn't an error but we have
// nothing to do. // nothing to do.
if (header->total_wire_size > size) if (header->total_wire_size > size)

View File

@@ -84,21 +84,14 @@ public:
std::shared_ptr<T> proto, std::shared_ptr<T> proto,
protocol::MessageType mt, protocol::MessageType mt,
uint16_t nbuffers, uint16_t nbuffers,
const char* msg, std::string msg)
bool log = false)
{ {
if (log) testcase("Compress/Decompress: " + msg);
printf("=== compress/decompress %s ===\n", msg);
Message m(*proto, mt); Message m(*proto, mt);
auto& buffer = m.getBuffer(Compressed::On); auto& buffer = m.getBuffer(Compressed::On);
if (log)
printf(
"==> compressed, original %d bytes, compressed %d bytes\n",
(int)m.getBuffer(Compressed::Off).size(),
(int)m.getBuffer(Compressed::On).size());
boost::beast::multi_buffer buffers; boost::beast::multi_buffer buffers;
// simulate multi-buffer // simulate multi-buffer
@@ -112,26 +105,15 @@ public:
buffers.commit(boost::asio::buffer_copy( buffers.commit(boost::asio::buffer_copy(
buffers.prepare(slice.size()), boost::asio::buffer(slice))); buffers.prepare(slice.size()), boost::asio::buffer(slice)));
} }
auto header =
ripple::detail::parseMessageHeader(buffers.data(), buffer.size());
if (log) boost::system::error_code ec;
printf( auto header = ripple::detail::parseMessageHeader(
"==> parsed header: buffers size %d, compressed %d, algorithm " ec, buffers.data(), buffer.size());
"%d, header size %d, payload size %d, buffer size %d\n",
(int)buffers.size(), BEAST_EXPECT(header);
header->algorithm != Algorithm::None,
(int)header->algorithm,
(int)header->header_size,
(int)header->payload_wire_size,
(int)buffer.size());
if (header->algorithm == Algorithm::None) if (header->algorithm == Algorithm::None)
{
if (log)
printf("==> NOT COMPRESSED\n");
return; return;
}
std::vector<std::uint8_t> decompressed; std::vector<std::uint8_t> decompressed;
decompressed.resize(header->uncompressed_size); decompressed.resize(header->uncompressed_size);
@@ -157,8 +139,6 @@ public:
uncompressed.begin() + ripple::compression::headerBytes, uncompressed.begin() + ripple::compression::headerBytes,
uncompressed.end(), uncompressed.end(),
decompressed.begin())); decompressed.begin()));
if (log)
printf("\n");
} }
std::shared_ptr<protocol::TMManifests> std::shared_ptr<protocol::TMManifests>