Repository navigation
Encapsulate libudpard in a jarnax::cyphal::Interface implementation #61
Copy link
Copy link
Open
Description
Plan: Encapsulate libudpard in a cyphal::Interface implementation
Summary
Extract the libudpard plumbing currently inlined in applications/nucleo-cyphal/source/CyphalApp.cpp (~400 of 795 lines) into a reusable jarnax::cyphal::Interface implementation (UdpInterface), so any application can use Cyphal/UDP without touching udpard directly. This is mostly an extraction/refactor, not greenfield work.
Current state
jarnax::cyphal::Interface(modules/jarnax/include/jarnax/cyphal/Interface.hpp:80) is defined with a GoogleMock (MockInterface.hpp), andNode/Serviceare already tested against the mock — but no concrete implementation exists.- libudpard v1.0 is vendored at
third-party/libudpard(plain C99, compiles on host). CyphalApp.cppalready does all the udpard work inline:- O1Heap →
UdpardMemoryResourcebridge (UdpardAlloc/UdpardFree) - TX pipeline init + drain loop (
ProcessTransmitQueue) - RX subscription (Heartbeat subject), RPC dispatcher (GetInfo request/response ports)
- hypha_ip glue: frame pool, multicast joins, MAC filters, UDP transmit/receive callbacks
- O1Heap →
Work breakdown
| Item | Est. | Notes |
|---|---|---|
Design mapping (PortId ↔ udpard ports, negated error codes → core::Status, timestamps) |
0.5d | Mostly mechanical |
| Socket/network abstraction over hypha (injectable) | 1–2d | ~100 line interface + ~150 line hypha adapter; required to keep unit tests host-runnable |
UdpInterface implementation |
2–3d | TX drain, subscription pool for Listen/Remove, dispatcher wiring, per-port transfer-ID counters, fragment gathering via udpardGather, statistics for GetStatistics |
Refactor CyphalApp onto it |
0.5–1d | Delete inline plumbing; app keeps only heartbeat/scanner/diagnostic logic |
| GoogleTest suite + cross-build verification | 2–3d | Mock the socket abstraction; run all host presets + both cross presets |
Total estimate: 4–7 days.
Impedance mismatches (design decisions)
Send(Metadata&, SerializedMessage)carries no priority or transfer-ID — udpard requires both. Plan: default priority = Nominal and keep internal per-port tid counters inside the implementation (avoids breakingMetadata,Node,Service, and their tests). ExtendingMetadatacan come later if needed.- Fragmented RX vs contiguous span:
Listener::OnReceivetakescore::Span; udpard deliversUdpardFragmentlists → one copy bounded by extent, plus a defined buffer ownership policy. - Multicast lifecycle: each
Listen()must join the subject's multicast group in hypha (and add the MAC filter);Remove()must leave it. The socket abstraction owns this. - Memory resource injection: constructor takes a
UdpardMemoryResourceinstead of reaching forO1HeapPool::Instance(). - Redundant interfaces: start with 1;
TransportStatisticsalready has the array shape for more later.
Steps
- Create branch
issue-<N>trackingdevelop - Define minimal socket abstraction (join/leave multicast, send datagram, receive callback) in
modules/jarnax - Implement
jarnax::cyphal::udp::UdpInterface(implements all 6 virtuals +jarnax::Loopablefor TX drain / RX dispatch) - Hypha adapter implementing the socket abstraction
- Unit tests (GoogleTest): Listen/Remove/IsListening lifecycle, Send→publish/request/respond paths, RX dispatch to Listener, error mapping, statistics
- Refactor
CyphalAppto useUdpInterface; verify on hardware via pylink/RTT - Run
cmake --workflow --preset on-host-native-llvm,on-host-native-clang, and both cross presets; then./scripts/build-all-presets.sh
Acceptance criteria
- No udpard types leak into application code (
CyphalAppincludes noudpard.h) - All host unit tests pass on LLVM and AppleClang
- Cross builds (M4/M7 GCC) unbroken
- nucleo-cyphal behaves identically on hardware (heartbeat, diagnostic record, GetInfo server/client verified with yactui)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
emrainey commentedon Aug 23, 2026
Progress update —
CyphalUDPInterfacecore encapsulation (commit de96db7 ondevelop):Done
O1HeapPoolmoved fromnucleo-cyphalintojarnax-cyphal-udp, implementscore::AllocatorwithUdpardMemoryResource/UdpardRxMemoryResourcesfactories (64 KiB arena).modules/jarnax/source/include/jarnax/services/CyphalUDPSocket.hpp—udp::Endpoint,DatagramHandler,Socket(Join/Leave/Send) plusMicrosecondClockso the generic module stays cortex-free and host-testable.CyphalUDPInterfacein the newCyphalUDPInterface.*ppfiles — implements all 6Interfacevirtuals +Loopable::Execute(TX drain) +DatagramHandler::OnDatagramReceived(RX dispatch). Pooled subscriptions (8), RPC ports (8), per-port TID counters (16), remembered request TIDs (8), fixed Nominal priority,udpardGatherscratch,TransportStatistics.nucleo-cyphalnow depends onjarnax-cyphal-udp;CyphalAppusesjarnax::cyphal::O1HeapPool.gtest-cyphal-o1heappool+gtest-cyphal-udpinterface(12 tests) withMockUDPSocket(gmock). RX datagrams are produced by a localUdpardTxinstance — no hand-built wire formats. Covers lifecycle, Join-once service group, publish/request/respond, TX-error stats, subject and RPC round-trips, unknown/bad datagram handling. Also fixed stale over-alignment expectation.Verification
on-host-native-llvm21/21,on-host-native-clang21/21,on-target-cortex-m4-gcc-arm-none-eabiandon-target-cortex-m7-gcc-arm-none-eabiall pass (62/62injarnax-cyphalalone). Cross builds clean.PLAN.mdupdated (steps 4,5,7 marked complete);GOTCHAS.mdappended with 6 new gotchas (empty-transferudpardGather,PortIdnon-assignable, client-group response routing, o1heap alignment, etc.).Remaining per #61
SocketCyphalAppontoCyphalUDPInterface(remove inline udpard plumbing)pylink-square-mcp/RTT +yactui(heartbeat, GetInfo server/client)./scripts/build-all-presets.shand PR todevelopCommit:
de96db7— pushed todevelop(branchdevelopahead ofmacmini/developby 3). Noudpard.htypes leak into app headers yet (full removal on app refactor).cyphal: encapsulate libudpard in CyphalUDPInterface (issue-61)
emrainey commentedon Sep 18, 2026
Progress update — socket abstraction replaced with a
udp::Dispatcher(commit8982428on branchissue-61):Done since last update
udp::Socketabstraction withudp::Dispatcher(Join/Leave/Send);CyphalUDPInterfacenow takes aDispatcherand incoming datagrams are pushed in by the dispatcher implementation (libhypha on target).nucleo::cyphal::HyphaUdpDispatcher, which adapts libhypha's UDP callback and multicast TX/RX preparation toudp::Dispatcher.Leave()stops local dispatching because libhypha has no unprepare API.MockUDPDispatcher(gmock); still 62/62 injarnax-cyphalalone.Verification
issue-61created offdevelop(currently31b1f9f) with the single commit8982428.on-host-native-llvm21/21,on-host-native-clang21/21,on-target-cortex-m4-gcc-arm-none-eabiandon-target-cortex-m7-gcc-arm-none-eabiall pass/build clean.Remaining per #61
CyphalAppontoCyphalUDPInterface(remove inline udpard plumbing)pylink-square-mcp/RTT +yactui(heartbeat, GetInfo server/client)./scripts/build-all-presets.shand PR todevelopemrainey commentedon Sep 18, 2026
Merged via PR #66 (
e0a8eafondevelop) — theudp::Dispatcherrefactor is in. Branchissue-61deleted.Remaining per #61:
CyphalAppontoCyphalUDPInterface(remove inline udpard plumbing)pylink-square-mcp/RTT +yactui(heartbeat, GetInfo server/client)./scripts/build-all-presets.shpass before closing