forked from Green-Sky/tomato
Squashed 'external/toxcore/c-toxcore/' changes from 1828c5356..c9cdae001
c9cdae001 fix(toxav): remove extra copy of video frame on encode 4f6d4546b test: Improve the fake network library. a2581e700 refactor(toxcore): generate `Friend_Request` and `Dht_Nodes_Response` 2aaa11770 refactor(toxcore): use Tox_Memory in generated events 5c367452b test(toxcore): fix incorrect mutex in tox_scenario_get_time 8f92e710f perf: Add a timed limit of number of cookie requests. 695b6417a test: Add some more simulated network support. 815ae9ce9 test(toxcore): fix thread-safety in scenario framework 6d85c754e test(toxcore): add unit tests for net_crypto 9c22e79cc test(support): add SimulatedEnvironment for deterministic testing f34fcb195 chore: Update windows Dockerfile to debian stable (trixie). ece0e8980 fix(group_moderation): allow validating unsorted sanction list signatures a4fa754d7 refactor: rename struct Packet to struct Net_Packet d6f330f85 cleanup: Fix some warnings from coverity. e206bffa2 fix(group_chats): fix sync packets reverting topics 0e4715598 test: Add new scenario testing framework. 668291f44 refactor(toxcore): decouple Network_Funcs from sockaddr via IP_Port fc4396cef fix: potential division by zero in toxav and unsafe hex parsing 8e8b352ab refactor: Add nullable annotations to struct members. 7740bb421 refactor: decouple net_crypto from DHT 1936d4296 test: add benchmark for toxav audio and video 46bfdc2df fix: correct printf format specifiers for unsigned integers REVERT: 1828c5356 fix(toxav): remove extra copy of video frame on encode git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: c9cdae001341e701fca980c9bb9febfeb95d2902
This commit is contained in:
23
testing/support/doubles/fake_clock.hh
Normal file
23
testing/support/doubles/fake_clock.hh
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
|
||||
|
||||
#include "../public/clock.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeClock : public ClockSystem {
|
||||
public:
|
||||
explicit FakeClock(uint64_t start_time_ms = 1000);
|
||||
|
||||
uint64_t current_time_ms() const override;
|
||||
uint64_t current_time_s() const override;
|
||||
|
||||
void advance(uint64_t ms);
|
||||
|
||||
private:
|
||||
uint64_t now_ms_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
|
||||
51
testing/support/doubles/fake_memory.hh
Normal file
51
testing/support/doubles/fake_memory.hh
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "../public/memory.hh"
|
||||
|
||||
// Forward declaration
|
||||
struct Tox_Memory;
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeMemory : public MemorySystem {
|
||||
public:
|
||||
using FailureInjector = std::function<bool(size_t size)>; // Return true to fail
|
||||
using Observer = std::function<void(bool success)>;
|
||||
|
||||
FakeMemory();
|
||||
~FakeMemory() override;
|
||||
|
||||
void *malloc(size_t size) override;
|
||||
void *realloc(void *ptr, size_t size) override;
|
||||
void free(void *ptr) override;
|
||||
|
||||
// Configure failure injection
|
||||
void set_failure_injector(FailureInjector injector);
|
||||
|
||||
// Configure observer
|
||||
void set_observer(Observer observer);
|
||||
|
||||
// Get the C-compatible struct
|
||||
struct Tox_Memory get_c_memory();
|
||||
|
||||
private:
|
||||
struct Header {
|
||||
size_t size;
|
||||
size_t magic;
|
||||
};
|
||||
static constexpr size_t kMagic = 0xDEADC0DE;
|
||||
static constexpr size_t kFreeMagic = 0xBAADF00D;
|
||||
|
||||
size_t current_allocation_ = 0;
|
||||
size_t max_allocation_ = 0;
|
||||
|
||||
FailureInjector failure_injector_;
|
||||
Observer observer_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
|
||||
299
testing/support/doubles/fake_network_stack.cc
Normal file
299
testing/support/doubles/fake_network_stack.cc
Normal file
@@ -0,0 +1,299 @@
|
||||
#include "fake_network_stack.hh"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "../../../toxcore/mem.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
static const Network_Funcs kVtable = {
|
||||
.close
|
||||
= [](void *obj, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->close(sock); },
|
||||
.accept
|
||||
= [](void *obj, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->accept(sock); },
|
||||
.bind
|
||||
= [](void *obj, Socket sock,
|
||||
const IP_Port *addr) { return static_cast<FakeNetworkStack *>(obj)->bind(sock, addr); },
|
||||
.listen
|
||||
= [](void *obj, Socket sock,
|
||||
int backlog) { return static_cast<FakeNetworkStack *>(obj)->listen(sock, backlog); },
|
||||
.connect =
|
||||
[](void *obj, Socket sock, const IP_Port *addr) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->connect(sock, addr);
|
||||
},
|
||||
.recvbuf
|
||||
= [](void *obj, Socket sock) { return static_cast<FakeNetworkStack *>(obj)->recvbuf(sock); },
|
||||
.recv = [](void *obj, Socket sock, uint8_t *buf,
|
||||
size_t len) { return static_cast<FakeNetworkStack *>(obj)->recv(sock, buf, len); },
|
||||
.recvfrom =
|
||||
[](void *obj, Socket sock, uint8_t *buf, size_t len, IP_Port *addr) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->recvfrom(sock, buf, len, addr);
|
||||
},
|
||||
.send = [](void *obj, Socket sock, const uint8_t *buf,
|
||||
size_t len) { return static_cast<FakeNetworkStack *>(obj)->send(sock, buf, len); },
|
||||
.sendto =
|
||||
[](void *obj, Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->sendto(sock, buf, len, addr);
|
||||
},
|
||||
.socket
|
||||
= [](void *obj, int domain, int type,
|
||||
int proto) { return static_cast<FakeNetworkStack *>(obj)->socket(domain, type, proto); },
|
||||
.socket_nonblock =
|
||||
[](void *obj, Socket sock, bool nonblock) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->socket_nonblock(sock, nonblock);
|
||||
},
|
||||
.getsockopt =
|
||||
[](void *obj, Socket sock, int level, int optname, void *optval, size_t *optlen) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->getsockopt(
|
||||
sock, level, optname, optval, optlen);
|
||||
},
|
||||
.setsockopt =
|
||||
[](void *obj, Socket sock, int level, int optname, const void *optval, size_t optlen) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->setsockopt(
|
||||
sock, level, optname, optval, optlen);
|
||||
},
|
||||
.getaddrinfo =
|
||||
[](void *obj, const Memory *mem, const char *address, int family, int protocol,
|
||||
IP_Port **addrs) {
|
||||
FakeNetworkStack *self = static_cast<FakeNetworkStack *>(obj);
|
||||
if (self->universe().is_verbose()) {
|
||||
std::cerr << "[FakeNetworkStack] getaddrinfo for " << address << std::endl;
|
||||
}
|
||||
if (strcmp(address, "127.0.0.1") == 0 || strcmp(address, "localhost") == 0) {
|
||||
*addrs = static_cast<IP_Port *>(mem_alloc(mem, sizeof(IP_Port)));
|
||||
memset(&(*addrs)->ip, 0, sizeof(IP));
|
||||
ip_init(&(*addrs)->ip, false);
|
||||
(*addrs)->ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
(*addrs)->port = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
IP ip;
|
||||
if (addr_parse_ip(address, &ip)) {
|
||||
*addrs = static_cast<IP_Port *>(mem_alloc(mem, sizeof(IP_Port)));
|
||||
(*addrs)->ip = ip;
|
||||
(*addrs)->port = 0;
|
||||
if (self->universe().is_verbose()) {
|
||||
std::cerr << "[FakeNetworkStack] resolved " << address << std::endl;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
.freeaddrinfo =
|
||||
[](void *obj, const Memory *mem, IP_Port *addrs) {
|
||||
mem_delete(mem, addrs);
|
||||
return 0;
|
||||
},
|
||||
};
|
||||
|
||||
FakeNetworkStack::FakeNetworkStack(NetworkUniverse &universe, const IP &node_ip)
|
||||
: universe_(universe)
|
||||
, node_ip_(node_ip)
|
||||
{
|
||||
}
|
||||
|
||||
FakeNetworkStack::~FakeNetworkStack() = default;
|
||||
|
||||
struct Network FakeNetworkStack::get_c_network() { return Network{&kVtable, this}; }
|
||||
|
||||
Socket FakeNetworkStack::socket(int domain, int type, int protocol)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
int fd = next_fd_++;
|
||||
|
||||
std::unique_ptr<FakeSocket> sock;
|
||||
if (type == SOCK_DGRAM) {
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeNetworkStack] create UDP socket fd=" << fd << std::endl;
|
||||
}
|
||||
sock = std::make_unique<FakeUdpSocket>(universe_);
|
||||
} else if (type == SOCK_STREAM) {
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeNetworkStack] create TCP socket fd=" << fd << std::endl;
|
||||
}
|
||||
sock = std::make_unique<FakeTcpSocket>(universe_);
|
||||
} else {
|
||||
// Unknown type
|
||||
return net_socket_from_native(-1);
|
||||
}
|
||||
|
||||
sockets_[fd] = std::move(sock);
|
||||
sockets_[fd]->set_ip(node_ip_);
|
||||
return net_socket_from_native(fd);
|
||||
}
|
||||
|
||||
FakeSocket *FakeNetworkStack::get_sock(Socket sock)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto it = sockets_.find(net_socket_to_native(sock));
|
||||
if (it != sockets_.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::close(Socket sock)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
int fd = net_socket_to_native(sock);
|
||||
auto it = sockets_.find(fd);
|
||||
if (it == sockets_.end()) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
it->second->close();
|
||||
sockets_.erase(it);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Delegate all others
|
||||
int FakeNetworkStack::bind(Socket sock, const IP_Port *addr)
|
||||
{
|
||||
if (auto *s = get_sock(sock)) {
|
||||
int ret = s->bind(addr);
|
||||
if (universe_.is_verbose() && ret == 0) {
|
||||
char ip_str[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&s->ip_address(), ip_str, sizeof(ip_str));
|
||||
std::cerr << "[FakeNetworkStack] bound socket to " << ip_str << ":" << s->local_port()
|
||||
<< std::endl;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::connect(Socket sock, const IP_Port *addr)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->connect(addr);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::listen(Socket sock, int backlog)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->listen(backlog);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Socket FakeNetworkStack::accept(Socket sock)
|
||||
{
|
||||
// This requires creating a new FD
|
||||
IP_Port addr;
|
||||
std::unique_ptr<FakeSocket> new_sock_obj;
|
||||
|
||||
{
|
||||
auto *s = get_sock(sock);
|
||||
if (!s) {
|
||||
errno = EBADF;
|
||||
return net_socket_from_native(-1);
|
||||
}
|
||||
new_sock_obj = s->accept(&addr);
|
||||
}
|
||||
|
||||
if (!new_sock_obj) {
|
||||
// errno set by accept
|
||||
return net_socket_from_native(-1);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
int fd = next_fd_++;
|
||||
sockets_[fd] = std::move(new_sock_obj);
|
||||
return net_socket_from_native(fd);
|
||||
}
|
||||
|
||||
int FakeNetworkStack::send(Socket sock, const uint8_t *buf, size_t len)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->send(buf, len);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::recv(Socket sock, uint8_t *buf, size_t len)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->recv(buf, len);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::recvbuf(Socket sock)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->recv_buffer_size();
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->sendto(buf, len, addr);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->recvfrom(buf, len, addr);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::socket_nonblock(Socket sock, bool nonblock)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->socket_nonblock(nonblock);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->getsockopt(level, optname, optval, optlen);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeNetworkStack::setsockopt(
|
||||
Socket sock, int level, int optname, const void *optval, size_t optlen)
|
||||
{
|
||||
if (auto *s = get_sock(sock))
|
||||
return s->setsockopt(level, optname, optval, optlen);
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
FakeUdpSocket *FakeNetworkStack::get_udp_socket(Socket sock)
|
||||
{
|
||||
if (auto *s = get_sock(sock)) {
|
||||
if (s->type() == SOCK_DGRAM) {
|
||||
return static_cast<FakeUdpSocket *>(s);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<FakeUdpSocket *> FakeNetworkStack::get_bound_udp_sockets()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<FakeUdpSocket *> result;
|
||||
for (const auto &pair : sockets_) {
|
||||
FakeSocket *s = pair.second.get();
|
||||
if (s->type() == SOCK_DGRAM && s->local_port() != 0) {
|
||||
result.push_back(static_cast<FakeUdpSocket *>(s));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
56
testing/support/doubles/fake_network_stack.hh
Normal file
56
testing/support/doubles/fake_network_stack.hh
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
#include "../public/network.hh"
|
||||
#include "fake_sockets.hh"
|
||||
#include "network_universe.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeNetworkStack : public NetworkSystem {
|
||||
public:
|
||||
explicit FakeNetworkStack(NetworkUniverse &universe, const IP &node_ip);
|
||||
~FakeNetworkStack() override;
|
||||
|
||||
// NetworkSystem Implementation
|
||||
Socket socket(int domain, int type, int protocol) override;
|
||||
int bind(Socket sock, const IP_Port *addr) override;
|
||||
int close(Socket sock) override;
|
||||
int sendto(Socket sock, const uint8_t *buf, size_t len, const IP_Port *addr) override;
|
||||
int recvfrom(Socket sock, uint8_t *buf, size_t len, IP_Port *addr) override;
|
||||
|
||||
int listen(Socket sock, int backlog) override;
|
||||
Socket accept(Socket sock) override;
|
||||
int connect(Socket sock, const IP_Port *addr) override;
|
||||
int send(Socket sock, const uint8_t *buf, size_t len) override;
|
||||
int recv(Socket sock, uint8_t *buf, size_t len) override;
|
||||
int recvbuf(Socket sock) override;
|
||||
|
||||
int socket_nonblock(Socket sock, bool nonblock) override;
|
||||
int getsockopt(Socket sock, int level, int optname, void *optval, size_t *optlen) override;
|
||||
int setsockopt(Socket sock, int level, int optname, const void *optval, size_t optlen) override;
|
||||
|
||||
struct Network get_c_network();
|
||||
|
||||
// For testing/fuzzing introspection
|
||||
FakeUdpSocket *get_udp_socket(Socket sock);
|
||||
std::vector<FakeUdpSocket *> get_bound_udp_sockets();
|
||||
|
||||
NetworkUniverse &universe() { return universe_; }
|
||||
|
||||
private:
|
||||
FakeSocket *get_sock(Socket sock);
|
||||
|
||||
NetworkUniverse &universe_;
|
||||
std::map<int, std::unique_ptr<FakeSocket>> sockets_;
|
||||
int next_fd_ = 100;
|
||||
IP node_ip_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H
|
||||
91
testing/support/doubles/fake_network_stack_test.cc
Normal file
91
testing/support/doubles/fake_network_stack_test.cc
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "fake_network_stack.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "network_universe.hh"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
namespace tox::test {
|
||||
namespace {
|
||||
|
||||
class FakeNetworkStackTest : public ::testing::Test {
|
||||
public:
|
||||
FakeNetworkStackTest()
|
||||
: stack{universe, make_ip(0x7F000001)}
|
||||
{
|
||||
}
|
||||
~FakeNetworkStackTest() override;
|
||||
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
FakeNetworkStack stack;
|
||||
};
|
||||
|
||||
FakeNetworkStackTest::~FakeNetworkStackTest() = default;
|
||||
|
||||
TEST_F(FakeNetworkStackTest, SocketCreationAndLifecycle)
|
||||
{
|
||||
Socket udp_sock = stack.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
ASSERT_NE(net_socket_to_native(udp_sock), -1);
|
||||
|
||||
// Check introspection
|
||||
ASSERT_NE(stack.get_udp_socket(udp_sock), nullptr);
|
||||
|
||||
// Bind
|
||||
IP_Port addr;
|
||||
ip_init(&addr.ip, false);
|
||||
addr.ip.ip.v4.uint32 = 0;
|
||||
addr.port = net_htons(9002);
|
||||
ASSERT_EQ(stack.bind(udp_sock, &addr), 0);
|
||||
|
||||
// Check introspection again
|
||||
auto sockets = stack.get_bound_udp_sockets();
|
||||
ASSERT_EQ(sockets.size(), 1);
|
||||
EXPECT_EQ(sockets[0]->local_port(), 9002);
|
||||
|
||||
ASSERT_EQ(stack.close(udp_sock), 0);
|
||||
ASSERT_EQ(stack.get_bound_udp_sockets().size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkStackTest, TcpSocketThroughStack)
|
||||
{
|
||||
Socket tcp_sock = stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
ASSERT_NE(net_socket_to_native(tcp_sock), -1);
|
||||
|
||||
IP_Port addr;
|
||||
ip_init(&addr.ip, false);
|
||||
addr.ip.ip.v4.uint32 = 0;
|
||||
addr.port = net_htons(9003);
|
||||
ASSERT_EQ(stack.bind(tcp_sock, &addr), 0);
|
||||
ASSERT_EQ(stack.listen(tcp_sock, 5), 0);
|
||||
|
||||
// Connect from another stack
|
||||
FakeNetworkStack client_stack{universe, make_ip(0x7F000002)};
|
||||
Socket client_sock = client_stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001); // Localhost
|
||||
server_addr.port = net_htons(9003);
|
||||
|
||||
ASSERT_EQ(client_stack.connect(client_sock, &server_addr), -1);
|
||||
ASSERT_EQ(errno, EINPROGRESS);
|
||||
|
||||
universe.process_events(0); // SYN
|
||||
universe.process_events(0); // SYN-ACK
|
||||
universe.process_events(0); // ACK
|
||||
|
||||
Socket accepted = stack.accept(tcp_sock);
|
||||
ASSERT_NE(net_socket_to_native(accepted), -1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
45
testing/support/doubles/fake_random.hh
Normal file
45
testing/support/doubles/fake_random.hh
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_RANDOM_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_RANDOM_H
|
||||
|
||||
#include <functional>
|
||||
#include <random>
|
||||
|
||||
#include "../public/random.hh"
|
||||
|
||||
// Forward declaration
|
||||
struct Tox_Random;
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeRandom : public RandomSystem {
|
||||
public:
|
||||
using EntropySource = std::function<void(uint8_t *out, size_t count)>;
|
||||
using Observer = std::function<void(const uint8_t *data, size_t count)>;
|
||||
|
||||
explicit FakeRandom(uint64_t seed);
|
||||
|
||||
uint32_t uniform(uint32_t upper_bound) override;
|
||||
void bytes(uint8_t *out, size_t count) override;
|
||||
|
||||
/**
|
||||
* @brief Set a custom entropy source.
|
||||
* If set, this function will be called to generate random bytes instead of the PRNG.
|
||||
*/
|
||||
void set_entropy_source(EntropySource source);
|
||||
|
||||
/**
|
||||
* @brief Set an observer to record generated bytes.
|
||||
*/
|
||||
void set_observer(Observer observer);
|
||||
|
||||
struct Tox_Random get_c_random();
|
||||
|
||||
private:
|
||||
std::minstd_rand rng_;
|
||||
EntropySource entropy_source_;
|
||||
Observer observer_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_RANDOM_H
|
||||
485
testing/support/doubles/fake_sockets.cc
Normal file
485
testing/support/doubles/fake_sockets.cc
Normal file
@@ -0,0 +1,485 @@
|
||||
#include "fake_sockets.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "network_universe.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
// --- FakeSocket ---
|
||||
|
||||
FakeSocket::FakeSocket(NetworkUniverse &universe, int type)
|
||||
: universe_(universe)
|
||||
, type_(type)
|
||||
{
|
||||
ip_init(&ip_, false);
|
||||
ip_.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
}
|
||||
|
||||
FakeSocket::~FakeSocket() = default;
|
||||
|
||||
int FakeSocket::close()
|
||||
{
|
||||
// Override in subclasses to unbind
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FakeSocket::getsockopt(int level, int optname, void *optval, size_t *optlen) { return 0; }
|
||||
int FakeSocket::setsockopt(int level, int optname, const void *optval, size_t optlen) { return 0; }
|
||||
int FakeSocket::socket_nonblock(bool nonblock)
|
||||
{
|
||||
nonblocking_ = nonblock;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- FakeUdpSocket ---
|
||||
|
||||
FakeUdpSocket::FakeUdpSocket(NetworkUniverse &universe)
|
||||
: FakeSocket(universe, SOCK_DGRAM)
|
||||
{
|
||||
}
|
||||
|
||||
FakeUdpSocket::~FakeUdpSocket() { close_impl(); }
|
||||
|
||||
int FakeUdpSocket::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
close_impl();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FakeUdpSocket::close_impl()
|
||||
{
|
||||
if (local_port_ != 0) {
|
||||
universe_.unbind_udp(ip_, local_port_);
|
||||
local_port_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int FakeUdpSocket::bind(const IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ != 0)
|
||||
return -1; // Already bound
|
||||
|
||||
uint16_t port = addr->port;
|
||||
if (port == 0) {
|
||||
port = universe_.find_free_port(ip_);
|
||||
} else {
|
||||
port = net_ntohs(port);
|
||||
}
|
||||
|
||||
if (universe_.bind_udp(ip_, port, this)) {
|
||||
local_port_ = port;
|
||||
return 0;
|
||||
}
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::connect(const IP_Port *addr)
|
||||
{
|
||||
// UDP connect just sets default dest.
|
||||
// Not strictly needed for toxcore UDP but good for completeness.
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::listen(int backlog)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
std::unique_ptr<FakeSocket> FakeUdpSocket::accept(IP_Port *addr)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return nullptr;
|
||||
}
|
||||
int FakeUdpSocket::send(const uint8_t *buf, size_t len)
|
||||
{
|
||||
errno = EDESTADDRREQ;
|
||||
return -1;
|
||||
}
|
||||
int FakeUdpSocket::recv(uint8_t *buf, size_t len)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ == 0) {
|
||||
// Implicit bind
|
||||
uint16_t p = universe_.find_free_port(ip_);
|
||||
if (universe_.bind_udp(ip_, p, this)) {
|
||||
local_port_ = p;
|
||||
} else {
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Packet p{};
|
||||
// Source
|
||||
p.from.ip = ip_;
|
||||
p.from.port = net_htons(local_port_);
|
||||
p.to = *addr;
|
||||
p.data.assign(buf, buf + len);
|
||||
p.is_tcp = false;
|
||||
|
||||
universe_.send_packet(p);
|
||||
if (universe_.is_verbose()) {
|
||||
uint32_t tip4 = net_ntohl(addr->ip.ip.v4.uint32);
|
||||
std::cerr << "[FakeUdpSocket] sent " << len << " bytes from port " << local_port_ << " to "
|
||||
<< ((tip4 >> 24) & 0xFF) << "." << ((tip4 >> 16) & 0xFF) << "."
|
||||
<< ((tip4 >> 8) & 0xFF) << "." << (tip4 & 0xFF) << ":" << net_ntohs(addr->port)
|
||||
<< std::endl;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr)
|
||||
{
|
||||
RecvObserver observer_copy;
|
||||
std::vector<uint8_t> data_copy;
|
||||
IP_Port from_copy;
|
||||
size_t copy_len = 0;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (recv_queue_.empty() && packet_source_) {
|
||||
// NOTE: We call packet_source_ with lock held.
|
||||
// Be careful not to call back into socket methods from packet_source_.
|
||||
std::vector<uint8_t> data;
|
||||
IP_Port from;
|
||||
if (packet_source_(data, from)) {
|
||||
recv_queue_.push_back({std::move(data), from});
|
||||
}
|
||||
}
|
||||
|
||||
if (recv_queue_.empty()) {
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto &p = recv_queue_.front();
|
||||
copy_len = std::min(len, p.data.size());
|
||||
std::memcpy(buf, p.data.data(), copy_len);
|
||||
*addr = p.from;
|
||||
|
||||
if (recv_observer_) {
|
||||
observer_copy = recv_observer_;
|
||||
data_copy = p.data;
|
||||
from_copy = p.from;
|
||||
}
|
||||
|
||||
recv_queue_.pop_front();
|
||||
}
|
||||
|
||||
if (observer_copy) {
|
||||
observer_copy(data_copy, from_copy);
|
||||
}
|
||||
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeUdpSocket] recv " << copy_len << " bytes at port " << local_port_
|
||||
<< " from port " << net_ntohs(addr->port) << std::endl;
|
||||
}
|
||||
|
||||
return copy_len;
|
||||
}
|
||||
|
||||
void FakeUdpSocket::push_packet(std::vector<uint8_t> data, IP_Port from)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (universe_.is_verbose()) {
|
||||
uint32_t fip4 = net_ntohl(from.ip.ip.v4.uint32);
|
||||
std::cerr << "[FakeUdpSocket] push " << data.size() << " bytes into queue for "
|
||||
<< ((ip_.ip.v4.uint32 >> 24) & 0xFF)
|
||||
<< "." // ip_ is in network order from net_htonl
|
||||
<< ((ip_.ip.v4.uint32 >> 16) & 0xFF) << "." << ((ip_.ip.v4.uint32 >> 8) & 0xFF)
|
||||
<< "." << (ip_.ip.v4.uint32 & 0xFF) << ":" << local_port_ << " from "
|
||||
<< ((fip4 >> 24) & 0xFF) << "." << ((fip4 >> 16) & 0xFF) << "."
|
||||
<< ((fip4 >> 8) & 0xFF) << "." << (fip4 & 0xFF) << ":" << net_ntohs(from.port)
|
||||
<< std::endl;
|
||||
}
|
||||
recv_queue_.push_back({std::move(data), from});
|
||||
}
|
||||
|
||||
void FakeUdpSocket::set_packet_source(PacketSource source)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
packet_source_ = std::move(source);
|
||||
}
|
||||
|
||||
void FakeUdpSocket::set_recv_observer(RecvObserver observer)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
recv_observer_ = std::move(observer);
|
||||
}
|
||||
|
||||
// --- FakeTcpSocket ---
|
||||
|
||||
FakeTcpSocket::FakeTcpSocket(NetworkUniverse &universe)
|
||||
: FakeSocket(universe, SOCK_STREAM)
|
||||
, remote_addr_{}
|
||||
{
|
||||
}
|
||||
|
||||
FakeTcpSocket::~FakeTcpSocket() { close_impl(); }
|
||||
|
||||
int FakeTcpSocket::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
close_impl();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FakeTcpSocket::close_impl()
|
||||
{
|
||||
if (local_port_ != 0) {
|
||||
universe_.unbind_tcp(ip_, local_port_, this);
|
||||
local_port_ = 0;
|
||||
}
|
||||
state_ = CLOSED;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::bind(const IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ != 0)
|
||||
return -1;
|
||||
|
||||
uint16_t port = addr->port;
|
||||
if (port == 0) {
|
||||
port = universe_.find_free_port(ip_);
|
||||
} else {
|
||||
port = net_ntohs(port);
|
||||
}
|
||||
|
||||
if (universe_.bind_tcp(ip_, port, this)) {
|
||||
local_port_ = port;
|
||||
return 0;
|
||||
}
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::listen(int backlog)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
state_ = LISTEN;
|
||||
backlog_ = backlog;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::connect(const IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ == 0) {
|
||||
// Implicit bind
|
||||
uint16_t p = universe_.find_free_port(ip_);
|
||||
if (universe_.bind_tcp(ip_, p, this)) {
|
||||
local_port_ = p;
|
||||
} else {
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
remote_addr_ = *addr;
|
||||
state_ = SYN_SENT;
|
||||
|
||||
Packet p{};
|
||||
p.from.ip = ip_;
|
||||
p.from.port = net_htons(local_port_);
|
||||
p.to = *addr;
|
||||
p.is_tcp = true;
|
||||
p.tcp_flags = 0x02; // SYN
|
||||
p.seq = next_seq_;
|
||||
|
||||
universe_.send_packet(p);
|
||||
|
||||
// Non-blocking connect not fully simulated (we return 0 but state is SYN_SENT).
|
||||
// Real connect() blocks or returns EINPROGRESS.
|
||||
// For simplicity, we assume the test will pump events until connected.
|
||||
errno = EINPROGRESS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ != LISTEN) {
|
||||
errno = EINVAL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (pending_connections_.empty()) {
|
||||
errno = EWOULDBLOCK;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto client = std::move(pending_connections_.front());
|
||||
pending_connections_.pop_front();
|
||||
|
||||
if (addr) {
|
||||
*addr = client->remote_addr();
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::send(const uint8_t *buf, size_t len)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ != ESTABLISHED) {
|
||||
errno = ENOTCONN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Wrap as TCP packet
|
||||
Packet p{};
|
||||
// Source
|
||||
p.from.ip = ip_;
|
||||
p.from.port = net_htons(local_port_);
|
||||
p.to = remote_addr_;
|
||||
p.data.assign(buf, buf + len);
|
||||
p.is_tcp = true;
|
||||
p.tcp_flags = 0x10; // ACK (Data packets usually have ACK)
|
||||
p.seq = next_seq_;
|
||||
p.ack = last_ack_;
|
||||
|
||||
next_seq_ += len;
|
||||
universe_.send_packet(p);
|
||||
return len;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::recv(uint8_t *buf, size_t len)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (recv_buffer_.empty()) {
|
||||
if (state_ == CLOSED || state_ == CLOSE_WAIT)
|
||||
return 0; // EOF
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t actual = std::min(len, recv_buffer_.size());
|
||||
for (size_t i = 0; i < actual; ++i) {
|
||||
buf[i] = recv_buffer_.front();
|
||||
recv_buffer_.pop_front();
|
||||
}
|
||||
return actual;
|
||||
}
|
||||
|
||||
size_t FakeTcpSocket::recv_buffer_size()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return recv_buffer_.size();
|
||||
}
|
||||
|
||||
int FakeTcpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
int FakeTcpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void FakeTcpSocket::handle_packet(const Packet &p)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "Handle Packet: Port " << local_port_ << " Flags "
|
||||
<< static_cast<int>(p.tcp_flags) << " State " << state_ << std::endl;
|
||||
}
|
||||
|
||||
if (state_ == LISTEN) {
|
||||
if (p.tcp_flags & 0x02) { // SYN
|
||||
// Create new socket for connection
|
||||
auto new_sock = std::make_unique<FakeTcpSocket>(universe_);
|
||||
// Bind to ephemeral? No, it's accepted on the same port but distinct 4-tuple.
|
||||
// In our simplified model, the new socket is not bound to the global map
|
||||
// until accepted? Or effectively bound to the 4-tuple.
|
||||
// For now, let's just create it and queue it.
|
||||
|
||||
new_sock->state_ = SYN_RECEIVED;
|
||||
new_sock->remote_addr_ = p.from;
|
||||
new_sock->local_port_ = local_port_;
|
||||
new_sock->last_ack_ = p.seq + 1;
|
||||
new_sock->next_seq_ = 1000; // Random ISN
|
||||
|
||||
universe_.bind_tcp(ip_, local_port_, new_sock.get());
|
||||
|
||||
// Send SYN-ACK
|
||||
Packet resp{};
|
||||
resp.from = p.to;
|
||||
resp.to = p.from;
|
||||
resp.is_tcp = true;
|
||||
resp.tcp_flags = 0x12; // SYN | ACK
|
||||
resp.seq = new_sock->next_seq_++;
|
||||
resp.ack = new_sock->last_ack_;
|
||||
|
||||
universe_.send_packet(resp);
|
||||
|
||||
// In real TCP, we wait for ACK to move to ESTABLISHED and accept queue.
|
||||
// Here we cheat and move to ESTABLISHED immediately or wait for ACK?
|
||||
// Let's wait for ACK.
|
||||
// But where do we store this half-open socket?
|
||||
// For simplicity: auto-transition to ESTABLISHED and queue it.
|
||||
new_sock->state_ = ESTABLISHED;
|
||||
pending_connections_.push_back(std::move(new_sock));
|
||||
}
|
||||
} else if (state_ == SYN_SENT) {
|
||||
if ((p.tcp_flags & 0x12) == 0x12) { // SYN | ACK
|
||||
state_ = ESTABLISHED;
|
||||
last_ack_ = p.seq + 1;
|
||||
next_seq_++; // Consumer SYN
|
||||
|
||||
// Send ACK
|
||||
Packet ack{};
|
||||
ack.from = p.to;
|
||||
ack.to = p.from;
|
||||
ack.is_tcp = true;
|
||||
ack.tcp_flags = 0x10; // ACK
|
||||
ack.seq = next_seq_;
|
||||
ack.ack = last_ack_;
|
||||
universe_.send_packet(ack);
|
||||
}
|
||||
} else if (state_ == ESTABLISHED) {
|
||||
if (p.tcp_flags & 0x01) { // FIN
|
||||
state_ = CLOSE_WAIT;
|
||||
// Send ACK
|
||||
Packet ack{};
|
||||
ack.from = p.to;
|
||||
ack.to = p.from;
|
||||
ack.is_tcp = true;
|
||||
ack.tcp_flags = 0x10; // ACK
|
||||
ack.seq = next_seq_;
|
||||
ack.ack = p.seq + 1; // Consume FIN
|
||||
universe_.send_packet(ack);
|
||||
} else if (!p.data.empty()) {
|
||||
recv_buffer_.insert(recv_buffer_.end(), p.data.begin(), p.data.end());
|
||||
last_ack_ += p.data.size();
|
||||
// Should send ACK?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeTcpSocket> FakeTcpSocket::create_connected(
|
||||
NetworkUniverse &universe, const IP_Port &remote, uint16_t local_port)
|
||||
{
|
||||
auto s = std::make_unique<FakeTcpSocket>(universe);
|
||||
s->state_ = ESTABLISHED;
|
||||
s->remote_addr_ = remote;
|
||||
s->local_port_ = local_port;
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
165
testing/support/doubles/fake_sockets.hh
Normal file
165
testing/support/doubles/fake_sockets.hh
Normal file
@@ -0,0 +1,165 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include "../../../toxcore/network.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class NetworkUniverse;
|
||||
struct Packet;
|
||||
|
||||
/**
|
||||
* @brief Abstract base class for all fake sockets.
|
||||
*/
|
||||
class FakeSocket {
|
||||
public:
|
||||
FakeSocket(NetworkUniverse &universe, int type);
|
||||
virtual ~FakeSocket();
|
||||
|
||||
virtual int bind(const IP_Port *addr) = 0;
|
||||
virtual int connect(const IP_Port *addr) = 0;
|
||||
virtual int listen(int backlog) = 0;
|
||||
virtual std::unique_ptr<FakeSocket> accept(IP_Port *addr) = 0;
|
||||
|
||||
virtual int send(const uint8_t *buf, size_t len) = 0;
|
||||
virtual int recv(uint8_t *buf, size_t len) = 0;
|
||||
|
||||
virtual size_t recv_buffer_size() { return 0; }
|
||||
|
||||
virtual int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) = 0;
|
||||
virtual int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) = 0;
|
||||
|
||||
virtual int getsockopt(int level, int optname, void *optval, size_t *optlen);
|
||||
virtual int setsockopt(int level, int optname, const void *optval, size_t optlen);
|
||||
virtual int socket_nonblock(bool nonblock);
|
||||
|
||||
virtual int close();
|
||||
|
||||
bool is_nonblocking() const { return nonblocking_; }
|
||||
int type() const { return type_; }
|
||||
uint16_t local_port() const { return local_port_; }
|
||||
const IP &ip_address() const { return ip_; }
|
||||
|
||||
void set_ip(const IP &ip) { ip_ = ip; }
|
||||
|
||||
protected:
|
||||
NetworkUniverse &universe_;
|
||||
int type_;
|
||||
bool nonblocking_ = false;
|
||||
uint16_t local_port_ = 0;
|
||||
IP ip_;
|
||||
std::mutex mutex_; // Use regular mutex (recursive_mutex not fully supported in MinGW)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implements UDP logic.
|
||||
*/
|
||||
class FakeUdpSocket : public FakeSocket {
|
||||
public:
|
||||
explicit FakeUdpSocket(NetworkUniverse &universe);
|
||||
~FakeUdpSocket() override;
|
||||
|
||||
int bind(const IP_Port *addr) override;
|
||||
int connect(const IP_Port *addr) override;
|
||||
int listen(int backlog) override;
|
||||
std::unique_ptr<FakeSocket> accept(IP_Port *addr) override;
|
||||
int close() override;
|
||||
|
||||
int send(const uint8_t *buf, size_t len) override;
|
||||
int recv(uint8_t *buf, size_t len) override;
|
||||
|
||||
int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) override;
|
||||
int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) override;
|
||||
|
||||
// Called by Universe to deliver a packet
|
||||
void push_packet(std::vector<uint8_t> data, IP_Port from);
|
||||
|
||||
using PacketSource = std::function<bool(std::vector<uint8_t> &data, IP_Port &from)>;
|
||||
void set_packet_source(PacketSource source);
|
||||
|
||||
using RecvObserver = std::function<void(const std::vector<uint8_t> &data, const IP_Port &from)>;
|
||||
void set_recv_observer(RecvObserver observer);
|
||||
|
||||
private:
|
||||
void close_impl();
|
||||
|
||||
struct PendingPacket {
|
||||
std::vector<uint8_t> data;
|
||||
IP_Port from;
|
||||
};
|
||||
std::deque<PendingPacket> recv_queue_;
|
||||
PacketSource packet_source_;
|
||||
RecvObserver recv_observer_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implements TCP logic (Simplified for simulation).
|
||||
*
|
||||
* Supports:
|
||||
* - 3-way handshake simulation (instant or delayed)
|
||||
* - Stream buffering
|
||||
* - Connection states
|
||||
*/
|
||||
class FakeTcpSocket : public FakeSocket {
|
||||
public:
|
||||
enum State { CLOSED, LISTEN, SYN_SENT, SYN_RECEIVED, ESTABLISHED, CLOSE_WAIT };
|
||||
|
||||
explicit FakeTcpSocket(NetworkUniverse &universe);
|
||||
~FakeTcpSocket() override;
|
||||
|
||||
int bind(const IP_Port *addr) override;
|
||||
int connect(const IP_Port *addr) override;
|
||||
int listen(int backlog) override;
|
||||
std::unique_ptr<FakeSocket> accept(IP_Port *addr) override;
|
||||
int close() override;
|
||||
|
||||
int send(const uint8_t *buf, size_t len) override;
|
||||
int recv(uint8_t *buf, size_t len) override;
|
||||
size_t recv_buffer_size() override;
|
||||
|
||||
int sendto(const uint8_t *buf, size_t len, const IP_Port *addr) override;
|
||||
int recvfrom(uint8_t *buf, size_t len, IP_Port *addr) override;
|
||||
|
||||
// Internal events
|
||||
void handle_packet(const Packet &p);
|
||||
|
||||
State state() const { return state_; }
|
||||
const IP_Port &remote_addr() const { return remote_addr_; }
|
||||
|
||||
// Factory for accepting connections
|
||||
static std::unique_ptr<FakeTcpSocket> create_connected(
|
||||
NetworkUniverse &universe, const IP_Port &remote, uint16_t local_port);
|
||||
|
||||
private:
|
||||
void close_impl();
|
||||
|
||||
State state_ = CLOSED;
|
||||
IP_Port remote_addr_;
|
||||
std::deque<uint8_t> recv_buffer_;
|
||||
std::deque<std::unique_ptr<FakeTcpSocket>> pending_connections_;
|
||||
int backlog_ = 0;
|
||||
|
||||
uint32_t next_seq_ = 0;
|
||||
uint32_t last_ack_ = 0;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H
|
||||
125
testing/support/doubles/fake_sockets_test.cc
Normal file
125
testing/support/doubles/fake_sockets_test.cc
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "fake_sockets.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "network_universe.hh"
|
||||
|
||||
namespace tox::test {
|
||||
namespace {
|
||||
|
||||
class FakeTcpSocketTest : public ::testing::Test {
|
||||
public:
|
||||
~FakeTcpSocketTest() override;
|
||||
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
FakeTcpSocket server{universe};
|
||||
FakeTcpSocket client{universe};
|
||||
};
|
||||
|
||||
FakeTcpSocketTest::~FakeTcpSocketTest() = default;
|
||||
|
||||
TEST_F(FakeTcpSocketTest, ConnectAndAccept)
|
||||
{
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
server_addr.port = net_htons(8080);
|
||||
|
||||
ASSERT_EQ(server.bind(&server_addr), 0);
|
||||
ASSERT_EQ(server.listen(5), 0);
|
||||
|
||||
// Client connects
|
||||
ASSERT_EQ(client.connect(&server_addr), -1);
|
||||
ASSERT_EQ(errno, EINPROGRESS);
|
||||
|
||||
// Process events (Client SYN -> Server)
|
||||
universe.process_events(0);
|
||||
|
||||
// Server accepts (SYN-ACK -> Client)
|
||||
universe.process_events(0);
|
||||
|
||||
// Client receives SYN-ACK, sends ACK (ACK -> Server)
|
||||
universe.process_events(0);
|
||||
|
||||
// Server receives ACK, connection established
|
||||
IP_Port client_addr;
|
||||
auto accepted = server.accept(&client_addr);
|
||||
ASSERT_NE(accepted, nullptr);
|
||||
auto *accepted_tcp = static_cast<FakeTcpSocket *>(accepted.get());
|
||||
EXPECT_EQ(accepted_tcp->state(), FakeTcpSocket::ESTABLISHED);
|
||||
|
||||
EXPECT_EQ(client.state(), FakeTcpSocket::ESTABLISHED);
|
||||
}
|
||||
|
||||
TEST_F(FakeTcpSocketTest, DataTransfer)
|
||||
{
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
server_addr.port = net_htons(8081);
|
||||
|
||||
server.bind(&server_addr);
|
||||
server.listen(5);
|
||||
client.connect(&server_addr);
|
||||
|
||||
// Handshake
|
||||
universe.process_events(0); // SYN
|
||||
universe.process_events(0); // SYN-ACK
|
||||
universe.process_events(0); // ACK
|
||||
|
||||
auto accepted = server.accept(nullptr);
|
||||
ASSERT_NE(accepted, nullptr);
|
||||
|
||||
// Send data Client -> Server
|
||||
uint8_t send_buf[] = "Hello";
|
||||
ASSERT_EQ(client.send(send_buf, 5), 5);
|
||||
|
||||
universe.process_events(0); // Data packet
|
||||
|
||||
uint8_t recv_buf[10];
|
||||
ASSERT_EQ(accepted->recv(recv_buf, 10), 5);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), 5), "Hello");
|
||||
}
|
||||
|
||||
class FakeUdpSocketTest : public ::testing::Test {
|
||||
public:
|
||||
~FakeUdpSocketTest() override;
|
||||
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
FakeUdpSocket server{universe};
|
||||
FakeUdpSocket client{universe};
|
||||
};
|
||||
|
||||
FakeUdpSocketTest::~FakeUdpSocketTest() = default;
|
||||
|
||||
TEST_F(FakeUdpSocketTest, BindAndSendTo)
|
||||
{
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
server_addr.port = net_htons(9000);
|
||||
|
||||
ASSERT_EQ(server.bind(&server_addr), 0);
|
||||
|
||||
const char *message = "UDP Packet";
|
||||
ASSERT_EQ(client.sendto(
|
||||
reinterpret_cast<const uint8_t *>(message), strlen(message), &server_addr),
|
||||
strlen(message));
|
||||
|
||||
universe.process_events(0);
|
||||
|
||||
IP_Port sender_addr;
|
||||
uint8_t recv_buf[100];
|
||||
int len = server.recvfrom(recv_buf, sizeof(recv_buf), &sender_addr);
|
||||
|
||||
ASSERT_GT(len, 0);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), len), message);
|
||||
EXPECT_EQ(sender_addr.port, net_htons(client.local_port()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
|
||||
// end of file
|
||||
145
testing/support/doubles/network_universe.cc
Normal file
145
testing/support/doubles/network_universe.cc
Normal file
@@ -0,0 +1,145 @@
|
||||
#include "network_universe.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "fake_sockets.hh"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const
|
||||
{
|
||||
if (port != other.port)
|
||||
return port < other.port;
|
||||
if (ip.family.value != other.ip.family.value)
|
||||
return ip.family.value < other.ip.family.value;
|
||||
|
||||
if (net_family_is_ipv4(ip.family)) {
|
||||
return ip.ip.v4.uint32 < other.ip.ip.v4.uint32;
|
||||
}
|
||||
|
||||
return std::memcmp(&ip.ip.v6, &other.ip.ip.v6, sizeof(ip.ip.v6)) < 0;
|
||||
}
|
||||
|
||||
NetworkUniverse::NetworkUniverse() { }
|
||||
NetworkUniverse::~NetworkUniverse() { }
|
||||
|
||||
bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
IP_Port_Key key{ip, port};
|
||||
if (udp_bindings_.count(key))
|
||||
return false;
|
||||
udp_bindings_[key] = socket;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkUniverse::unbind_udp(IP ip, uint16_t port)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
udp_bindings_.erase({ip, port});
|
||||
}
|
||||
|
||||
bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
tcp_bindings_.insert({{ip, port}, socket});
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetworkUniverse::unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
auto range = tcp_bindings_.equal_range({ip, port});
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
if (it->second == socket) {
|
||||
tcp_bindings_.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkUniverse::send_packet(Packet p)
|
||||
{
|
||||
// Apply filters
|
||||
for (const auto &filter : filters_) {
|
||||
if (!filter(p))
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify observers
|
||||
for (const auto &observer : observers_) {
|
||||
observer(p);
|
||||
}
|
||||
|
||||
p.delivery_time += global_latency_ms_;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
event_queue_.push(std::move(p));
|
||||
}
|
||||
|
||||
void NetworkUniverse::process_events(uint64_t current_time_ms)
|
||||
{
|
||||
while (true) {
|
||||
Packet p;
|
||||
std::vector<FakeTcpSocket *> tcp_targets;
|
||||
FakeUdpSocket *udp_target = nullptr;
|
||||
bool has_packet = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
if (!event_queue_.empty() && event_queue_.top().delivery_time <= current_time_ms) {
|
||||
p = event_queue_.top();
|
||||
event_queue_.pop();
|
||||
has_packet = true;
|
||||
|
||||
if (p.is_tcp) {
|
||||
auto range = tcp_bindings_.equal_range({p.to.ip, net_ntohs(p.to.port)});
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
tcp_targets.push_back(it->second);
|
||||
}
|
||||
} else {
|
||||
if (udp_bindings_.count({p.to.ip, net_ntohs(p.to.port)})) {
|
||||
udp_target = udp_bindings_[{p.to.ip, net_ntohs(p.to.port)}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_packet) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (p.is_tcp) {
|
||||
for (auto *it : tcp_targets) {
|
||||
it->handle_packet(p);
|
||||
}
|
||||
} else {
|
||||
if (udp_target) {
|
||||
udp_target->push_packet(std::move(p.data), p.from);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkUniverse::set_latency(uint64_t ms) { global_latency_ms_ = ms; }
|
||||
|
||||
void NetworkUniverse::set_verbose(bool verbose) { verbose_ = verbose; }
|
||||
|
||||
bool NetworkUniverse::is_verbose() const { return verbose_; }
|
||||
|
||||
void NetworkUniverse::add_filter(PacketFilter filter) { filters_.push_back(std::move(filter)); }
|
||||
|
||||
void NetworkUniverse::add_observer(PacketSink sink) { observers_.push_back(std::move(sink)); }
|
||||
|
||||
uint16_t NetworkUniverse::find_free_port(IP ip, uint16_t start)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
for (uint16_t port = start; port < 65535; ++port) {
|
||||
if (!udp_bindings_.count({ip, port}))
|
||||
return port;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
90
testing/support/doubles/network_universe.hh
Normal file
90
testing/support/doubles/network_universe.hh
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_NETWORK_UNIVERSE_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_NETWORK_UNIVERSE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include "../../../toxcore/network.h"
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
class FakeUdpSocket;
|
||||
class FakeTcpSocket;
|
||||
|
||||
struct Packet {
|
||||
IP_Port from;
|
||||
IP_Port to;
|
||||
std::vector<uint8_t> data;
|
||||
uint64_t delivery_time;
|
||||
bool is_tcp = false;
|
||||
|
||||
// TCP Simulation Fields
|
||||
uint8_t tcp_flags = 0; // 0x01=FIN, 0x02=SYN, 0x10=ACK
|
||||
uint32_t seq = 0;
|
||||
uint32_t ack = 0;
|
||||
|
||||
bool operator>(const Packet &other) const { return delivery_time > other.delivery_time; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The God Object for the network simulation.
|
||||
* Manages routing, latency, and connectivity.
|
||||
*/
|
||||
class NetworkUniverse {
|
||||
public:
|
||||
using PacketFilter = std::function<bool(Packet &)>;
|
||||
using PacketSink = std::function<void(const Packet &)>;
|
||||
|
||||
NetworkUniverse();
|
||||
~NetworkUniverse();
|
||||
|
||||
// Registration
|
||||
// Returns true if binding succeeded
|
||||
bool bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket);
|
||||
void unbind_udp(IP ip, uint16_t port);
|
||||
|
||||
bool bind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket);
|
||||
void unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket);
|
||||
|
||||
// Routing
|
||||
void send_packet(Packet p);
|
||||
|
||||
// Simulation
|
||||
void process_events(uint64_t current_time_ms);
|
||||
void set_latency(uint64_t ms);
|
||||
void set_verbose(bool verbose);
|
||||
bool is_verbose() const;
|
||||
void add_filter(PacketFilter filter);
|
||||
void add_observer(PacketSink sink);
|
||||
|
||||
// Helpers
|
||||
// Finds a free port starting from 'start'
|
||||
uint16_t find_free_port(IP ip, uint16_t start = 33445);
|
||||
|
||||
struct IP_Port_Key {
|
||||
IP ip;
|
||||
uint16_t port;
|
||||
bool operator<(const IP_Port_Key &other) const;
|
||||
};
|
||||
|
||||
private:
|
||||
std::map<IP_Port_Key, FakeUdpSocket *> udp_bindings_;
|
||||
std::multimap<IP_Port_Key, FakeTcpSocket *> tcp_bindings_;
|
||||
|
||||
std::priority_queue<Packet, std::vector<Packet>, std::greater<Packet>> event_queue_;
|
||||
std::vector<PacketFilter> filters_;
|
||||
std::vector<PacketSink> observers_;
|
||||
|
||||
uint64_t global_latency_ms_ = 0;
|
||||
bool verbose_ = false;
|
||||
std::recursive_mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_NETWORK_UNIVERSE_H
|
||||
240
testing/support/doubles/network_universe_test.cc
Normal file
240
testing/support/doubles/network_universe_test.cc
Normal file
@@ -0,0 +1,240 @@
|
||||
#include "network_universe.hh"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "fake_sockets.hh"
|
||||
|
||||
namespace tox::test {
|
||||
namespace {
|
||||
|
||||
class NetworkUniverseTest : public ::testing::Test {
|
||||
public:
|
||||
~NetworkUniverseTest() override;
|
||||
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
FakeUdpSocket s1{universe};
|
||||
FakeUdpSocket s2{universe};
|
||||
};
|
||||
|
||||
NetworkUniverseTest::~NetworkUniverseTest() = default;
|
||||
|
||||
TEST_F(NetworkUniverseTest, LatencySimulation)
|
||||
{
|
||||
universe.set_latency(100);
|
||||
|
||||
IP_Port s2_addr;
|
||||
ip_init(&s2_addr.ip, false);
|
||||
s2_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
s2_addr.port = net_htons(9004);
|
||||
s2.bind(&s2_addr);
|
||||
|
||||
uint8_t data[] = "Ping";
|
||||
s1.sendto(data, 4, &s2_addr);
|
||||
|
||||
// Time 0: packet sent but delivery time is 100
|
||||
universe.process_events(0);
|
||||
IP_Port from;
|
||||
uint8_t buf[10];
|
||||
ASSERT_EQ(s2.recvfrom(buf, 10, &from), -1);
|
||||
|
||||
// Time 50: still not delivered
|
||||
universe.process_events(50);
|
||||
ASSERT_EQ(s2.recvfrom(buf, 10, &from), -1);
|
||||
|
||||
// Time 100: delivered
|
||||
universe.process_events(100);
|
||||
ASSERT_EQ(s2.recvfrom(buf, 10, &from), 4);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, RoutesBasedOnIpAndPort)
|
||||
{
|
||||
IP ip1{}, ip2{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x01010101);
|
||||
ip_init(&ip2, false);
|
||||
ip2.ip.v4.uint32 = net_htonl(0x02020202);
|
||||
|
||||
uint16_t port = 33445;
|
||||
|
||||
FakeUdpSocket sock1{universe};
|
||||
FakeUdpSocket sock2{universe};
|
||||
|
||||
sock1.set_ip(ip1);
|
||||
sock2.set_ip(ip2);
|
||||
|
||||
IP_Port addr1{ip1, net_htons(port)};
|
||||
IP_Port addr2{ip2, net_htons(port)};
|
||||
|
||||
ASSERT_EQ(sock1.bind(&addr1), 0);
|
||||
ASSERT_EQ(sock2.bind(&addr2), 0);
|
||||
|
||||
const char *msg1 = "To IP 1";
|
||||
const char *msg2 = "To IP 2";
|
||||
|
||||
FakeUdpSocket sender{universe};
|
||||
sender.sendto(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1), &addr1);
|
||||
sender.sendto(reinterpret_cast<const uint8_t *>(msg2), strlen(msg2), &addr2);
|
||||
|
||||
universe.process_events(0);
|
||||
|
||||
uint8_t buf[100];
|
||||
IP_Port from;
|
||||
|
||||
int len1 = sock1.recvfrom(buf, sizeof(buf), &from);
|
||||
ASSERT_GT(len1, 0);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len1)), msg1);
|
||||
|
||||
int len2 = sock2.recvfrom(buf, sizeof(buf), &from);
|
||||
ASSERT_GT(len2, 0);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len2)), msg2);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, FindFreePortIsIpSpecific)
|
||||
{
|
||||
IP ip1{}, ip2{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x01010101);
|
||||
ip_init(&ip2, false);
|
||||
ip2.ip.v4.uint32 = net_htonl(0x02020202);
|
||||
|
||||
FakeUdpSocket sock1{universe};
|
||||
sock1.set_ip(ip1);
|
||||
|
||||
uint16_t port = 33445;
|
||||
IP_Port addr1{ip1, net_htons(port)};
|
||||
ASSERT_EQ(sock1.bind(&addr1), 0);
|
||||
|
||||
// Port 33445 should be busy for ip1, but free for ip2
|
||||
EXPECT_EQ(universe.find_free_port(ip1, port), port + 1);
|
||||
EXPECT_EQ(universe.find_free_port(ip2, port), port);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, IpPortKeyEqualityRobustness)
|
||||
{
|
||||
IP ip1{}, ip2{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
ip_init(&ip2, false);
|
||||
ip2.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
|
||||
uint16_t port = 12345;
|
||||
|
||||
// Force different garbage in the union padding for IPv4
|
||||
// The union is 16 bytes. IP4 is 4 bytes. Trailing 12 bytes are unused.
|
||||
memset(ip1.ip.v6.uint8 + 4, 0x11, 12);
|
||||
memset(ip2.ip.v6.uint8 + 4, 0x22, 12);
|
||||
|
||||
NetworkUniverse::IP_Port_Key key1{ip1, port};
|
||||
NetworkUniverse::IP_Port_Key key2{ip2, port};
|
||||
|
||||
// They should be considered equal (neither is less than the other)
|
||||
EXPECT_FALSE(key1 < key2);
|
||||
EXPECT_FALSE(key2 < key1);
|
||||
|
||||
// Now try with different IPv4 but same garbage
|
||||
ip2.ip.v4.uint32 = net_htonl(0x7F000002);
|
||||
memset(ip2.ip.v6.uint8 + 4, 0x11, 12); // same garbage as ip1
|
||||
NetworkUniverse::IP_Port_Key key3{ip2, port};
|
||||
|
||||
EXPECT_TRUE(key1 < key3 || key3 < key1);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, IPv4v6Distinction)
|
||||
{
|
||||
IP ip1{}, ip2{};
|
||||
ip_init(&ip1, false); // IPv4
|
||||
ip1.ip.v4.uint32 = net_htonl(0x01020304);
|
||||
ip_init(&ip2, true); // IPv6
|
||||
// Set IPv6 bytes to match IPv4 bytes at the beginning
|
||||
memset(ip2.ip.v6.uint8, 0, 16);
|
||||
ip2.ip.v6.uint32[0] = net_htonl(0x01020304);
|
||||
|
||||
uint16_t port = 12345;
|
||||
|
||||
NetworkUniverse::IP_Port_Key key1{ip1, port};
|
||||
NetworkUniverse::IP_Port_Key key2{ip2, port};
|
||||
|
||||
// Different families must be different even if underlying bytes happen to match
|
||||
EXPECT_TRUE(key1 < key2 || key2 < key1);
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, ManyNodes)
|
||||
{
|
||||
const int num_nodes = 5000;
|
||||
struct NodeInfo {
|
||||
std::unique_ptr<FakeUdpSocket> sock;
|
||||
IP_Port addr;
|
||||
};
|
||||
std::vector<NodeInfo> nodes;
|
||||
nodes.reserve(num_nodes);
|
||||
|
||||
for (int i = 0; i < num_nodes; ++i) {
|
||||
auto sock = std::make_unique<FakeUdpSocket>(universe);
|
||||
IP ip{};
|
||||
ip_init(&ip, false);
|
||||
ip.ip.v4.uint32 = net_htonl(0x0A000000 + i); // 10.0.x.y
|
||||
sock->set_ip(ip);
|
||||
|
||||
IP_Port addr{ip, net_htons(33445)};
|
||||
ASSERT_EQ(sock->bind(&addr), 0);
|
||||
nodes.push_back({std::move(sock), addr});
|
||||
}
|
||||
|
||||
const int num_messages = 100;
|
||||
// Send messages from first num_messages to last num_messages nodes
|
||||
for (int i = 0; i < num_messages; ++i) {
|
||||
const char *msg = "Stress test";
|
||||
nodes[i].sock->sendto(reinterpret_cast<const uint8_t *>(msg), strlen(msg),
|
||||
&nodes[num_nodes - 1 - i].addr);
|
||||
}
|
||||
|
||||
universe.process_events(0);
|
||||
|
||||
for (int i = 0; i < num_messages; ++i) {
|
||||
uint8_t buf[100];
|
||||
IP_Port from;
|
||||
int len = nodes[num_nodes - 1 - i].sock->recvfrom(buf, sizeof(buf), &from);
|
||||
ASSERT_GT(len, 0);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len)),
|
||||
"Stress test");
|
||||
EXPECT_TRUE(ip_equal(&from.ip, &nodes[i].addr.ip));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, IpPadding)
|
||||
{
|
||||
IP ip1{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
|
||||
FakeUdpSocket sock{universe};
|
||||
sock.set_ip(ip1);
|
||||
IP_Port bind_addr{ip1, net_htons(12345)};
|
||||
ASSERT_EQ(sock.bind(&bind_addr), 0);
|
||||
|
||||
// Create an address with garbage in the padding
|
||||
IP_Port target_addr;
|
||||
memset(&target_addr, 0xAA, sizeof(target_addr));
|
||||
ip_init(&target_addr.ip, false);
|
||||
target_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
target_addr.port = net_htons(12345);
|
||||
|
||||
FakeUdpSocket sender{universe};
|
||||
const char *msg = "Padding test";
|
||||
sender.sendto(reinterpret_cast<const uint8_t *>(msg), strlen(msg), &target_addr);
|
||||
|
||||
universe.process_events(0);
|
||||
|
||||
uint8_t buf[100];
|
||||
IP_Port from;
|
||||
int len = sock.recvfrom(buf, sizeof(buf), &from);
|
||||
|
||||
// If this fails, it means NetworkUniverse is not robust against padding garbage
|
||||
ASSERT_GT(len, 0);
|
||||
EXPECT_EQ(
|
||||
std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len)), "Padding test");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
Reference in New Issue
Block a user