Skip to content

Encapsulate libudpard in a jarnax::cyphal::Interface implementation #61

Description

@emrainey

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), and Node/Service are 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.cpp already does all the udpard work inline:
    • O1Heap → UdpardMemoryResource bridge (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

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)

  1. 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 breaking Metadata, Node, Service, and their tests). Extending Metadata can come later if needed.
  2. Fragmented RX vs contiguous span: Listener::OnReceive takes core::Span; udpard delivers UdpardFragment lists → one copy bounded by extent, plus a defined buffer ownership policy.
  3. 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.
  4. Memory resource injection: constructor takes a UdpardMemoryResource instead of reaching for O1HeapPool::Instance().
  5. Redundant interfaces: start with 1; TransportStatistics already has the array shape for more later.

Steps

  • Create branch issue-<N> tracking develop
  • Define minimal socket abstraction (join/leave multicast, send datagram, receive callback) in modules/jarnax
  • Implement jarnax::cyphal::udp::UdpInterface (implements all 6 virtuals + jarnax::Loopable for 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 CyphalApp to use UdpInterface; 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 (CyphalApp includes no udpard.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)

Activity

  1. self-assigned this
    on Aug 21, 2026
  2. emrainey commented on Aug 23, 2026

    @emrainey
    OwnerAuthor

    Progress update — CyphalUDPInterface core encapsulation (commit de96db7 on develop):

    Done

    • O1HeapPool moved from nucleo-cyphal into jarnax-cyphal-udp, implements core::Allocator with UdpardMemoryResource/UdpardRxMemoryResources factories (64 KiB arena).
    • Socket abstraction modules/jarnax/source/include/jarnax/services/CyphalUDPSocket.hpp — udp::Endpoint, DatagramHandler, Socket (Join/Leave/Send) plus MicrosecondClock so the generic module stays cortex-free and host-testable.
    • CyphalUDPInterface in the new CyphalUDPInterface.*pp files — implements all 6 Interface virtuals + 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, udpardGather scratch, TransportStatistics.
    • nucleo-cyphal now depends on jarnax-cyphal-udp; CyphalApp uses jarnax::cyphal::O1HeapPool.
    • GoogleTest suite: gtest-cyphal-o1heappool + gtest-cyphal-udpinterface (12 tests) with MockUDPSocket (gmock). RX datagrams are produced by a local UdpardTx instance — 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-llvm 21/21, on-host-native-clang 21/21, on-target-cortex-m4-gcc-arm-none-eabi and on-target-cortex-m7-gcc-arm-none-eabi all pass (62/62 in jarnax-cyphal alone). Cross builds clean.
    • PLAN.md updated (steps 4,5,7 marked complete); GOTCHAS.md appended with 6 new gotchas (empty-transfer udpardGather, PortId non-assignable, client-group response routing, o1heap alignment, etc.).

    Remaining per #61

    • Hypha adapter implementing Socket
    • Refactor CyphalApp onto CyphalUDPInterface (remove inline udpard plumbing)
    • Hardware verification via pylink-square-mcp/RTT + yactui (heartbeat, GetInfo server/client)
    • Final ./scripts/build-all-presets.sh and PR to develop

    Commit: de96db7 — pushed to develop (branch develop ahead of macmini/develop by 3). No udpard.h types leak into app headers yet (full removal on app refactor).

  3. emrainey commented on Sep 18, 2026

    @emrainey
    OwnerAuthor

    Progress update — socket abstraction replaced with a udp::Dispatcher (commit 8982428 on branch issue-61):

    Done since last update

    • Replaced the udp::Socket abstraction with udp::Dispatcher (Join/Leave/Send); CyphalUDPInterface now takes a Dispatcher and incoming datagrams are pushed in by the dispatcher implementation (libhypha on target).
    • Added application-owned nucleo::cyphal::HyphaUdpDispatcher, which adapts libhypha's UDP callback and multicast TX/RX preparation to udp::Dispatcher. Leave() stops local dispatching because libhypha has no unprepare API.
    • GoogleTest suite migrated to MockUDPDispatcher (gmock); still 62/62 in jarnax-cyphal alone.

    Verification

    • Branch issue-61 created off develop (currently 31b1f9f) with the single commit 8982428.
    • on-host-native-llvm 21/21, on-host-native-clang 21/21, on-target-cortex-m4-gcc-arm-none-eabi and on-target-cortex-m7-gcc-arm-none-eabi all pass/build clean.

    Remaining per #61

    • Refactor CyphalApp onto CyphalUDPInterface (remove inline udpard plumbing)
    • Hardware verification via pylink-square-mcp/RTT + yactui (heartbeat, GetInfo server/client)
    • Final ./scripts/build-all-presets.sh and PR to develop
  4. emrainey commented on Sep 18, 2026

    @emrainey
    OwnerAuthor

    Merged via PR #66 (e0a8eaf on develop) — the udp::Dispatcher refactor is in. Branch issue-61 deleted.

    Remaining per #61:

    • Refactor CyphalApp onto CyphalUDPInterface (remove inline udpard plumbing)
    • Hardware verification via pylink-square-mcp/RTT + yactui (heartbeat, GetInfo server/client)
    • Final ./scripts/build-all-presets.sh pass before closing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions