diff --git a/cfg/xrpld-example.cfg b/cfg/xrpld-example.cfg index 6a44561c68..747bafe077 100644 --- a/cfg/xrpld-example.cfg +++ b/cfg/xrpld-example.cfg @@ -549,6 +549,45 @@ # only be used for local testing and debugging. Do not disable # on mainnet. # +# max_untrusted_count = +# +# The number of manifests the server keeps for validators it does not +# list, and the number it sends and processes in a single peer protocol +# message. Once the server holds this many, a manifest for a new +# unlisted validator is rejected, so peer gossip cannot grow the cache +# without end. +# +# This option can take any value between 50 and 1000, inclusive. If +# the option is not present the server uses its built-in value. +# +# The current default (which is subject to change) is 300. +# +# max_trusted_count = +# +# The number of manifests for listed validators to allow for when +# sizing peer protocol messages. Manifests for listed validators are +# never dropped, whether sending or receiving, because doing so would +# delay a validator key change reaching this server. Set this above the +# number of validators the server lists. +# +# Together the two counts above set the largest manifest message the +# server accepts: bigger messages are discarded without reading them, +# and without penalising the sender. Raising either means the server +# accepts and sends bigger messages than a peer using the defaults, and +# those peers will discard what this server sends. Lowering either below +# what peers send makes this server discard their manifest messages, +# which it does without recording anything. +# +# This option can take any value between 50 and 1000, inclusive. If +# the option is not present the server uses its built-in value. +# +# The current default (which is subject to change) is 300. +# +# NOTE: These two options (max_untrusted_count and max_trusted_count) +# are transitional. They exist to bound manifest-message size and cache +# growth during the network upgrade. They may be removed in a future +# release once the fleet has upgraded, and should not be relied upon as +# stable configuration. # # [transaction_queue] EXPERIMENTAL # diff --git a/include/xrpl/config/Constants.h b/include/xrpl/config/Constants.h index 0fe4efee63..85d9e3f147 100644 --- a/include/xrpl/config/Constants.h +++ b/include/xrpl/config/Constants.h @@ -119,7 +119,9 @@ struct Keys static constexpr auto kLogInterval = "log_interval"; static constexpr auto kMaxDivergedTime = "max_diverged_time"; static constexpr auto kMaxLedgerCountsToStore = "max_ledger_counts_to_store"; + static constexpr auto kMaxTrustedCount = "max_trusted_count"; static constexpr auto kMaxUnknownTime = "max_unknown_time"; + static constexpr auto kMaxUntrustedCount = "max_untrusted_count"; static constexpr auto kMaximumTxnInLedger = "maximum_txn_in_ledger"; static constexpr auto kMaximumTxnPerAccount = "maximum_txn_per_account"; static constexpr auto kMemoryLevel = "memory_level"; diff --git a/include/xrpl/server/Manifest.h b/include/xrpl/server/Manifest.h index 2de5bf4752..786967b057 100644 --- a/include/xrpl/server/Manifest.h +++ b/include/xrpl/server/Manifest.h @@ -201,21 +201,67 @@ constexpr std::size_t kMaxManifestBytes = 358; constexpr std::size_t kMaxManifestBase64 = base64::encodedSize(kMaxManifestBytes); /** - * Maximum number of manifests carried in a single TMManifests message. + * Default number of untrusted manifests to store in cache and allowed + * in one Manifest message. * - * Outbound, the TMManifests message sent to a peer includes every trusted - * manifest and fills the rest of this budget with untrusted gossip, so it - * never exceeds this size. Inbound, trusted manifests are always processed - * and untrusted ones are processed up to this many, so a peer sending its - * whole cache cannot force unbounded work. + * Bounds unlisted validators two ways. In the cache, a manifest for a + * brand-new unlisted key is rejected once this many are held, so peer gossip + * cannot grow the cache without end. In a TMManifests message, this many are + * sent and processed, so a peer sending its whole cache cannot force unbounded + * work. * - * The trusted set is tiny relative to this bound, so trusted manifests are - * not dropped in practice. This is a transitional per-message cap; the cache - * already bounds untrusted manifests (see kMaxUntrustedCount), so it is no - * longer needed once the network has upgraded past nodes that send their - * whole cache in one message. + * Operators can override this with `[overlay] max_untrusted_count`. Both users + * read the configured value and fall back to this default. */ -constexpr std::size_t kMaxManifestsPerMessage = 200; +constexpr std::size_t kMaxUntrustedCount = 300; + +/** + * Default number of trusted manifests allowed in a Manifest message. + * Not used atm while creating the message, but used to calculate the higher limit on + * received message size. Introduced to maintain consistency. Future implementation + * will use this limit. + * + * Trusted manifests are never dropped: every one this node holds is sent, and + * every one received is processed, since dropping one would delay a validator + * key rotation. This count only sizes the largest message accepted, so it must + * stay above any realistic validator list. Cap can be increased in the config + * file if messages get rejected with actual trusted manifest count crossing + * configured(or else default) value. + * Operators can override this with `[overlay] max_trusted_count`. + */ +constexpr std::size_t kMaxTrustedCount = 300; + +/** + * Number of untrusted manifests to store in cache and allowed + * in one Manifest message.. + * + * Returns the operator's override when one is configured, otherwise + * @ref kMaxUntrustedCount. Config stores an override rather than the default + * itself because the core module cannot depend on this module. + * + * @param configured The value from `[overlay] max_untrusted_count`, or + * `std::nullopt` when the operator did not set it. + */ +constexpr std::size_t +untrustedManifestCount(std::optional const& configured) +{ + return configured.value_or(kMaxUntrustedCount); +} + +/** + * Number of trusted manifests allowed in a Manifest message. + * + * Not a cap on how many are sent or processed; see @ref kMaxTrustedCount. + * but used to calculate the higher limit on received message size. + * + * @param configured The value from `[overlay] max_trusted_count`, or + * `std::nullopt` when the operator did not set it. + */ +constexpr std::size_t +trustedManifestCount(std::optional const& configured) +{ + return configured.value_or(kMaxTrustedCount); +} /** * Constructs Manifest from serialized string @@ -361,9 +407,10 @@ private: /** * Maximum number of untrusted master keys kept in the cache. * - * Once reached, a manifest for a brand-new unlisted key is rejected. + * Once reached, a manifest for a brand-new unlisted key is rejected. Set + * from the config, defaulting to @ref kMaxUntrustedCount. */ - static constexpr std::size_t kMaxUntrustedCount = 100; + std::size_t const maxUntrustedCount_; /** * Running count of manifests rejected because the untrusted cap was full. @@ -381,7 +428,17 @@ private: static constexpr std::uint64_t kUntrustedRejectCount = 10000; public: - explicit ManifestCache(beast::Journal j = beast::Journal(beast::Journal::getNullSink())) : j_(j) + /** + * @param j Journal for logging. + * + * @param maxUntrustedCount Untrusted master keys to keep. Pass the + * configured value; defaults to @ref kMaxUntrustedCount. Taken as a + * parameter because this module cannot depend on the config. + */ + explicit ManifestCache( + beast::Journal j = beast::Journal(beast::Journal::getNullSink()), + std::size_t maxUntrustedCount = kMaxUntrustedCount) + : j_(j), maxUntrustedCount_(maxUntrustedCount) { } diff --git a/src/libxrpl/server/Manifest.cpp b/src/libxrpl/server/Manifest.cpp index 3da3f9e9cd..0760196a3b 100644 --- a/src/libxrpl/server/Manifest.cpp +++ b/src/libxrpl/server/Manifest.cpp @@ -487,7 +487,7 @@ ManifestCache::applyManifest(Manifest m, ManifestRateLimitCapPolicy const cap) lock.owns_lock(), "xrpl::ManifestCache::applyManifest::atUntrustedCap : locked"); (void)lock; // not used. parameter is present to ensure the mutex is // locked when the lambda is called. - if (iter == map_.end() && !uncapped && untrustedKeys_.size() >= kMaxUntrustedCount) + if (iter == map_.end() && !uncapped && untrustedKeys_.size() >= maxUntrustedCount_) { // Log each rejection at debug, but warn only once per interval so a // flood does not fill the log. diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index e98a0e1e88..ac5471fd3c 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -1575,6 +1575,87 @@ r.ripple.com:51235 // Above upper bound BEAST_EXPECT(!testDiverged("901")); + + testcase("overlay: manifest counts"); + + // Both keys share one range and one parse path, so exercise each + // through the same helper. + auto testCount = [](std::string const& key, + std::string const& value) -> std::optional { + try + { + Config c; + c.loadFromString("[overlay]\n" + key + "=" + value); + return key == "max_trusted_count" ? c.maxTrustedCount : c.maxUntrustedCount; + } + catch (std::runtime_error const&) + { + return {}; + } + }; + + for (auto const* key : {"max_untrusted_count", "max_trusted_count"}) + { + // Failures. A bad value must surface as std::runtime_error, not + // the std::bad_cast that the underlying parse throws. + BEAST_EXPECT(!testCount(key, "none")); + BEAST_EXPECT(!testCount(key, "0.5")); + BEAST_EXPECT(!testCount(key, "400 manifests")); + BEAST_EXPECT(!testCount(key, "-1")); + + // Below lower bound + BEAST_EXPECT(!testCount(key, "0")); + BEAST_EXPECT(!testCount(key, "49")); + + // In bounds + BEAST_EXPECT(testCount(key, "50") == 50); + BEAST_EXPECT(testCount(key, "51") == 51); + BEAST_EXPECT(testCount(key, "300") == 300); + BEAST_EXPECT(testCount(key, "400") == 400); + BEAST_EXPECT(testCount(key, "999") == 999); + BEAST_EXPECT(testCount(key, "1000") == 1000); + + // Above upper bound + BEAST_EXPECT(!testCount(key, "1001")); + } + + // Each key is independent: setting one leaves the other unset. + { + Config c; + c.loadFromString("[overlay]\nmax_untrusted_count=500"); + BEAST_EXPECT(c.maxUntrustedCount == 500); + BEAST_EXPECT(!c.maxTrustedCount); + } + { + Config c; + c.loadFromString("[overlay]\nmax_trusted_count=500"); + BEAST_EXPECT(c.maxTrustedCount == 500); + BEAST_EXPECT(!c.maxUntrustedCount); + } + + // Both can be set together. + { + Config c; + c.loadFromString("[overlay]\nmax_untrusted_count=250\nmax_trusted_count=750"); + BEAST_EXPECT(c.maxUntrustedCount == 250); + BEAST_EXPECT(c.maxTrustedCount == 750); + } + + // Unset leaves no override, so the use sites fall back to the defaults. + { + Config c; + c.loadFromString("[overlay]\nip_limit=64"); + BEAST_EXPECT(!c.maxUntrustedCount); + BEAST_EXPECT(!c.maxTrustedCount); + } + + // No [overlay] section at all leaves both unset too. + { + Config c; + c.loadFromString(""); + BEAST_EXPECT(!c.maxUntrustedCount); + BEAST_EXPECT(!c.maxTrustedCount); + } } void diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index d329475874..b0f0226e49 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -91,6 +91,7 @@ #include #include #include +#include #include #include #include @@ -428,8 +429,14 @@ public: , cluster_(std::make_unique(logs_->journal("Overlay"))) , peerReservations_( std::make_unique(logs_->journal("PeerReservationTable"))) - , validatorManifests_(std::make_unique(logs_->journal("ManifestCache"))) - , publisherManifests_(std::make_unique(logs_->journal("ManifestCache"))) + , validatorManifests_( + std::make_unique( + logs_->journal("ManifestCache"), + untrustedManifestCount(config_->maxUntrustedCount))) + , publisherManifests_( + std::make_unique( + logs_->journal("ManifestCache"), + untrustedManifestCount(config_->maxUntrustedCount))) , validators_( std::make_unique( *validatorManifests_, @@ -1190,6 +1197,15 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) JLOG(journal_.info()) << "Process starting: " << BuildInfo::getFullVersionString() << ", Instance Cookie: " << instanceCookie_; + // Log the resolved manifest counts, whether configured or defaulted, so a + // shared log shows what the server is running without needing its config. + JLOG(journal_.warn()) << "Manifest counts: max_untrusted_count " + << untrustedManifestCount(config_->maxUntrustedCount) + << (config_->maxUntrustedCount ? " (configured)" : " (default)") + << ", max_trusted_count " + << trustedManifestCount(config_->maxTrustedCount) + << (config_->maxTrustedCount ? " (configured)" : " (default)"); + if (numberOfThreads(*config_) < 2) { JLOG(journal_.warn()) << "Limited to a single I/O service thread by " diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index a7cb5d053a..2492614825 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -295,6 +295,23 @@ public: // How long can a peer remain in the "diverged" state std::chrono::seconds maxDivergedTime{300}; + // Optional overrides for how many manifests are kept in the cache and + // carried in one TMManifests message, split by whether this node lists the + // validator. Unset means use the built-in defaults (kMaxUntrustedCount and + // kMaxTrustedCount in Manifest.h). Kept as overrides here, rather than the + // defaults themselves, so the core module need not depend on the server + // module that owns the constants. + std::optional maxUntrustedCount; + std::optional maxTrustedCount; + + // Bounds for both counts above. The lower bound leaves room for a small + // network or a deliberately tight limit; note that setting a count below + // what peers actually send means their manifest messages are dropped for + // being oversized. The upper bound keeps the implied message size well + // under the overall protocol message limit. + static constexpr std::size_t kMinManifestCount = 50; + static constexpr std::size_t kMaxManifestCount = 1000; + // Enable the beta API version bool betaRpcApi = false; diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index 616717c5fd..e93ccec56e 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -923,6 +923,38 @@ Config::loadFromString(std::string const& fileContents) std::string("Invalid value 'max_diverged_time' in ") + Sections::kOverlay + ": the time must be between 60 and 900 seconds, inclusive."); } + + // Both manifest counts parse and validate identically, so read them + // the same way. Returns nullopt when the key is absent, leaving the + // built-in default in effect at the use site. + auto manifestCount = [&sec](char const* key) -> std::optional { + std::optional count; + + try + { + if (auto val = sec.get(key)) + count = beast::lexicalCastThrow(*val); + } + catch (...) + { + Throw( + std::string("Invalid value '") + key + "' in " + Sections::kOverlay + + ": must be of the form '' representing a count of manifests."); + } + + if (count && (*count < kMinManifestCount || *count > kMaxManifestCount)) + { + Throw( + std::string("Invalid value '") + key + "' in " + Sections::kOverlay + + ": the count must be between " + std::to_string(kMinManifestCount) + " and " + + std::to_string(kMaxManifestCount) + ", inclusive."); + } + + return count; + }; + + maxUntrustedCount = manifestCount(Keys::kMaxUntrustedCount); + maxTrustedCount = manifestCount(Keys::kMaxTrustedCount); } if (getSingleSection(secConfig, Sections::kAmendmentMajorityTime, strTemp, j_)) diff --git a/src/xrpld/overlay/Message.h b/src/xrpld/overlay/Message.h index bd4772b451..065da696d8 100644 --- a/src/xrpld/overlay/Message.h +++ b/src/xrpld/overlay/Message.h @@ -24,12 +24,37 @@ constexpr std::size_t kMaximumMessageSize = megabytes(64); // so we define a separate limit for them. constexpr std::size_t kMaximumPingMessageSize = kilobytes(1); -// Upper bound on the wire size of a TMManifests message: kMaxManifestsPerMessage entries -// of at most kMaxManifestBytes each, plus a small allowance for protobuf -// framing per entry. +// Allowance for protobuf framing around each manifest in a TMManifests message. constexpr std::size_t kManifestFramingBytes = 8; -constexpr std::size_t kMaximumManifestsMessageSize = - kMaxManifestsPerMessage * (kMaxManifestBytes + kManifestFramingBytes); + +/** + * Upper bound on the wire size of a TMManifests message. + * + * Allows both counts' worth of entries at @ref kMaxManifestBytes each, plus + * framing per entry. Messages larger than this are dropped before parsing, + * which bounds the work an oversized message can cause. + * + * @param trustedCount Trusted manifests per message. + * + * @param untrustedCount Untrusted manifests per message. + * + * @note A node that raises either count accepts larger messages than a peer + * running the defaults, and the messages it sends may be dropped by such a + * peer. Lowering either count below what peers send drops their manifest + * messages, including any trusted key rotations they carry, and the drop + * is not recorded on either side. + */ +constexpr std::size_t +maximumManifestsMessageSize(std::size_t const trustedCount, std::size_t const untrustedCount) +{ + return (trustedCount + untrustedCount) * (kMaxManifestBytes + kManifestFramingBytes); +} + +// The message size the defaults imply must stay within the overall protocol +// message limit. The same check for the largest configurable counts lives in +// OverlayImpl.h, where the configured bound is visible. +static_assert( + maximumManifestsMessageSize(kMaxTrustedCount, kMaxUntrustedCount) < kMaximumMessageSize); // VFALCO NOTE If we forward declare Message and write out shared_ptr // instead of using the in-class type alias, we can remove the diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index 5bac7df720..d1f145a601 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -667,7 +667,10 @@ OverlayImpl::onManifests( auto const& journal = from->pJournal(); // Process every trusted manifest, but stop processing untrusted ones once - // kMaxManifestsPerMessage of them have been handled, so the work stays bounded. + // the configured untrusted count has been handled, so the work stays + // bounded. Trusted manifests are always processed: dropping one would delay + // a validator key rotation reaching this node. + auto const maxUntrusted = untrustedManifestCount(app_.config().maxUntrustedCount); auto const total = static_cast(m->list_size()); std::size_t untrusted = 0; bool skippedUntrusted = false; @@ -686,23 +689,18 @@ OverlayImpl::onManifests( // first avoids holding the two locks in opposite orders. bool const isTrusted = app_.getValidators().listed(mo->masterKey); - // Bound untrusted work: process at most kMaxManifestsPerMessage - // untrusted manifests, but never skip a trusted one. Trusted - // manifests are not counted against the cap. + // Bound untrusted work: process at most maxUntrusted untrusted + // manifests, but never skip a trusted one. Trusted manifests are + // not counted against the cap. if (!isTrusted) { - if (untrusted >= kMaxManifestsPerMessage) + if (untrusted >= maxUntrusted) { skippedUntrusted = true; continue; } ++untrusted; } - // Updates to a known key are relayed even when untrusted. Use - // getSequence, not getManifest, to avoid copying the cached payload - // on this hot path. - bool const isKnown = - app_.getValidatorManifests().getSequence(mo->masterKey).has_value(); auto const result = app_.getValidatorManifests().applyManifest( std::move(*mo), @@ -721,22 +719,17 @@ OverlayImpl::onManifests( "deserialization succeeded"); // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above app_.getOPs().pubManifest(*mo); + // NOLINTEND(bugprone-unchecked-optional-access) + + relay.add_list()->set_stobject(s); - // Relay only trusted manifests or updates to known keys, so - // untrusted gossip for a brand-new key cannot be amplified. // Persist to the wallet DB only for trusted keys, so untrusted // gossip never survives a restart. - if (isTrusted || isKnown) + if (isTrusted) { - relay.add_list()->set_stobject(s); - - if (isTrusted) - { - auto db = app_.getWalletDB().checkoutDb(); - addValidatorManifest(*db, serialized); - } + auto db = app_.getWalletDB().checkoutDb(); + addValidatorManifest(*db, serialized); } - // NOLINTEND(bugprone-unchecked-optional-access) } } else @@ -749,13 +742,12 @@ OverlayImpl::onManifests( if (skippedUntrusted) { // The sender exceeded the untrusted per-message cap. Charge it (once, - // here) so a flood of untrusted manifests is penalized, while an honest - // message of trusted manifests never is. + // here) so a flood of untrusted manifests is penalized. from->charge(Resource::kFeeMalformedRequest, "too many untrusted manifests"); JLOG(journal.warn()) << "Manifests: message had " << total - << " entries; processed all trusted plus the first " - << kMaxManifestsPerMessage << " untrusted"; + << " entries; processed all trusted plus the first " << maxUntrusted + << " untrusted"; } if (!relay.list().empty()) @@ -1283,10 +1275,9 @@ OverlayImpl::getManifestsMessage() }); // Phase 2: no cache lock held, so trust checks are safe. Include every - // trusted manifest, then fill any remaining headroom up to - // kMaxManifestsPerMessage with untrusted gossip, so the whole message - // stays within the per-message cap the receiver enforces (trusted - // count is tiny in practice, so this effectively never drops trusted). + // trusted manifest, then fill any remaining headroom up to the + // configured untrusted count with gossip. Trusted manifests are never + // dropped; the trusted count only sizes the accepted message. std::vector selected; std::vector untrusted; for (auto const& e : cached) @@ -1302,7 +1293,8 @@ OverlayImpl::getManifestsMessage() } // Cap untrusted only; trusted manifests are all included above. - auto const take = std::min(kMaxManifestsPerMessage, untrusted.size()); + auto const take = + std::min(untrustedManifestCount(app_.config().maxUntrustedCount), untrusted.size()); selected.insert(selected.end(), untrusted.begin(), untrusted.begin() + take); // Shuffle the order. Cryptographic randomness is not needed here. diff --git a/src/xrpld/overlay/detail/OverlayImpl.h b/src/xrpld/overlay/detail/OverlayImpl.h index 092ac86a6d..4c8fcd2a00 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.h +++ b/src/xrpld/overlay/detail/OverlayImpl.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -54,6 +55,13 @@ namespace xrpl { +// The largest counts an operator can configure must still imply a message size +// within the overall protocol message limit. The same check for the defaults +// lives in Message.h. +static_assert( + maximumManifestsMessageSize(Config::kMaxManifestCount, Config::kMaxManifestCount) < + kMaximumMessageSize); + class PeerImp; class BasicConfig; diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index 0085927550..720d9a1195 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -464,6 +465,21 @@ public: return compressionEnabled_ == Compressed::On; } + /** + * Largest TMManifests message this node accepts, in bytes. + * + * Read by invokeProtocolMessage to drop oversized messages before + * parsing. Not part of the Peer interface: the message handler is a + * template parameter, so only PeerImp needs to provide this. + */ + [[nodiscard]] std::size_t + maxManifestsMessageSize() const + { + return maximumManifestsMessageSize( + trustedManifestCount(app_.config().maxTrustedCount), + untrustedManifestCount(app_.config().maxUntrustedCount)); + } + bool txReduceRelayEnabled() const override { diff --git a/src/xrpld/overlay/detail/ProtocolMessage.h b/src/xrpld/overlay/detail/ProtocolMessage.h index abd087f3c8..f7d5e26272 100644 --- a/src/xrpld/overlay/detail/ProtocolMessage.h +++ b/src/xrpld/overlay/detail/ProtocolMessage.h @@ -375,13 +375,16 @@ invokeProtocolMessage(Buffers const& buffers, Handler& handler, std::size_t& hin } // Drop an oversized TMManifests without penalty: consume the bytes and - // return no error, so the connection is preserved. - if (header->messageType == protocol::mtMANIFESTS && - (header->payloadWireSize > kMaximumManifestsMessageSize || - header->uncompressedSize > kMaximumManifestsMessageSize)) + // return no error, so the connection is preserved. The limit follows this + // node's configured manifests-per-message count. + if (header->messageType == protocol::mtMANIFESTS) { - result.first = header->totalWireSize; - return result; + auto const maxSize = handler.maxManifestsMessageSize(); + if (header->payloadWireSize > maxSize || header->uncompressedSize > maxSize) + { + result.first = header->totalWireSize; + return result; + } } bool success = false;