Squashed 'external/toxcore/c-toxcore/' changes from c9cdae001..9ed2fa80d
9ed2fa80d fix(toxav): remove extra copy of video frame on encode de30cf3ad docs: Add new file kinds, that should be useful to all clients. d5b5e879d fix(DHT): Correct node skipping logic timed out nodes. 30e71fe97 refactor: Generate event dispatch functions and add tox_events_dispatch. 8fdbb0b50 style: Format parameter lists in event handlers. d00dee12b refactor: Add warning logs when losing chat invites. b144e8db1 feat: Add a way to look up a file number by ID. 849281ea0 feat: Add a way to fetch groups by chat ID. a2c177396 refactor: Harden event system and improve type safety. 8f5caa656 refactor: Add MessagePack string support to bin_pack. 34e8d5ad5 chore: Add GitHub CodeQL workflow and local Docker runner. f7b068010 refactor: Add nullability annotations to event headers. 788abe651 refactor(toxav): Use system allocator for mutexes. 2e4b423eb refactor: Use specific typedefs for public API arrays. 2baf34775 docs(toxav): update idle iteration interval see 679444751876fa3882a717772918ebdc8f083354 2f87ac67b feat: Add Event Loop abstraction (Ev). f8dfc38d8 test: Fix data race in ToxScenario virtual_clock. 38313921e test(TCP): Add regression test for TCP priority queue integrity. f94a50d9a refactor(toxav): Replace mutable_mutex with dynamically allocated mutex. ad054511e refactor: Internalize DHT structs and add debug helpers. 8b467cc96 fix: Prevent potential integer overflow in group chat handshake. 4962bdbb8 test: Improve TCP simulation and add tests 5f0227093 refactor: Allow nullable data in group chat handlers. e97b18ea9 chore: Improve Windows Docker support. b14943bbd refactor: Move Logger out of Messenger into Tox. dd3136250 cleanup: Apply nullability qualifiers to C++ codebase. 1849f70fc refactor: Extract low-level networking code to net and os_network. 8fec75421 refactor: Delete tox_random, align on rng and os_random. a03ae8051 refactor: Delete tox_memory, align on mem and os_memory. 4c88fed2c refactor: Use `std::` prefixes more consistently in C++ code. 72452f2ae test: Add some more tests for onion and shared key cache. d5a51b09a cleanup: Use tox_attributes.h in tox_private.h and install it. b6f5b9fc5 test: Add some benchmarks for various high level things. 8a8d02785 test(support): Introduce threaded Tox runner and simulation barrier d68d1d095 perf(toxav): optimize audio and video intermediate buffers by keeping them around REVERT: c9cdae001 fix(toxav): remove extra copy of video frame on encode git-subtree-dir: external/toxcore/c-toxcore git-subtree-split: 9ed2fa80d582c714d6bdde6a7648220a92cddff8
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_CLOCK_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../public/clock.hh"
|
||||
|
||||
namespace tox::test {
|
||||
@@ -15,7 +18,7 @@ public:
|
||||
void advance(uint64_t ms);
|
||||
|
||||
private:
|
||||
uint64_t now_ms_;
|
||||
std::atomic<uint64_t> now_ms_;
|
||||
};
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#ifndef C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_MEMORY_H
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
|
||||
#include "../public/memory.hh"
|
||||
|
||||
// Forward declaration
|
||||
struct Tox_Memory;
|
||||
struct Memory;
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
@@ -18,9 +19,9 @@ public:
|
||||
FakeMemory();
|
||||
~FakeMemory() override;
|
||||
|
||||
void *malloc(size_t size) override;
|
||||
void *realloc(void *ptr, size_t size) override;
|
||||
void free(void *ptr) override;
|
||||
void *_Nullable malloc(size_t size) override;
|
||||
void *_Nullable realloc(void *_Nullable ptr, size_t size) override;
|
||||
void free(void *_Nullable ptr) override;
|
||||
|
||||
// Configure failure injection
|
||||
void set_failure_injector(FailureInjector injector);
|
||||
@@ -28,10 +29,18 @@ public:
|
||||
// Configure observer
|
||||
void set_observer(Observer observer);
|
||||
|
||||
// Get the C-compatible struct
|
||||
struct Tox_Memory get_c_memory();
|
||||
/**
|
||||
* @brief Returns C-compatible Memory struct.
|
||||
*/
|
||||
struct Memory c_memory() override;
|
||||
|
||||
size_t current_allocation() const;
|
||||
size_t max_allocation() const;
|
||||
|
||||
private:
|
||||
void on_allocation(size_t size);
|
||||
void on_deallocation(size_t size);
|
||||
|
||||
struct Header {
|
||||
size_t size;
|
||||
size_t magic;
|
||||
@@ -39,8 +48,8 @@ private:
|
||||
static constexpr size_t kMagic = 0xDEADC0DE;
|
||||
static constexpr size_t kFreeMagic = 0xBAADF00D;
|
||||
|
||||
size_t current_allocation_ = 0;
|
||||
size_t max_allocation_ = 0;
|
||||
std::atomic<size_t> current_allocation_{0};
|
||||
std::atomic<size_t> max_allocation_{0};
|
||||
|
||||
FailureInjector failure_injector_;
|
||||
Observer observer_;
|
||||
|
||||
@@ -8,55 +8,60 @@
|
||||
|
||||
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); },
|
||||
static const Network_Funcs kNetworkVtable = {
|
||||
.close = [](void *_Nonnull obj,
|
||||
Socket sock) { return static_cast<FakeNetworkStack *>(obj)->close(sock); },
|
||||
.accept = [](void *_Nonnull obj,
|
||||
Socket sock) { return static_cast<FakeNetworkStack *>(obj)->accept(sock); },
|
||||
.bind =
|
||||
[](void *_Nonnull obj, Socket sock, const IP_Port *_Nonnull addr) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->bind(sock, addr);
|
||||
},
|
||||
.listen
|
||||
= [](void *obj, Socket sock,
|
||||
= [](void *_Nonnull obj, Socket sock,
|
||||
int backlog) { return static_cast<FakeNetworkStack *>(obj)->listen(sock, backlog); },
|
||||
.connect =
|
||||
[](void *obj, Socket sock, const IP_Port *addr) {
|
||||
[](void *_Nonnull obj, Socket sock, const IP_Port *_Nonnull 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,
|
||||
.recvbuf = [](void *_Nonnull obj,
|
||||
Socket sock) { return static_cast<FakeNetworkStack *>(obj)->recvbuf(sock); },
|
||||
.recv = [](void *_Nonnull obj, Socket sock, uint8_t *_Nonnull 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) {
|
||||
[](void *_Nonnull obj, Socket sock, uint8_t *_Nonnull buf, size_t len,
|
||||
IP_Port *_Nonnull addr) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->recvfrom(sock, buf, len, addr);
|
||||
},
|
||||
.send = [](void *obj, Socket sock, const uint8_t *buf,
|
||||
.send = [](void *_Nonnull obj, Socket sock, const uint8_t *_Nonnull 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) {
|
||||
[](void *_Nonnull obj, Socket sock, const uint8_t *_Nonnull buf, size_t len,
|
||||
const IP_Port *_Nonnull addr) {
|
||||
return static_cast<FakeNetworkStack *>(obj)->sendto(sock, buf, len, addr);
|
||||
},
|
||||
.socket
|
||||
= [](void *obj, int domain, int type,
|
||||
= [](void *_Nonnull obj, int domain, int type,
|
||||
int proto) { return static_cast<FakeNetworkStack *>(obj)->socket(domain, type, proto); },
|
||||
.socket_nonblock =
|
||||
[](void *obj, Socket sock, bool nonblock) {
|
||||
[](void *_Nonnull 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) {
|
||||
[](void *_Nonnull obj, Socket sock, int level, int optname, void *_Nonnull optval,
|
||||
size_t *_Nonnull 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) {
|
||||
[](void *_Nonnull obj, Socket sock, int level, int optname, const void *_Nonnull 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) {
|
||||
[](void *_Nonnull obj, const Memory *_Nonnull mem, const char *_Nonnull address, int family,
|
||||
int protocol, IP_Port *_Nullable *_Nonnull addrs) {
|
||||
FakeNetworkStack *self = static_cast<FakeNetworkStack *>(obj);
|
||||
if (self->universe().is_verbose()) {
|
||||
std::cerr << "[FakeNetworkStack] getaddrinfo for " << address << std::endl;
|
||||
@@ -83,7 +88,7 @@ static const Network_Funcs kVtable = {
|
||||
return 0;
|
||||
},
|
||||
.freeaddrinfo =
|
||||
[](void *obj, const Memory *mem, IP_Port *addrs) {
|
||||
[](void *_Nonnull obj, const Memory *_Nonnull mem, IP_Port *_Nullable addrs) {
|
||||
mem_delete(mem, addrs);
|
||||
return 0;
|
||||
},
|
||||
@@ -97,7 +102,7 @@ FakeNetworkStack::FakeNetworkStack(NetworkUniverse &universe, const IP &node_ip)
|
||||
|
||||
FakeNetworkStack::~FakeNetworkStack() = default;
|
||||
|
||||
struct Network FakeNetworkStack::get_c_network() { return Network{&kVtable, this}; }
|
||||
struct Network FakeNetworkStack::c_network() { return Network{&kNetworkVtable, this}; }
|
||||
|
||||
Socket FakeNetworkStack::socket(int domain, int type, int protocol)
|
||||
{
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
#define C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_NETWORK_STACK_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "../../../toxcore/net.h"
|
||||
#include "../public/network.hh"
|
||||
#include "fake_sockets.hh"
|
||||
#include "network_universe.hh"
|
||||
@@ -17,33 +20,38 @@ public:
|
||||
|
||||
// NetworkSystem Implementation
|
||||
Socket socket(int domain, int type, int protocol) override;
|
||||
int bind(Socket sock, const IP_Port *addr) override;
|
||||
int bind(Socket sock, const IP_Port *_Nonnull 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 sendto(Socket sock, const uint8_t *_Nonnull buf, size_t len,
|
||||
const IP_Port *_Nonnull addr) override;
|
||||
int recvfrom(Socket sock, uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull 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 connect(Socket sock, const IP_Port *_Nonnull addr) override;
|
||||
int send(Socket sock, const uint8_t *_Nonnull buf, size_t len) override;
|
||||
int recv(Socket sock, uint8_t *_Nonnull 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;
|
||||
int getsockopt(Socket sock, int level, int optname, void *_Nonnull optval,
|
||||
size_t *_Nonnull optlen) override;
|
||||
int setsockopt(
|
||||
Socket sock, int level, int optname, const void *_Nonnull optval, size_t optlen) override;
|
||||
|
||||
struct Network get_c_network();
|
||||
/**
|
||||
* @brief Returns C-compatible Network struct.
|
||||
*/
|
||||
struct Network c_network() override;
|
||||
|
||||
// For testing/fuzzing introspection
|
||||
FakeUdpSocket *get_udp_socket(Socket sock);
|
||||
FakeSocket *_Nullable get_sock(Socket sock);
|
||||
FakeUdpSocket *_Nullable 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;
|
||||
|
||||
@@ -87,5 +87,62 @@ namespace {
|
||||
ASSERT_NE(net_socket_to_native(accepted), -1);
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkStackTest, LoopbackRedirection)
|
||||
{
|
||||
// 1. Create a stack with a specific IP (20.0.0.1)
|
||||
FakeNetworkStack my_stack{universe, make_ip(0x14000001)};
|
||||
Socket sock = my_stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
IP_Port bind_addr;
|
||||
ip_init(&bind_addr.ip, false);
|
||||
bind_addr.ip.ip.v4.uint32 = net_htonl(0x14000001);
|
||||
bind_addr.port = net_htons(12345);
|
||||
ASSERT_EQ(my_stack.bind(sock, &bind_addr), 0);
|
||||
ASSERT_EQ(my_stack.listen(sock, 5), 0);
|
||||
|
||||
// 2. Connect to 127.0.0.1:12345 from the same stack
|
||||
Socket client = my_stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
IP_Port connect_addr;
|
||||
ip_init(&connect_addr.ip, false);
|
||||
connect_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
connect_addr.port = net_htons(12345);
|
||||
|
||||
// Should redirect to 20.0.0.1:12345 because 127.0.0.1 is not bound
|
||||
ASSERT_EQ(my_stack.connect(client, &connect_addr), -1);
|
||||
ASSERT_EQ(errno, EINPROGRESS);
|
||||
|
||||
universe.process_events(0); // SYN
|
||||
|
||||
Socket accepted = my_stack.accept(sock);
|
||||
ASSERT_NE(net_socket_to_native(accepted), -1);
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkStackTest, ImplicitBindAvoidsCollision)
|
||||
{
|
||||
// Bind server to 33445 (default start of find_free_port)
|
||||
Socket server = stack.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
IP_Port addr;
|
||||
ip_init(&addr.ip, false);
|
||||
addr.ip.ip.v4.uint32 = 0;
|
||||
addr.port = net_htons(33445);
|
||||
ASSERT_EQ(stack.bind(server, &addr), 0);
|
||||
|
||||
// Create client and connect (implicit bind)
|
||||
Socket 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);
|
||||
server_addr.port = net_htons(33445);
|
||||
|
||||
// Should find a free port (not 33445)
|
||||
ASSERT_EQ(stack.connect(client, &server_addr), -1);
|
||||
ASSERT_EQ(errno, EINPROGRESS);
|
||||
|
||||
auto *client_obj = stack.get_sock(client);
|
||||
ASSERT_NE(client_obj, nullptr);
|
||||
ASSERT_NE(client_obj->local_port(), 33445);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
|
||||
459
testing/support/doubles/fake_network_tcp_test.cc
Normal file
459
testing/support/doubles/fake_network_tcp_test.cc
Normal file
@@ -0,0 +1,459 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "fake_sockets.hh"
|
||||
#include "network_universe.hh"
|
||||
|
||||
namespace tox::test {
|
||||
namespace {
|
||||
|
||||
class FakeNetworkTcpTest : public ::testing::Test {
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
|
||||
IP make_ip(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
|
||||
{
|
||||
IP ip;
|
||||
ip_init(&ip, false);
|
||||
ip.ip.v4.uint8[0] = a;
|
||||
ip.ip.v4.uint8[1] = b;
|
||||
ip.ip.v4.uint8[2] = c;
|
||||
ip.ip.v4.uint8[3] = d;
|
||||
return ip;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FakeNetworkTcpTest, MultipleConnectionsToSamePort)
|
||||
{
|
||||
universe.set_verbose(true);
|
||||
IP server_ip = make_ip(10, 0, 0, 1);
|
||||
uint16_t server_port = 12345;
|
||||
|
||||
FakeTcpSocket server(universe);
|
||||
server.set_ip(server_ip);
|
||||
IP_Port server_addr{server_ip, net_htons(server_port)};
|
||||
ASSERT_EQ(server.bind(&server_addr), 0);
|
||||
ASSERT_EQ(server.listen(5), 0);
|
||||
|
||||
// Client 1
|
||||
IP client1_ip = make_ip(10, 0, 0, 2);
|
||||
FakeTcpSocket client1(universe);
|
||||
client1.set_ip(client1_ip);
|
||||
client1.connect(&server_addr);
|
||||
|
||||
// Client 2 (same IP as client 1, different port)
|
||||
FakeTcpSocket client2(universe);
|
||||
client2.set_ip(client1_ip);
|
||||
client2.connect(&server_addr);
|
||||
|
||||
// Handshake for both
|
||||
// 1. SYNs
|
||||
universe.process_events(0);
|
||||
universe.process_events(0);
|
||||
|
||||
// 2. SYN-ACKs
|
||||
universe.process_events(0);
|
||||
universe.process_events(0);
|
||||
|
||||
// 3. ACKs
|
||||
universe.process_events(0);
|
||||
universe.process_events(0);
|
||||
|
||||
auto accepted1 = server.accept(nullptr);
|
||||
auto accepted2 = server.accept(nullptr);
|
||||
|
||||
ASSERT_NE(accepted1, nullptr);
|
||||
ASSERT_NE(accepted2, nullptr);
|
||||
|
||||
EXPECT_EQ(
|
||||
static_cast<FakeTcpSocket *>(accepted1.get())->state(), FakeTcpSocket::ESTABLISHED);
|
||||
EXPECT_EQ(
|
||||
static_cast<FakeTcpSocket *>(accepted2.get())->state(), FakeTcpSocket::ESTABLISHED);
|
||||
|
||||
// Verify data isolation
|
||||
const char *msg1 = "Message 1";
|
||||
const char *msg2 = "Message 2";
|
||||
|
||||
client1.send(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1));
|
||||
client2.send(reinterpret_cast<const uint8_t *>(msg2), strlen(msg2));
|
||||
|
||||
universe.process_events(0);
|
||||
universe.process_events(0);
|
||||
|
||||
uint8_t buf[100];
|
||||
int len1 = accepted1->recv(buf, sizeof(buf));
|
||||
EXPECT_EQ(len1, strlen(msg1));
|
||||
int len2 = accepted2->recv(buf, sizeof(buf));
|
||||
EXPECT_EQ(len2, strlen(msg2));
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkTcpTest, DuplicateSynCreatesDuplicateConnections)
|
||||
{
|
||||
universe.set_verbose(true);
|
||||
IP server_ip = make_ip(10, 0, 0, 1);
|
||||
uint16_t server_port = 12345;
|
||||
|
||||
FakeTcpSocket server(universe);
|
||||
server.set_ip(server_ip);
|
||||
IP_Port server_addr{server_ip, net_htons(server_port)};
|
||||
server.bind(&server_addr);
|
||||
server.listen(5);
|
||||
|
||||
IP client_ip = make_ip(10, 0, 0, 2);
|
||||
IP_Port client_addr{client_ip, net_htons(33445)};
|
||||
|
||||
Packet p{};
|
||||
p.from = client_addr;
|
||||
p.to = server_addr;
|
||||
p.is_tcp = true;
|
||||
p.tcp_flags = 0x02; // SYN
|
||||
p.seq = 100;
|
||||
|
||||
universe.send_packet(p);
|
||||
universe.send_packet(p); // Duplicate SYN
|
||||
|
||||
universe.process_events(0);
|
||||
universe.process_events(0);
|
||||
|
||||
// Now send ACK from client
|
||||
Packet ack{};
|
||||
ack.from = client_addr;
|
||||
ack.to = server_addr;
|
||||
ack.is_tcp = true;
|
||||
ack.tcp_flags = 0x10; // ACK
|
||||
ack.ack = 101;
|
||||
|
||||
universe.send_packet(ack);
|
||||
universe.process_events(0);
|
||||
|
||||
auto accepted1 = server.accept(nullptr);
|
||||
auto accepted2 = server.accept(nullptr);
|
||||
|
||||
ASSERT_NE(accepted1, nullptr);
|
||||
EXPECT_EQ(accepted2, nullptr); // This should pass now
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkTcpTest, PeerCloseClearsConnection)
|
||||
{
|
||||
universe.set_verbose(true);
|
||||
IP server_ip = make_ip(10, 0, 0, 1);
|
||||
uint16_t server_port = 12345;
|
||||
|
||||
FakeTcpSocket server(universe);
|
||||
server.set_ip(server_ip);
|
||||
IP_Port server_addr{server_ip, net_htons(server_port)};
|
||||
server.bind(&server_addr);
|
||||
server.listen(5);
|
||||
|
||||
IP client_ip = make_ip(10, 0, 0, 2);
|
||||
FakeTcpSocket client(universe);
|
||||
client.set_ip(client_ip);
|
||||
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);
|
||||
EXPECT_EQ(
|
||||
static_cast<FakeTcpSocket *>(accepted.get())->state(), FakeTcpSocket::ESTABLISHED);
|
||||
|
||||
// Client closes
|
||||
client.close();
|
||||
universe.process_events(0); // Deliver RST/FIN
|
||||
|
||||
// Server should no longer be ESTABLISHED
|
||||
EXPECT_EQ(static_cast<FakeTcpSocket *>(accepted.get())->state(), FakeTcpSocket::CLOSED);
|
||||
|
||||
// Now if client reconnects with same port
|
||||
FakeTcpSocket client2(universe);
|
||||
client2.set_ip(client_ip);
|
||||
client2.connect(&server_addr);
|
||||
universe.process_events(0); // Deliver SYN
|
||||
|
||||
// Node 2 port 20002 should have: 1 LISTEN, 0 ESTABLISHED (old one gone), 1 SYN_RECEIVED
|
||||
// (new one) Total targets should be 2.
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkTcpTest, DataNotProcessedByMultipleSockets)
|
||||
{
|
||||
universe.set_verbose(true);
|
||||
IP server_ip = make_ip(10, 0, 0, 1);
|
||||
uint16_t server_port = 12345;
|
||||
|
||||
FakeTcpSocket server(universe);
|
||||
server.set_ip(server_ip);
|
||||
IP_Port server_addr{server_ip, net_htons(server_port)};
|
||||
server.bind(&server_addr);
|
||||
server.listen(5);
|
||||
|
||||
IP client_ip = make_ip(10, 0, 0, 2);
|
||||
IP_Port client_addr{client_ip, net_htons(33445)};
|
||||
|
||||
// Manually create two "established" sockets on the same port for the same peer
|
||||
// This simulates a bug where duplicate connections were allowed.
|
||||
auto sock1 = FakeTcpSocket::create_connected(universe, client_addr, server_port);
|
||||
sock1->set_ip(server_ip);
|
||||
auto sock2 = FakeTcpSocket::create_connected(universe, client_addr, server_port);
|
||||
sock2->set_ip(server_ip);
|
||||
|
||||
universe.bind_tcp(server_ip, server_port, sock1.get());
|
||||
universe.bind_tcp(server_ip, server_port, sock2.get());
|
||||
|
||||
// Send data from client to server
|
||||
Packet p{};
|
||||
p.from = client_addr;
|
||||
p.to = server_addr;
|
||||
p.is_tcp = true;
|
||||
p.tcp_flags = 0x10; // ACK (Data)
|
||||
const char *data = "Unique";
|
||||
p.data.assign(data, data + strlen(data));
|
||||
|
||||
universe.send_packet(p);
|
||||
universe.process_events(0);
|
||||
|
||||
// Only ONE of them should have received it, or at least they shouldn't BOTH have it
|
||||
// in a way that suggests duplicate delivery.
|
||||
EXPECT_TRUE((sock1->recv_buffer_size() == strlen(data))
|
||||
^ (sock2->recv_buffer_size() == strlen(data)));
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkTcpTest, ConnectionCollision)
|
||||
{
|
||||
universe.set_verbose(true);
|
||||
IP server_ip = make_ip(10, 0, 0, 1);
|
||||
uint16_t server_port = 12345;
|
||||
|
||||
FakeTcpSocket server(universe);
|
||||
server.set_ip(server_ip);
|
||||
IP_Port server_addr{server_ip, net_htons(server_port)};
|
||||
server.bind(&server_addr);
|
||||
server.listen(5);
|
||||
|
||||
IP client_ip = make_ip(10, 0, 0, 2);
|
||||
|
||||
FakeTcpSocket client1(universe);
|
||||
client1.set_ip(client_ip);
|
||||
// Bind to specific port to force collision later
|
||||
IP_Port client_bind_addr{client_ip, net_htons(33445)};
|
||||
client1.bind(&client_bind_addr);
|
||||
client1.connect(&server_addr);
|
||||
|
||||
// Handshake 1
|
||||
universe.process_events(0); // SYN
|
||||
universe.process_events(0); // SYN-ACK
|
||||
universe.process_events(0); // ACK
|
||||
|
||||
auto accepted1 = server.accept(nullptr);
|
||||
ASSERT_NE(accepted1, nullptr);
|
||||
EXPECT_EQ(
|
||||
static_cast<FakeTcpSocket *>(accepted1.get())->state(), FakeTcpSocket::ESTABLISHED);
|
||||
|
||||
// Now client 1 "reconnects" (e.g. after a crash or timeout, but using same port)
|
||||
FakeTcpSocket client2(universe);
|
||||
client2.set_ip(client_ip);
|
||||
client2.bind(&client_bind_addr); // Forced collision
|
||||
client2.connect(&server_addr);
|
||||
|
||||
// Deliver new SYN
|
||||
universe.process_events(0);
|
||||
|
||||
// server_addr port 12345 now has:
|
||||
// 1. LISTEN socket
|
||||
// 2. accepted1 (ESTABLISHED with 10.0.0.2:33445)
|
||||
|
||||
// In our simplified simulation, the ESTABLISHED socket now handles the SYN by returning
|
||||
// true (ignoring it). So no new connection is created.
|
||||
auto accepted2 = server.accept(nullptr);
|
||||
EXPECT_EQ(accepted2, nullptr);
|
||||
|
||||
const char *msg1 = "Data 1";
|
||||
client1.send(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1));
|
||||
universe.process_events(0);
|
||||
|
||||
// Data should still go to accepted1
|
||||
EXPECT_EQ(accepted1->recv_buffer_size(), strlen(msg1));
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkTcpTest, LoopbackConnection)
|
||||
{
|
||||
universe.set_verbose(true);
|
||||
IP node_ip = make_ip(10, 0, 0, 1);
|
||||
uint16_t port = 12345;
|
||||
|
||||
FakeTcpSocket server(universe);
|
||||
server.set_ip(node_ip);
|
||||
IP_Port listen_addr{node_ip, net_htons(port)};
|
||||
server.bind(&listen_addr);
|
||||
server.listen(5);
|
||||
|
||||
FakeTcpSocket client(universe);
|
||||
client.set_ip(node_ip);
|
||||
IP loopback_ip;
|
||||
ip_init(&loopback_ip, false);
|
||||
loopback_ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
IP_Port server_loopback_addr{loopback_ip, net_htons(port)};
|
||||
|
||||
client.connect(&server_loopback_addr);
|
||||
|
||||
// SYN (Client -> 127.0.0.1:12345)
|
||||
universe.process_events(0);
|
||||
|
||||
// SYN-ACK (Server -> Client)
|
||||
universe.process_events(0);
|
||||
|
||||
// ACK (Client -> Server)
|
||||
universe.process_events(0);
|
||||
|
||||
EXPECT_EQ(client.state(), FakeTcpSocket::ESTABLISHED);
|
||||
auto accepted = server.accept(nullptr);
|
||||
ASSERT_NE(accepted, nullptr);
|
||||
EXPECT_EQ(
|
||||
static_cast<FakeTcpSocket *>(accepted.get())->state(), FakeTcpSocket::ESTABLISHED);
|
||||
|
||||
// Data Transfer
|
||||
const char *msg = "Loopback";
|
||||
client.send(reinterpret_cast<const uint8_t *>(msg), strlen(msg));
|
||||
universe.process_events(0);
|
||||
|
||||
uint8_t buf[100];
|
||||
int len = accepted->recv(buf, sizeof(buf));
|
||||
ASSERT_EQ(len, strlen(msg));
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), len), msg);
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkTcpTest, SimultaneousConnect)
|
||||
{
|
||||
universe.set_verbose(true);
|
||||
IP ipA = make_ip(10, 0, 0, 1);
|
||||
IP ipB = make_ip(10, 0, 0, 2);
|
||||
uint16_t portA = 10001;
|
||||
uint16_t portB = 10002;
|
||||
|
||||
FakeTcpSocket sockA(universe);
|
||||
sockA.set_ip(ipA);
|
||||
IP_Port addrA{ipA, net_htons(portA)};
|
||||
sockA.bind(&addrA);
|
||||
sockA.listen(5);
|
||||
|
||||
FakeTcpSocket sockB(universe);
|
||||
sockB.set_ip(ipB);
|
||||
IP_Port addrB{ipB, net_htons(portB)};
|
||||
sockB.bind(&addrB);
|
||||
sockB.listen(5);
|
||||
|
||||
// A connects to B
|
||||
sockA.connect(&addrB);
|
||||
// B connects to A
|
||||
sockB.connect(&addrA);
|
||||
|
||||
// This is "simultaneous open" in TCP but here they are also LISTENing.
|
||||
// Toxcore uses this pattern sometimes.
|
||||
|
||||
universe.process_events(0); // SYN from A to B
|
||||
universe.process_events(0); // SYN from B to A
|
||||
|
||||
universe.process_events(0); // SYN-ACK from B to A (for A's SYN)
|
||||
universe.process_events(0); // SYN-ACK from A to B (for B's SYN)
|
||||
|
||||
universe.process_events(0); // ACK from A to B
|
||||
universe.process_events(0); // ACK from B to A
|
||||
|
||||
EXPECT_EQ(sockA.state(), FakeTcpSocket::ESTABLISHED);
|
||||
EXPECT_EQ(sockB.state(), FakeTcpSocket::ESTABLISHED);
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkTcpTest, DataInHandshakeAck)
|
||||
{
|
||||
universe.set_verbose(true);
|
||||
IP server_ip = make_ip(10, 0, 0, 1);
|
||||
uint16_t server_port = 12345;
|
||||
|
||||
FakeTcpSocket server(universe);
|
||||
server.set_ip(server_ip);
|
||||
IP_Port server_addr{server_ip, net_htons(server_port)};
|
||||
server.bind(&server_addr);
|
||||
server.listen(5);
|
||||
|
||||
IP client_ip = make_ip(10, 0, 0, 2);
|
||||
IP_Port client_addr{client_ip, net_htons(33445)};
|
||||
|
||||
// 1. SYN
|
||||
Packet syn{};
|
||||
syn.from = client_addr;
|
||||
syn.to = server_addr;
|
||||
syn.is_tcp = true;
|
||||
syn.tcp_flags = 0x02;
|
||||
universe.send_packet(syn);
|
||||
universe.process_events(0);
|
||||
|
||||
// 2. SYN-ACK (Server -> Client)
|
||||
universe.process_events(0);
|
||||
|
||||
// 3. ACK + Data (Client -> Server)
|
||||
Packet ack{};
|
||||
ack.from = client_addr;
|
||||
ack.to = server_addr;
|
||||
ack.is_tcp = true;
|
||||
ack.tcp_flags = 0x10;
|
||||
const char *data = "HandshakeData";
|
||||
ack.data.assign(data, data + strlen(data));
|
||||
universe.send_packet(ack);
|
||||
universe.process_events(0);
|
||||
|
||||
auto accepted = server.accept(nullptr);
|
||||
ASSERT_NE(accepted, nullptr);
|
||||
EXPECT_EQ(accepted->recv_buffer_size(), strlen(data));
|
||||
}
|
||||
|
||||
TEST_F(FakeNetworkTcpTest, LoopbackWithNodeIPMixed)
|
||||
{
|
||||
universe.set_verbose(true);
|
||||
IP node_ip = make_ip(10, 0, 0, 1);
|
||||
uint16_t port = 12345;
|
||||
|
||||
FakeTcpSocket server(universe);
|
||||
server.set_ip(node_ip);
|
||||
IP_Port listen_addr{node_ip, net_htons(port)};
|
||||
server.bind(&listen_addr);
|
||||
server.listen(5);
|
||||
|
||||
FakeTcpSocket client(universe);
|
||||
client.set_ip(node_ip);
|
||||
IP loopback_ip;
|
||||
ip_init(&loopback_ip, false);
|
||||
loopback_ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
IP_Port server_loopback_addr{loopback_ip, net_htons(port)};
|
||||
|
||||
// Client connects to 127.0.0.1
|
||||
client.connect(&server_loopback_addr);
|
||||
|
||||
universe.process_events(0); // SYN (Client -> 127.0.0.1)
|
||||
universe.process_events(0); // SYN-ACK (Server -> Client)
|
||||
universe.process_events(0); // ACK (Client -> Server)
|
||||
|
||||
EXPECT_EQ(client.state(), FakeTcpSocket::ESTABLISHED);
|
||||
auto accepted = server.accept(nullptr);
|
||||
ASSERT_NE(accepted, nullptr);
|
||||
|
||||
// Now manually simulate a packet coming from the server's EXTERNAL IP to the client.
|
||||
// This happens because the server socket is bound to node_ip, so its packets might
|
||||
// be delivered as coming from node_ip even if the client connected to 127.0.0.1.
|
||||
Packet p{};
|
||||
p.from = listen_addr; // node_ip:port
|
||||
p.to.ip = node_ip;
|
||||
p.to.port = net_htons(client.local_port());
|
||||
p.is_tcp = true;
|
||||
p.tcp_flags = 0x10; // ACK
|
||||
const char *msg = "MixedIP";
|
||||
p.data.assign(msg, msg + strlen(msg));
|
||||
|
||||
universe.send_packet(p);
|
||||
universe.process_events(0);
|
||||
|
||||
EXPECT_EQ(client.recv_buffer_size(), strlen(msg));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
75
testing/support/doubles/fake_network_udp_test.cc
Normal file
75
testing/support/doubles/fake_network_udp_test.cc
Normal file
@@ -0,0 +1,75 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "fake_network_stack.hh"
|
||||
#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 FakeNetworkUdpTest : public ::testing::Test {
|
||||
public:
|
||||
FakeNetworkUdpTest()
|
||||
: ip1(make_ip(0x0A000001)) // 10.0.0.1
|
||||
, ip2(make_ip(0x0A000002)) // 10.0.0.2
|
||||
, stack1{universe, ip1}
|
||||
, stack2{universe, ip2}
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
NetworkUniverse universe;
|
||||
IP ip1, ip2;
|
||||
FakeNetworkStack stack1;
|
||||
FakeNetworkStack stack2;
|
||||
};
|
||||
|
||||
TEST_F(FakeNetworkUdpTest, UdpExchange)
|
||||
{
|
||||
Socket sock1 = stack1.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
Socket sock2 = stack2.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
IP_Port addr1;
|
||||
addr1.ip = ip1;
|
||||
addr1.port = net_htons(1234);
|
||||
ASSERT_EQ(stack1.bind(sock1, &addr1), 0);
|
||||
|
||||
IP_Port addr2;
|
||||
addr2.ip = ip2;
|
||||
addr2.port = net_htons(5678);
|
||||
ASSERT_EQ(stack2.bind(sock2, &addr2), 0);
|
||||
|
||||
const char *msg = "Hello UDP";
|
||||
size_t msg_len = strlen(msg) + 1;
|
||||
|
||||
// Send from 1 to 2
|
||||
ASSERT_EQ(stack1.sendto(sock1, reinterpret_cast<const uint8_t *>(msg), msg_len, &addr2),
|
||||
static_cast<int>(msg_len));
|
||||
|
||||
// Delivery
|
||||
universe.process_events(10); // With some time offset
|
||||
|
||||
// Receive at 2
|
||||
uint8_t buffer[1024];
|
||||
IP_Port from_addr;
|
||||
int recv_len = stack2.recvfrom(sock2, buffer, sizeof(buffer), &from_addr);
|
||||
|
||||
ASSERT_EQ(recv_len, static_cast<int>(msg_len));
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(buffer), msg);
|
||||
EXPECT_EQ(net_ntohl(from_addr.ip.ip.v4.uint32), net_ntohl(ip1.ip.v4.uint32));
|
||||
EXPECT_EQ(net_ntohs(from_addr.port), 1234);
|
||||
|
||||
stack1.close(sock1);
|
||||
stack2.close(sock2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
@@ -7,19 +7,19 @@
|
||||
#include "../public/random.hh"
|
||||
|
||||
// Forward declaration
|
||||
struct Tox_Random;
|
||||
struct 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)>;
|
||||
using EntropySource = std::function<void(uint8_t *_Nonnull out, size_t count)>;
|
||||
using Observer = std::function<void(const uint8_t *_Nonnull 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;
|
||||
void bytes(uint8_t *_Nonnull out, size_t count) override;
|
||||
|
||||
/**
|
||||
* @brief Set a custom entropy source.
|
||||
@@ -32,7 +32,10 @@ public:
|
||||
*/
|
||||
void set_observer(Observer observer);
|
||||
|
||||
struct Tox_Random get_c_random();
|
||||
/**
|
||||
* @brief Returns C-compatible Random struct.
|
||||
*/
|
||||
struct Random c_random() override;
|
||||
|
||||
private:
|
||||
std::minstd_rand rng_;
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "network_universe.hh"
|
||||
|
||||
@@ -27,8 +31,14 @@ int FakeSocket::close()
|
||||
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::getsockopt(int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int FakeSocket::setsockopt(int level, int optname, const void *_Nonnull optval, size_t optlen)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int FakeSocket::socket_nonblock(bool nonblock)
|
||||
{
|
||||
nonblocking_ = nonblock;
|
||||
@@ -59,7 +69,7 @@ void FakeUdpSocket::close_impl()
|
||||
}
|
||||
}
|
||||
|
||||
int FakeUdpSocket::bind(const IP_Port *addr)
|
||||
int FakeUdpSocket::bind(const IP_Port *_Nonnull addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ != 0)
|
||||
@@ -80,7 +90,7 @@ int FakeUdpSocket::bind(const IP_Port *addr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::connect(const IP_Port *addr)
|
||||
int FakeUdpSocket::connect(const IP_Port *_Nonnull addr)
|
||||
{
|
||||
// UDP connect just sets default dest.
|
||||
// Not strictly needed for toxcore UDP but good for completeness.
|
||||
@@ -92,23 +102,29 @@ int FakeUdpSocket::listen(int backlog)
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
std::unique_ptr<FakeSocket> FakeUdpSocket::accept(IP_Port *addr)
|
||||
std::unique_ptr<FakeSocket> FakeUdpSocket::accept(IP_Port *_Nullable addr)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return nullptr;
|
||||
}
|
||||
int FakeUdpSocket::send(const uint8_t *buf, size_t len)
|
||||
int FakeUdpSocket::send(const uint8_t *_Nonnull buf, size_t len)
|
||||
{
|
||||
errno = EDESTADDRREQ;
|
||||
return -1;
|
||||
}
|
||||
int FakeUdpSocket::recv(uint8_t *buf, size_t len)
|
||||
int FakeUdpSocket::recv(uint8_t *_Nonnull buf, size_t len)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr)
|
||||
size_t FakeUdpSocket::recv_buffer_size()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return recv_queue_.size();
|
||||
}
|
||||
|
||||
int FakeUdpSocket::sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ == 0) {
|
||||
@@ -132,16 +148,15 @@ int FakeUdpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr)
|
||||
|
||||
universe_.send_packet(p);
|
||||
if (universe_.is_verbose()) {
|
||||
uint32_t tip4 = net_ntohl(addr->ip.ip.v4.uint32);
|
||||
Ip_Ntoa ip_str;
|
||||
net_ip_ntoa(&addr->ip, &ip_str);
|
||||
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;
|
||||
<< ip_str.buf << ":" << net_ntohs(addr->port) << std::endl;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int FakeUdpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr)
|
||||
int FakeUdpSocket::recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr)
|
||||
{
|
||||
RecvObserver observer_copy;
|
||||
std::vector<uint8_t> data_copy;
|
||||
@@ -196,15 +211,13 @@ 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);
|
||||
Ip_Ntoa local_ip_str, from_ip_str;
|
||||
net_ip_ntoa(&ip_, &local_ip_str);
|
||||
net_ip_ntoa(&from.ip, &from_ip_str);
|
||||
|
||||
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;
|
||||
<< local_ip_str.buf << ":" << local_port_ << " from " << from_ip_str.buf << ":"
|
||||
<< net_ntohs(from.port) << std::endl;
|
||||
}
|
||||
recv_queue_.push_back({std::move(data), from});
|
||||
}
|
||||
@@ -225,8 +238,8 @@ void FakeUdpSocket::set_recv_observer(RecvObserver observer)
|
||||
|
||||
FakeTcpSocket::FakeTcpSocket(NetworkUniverse &universe)
|
||||
: FakeSocket(universe, SOCK_STREAM)
|
||||
, remote_addr_{}
|
||||
{
|
||||
ipport_reset(&remote_addr_);
|
||||
}
|
||||
|
||||
FakeTcpSocket::~FakeTcpSocket() { close_impl(); }
|
||||
@@ -234,6 +247,17 @@ FakeTcpSocket::~FakeTcpSocket() { close_impl(); }
|
||||
int FakeTcpSocket::close()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ == ESTABLISHED || state_ == SYN_SENT || state_ == SYN_RECEIVED
|
||||
|| state_ == CLOSE_WAIT) {
|
||||
// Send RST to peer
|
||||
Packet p{};
|
||||
p.from.ip = ip_;
|
||||
p.from.port = net_htons(local_port_);
|
||||
p.to = remote_addr_;
|
||||
p.is_tcp = true;
|
||||
p.tcp_flags = 0x04; // RST
|
||||
universe_.send_packet(p);
|
||||
}
|
||||
close_impl();
|
||||
return 0;
|
||||
}
|
||||
@@ -247,7 +271,7 @@ void FakeTcpSocket::close_impl()
|
||||
state_ = CLOSED;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::bind(const IP_Port *addr)
|
||||
int FakeTcpSocket::bind(const IP_Port *_Nonnull addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (local_port_ != 0)
|
||||
@@ -276,14 +300,25 @@ int FakeTcpSocket::listen(int backlog)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::connect(const IP_Port *addr)
|
||||
int FakeTcpSocket::connect(const IP_Port *_Nonnull addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (universe_.is_verbose()) {
|
||||
Ip_Ntoa ip_str, dest_str;
|
||||
net_ip_ntoa(&ip_, &ip_str);
|
||||
net_ip_ntoa(&addr->ip, &dest_str);
|
||||
std::cerr << "[FakeTcpSocket] connect from " << ip_str.buf << " to " << dest_str.buf << ":"
|
||||
<< net_ntohs(addr->port) << std::endl;
|
||||
}
|
||||
|
||||
if (local_port_ == 0) {
|
||||
// Implicit bind
|
||||
uint16_t p = universe_.find_free_port(ip_);
|
||||
if (universe_.bind_tcp(ip_, p, this)) {
|
||||
local_port_ = p;
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeTcpSocket] implicit bind to port " << local_port_ << std::endl;
|
||||
}
|
||||
} else {
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
@@ -310,7 +345,7 @@ int FakeTcpSocket::connect(const IP_Port *addr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *addr)
|
||||
std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *_Nullable addr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ != LISTEN) {
|
||||
@@ -318,13 +353,16 @@ std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *addr)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (pending_connections_.empty()) {
|
||||
auto it = std::find_if(pending_connections_.begin(), pending_connections_.end(),
|
||||
[](const std::unique_ptr<FakeTcpSocket> &s) { return s->state() == ESTABLISHED; });
|
||||
|
||||
if (it == pending_connections_.end()) {
|
||||
errno = EWOULDBLOCK;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto client = std::move(pending_connections_.front());
|
||||
pending_connections_.pop_front();
|
||||
auto client = std::move(*it);
|
||||
pending_connections_.erase(it);
|
||||
|
||||
if (addr) {
|
||||
*addr = client->remote_addr();
|
||||
@@ -332,11 +370,19 @@ std::unique_ptr<FakeSocket> FakeTcpSocket::accept(IP_Port *addr)
|
||||
return client;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::send(const uint8_t *buf, size_t len)
|
||||
int FakeTcpSocket::send(const uint8_t *_Nonnull buf, size_t len)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ != ESTABLISHED) {
|
||||
errno = ENOTCONN;
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeTcpSocket] send failed: state " << state_ << " port " << local_port_
|
||||
<< std::endl;
|
||||
}
|
||||
if (state_ == SYN_SENT || state_ == SYN_RECEIVED) {
|
||||
errno = EWOULDBLOCK;
|
||||
} else {
|
||||
errno = ENOTCONN;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -357,7 +403,7 @@ int FakeTcpSocket::send(const uint8_t *buf, size_t len)
|
||||
return len;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::recv(uint8_t *buf, size_t len)
|
||||
int FakeTcpSocket::recv(uint8_t *_Nonnull buf, size_t len)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (recv_buffer_.empty()) {
|
||||
@@ -368,6 +414,13 @@ int FakeTcpSocket::recv(uint8_t *buf, size_t len)
|
||||
}
|
||||
|
||||
size_t actual = std::min(len, recv_buffer_.size());
|
||||
if (universe_.is_verbose() && actual > 0) {
|
||||
char remote_ip_str[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&remote_addr_.ip, remote_ip_str, sizeof(remote_ip_str));
|
||||
std::cerr << "[FakeTcpSocket] Port " << local_port_ << " (Peer: " << remote_ip_str << ":"
|
||||
<< net_ntohs(remote_addr_.port) << ") recv requested " << len << " got " << actual
|
||||
<< " (remaining " << recv_buffer_.size() - actual << ")" << std::endl;
|
||||
}
|
||||
for (size_t i = 0; i < actual; ++i) {
|
||||
buf[i] = recv_buffer_.front();
|
||||
recv_buffer_.pop_front();
|
||||
@@ -381,37 +434,109 @@ size_t FakeTcpSocket::recv_buffer_size()
|
||||
return recv_buffer_.size();
|
||||
}
|
||||
|
||||
int FakeTcpSocket::sendto(const uint8_t *buf, size_t len, const IP_Port *addr)
|
||||
bool FakeTcpSocket::is_readable()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_ == LISTEN) {
|
||||
return std::any_of(pending_connections_.begin(), pending_connections_.end(),
|
||||
[](const std::unique_ptr<FakeTcpSocket> &s) { return s->state() == ESTABLISHED; });
|
||||
}
|
||||
return !recv_buffer_.empty() || state_ == CLOSED || state_ == CLOSE_WAIT;
|
||||
}
|
||||
|
||||
bool FakeTcpSocket::is_writable()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return state_ == ESTABLISHED;
|
||||
}
|
||||
|
||||
int FakeTcpSocket::sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
int FakeTcpSocket::recvfrom(uint8_t *buf, size_t len, IP_Port *addr)
|
||||
int FakeTcpSocket::recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr)
|
||||
{
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void FakeTcpSocket::handle_packet(const Packet &p)
|
||||
int FakeTcpSocket::getsockopt(
|
||||
int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen)
|
||||
{
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeTcpSocket] getsockopt level=" << level << " optname=" << optname
|
||||
<< " state=" << state_ << std::endl;
|
||||
}
|
||||
if (level == SOL_SOCKET && optname == SO_ERROR) {
|
||||
int error = 0;
|
||||
if (state_ == SYN_SENT || state_ == SYN_RECEIVED) {
|
||||
error = EINPROGRESS;
|
||||
} else if (state_ == CLOSED) {
|
||||
error = ECONNREFUSED;
|
||||
}
|
||||
|
||||
if (*optlen >= sizeof(int)) {
|
||||
*static_cast<int *>(optval) = error;
|
||||
*optlen = sizeof(int);
|
||||
}
|
||||
if (universe_.is_verbose()) {
|
||||
std::cerr << "[FakeTcpSocket] getsockopt SO_ERROR returning error=" << error
|
||||
<< std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool 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;
|
||||
char remote_ip_str[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&remote_addr_.ip, remote_ip_str, sizeof(remote_ip_str));
|
||||
std::cerr << "Handle Packet: Port " << local_port_ << " (Peer: " << remote_ip_str << ":"
|
||||
<< net_ntohs(remote_addr_.port) << ") Flags " << TcpFlags{p.tcp_flags}
|
||||
<< " State " << state_ << " From " << net_ntohs(p.from.port) << std::endl;
|
||||
}
|
||||
|
||||
if (state_ != LISTEN) {
|
||||
// Filter packets not from our peer
|
||||
bool port_match = net_ntohs(p.from.port) == net_ntohs(remote_addr_.port);
|
||||
bool ip_match = ip_equal(&p.from.ip, &remote_addr_.ip)
|
||||
|| (is_loopback(p.from.ip) && ip_equal(&remote_addr_.ip, &ip_))
|
||||
|| (is_loopback(remote_addr_.ip) && ip_equal(&p.from.ip, &ip_));
|
||||
|
||||
if (!port_match || !ip_match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p.tcp_flags & 0x04) { // RST
|
||||
state_ = CLOSED;
|
||||
if (local_port_ != 0) {
|
||||
universe_.unbind_tcp(ip_, local_port_, this);
|
||||
local_port_ = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (state_ == LISTEN) {
|
||||
if (p.tcp_flags & 0x02) { // SYN
|
||||
// Check for duplicate SYN from same peer
|
||||
for (const auto &pending : pending_connections_) {
|
||||
if (ipport_equal(&p.from, &pending->remote_addr_)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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->set_ip(ip_); // Inherit IP from listening socket
|
||||
new_sock->last_ack_ = p.seq + 1;
|
||||
new_sock->next_seq_ = 1000; // Random ISN
|
||||
|
||||
@@ -428,13 +553,9 @@ void FakeTcpSocket::handle_packet(const Packet &p)
|
||||
|
||||
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;
|
||||
// Add to pending, but it's still SYN_RECEIVED
|
||||
pending_connections_.push_back(std::move(new_sock));
|
||||
return true;
|
||||
}
|
||||
} else if (state_ == SYN_SENT) {
|
||||
if ((p.tcp_flags & 0x12) == 0x12) { // SYN | ACK
|
||||
@@ -451,8 +572,31 @@ void FakeTcpSocket::handle_packet(const Packet &p)
|
||||
ack.seq = next_seq_;
|
||||
ack.ack = last_ack_;
|
||||
universe_.send_packet(ack);
|
||||
return true;
|
||||
} else if (p.tcp_flags & 0x02) { // SYN (Simultaneous Open)
|
||||
state_ = SYN_RECEIVED;
|
||||
last_ack_ = p.seq + 1;
|
||||
|
||||
// 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 = next_seq_++;
|
||||
resp.ack = last_ack_;
|
||||
universe_.send_packet(resp);
|
||||
return true;
|
||||
}
|
||||
} else if (state_ == ESTABLISHED) {
|
||||
} else if (state_ == SYN_RECEIVED) {
|
||||
if (p.tcp_flags & 0x10) { // ACK
|
||||
state_ = ESTABLISHED;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (state_ == ESTABLISHED) {
|
||||
if (p.tcp_flags & 0x01) { // FIN
|
||||
state_ = CLOSE_WAIT;
|
||||
// Send ACK
|
||||
@@ -464,12 +608,23 @@ void FakeTcpSocket::handle_packet(const Packet &p)
|
||||
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?
|
||||
return true;
|
||||
} else {
|
||||
if (!p.data.empty()) {
|
||||
if (universe_.is_verbose()) {
|
||||
char remote_ip_str[TOX_INET_ADDRSTRLEN];
|
||||
ip_parse_addr(&remote_addr_.ip, remote_ip_str, sizeof(remote_ip_str));
|
||||
std::cerr << "[FakeTcpSocket] Port " << local_port_
|
||||
<< " (Peer: " << remote_ip_str << ":" << net_ntohs(remote_addr_.port)
|
||||
<< ") adding " << p.data.size() << " bytes to buffer (currently "
|
||||
<< recv_buffer_.size() << ")" << std::endl;
|
||||
}
|
||||
recv_buffer_.insert(recv_buffer_.end(), p.data.begin(), p.data.end());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<FakeTcpSocket> FakeTcpSocket::create_connected(
|
||||
@@ -482,4 +637,23 @@ std::unique_ptr<FakeTcpSocket> FakeTcpSocket::create_connected(
|
||||
return s;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, FakeTcpSocket::State state)
|
||||
{
|
||||
switch (state) {
|
||||
case FakeTcpSocket::CLOSED:
|
||||
return os << "CLOSED";
|
||||
case FakeTcpSocket::LISTEN:
|
||||
return os << "LISTEN";
|
||||
case FakeTcpSocket::SYN_SENT:
|
||||
return os << "SYN_SENT";
|
||||
case FakeTcpSocket::SYN_RECEIVED:
|
||||
return os << "SYN_RECEIVED";
|
||||
case FakeTcpSocket::ESTABLISHED:
|
||||
return os << "ESTABLISHED";
|
||||
case FakeTcpSocket::CLOSE_WAIT:
|
||||
return os << "CLOSE_WAIT";
|
||||
}
|
||||
return os << "UNKNOWN(" << static_cast<int>(state) << ")";
|
||||
}
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#include "../../../toxcore/attributes.h"
|
||||
#include "../../../toxcore/network.h"
|
||||
|
||||
namespace tox::test {
|
||||
@@ -33,21 +34,23 @@ 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 bind(const IP_Port *_Nonnull addr) = 0;
|
||||
virtual int connect(const IP_Port *_Nonnull addr) = 0;
|
||||
virtual int listen(int backlog) = 0;
|
||||
virtual std::unique_ptr<FakeSocket> accept(IP_Port *addr) = 0;
|
||||
virtual std::unique_ptr<FakeSocket> accept(IP_Port *_Nullable addr) = 0;
|
||||
|
||||
virtual int send(const uint8_t *buf, size_t len) = 0;
|
||||
virtual int recv(uint8_t *buf, size_t len) = 0;
|
||||
virtual int send(const uint8_t *_Nonnull buf, size_t len) = 0;
|
||||
virtual int recv(uint8_t *_Nonnull buf, size_t len) = 0;
|
||||
|
||||
virtual size_t recv_buffer_size() { return 0; }
|
||||
virtual bool is_readable() { return recv_buffer_size() > 0; }
|
||||
virtual bool is_writable() { return true; }
|
||||
|
||||
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 sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr) = 0;
|
||||
virtual int recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull 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 getsockopt(int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen);
|
||||
virtual int setsockopt(int level, int optname, const void *_Nonnull optval, size_t optlen);
|
||||
virtual int socket_nonblock(bool nonblock);
|
||||
|
||||
virtual int close();
|
||||
@@ -76,17 +79,18 @@ public:
|
||||
explicit FakeUdpSocket(NetworkUniverse &universe);
|
||||
~FakeUdpSocket() override;
|
||||
|
||||
int bind(const IP_Port *addr) override;
|
||||
int connect(const IP_Port *addr) override;
|
||||
int bind(const IP_Port *_Nonnull addr) override;
|
||||
int connect(const IP_Port *_Nonnull addr) override;
|
||||
int listen(int backlog) override;
|
||||
std::unique_ptr<FakeSocket> accept(IP_Port *addr) override;
|
||||
std::unique_ptr<FakeSocket> accept(IP_Port *_Nullable 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 send(const uint8_t *_Nonnull buf, size_t len) override;
|
||||
int recv(uint8_t *_Nonnull 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;
|
||||
int sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr) override;
|
||||
int recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr) override;
|
||||
|
||||
// Called by Universe to deliver a packet
|
||||
void push_packet(std::vector<uint8_t> data, IP_Port from);
|
||||
@@ -124,21 +128,25 @@ public:
|
||||
explicit FakeTcpSocket(NetworkUniverse &universe);
|
||||
~FakeTcpSocket() override;
|
||||
|
||||
int bind(const IP_Port *addr) override;
|
||||
int connect(const IP_Port *addr) override;
|
||||
int bind(const IP_Port *_Nonnull addr) override;
|
||||
int connect(const IP_Port *_Nonnull addr) override;
|
||||
int listen(int backlog) override;
|
||||
std::unique_ptr<FakeSocket> accept(IP_Port *addr) override;
|
||||
std::unique_ptr<FakeSocket> accept(IP_Port *_Nullable 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 send(const uint8_t *_Nonnull buf, size_t len) override;
|
||||
int recv(uint8_t *_Nonnull buf, size_t len) override;
|
||||
size_t recv_buffer_size() override;
|
||||
bool is_readable() override;
|
||||
bool is_writable() 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;
|
||||
int sendto(const uint8_t *_Nonnull buf, size_t len, const IP_Port *_Nonnull addr) override;
|
||||
int recvfrom(uint8_t *_Nonnull buf, size_t len, IP_Port *_Nonnull addr) override;
|
||||
|
||||
int getsockopt(int level, int optname, void *_Nonnull optval, size_t *_Nonnull optlen) override;
|
||||
|
||||
// Internal events
|
||||
void handle_packet(const Packet &p);
|
||||
bool handle_packet(const Packet &p);
|
||||
|
||||
State state() const { return state_; }
|
||||
const IP_Port &remote_addr() const { return remote_addr_; }
|
||||
@@ -160,6 +168,8 @@ private:
|
||||
uint32_t last_ack_ = 0;
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, FakeTcpSocket::State state);
|
||||
|
||||
} // namespace tox::test
|
||||
|
||||
#endif // C_TOXCORE_TESTING_SUPPORT_DOUBLES_FAKE_SOCKETS_H
|
||||
|
||||
@@ -82,6 +82,46 @@ namespace {
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), 5), "Hello");
|
||||
}
|
||||
|
||||
TEST_F(FakeTcpSocketTest, RecvBuffering)
|
||||
{
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
server_addr.port = net_htons(8082);
|
||||
|
||||
server.bind(&server_addr);
|
||||
server.listen(5);
|
||||
client.connect(&server_addr);
|
||||
|
||||
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);
|
||||
|
||||
uint8_t msg1[] = "Part1";
|
||||
uint8_t msg2[] = "Part2";
|
||||
client.send(msg1, 5);
|
||||
client.send(msg2, 5);
|
||||
|
||||
universe.process_events(0); // Deliver Part1
|
||||
universe.process_events(0); // Deliver Part2
|
||||
|
||||
EXPECT_EQ(accepted->recv_buffer_size(), 10);
|
||||
|
||||
uint8_t recv_buf[20];
|
||||
// Read partial
|
||||
ASSERT_EQ(accepted->recv(recv_buf, 3), 3);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), 3), "Par");
|
||||
EXPECT_EQ(accepted->recv_buffer_size(), 7);
|
||||
|
||||
// Read rest
|
||||
ASSERT_EQ(accepted->recv(recv_buf, 7), 7);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(recv_buf), 7), "t1Part2");
|
||||
EXPECT_EQ(accepted->recv_buffer_size(), 0);
|
||||
}
|
||||
|
||||
class FakeUdpSocketTest : public ::testing::Test {
|
||||
public:
|
||||
~FakeUdpSocketTest() override;
|
||||
@@ -119,6 +159,40 @@ namespace {
|
||||
EXPECT_EQ(sender_addr.port, net_htons(client.local_port()));
|
||||
}
|
||||
|
||||
TEST_F(FakeUdpSocketTest, RecvBuffering)
|
||||
{
|
||||
IP_Port server_addr;
|
||||
ip_init(&server_addr.ip, false);
|
||||
server_addr.ip.ip.v4.uint32 = net_htonl(0x7F000001);
|
||||
server_addr.port = net_htons(9001);
|
||||
|
||||
server.bind(&server_addr);
|
||||
|
||||
const char *msg1 = "Msg1";
|
||||
const char *msg2 = "Msg2";
|
||||
|
||||
client.sendto(reinterpret_cast<const uint8_t *>(msg1), strlen(msg1), &server_addr);
|
||||
client.sendto(reinterpret_cast<const uint8_t *>(msg2), strlen(msg2), &server_addr);
|
||||
|
||||
universe.process_events(0); // Deliver msg1
|
||||
universe.process_events(0); // Deliver msg2
|
||||
|
||||
EXPECT_EQ(server.recv_buffer_size(), 2);
|
||||
|
||||
IP_Port sender;
|
||||
uint8_t buf[10];
|
||||
|
||||
int len = server.recvfrom(buf, sizeof(buf), &sender);
|
||||
ASSERT_EQ(len, 4);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), len), "Msg1");
|
||||
EXPECT_EQ(server.recv_buffer_size(), 1);
|
||||
|
||||
len = server.recvfrom(buf, sizeof(buf), &sender);
|
||||
ASSERT_EQ(len, 4);
|
||||
EXPECT_EQ(std::string(reinterpret_cast<char *>(buf), len), "Msg2");
|
||||
EXPECT_EQ(server.recv_buffer_size(), 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
|
||||
|
||||
@@ -7,6 +7,30 @@
|
||||
|
||||
namespace tox::test {
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, TcpFlags flags)
|
||||
{
|
||||
bool first = true;
|
||||
if (flags.value & 0x02) {
|
||||
os << (first ? "" : "|") << "SYN";
|
||||
first = false;
|
||||
}
|
||||
if (flags.value & 0x10) {
|
||||
os << (first ? "" : "|") << "ACK";
|
||||
first = false;
|
||||
}
|
||||
if (flags.value & 0x01) {
|
||||
os << (first ? "" : "|") << "FIN";
|
||||
first = false;
|
||||
}
|
||||
if (flags.value & 0x04) {
|
||||
os << (first ? "" : "|") << "RST";
|
||||
first = false;
|
||||
}
|
||||
if (first)
|
||||
os << "NONE";
|
||||
return os << "(" << static_cast<int>(flags.value) << ")";
|
||||
}
|
||||
|
||||
bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const
|
||||
{
|
||||
if (port != other.port)
|
||||
@@ -24,7 +48,7 @@ bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const
|
||||
NetworkUniverse::NetworkUniverse() { }
|
||||
NetworkUniverse::~NetworkUniverse() { }
|
||||
|
||||
bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket)
|
||||
bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *_Nonnull socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
IP_Port_Key key{ip, port};
|
||||
@@ -40,14 +64,14 @@ void NetworkUniverse::unbind_udp(IP ip, uint16_t port)
|
||||
udp_bindings_.erase({ip, port});
|
||||
}
|
||||
|
||||
bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *socket)
|
||||
bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull 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)
|
||||
void NetworkUniverse::unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
auto range = tcp_bindings_.equal_range({ip, port});
|
||||
@@ -75,9 +99,64 @@ void NetworkUniverse::send_packet(Packet p)
|
||||
p.delivery_time += global_latency_ms_;
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
p.sequence_number = next_packet_id_++;
|
||||
|
||||
if (verbose_) {
|
||||
Ip_Ntoa from_str, to_str;
|
||||
net_ip_ntoa(&p.from.ip, &from_str);
|
||||
net_ip_ntoa(&p.to.ip, &to_str);
|
||||
std::cerr << "[NetworkUniverse] Enqueued packet #" << p.sequence_number << " from "
|
||||
<< from_str.buf << ":" << net_ntohs(p.from.port) << " to " << to_str.buf << ":"
|
||||
<< net_ntohs(p.to.port);
|
||||
if (p.is_tcp) {
|
||||
std::cerr << " (TCP Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq
|
||||
<< " Ack=" << p.ack << ")";
|
||||
}
|
||||
std::cerr << " with size " << p.data.size() << std::endl;
|
||||
}
|
||||
|
||||
event_queue_.push(std::move(p));
|
||||
}
|
||||
|
||||
static bool is_ipv4_mapped(const IP &ip)
|
||||
{
|
||||
if (!net_family_is_ipv6(ip.family))
|
||||
return false;
|
||||
const uint8_t *b = ip.ip.v6.uint8;
|
||||
for (int i = 0; i < 10; ++i)
|
||||
if (b[i] != 0)
|
||||
return false;
|
||||
if (b[10] != 0xFF || b[11] != 0xFF)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static IP extract_ipv4(const IP &ip)
|
||||
{
|
||||
IP ip4;
|
||||
ip_init(&ip4, false);
|
||||
const uint8_t *b = ip.ip.v6.uint8;
|
||||
std::memcpy(ip4.ip.v4.uint8, b + 12, 4);
|
||||
return ip4;
|
||||
}
|
||||
|
||||
bool is_loopback(const IP &ip)
|
||||
{
|
||||
if (net_family_is_ipv4(ip.family)) {
|
||||
return ip.ip.v4.uint32 == net_htonl(0x7F000001);
|
||||
}
|
||||
if (net_family_is_ipv6(ip.family)) {
|
||||
const uint8_t *b = ip.ip.v6.uint8;
|
||||
for (int i = 0; i < 15; ++i) {
|
||||
if (b[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return b[15] == 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NetworkUniverse::process_events(uint64_t current_time_ms)
|
||||
{
|
||||
while (true) {
|
||||
@@ -88,19 +167,101 @@ void NetworkUniverse::process_events(uint64_t current_time_ms)
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
if (!event_queue_.empty()) {
|
||||
const Packet &top = event_queue_.top();
|
||||
if (verbose_) {
|
||||
std::cerr << "[NetworkUniverse] Peek packet: time=" << top.delivery_time
|
||||
<< " current=" << current_time_ms << " tcp=" << top.is_tcp
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
if (!event_queue_.empty() && event_queue_.top().delivery_time <= current_time_ms) {
|
||||
p = event_queue_.top();
|
||||
event_queue_.pop();
|
||||
has_packet = true;
|
||||
|
||||
if (verbose_) {
|
||||
Ip_Ntoa from_str, to_str;
|
||||
net_ip_ntoa(&p.from.ip, &from_str);
|
||||
net_ip_ntoa(&p.to.ip, &to_str);
|
||||
std::cerr << "[NetworkUniverse] Processing packet #" << p.sequence_number
|
||||
<< " from " << from_str.buf << ":" << net_ntohs(p.from.port) << " to "
|
||||
<< to_str.buf << ":" << net_ntohs(p.to.port)
|
||||
<< " (TCP=" << (p.is_tcp ? "true" : "false");
|
||||
if (p.is_tcp) {
|
||||
std::cerr << " Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq
|
||||
<< " Ack=" << p.ack;
|
||||
}
|
||||
std::cerr << " Size=" << p.data.size() << ")" << std::endl;
|
||||
}
|
||||
|
||||
IP target_ip = p.to.ip;
|
||||
|
||||
if (p.is_tcp) {
|
||||
auto range = tcp_bindings_.equal_range({p.to.ip, net_ntohs(p.to.port)});
|
||||
if (is_loopback(target_ip)
|
||||
&& tcp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) {
|
||||
if (verbose_) {
|
||||
std::cerr << "[NetworkUniverse] Loopback packet to "
|
||||
<< static_cast<int>(target_ip.ip.v4.uint8[3])
|
||||
<< " redirected to "
|
||||
<< static_cast<int>(p.from.ip.ip.v4.uint8[3]) << std::endl;
|
||||
}
|
||||
target_ip = p.from.ip;
|
||||
}
|
||||
|
||||
auto range = tcp_bindings_.equal_range({target_ip, net_ntohs(p.to.port)});
|
||||
FakeTcpSocket *listen_match = nullptr;
|
||||
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
tcp_targets.push_back(it->second);
|
||||
FakeTcpSocket *s = it->second;
|
||||
if (s->state() == FakeTcpSocket::LISTEN) {
|
||||
listen_match = s;
|
||||
} else {
|
||||
const IP_Port &remote = s->remote_addr();
|
||||
if (net_ntohs(p.from.port) == net_ntohs(remote.port)) {
|
||||
if (ip_equal(&p.from.ip, &remote.ip)
|
||||
|| (is_loopback(p.from.ip) && ip_equal(&remote.ip, &target_ip))
|
||||
|| (is_loopback(remote.ip)
|
||||
&& ip_equal(&p.from.ip, &target_ip))) {
|
||||
tcp_targets.push_back(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (listen_match && (p.tcp_flags & 0x02)) {
|
||||
tcp_targets.push_back(listen_match);
|
||||
}
|
||||
|
||||
if (verbose_) {
|
||||
std::cerr << "[NetworkUniverse] Routing TCP to "
|
||||
<< static_cast<int>(target_ip.ip.v4.uint8[0]) << "."
|
||||
<< static_cast<int>(target_ip.ip.v4.uint8[3]) << ":"
|
||||
<< net_ntohs(p.to.port)
|
||||
<< ". Targets found: " << tcp_targets.size() << std::endl;
|
||||
}
|
||||
if (tcp_targets.empty()) {
|
||||
if (verbose_) {
|
||||
std::cerr << "[NetworkUniverse] WARNING: No TCP targets for "
|
||||
<< static_cast<int>(target_ip.ip.v4.uint8[0]) << "."
|
||||
<< static_cast<int>(target_ip.ip.v4.uint8[3]) << ":"
|
||||
<< net_ntohs(p.to.port) << std::endl;
|
||||
}
|
||||
}
|
||||
} 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 (is_loopback(target_ip)
|
||||
&& udp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) {
|
||||
target_ip = p.from.ip;
|
||||
}
|
||||
|
||||
if (udp_bindings_.count({target_ip, net_ntohs(p.to.port)})) {
|
||||
udp_target = udp_bindings_[{target_ip, net_ntohs(p.to.port)}];
|
||||
} else if (is_ipv4_mapped(target_ip)) {
|
||||
IP ip4 = extract_ipv4(target_ip);
|
||||
if (udp_bindings_.count({ip4, net_ntohs(p.to.port)})) {
|
||||
udp_target = udp_bindings_[{ip4, net_ntohs(p.to.port)}];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +273,9 @@ void NetworkUniverse::process_events(uint64_t current_time_ms)
|
||||
|
||||
if (p.is_tcp) {
|
||||
for (auto *it : tcp_targets) {
|
||||
it->handle_packet(p);
|
||||
if (it->handle_packet(p)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (udp_target) {
|
||||
@@ -136,7 +299,7 @@ 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}))
|
||||
if (!udp_bindings_.count({ip, port}) && !tcp_bindings_.count({ip, port}))
|
||||
return port;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
#include "../../../toxcore/attributes.h"
|
||||
#include "../../../toxcore/network.h"
|
||||
|
||||
namespace tox::test {
|
||||
@@ -16,11 +17,17 @@ namespace tox::test {
|
||||
class FakeUdpSocket;
|
||||
class FakeTcpSocket;
|
||||
|
||||
struct TcpFlags {
|
||||
uint8_t value;
|
||||
};
|
||||
std::ostream &operator<<(std::ostream &os, TcpFlags flags);
|
||||
|
||||
struct Packet {
|
||||
IP_Port from;
|
||||
IP_Port to;
|
||||
std::vector<uint8_t> data;
|
||||
uint64_t delivery_time;
|
||||
uint64_t sequence_number = 0;
|
||||
bool is_tcp = false;
|
||||
|
||||
// TCP Simulation Fields
|
||||
@@ -28,9 +35,17 @@ struct Packet {
|
||||
uint32_t seq = 0;
|
||||
uint32_t ack = 0;
|
||||
|
||||
bool operator>(const Packet &other) const { return delivery_time > other.delivery_time; }
|
||||
bool operator>(const Packet &other) const
|
||||
{
|
||||
if (delivery_time != other.delivery_time) {
|
||||
return delivery_time > other.delivery_time;
|
||||
}
|
||||
return sequence_number > other.sequence_number;
|
||||
}
|
||||
};
|
||||
|
||||
bool is_loopback(const IP &ip);
|
||||
|
||||
/**
|
||||
* @brief The God Object for the network simulation.
|
||||
* Manages routing, latency, and connectivity.
|
||||
@@ -45,11 +60,11 @@ public:
|
||||
|
||||
// Registration
|
||||
// Returns true if binding succeeded
|
||||
bool bind_udp(IP ip, uint16_t port, FakeUdpSocket *socket);
|
||||
bool bind_udp(IP ip, uint16_t port, FakeUdpSocket *_Nonnull 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);
|
||||
bool bind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket);
|
||||
void unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket);
|
||||
|
||||
// Routing
|
||||
void send_packet(Packet p);
|
||||
@@ -81,6 +96,7 @@ private:
|
||||
std::vector<PacketSink> observers_;
|
||||
|
||||
uint64_t global_latency_ms_ = 0;
|
||||
uint64_t next_packet_id_ = 0;
|
||||
bool verbose_ = false;
|
||||
std::recursive_mutex mutex_;
|
||||
};
|
||||
|
||||
@@ -236,5 +236,86 @@ namespace {
|
||||
std::string(reinterpret_cast<char *>(buf), static_cast<size_t>(len)), "Padding test");
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, TcpRoutingSpecificity)
|
||||
{
|
||||
IP ip1{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x0A000001); // 10.0.0.1
|
||||
|
||||
uint16_t port = 12345;
|
||||
IP_Port local_addr{ip1, net_htons(port)};
|
||||
|
||||
FakeTcpSocket listen_sock(universe);
|
||||
listen_sock.set_ip(ip1);
|
||||
listen_sock.bind(&local_addr);
|
||||
listen_sock.listen(5);
|
||||
|
||||
IP remote_ip{};
|
||||
ip_init(&remote_ip, false);
|
||||
remote_ip.ip.v4.uint32 = net_htonl(0x0A000002); // 10.0.0.2
|
||||
IP_Port remote_addr{remote_ip, net_htons(33445)};
|
||||
|
||||
auto established_sock = FakeTcpSocket::create_connected(universe, remote_addr, port);
|
||||
established_sock->set_ip(ip1);
|
||||
universe.bind_tcp(ip1, port, established_sock.get());
|
||||
|
||||
// Send a data packet from remote to local
|
||||
Packet p{};
|
||||
p.from = remote_addr;
|
||||
p.to = local_addr;
|
||||
p.is_tcp = true;
|
||||
p.tcp_flags = 0x10; // ACK (Data)
|
||||
const char *data = "Specific";
|
||||
p.data.assign(data, data + strlen(data));
|
||||
|
||||
universe.send_packet(p);
|
||||
universe.process_events(0);
|
||||
|
||||
// established_sock should have received it
|
||||
EXPECT_EQ(established_sock->recv_buffer_size(), strlen(data));
|
||||
|
||||
// listen_sock should NOT have received it (it doesn't have a buffer, but it shouldn't have
|
||||
// been called) We can't easily check listen_sock wasn't called without mocks or checking
|
||||
// logs, but we can check that it didn't create a new pending connection.
|
||||
EXPECT_FALSE(listen_sock.is_readable());
|
||||
}
|
||||
|
||||
TEST_F(NetworkUniverseTest, PacketOrdering)
|
||||
{
|
||||
IP ip1{}, ip2{};
|
||||
ip_init(&ip1, false);
|
||||
ip1.ip.v4.uint32 = net_htonl(0x0A000001);
|
||||
ip_init(&ip2, false);
|
||||
ip2.ip.v4.uint32 = net_htonl(0x0A000002);
|
||||
|
||||
uint16_t port = 33445;
|
||||
IP_Port addr1{ip1, net_htons(port)};
|
||||
IP_Port addr2{ip2, net_htons(port)};
|
||||
|
||||
FakeUdpSocket sock1{universe};
|
||||
sock1.set_ip(ip1);
|
||||
sock1.bind(&addr1);
|
||||
|
||||
FakeUdpSocket sock2{universe};
|
||||
sock2.set_ip(ip2);
|
||||
sock2.bind(&addr2);
|
||||
|
||||
// Send 10 packets with the same delivery time (global latency = 0)
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
uint8_t data = static_cast<uint8_t>(i);
|
||||
sock1.sendto(&data, 1, &addr2);
|
||||
}
|
||||
|
||||
universe.process_events(0);
|
||||
|
||||
// They should be received in the exact order they were sent
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
uint8_t buf[1];
|
||||
IP_Port from;
|
||||
ASSERT_EQ(sock2.recvfrom(buf, 1, &from), 1);
|
||||
EXPECT_EQ(buf[0], i) << "Packet " << i << " was delivered out of order";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tox::test
|
||||
|
||||
Reference in New Issue
Block a user