Skip to content

Commit 0ce3960

Browse files
Sync nuclear subtree from NUClear@2441bdf
Pull PR #190 review fixes: fragment validation, Discovery/Reliability hardening, LogLevel/MSVC CI fixes, and related nuclearnet updates. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent b4a07f5 commit 0ce3960

12 files changed

Lines changed: 803 additions & 787 deletions

src/nuclear/src/nuclearnet/Discovery.cpp

Lines changed: 396 additions & 402 deletions
Large diffs are not rendered by default.

src/nuclear/src/nuclearnet/Discovery.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,10 @@ namespace network {
235235
* Get a specific peer's info.
236236
*
237237
* @param address The peer's address
238-
* @return Pointer to peer info, or nullptr if not found
238+
* @param out Filled with peer info on success
239+
* @return true if the peer was found
239240
*/
240-
const PeerInfo* get_peer(const sock_t& address) const;
241+
bool get_peer(const sock_t& address, PeerInfo& out) const;
241242

242243
/**
243244
* Evict all known peers, firing leave callbacks for any that were fully connected.
Lines changed: 87 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,104 @@
11
/*
2-
* MIT License
3-
*
4-
* Copyright (c) 2025 NUClear Contributors
5-
*
6-
* This file is part of the NUClear codebase.
7-
* See https://github.com/Fastcode/NUClear for further info.
8-
*
9-
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
10-
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
11-
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
12-
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13-
*
14-
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
15-
* the Software.
16-
*
17-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18-
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20-
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21-
* IN THE SOFTWARE.
22-
*/
2+
* MIT License
3+
*
4+
* Copyright (c) 2025 NUClear Contributors
5+
*
6+
* This file is part of the NUClear codebase.
7+
* See https://github.com/Fastcode/NUClear for further info.
8+
*
9+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
10+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
11+
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
12+
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
15+
* the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18+
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20+
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21+
* IN THE SOFTWARE.
22+
*/
2323

2424
#ifndef NUCLEAR_NETWORK_FILE_DESCRIPTOR_HPP
2525
#define NUCLEAR_NETWORK_FILE_DESCRIPTOR_HPP
2626

2727
#include "../util/platform.hpp"
2828

29-
namespace NUClear {
30-
namespace network {
31-
32-
/**
33-
* RAII wrapper for a file descriptor (socket).
34-
* Automatically closes the file descriptor on destruction.
35-
* Non-copyable, move-only.
36-
*/
37-
class FileDescriptor {
38-
public:
39-
/// Construct with an invalid descriptor
40-
FileDescriptor() = default;
41-
42-
/// Construct taking ownership of an existing file descriptor
43-
explicit FileDescriptor(fd_t fd) : fd(fd) {}
29+
namespace NUClear {
30+
namespace network {
4431

45-
/// Destructor closes the descriptor if valid
46-
~FileDescriptor() {
32+
/**
33+
* RAII wrapper for a file descriptor (socket).
34+
* Automatically closes the file descriptor on destruction.
35+
* Non-copyable, move-only.
36+
*/
37+
class FileDescriptor {
38+
public:
39+
/// Construct with an invalid descriptor
40+
FileDescriptor() = default;
41+
42+
/// Construct taking ownership of an existing file descriptor
43+
explicit FileDescriptor(fd_t fd) : fd(fd) {}
44+
45+
/// Destructor closes the descriptor if valid
46+
~FileDescriptor() {
47+
reset();
48+
}
49+
50+
// Non-copyable
51+
FileDescriptor(const FileDescriptor&) = delete;
52+
FileDescriptor& operator=(const FileDescriptor&) = delete;
53+
54+
// Movable
55+
FileDescriptor(FileDescriptor&& other) noexcept : fd(other.fd) {
56+
other.fd = INVALID_SOCKET;
57+
}
58+
FileDescriptor& operator=(FileDescriptor&& other) noexcept {
59+
if (this != &other) {
4760
reset();
48-
}
49-
50-
// Non-copyable
51-
FileDescriptor(const FileDescriptor&) = delete;
52-
FileDescriptor& operator=(const FileDescriptor&) = delete;
53-
54-
// Movable
55-
FileDescriptor(FileDescriptor&& other) noexcept : fd(other.fd) {
61+
fd = other.fd;
5662
other.fd = INVALID_SOCKET;
5763
}
58-
FileDescriptor& operator=(FileDescriptor&& other) noexcept {
59-
if (this != &other) {
60-
reset();
61-
fd = other.fd;
62-
other.fd = INVALID_SOCKET;
63-
}
64-
return *this;
65-
}
66-
67-
/// Get the raw file descriptor
68-
fd_t get() const {
69-
return fd;
70-
}
71-
72-
/// Check if the descriptor is valid
73-
bool valid() const {
74-
return fd != INVALID_SOCKET;
75-
}
76-
77-
/// Implicit conversion to fd_t for use with system calls
78-
operator fd_t() const { // NOLINT(google-explicit-constructor)
79-
return fd;
80-
}
81-
82-
/// Release ownership without closing
83-
fd_t release() {
84-
const fd_t old = fd;
85-
fd = INVALID_SOCKET;
86-
return old;
87-
}
88-
89-
/// Close the current descriptor and take ownership of a new one
90-
void reset(fd_t new_fd = INVALID_SOCKET) {
91-
if (fd != INVALID_SOCKET) {
92-
::close(fd);
93-
}
94-
fd = new_fd;
64+
return *this;
65+
}
66+
67+
/// Get the raw file descriptor
68+
fd_t get() const {
69+
return fd;
70+
}
71+
72+
/// Check if the descriptor is valid
73+
bool valid() const {
74+
return fd != INVALID_SOCKET;
75+
}
76+
77+
/// Implicit conversion to fd_t for use with system calls
78+
operator fd_t() const { // NOLINT(google-explicit-constructor)
79+
return fd;
80+
}
81+
82+
/// Release ownership without closing
83+
fd_t release() {
84+
const fd_t old = fd;
85+
fd = INVALID_SOCKET;
86+
return old;
87+
}
88+
89+
/// Close the current descriptor and take ownership of a new one
90+
void reset(fd_t new_fd = INVALID_SOCKET) {
91+
if (fd != INVALID_SOCKET) {
92+
::close(fd);
9593
}
94+
fd = new_fd;
95+
}
9696

97-
private:
98-
fd_t fd{INVALID_SOCKET};
99-
};
97+
private:
98+
fd_t fd{INVALID_SOCKET};
99+
};
100100

101-
} // namespace network
101+
} // namespace network
102102
} // namespace NUClear
103103

104104
#endif // NUCLEAR_NETWORK_FILE_DESCRIPTOR_HPP

src/nuclear/src/nuclearnet/Fragmentation.cpp

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,16 @@ namespace network {
3838
std::chrono::steady_clock::duration assembly_timeout)
3939
: packet_mtu(packet_mtu), max_assembly_size(max_assembly_size), assembly_timeout(assembly_timeout) {}
4040

41+
uint16_t Fragmentation::compute_fragment_count(const std::size_t payload_length, const uint16_t mtu) {
42+
return payload_length == 0 ? 1 : static_cast<uint16_t>((payload_length + mtu - 1) / mtu);
43+
}
44+
4145
std::vector<Fragmentation::Fragment> Fragmentation::fragment(uint16_t packet_id,
4246
uint64_t hash,
4347
uint8_t flags,
4448
const std::vector<uint8_t>& payload) const {
4549
// Calculate how many fragments we need
46-
const uint16_t packet_count =
47-
payload.empty() ? 1 : static_cast<uint16_t>((payload.size() + packet_mtu - 1) / packet_mtu);
50+
const uint16_t packet_count = compute_fragment_count(payload.size(), packet_mtu);
4851

4952
std::vector<Fragment> fragments;
5053
fragments.reserve(packet_count);
@@ -82,7 +85,11 @@ namespace network {
8285
return false;
8386
}
8487

85-
// Enforce max assembly size check
88+
if (data_length > packet_mtu) {
89+
return false;
90+
}
91+
92+
// Fast reject for absurd packet counts
8693
if (max_assembly_size > 0) {
8794
const std::size_t projected_size = static_cast<std::size_t>(packet_count) * packet_mtu;
8895
if (projected_size > max_assembly_size) {
@@ -94,14 +101,23 @@ namespace network {
94101

95102
const std::lock_guard<std::mutex> lock(assembly_mutex);
96103

97-
auto& assembly = assemblies[key];
98-
assembly.hash = hash;
99-
assembly.flags = flags;
100-
assembly.packet_count = packet_count;
101-
assembly.last_update = now;
104+
auto& assembly = assemblies[key];
105+
assembly.hash = hash;
106+
assembly.flags = flags;
107+
assembly.packet_count = packet_count;
108+
assembly.last_update = now;
109+
110+
auto& frag = assembly.fragments[packet_no];
111+
if (frag.empty()) {
112+
if (max_assembly_size > 0 && assembly.accumulated_size + data_length > max_assembly_size) {
113+
assemblies.erase(key);
114+
return false;
115+
}
116+
assembly.accumulated_size += data_length;
117+
}
102118

103119
// Store this fragment
104-
assembly.fragments[packet_no].assign(data, data + data_length);
120+
frag.assign(data, data + data_length);
105121

106122
// Check if we have all fragments
107123
if (assembly.fragments.size() == packet_count) {

src/nuclear/src/nuclearnet/Fragmentation.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ namespace network {
134134
return packet_mtu;
135135
}
136136

137+
/**
138+
* Compute the number of fragments required for a payload.
139+
*/
140+
static uint16_t compute_fragment_count(std::size_t payload_length, uint16_t packet_mtu);
141+
137142
private:
138143
/// Key for an in-progress assembly: (source_key, packet_id)
139144
using AssemblyKey = std::pair<uint64_t, uint16_t>;
@@ -143,6 +148,7 @@ namespace network {
143148
uint64_t hash{0};
144149
uint8_t flags{0};
145150
uint16_t packet_count{0};
151+
std::size_t accumulated_size{0};
146152
std::chrono::steady_clock::time_point last_update;
147153
std::map<uint16_t, std::vector<uint8_t>> fragments;
148154
};

src/nuclear/src/nuclearnet/Log.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace network {
3030

3131
namespace {
3232

33-
std::atomic<int> g_log_level{static_cast<int>(LogLevel::Off)};
33+
std::atomic<std::uint8_t> g_log_level{static_cast<std::uint8_t>(LogLevel::Off)};
3434

3535
const char* level_name(LogLevel level) {
3636
switch (level) {
@@ -46,7 +46,7 @@ namespace {
4646
} // namespace
4747

4848
void set_log_level(LogLevel level) {
49-
g_log_level.store(static_cast<int>(level), std::memory_order_relaxed);
49+
g_log_level.store(static_cast<std::uint8_t>(level), std::memory_order_relaxed);
5050
}
5151

5252
LogLevel get_log_level() {
@@ -67,7 +67,7 @@ namespace {
6767
}
6868

6969
std::string sock_str(const util::network::sock_t& address) {
70-
const auto addr = address.address();
70+
const auto addr = address.address(true);
7171
return addr.first + ":" + std::to_string(addr.second);
7272
}
7373

src/nuclear/src/nuclearnet/Log.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
namespace NUClear {
3434
namespace network {
3535

36-
enum class LogLevel : int {
36+
enum class LogLevel : std::uint8_t {
3737
Off = 0,
3838
Error = 1,
3939
Warn = 2,
@@ -46,7 +46,7 @@ namespace network {
4646
LogLevel get_log_level();
4747

4848
inline bool should_log(LogLevel level) {
49-
return static_cast<int>(level) <= static_cast<int>(get_log_level());
49+
return static_cast<std::uint8_t>(level) <= static_cast<std::uint8_t>(get_log_level());
5050
}
5151

5252
void log(LogLevel level, const char* component, const std::string& message);

0 commit comments

Comments
 (0)