mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-01 00:19:07 +02:00
Merge bitcoin/bitcoin#31022: test: Add mockable steady clock, tests for PCP and NATPMP implementations
0f716f28896c6edfcd4e2a2b25c88f478a029c7b qa: cover PROTOCOL_ERROR variant in PCP unit tests (Antoine Poinsot) fc700bb47fd8b6ac58f612b932aef0e361686cc3 test: Add tests for PCP and NATPMP implementations (laanwj) caf952103317a7fa8bd2bceb35d4e8ace5968906 net: Use mockable steady clock in PCP implementation (laanwj) 03648321ecb704b69e47eed7e3df6a779aee8f11 util: Add mockable steady_clock (laanwj) ab1d3ece026844e682676673b8a461964a5b3ce4 net: Add optional length checking to CService::SetSockAddr (laanwj) Pull request description: Add a NodeSteadyClock, a steady_clock that can be mocked with millisecond precision. Use this in the PCP implementation. Then add a mock for a simple scriptable UDP server,, which is used to test various code paths (including successful mappings, timeouts and errors) in the PCP and NATPMP implementations. Includes "net: Add optional length checking to CService::SetSockAddr" from #31014 as a prerequisite. ACKs for top commit: darosior: re-ACK 0f716f28896c6edfcd4e2a2b25c88f478a029c7b i-am-yuvi: Concept ACK 0f716f28896c6edfcd4e2a2b25c88f478a029c7b achow101: ACK 0f716f28896c6edfcd4e2a2b25c88f478a029c7b Tree-SHA512: 6f91b24e6fe46a3fded7a13972efd77c98e6ef235f8898e4ae44068c5df32d1cdabb22cb66c351b338dc98cb2073b624e43607a28107f4999302bfbe7a138229
This commit is contained in:
commit
c65233230f
@ -169,16 +169,9 @@ std::optional<CNetAddr> QueryDefaultGatewayImpl(sa_family_t family)
|
||||
|
||||
std::optional<CNetAddr> FromSockAddr(const struct sockaddr* addr)
|
||||
{
|
||||
// Check valid length. Note that sa_len is not part of POSIX, and exists on MacOS and some BSDs only, so we can't
|
||||
// do this check in SetSockAddr.
|
||||
if (!(addr->sa_family == AF_INET && addr->sa_len == sizeof(struct sockaddr_in)) &&
|
||||
!(addr->sa_family == AF_INET6 && addr->sa_len == sizeof(struct sockaddr_in6))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Fill in a CService from the sockaddr, then drop the port part.
|
||||
CService service;
|
||||
if (service.SetSockAddr(addr)) {
|
||||
if (service.SetSockAddr(addr, addr->sa_len)) {
|
||||
return (CNetAddr)service;
|
||||
}
|
||||
return std::nullopt;
|
||||
|
@ -235,9 +235,9 @@ std::optional<std::vector<uint8_t>> PCPSendRecv(Sock &sock, const std::string &p
|
||||
}
|
||||
|
||||
// Wait for response(s) until we get a valid response, a network error, or time out.
|
||||
auto cur_time = time_point_cast<milliseconds>(steady_clock::now());
|
||||
auto cur_time = time_point_cast<milliseconds>(MockableSteadyClock::now());
|
||||
auto deadline = cur_time + timeout_per_try;
|
||||
while ((cur_time = time_point_cast<milliseconds>(steady_clock::now())) < deadline) {
|
||||
while ((cur_time = time_point_cast<milliseconds>(MockableSteadyClock::now())) < deadline) {
|
||||
Sock::Event occurred = 0;
|
||||
if (!sock.Wait(deadline - cur_time, Sock::RECV, &occurred)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "%s: Could not wait on socket: %s\n", protocol, NetworkErrorString(WSAGetLastError()));
|
||||
@ -426,7 +426,7 @@ std::variant<MappingResult, MappingError> PCPRequestPortMap(const PCPMappingNonc
|
||||
return MappingError::NETWORK_ERROR;
|
||||
}
|
||||
CService internal;
|
||||
if (!internal.SetSockAddr((struct sockaddr*)&internal_addr)) return MappingError::NETWORK_ERROR;
|
||||
if (!internal.SetSockAddr((struct sockaddr*)&internal_addr, internal_addrlen)) return MappingError::NETWORK_ERROR;
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "pcp: Internal address after connect: %s\n", internal.ToStringAddr());
|
||||
|
||||
// Build request packet. Make sure the packet is zeroed so that reserved fields are zero
|
||||
|
@ -387,7 +387,7 @@ static CAddress GetBindAddress(const Sock& sock)
|
||||
struct sockaddr_storage sockaddr_bind;
|
||||
socklen_t sockaddr_bind_len = sizeof(sockaddr_bind);
|
||||
if (!sock.GetSockName((struct sockaddr*)&sockaddr_bind, &sockaddr_bind_len)) {
|
||||
addr_bind.SetSockAddr((const struct sockaddr*)&sockaddr_bind);
|
||||
addr_bind.SetSockAddr((const struct sockaddr*)&sockaddr_bind, sockaddr_bind_len);
|
||||
} else {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "getsockname failed\n");
|
||||
}
|
||||
@ -1746,7 +1746,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) {
|
||||
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr, len)) {
|
||||
LogPrintLevel(BCLog::NET, BCLog::Level::Warning, "Unknown socket family\n");
|
||||
} else {
|
||||
addr = CAddress{MaybeFlipIPv6toCJDNS(addr), NODE_NONE};
|
||||
|
@ -807,13 +807,15 @@ CService::CService(const struct sockaddr_in6 &addr) : CNetAddr(addr.sin6_addr, a
|
||||
assert(addr.sin6_family == AF_INET6);
|
||||
}
|
||||
|
||||
bool CService::SetSockAddr(const struct sockaddr *paddr)
|
||||
bool CService::SetSockAddr(const struct sockaddr *paddr, socklen_t addrlen)
|
||||
{
|
||||
switch (paddr->sa_family) {
|
||||
case AF_INET:
|
||||
if (addrlen != sizeof(struct sockaddr_in)) return false;
|
||||
*this = CService(*(const struct sockaddr_in*)paddr);
|
||||
return true;
|
||||
case AF_INET6:
|
||||
if (addrlen != sizeof(struct sockaddr_in6)) return false;
|
||||
*this = CService(*(const struct sockaddr_in6*)paddr);
|
||||
return true;
|
||||
default:
|
||||
|
@ -539,7 +539,14 @@ public:
|
||||
explicit CService(const struct sockaddr_in& addr);
|
||||
uint16_t GetPort() const;
|
||||
bool GetSockAddr(struct sockaddr* paddr, socklen_t* addrlen) const;
|
||||
bool SetSockAddr(const struct sockaddr* paddr);
|
||||
/**
|
||||
* Set CService from a network sockaddr.
|
||||
* @param[in] paddr Pointer to sockaddr structure
|
||||
* @param[in] addrlen Length of sockaddr structure in bytes. This will be checked to exactly match the length of
|
||||
* a socket address of the provided family, unless std::nullopt is passed
|
||||
* @returns true on success
|
||||
*/
|
||||
bool SetSockAddr(const struct sockaddr* paddr, socklen_t addrlen);
|
||||
/**
|
||||
* Get the address family
|
||||
* @returns AF_UNSPEC if unspecified
|
||||
|
@ -87,6 +87,7 @@ add_executable(test_bitcoin
|
||||
netbase_tests.cpp
|
||||
node_warnings_tests.cpp
|
||||
orphanage_tests.cpp
|
||||
pcp_tests.cpp
|
||||
peerman_tests.cpp
|
||||
pmt_tests.cpp
|
||||
policy_fee_tests.cpp
|
||||
|
699
src/test/pcp_tests.cpp
Normal file
699
src/test/pcp_tests.cpp
Normal file
@ -0,0 +1,699 @@
|
||||
// Copyright (c) 2024 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <common/pcp.h>
|
||||
#include <netbase.h>
|
||||
#include <test/util/logging.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/time.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
/// UDP test server operation.
|
||||
struct TestOp {
|
||||
std::chrono::milliseconds delay;
|
||||
enum Op {
|
||||
SEND, // Expect send (with optional data)
|
||||
RECV, // Expect receive (with data)
|
||||
NOP, // Just delay
|
||||
} op;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
//! Injected error.
|
||||
//! Set this field to a non-zero value to return the errno code from send or receive operation.
|
||||
int error;
|
||||
|
||||
TestOp(std::chrono::milliseconds delay_in, Op op_in, const std::vector<uint8_t> &data_in, int error_in):
|
||||
delay(delay_in), op(op_in), data(data_in), error(error_in) {}
|
||||
};
|
||||
|
||||
/// Save the value of CreateSock and restore when the test ends.
|
||||
class PCPTestingSetup : public BasicTestingSetup
|
||||
{
|
||||
public:
|
||||
explicit PCPTestingSetup(const ChainType chainType = ChainType::MAIN,
|
||||
TestOpts opts = {})
|
||||
: BasicTestingSetup{chainType, opts},
|
||||
m_create_sock_orig{CreateSock}
|
||||
{
|
||||
const std::optional<CService> local_ipv4{Lookup("192.168.0.6", 1, false)};
|
||||
const std::optional<CService> local_ipv6{Lookup("2a10:1234:5678:9abc:def0:1234:5678:9abc", 1, false)};
|
||||
const std::optional<CService> gateway_ipv4{Lookup("192.168.0.1", 1, false)};
|
||||
const std::optional<CService> gateway_ipv6{Lookup("2a10:1234:5678:9abc:def0:0000:0000:0000", 1, false)};
|
||||
BOOST_REQUIRE(local_ipv4 && local_ipv6 && gateway_ipv4 && gateway_ipv6);
|
||||
default_local_ipv4 = *local_ipv4;
|
||||
default_local_ipv6 = *local_ipv6;
|
||||
default_gateway_ipv4 = *gateway_ipv4;
|
||||
default_gateway_ipv6 = *gateway_ipv6;
|
||||
|
||||
struct in_addr inaddr_any;
|
||||
inaddr_any.s_addr = htonl(INADDR_ANY);
|
||||
bind_any_ipv4 = CNetAddr(inaddr_any);
|
||||
}
|
||||
|
||||
~PCPTestingSetup()
|
||||
{
|
||||
CreateSock = m_create_sock_orig;
|
||||
MockableSteadyClock::ClearMockTime();
|
||||
}
|
||||
|
||||
// Default testing nonce.
|
||||
static constexpr PCPMappingNonce TEST_NONCE{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc};
|
||||
// Default network addresses.
|
||||
CNetAddr default_local_ipv4;
|
||||
CNetAddr default_local_ipv6;
|
||||
CNetAddr default_gateway_ipv4;
|
||||
CNetAddr default_gateway_ipv6;
|
||||
// IPv4 bind
|
||||
CNetAddr bind_any_ipv4;
|
||||
private:
|
||||
const decltype(CreateSock) m_create_sock_orig;
|
||||
};
|
||||
|
||||
/** Simple scripted UDP server emulation for testing.
|
||||
*/
|
||||
class PCPTestSock final : public Sock
|
||||
{
|
||||
public:
|
||||
// Note: we awkwardly mark all methods as const, and properties as mutable,
|
||||
// because Sock expects all networking calls to be const.
|
||||
explicit PCPTestSock(const CNetAddr &local_ip, const CNetAddr &gateway_ip, const std::vector<TestOp> &script)
|
||||
: Sock{INVALID_SOCKET},
|
||||
m_script(script),
|
||||
m_local_ip(local_ip),
|
||||
m_gateway_ip(gateway_ip)
|
||||
{
|
||||
ElapseTime(std::chrono::seconds(0)); // start mocking steady time
|
||||
PrepareOp();
|
||||
}
|
||||
|
||||
PCPTestSock& operator=(Sock&& other) override
|
||||
{
|
||||
assert(false && "Move of Sock into PCPTestSock not allowed.");
|
||||
return *this;
|
||||
}
|
||||
|
||||
ssize_t Send(const void* data, size_t len, int) const override {
|
||||
if (!m_connected) return -1;
|
||||
Span in_pkt = Span(static_cast<const uint8_t*>(data), len);
|
||||
if (AtEndOfScript() || CurOp().op != TestOp::SEND) {
|
||||
// Ignore sends after end of script, or sends when we expect a receive.
|
||||
FailScript();
|
||||
return len;
|
||||
}
|
||||
if (CurOp().error) return -1; // Inject failure
|
||||
if (CurOp().data.empty() || std::ranges::equal(CurOp().data, in_pkt)) {
|
||||
AdvanceOp();
|
||||
} else {
|
||||
// Wrong send, fail script
|
||||
FailScript();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
ssize_t Recv(void* buf, size_t len, int flags) const override
|
||||
{
|
||||
if (!m_connected || AtEndOfScript() || CurOp().op != TestOp::RECV || m_time_left != 0s) {
|
||||
return -1;
|
||||
}
|
||||
if (CurOp().error) return -1; // Inject failure
|
||||
const auto &recv_pkt = CurOp().data;
|
||||
const size_t consume_bytes{std::min(len, recv_pkt.size())};
|
||||
std::memcpy(buf, recv_pkt.data(), consume_bytes);
|
||||
if ((flags & MSG_PEEK) == 0) {
|
||||
AdvanceOp();
|
||||
}
|
||||
return consume_bytes;
|
||||
}
|
||||
|
||||
int Connect(const sockaddr* sa, socklen_t sa_len) const override {
|
||||
CService service;
|
||||
if (service.SetSockAddr(sa, sa_len) && service == CService(m_gateway_ip, 5351)) {
|
||||
if (m_bound.IsBindAny()) { // If bind-any, bind to local ip.
|
||||
m_bound = CService(m_local_ip, 0);
|
||||
}
|
||||
if (m_bound.GetPort() == 0) { // If no port assigned, assign port 1.
|
||||
m_bound = CService(m_bound, 1);
|
||||
}
|
||||
m_connected = true;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Bind(const sockaddr* sa, socklen_t sa_len) const override {
|
||||
CService service;
|
||||
if (service.SetSockAddr(sa, sa_len)) {
|
||||
// Can only bind to one of our local ips
|
||||
if (!service.IsBindAny() && service != m_local_ip) {
|
||||
return -1;
|
||||
}
|
||||
m_bound = service;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Listen(int) const override { return -1; }
|
||||
|
||||
std::unique_ptr<Sock> Accept(sockaddr* addr, socklen_t* addr_len) const override
|
||||
{
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
int GetSockOpt(int level, int opt_name, void* opt_val, socklen_t* opt_len) const override
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int SetSockOpt(int, int, const void*, socklen_t) const override { return 0; }
|
||||
|
||||
int GetSockName(sockaddr* name, socklen_t* name_len) const override
|
||||
{
|
||||
// Return the address we've been bound to.
|
||||
return m_bound.GetSockAddr(name, name_len) ? 0 : -1;
|
||||
}
|
||||
|
||||
bool SetNonBlocking() const override { return true; }
|
||||
|
||||
bool IsSelectable() const override { return true; }
|
||||
|
||||
bool Wait(std::chrono::milliseconds timeout,
|
||||
Event requested,
|
||||
Event* occurred = nullptr) const override
|
||||
{
|
||||
// Only handles receive events.
|
||||
if (AtEndOfScript() || requested != Sock::RECV) {
|
||||
ElapseTime(timeout);
|
||||
} else {
|
||||
std::chrono::milliseconds delay = std::min(m_time_left, timeout);
|
||||
ElapseTime(delay);
|
||||
m_time_left -= delay;
|
||||
if (CurOp().op == TestOp::RECV && m_time_left == 0s && occurred != nullptr) {
|
||||
*occurred = Sock::RECV;
|
||||
}
|
||||
if (CurOp().op == TestOp::NOP) {
|
||||
// This was a pure delay operation, move to the next op.
|
||||
AdvanceOp();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WaitMany(std::chrono::milliseconds timeout, EventsPerSock& events_per_sock) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsConnected(std::string&) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<TestOp> m_script;
|
||||
mutable size_t m_script_ptr = 0;
|
||||
mutable std::chrono::milliseconds m_time_left;
|
||||
mutable std::chrono::milliseconds m_time{MockableSteadyClock::INITIAL_MOCK_TIME};
|
||||
mutable bool m_connected{false};
|
||||
mutable CService m_bound;
|
||||
mutable CNetAddr m_local_ip;
|
||||
mutable CNetAddr m_gateway_ip;
|
||||
|
||||
void ElapseTime(std::chrono::milliseconds duration) const
|
||||
{
|
||||
m_time += duration;
|
||||
MockableSteadyClock::SetMockTime(m_time);
|
||||
}
|
||||
|
||||
bool AtEndOfScript() const { return m_script_ptr == m_script.size(); }
|
||||
const TestOp &CurOp() const {
|
||||
BOOST_REQUIRE(m_script_ptr < m_script.size());
|
||||
return m_script[m_script_ptr];
|
||||
}
|
||||
|
||||
void PrepareOp() const {
|
||||
if (AtEndOfScript()) return;
|
||||
m_time_left = CurOp().delay;
|
||||
}
|
||||
|
||||
void AdvanceOp() const
|
||||
{
|
||||
m_script_ptr += 1;
|
||||
PrepareOp();
|
||||
}
|
||||
|
||||
void FailScript() const { m_script_ptr = m_script.size(); }
|
||||
};
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(pcp_tests, PCPTestingSetup)
|
||||
|
||||
// NAT-PMP IPv4 good-weather scenario.
|
||||
BOOST_AUTO_TEST_CASE(natpmp_ipv4)
|
||||
{
|
||||
const std::vector<TestOp> script{
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x00, 0x00, // version, opcode (request external IP)
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
2ms, TestOp::RECV,
|
||||
{
|
||||
0x00, 0x80, 0x00, 0x00, // version, opcode (external IP), result code (success)
|
||||
0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
|
||||
0x01, 0x02, 0x03, 0x04, // external IP address
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x00, 0x02, 0x00, 0x00, // version, opcode (request map TCP)
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
|
||||
0x00, 0x00, 0x03, 0xe8, // requested mapping lifetime in seconds
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
2ms, TestOp::RECV,
|
||||
{
|
||||
0x00, 0x82, 0x00, 0x00, // version, opcode (mapped TCP)
|
||||
0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, mapped external port
|
||||
0x00, 0x00, 0x01, 0xf4, // mapping lifetime in seconds
|
||||
}, 0
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
auto res = NATPMPRequestPortMap(default_gateway_ipv4, 1234, 1000, 1, 200ms);
|
||||
|
||||
MappingResult* mapping = std::get_if<MappingResult>(&res);
|
||||
BOOST_REQUIRE(mapping);
|
||||
BOOST_CHECK_EQUAL(mapping->version, 0);
|
||||
BOOST_CHECK_EQUAL(mapping->internal.ToStringAddrPort(), "192.168.0.6:1234");
|
||||
BOOST_CHECK_EQUAL(mapping->external.ToStringAddrPort(), "1.2.3.4:1234");
|
||||
BOOST_CHECK_EQUAL(mapping->lifetime, 500);
|
||||
}
|
||||
|
||||
// PCP IPv4 good-weather scenario.
|
||||
BOOST_AUTO_TEST_CASE(pcp_ipv4)
|
||||
{
|
||||
const std::vector<TestOp> script{
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x02, 0x01, 0x00, 0x00, // version, opcode
|
||||
0x00, 0x00, 0x03, 0xe8, // lifetime
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x06, // internal IP
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // suggested external IP
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
250ms, TestOp::RECV, // 250ms delay before answer
|
||||
{
|
||||
0x02, 0x81, 0x00, 0x00, // version, opcode, result success
|
||||
0x00, 0x00, 0x01, 0xf4, // granted lifetime
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, assigned external port
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, // assigned external IP
|
||||
}, 0
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, 1, 1000ms);
|
||||
|
||||
MappingResult* mapping = std::get_if<MappingResult>(&res);
|
||||
BOOST_REQUIRE(mapping);
|
||||
BOOST_CHECK_EQUAL(mapping->version, 2);
|
||||
BOOST_CHECK_EQUAL(mapping->internal.ToStringAddrPort(), "192.168.0.6:1234");
|
||||
BOOST_CHECK_EQUAL(mapping->external.ToStringAddrPort(), "1.2.3.4:1234");
|
||||
BOOST_CHECK_EQUAL(mapping->lifetime, 500);
|
||||
}
|
||||
|
||||
// PCP IPv6 good-weather scenario.
|
||||
BOOST_AUTO_TEST_CASE(pcp_ipv6)
|
||||
{
|
||||
const std::vector<TestOp> script{
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x02, 0x01, 0x00, 0x00, // version, opcode
|
||||
0x00, 0x00, 0x03, 0xe8, // lifetime
|
||||
0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // internal IP
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
|
||||
0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
500ms, TestOp::RECV, // 500ms delay before answer
|
||||
{
|
||||
0x02, 0x81, 0x00, 0x00, // version, opcode, result success
|
||||
0x00, 0x00, 0x01, 0xf4, // granted lifetime
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, assigned external port
|
||||
0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
|
||||
}, 0
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET6 && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv6, default_gateway_ipv6, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv6, default_local_ipv6, 1234, 1000, 1, 1000ms);
|
||||
|
||||
MappingResult* mapping = std::get_if<MappingResult>(&res);
|
||||
BOOST_REQUIRE(mapping);
|
||||
BOOST_CHECK_EQUAL(mapping->version, 2);
|
||||
BOOST_CHECK_EQUAL(mapping->internal.ToStringAddrPort(), "[2a10:1234:5678:9abc:def0:1234:5678:9abc]:1234");
|
||||
BOOST_CHECK_EQUAL(mapping->external.ToStringAddrPort(), "[2a10:1234:5678:9abc:def0:1234:5678:9abc]:1234");
|
||||
BOOST_CHECK_EQUAL(mapping->lifetime, 500);
|
||||
}
|
||||
|
||||
// PCP timeout.
|
||||
BOOST_AUTO_TEST_CASE(pcp_timeout)
|
||||
{
|
||||
const std::vector<TestOp> script{};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
ASSERT_DEBUG_LOG("pcp: Retrying (1)");
|
||||
ASSERT_DEBUG_LOG("pcp: Retrying (2)");
|
||||
ASSERT_DEBUG_LOG("pcp: Timeout");
|
||||
|
||||
auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, 3, 2000ms);
|
||||
|
||||
MappingError* err = std::get_if<MappingError>(&res);
|
||||
BOOST_REQUIRE(err);
|
||||
BOOST_CHECK_EQUAL(*err, MappingError::NETWORK_ERROR);
|
||||
}
|
||||
|
||||
// PCP failure receiving (router sends ICMP port closed).
|
||||
BOOST_AUTO_TEST_CASE(pcp_connrefused)
|
||||
{
|
||||
const std::vector<TestOp> script{
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{ // May send anything.
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
0ms, TestOp::RECV,
|
||||
{
|
||||
}, ECONNREFUSED
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
ASSERT_DEBUG_LOG("pcp: Could not receive response");
|
||||
|
||||
auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, 3, 2000ms);
|
||||
|
||||
MappingError* err = std::get_if<MappingError>(&res);
|
||||
BOOST_REQUIRE(err);
|
||||
BOOST_CHECK_EQUAL(*err, MappingError::NETWORK_ERROR);
|
||||
}
|
||||
|
||||
// PCP IPv6 success after one timeout.
|
||||
BOOST_AUTO_TEST_CASE(pcp_ipv6_timeout_success)
|
||||
{
|
||||
const std::vector<TestOp> script{
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x02, 0x01, 0x00, 0x00, // version, opcode
|
||||
0x00, 0x00, 0x03, 0xe8, // lifetime
|
||||
0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // internal IP
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
|
||||
0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
2001ms, TestOp::NOP, // Takes longer to respond than timeout of 2000ms
|
||||
{}, 0
|
||||
},
|
||||
{
|
||||
0ms, TestOp::SEND, // Repeated send (try 2)
|
||||
{
|
||||
0x02, 0x01, 0x00, 0x00, // version, opcode
|
||||
0x00, 0x00, 0x03, 0xe8, // lifetime
|
||||
0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // internal IP
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
|
||||
0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
200ms, TestOp::RECV, // This time we're in time
|
||||
{
|
||||
0x02, 0x81, 0x00, 0x00, // version, opcode, result success
|
||||
0x00, 0x00, 0x01, 0xf4, // granted lifetime
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, assigned external port
|
||||
0x2a, 0x10, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, // suggested external IP
|
||||
}, 0
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET6 && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv6, default_gateway_ipv6, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
ASSERT_DEBUG_LOG("pcp: Retrying (1)");
|
||||
ASSERT_DEBUG_LOG("pcp: Timeout");
|
||||
|
||||
auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv6, default_local_ipv6, 1234, 1000, 2, 2000ms);
|
||||
|
||||
BOOST_CHECK(std::get_if<MappingResult>(&res));
|
||||
}
|
||||
|
||||
// PCP IPv4 failure (no resources).
|
||||
BOOST_AUTO_TEST_CASE(pcp_ipv4_fail_no_resources)
|
||||
{
|
||||
const std::vector<TestOp> script{
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x02, 0x01, 0x00, 0x00, // version, opcode
|
||||
0x00, 0x00, 0x03, 0xe8, // lifetime
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x06, // internal IP
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // suggested external IP
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
500ms, TestOp::RECV,
|
||||
{
|
||||
0x02, 0x81, 0x00, 0x08, // version, opcode, result 0x08: no resources
|
||||
0x00, 0x00, 0x00, 0x00, // granted lifetime
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x00, 0x00, // internal port, assigned external port
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // assigned external IP
|
||||
}, 0
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, 3, 1000ms);
|
||||
|
||||
MappingError* err = std::get_if<MappingError>(&res);
|
||||
BOOST_REQUIRE(err);
|
||||
BOOST_CHECK_EQUAL(*err, MappingError::NO_RESOURCES);
|
||||
}
|
||||
|
||||
// PCP IPv4 failure (test NATPMP downgrade scenario).
|
||||
BOOST_AUTO_TEST_CASE(pcp_ipv4_fail_unsupported_version)
|
||||
{
|
||||
const std::vector<TestOp> script{
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x02, 0x01, 0x00, 0x00, // version, opcode
|
||||
0x00, 0x00, 0x03, 0xe8, // lifetime
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x06, // internal IP
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // suggested external IP
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
500ms, TestOp::RECV,
|
||||
{
|
||||
0x00, 0x81, 0x00, 0x01, // version, opcode, result 0x01: unsupported version
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}, 0
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, 3, 1000ms);
|
||||
|
||||
MappingError* err = std::get_if<MappingError>(&res);
|
||||
BOOST_REQUIRE(err);
|
||||
BOOST_CHECK_EQUAL(*err, MappingError::UNSUPP_VERSION);
|
||||
}
|
||||
|
||||
// NAT-PMP IPv4 protocol error scenarii.
|
||||
BOOST_AUTO_TEST_CASE(natpmp_protocol_error)
|
||||
{
|
||||
// First scenario: non-0 result code when requesting external IP.
|
||||
std::vector<TestOp> script{
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x00, 0x00, // version, opcode (request external IP)
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
2ms, TestOp::RECV,
|
||||
{
|
||||
0x00, 0x80, 0x00, 0x42, // version, opcode (external IP), result code (*NOT* success)
|
||||
0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
|
||||
0x01, 0x02, 0x03, 0x04, // external IP address
|
||||
}, 0
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
auto res = NATPMPRequestPortMap(default_gateway_ipv4, 1234, 1000, 1, 200ms);
|
||||
|
||||
MappingError* err = std::get_if<MappingError>(&res);
|
||||
BOOST_REQUIRE(err);
|
||||
BOOST_CHECK_EQUAL(*err, MappingError::PROTOCOL_ERROR);
|
||||
|
||||
// First scenario: non-0 result code when requesting port mapping.
|
||||
script = {
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x00, 0x00, // version, opcode (request external IP)
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
2ms, TestOp::RECV,
|
||||
{
|
||||
0x00, 0x80, 0x00, 0x00, // version, opcode (external IP), result code (success)
|
||||
0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
|
||||
0x01, 0x02, 0x03, 0x04, // external IP address
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x00, 0x02, 0x00, 0x00, // version, opcode (request map TCP)
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
|
||||
0x00, 0x00, 0x03, 0xe8, // requested mapping lifetime in seconds
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
2ms, TestOp::RECV,
|
||||
{
|
||||
0x00, 0x82, 0x00, 0x43, // version, opcode (mapped TCP)
|
||||
0x66, 0xfd, 0xa1, 0xee, // seconds sinds start of epoch
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, mapped external port
|
||||
0x00, 0x00, 0x01, 0xf4, // mapping lifetime in seconds
|
||||
}, 0
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
res = NATPMPRequestPortMap(default_gateway_ipv4, 1234, 1000, 1, 200ms);
|
||||
|
||||
err = std::get_if<MappingError>(&res);
|
||||
BOOST_REQUIRE(err);
|
||||
BOOST_CHECK_EQUAL(*err, MappingError::PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
// PCP IPv4 protocol error scenario.
|
||||
BOOST_AUTO_TEST_CASE(pcp_protocol_error)
|
||||
{
|
||||
const std::vector<TestOp> script{
|
||||
{
|
||||
0ms, TestOp::SEND,
|
||||
{
|
||||
0x02, 0x01, 0x00, 0x00, // version, opcode
|
||||
0x00, 0x00, 0x03, 0xe8, // lifetime
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x06, // internal IP
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, suggested external port
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, // suggested external IP
|
||||
}, 0
|
||||
},
|
||||
{
|
||||
250ms, TestOp::RECV, // 250ms delay before answer
|
||||
{
|
||||
0x02, 0x81, 0x00, 0x42, // version, opcode, result error
|
||||
0x00, 0x00, 0x01, 0xf4, // granted lifetime
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, // nonce
|
||||
0x06, 0x00, 0x00, 0x00, // protocol (TCP), reserved
|
||||
0x04, 0xd2, 0x04, 0xd2, // internal port, assigned external port
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, // assigned external IP
|
||||
}, 0
|
||||
},
|
||||
};
|
||||
CreateSock = [this, &script](int domain, int type, int protocol) {
|
||||
if (domain == AF_INET && type == SOCK_DGRAM && protocol == IPPROTO_UDP) return std::make_unique<PCPTestSock>(default_local_ipv4, default_gateway_ipv4, script);
|
||||
return std::unique_ptr<PCPTestSock>();
|
||||
};
|
||||
|
||||
auto res = PCPRequestPortMap(TEST_NONCE, default_gateway_ipv4, bind_any_ipv4, 1234, 1000, 1, 1000ms);
|
||||
|
||||
MappingError* err = std::get_if<MappingError>(&res);
|
||||
BOOST_REQUIRE(err);
|
||||
BOOST_CHECK_EQUAL(*err, MappingError::PROTOCOL_ERROR);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -21,6 +21,7 @@ void UninterruptibleSleep(const std::chrono::microseconds& n) { std::this_thread
|
||||
|
||||
static std::atomic<std::chrono::seconds> g_mock_time{}; //!< For testing
|
||||
std::atomic<bool> g_used_system_time{false};
|
||||
static std::atomic<std::chrono::milliseconds> g_mock_steady_time{}; //!< For testing
|
||||
|
||||
NodeClock::time_point NodeClock::now() noexcept
|
||||
{
|
||||
@ -48,6 +49,30 @@ std::chrono::seconds GetMockTime()
|
||||
return g_mock_time.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
MockableSteadyClock::time_point MockableSteadyClock::now() noexcept
|
||||
{
|
||||
const auto mocktime{g_mock_steady_time.load(std::memory_order_relaxed)};
|
||||
if (!mocktime.count()) {
|
||||
g_used_system_time = true;
|
||||
}
|
||||
const auto ret{
|
||||
mocktime.count() ?
|
||||
mocktime :
|
||||
std::chrono::steady_clock::now().time_since_epoch()};
|
||||
return time_point{ret};
|
||||
};
|
||||
|
||||
void MockableSteadyClock::SetMockTime(std::chrono::milliseconds mock_time_in)
|
||||
{
|
||||
Assert(mock_time_in >= 0s);
|
||||
g_mock_steady_time.store(mock_time_in, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void MockableSteadyClock::ClearMockTime()
|
||||
{
|
||||
g_mock_steady_time.store(0ms, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int64_t GetTime() { return GetTime<std::chrono::seconds>().count(); }
|
||||
|
||||
std::string FormatISO8601DateTime(int64_t nTime)
|
||||
|
@ -31,6 +31,31 @@ using SteadyMicroseconds = std::chrono::time_point<std::chrono::steady_clock, st
|
||||
|
||||
using SystemClock = std::chrono::system_clock;
|
||||
|
||||
/**
|
||||
* Version of SteadyClock that is mockable in the context of tests (set the
|
||||
* current value with SetMockTime), otherwise the system steady clock.
|
||||
*/
|
||||
struct MockableSteadyClock : public std::chrono::steady_clock {
|
||||
using time_point = std::chrono::time_point<MockableSteadyClock>;
|
||||
|
||||
static constexpr std::chrono::milliseconds INITIAL_MOCK_TIME{1};
|
||||
|
||||
/** Return current system time or mocked time, if set */
|
||||
static time_point now() noexcept;
|
||||
static std::time_t to_time_t(const time_point&) = delete; // unused
|
||||
static time_point from_time_t(std::time_t) = delete; // unused
|
||||
|
||||
/** Set mock time for testing.
|
||||
* When mocking the steady clock, start at INITIAL_MOCK_TIME and add durations to elapse time as necessary
|
||||
* for testing.
|
||||
* To stop mocking, call ClearMockTime().
|
||||
*/
|
||||
static void SetMockTime(std::chrono::milliseconds mock_time_in);
|
||||
|
||||
/** Clear mock time, go back to system steady clock. */
|
||||
static void ClearMockTime();
|
||||
};
|
||||
|
||||
void UninterruptibleSleep(const std::chrono::microseconds& n);
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user