Skip to content

Commit 73ac195

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#23249: util: ParseByteUnits - Parse a string with suffix unit
21b58f4 util: ParseByteUnits - Parse a string with suffix unit [k|K|m|M|g|G|t|T] (Douglas Chimento) Pull request description: A convenience utility for parsing human readable strings sizes e.g. `500G` is `500 * 1 << 30` The argument/setting `maxuploadtarget` now accept human readable byte units `[k|K|m|M|g|G||t|T]` This change backward compatible, defaults to `M` if no unit specified. ACKs for top commit: vasild: ACK 21b58f4 ryanofsky: Code review ACK 21b58f4. Only changes since last review are dropping optional has_value call, fixing comment punctuation, squashing commits. Tree-SHA512: c9b85acc0f77c847a0290b27ac5dc586ecc078110cf133063140576a04c11aa9c553159b9b4993488edcf6e60db6837de7c83b2964639bc21e8ffa4d455a5eb7
2 parents 4018e23 + 21b58f4 commit 73ac195

File tree

6 files changed

+138
-4
lines changed

6 files changed

+138
-4
lines changed

doc/release-notes.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ Updated settings
127127
mean `-persistmempool=1`. Passing `-persistmempool=0`, `-persistmempool=1`
128128
and `-nopersistmempool` is unaffected. (#23061)
129129

130+
- `-maxuploadtarget` now allows human readable byte units [k|K|m|M|g|G|t|T].
131+
E.g. `-maxuploadtarget=500g`. No whitespace, +- or fractions allowed.
132+
Default is `M` if no suffix provided. (#23249)
133+
130134
Tools and Utilities
131135
-------------------
132136

src/init.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
#include <util/asmap.h>
6060
#include <util/check.h>
6161
#include <util/moneystr.h>
62+
#include <util/strencodings.h>
6263
#include <util/string.h>
6364
#include <util/syscall_sandbox.h>
6465
#include <util/system.h>
@@ -436,7 +437,7 @@ void SetupServerArgs(ArgsManager& argsman)
436437
argsman.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
437438
argsman.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
438439
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);
439-
argsman.AddArg("-maxuploadtarget=<n>", 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);
440+
argsman.AddArg("-maxuploadtarget=<n>", 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);
440441
argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
441442
argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
442443
argsman.AddArg("-i2pacceptincoming", "If set and -i2psam is also set then incoming I2P connections are accepted via the SAM proxy. If this is not set but -i2psam is set then only outgoing connections will be made to the I2P network. Ignored if -i2psam is not set. Listening for incoming I2P connections is done through the SAM proxy, not by binding to a local address and port (default: 1)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
@@ -1109,6 +1110,12 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
11091110
{
11101111
const ArgsManager& args = *Assert(node.args);
11111112
const CChainParams& chainparams = Params();
1113+
1114+
auto opt_max_upload = ParseByteUnits(args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET), ByteUnit::M);
1115+
if (!opt_max_upload) {
1116+
return InitError(strprintf(_("Unable to parse -maxuploadtarget: '%s' (possible integer overflow?)"), args.GetArg("-maxuploadtarget", "")));
1117+
}
1118+
11121119
// ********************************************************* Step 4a: application initialization
11131120
if (!CreatePidFile(args)) {
11141121
// Detailed error printed inside CreatePidFile().
@@ -1760,8 +1767,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
17601767
connOptions.nSendBufferMaxSize = 1000 * args.GetIntArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER);
17611768
connOptions.nReceiveFloodSize = 1000 * args.GetIntArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER);
17621769
connOptions.m_added_nodes = args.GetArgs("-addnode");
1763-
1764-
connOptions.nMaxOutboundLimit = 1024 * 1024 * args.GetIntArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET);
1770+
connOptions.nMaxOutboundLimit = *opt_max_upload;
17651771
connOptions.m_peer_connect_timeout = peer_connect_timeout;
17661772

17671773
for (const std::string& bind_arg : args.GetArgs("-bind")) {

src/net.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ static const bool DEFAULT_LISTEN = true;
7070
/** The maximum number of peer connections to maintain. */
7171
static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125;
7272
/** The default for -maxuploadtarget. 0 = Unlimited */
73-
static constexpr uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0;
73+
static const std::string DEFAULT_MAX_UPLOAD_TARGET{"0M"};
7474
/** Default for blocks only*/
7575
static const bool DEFAULT_BLOCKSONLY = false;
7676
/** -peertimeout default */

src/test/util_tests.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2456,4 +2456,52 @@ BOOST_AUTO_TEST_CASE(remove_prefix)
24562456
BOOST_CHECK_EQUAL(RemovePrefix("", ""), "");
24572457
}
24582458

2459+
BOOST_AUTO_TEST_CASE(util_ParseByteUnits)
2460+
{
2461+
auto noop = ByteUnit::NOOP;
2462+
2463+
// no multiplier
2464+
BOOST_CHECK_EQUAL(ParseByteUnits("1", noop).value(), 1);
2465+
BOOST_CHECK_EQUAL(ParseByteUnits("0", noop).value(), 0);
2466+
2467+
BOOST_CHECK_EQUAL(ParseByteUnits("1k", noop).value(), 1000ULL);
2468+
BOOST_CHECK_EQUAL(ParseByteUnits("1K", noop).value(), 1ULL << 10);
2469+
2470+
BOOST_CHECK_EQUAL(ParseByteUnits("2m", noop).value(), 2'000'000ULL);
2471+
BOOST_CHECK_EQUAL(ParseByteUnits("2M", noop).value(), 2ULL << 20);
2472+
2473+
BOOST_CHECK_EQUAL(ParseByteUnits("3g", noop).value(), 3'000'000'000ULL);
2474+
BOOST_CHECK_EQUAL(ParseByteUnits("3G", noop).value(), 3ULL << 30);
2475+
2476+
BOOST_CHECK_EQUAL(ParseByteUnits("4t", noop).value(), 4'000'000'000'000ULL);
2477+
BOOST_CHECK_EQUAL(ParseByteUnits("4T", noop).value(), 4ULL << 40);
2478+
2479+
// check default multiplier
2480+
BOOST_CHECK_EQUAL(ParseByteUnits("5", ByteUnit::K).value(), 5ULL << 10);
2481+
2482+
// NaN
2483+
BOOST_CHECK(!ParseByteUnits("", noop));
2484+
BOOST_CHECK(!ParseByteUnits("foo", noop));
2485+
2486+
// whitespace
2487+
BOOST_CHECK(!ParseByteUnits("123m ", noop));
2488+
BOOST_CHECK(!ParseByteUnits(" 123m", noop));
2489+
2490+
// no +-
2491+
BOOST_CHECK(!ParseByteUnits("-123m", noop));
2492+
BOOST_CHECK(!ParseByteUnits("+123m", noop));
2493+
2494+
// zero padding
2495+
BOOST_CHECK_EQUAL(ParseByteUnits("020M", noop).value(), 20ULL << 20);
2496+
2497+
// fractions not allowed
2498+
BOOST_CHECK(!ParseByteUnits("0.5T", noop));
2499+
2500+
// overflow
2501+
BOOST_CHECK(!ParseByteUnits("18446744073709551615g", noop));
2502+
2503+
// invalid unit
2504+
BOOST_CHECK(!ParseByteUnits("1x", noop));
2505+
}
2506+
24592507
BOOST_AUTO_TEST_SUITE_END()

src/util/strencodings.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <algorithm>
1212
#include <cstdlib>
1313
#include <cstring>
14+
#include <limits>
1415
#include <optional>
1516

1617
static const std::string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -526,3 +527,48 @@ std::string HexStr(const Span<const uint8_t> s)
526527
assert(it == rv.end());
527528
return rv;
528529
}
530+
531+
std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier)
532+
{
533+
if (str.empty()) {
534+
return std::nullopt;
535+
}
536+
auto multiplier = default_multiplier;
537+
char unit = str.back();
538+
switch (unit) {
539+
case 'k':
540+
multiplier = ByteUnit::k;
541+
break;
542+
case 'K':
543+
multiplier = ByteUnit::K;
544+
break;
545+
case 'm':
546+
multiplier = ByteUnit::m;
547+
break;
548+
case 'M':
549+
multiplier = ByteUnit::M;
550+
break;
551+
case 'g':
552+
multiplier = ByteUnit::g;
553+
break;
554+
case 'G':
555+
multiplier = ByteUnit::G;
556+
break;
557+
case 't':
558+
multiplier = ByteUnit::t;
559+
break;
560+
case 'T':
561+
multiplier = ByteUnit::T;
562+
break;
563+
default:
564+
unit = 0;
565+
break;
566+
}
567+
568+
uint64_t unit_amount = static_cast<uint64_t>(multiplier);
569+
auto parsed_num = ToIntegral<uint64_t>(unit ? str.substr(0, str.size() - 1) : str);
570+
if (!parsed_num || parsed_num > std::numeric_limits<uint64_t>::max() / unit_amount) { // check overflow
571+
return std::nullopt;
572+
}
573+
return *parsed_num * unit_amount;
574+
}

src/util/strencodings.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,23 @@ enum SafeChars
2929
SAFE_CHARS_URI, //!< Chars allowed in URIs (RFC 3986)
3030
};
3131

32+
/**
33+
* Used by ParseByteUnits()
34+
* Lowercase base 1000
35+
* Uppercase base 1024
36+
*/
37+
enum class ByteUnit : uint64_t {
38+
NOOP = 1ULL,
39+
k = 1000ULL,
40+
K = 1024ULL,
41+
m = 1'000'000ULL,
42+
M = 1ULL << 20,
43+
g = 1'000'000'000ULL,
44+
G = 1ULL << 30,
45+
t = 1'000'000'000'000ULL,
46+
T = 1ULL << 40,
47+
};
48+
3249
/**
3350
* Remove unsafe chars. Safe chars chosen to allow simple messages/URLs/email
3451
* addresses, but avoid anything even possibly remotely dangerous like & or >
@@ -305,4 +322,17 @@ std::string ToUpper(const std::string& str);
305322
*/
306323
std::string Capitalize(std::string str);
307324

325+
/**
326+
* Parse a string with suffix unit [k|K|m|M|g|G|t|T].
327+
* Must be a whole integer, fractions not allowed (0.5t), no whitespace or +-
328+
* Lowercase units are 1000 base. Uppercase units are 1024 base.
329+
* Examples: 2m,27M,19g,41T
330+
*
331+
* @param[in] str the string to convert into bytes
332+
* @param[in] default_multiplier if no unit is found in str use this unit
333+
* @returns optional uint64_t bytes from str or nullopt
334+
* if ToIntegral is false, str is empty, trailing whitespace or overflow
335+
*/
336+
std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier);
337+
308338
#endif // BITCOIN_UTIL_STRENCODINGS_H

0 commit comments

Comments
 (0)