diff --git a/builds/cmake/install-cmake.sh b/builds/cmake/install-cmake.sh
index 3b39995b3..b54c820e1 100755
--- a/builds/cmake/install-cmake.sh
+++ b/builds/cmake/install-cmake.sh
@@ -71,7 +71,7 @@ if [[ -z ${secp256k1_OWNER} ]]; then
secp256k1_OWNER="bitcoin-core"
fi
if [[ -z ${secp256k1_TAG} ]]; then
- secp256k1_TAG="v0.7.0"
+ secp256k1_TAG="v0.8.0"
fi
if [[ -z ${UltrafastSecp256k1_OWNER} ]]; then
diff --git a/builds/cmake/install-presets.sh b/builds/cmake/install-presets.sh
index 33fd3cf56..3cca40c25 100755
--- a/builds/cmake/install-presets.sh
+++ b/builds/cmake/install-presets.sh
@@ -61,7 +61,7 @@ if [[ -z ${secp256k1_OWNER} ]]; then
secp256k1_OWNER="bitcoin-core"
fi
if [[ -z ${secp256k1_TAG} ]]; then
- secp256k1_TAG="v0.7.0"
+ secp256k1_TAG="v0.8.0"
fi
if [[ -z ${UltrafastSecp256k1_OWNER} ]]; then
diff --git a/builds/gnu/configure.ac b/builds/gnu/configure.ac
index 53187daf8..e318269dd 100644
--- a/builds/gnu/configure.ac
+++ b/builds/gnu/configure.ac
@@ -97,7 +97,7 @@ AC_SUBST([pkgconfigdir],[${with_pkgconfigdir}])
AC_MSG_CHECKING([--enable-ndebug option])
AC_ARG_ENABLE([ndebug],
AS_HELP_STRING([--enable-ndebug],
- [Compile with NDEBUG assertion. @<:@default=yes@:>@]),
+ [Compile with NDEBUG (no debug assertions). @<:@default=yes@:>@]),
[enable_ndebug=$enableval],
[enable_ndebug=yes])
AC_MSG_RESULT([$enable_ndebug])
diff --git a/builds/gnu/install-gnu.sh b/builds/gnu/install-gnu.sh
index cec6e58c2..af3f30f15 100755
--- a/builds/gnu/install-gnu.sh
+++ b/builds/gnu/install-gnu.sh
@@ -71,7 +71,7 @@ if [[ -z ${secp256k1_OWNER} ]]; then
secp256k1_OWNER="bitcoin-core"
fi
if [[ -z ${secp256k1_TAG} ]]; then
- secp256k1_TAG="v0.7.0"
+ secp256k1_TAG="v0.8.0"
fi
if [[ -z ${UltrafastSecp256k1_OWNER} ]]; then
diff --git a/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj b/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj
index de20c95c6..2b7453c0f 100644
--- a/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj
+++ b/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj
@@ -422,7 +422,7 @@
-
+
@@ -438,7 +438,7 @@
-
+
diff --git a/builds/msvc/vs2026/libbitcoin-network-test/packages.config b/builds/msvc/vs2026/libbitcoin-network-test/packages.config
index 241bbaedc..28d417f8c 100644
--- a/builds/msvc/vs2026/libbitcoin-network-test/packages.config
+++ b/builds/msvc/vs2026/libbitcoin-network-test/packages.config
@@ -16,5 +16,5 @@
-
+
diff --git a/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj b/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj
index 1e3285e68..02af753cd 100644
--- a/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj
+++ b/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj
@@ -598,7 +598,7 @@
-
+
@@ -613,7 +613,7 @@
-
+
diff --git a/builds/msvc/vs2026/libbitcoin-network/packages.config b/builds/msvc/vs2026/libbitcoin-network/packages.config
index 5efab0603..b8c3a66ec 100644
--- a/builds/msvc/vs2026/libbitcoin-network/packages.config
+++ b/builds/msvc/vs2026/libbitcoin-network/packages.config
@@ -15,5 +15,5 @@
-
+
diff --git a/include/bitcoin/network/channels/channel.hpp b/include/bitcoin/network/channels/channel.hpp
index 394e8e859..199b466c8 100644
--- a/include/bitcoin/network/channels/channel.hpp
+++ b/include/bitcoin/network/channels/channel.hpp
@@ -93,6 +93,12 @@ class BCT_API channel
const options_t& options() const NOEXCEPT;
protected:
+ /// The network and service rate limits overlap, so the more restrictive
+ /// applies. Zero is unlimited, and therefore the less restrictive, so the
+ /// maximum applies if either is zero (zero only if both are zero).
+ static uint32_t rate_limited(const settings_t& settings,
+ const options_t& options) NOEXCEPT;
+
/// Construct a channel to encapsulated and communicate on the socket.
channel(const logger& log, const socket::ptr& socket, uint64_t identifier,
const settings_t& settings, const options_t& options) NOEXCEPT;
diff --git a/include/bitcoin/network/net/proxy.hpp b/include/bitcoin/network/net/proxy.hpp
index 1e1a411fb..c8cd9b361 100644
--- a/include/bitcoin/network/net/proxy.hpp
+++ b/include/bitcoin/network/net/proxy.hpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include
namespace libbitcoin {
@@ -40,6 +41,11 @@ namespace network {
/// Completion handler is invoked once write is complete, at which point the
/// next queued write is invoked. When a channel stops with pending writes the
/// write queue is purged without invoke of the purged handlers.
+/// Each send is allocated (bytes/rate_limit) of time, and its completion is
+/// deferred by whatever portion of that allocation the write did not consume.
+/// Since nothing is produced until the completion handler is invoked, this
+/// throttles the channel without queueing. Reads are not metered, as they are
+/// bounded by protocol correctness. Zero rate_limit disables the throttle.
class BCT_API proxy
: public enable_shared_from_base, public reporter
{
@@ -111,7 +117,7 @@ class BCT_API proxy
const config::endpoint& endpoint() const NOEXCEPT;
protected:
- proxy(const socket::ptr& socket) NOEXCEPT;
+ proxy(const socket::ptr& socket, uint32_t rate_limit) NOEXCEPT;
/// Stranded event, allows timer reset.
virtual void reading() NOEXCEPT;
@@ -122,6 +128,15 @@ class BCT_API proxy
/// Subscribe to stop notification (requires strand).
void subscribe_stop(result_handler&& handler) NOEXCEPT;
+ /// Throttle.
+ /// -----------------------------------------------------------------------
+
+ /// Unconsumed portion of the byte allocation, by which a send completing
+ /// now is deferred. Zero if unlimited, stopped, or fully consumed by the
+ /// transmission (requires strand).
+ steady_clock::duration unconsumed(size_t bytes,
+ const steady_clock::time_point& start) const NOEXCEPT;
+
/// Wait.
/// -----------------------------------------------------------------------
@@ -243,15 +258,25 @@ class BCT_API proxy
void handle_write(const code& ec, size_t bytes,
const count_handler& handler) NOEXCEPT;
+ // Meter sent bytes and defer the completion by the unconsumed allocation.
+ count_handler metered(count_handler&& handler) NOEXCEPT;
+ void handle_metered(const code& ec, size_t bytes,
+ const steady_clock::time_point& start,
+ const count_handler& handler) NOEXCEPT;
+ void handle_charge(const code&, const code& ec, size_t bytes,
+ const count_handler& handler) NOEXCEPT;
+
// Invoke reading() on strand.
void do_reading() NOEXCEPT;
// These are thread safe.
std::atomic_bool paused_{ true };
std::atomic total_{};
+ const uint32_t rate_limit_;
socket::ptr socket_;
// These are protected by strand.
+ deadline::ptr throttle_;
stop_subscriber stop_subscriber_{};
socket::http_parser_ptr parser_{};
queue deferred_{};
diff --git a/include/bitcoin/network/settings.hpp b/include/bitcoin/network/settings.hpp
index 16eafed96..1a7be297b 100644
--- a/include/bitcoin/network/settings.hpp
+++ b/include/bitcoin/network/settings.hpp
@@ -75,6 +75,10 @@ struct BCT_API settings
uint32_t maximum_request{ maximum_request_default };
uint32_t minimum_buffer{ maximum_request_default };
+ /// Service send rate limit, overlapping the network rate limit (see
+ /// settings::rate_limited). Zero is unlimited.
+ uint32_t rate_limit{ 0 };
+
/// Helpers.
virtual bool enabled() const NOEXCEPT;
virtual steady_clock::duration inactivity() const NOEXCEPT;
@@ -297,7 +301,12 @@ struct BCT_API settings
uint32_t handshake_timeout_seconds{ 15 };
uint32_t channel_heartbeat_minutes{ 5 };
uint32_t maximum_skew_minutes{ 120 };
- uint32_t rate_limit{ 1024 };
+
+ /// Bytes/second allocated to each channel for sending, zero is unlimited.
+ /// A send is deferred by the unconsumed portion of its byte allocation,
+ /// which the next send of the channel cannot start until it expires.
+ /// Overlaps tcp_server::rate_limit (see settings::rate_limited).
+ uint32_t rate_limit{ 0 };
std::string user_agent{ BC_USER_AGENT };
std::filesystem::path path{};
config::authorities blacklists{};
diff --git a/src/channels/channel.cpp b/src/channels/channel.cpp
index e4ea0cb18..1f2a54edd 100644
--- a/src/channels/channel.cpp
+++ b/src/channels/channel.cpp
@@ -18,6 +18,7 @@
*/
#include
+#include
#include
#include
#include
@@ -41,12 +42,21 @@ inline deadline::ptr make_timer(const logger& log, asio::strand& strand,
emplace_shared(log, strand, span) : nullptr;
}
+// protected/static
+uint32_t channel::rate_limited(const settings_t& settings,
+ const options_t& options) NOEXCEPT
+{
+ return to_bool(settings.rate_limit) && to_bool(options.rate_limit) ?
+ std::min(settings.rate_limit, options.rate_limit) :
+ std::max(settings.rate_limit, options.rate_limit);
+}
+
// Protocols invoke channel stop for application layer protocol violations.
// Channels invoke channel stop for channel timouts and communcation failures.
channel::channel(const logger& log, const socket::ptr& socket,
uint64_t identifier, const settings_t& settings,
const options_t& options) NOEXCEPT
- : proxy(socket),
+ : proxy(socket, rate_limited(settings, options)),
options_(options),
settings_(settings),
identifier_(identifier),
@@ -112,7 +122,9 @@ void channel::handle_monitor(const code& ec) NOEXCEPT
// Timers.
// ----------------------------------------------------------------------------
-// TODO: build DoS protection around rate_limit_, total(), and time.
+// Send throttling (settings.rate_limit) is implemented by the proxy. A channel
+// whose accrued deferral exceeds inactivity is dropped by that timer, which is
+// the intended outcome (the throttle degrades to disconnection under abuse).
// A restarted timer invokes completion handler with error::operation_canceled.
// Called from start or strand.
diff --git a/src/net/proxy.cpp b/src/net/proxy.cpp
index 5af17ecde..bd0ec1601 100644
--- a/src/net/proxy.cpp
+++ b/src/net/proxy.cpp
@@ -28,11 +28,21 @@ namespace network {
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
+// Factory for variable deadline timer pointer construction (or null).
+inline deadline::ptr make_throttle(const logger& log, asio::strand& strand,
+ uint32_t rate_limit) NOEXCEPT
+{
+ return to_bool(rate_limit) ?
+ system::emplace_shared(log, strand) : nullptr;
+}
+
// This is created in a started state and must be stopped, as the subscribers
// assert if not stopped. Subscribers may hold protocols even if the service
// is not started.
-proxy::proxy(const socket::ptr& socket) NOEXCEPT
- : socket_(socket),
+proxy::proxy(const socket::ptr& socket, uint32_t rate_limit) NOEXCEPT
+ : rate_limit_(rate_limit),
+ socket_(socket),
+ throttle_(make_throttle(socket->log, socket->strand(), rate_limit)),
reporter(socket->log)
{
}
@@ -75,6 +85,10 @@ void proxy::do_stop(const code& ec) NOEXCEPT
BC_ASSERT(stranded());
using namespace std::placeholders;
+ // The socket is not yet stopped, so a deferred send still holds the queue.
+ // Release it here so that the close part is not delayed by the throttle.
+ if (throttle_) throttle_->stop();
+
batched_ = false;
parted_ = false;
@@ -138,6 +152,9 @@ void proxy::stopping(const code& ec) NOEXCEPT
{
BC_ASSERT(stranded());
+ // Release any deferred send (fires pending charge with canceled).
+ if (throttle_) throttle_->stop();
+
// Release any http message parse in progress.
parser_.reset();
diff --git a/src/net/proxy_actions.cpp b/src/net/proxy_actions.cpp
index 6946c288a..df90feba5 100644
--- a/src/net/proxy_actions.cpp
+++ b/src/net/proxy_actions.cpp
@@ -73,8 +73,8 @@ void proxy::do_ws_write(const asio::const_buffer& payload, bool binary,
const count_handler& handler) NOEXCEPT
{
socket_->ws_write({ payload.data(), payload.size() }, binary,
- std::bind(&proxy::handle_write,
- shared_from_this(), _1, _2, handler));
+ metered(std::bind(&proxy::handle_write,
+ shared_from_this(), _1, _2, handler)));
}
// TCP (generic, fixed size).
@@ -103,8 +103,8 @@ void proxy::do_tcp_write(const asio::const_buffer& payload,
const count_handler& handler) NOEXCEPT
{
socket_->tcp_write({ payload.data(), payload.size() },
- std::bind(&proxy::handle_write,
- shared_from_this(), _1, _2, handler));
+ metered(std::bind(&proxy::handle_write,
+ shared_from_this(), _1, _2, handler)));
}
// RPC (TCP: electrum/stratum_v1, WS: btcd).
@@ -256,8 +256,8 @@ void proxy::do_response_write(const rpc::response_ptr& response,
}
socket_->rpc_write(std::move(*response),
- std::bind(&proxy::handle_write,
- shared_from_this(), _1, _2, handler));
+ metered(std::bind(&proxy::handle_write,
+ shared_from_this(), _1, _2, handler)));
}
// private
@@ -265,8 +265,8 @@ void proxy::do_notification_write(const rpc::request_ptr& notification,
const count_handler& handler) NOEXCEPT
{
socket_->rpc_notify(std::move(*notification),
- std::bind(&proxy::handle_write,
- shared_from_this(), _1, _2, handler));
+ metered(std::bind(&proxy::handle_write,
+ shared_from_this(), _1, _2, handler)));
}
// HTTP/WS (generic/rpc).
@@ -466,7 +466,8 @@ void proxy::write(http::response&& response,
if (parted_)
{
- socket_->rpc_write_chunk(std::move(part), std::move(handler));
+ socket_->rpc_write_chunk(std::move(part),
+ metered(std::move(handler)));
return;
}
@@ -477,13 +478,13 @@ void proxy::write(http::response&& response,
const auto out = move_shared(std::move(part));
socket_->http_write_header(std::move(response),
- std::bind(&proxy::handle_http_header_write,
- shared_from_this(), _1, _2, out, std::move(handler)));
+ metered(std::bind(&proxy::handle_http_header_write,
+ shared_from_this(), _1, _2, out, std::move(handler))));
return;
}
// http is half duplex so there is no interleave risk.
- socket_->http_write(std::move(response), std::move(handler));
+ socket_->http_write(std::move(response), metered(std::move(handler)));
}
// private
@@ -498,7 +499,7 @@ void proxy::handle_http_header_write(const code& ec, size_t bytes,
return;
}
- socket_->rpc_write_chunk(std::move(*part), move_copy(handler));
+ socket_->rpc_write_chunk(std::move(*part), metered(move_copy(handler)));
}
// private
@@ -506,8 +507,8 @@ void proxy::do_http_write(const http::response_ptr& response,
const count_handler& handler) NOEXCEPT
{
socket_->http_write(std::move(*response),
- std::bind(&proxy::handle_write,
- shared_from_this(), _1, _2, handler));
+ metered(std::bind(&proxy::handle_write,
+ shared_from_this(), _1, _2, handler)));
}
BC_POP_WARNING()
diff --git a/src/net/proxy_queue.cpp b/src/net/proxy_queue.cpp
index 32e899584..6f3438723 100644
--- a/src/net/proxy_queue.cpp
+++ b/src/net/proxy_queue.cpp
@@ -27,6 +27,13 @@
namespace libbitcoin {
namespace network {
+// Shared pointers required in handler parameters so closures control lifetime.
+BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR)
+BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED)
+BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
+
+using namespace std::placeholders;
+
// Send cycle (send continues until queue is empty).
// ----------------------------------------------------------------------------
// private
@@ -67,13 +74,88 @@ void proxy::handle_write(const code& ec, size_t bytes,
if (queue_.empty())
return;
+ // Handler precedes pop so that a handler send does not start a second
+ // write loop (a non-empty queue defers the start to the pop below).
handler(ec, bytes);
queue_.pop_front();
- total_ = system::ceilinged_add(total_.load(), bytes);
// All handlers must be invoked unless stopped, so continue despite code.
write();
}
+// Throttle (sent bytes are allocated time at the configured rate).
+// ----------------------------------------------------------------------------
+// private
+// Applied to every send, queued or not, so that the deferral is imposed
+// without imposing the queue (http is half duplex, so it is not queued).
+
+// Nanoseconds allocated to the transmission of bytes at the given rate.
+inline steady_clock::duration to_allocation(size_t bytes,
+ uint32_t rate) NOEXCEPT
+{
+ using namespace system;
+ constexpr auto nanos = 1'000'000'000_u64;
+
+ // Overflow implies an unusable rate/size, saturated at the type maximum.
+ const auto span = ceilinged_multiply(bytes, nanos) / rate;
+ return nanoseconds{ limit(span) };
+}
+
+count_handler proxy::metered(count_handler&& handler) NOEXCEPT
+{
+ // Stamped at issue, so that only transmission time is credited against
+ // the allocation. Time spent idle between sends earns nothing.
+ return std::bind(&proxy::handle_metered,
+ shared_from_this(), _1, _2, steady_clock::now(), std::move(handler));
+}
+
+steady_clock::duration proxy::unconsumed(size_t bytes,
+ const steady_clock::time_point& start) const NOEXCEPT
+{
+ BC_ASSERT(stranded());
+
+ // Stop is never deferred, and a null throttle implies no rate limit.
+ if (!throttle_ || stopped())
+ return {};
+
+ const auto allocated = to_allocation(bytes, rate_limit_);
+ const auto consumed = steady_clock::now() - start;
+ return consumed < allocated ? allocated - consumed :
+ steady_clock::duration{};
+}
+
+void proxy::handle_metered(const code& ec, size_t bytes,
+ const steady_clock::time_point& start,
+ const count_handler& handler) NOEXCEPT
+{
+ BC_ASSERT(stranded());
+ total_ = system::ceilinged_add(total_.load(), bytes);
+
+ // A send that consumed its full allocation is not deferred.
+ const auto delay = unconsumed(bytes, start);
+ if (is_zero(delay.count()))
+ {
+ handler(ec, bytes);
+ return;
+ }
+
+ // Handler is posted to the strand, and fired by stop (canceled).
+ throttle_->start(std::bind(&proxy::handle_charge,
+ shared_from_this(), _1, ec, bytes, handler), delay);
+}
+
+void proxy::handle_charge(const code&, const code& ec, size_t bytes,
+ const count_handler& handler) NOEXCEPT
+{
+ BC_ASSERT(stranded());
+
+ // The timer code is discarded, as the send result is what is reported.
+ handler(ec, bytes);
+}
+
+BC_POP_WARNING()
+BC_POP_WARNING()
+BC_POP_WARNING()
+
} // namespace network
} // namespace libbitcoin
diff --git a/test/channels/channel.cpp b/test/channels/channel.cpp
index 8d5f3557d..c5eca68ab 100644
--- a/test/channels/channel.cpp
+++ b/test/channels/channel.cpp
@@ -31,8 +31,56 @@ struct accessor
: channel(log, socket, identifier, settings, options)
{
}
+
+ static uint32_t rate_limited1(const network::settings& settings,
+ const channel::options_t& options) NOEXCEPT
+ {
+ return channel::rate_limited(settings, options);
+ }
};
+// rate_limited
+
+BOOST_AUTO_TEST_CASE(channel__rate_limited__both_zero__zero)
+{
+ settings set(bc::system::chain::selection::mainnet);
+ set.rate_limit = 0;
+ set.outbound.rate_limit = 0;
+ BOOST_REQUIRE_EQUAL(accessor::rate_limited1(set, set.outbound), 0u);
+}
+
+BOOST_AUTO_TEST_CASE(channel__rate_limited__network_zero__service)
+{
+ settings set(bc::system::chain::selection::mainnet);
+ set.rate_limit = 0;
+ set.outbound.rate_limit = 42;
+ BOOST_REQUIRE_EQUAL(accessor::rate_limited1(set, set.outbound), 42u);
+}
+
+BOOST_AUTO_TEST_CASE(channel__rate_limited__service_zero__network)
+{
+ settings set(bc::system::chain::selection::mainnet);
+ set.rate_limit = 42;
+ set.outbound.rate_limit = 0;
+ BOOST_REQUIRE_EQUAL(accessor::rate_limited1(set, set.outbound), 42u);
+}
+
+BOOST_AUTO_TEST_CASE(channel__rate_limited__network_lesser__network)
+{
+ settings set(bc::system::chain::selection::mainnet);
+ set.rate_limit = 24;
+ set.outbound.rate_limit = 42;
+ BOOST_REQUIRE_EQUAL(accessor::rate_limited1(set, set.outbound), 24u);
+}
+
+BOOST_AUTO_TEST_CASE(channel__rate_limited__service_lesser__service)
+{
+ settings set(bc::system::chain::selection::mainnet);
+ set.rate_limit = 42;
+ set.outbound.rate_limit = 24;
+ BOOST_REQUIRE_EQUAL(accessor::rate_limited1(set, set.outbound), 24u);
+}
+
BOOST_AUTO_TEST_CASE(channel__stopped__default__false)
{
constexpr auto expected = 42u;
diff --git a/test/net/proxy.cpp b/test/net/proxy.cpp
index d012a8370..1798f14b2 100644
--- a/test/net/proxy.cpp
+++ b/test/net/proxy.cpp
@@ -30,13 +30,90 @@ class mock_proxy
proxy::subscribe_stop(std::move(handler));
}
+ // Call must be stranded.
+ steady_clock::duration unconsumed1(size_t bytes,
+ const steady_clock::time_point& start) const NOEXCEPT
+ {
+ return proxy::unconsumed(bytes, start);
+ }
+
// Access protected constructor.
- mock_proxy(const socket::ptr& socket) NOEXCEPT
- : proxy(socket)
+ mock_proxy(const socket::ptr& socket, uint32_t rate_limit=0) NOEXCEPT
+ : proxy(socket, rate_limit)
{
}
};
+// Obtain the deferral for a send of bytes that started at the given offset.
+static milliseconds get_unconsumed(uint32_t rate_limit, size_t bytes,
+ const steady_clock::duration& elapsed) NOEXCEPT
+{
+ const logger log{};
+ threadpool pool(1);
+ socket::parameters params{ .maximum_request = 42 };
+ auto socket_ptr = std::make_shared(log, pool.service(), std::move(params));
+ auto proxy_ptr = std::make_shared(socket_ptr, rate_limit);
+
+ std::promise deferral;
+ boost::asio::post(proxy_ptr->strand(), [=, &deferral]() NOEXCEPT
+ {
+ deferral.set_value(std::chrono::duration_cast(
+ proxy_ptr->unconsumed1(bytes, steady_clock::now() - elapsed)));
+ });
+
+ const auto result = deferral.get_future().get();
+ proxy_ptr->stop(error::invalid_magic);
+ pool.stop();
+ return result;
+}
+
+BOOST_AUTO_TEST_CASE(proxy__unconsumed__unlimited__zero)
+{
+ BOOST_REQUIRE_EQUAL(get_unconsumed(0, 1000, seconds(0)), milliseconds(0));
+}
+
+BOOST_AUTO_TEST_CASE(proxy__unconsumed__untransmitted__full_allocation)
+{
+ // 1000 bytes at 1000 bytes/second is allocated one second, unconsumed.
+ const auto deferral = get_unconsumed(1000, 1000, seconds(0));
+ BOOST_REQUIRE_GT(deferral, milliseconds(900));
+ BOOST_REQUIRE_LE(deferral, milliseconds(1000));
+}
+
+BOOST_AUTO_TEST_CASE(proxy__unconsumed__partly_transmitted__remainder)
+{
+ // Transmission consumed 250ms of the 1000ms allocation.
+ const auto deferral = get_unconsumed(1000, 1000, milliseconds(250));
+ BOOST_REQUIRE_GT(deferral, milliseconds(650));
+ BOOST_REQUIRE_LE(deferral, milliseconds(750));
+}
+
+BOOST_AUTO_TEST_CASE(proxy__unconsumed__fully_transmitted__zero)
+{
+ // Transmission was slower than the rate limit, so there is nothing to add.
+ BOOST_REQUIRE_EQUAL(get_unconsumed(1000, 1000, seconds(2)),
+ milliseconds(0));
+}
+
+BOOST_AUTO_TEST_CASE(proxy__unconsumed__stopped__zero)
+{
+ const logger log{};
+ threadpool pool(1);
+ socket::parameters params{ .maximum_request = 42 };
+ auto socket_ptr = std::make_shared(log, pool.service(), std::move(params));
+ auto proxy_ptr = std::make_shared(socket_ptr, 1000);
+ proxy_ptr->stop(error::invalid_magic);
+
+ std::promise deferral;
+ boost::asio::post(proxy_ptr->strand(), [=, &deferral]() NOEXCEPT
+ {
+ deferral.set_value(
+ proxy_ptr->unconsumed1(1000, steady_clock::now()).count());
+ });
+
+ BOOST_REQUIRE_EQUAL(deferral.get_future().get(), 0);
+}
+
BOOST_AUTO_TEST_CASE(proxy__paused__default__true)
{
const logger log{};
diff --git a/test/settings.cpp b/test/settings.cpp
index 05ca1532f..49d7b5935 100644
--- a/test/settings.cpp
+++ b/test/settings.cpp
@@ -53,7 +53,7 @@ BOOST_AUTO_TEST_CASE(settings__construct__default__expected)
BOOST_REQUIRE_EQUAL(instance.handshake_timeout_seconds, 15u);
BOOST_REQUIRE_EQUAL(instance.channel_heartbeat_minutes, 5u);
BOOST_REQUIRE_EQUAL(instance.maximum_skew_minutes, 120u);
- BOOST_REQUIRE_EQUAL(instance.rate_limit, 1024u);
+ BOOST_REQUIRE_EQUAL(instance.rate_limit, 0u);
BOOST_REQUIRE_EQUAL(instance.user_agent, BC_USER_AGENT);
BOOST_REQUIRE(instance.path.empty());
BOOST_REQUIRE(instance.blacklists.empty());
@@ -356,6 +356,7 @@ BOOST_AUTO_TEST_CASE(settings__tcp_server__defaults__expected)
BOOST_REQUIRE_EQUAL(instance.inactivity_minutes, 10u);
BOOST_REQUIRE_EQUAL(instance.expiration_minutes, 60u);
BOOST_REQUIRE_EQUAL(instance.maximum_request, maximum_request);
+ BOOST_REQUIRE_EQUAL(instance.rate_limit, 0u);
BOOST_REQUIRE(!instance.enabled());
BOOST_REQUIRE(instance.inactivity() == minutes(10));
BOOST_REQUIRE(instance.expiration() == minutes(60));