Coverage Report

Created: 2026-10-24 08:16

/work/testing/support/doubles/network_universe.cc
Line
Count
Source (jump to first uncovered line)
1
#include "network_universe.hh"
2
3
#include <cstring>
4
#include <iostream>
5
6
#include "fake_sockets.hh"
7
8
namespace tox::test {
9
10
std::ostream &operator<<(std::ostream &os, TcpFlags flags)
11
134
{
12
134
    bool first = true;
13
134
    if (flags.value & 0x02) {
14
86
        os << (first ? "" : "|") << "SYN";
15
86
        first = false;
16
86
    }
17
134
    if (flags.value & 0x10) {
18
85
        os << (first ? "" : "|") << "ACK";
19
85
        first = false;
20
85
    }
21
134
    if (flags.value & 0x01) {
22
0
        os << (first ? "" : "|") << "FIN";
23
0
        first = false;
24
0
    }
25
134
    if (flags.value & 0x04) {
26
3
        os << (first ? "" : "|") << "RST";
27
3
        first = false;
28
3
    }
29
134
    if (first)
30
0
        os << "NONE";
31
134
    return os << "(" << static_cast<int>(flags.value) << ")";
32
134
}
33
34
bool NetworkUniverse::IP_Port_Key::operator<(const IP_Port_Key &other) const
35
684k
{
36
684k
    if (port != other.port)
37
191k
        return port < other.port;
38
493k
    if (ip.family.value != other.ip.family.value)
39
16
        return ip.family.value < other.ip.family.value;
40
41
493k
    if (net_family_is_ipv4(ip.family)) {
42
493k
        return ip.ip.v4.uint32 < other.ip.ip.v4.uint32;
43
493k
    }
44
45
0
    return std::memcmp(&ip.ip.v6, &other.ip.ip.v6, sizeof(ip.ip.v6)) < 0;
46
493k
}
47
48
2.84k
NetworkUniverse::NetworkUniverse() { }
49
2.84k
NetworkUniverse::~NetworkUniverse() { }
50
51
bool NetworkUniverse::bind_udp(IP ip, uint16_t port, FakeUdpSocket *_Nonnull socket)
52
10.2k
{
53
10.2k
    std::lock_guard<std::recursive_mutex> lock(mutex_);
54
10.2k
    IP_Port_Key key{ip, port};
55
10.2k
    if (udp_bindings_.count(key))
56
1.46k
        return false;
57
8.82k
    udp_bindings_[key] = socket;
58
8.82k
    return true;
59
10.2k
}
60
61
void NetworkUniverse::unbind_udp(IP ip, uint16_t port)
62
8.82k
{
63
8.82k
    std::lock_guard<std::recursive_mutex> lock(mutex_);
64
8.82k
    udp_bindings_.erase({ip, port});
65
8.82k
}
66
67
bool NetworkUniverse::bind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket)
68
72
{
69
72
    std::lock_guard<std::recursive_mutex> lock(mutex_);
70
72
    tcp_bindings_.insert({{ip, port}, socket});
71
72
    return true;
72
72
}
73
74
void NetworkUniverse::unbind_tcp(IP ip, uint16_t port, FakeTcpSocket *_Nonnull socket)
75
72
{
76
72
    std::lock_guard<std::recursive_mutex> lock(mutex_);
77
72
    auto range = tcp_bindings_.equal_range({ip, port});
78
92
    for (auto it = range.first; it != range.second; ++it) {
79
92
        if (it->second == socket) {
80
72
            tcp_bindings_.erase(it);
81
72
            break;
82
72
        }
83
92
    }
84
72
}
85
86
void NetworkUniverse::send_packet(Packet p)
87
41.1k
{
88
41.1k
    std::lock_guard<std::recursive_mutex> lock(mutex_);
89
90
    // Apply filters
91
41.1k
    for (const auto &filter : filters_) {
92
24
        if (!filter(p))
93
9
            return;
94
24
    }
95
96
    // Notify observers
97
41.1k
    for (const auto &observer : observers_) {
98
11.1k
        observer(p);
99
11.1k
    }
100
101
41.1k
    p.delivery_time += global_latency_ms_;
102
103
41.1k
    p.sequence_number = next_packet_id_++;
104
105
41.1k
    if (verbose_) {
106
45
        Ip_Ntoa from_str, to_str;
107
45
        net_ip_ntoa(&p.from.ip, &from_str);
108
45
        net_ip_ntoa(&p.to.ip, &to_str);
109
45
        std::cerr << "[NetworkUniverse] Enqueued packet #" << p.sequence_number << " from "
110
45
                  << from_str.buf << ":" << net_ntohs(p.from.port) << " to " << to_str.buf << ":"
111
45
                  << net_ntohs(p.to.port);
112
45
        if (p.is_tcp) {
113
45
            std::cerr << " (TCP Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq
114
45
                      << " Ack=" << p.ack << ")";
115
45
        }
116
45
        std::cerr << " with size " << p.data.size() << std::endl;
117
45
    }
118
41.1k
    event_queue_.push(std::move(p));
119
41.1k
}
120
121
static bool is_ipv4_mapped(const IP &ip)
122
69
{
123
69
    if (!net_family_is_ipv6(ip.family))
124
0
        return false;
125
69
    const uint8_t *b = ip.ip.v6.uint8;
126
429
    for (int i = 0; i < 10; ++i)
127
393
        if (b[i] != 0)
128
33
            return false;
129
36
    if (b[10] != 0xFF || b[11] != 0xFF)
130
0
        return false;
131
36
    return true;
132
36
}
133
134
static IP extract_ipv4(const IP &ip)
135
36
{
136
36
    IP ip4;
137
36
    ip_init(&ip4, false);
138
36
    const uint8_t *b = ip.ip.v6.uint8;
139
36
    std::memcpy(ip4.ip.v4.uint8, b + 12, 4);
140
36
    return ip4;
141
36
}
142
143
bool is_loopback(const IP &ip)
144
40.3k
{
145
40.3k
    if (net_family_is_ipv4(ip.family)) {
146
40.2k
        return ip.ip.v4.uint32 == net_htonl(0x7F000001);
147
40.2k
    }
148
69
    if (net_family_is_ipv6(ip.family)) {
149
69
        const uint8_t *b = ip.ip.v6.uint8;
150
429
        for (int i = 0; i < 15; ++i) {
151
429
            if (b[i] != 0) {
152
69
                return false;
153
69
            }
154
429
        }
155
0
        return b[15] == 1;
156
69
    }
157
0
    return false;
158
69
}
159
160
void NetworkUniverse::process_events(uint64_t current_time_ms)
161
3.19k
{
162
43.5k
    while (true) {
163
43.5k
        Packet p;
164
43.5k
        std::vector<FakeTcpSocket *> tcp_targets;
165
43.5k
        FakeUdpSocket *udp_target = nullptr;
166
43.5k
        bool has_packet = false;
167
168
43.5k
        {
169
43.5k
            std::lock_guard<std::recursive_mutex> lock(mutex_);
170
43.5k
            if (!event_queue_.empty()) {
171
40.3k
                const Packet &top = event_queue_.top();
172
40.3k
                if (verbose_) {
173
45
                    std::cerr << "[NetworkUniverse] Peek packet: time=" << top.delivery_time
174
45
                              << " current=" << current_time_ms << " tcp=" << top.is_tcp
175
45
                              << std::endl;
176
45
                }
177
40.3k
            }
178
179
43.5k
            if (!event_queue_.empty() && event_queue_.top().delivery_time <= current_time_ms) {
180
40.3k
                p = event_queue_.top();
181
40.3k
                event_queue_.pop();
182
40.3k
                has_packet = true;
183
184
40.3k
                if (verbose_) {
185
45
                    Ip_Ntoa from_str, to_str;
186
45
                    net_ip_ntoa(&p.from.ip, &from_str);
187
45
                    net_ip_ntoa(&p.to.ip, &to_str);
188
45
                    std::cerr << "[NetworkUniverse] Processing packet #" << p.sequence_number
189
45
                              << " from " << from_str.buf << ":" << net_ntohs(p.from.port) << " to "
190
45
                              << to_str.buf << ":" << net_ntohs(p.to.port)
191
45
                              << " (TCP=" << (p.is_tcp ? "true" : "false");
192
45
                    if (p.is_tcp) {
193
45
                        std::cerr << " Flags=" << TcpFlags{p.tcp_flags} << " Seq=" << p.seq
194
45
                                  << " Ack=" << p.ack;
195
45
                    }
196
45
                    std::cerr << " Size=" << p.data.size() << ")" << std::endl;
197
45
                }
198
199
40.3k
                IP target_ip = p.to.ip;
200
201
40.3k
                if (p.is_tcp) {
202
83
                    if (is_loopback(target_ip)
203
83
                        && tcp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) {
204
5
                        if (verbose_) {
205
5
                            std::cerr << "[NetworkUniverse] Loopback packet to "
206
5
                                      << static_cast<int>(target_ip.ip.v4.uint8[3])
207
5
                                      << " redirected to "
208
5
                                      << static_cast<int>(p.from.ip.ip.v4.uint8[3]) << std::endl;
209
5
                        }
210
5
                        target_ip = p.from.ip;
211
5
                    }
212
213
83
                    auto range = tcp_bindings_.equal_range({target_ip, net_ntohs(p.to.port)});
214
83
                    FakeTcpSocket *listen_match = nullptr;
215
216
209
                    for (auto it = range.first; it != range.second; ++it) {
217
126
                        FakeTcpSocket *s = it->second;
218
126
                        if (s->state() == FakeTcpSocket::LISTEN) {
219
52
                            listen_match = s;
220
74
                        } else {
221
74
                            const IP_Port &remote = s->remote_addr();
222
74
                            if (net_ntohs(p.from.port) == net_ntohs(remote.port)) {
223
64
                                if (ip_equal(&p.from.ip, &remote.ip)
224
64
                                    || (is_loopback(p.from.ip) && ip_equal(&remote.ip, &target_ip))
225
64
                                    || (is_loopback(remote.ip)
226
64
                                        && ip_equal(&p.from.ip, &target_ip))) {
227
64
                                    tcp_targets.push_back(s);
228
64
                                }
229
64
                            }
230
74
                        }
231
126
                    }
232
233
83
                    if (listen_match && (p.tcp_flags & 0x02)) {
234
20
                        tcp_targets.push_back(listen_match);
235
20
                    }
236
237
83
                    if (verbose_) {
238
45
                        std::cerr << "[NetworkUniverse] Routing TCP to "
239
45
                                  << static_cast<int>(target_ip.ip.v4.uint8[0]) << "."
240
45
                                  << static_cast<int>(target_ip.ip.v4.uint8[3]) << ":"
241
45
                                  << net_ntohs(p.to.port)
242
45
                                  << ". Targets found: " << tcp_targets.size() << std::endl;
243
45
                    }
244
83
                    if (tcp_targets.empty()) {
245
2
                        if (verbose_) {
246
2
                            std::cerr << "[NetworkUniverse] WARNING: No TCP targets for "
247
2
                                      << static_cast<int>(target_ip.ip.v4.uint8[0]) << "."
248
2
                                      << static_cast<int>(target_ip.ip.v4.uint8[3]) << ":"
249
2
                                      << net_ntohs(p.to.port) << std::endl;
250
2
                        }
251
2
                    }
252
40.2k
                } else {
253
40.2k
                    if (is_loopback(target_ip)
254
40.2k
                        && udp_bindings_.count({target_ip, net_ntohs(p.to.port)}) == 0) {
255
0
                        target_ip = p.from.ip;
256
0
                    }
257
258
40.2k
                    if (udp_bindings_.count({target_ip, net_ntohs(p.to.port)})) {
259
40.2k
                        udp_target = udp_bindings_[{target_ip, net_ntohs(p.to.port)}];
260
40.2k
                    } else if (is_ipv4_mapped(target_ip)) {
261
36
                        IP ip4 = extract_ipv4(target_ip);
262
36
                        if (udp_bindings_.count({ip4, net_ntohs(p.to.port)})) {
263
0
                            udp_target = udp_bindings_[{ip4, net_ntohs(p.to.port)}];
264
0
                        }
265
36
                    }
266
40.2k
                }
267
40.3k
            }
268
43.5k
        }
269
270
43.5k
        if (!has_packet) {
271
3.19k
            break;
272
3.19k
        }
273
274
40.3k
        if (p.is_tcp) {
275
83
            for (auto *it : tcp_targets) {
276
82
                if (it->handle_packet(p)) {
277
80
                    break;
278
80
                }
279
82
            }
280
40.2k
        } else {
281
40.2k
            if (udp_target) {
282
40.2k
                udp_target->push_packet(std::move(p.data), p.from);
283
40.2k
            }
284
40.2k
        }
285
40.3k
    }
286
3.19k
}
287
288
1
void NetworkUniverse::set_latency(uint64_t ms) { global_latency_ms_ = ms; }
289
290
11
void NetworkUniverse::set_verbose(bool verbose) { verbose_ = verbose; }
291
292
162k
bool NetworkUniverse::is_verbose() const { return verbose_; }
293
294
void NetworkUniverse::add_filter(PacketFilter filter)
295
2
{
296
2
    std::lock_guard<std::recursive_mutex> lock(mutex_);
297
2
    filters_.push_back(std::move(filter));
298
2
}
299
300
void NetworkUniverse::add_observer(PacketSink sink)
301
1
{
302
1
    std::lock_guard<std::recursive_mutex> lock(mutex_);
303
1
    observers_.push_back(std::move(sink));
304
1
}
305
306
uint16_t NetworkUniverse::find_free_port(IP ip, uint16_t start)
307
23
{
308
23
    std::lock_guard<std::recursive_mutex> lock(mutex_);
309
28
    for (uint16_t port = start; port < 65535; ++port) {
310
28
        if (!udp_bindings_.count({ip, port}) && !tcp_bindings_.count({ip, port}))
311
23
            return port;
312
28
    }
313
0
    return 0;
314
23
}
315
316
}  // namespace tox::test