mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Add protocol message compression support:
* Peers negotiate compression via HTTP Header "X-Offer-Compression: lz4" * Messages greater than 70 bytes and protocol type messages MANIFESTS, ENDPOINTS, TRANSACTION, GET_LEDGER, LEDGER_DATA, GET_OBJECT, and VALIDATORLIST are compressed * If the compressed message is larger than the uncompressed message then the uncompressed message is sent * Compression flag and the compression algorithm type are included in the message header * Only LZ4 block compression is currently supported
This commit is contained in:
committed by
manojsdoshi
parent
ade5eb71cf
commit
758a3792eb
153
src/ripple/basics/CompressionAlgorithms.h
Normal file
153
src/ripple/basics/CompressionAlgorithms.h
Normal file
@@ -0,0 +1,153 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2020 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLED_COMPRESSIONALGORITHMS_H_INCLUDED
|
||||
#define RIPPLED_COMPRESSIONALGORITHMS_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <lz4.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace compression_algorithms {
|
||||
|
||||
/** Convenience wrapper for Throw
|
||||
* @param message Message to log/throw
|
||||
*/
|
||||
inline void doThrow(const char *message)
|
||||
{
|
||||
Throw<std::runtime_error>(message);
|
||||
}
|
||||
|
||||
/** LZ4 block compression.
|
||||
* @tparam BufferFactory Callable object or lambda.
|
||||
* Takes the requested buffer size and returns allocated buffer pointer.
|
||||
* @param in Data to compress
|
||||
* @param inSize Size of the data
|
||||
* @param bf Compressed buffer allocator
|
||||
* @return Size of compressed data, or zero if failed to compress
|
||||
*/
|
||||
template<typename BufferFactory>
|
||||
std::size_t
|
||||
lz4Compress(void const* in,
|
||||
std::size_t inSize, BufferFactory&& bf)
|
||||
{
|
||||
if (inSize > UINT32_MAX)
|
||||
doThrow("lz4 compress: invalid size");
|
||||
|
||||
auto const outCapacity = LZ4_compressBound(inSize);
|
||||
|
||||
// Request the caller to allocate and return the buffer to hold compressed data
|
||||
auto compressed = bf(outCapacity);
|
||||
|
||||
auto compressedSize = LZ4_compress_default(
|
||||
reinterpret_cast<const char*>(in),
|
||||
reinterpret_cast<char*>(compressed),
|
||||
inSize,
|
||||
outCapacity);
|
||||
if (compressedSize == 0)
|
||||
doThrow("lz4 compress: failed");
|
||||
|
||||
return compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param in Compressed data
|
||||
* @param inSize Size of compressed data
|
||||
* @param decompressed Buffer to hold decompressed data
|
||||
* @param decompressedSize Size of the decompressed buffer
|
||||
* @return size of the decompressed data
|
||||
*/
|
||||
inline
|
||||
std::size_t
|
||||
lz4Decompress(std::uint8_t const* in, std::size_t inSize,
|
||||
std::uint8_t* decompressed, std::size_t decompressedSize)
|
||||
{
|
||||
auto ret = LZ4_decompress_safe(reinterpret_cast<const char*>(in),
|
||||
reinterpret_cast<char*>(decompressed), inSize, decompressedSize);
|
||||
|
||||
if (ret <= 0 || ret != decompressedSize)
|
||||
doThrow("lz4 decompress: failed");
|
||||
|
||||
return decompressedSize;
|
||||
}
|
||||
|
||||
/** LZ4 block decompression.
|
||||
* @tparam InputStream ZeroCopyInputStream
|
||||
* @param in Input source stream
|
||||
* @param inSize Size of compressed data
|
||||
* @param decompressed Buffer to hold decompressed data
|
||||
* @param decompressedSize Size of the decompressed buffer
|
||||
* @return size of the decompressed data
|
||||
*/
|
||||
template<typename InputStream>
|
||||
std::size_t
|
||||
lz4Decompress(InputStream& in, std::size_t inSize,
|
||||
std::uint8_t* decompressed, std::size_t decompressedSize)
|
||||
{
|
||||
std::vector<std::uint8_t> compressed;
|
||||
std::uint8_t const* chunk = nullptr;
|
||||
int chunkSize = 0;
|
||||
int copiedInSize = 0;
|
||||
auto const currentBytes = in.ByteCount();
|
||||
|
||||
// Use the first chunk if it is >= inSize bytes of the compressed message.
|
||||
// Otherwise copy inSize bytes of chunks into compressed buffer and
|
||||
// use the buffer to decompress.
|
||||
while (in.Next(reinterpret_cast<void const**>(&chunk), &chunkSize))
|
||||
{
|
||||
if (copiedInSize == 0)
|
||||
{
|
||||
if (chunkSize >= inSize)
|
||||
{
|
||||
copiedInSize = inSize;
|
||||
break;
|
||||
}
|
||||
compressed.resize(inSize);
|
||||
}
|
||||
|
||||
chunkSize = chunkSize < (inSize - copiedInSize) ? chunkSize : (inSize - copiedInSize);
|
||||
|
||||
std::copy(chunk, chunk + chunkSize, compressed.data() + copiedInSize);
|
||||
|
||||
copiedInSize += chunkSize;
|
||||
|
||||
if (copiedInSize == inSize)
|
||||
{
|
||||
chunk = compressed.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Put back unused bytes
|
||||
if (in.ByteCount() > (currentBytes + copiedInSize))
|
||||
in.BackUp(in.ByteCount() - currentBytes - copiedInSize);
|
||||
|
||||
if ((copiedInSize == 0 && chunkSize < inSize) || (copiedInSize > 0 && copiedInSize != inSize))
|
||||
doThrow("lz4 decompress: insufficient input size");
|
||||
|
||||
return lz4Decompress(chunk, inSize, decompressed, decompressedSize);
|
||||
}
|
||||
|
||||
} // compression
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif //RIPPLED_COMPRESSIONALGORITHMS_H_INCLUDED
|
||||
@@ -171,6 +171,9 @@ public:
|
||||
std::string SSL_VERIFY_FILE;
|
||||
std::string SSL_VERIFY_DIR;
|
||||
|
||||
// Compression
|
||||
bool COMPRESSION = false;
|
||||
|
||||
// Thread pool configuration
|
||||
std::size_t WORKERS = 0;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ struct ConfigSection
|
||||
// VFALCO TODO Rename and replace these macros with variables.
|
||||
#define SECTION_AMENDMENTS "amendments"
|
||||
#define SECTION_CLUSTER_NODES "cluster_nodes"
|
||||
#define SECTION_COMPRESSION "compression"
|
||||
#define SECTION_DEBUG_LOGFILE "debug_logfile"
|
||||
#define SECTION_ELB_SUPPORT "elb_support"
|
||||
#define SECTION_FEE_DEFAULT "fee_default"
|
||||
|
||||
@@ -454,6 +454,9 @@ void Config::loadFromString (std::string const& fileContents)
|
||||
if (getSingleSection (secConfig, SECTION_WORKERS, strTemp, j_))
|
||||
WORKERS = beast::lexicalCastThrow <std::size_t> (strTemp);
|
||||
|
||||
if (getSingleSection (secConfig, SECTION_COMPRESSION, strTemp, j_))
|
||||
COMPRESSION = beast::lexicalCastThrow <bool> (strTemp);
|
||||
|
||||
// Do not load trusted validator configuration for standalone mode
|
||||
if (! RUN_STANDALONE)
|
||||
{
|
||||
|
||||
103
src/ripple/overlay/Compression.h
Normal file
103
src/ripple/overlay/Compression.h
Normal file
@@ -0,0 +1,103 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2020 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLED_COMPRESSION_H_INCLUDED
|
||||
#define RIPPLED_COMPRESSION_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/CompressionAlgorithms.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <lz4frame.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace compression {
|
||||
|
||||
std::size_t constexpr headerBytes = 6;
|
||||
std::size_t constexpr headerBytesCompressed = 10;
|
||||
|
||||
enum class Algorithm : std::uint8_t {
|
||||
None = 0x00,
|
||||
LZ4 = 0x01
|
||||
};
|
||||
|
||||
enum class Compressed : std::uint8_t {
|
||||
On,
|
||||
Off
|
||||
};
|
||||
|
||||
/** Decompress input stream.
|
||||
* @tparam InputStream ZeroCopyInputStream
|
||||
* @param in Input source stream
|
||||
* @param inSize Size of compressed data
|
||||
* @param decompressed Buffer to hold decompressed message
|
||||
* @param algorithm Compression algorithm type
|
||||
* @return Size of decompressed data or zero if failed to decompress
|
||||
*/
|
||||
template<typename InputStream>
|
||||
std::size_t
|
||||
decompress(InputStream& in, std::size_t inSize, std::uint8_t* decompressed,
|
||||
std::size_t decompressedSize, Algorithm algorithm = Algorithm::LZ4) {
|
||||
try
|
||||
{
|
||||
if (algorithm == Algorithm::LZ4)
|
||||
return ripple::compression_algorithms::lz4Decompress(in, inSize,
|
||||
decompressed, decompressedSize);
|
||||
else
|
||||
{
|
||||
JLOG(debugLog().warn()) << "decompress: invalid compression algorithm "
|
||||
<< static_cast<int>(algorithm);
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Compress input data.
|
||||
* @tparam BufferFactory Callable object or lambda.
|
||||
* Takes the requested buffer size and returns allocated buffer pointer.
|
||||
* @param in Data to compress
|
||||
* @param inSize Size of the data
|
||||
* @param bf Compressed buffer allocator
|
||||
* @param algorithm Compression algorithm type
|
||||
* @return Size of compressed data, or zero if failed to compress
|
||||
*/
|
||||
template<class BufferFactory>
|
||||
std::size_t
|
||||
compress(void const* in,
|
||||
std::size_t inSize, BufferFactory&& bf, Algorithm algorithm = Algorithm::LZ4) {
|
||||
try
|
||||
{
|
||||
if (algorithm == Algorithm::LZ4)
|
||||
return ripple::compression_algorithms::lz4Compress(in, inSize, std::forward<BufferFactory>(bf));
|
||||
else
|
||||
{
|
||||
JLOG(debugLog().warn()) << "compress: invalid compression algorithm"
|
||||
<< static_cast<int>(algorithm);
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
catch (...) {}
|
||||
return 0;
|
||||
}
|
||||
} // compression
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif //RIPPLED_COMPRESSION_H_INCLUDED
|
||||
@@ -20,6 +20,7 @@
|
||||
#ifndef RIPPLE_OVERLAY_MESSAGE_H_INCLUDED
|
||||
#define RIPPLE_OVERLAY_MESSAGE_H_INCLUDED
|
||||
|
||||
#include <ripple/overlay/Compression.h>
|
||||
#include <ripple/protocol/messages.h>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/buffers_iterator.hpp>
|
||||
@@ -47,27 +48,61 @@ namespace ripple {
|
||||
|
||||
class Message : public std::enable_shared_from_this <Message>
|
||||
{
|
||||
using Compressed = compression::Compressed;
|
||||
using Algorithm = compression::Algorithm;
|
||||
public:
|
||||
/** Constructor
|
||||
* @param message Protocol message to serialize
|
||||
* @param type Protocol message type
|
||||
*/
|
||||
Message (::google::protobuf::Message const& message, int type);
|
||||
|
||||
public:
|
||||
/** Retrieve the packed message data. */
|
||||
/** Retrieve the packed message data. If compressed message is requested but the message
|
||||
* is not compressible then the uncompressed buffer is returned.
|
||||
* @param compressed Request compressed (Compress::On) or
|
||||
* uncompressed (Compress::Off) payload buffer
|
||||
* @return Payload buffer
|
||||
*/
|
||||
std::vector <uint8_t> const&
|
||||
getBuffer () const
|
||||
{
|
||||
return mBuffer;
|
||||
}
|
||||
getBuffer (Compressed tryCompressed);
|
||||
|
||||
/** Get the traffic category */
|
||||
std::size_t
|
||||
getCategory () const
|
||||
{
|
||||
return mCategory;
|
||||
return category_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector <uint8_t> mBuffer;
|
||||
std::size_t mCategory;
|
||||
std::vector <uint8_t> buffer_;
|
||||
std::vector <uint8_t> bufferCompressed_;
|
||||
std::size_t category_;
|
||||
std::once_flag once_flag_;
|
||||
|
||||
/** Set the payload header
|
||||
* @param in Pointer to the payload
|
||||
* @param payloadBytes Size of the payload excluding the header size
|
||||
* @param type Protocol message type
|
||||
* @param comprAlgorithm Compression algorithm used in compression,
|
||||
* currently LZ4 only. If None then the message is uncompressed.
|
||||
* @param uncompressedBytes Size of the uncompressed message
|
||||
*/
|
||||
void setHeader(std::uint8_t* in, std::uint32_t payloadBytes, int type,
|
||||
Algorithm comprAlgorithm, std::uint32_t uncompressedBytes);
|
||||
|
||||
/** Try to compress the payload.
|
||||
* Can be called concurrently by multiple peers but is compressed once.
|
||||
* If the message is not compressible then the serialized buffer_ is used.
|
||||
*/
|
||||
void compress();
|
||||
|
||||
/** Get the message type from the payload header.
|
||||
* First four bytes are the compression/algorithm flag and the payload size.
|
||||
* Next two bytes are the message type
|
||||
* @param in Payload header pointer
|
||||
* @return Message type
|
||||
*/
|
||||
int getType(std::uint8_t const* in) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ ConnectAttempt::onHandshake (error_code ec)
|
||||
if (! sharedValue)
|
||||
return close(); // makeSharedValue logs
|
||||
|
||||
req_ = makeRequest(!overlay_.peerFinder().config().peerPrivate);
|
||||
req_ = makeRequest(!overlay_.peerFinder().config().peerPrivate, app_.config().COMPRESSION);
|
||||
|
||||
buildHandshake(req_, *sharedValue, overlay_.setup().networkID,
|
||||
overlay_.setup().public_ip, remote_endpoint_.address(), app_);
|
||||
@@ -264,7 +264,7 @@ ConnectAttempt::onShutdown (error_code ec)
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
auto
|
||||
ConnectAttempt::makeRequest (bool crawl) -> request_type
|
||||
ConnectAttempt::makeRequest (bool crawl, bool compressionEnabled) -> request_type
|
||||
{
|
||||
request_type m;
|
||||
m.method(boost::beast::http::verb::get);
|
||||
@@ -275,6 +275,8 @@ ConnectAttempt::makeRequest (bool crawl) -> request_type
|
||||
m.insert ("Connection", "Upgrade");
|
||||
m.insert ("Connect-As", "Peer");
|
||||
m.insert ("Crawl", crawl ? "public" : "private");
|
||||
if (compressionEnabled)
|
||||
m.insert("X-Offer-Compression", "lz4");
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ private:
|
||||
|
||||
static
|
||||
request_type
|
||||
makeRequest (bool crawl);
|
||||
makeRequest (bool crawl, bool compressionEnabled);
|
||||
|
||||
void processResponse();
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/basics/safe_cast.h>
|
||||
#include <ripple/overlay/Message.h>
|
||||
#include <ripple/overlay/impl/TrafficCount.h>
|
||||
#include <cstdint>
|
||||
@@ -25,8 +24,9 @@
|
||||
namespace ripple {
|
||||
|
||||
Message::Message (::google::protobuf::Message const& message, int type)
|
||||
: mCategory(TrafficCount::categorize(message, type, false))
|
||||
: category_(TrafficCount::categorize(message, type, false))
|
||||
{
|
||||
using namespace ripple::compression;
|
||||
|
||||
#if defined(GOOGLE_PROTOBUF_VERSION) && (GOOGLE_PROTOBUF_VERSION >= 3011000)
|
||||
auto const messageBytes = message.ByteSizeLong ();
|
||||
@@ -36,23 +36,129 @@ Message::Message (::google::protobuf::Message const& message, int type)
|
||||
|
||||
assert (messageBytes != 0);
|
||||
|
||||
/** Number of bytes in a message header. */
|
||||
std::size_t constexpr headerBytes = 6;
|
||||
buffer_.resize (headerBytes + messageBytes);
|
||||
|
||||
mBuffer.resize (headerBytes + messageBytes);
|
||||
|
||||
auto ptr = mBuffer.data();
|
||||
|
||||
*ptr++ = static_cast<std::uint8_t>((messageBytes >> 24) & 0xFF);
|
||||
*ptr++ = static_cast<std::uint8_t>((messageBytes >> 16) & 0xFF);
|
||||
*ptr++ = static_cast<std::uint8_t>((messageBytes >> 8) & 0xFF);
|
||||
*ptr++ = static_cast<std::uint8_t>(messageBytes & 0xFF);
|
||||
|
||||
*ptr++ = static_cast<std::uint8_t>((type >> 8) & 0xFF);
|
||||
*ptr++ = static_cast<std::uint8_t> (type & 0xFF);
|
||||
setHeader(buffer_.data(), messageBytes, type, Algorithm::None, 0);
|
||||
|
||||
if (messageBytes != 0)
|
||||
message.SerializeToArray(ptr, messageBytes);
|
||||
message.SerializeToArray(buffer_.data() + headerBytes, messageBytes);
|
||||
}
|
||||
|
||||
void
|
||||
Message::compress()
|
||||
{
|
||||
using namespace ripple::compression;
|
||||
auto const messageBytes = buffer_.size () - headerBytes;
|
||||
|
||||
auto type = getType(buffer_.data());
|
||||
|
||||
bool const compressible = [&]{
|
||||
if (messageBytes <= 70)
|
||||
return false;
|
||||
switch(type)
|
||||
{
|
||||
case protocol::mtMANIFESTS:
|
||||
case protocol::mtENDPOINTS:
|
||||
case protocol::mtTRANSACTION:
|
||||
case protocol::mtGET_LEDGER:
|
||||
case protocol::mtLEDGER_DATA:
|
||||
case protocol::mtGET_OBJECTS:
|
||||
case protocol::mtVALIDATORLIST:
|
||||
return true;
|
||||
case protocol::mtPING:
|
||||
case protocol::mtCLUSTER:
|
||||
case protocol::mtPROPOSE_LEDGER:
|
||||
case protocol::mtSTATUS_CHANGE:
|
||||
case protocol::mtHAVE_SET:
|
||||
case protocol::mtVALIDATION:
|
||||
case protocol::mtGET_SHARD_INFO:
|
||||
case protocol::mtSHARD_INFO:
|
||||
case protocol::mtGET_PEER_SHARD_INFO:
|
||||
case protocol::mtPEER_SHARD_INFO:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
|
||||
if (compressible)
|
||||
{
|
||||
auto payload = static_cast<void const*>(buffer_.data() + headerBytes);
|
||||
|
||||
auto compressedSize = ripple::compression::compress(
|
||||
payload,
|
||||
messageBytes,
|
||||
[&](std::size_t inSize) { // size of required compressed buffer
|
||||
bufferCompressed_.resize(inSize + headerBytesCompressed);
|
||||
return (bufferCompressed_.data() + headerBytesCompressed);
|
||||
});
|
||||
|
||||
if (compressedSize < (messageBytes - (headerBytesCompressed - headerBytes)))
|
||||
{
|
||||
bufferCompressed_.resize(headerBytesCompressed + compressedSize);
|
||||
setHeader(bufferCompressed_.data(), compressedSize, type, Algorithm::LZ4, messageBytes);
|
||||
}
|
||||
else
|
||||
bufferCompressed_.resize(0);
|
||||
}
|
||||
}
|
||||
|
||||
/** Set payload header
|
||||
* Uncompressed message header
|
||||
* 47-42 Set to 0
|
||||
* 41-16 Payload size
|
||||
* 15-0 Message Type
|
||||
* Compressed message header
|
||||
* 79 Set to 0, indicates the message is compressed
|
||||
* 78-76 Compression algorithm, value 1-7. Set to 1 to indicate LZ4 compression
|
||||
* 75-74 Set to 0
|
||||
* 73-48 Payload size
|
||||
* 47-32 Message Type
|
||||
* 31-0 Uncompressed message size
|
||||
*/
|
||||
void
|
||||
Message::setHeader(std::uint8_t* in, std::uint32_t payloadBytes, int type,
|
||||
Algorithm comprAlgorithm, std::uint32_t uncompressedBytes)
|
||||
{
|
||||
auto h = in;
|
||||
|
||||
auto pack = [](std::uint8_t*& in, std::uint32_t size) {
|
||||
*in++ = static_cast<std::uint8_t>((size >> 24) & 0x0F); // leftmost 4 are compression bits
|
||||
*in++ = static_cast<std::uint8_t>((size >> 16) & 0xFF);
|
||||
*in++ = static_cast<std::uint8_t>((size >> 8) & 0xFF);
|
||||
*in++ = static_cast<std::uint8_t>(size & 0xFF);
|
||||
};
|
||||
|
||||
pack(in, payloadBytes);
|
||||
|
||||
*in++ = static_cast<std::uint8_t>((type >> 8) & 0xFF);
|
||||
*in++ = static_cast<std::uint8_t> (type & 0xFF);
|
||||
|
||||
if (comprAlgorithm != Algorithm::None)
|
||||
{
|
||||
pack(in, uncompressedBytes);
|
||||
*h |= 0x80 | (static_cast<uint8_t>(comprAlgorithm) << 4);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector <uint8_t> const&
|
||||
Message::getBuffer (Compressed tryCompressed)
|
||||
{
|
||||
if (tryCompressed == Compressed::Off)
|
||||
return buffer_;
|
||||
|
||||
std::call_once(once_flag_, &Message::compress, this);
|
||||
|
||||
if (bufferCompressed_.size() > 0)
|
||||
return bufferCompressed_;
|
||||
else
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
int
|
||||
Message::getType(std::uint8_t const* in) const
|
||||
{
|
||||
int type = (static_cast<int>(*(in + 4)) << 8) + *(in + 5);
|
||||
return type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ PeerImp::PeerImp (Application& app, id_t id,
|
||||
, slot_ (slot)
|
||||
, request_(std::move(request))
|
||||
, headers_(request_)
|
||||
, compressionEnabled_(headers_["X-Offer-Compression"] == "lz4" ? Compressed::On : Compressed::Off)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -219,7 +220,7 @@ PeerImp::send (std::shared_ptr<Message> const& m)
|
||||
|
||||
overlay_.reportTraffic (
|
||||
safe_cast<TrafficCount::category>(m->getCategory()),
|
||||
false, static_cast<int>(m->getBuffer().size()));
|
||||
false, static_cast<int>(m->getBuffer(compressionEnabled_).size()));
|
||||
|
||||
auto sendq_size = send_queue_.size();
|
||||
|
||||
@@ -246,7 +247,7 @@ PeerImp::send (std::shared_ptr<Message> const& m)
|
||||
|
||||
boost::asio::async_write(
|
||||
stream_,
|
||||
boost::asio::buffer(send_queue_.front()->getBuffer()),
|
||||
boost::asio::buffer(send_queue_.front()->getBuffer(compressionEnabled_)),
|
||||
bind_executor(
|
||||
strand_,
|
||||
std::bind(
|
||||
@@ -757,6 +758,8 @@ PeerImp::makeResponse (bool crawl,
|
||||
resp.insert("Connect-As", "Peer");
|
||||
resp.insert("Server", BuildInfo::getFullVersionString());
|
||||
resp.insert("Crawl", crawl ? "public" : "private");
|
||||
if (req["X-Offer-Compression"] == "lz4" && app_.config().COMPRESSION)
|
||||
resp.insert("X-Offer-Compression", "lz4");
|
||||
|
||||
buildHandshake(resp, sharedValue, overlay_.setup().networkID,
|
||||
overlay_.setup().public_ip, remote_ip, app_);
|
||||
@@ -945,7 +948,7 @@ PeerImp::onWriteMessage (error_code ec, std::size_t bytes_transferred)
|
||||
// Timeout on writes only
|
||||
return boost::asio::async_write(
|
||||
stream_,
|
||||
boost::asio::buffer(send_queue_.front()->getBuffer()),
|
||||
boost::asio::buffer(send_queue_.front()->getBuffer(compressionEnabled_)),
|
||||
bind_executor(
|
||||
strand_,
|
||||
std::bind(
|
||||
|
||||
@@ -99,6 +99,7 @@ private:
|
||||
using address_type = boost::asio::ip::address;
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using waitable_timer = boost::asio::basic_waitable_timer<std::chrono::steady_clock>;
|
||||
using Compressed = compression::Compressed;
|
||||
|
||||
Application& app_;
|
||||
id_t const id_;
|
||||
@@ -201,6 +202,8 @@ private:
|
||||
std::mutex mutable shardInfoMutex_;
|
||||
hash_map<PublicKey, ShardInfo> shardInfo_;
|
||||
|
||||
Compressed compressionEnabled_ = Compressed::Off;
|
||||
|
||||
friend class OverlayImpl;
|
||||
|
||||
class Metrics {
|
||||
@@ -600,6 +603,9 @@ PeerImp::PeerImp (Application& app, std::unique_ptr<stream_type>&& stream_ptr,
|
||||
, slot_ (std::move(slot))
|
||||
, response_(std::move(response))
|
||||
, headers_(response_)
|
||||
, compressionEnabled_(
|
||||
headers_["X-Offer-Compression"] == "lz4" && app_.config().COMPRESSION
|
||||
? Compressed::On : Compressed::Off)
|
||||
{
|
||||
read_buffer_.commit (boost::asio::buffer_copy(read_buffer_.prepare(
|
||||
boost::asio::buffer_size(buffers)), buffers));
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <ripple/basics/ByteUtilities.h>
|
||||
#include <ripple/protocol/messages.h>
|
||||
#include <ripple/overlay/Compression.h>
|
||||
#include <ripple/overlay/Message.h>
|
||||
#include <ripple/overlay/impl/ZeroCopyStream.h>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
@@ -81,36 +82,66 @@ struct MessageHeader
|
||||
/** The size of the payload on the wire. */
|
||||
std::uint32_t payload_wire_size = 0;
|
||||
|
||||
/** Uncompressed message size if the message is compressed. */
|
||||
std::uint32_t uncompressed_size = 0;
|
||||
|
||||
/** The type of the message. */
|
||||
std::uint16_t message_type = 0;
|
||||
|
||||
/** Indicates which compression algorithm the payload is compressed with.
|
||||
* Currenly only lz4 is supported. If None then the message is not compressed.
|
||||
*/
|
||||
compression::Algorithm algorithm = compression::Algorithm::None;
|
||||
};
|
||||
|
||||
template<typename BufferSequence>
|
||||
auto
|
||||
buffersBegin(BufferSequence const &bufs)
|
||||
{
|
||||
return boost::asio::buffers_iterator<BufferSequence, std::uint8_t>::begin(bufs);
|
||||
}
|
||||
|
||||
template <class BufferSequence>
|
||||
boost::optional<MessageHeader> parseMessageHeader(
|
||||
BufferSequence const& bufs,
|
||||
std::size_t size)
|
||||
{
|
||||
auto iter = boost::asio::buffers_iterator<BufferSequence, std::uint8_t>::begin(bufs);
|
||||
using namespace ripple::compression;
|
||||
auto iter = buffersBegin(bufs);
|
||||
|
||||
MessageHeader hdr;
|
||||
auto const compressed = (*iter & 0x80) == 0x80;
|
||||
|
||||
// Version 1 header: uncompressed payload.
|
||||
// The top six bits of the first byte are 0.
|
||||
if ((*iter & 0xFC) == 0)
|
||||
// Check valid header
|
||||
if ((*iter & 0xFC) == 0 || compressed)
|
||||
{
|
||||
hdr.header_size = 6;
|
||||
hdr.header_size = compressed ? headerBytesCompressed : headerBytes;
|
||||
|
||||
if (size < hdr.header_size)
|
||||
return {};
|
||||
|
||||
if (compressed)
|
||||
{
|
||||
uint8_t algorithm = (*iter & 0x70) >> 4;
|
||||
if (algorithm != static_cast<std::uint8_t>(compression::Algorithm::LZ4))
|
||||
return {};
|
||||
hdr.algorithm = compression::Algorithm::LZ4;
|
||||
}
|
||||
|
||||
for (int i = 0; i != 4; ++i)
|
||||
hdr.payload_wire_size = (hdr.payload_wire_size << 8) + *iter++;
|
||||
// clear the compression bits
|
||||
hdr.payload_wire_size &= 0x03FFFFFF;
|
||||
|
||||
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++;
|
||||
|
||||
if (compressed)
|
||||
for (int i = 0; i != 4; ++i)
|
||||
hdr.uncompressed_size = (hdr.uncompressed_size << 8) + *iter++;
|
||||
|
||||
return hdr;
|
||||
}
|
||||
|
||||
@@ -130,7 +161,22 @@ invoke (
|
||||
ZeroCopyInputStream<Buffers> stream(buffers);
|
||||
stream.Skip(header.header_size);
|
||||
|
||||
if (! m->ParseFromZeroCopyStream(&stream))
|
||||
if (header.algorithm != compression::Algorithm::None)
|
||||
{
|
||||
std::vector<std::uint8_t> payload;
|
||||
payload.resize(header.uncompressed_size);
|
||||
|
||||
auto payloadSize = ripple::compression::decompress(
|
||||
stream,
|
||||
header.payload_wire_size,
|
||||
payload.data(),
|
||||
header.uncompressed_size,
|
||||
header.algorithm);
|
||||
|
||||
if (payloadSize == 0 || !m->ParseFromArray(payload.data(), payloadSize))
|
||||
return false;
|
||||
}
|
||||
else if (!m->ParseFromZeroCopyStream(&stream))
|
||||
return false;
|
||||
|
||||
handler.onMessageBegin (header.message_type, m, header.payload_wire_size);
|
||||
|
||||
Reference in New Issue
Block a user