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:
Green Sky
2026-01-11 14:42:31 +01:00
parent e95f2cbb1c
commit 565efa4f39
328 changed files with 19057 additions and 13982 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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