From 3d769c7a644895877345ffbb001b2309128370ca Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 3 Oct 2024 20:01:19 +0000 Subject: [PATCH] merge bitcoin#23249: ParseByteUnits - Parse a string with suffix unit --- doc/release-notes-6296.md | 6 ++++ src/init.cpp | 10 ++++-- src/net.h | 2 +- src/test/util_tests.cpp | 48 +++++++++++++++++++++++++ src/util/strencodings.cpp | 74 +++++++++++++++++++++++++++++++-------- src/util/strencodings.h | 30 ++++++++++++++++ 6 files changed, 153 insertions(+), 17 deletions(-) create mode 100644 doc/release-notes-6296.md diff --git a/doc/release-notes-6296.md b/doc/release-notes-6296.md new file mode 100644 index 0000000000..e3838f990c --- /dev/null +++ b/doc/release-notes-6296.md @@ -0,0 +1,6 @@ +Updated settings +---------------- + +- `-maxuploadtarget` now allows human readable byte units [k|K|m|M|g|G|t|T]. + E.g. `-maxuploadtarget=500g`. No whitespace, +- or fractions allowed. + Default is `M` if no suffix provided. diff --git a/src/init.cpp b/src/init.cpp index d974c39be2..3fd49789df 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -583,7 +583,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-maxreceivebuffer=", strprintf("Maximum per-connection receive buffer, *1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxsendbuffer=", strprintf("Maximum per-connection memory usage for the send buffer, *1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-maxuploadtarget=", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h). Limit does not apply to peers with 'download' permission. 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-maxuploadtarget=", strprintf("Tries to keep outbound traffic under the given target per 24h. Limit does not apply to peers with 'download' permission or blocks created within past week. 0 = no limit (default: %s). Optional suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000 base while uppercase is 1024 base", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-onion=", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2psam=", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2pacceptincoming", strprintf("Whether to accept inbound I2P connections (default: %i). Ignored if -i2psam is not set. Listening for inbound I2P connections is done through the SAM proxy, not by binding to a local address and port.", DEFAULT_I2P_ACCEPT_INCOMING), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -1440,6 +1440,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) { const ArgsManager& args = *Assert(node.args); const CChainParams& chainparams = Params(); + + auto opt_max_upload = ParseByteUnits(args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET), ByteUnit::M); + if (!opt_max_upload) { + return InitError(strprintf(_("Unable to parse -maxuploadtarget: '%s' (possible integer overflow?)"), args.GetArg("-maxuploadtarget", ""))); + } + // ********************************************************* Step 4a: application initialization if (!CreatePidFile(args)) { // Detailed error printed inside CreatePidFile(). @@ -2388,7 +2394,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) connOptions.nSendBufferMaxSize = 1000 * args.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000 * args.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); connOptions.m_added_nodes = args.GetArgs("-addnode"); - connOptions.nMaxOutboundLimit = 1024 * 1024 * args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET); + connOptions.nMaxOutboundLimit = *opt_max_upload; connOptions.m_peer_connect_timeout = peer_connect_timeout; // Port to bind to if `-bind=addr` is provided without a `:port` suffix. diff --git a/src/net.h b/src/net.h index 23988dd1d2..de74b27c54 100644 --- a/src/net.h +++ b/src/net.h @@ -100,7 +100,7 @@ static const bool DEFAULT_LISTEN = true; */ static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125; /** The default for -maxuploadtarget. 0 = Unlimited */ -static constexpr uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0; +static const std::string DEFAULT_MAX_UPLOAD_TARGET{"0M"}; /** Default for blocks only*/ static const bool DEFAULT_BLOCKSONLY = false; /** -peertimeout default */ diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 80d5e80dbe..2f31bf54e2 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -2685,4 +2685,52 @@ BOOST_AUTO_TEST_CASE(remove_prefix) BOOST_CHECK_EQUAL(RemovePrefix("", ""), ""); } +BOOST_AUTO_TEST_CASE(util_ParseByteUnits) +{ + auto noop = ByteUnit::NOOP; + + // no multiplier + BOOST_CHECK_EQUAL(ParseByteUnits("1", noop).value(), 1); + BOOST_CHECK_EQUAL(ParseByteUnits("0", noop).value(), 0); + + BOOST_CHECK_EQUAL(ParseByteUnits("1k", noop).value(), 1000ULL); + BOOST_CHECK_EQUAL(ParseByteUnits("1K", noop).value(), 1ULL << 10); + + BOOST_CHECK_EQUAL(ParseByteUnits("2m", noop).value(), 2'000'000ULL); + BOOST_CHECK_EQUAL(ParseByteUnits("2M", noop).value(), 2ULL << 20); + + BOOST_CHECK_EQUAL(ParseByteUnits("3g", noop).value(), 3'000'000'000ULL); + BOOST_CHECK_EQUAL(ParseByteUnits("3G", noop).value(), 3ULL << 30); + + BOOST_CHECK_EQUAL(ParseByteUnits("4t", noop).value(), 4'000'000'000'000ULL); + BOOST_CHECK_EQUAL(ParseByteUnits("4T", noop).value(), 4ULL << 40); + + // check default multiplier + BOOST_CHECK_EQUAL(ParseByteUnits("5", ByteUnit::K).value(), 5ULL << 10); + + // NaN + BOOST_CHECK(!ParseByteUnits("", noop)); + BOOST_CHECK(!ParseByteUnits("foo", noop)); + + // whitespace + BOOST_CHECK(!ParseByteUnits("123m ", noop)); + BOOST_CHECK(!ParseByteUnits(" 123m", noop)); + + // no +- + BOOST_CHECK(!ParseByteUnits("-123m", noop)); + BOOST_CHECK(!ParseByteUnits("+123m", noop)); + + // zero padding + BOOST_CHECK_EQUAL(ParseByteUnits("020M", noop).value(), 20ULL << 20); + + // fractions not allowed + BOOST_CHECK(!ParseByteUnits("0.5T", noop)); + + // overflow + BOOST_CHECK(!ParseByteUnits("18446744073709551615g", noop)); + + // invalid unit + BOOST_CHECK(!ParseByteUnits("1x", noop)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 4fb69b95ea..be05c9b9d1 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -501,20 +502,6 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out) return true; } -std::string HexStr(const Span s) -{ - std::string rv(s.size() * 2, '\0'); - static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - auto it = rv.begin(); - for (uint8_t v : s) { - *it++ = hexmap[v >> 4]; - *it++ = hexmap[v & 15]; - } - assert(it == rv.end()); - return rv; -} - std::string ToLower(const std::string& str) { std::string r; @@ -535,3 +522,62 @@ std::string Capitalize(std::string str) str[0] = ToUpper(str.front()); return str; } + +std::string HexStr(const Span s) +{ + std::string rv(s.size() * 2, '\0'); + static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + auto it = rv.begin(); + for (uint8_t v : s) { + *it++ = hexmap[v >> 4]; + *it++ = hexmap[v & 15]; + } + assert(it == rv.end()); + return rv; +} + +std::optional ParseByteUnits(const std::string& str, ByteUnit default_multiplier) +{ + if (str.empty()) { + return std::nullopt; + } + auto multiplier = default_multiplier; + char unit = str.back(); + switch (unit) { + case 'k': + multiplier = ByteUnit::k; + break; + case 'K': + multiplier = ByteUnit::K; + break; + case 'm': + multiplier = ByteUnit::m; + break; + case 'M': + multiplier = ByteUnit::M; + break; + case 'g': + multiplier = ByteUnit::g; + break; + case 'G': + multiplier = ByteUnit::G; + break; + case 't': + multiplier = ByteUnit::t; + break; + case 'T': + multiplier = ByteUnit::T; + break; + default: + unit = 0; + break; + } + + uint64_t unit_amount = static_cast(multiplier); + auto parsed_num = ToIntegral(unit ? str.substr(0, str.size() - 1) : str); + if (!parsed_num || parsed_num > std::numeric_limits::max() / unit_amount) { // check overflow + return std::nullopt; + } + return *parsed_num * unit_amount; +} diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 1e1804099c..b7d18bc74e 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -28,6 +28,23 @@ enum SafeChars SAFE_CHARS_URI, //!< Chars allowed in URIs (RFC 3986) }; +/** + * Used by ParseByteUnits() + * Lowercase base 1000 + * Uppercase base 1024 +*/ +enum class ByteUnit : uint64_t { + NOOP = 1ULL, + k = 1000ULL, + K = 1024ULL, + m = 1'000'000ULL, + M = 1ULL << 20, + g = 1'000'000'000ULL, + G = 1ULL << 30, + t = 1'000'000'000'000ULL, + T = 1ULL << 40, +}; + /** * Remove unsafe chars. Safe chars chosen to allow simple messages/URLs/email * addresses, but avoid anything even possibly remotely dangerous like & or > @@ -312,4 +329,17 @@ std::string ToUpper(const std::string& str); */ std::string Capitalize(std::string str); +/** + * Parse a string with suffix unit [k|K|m|M|g|G|t|T]. + * Must be a whole integer, fractions not allowed (0.5t), no whitespace or +- + * Lowercase units are 1000 base. Uppercase units are 1024 base. + * Examples: 2m,27M,19g,41T + * + * @param[in] str the string to convert into bytes + * @param[in] default_multiplier if no unit is found in str use this unit + * @returns optional uint64_t bytes from str or nullopt + * if ToIntegral is false, str is empty, trailing whitespace or overflow + */ +std::optional ParseByteUnits(const std::string& str, ByteUnit default_multiplier); + #endif // BITCOIN_UTIL_STRENCODINGS_H