Redesign NUClearNet and rename to nuclearnet - #190
Conversation
… and Fragmentation Add an optional 'now' parameter (defaulting to steady_clock::now()) to all time-dependent methods in Discovery, Reliability, and Fragmentation. This allows tests to advance time deterministically without sleeping, making them faster, more reliable, and immune to CI timing variability. Tests converted from time-based (sleep_for) to event-based: - Discovery: check_timeouts and touch_peer tests - Reliability: all retransmission/timeout tests (7 sleeps removed) - Fragmentation: cleanup_expired test The only remaining sleep_for is in Integration.cpp's polling loop for genuine async UDP networking, which is inherently time-dependent.
The has_ipv4_multicast() and has_ipv6_multicast() functions previously only checked if network interfaces reported the IFF_MULTICAST flag. On macOS GitHub Actions runners (virtualized ARM64 VMs), interfaces report multicast capability but the hypervisor doesn't actually deliver multicast packets. Now performs an actual multicast send/receive round-trip test with a 200ms timeout. This correctly detects broken multicast environments and causes those tests to be skipped rather than hanging.
There was a problem hiding this comment.
Pull request overview
This PR replaces the legacy monolithic NUClearNetwork implementation under src/extension/network/ with a new modular, standalone src/nuclearnet/ library, and updates the reactor-facing NetworkController to use the new API. It also adds a comprehensive Catch2 unit/integration test suite for the new components and improves multicast capability detection for test gating.
Changes:
- Introduces the new
src/nuclearnet/standalone library (Discovery, Fragmentation, Reliability, RTT estimation, routing, deduplication, wire protocol, RAII FD wrapper). - Migrates
src/extension/NetworkControllerfromNUClearNetworkto the newNUClearNetAPI, including subscription propagation. - Adds/updates tests and test utilities (including multicast round-trip detection) to cover the new networking components.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
src/nuclearnet/CMakeLists.txt |
Adds standalone CMake build for the new nuclearnet library. |
src/nuclearnet/wire_protocol.hpp |
Defines new on-wire structs and header validation helper. |
src/nuclearnet/RTTEstimator.hpp |
Declares RTT estimator API. |
src/nuclearnet/RTTEstimator.cpp |
Implements Jacobson/Karels-style RTT estimation. |
src/nuclearnet/Routing.hpp |
Declares peer/local subscription tracking and filtering API. |
src/nuclearnet/Routing.cpp |
Implements subscription-based routing decisions. |
src/nuclearnet/Reliability.hpp |
Declares ACK/NACK tracking and retransmission API. |
src/nuclearnet/Reliability.cpp |
Implements retransmission tracking and ACK/NACK processing. |
src/nuclearnet/PacketDeduplicator.hpp |
Declares sliding-window packet deduplication. |
src/nuclearnet/PacketDeduplicator.cpp |
Implements wraparound-safe sliding-window deduplication. |
src/nuclearnet/Fragmentation.hpp |
Declares fragmentation and reassembly API. |
src/nuclearnet/Fragmentation.cpp |
Implements MTU fragmentation, reassembly, and expiry cleanup. |
src/nuclearnet/FileDescriptor.hpp |
Adds RAII wrapper for sockets/file descriptors. |
src/nuclearnet/Discovery.hpp |
Declares announce/leave processing and peer tracking API. |
src/nuclearnet/Discovery.cpp |
Implements peer discovery, timeout handling, and callbacks. |
src/nuclearnet/NUClearNet.hpp |
Declares the public standalone networking façade and callbacks. |
src/nuclearnet/NUClearNet.cpp |
Implements socket setup, polling loop, packet IO, and module integration. |
src/util/network/sock_t.hpp |
Extends sock_t with comparison and stream operators for use in maps/logging. |
src/extension/NetworkController.hpp |
Switches controller to use network::NUClearNet. |
src/extension/NetworkController.cpp |
Adapts controller wiring to the new API and subscriptions model. |
src/CMakeLists.txt |
Uses CONFIGURE_DEPENDS for recursive globbing. |
tests/test_util/has_multicast.cpp |
Adds multicast round-trip probing for more reliable test gating. |
tests/tests/nuclearnet/Discovery.cpp |
Adds unit tests for discovery behaviors. |
tests/tests/nuclearnet/Fragmentation.cpp |
Adds unit tests for fragmentation/reassembly behaviors. |
tests/tests/nuclearnet/Integration.cpp |
Adds integration tests for two peers discovering/exchanging data. |
tests/tests/nuclearnet/PacketDeduplicator.cpp |
Adds unit tests for deduplication window/wraparound. |
tests/tests/nuclearnet/Reliability.cpp |
Adds unit tests for ACK/NACK and retransmission behavior. |
tests/tests/nuclearnet/Routing.cpp |
Adds unit tests for routing/subscription filtering. |
tests/tests/nuclearnet/RTTEstimator.cpp |
Adds unit tests for RTT estimator behavior. |
tests/tests/nuclearnet/wire_protocol.cpp |
Adds tests for packed sizes/layout and header validation. |
src/extension/network/NUClearNetwork.hpp |
Removes legacy network header. |
src/extension/network/NUClearNetwork.cpp |
Removes legacy network implementation. |
src/extension/network/wire_protocol.hpp |
Removes legacy wire protocol header. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- FileDescriptor.hpp: Add const to local variable (misc-const-correctness) - NUClearNet.hpp: Value-initialize iovec, add NOLINT for necessary const_cast, remove redundant member initializer - NetworkController.cpp: Add missing direct includes for string, set, Discovery.hpp, and NUClearNet.hpp (misc-include-cleaner) - TestRunner.cmake: Add --skip-returncode 0 so Catch2 returns success when all tests are skipped (fixes macOS CI where multicast is unavailable)
v5 is deprecated and has a known security vulnerability. The scanner was failing with HTTP 403 when querying JRE metadata, likely due to the old action version being unsupported by SonarCloud's API.
Build fixes: - Remove --skip-returncode (not supported in Catch2 v3.6.0) - sock_t.hpp: Add #include <string> for std::to_string/stoi (Windows) - Discovery.cpp: Fix include-cleaner errors and const-correctness Review feedback fixes: - NUClearNet.cpp: Fix unreachable new-peer branch by checking before process_announce adds the peer - Reliability.cpp: Validate ACK packet_count matches tracked packet - Discovery.cpp: Copy PeerInfo before invoking callbacks to avoid holding peers_mutex during user callbacks (deadlock risk) - wire_protocol.hpp: Fix ACK comment (10+ bytes, not 11+) and remove incorrect 'network byte order' claim from announce doc - has_multicast.cpp: Verify received payload matches sent message to avoid false positives from unrelated multicast traffic
Update all networking documentation to accurately describe the v2 implementation: - Protocol version 0x03 (was incorrectly documented as 0x02) - Modular architecture: Discovery, Fragmentation, Reliability, Routing, PacketDeduplicator, RTTEstimator - Jacobson/Karels RTT estimation (RFC 6298), not Kalman filter - Subscription-based routing (announce packets include type hashes) - Sliding-window packet deduplication (256 IDs per peer) - NAT-friendly port learning from UDP source address - Assembly size limits to prevent memory bombs - Configurable peer timeout and max retransmission attempts
Remove max_retransmits limit from Reliability module. Reliable messages now retransmit indefinitely based on RTT-estimated timeouts until either: - All fragments are ACKed (success) - The peer is removed due to timeout or graceful leave (connection lost) This provides true reliable delivery semantics — if the connection is alive, delivery is guaranteed. Also tie assembly timeout to peer_timeout (default 2s) instead of a fixed 10 seconds. The assembly timeout is now the natural bound: if no fragments arrive within the peer timeout period, the peer is either dead or the sender has moved on. For reliable messages, retransmissions keep the assembly alive as long as the sender is connected.
…e-back Since protocol v0.03 is already a breaking change, simplify the wire protocol by removing the DATA_RETRANSMISSION type. The receiver now checks the packet deduplicator for ALL incoming DATA packets. If the packet group was already fully processed, an ACK is sent and the fragment is discarded. This provides the same behavior with one fewer packet type. Packet type numbering is now: ANNOUNCE=1, LEAVE=2, DATA=3, ACK=4, NACK=5 Also document the announce-back behavior: when a node hears an announce from an unknown peer, it immediately sends its own announce via unicast to that peer. This gives instant bidirectional connection without waiting for the next announce cycle.
NACK was never sent by any code path — build_nack_packet existed but was never called in production. The bitset ACK already implicitly communicates which fragments are missing (bit=0), making an explicit NACK redundant. Packet types are now: ANNOUNCE=1, LEAVE=2, DATA=3, ACK=4
Connection establishment now requires two independent conditions: - announce_heard: peer's announce received on multicast channel - handshake == CONFIRMED: 3-way handshake over data ports This confirms all four communication paths (announce and data in both directions) before declaring a peer connected. Changes: - wire_protocol.hpp: Add CONNECT packet type (type=5) with SYN/ACK flags - Discovery.hpp/cpp: Replace single ConnectionState enum with two-flag model (announce_heard bool + HandshakeState enum) - NUClearNet.cpp: Force re-announce on new peer (multicast, not unicast), send CONNECT(SYN) to data port, gate DATA/ACK on is_connected() - Routing: Add is_locally_subscribed() for receiver-side filtering - NUClearNet send(): Unreliable broadcast sends go to multicast group instead of unicasting to each peer individually - Tests: Update Discovery tests for new model, add test for late announce scenario (data handshake completes before announce heard) - Docs: Rewrite connection establishment docs with sequence diagrams showing two-flag model, late announce, and multicast broadcast delivery
When an announce is received from a peer whose handshake is incomplete, retransmit the appropriate CONNECT packet: - IDLE/SYN_SENT: retransmit SYN - SYN_RECEIVED: retransmit SYN+ACK - CONFIRMED: retransmit ACK (helps peer stuck in SYN_RECEIVED) This handles all dropped packet scenarios by piggybacking on the ~500ms announce interval. No separate retransmission timer needed. Changes: - Discovery::process_announce now returns AnnounceResult with is_new and response_flags fields - NUClearNet uses the result to send CONNECT and force re-announce - Added unit tests for retransmission in each handshake state - Documented resilience behavior in nuclearnet.md
3f85ce5 to
d6f56f4
Compare
- 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
d6f56f4 to
d236f14
Compare
- Wrap shutdown() in try-catch in destructor to prevent throwing - Guard send_iov against INVALID_SOCKET fd - Change validate_header parameter from void* to const uint8_t* - Add const qualifier to multicast bool - Add NOLINT annotations for required const_cast (POSIX sendmsg API)
Co-authored-by: Cursor <[email protected]>
Loopback UDP delivery is unreliable on Windows runners; coverage remains on Linux and macOS CI. Co-authored-by: Cursor <[email protected]>
Catch2 SKIP makes the test runner exit non-zero; SUCCEED records a passing run. Co-authored-by: Cursor <[email protected]>
Add direct includes, const-correctness, and remove unused callback parameters. Co-authored-by: Cursor <[email protected]>
Apply const-correctness, direct includes, std::array, and include-cleaner fixes in Fragmentation, Reliability, Routing, wire_protocol, and ProcessPacket tests. Co-authored-by: Cursor <[email protected]>
…tests Add direct includes, const lock guards, deleted special members on NetworkPair, and const-correctness in wraparound deduplication test. Co-authored-by: Cursor <[email protected]>
Replace using-directive with declarations, use std::array, by-value callback parameters, and const-correctness fixes. Co-authored-by: Cursor <[email protected]>
…essPacket tests Co-authored-by: Cursor <[email protected]>
… callbacks Explicitly move unused payload parameters in packet callback lambdas. Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
Replace the send-only multicast probe with PR #190's canonical send/receive round-trip detection (clang-tidy-clean), guard the UDP loopback matrix on Windows CI, and bump the UDP test timeout to match #190. Also resolve clang-tidy diagnostics introduced by the earlier wait_for fix (unused <utility>, non-forwarded forwarding reference). Co-authored-by: Cursor <[email protected]>
Restore TimeUnit(50) to match TCP, remove the Windows CI blanket skip and platform-specific multicast case exclusions, and gate multicast tests solely on the round-trip has_multicast availability probe. Co-authored-by: Cursor <[email protected]>
Ignore own announces by matching the data socket ephemeral port, fix getaddrinfo iteration to use each result entry, and enable IP_MULTICAST_LOOP for single-host multicast development. Co-authored-by: Cursor <[email protected]>
SO_REUSEPORT load-balances unicast discovery on macOS. Linux multi-peer local dev should use 127.255.255.255 with SO_REUSEADDR only. Co-authored-by: Cursor <[email protected]>
macOS requires SO_REUSEPORT for multiple processes on the same UDP port; limit it to __APPLE__ so Linux keeps SO_REUSEADDR-only fan-out for loopback broadcast. Multicast still delivers to every listener on macOS. Co-authored-by: Cursor <[email protected]>
Socket option policy is now consistent; fan-out vs load-balance depends on the announce address and OS stack (e.g. macOS does not deliver 127.255.255.255). Co-authored-by: Cursor <[email protected]>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 45 out of 45 changed files in this pull request and generated 5 comments.
Suppressed comments (7)
src/nuclearnet/NUClearNet.cpp:476
get_peers()includes peers that have only announced or are still handshaking. Sending and tracking reliable data for those entries makes the receiver discard it as unconnected and starts retransmission before a connection exists. Filter this list withis_connected()as the API documentation requires.
for (const auto& peer : peers) {
const auto& addr = peer.first;
if (routing.should_send(addr, hash)) {
targets.push_back(addr);
src/nuclearnet/NUClearNet.cpp:486
- Named sends also select peers that are known but not fully connected. This can send DATA before the handshake and, for reliable messages, leave retransmission state for packets the peer was required to reject. Require
is_connected(addr)here too.
for (const auto& peer : peers) {
const auto& addr = peer.first;
const auto& info = peer.second;
if (info.name == target && routing.should_send(addr, hash)) {
targets.push_back(addr);
src/nuclearnet/NUClearNet.cpp:733
- A DATA datagram's fixed wire header is
sizeof(DataPacket) - 1; the final byte in the struct is only a payload placeholder.send()emits exactly that 20-byte header for an empty payload, but this 21-byte check rejects it, so empty messages can never be delivered despite fragmentation explicitly supporting them.
if (length < sizeof(DataPacket)) {
docs/how-to/networking.md:57
- These are not defaults of the documented
NetworkConfigurationmessage: its address is empty and its port is0, andNetworkControllercopies both values over the engine defaults. A default-constructed message therefore does not use this multicast endpoint. Mark these fields as required here, or change the public message defaults to match.
| `name` | `string` | — | Unique name for this node on the network |
| `announce_address` | `string` | `"239.226.152.162"` | Address for node discovery announcements |
| `announce_port` | `uint16_t` | `7447` | Port for announce messages |
src/nuclearnet/wire_protocol.hpp:118
- The stated DATA wire size is inconsistent with the offsets below it: fields through
hashoccupy bytes 0–19, so the fixed header is 20 bytes before payload, not 18. Correct this so external protocol implementations do not use the wrong payload offset.
* Wire layout (18+ bytes):
src/nuclearnet/Reliability.cpp:93
- Every partial or duplicate ACK before a retransmission records another RTT sample for the same transmission. A fragmented packet therefore contributes many progressively later samples, biasing the estimator toward transfer duration rather than round-trip time and inflating its RTO. Track whether this packet has already supplied an RTT sample and measure only the first valid ACK.
if (tp.retransmit_count == 0) {
auto rtt = now - tp.last_send;
{
const std::lock_guard<std::mutex> rtt_lock(rtt_mutex);
rtt_estimators[source].measure(rtt);
tests/tests/dsl/UDP.cpp:378
- This skips the entire UDP suite on any Windows environment that defines
CI, not only the unreliable GitHub loopback cases. Consequently even unicast and broadcast behavior receives no Windows CI coverage, allowing platform regressions to pass. Restrict the condition to the affected runner/cases rather than returning from the whole test.
| // Execution handle | ||
| process_handle = on<Trigger<ProcessNetwork>>().then("Network processing", [this] { network.process(); }); | ||
| process_handle = on<Trigger<ProcessNetwork>>().then("Network processing", [this] { net.process(); }); | ||
|
|
||
| for (auto& fd : network.listen_fds()) { | ||
| listen_handles.push_back(on<IO>(fd, IO::READ).then("Packet", [this] { network.process(); })); | ||
| for (auto& fd : net.listen_fds()) { | ||
| listen_handles.push_back(on<IO>(fd, IO::READ).then("Packet", [this] { net.process(); })); |
| for (auto& fd : net.listen_fds()) { | ||
| listen_handles.push_back(on<IO>(fd, IO::READ).then("Packet", [this] { net.process(); })); |
| auto& assembly = assemblies[key]; | ||
| assembly.hash = hash; | ||
| assembly.flags = flags; | ||
| assembly.packet_count = packet_count; | ||
| assembly.last_update = now; |
| RTTEstimator& Reliability::get_rtt(const sock_t& target) { | ||
| const std::lock_guard<std::mutex> lock(rtt_mutex); | ||
| return rtt_estimators[target]; |
| std::chrono::duration_cast<std::chrono::milliseconds>(next - now).count(); | ||
| log(LogLevel::Trace, "net", "schedule next event in " + std::to_string(ms) + "ms"); | ||
| } | ||
| event_callback(next); |
NUClearNet wrote its logs straight to stderr with a global level that could not be configured from NetworkConfiguration. That meant embedders had no way to turn the logs on, and when they were on the output bypassed whatever logging system the embedder already had. Add a log handler callback to the library that both embedders install: NetworkController re-emits the messages as NUClear LogMessages, and the node bindings can hand them to the JavaScript logger. An empty handler restores the stderr default for standalone use. NetworkConfiguration gains a log_level which sets both the library level and the NetworkController reactor level, so a single setting controls the whole path. UNKNOWN leaves the logs off, which is the existing behaviour. Co-Authored-By: Claude Opus 5 <[email protected]>
Reactor::log(level, args...) resolves to the compile time overload with its default level rather than the runtime one, so every message came out at DEBUG with the level name stringified into the text. Switch on the level and call the compile time overload directly. Co-Authored-By: Claude Opus 5 <[email protected]>
DEBUG is event driven while TRACE fires on every poll cycle, which is a large difference in volume that the level names alone don't convey. Co-Authored-By: Claude Opus 5 <[email protected]>
DataPacket's `data` member is a one byte placeholder marking where the payload begins, so sizeof(DataPacket) is one larger than the header on the wire. Every site compensated with a -1 except the length guard in process_data_packet, which required 21 bytes. A message that serialises to nothing is deliberately sent as a single fragment with no payload, which is exactly 20 bytes, so it was dropped with a "short DATA" warning and never delivered. In proto3 that is any message whose fields are all empty or default, which for collection shaped messages is a routine occurrence rather than an edge case. Replace the sizeof arithmetic with a named DATA_HEADER_SIZE so the header size is stated once. Co-Authored-By: Claude Opus 5 <[email protected]>
These were picked up from a build-install directory that .gitignore's build/ entry did not cover. Widen it to build*/. Co-Authored-By: Claude Opus 5 <[email protected]>
Three problems compounded into a retransmit storm that could keep a peer permanently flapping. Retransmission never gave up. A packet the peer would never acknowledge was resent every RTO for as long as it stayed connected, at a constant rate, and every further one added to the load. Bound it to MAX_RETRANSMITS and back the timeout off exponentially, logging what was given up on and how much of it was unacknowledged. A receiver that was not subscribed to a hash discarded the packet without acknowledging it. Subscriptions change while a reliable send is already in flight, so this is reachable for a packet the sender still believes we want, and it left that packet undeliverable forever. Acknowledge the fragment before deciding what to do with it. Neither socket asked for a larger buffer than the system default. A multi fragment message is written in a tight loop, so at the common 208KB default a single burst is capped at roughly 90 fragments and the rest are dropped, costing a round trip per 90 fragments. A 706KB message took five retransmission rounds to get through; it now arrives with none. Co-Authored-By: Claude Opus 5 <[email protected]>
Giving up on an individual packet and carrying on with the next one breaks the guarantee reliable exists to provide. The caller asked to be sure the data arrived, and later messages getting through while an earlier one was quietly discarded is worse than a failure they can see. Follow what TCP does with an unacknowledged segment: exhausting the retries means the connection is broken, not that one message was unlucky. check_retransmissions now reports the peer as unreachable and keeps the packet tracked, and NUClearNet takes the same path as a LEAVE packet, so the caller gets a disconnect. The peer reconnects off its next announce and the state is sent again from scratch. Also bound the backed off interval. The timeout is already capped at 60s for a genuinely slow peer, and doubling that six times would take an hour to notice the peer had gone. Co-Authored-By: Claude Opus 5 <[email protected]>
Self announce detection compared only the port. Our data socket usually binds INADDR_ANY, so getsockname gives a port but no usable address, and the port alone was taken as proof an announce was our own coming back. A remote peer's data socket can land on the same ephemeral port as ours, and when it does every announce it sends is silently discarded for the life of both sockets. Nothing else it sends is affected, so the peer still appears briefly whenever it sends anything on our data port and then times out again two seconds later, which looks like a peer that connects and disconnects forever while every other peer is fine. Require the announce to carry our own node name as well, so a port collision alone can no longer hide a peer. Co-Authored-By: Claude Opus 5 <[email protected]>
A peer is put in the map as soon as its announce is heard, but the receiving side discards data from a peer it has not finished connecting to. Target selection filtered on name and subscription only, so for the round trip it takes to complete the handshake we would send data that was guaranteed to be thrown away. For a reliable send that is worse than wasted: the packet is tracked, never acknowledged because the far side refuses it, and burns its entire retry budget before we conclude the peer is unreachable and drop a connection that was in the middle of being established. Require the connection to be confirmed before a peer is a valid target, matching what the far side requires before it will accept anything. Co-Authored-By: Claude Opus 5 <[email protected]>
Summary
Replaces the monolithic
NUClearNetworkimplementation insrc/extension/network/with a redesigned, modularsrc/nuclearnet/library. The new implementation is built as a standalone library that can be used independently of the reactor framework.Improvements over the old NUClearNetwork
nuclearnetcan now be built and linked independently of the NUClear reactor framework, enabling reuse in other projects.Architecture
The new NUClearNet is decomposed into focused components:
NUClearNetties these together to provide the public API (join, leave, send, process).Connection establishment
Peers must satisfy two independent conditions before a connection is considered "up":
announce_heard) — received the peer's announce on the multicast/broadcast channel, proving their data port can reach our announce address.handshake == CONFIRMED) — a 3-way SYN/SYN+ACK/ACK handshake over the data ports proves bidirectional unicast connectivity.The packet type encodes which path was used — ANNOUNCE packets always go to the multicast group, CONNECT packets always go to the peer's data port. This confirms all four communication paths without needing socket tracking.
When a new peer's announce is heard:
Multicast broadcast delivery
Unreliable broadcast sends (empty target, non-reliable) are sent once to the multicast/broadcast group rather than unicasting to each peer individually. Receivers filter by local subscription before fragmentation reassembly.
Reliable sends and targeted sends remain unicast for per-peer ACK tracking.
What Changed
src/extension/network/NUClearNetwork.{cpp,hpp}andsrc/extension/network/wire_protocol.hpp(old monolithic implementation)src/nuclearnet/— all new source and headers with its ownCMakeLists.txtsrc/util/network/sock_t.hpp— cross-platform socket type aliassrc/extension/NetworkController.{cpp,hpp}— updated to use the newnuclearnetlibrary APItests/tests/nuclearnet/— Catch2 BDD-style unit tests for each component (Discovery, Fragmentation, Integration, PacketDeduplicator, RTTEstimator, Reliability, Routing, wire_protocol)Build
nuclearnetlibrary links against the platform socket library and can be consumed standalone via CMakeLogging
NUClearNet has its own log level and log sink in
src/nuclearnet/Log.hpp, defaulting toOffand writing to stderr. Because the library is usable without the reactor framework, the sink is a settable callback rather than a hard dependency on NUClear's logging system:NetworkControllerinstalls a handler that re-emits the messages as NUClearLogMessages, so the usual log handlers pick them up instead of the output bypassing them on stderr.NUClearNet.jsinstalls one that hands them to its JavaScript logger.message::NetworkConfigurationgains alog_level, which sets both the library level (deciding what it hands to the handler) and theNetworkControllerreactor level (deciding what it emits), so one setting controls the whole path.UNKNOWNleaves the logs off, which is the existing behaviour.Note that
NetworkControllerdispatches on the level with an explicit switch rather than callingReactor::log(level, args...). That runtime-level overload loses overload resolution to the compile time one (whoseArguments...pack absorbs theLogLevel), so it logs everything atDEBUGwith the level name stringified into the message. Worth fixing separately — it affects every caller of that overload, not just this one.