Skip to content

Commit 3d769c7

Browse files
committed
merge bitcoin#23249: ParseByteUnits - Parse a string with suffix unit
1 parent edd0bab commit 3d769c7

File tree

6 files changed

+153
-17
lines changed

6 files changed

+153
-17
lines changed

doc/release-notes-6296.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Updated settings
2+
----------------
3+
4+
- `-maxuploadtarget` now allows human readable byte units [k|K|m|M|g|G|t|T].
5+
E.g. `-maxuploadtarget=500g`. No whitespace, +- or fractions allowed.
6+
Default is `M` if no suffix provided.

src/init.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ void SetupServerArgs(ArgsManager& argsman)
583583
argsman.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
584584
argsman.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection memory usage for the send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
585585
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);
586-
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);
586+
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);
587587
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);
588588
argsman.AddArg("-i2psam=<ip:port>", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
589589
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)
14401440
{
14411441
const ArgsManager& args = *Assert(node.args);
14421442
const CChainParams& chainparams = Params();
1443+
1444+
auto opt_max_upload = ParseByteUnits(args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET), ByteUnit::M);
1445+
if (!opt_max_upload) {
1446+
return InitError(strprintf(_("Unable to parse -maxuploadtarget: '%s' (possible integer overflow?)"), args.GetArg("-maxuploadtarget", "")));
1447+
}
1448+
14431449
// ********************************************************* Step 4a: application initialization
14441450
if (!CreatePidFile(args)) {
14451451
// Detailed error printed inside CreatePidFile().
@@ -2388,7 +2394,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
23882394
connOptions.nSendBufferMaxSize = 1000 * args.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER);
23892395
connOptions.nReceiveFloodSize = 1000 * args.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER);
23902396
connOptions.m_added_nodes = args.GetArgs("-addnode");
2391-
connOptions.nMaxOutboundLimit = 1024 * 1024 * args.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET);
2397+
connOptions.nMaxOutboundLimit = *opt_max_upload;
23922398
connOptions.m_peer_connect_timeout = peer_connect_timeout;
23932399

23942400
// Port to bind to if `-bind=addr` is provided without a `:port` suffix.

src/net.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ static const bool DEFAULT_LISTEN = true;
100100
*/
101101
static const unsigned int DEFAULT_MAX_PEER_CONNECTIONS = 125;
102102
/** The default for -maxuploadtarget. 0 = Unlimited */
103-
static constexpr uint64_t DEFAULT_MAX_UPLOAD_TARGET = 0;
103+
static const std::string DEFAULT_MAX_UPLOAD_TARGET{"0M"};
104104
/** Default for blocks only*/
105105
static const bool DEFAULT_BLOCKSONLY = false;
106106
/** -peertimeout default */

src/test/util_tests.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,4 +2685,52 @@ BOOST_AUTO_TEST_CASE(remove_prefix)
26852685
BOOST_CHECK_EQUAL(RemovePrefix("", ""), "");
26862686
}
26872687

2688+
BOOST_AUTO_TEST_CASE(util_ParseByteUnits)
2689+
{
2690+
auto noop = ByteUnit::NOOP;
2691+
2692+
// no multiplier
2693+
BOOST_CHECK_EQUAL(ParseByteUnits("1", noop).value(), 1);
2694+
BOOST_CHECK_EQUAL(ParseByteUnits("0", noop).value(), 0);
2695+
2696+
BOOST_CHECK_EQUAL(ParseByteUnits("1k", noop).value(), 1000ULL);
2697+
BOOST_CHECK_EQUAL(ParseByteUnits("1K", noop).value(), 1ULL << 10);
2698+
2699+
BOOST_CHECK_EQUAL(ParseByteUnits("2m", noop).value(), 2'000'000ULL);
2700+
BOOST_CHECK_EQUAL(ParseByteUnits("2M", noop).value(), 2ULL << 20);
2701+
2702+
BOOST_CHECK_EQUAL(ParseByteUnits("3g", noop).value(), 3'000'000'000ULL);
2703+
BOOST_CHECK_EQUAL(ParseByteUnits("3G", noop).value(), 3ULL << 30);
2704+
2705+
BOOST_CHECK_EQUAL(ParseByteUnits("4t", noop).value(), 4'000'000'000'000ULL);
2706+
BOOST_CHECK_EQUAL(ParseByteUnits("4T", noop).value(), 4ULL << 40);
2707+
2708+
// check default multiplier
2709+
BOOST_CHECK_EQUAL(ParseByteUnits("5", ByteUnit::K).value(), 5ULL << 10);
2710+
2711+
// NaN
2712+
BOOST_CHECK(!ParseByteUnits("", noop));
2713+
BOOST_CHECK(!ParseByteUnits("foo", noop));
2714+
2715+
// whitespace
2716+
BOOST_CHECK(!ParseByteUnits("123m ", noop));
2717+
BOOST_CHECK(!ParseByteUnits(" 123m", noop));
2718+
2719+
// no +-
2720+
BOOST_CHECK(!ParseByteUnits("-123m", noop));
2721+
BOOST_CHECK(!ParseByteUnits("+123m", noop));
2722+
2723+
// zero padding
2724+
BOOST_CHECK_EQUAL(ParseByteUnits("020M", noop).value(), 20ULL << 20);
2725+
2726+
// fractions not allowed
2727+
BOOST_CHECK(!ParseByteUnits("0.5T", noop));
2728+
2729+
// overflow
2730+
BOOST_CHECK(!ParseByteUnits("18446744073709551615g", noop));
2731+
2732+
// invalid unit
2733+
BOOST_CHECK(!ParseByteUnits("1x", noop));
2734+
}
2735+
26882736
BOOST_AUTO_TEST_SUITE_END()

src/util/strencodings.cpp

Lines changed: 60 additions & 14 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";
@@ -501,20 +502,6 @@ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out)
501502
return true;
502503
}
503504

504-
std::string HexStr(const Span<const uint8_t> s)
505-
{
506-
std::string rv(s.size() * 2, '\0');
507-
static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
508-
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
509-
auto it = rv.begin();
510-
for (uint8_t v : s) {
511-
*it++ = hexmap[v >> 4];
512-
*it++ = hexmap[v & 15];
513-
}
514-
assert(it == rv.end());
515-
return rv;
516-
}
517-
518505
std::string ToLower(const std::string& str)
519506
{
520507
std::string r;
@@ -535,3 +522,62 @@ std::string Capitalize(std::string str)
535522
str[0] = ToUpper(str.front());
536523
return str;
537524
}
525+
526+
std::string HexStr(const Span<const uint8_t> s)
527+
{
528+
std::string rv(s.size() * 2, '\0');
529+
static constexpr char hexmap[16] = { '0', '1', '2', '3', '4', '5', '6', '7',
530+
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
531+
auto it = rv.begin();
532+
for (uint8_t v : s) {
533+
*it++ = hexmap[v >> 4];
534+
*it++ = hexmap[v & 15];
535+
}
536+
assert(it == rv.end());
537+
return rv;
538+
}
539+
540+
std::optional<uint64_t> ParseByteUnits(const std::string& str, ByteUnit default_multiplier)
541+
{
542+
if (str.empty()) {
543+
return std::nullopt;
544+
}
545+
auto multiplier = default_multiplier;
546+
char unit = str.back();
547+
switch (unit) {
548+
case 'k':
549+
multiplier = ByteUnit::k;
550+
break;
551+
case 'K':
552+
multiplier = ByteUnit::K;
553+
break;
554+
case 'm':
555+
multiplier = ByteUnit::m;
556+
break;
557+
case 'M':
558+
multiplier = ByteUnit::M;
559+
break;
560+
case 'g':
561+
multiplier = ByteUnit::g;
562+
break;
563+
case 'G':
564+
multiplier = ByteUnit::G;
565+
break;
566+
case 't':
567+
multiplier = ByteUnit::t;
568+
break;
569+
case 'T':
570+
multiplier = ByteUnit::T;
571+
break;
572+
default:
573+
unit = 0;
574+
break;
575+
}
576+
577+
uint64_t unit_amount = static_cast<uint64_t>(multiplier);
578+
auto parsed_num = ToIntegral<uint64_t>(unit ? str.substr(0, str.size() - 1) : str);
579+
if (!parsed_num || parsed_num > std::numeric_limits<uint64_t>::max() / unit_amount) { // check overflow
580+
return std::nullopt;
581+
}
582+
return *parsed_num * unit_amount;
583+
}

src/util/strencodings.h

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

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

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

0 commit comments

Comments
 (0)