Skip to content

Commit d236f14

Browse files
fix: address CI failures and review comments
- Remove unused variable is_new_peer (Werror on all GCC/Clang) - Remove 'struct' keyword from iovec/msghdr declarations for Windows MSVC compatibility (WSABUF is a typedef, not a struct in namespaces) - Fix clang-tidy issues in Discovery.cpp: add missing includes (<set>, <map>, <mutex>), make 'name' const, suppress bugprone-not-null-terminated-result for wire format memcpy, collapse duplicate IDLE branches (bugprone-branch-clone) - Fix clang-tidy issues in Fragmentation.cpp: add missing includes, make local variables const, use data() instead of begin() iterators to avoid narrowing conversions - Add NOLINT for wire_protocol.hpp macro and C-style array (necessary for packed struct wire format) - Fix Windows read_socket blocking: set socket non-blocking before drain loop since MSG_DONTWAIT has no effect on Windows - Fix IPv6 reassembly key: XOR-fold full sockaddr_storage instead of only first 8 bytes to prevent collisions between IPv6 peers - Clear deduplicators on reset() to prevent stale state - Add util sources to standalone nuclearnet CMake target
1 parent 289667d commit d236f14

6 files changed

Lines changed: 73 additions & 32 deletions

File tree

src/nuclearnet/CMakeLists.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@ find_package(Threads REQUIRED)
3838
file(GLOB_RECURSE NUCLEARNET_SOURCES CONFIGURE_DEPENDS "*.cpp")
3939
file(GLOB_RECURSE NUCLEARNET_HEADERS CONFIGURE_DEPENDS "*.hpp")
4040

41-
add_library(nuclearnet STATIC ${NUCLEARNET_SOURCES})
41+
# Include required utility sources that nuclearnet depends on
42+
set(NUCLEARNET_UTIL_SOURCES
43+
${CMAKE_CURRENT_SOURCE_DIR}/../util/network/resolve.cpp
44+
)
45+
# Include platform shim on Windows
46+
if(WIN32)
47+
list(APPEND NUCLEARNET_UTIL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../util/platform.cpp)
48+
endif()
49+
50+
add_library(nuclearnet STATIC ${NUCLEARNET_SOURCES} ${NUCLEARNET_UTIL_SOURCES})
4251
add_library(NUClear::nuclearnet ALIAS nuclearnet)
4352

4453
target_include_directories(nuclearnet

src/nuclearnet/Discovery.cpp

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
#include <chrono>
2727
#include <cstdint>
2828
#include <cstring>
29+
#include <map>
30+
#include <mutex>
31+
#include <set>
2932
#include <string>
3033
#include <utility>
3134
#include <vector>
@@ -68,7 +71,7 @@
6871
auto name_len = static_cast<uint16_t>(name.size());
6972
std::memcpy(ptr, &name_len, sizeof(uint16_t));
7073
ptr += sizeof(uint16_t);
71-
std::memcpy(ptr, name.data(), name.size());
74+
std::memcpy(ptr, name.data(), name.size()); // NOLINT(bugprone-not-null-terminated-result)
7275
ptr += name.size();
7376

7477
// Write subscription count and hashes
@@ -124,7 +127,7 @@
124127
}
125128

126129
// Read name
127-
std::string name(reinterpret_cast<const char*>(ptr), name_len);
130+
const std::string name(reinterpret_cast<const char*>(ptr), name_len);
128131
ptr += name_len;
129132
remaining -= name_len;
130133

@@ -286,13 +289,8 @@
286289

287290
switch (peer.handshake) {
288291
case HandshakeState::IDLE:
289-
if (has_syn && !has_ack) {
290-
// Received SYN — respond with SYN+ACK
291-
peer.handshake = HandshakeState::SYN_RECEIVED;
292-
result.response_flags = SYN | CON_ACK;
293-
}
294-
else if (has_syn && has_ack) {
295-
// SYN+ACK but we haven't sent SYN — treat as SYN, respond SYN+ACK
292+
if (has_syn) {
293+
// Received SYN (or SYN+ACK when we haven't sent SYN) — respond with SYN+ACK
296294
peer.handshake = HandshakeState::SYN_RECEIVED;
297295
result.response_flags = SYN | CON_ACK;
298296
}

src/nuclearnet/Fragmentation.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@
2323
#include "Fragmentation.hpp"
2424

2525
#include <algorithm>
26+
#include <chrono>
27+
#include <cstdint>
2628
#include <cstring>
29+
#include <mutex>
30+
#include <utility>
31+
#include <vector>
2732

2833
namespace NUClear {
2934
namespace network {
@@ -38,7 +43,7 @@ namespace network {
3843
uint8_t flags,
3944
const std::vector<uint8_t>& payload) const {
4045
// Calculate how many fragments we need
41-
uint16_t packet_count =
46+
const uint16_t packet_count =
4247
payload.empty() ? 1 : static_cast<uint16_t>((payload.size() + packet_mtu - 1) / packet_mtu);
4348

4449
std::vector<Fragment> fragments;
@@ -53,9 +58,9 @@ namespace network {
5358
frag.hash = hash;
5459

5560
// Calculate the data slice for this fragment
56-
std::size_t offset = static_cast<std::size_t>(i) * packet_mtu;
57-
std::size_t length = std::min(static_cast<std::size_t>(packet_mtu), payload.size() - offset);
58-
frag.data.assign(payload.begin() + offset, payload.begin() + offset + length);
61+
const std::size_t offset = static_cast<std::size_t>(i) * packet_mtu;
62+
const std::size_t length = std::min(static_cast<std::size_t>(packet_mtu), payload.size() - offset);
63+
frag.data.assign(payload.data() + offset, payload.data() + offset + length);
5964

6065
fragments.push_back(std::move(frag));
6166
}
@@ -79,13 +84,13 @@ namespace network {
7984

8085
// Enforce max assembly size check
8186
if (max_assembly_size > 0) {
82-
std::size_t projected_size = static_cast<std::size_t>(packet_count) * packet_mtu;
87+
const std::size_t projected_size = static_cast<std::size_t>(packet_count) * packet_mtu;
8388
if (projected_size > max_assembly_size) {
8489
return false;
8590
}
8691
}
8792

88-
AssemblyKey key{source_key, packet_id};
93+
const AssemblyKey key{source_key, packet_id};
8994

9095
const std::lock_guard<std::mutex> lock(assembly_mutex);
9196

src/nuclearnet/NUClearNet.cpp

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ namespace network {
7171
// Shut down existing connections
7272
shutdown();
7373

74+
// Clear per-peer state that should not survive a reset
75+
deduplicators.clear();
76+
7477
config = cfg;
7578
node_name = cfg.name;
7679

@@ -142,6 +145,16 @@ namespace network {
142145
throw std::system_error(network_errno, std::system_category(), "Failed to bind data socket");
143146
}
144147

148+
// Set non-blocking so sends don't block when the kernel buffer is full
149+
// (unreliable sends should be fire-and-forget, not back-pressure the caller)
150+
#ifdef _WIN32
151+
u_long non_blocking = 1;
152+
ioctl(fd, FIONBIO, &non_blocking);
153+
#else
154+
int flags = ::fcntl(fd, F_GETFL, 0);
155+
::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
156+
#endif
157+
145158
data_fd.reset(fd);
146159
}
147160

@@ -191,6 +204,16 @@ namespace network {
191204
}
192205
}
193206

207+
// Set non-blocking so read_socket drain loop works on Windows
208+
// (where MSG_DONTWAIT has no effect)
209+
#ifdef _WIN32
210+
u_long non_blocking = 1;
211+
ioctl(fd, FIONBIO, &non_blocking);
212+
#else
213+
int flags = ::fcntl(fd, F_GETFL, 0);
214+
::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
215+
#endif
216+
194217
announce_fd.reset(fd);
195218
}
196219

@@ -231,7 +254,7 @@ namespace network {
231254
header.flags = req.flags;
232255
header.hash = req.hash;
233256

234-
struct iovec iov[2];
257+
iovec iov[2];
235258
iov[0].iov_base = reinterpret_cast<void*>(&header);
236259
iov[0].iov_len = sizeof(DataPacket) - 1;
237260
iov[1].iov_base = const_cast<void*>(static_cast<const void*>(req.data.data()));
@@ -285,7 +308,7 @@ namespace network {
285308
std::size_t offset = static_cast<std::size_t>(i) * packet_mtu;
286309
std::size_t frag_len = std::min(static_cast<std::size_t>(packet_mtu), length - offset);
287310

288-
struct iovec iov[2];
311+
iovec iov[2];
289312
iov[0].iov_base = reinterpret_cast<void*>(&header);
290313
iov[0].iov_len = sizeof(DataPacket) - 1;
291314
iov[1].iov_base = const_cast<void*>(static_cast<const void*>(payload + offset));
@@ -337,7 +360,7 @@ namespace network {
337360
std::size_t offset = static_cast<std::size_t>(i) * packet_mtu;
338361
std::size_t frag_len = std::min(static_cast<std::size_t>(packet_mtu), length - offset);
339362

340-
struct iovec iov[2];
363+
iovec iov[2];
341364
iov[0].iov_base = reinterpret_cast<void*>(&header);
342365
iov[0].iov_len = sizeof(DataPacket) - 1;
343366
iov[1].iov_base = const_cast<void*>(static_cast<const void*>(payload + offset));
@@ -402,9 +425,12 @@ namespace network {
402425

403426
void NUClearNet::read_socket(fd_t fd) {
404427
// Stack buffer — 65535 is the maximum UDP datagram size
405-
alignas(8) uint8_t buffer[65535];
428+
alignas(8) uint8_t buffer[65535]; // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
406429
sock_t source{};
407430

431+
// Drain all pending datagrams from the socket
432+
// The data socket is non-blocking, so recvfrom returns -1/EWOULDBLOCK when empty
433+
// For the announce socket, MSG_DONTWAIT provides the same behavior on POSIX
408434
for (;;) {
409435
socklen_t source_len = sizeof(source.storage);
410436
ssize_t received = ::recvfrom(fd,
@@ -434,8 +460,6 @@ namespace network {
434460

435461
switch (header->type) {
436462
case ANNOUNCE: {
437-
// Check if this is a new peer before processing (which adds them)
438-
const bool is_new_peer = !discovery->has_peer(source);
439463
auto announce_result = discovery->process_announce(source, data, length);
440464

441465
if (announce_result.is_new) {
@@ -509,10 +533,13 @@ namespace network {
509533
const uint8_t* frag_data = data + sizeof(DataPacket) - 1;
510534
std::size_t frag_length = length - (sizeof(DataPacket) - 1);
511535

512-
// Use a hash of the source address bytes as the source key for fragmentation
513-
// We use the first 8 bytes of the storage as a simple key
536+
// Use a hash of the full source address as the source key for fragmentation
537+
// This ensures IPv6 addresses are properly distinguished
514538
uint64_t source_key = 0;
515-
std::memcpy(&source_key, &source.storage, std::min(sizeof(source_key), sizeof(source.storage)));
539+
const auto* bytes = reinterpret_cast<const uint8_t*>(&source.storage);
540+
for (std::size_t i = 0; i < sizeof(source.storage); ++i) {
541+
source_key ^= static_cast<uint64_t>(bytes[i]) << ((i % 8) * 8);
542+
}
516543

517544
// Submit to fragmentation
518545
Fragmentation::AssembledPacket assembled;
@@ -580,11 +607,11 @@ namespace network {
580607
}
581608
}
582609

583-
void NUClearNet::send_iov(fd_t fd, const sock_t& target, const struct iovec* iov, int iovcnt) {
584-
struct msghdr msg{};
585-
msg.msg_name = const_cast<struct sockaddr*>(&target.sock);
610+
void NUClearNet::send_iov(fd_t fd, const sock_t& target, const iovec* iov, int iovcnt) {
611+
msghdr msg{};
612+
msg.msg_name = const_cast<sockaddr*>(&target.sock);
586613
msg.msg_namelen = target.size();
587-
msg.msg_iov = const_cast<struct iovec*>(iov);
614+
msg.msg_iov = const_cast<iovec*>(iov);
588615
msg.msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(iovcnt);
589616
msg.msg_control = nullptr;
590617
msg.msg_controllen = 0;

src/nuclearnet/NUClearNet.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,11 @@ namespace network {
177177
void process_packet(const sock_t& source, const uint8_t* data, std::size_t length);
178178

179179
/// Send raw bytes to a target using scatter IO (multiple buffers without copying)
180-
void send_iov(fd_t fd, const sock_t& target, const struct iovec* iov, int iovcnt);
180+
void send_iov(fd_t fd, const sock_t& target, const iovec* iov, int iovcnt);
181181

182182
/// Send a single contiguous buffer to a target
183183
void send_buf(fd_t fd, const sock_t& target, const uint8_t* data, std::size_t length) {
184-
struct iovec iov{};
184+
iovec iov{};
185185
iov.iov_base = const_cast<void*>(static_cast<const void*>(data)); // NOLINT(cppcoreguidelines-pro-type-const-cast)
186186
iov.iov_len = length;
187187
send_iov(fd, target, &iov, 1);

src/nuclearnet/wire_protocol.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828

2929
// Packing macros for wire format structs
3030
#ifdef _MSC_VER
31+
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
3132
#define NUCLEAR_NET_PACK(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop))
3233
#else
34+
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
3335
#define NUCLEAR_NET_PACK(...) __VA_ARGS__ __attribute__((__packed__))
3436
#endif
3537

@@ -71,7 +73,7 @@ namespace network {
7173
explicit PacketHeader(PacketType t) : type(t) {}
7274

7375
/// Magic bytes: radioactive symbol in UTF-8
74-
uint8_t header[3] = {0xE2, 0x98, 0xA2};
76+
uint8_t header[3] = {0xE2, 0x98, 0xA2}; // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays)
7577
/// Protocol version
7678
uint8_t version = PROTOCOL_VERSION;
7779
/// Packet type

0 commit comments

Comments
 (0)