Files
punktfunk/crates/punktfunk-core
enricobuehler aa070f2d55 feat(ffi): hand-mirrored C structs are now layout-checked at compile time
The sharpest memory-safety risk left in this codebase is not an `unsafe` block — it is a
hand-written `#[repr(C)]` mirror of an external C struct. Get a field offset wrong and nothing fails
to compile and nothing reliably crashes: the library reads a pointer, a length or a pitch out of the
wrong bytes. Eleven such structs across five files had NO check at all.

Guarded here, each next to the struct it protects:

* `AVCUDADeviceContext`, and `AVD3D11VADeviceContext`/`AVD3D11VAFramesContext`. `ffmpeg-sys-next`
  binds none of them, so these mirrors are the only definitions — and we WRITE through them
  (`cuda_ctx`, `device`, `bind_flags`). ⚠ The D3D11VA pair is duplicated VERBATIM in two crates
  (pf-encode's `ffmpeg_win.rs`, pf-client-core's `video_d3d11.rs`) because neither can depend on the
  other; they must agree with libav and with each other, and now a drift in either is a build error.
* The six cuda.h structs. Three were already asserted — but only in `#[cfg(test)]`, so the check ran
  when someone ran the tests and never in a release build. They are `const` now. The other three,
  including `CUDA_MEMCPY2D` which is filled on EVERY zero-copy frame, had nothing.
* `MsghdrX`, Darwin's `msghdr_x`, which `libc` does not expose. Its layout is not reviewable by eye:
  the 32-bit fields force padding before each following pointer, so `msg_iov` sits at 16 and not 12.
  `sendmsg_x`/`recvmsg_x` take the pointer and length from it.
* `IPolicyConfigVtbl` — the sharpest of the set. It mirrors an UNDOCUMENTED COM interface, and
  `set_default_endpoint` is called by SLOT INDEX through a ten-entry `_reserved` gap that carries no
  names to anchor a review. A field added or resized above it does not break the build; it calls a
  different function pointer through a mismatched signature.

Every assertion is `const _: () = assert!(..)`, so it holds on every build including release and
cannot be skipped. The compiler verified the numbers — the sizes and offsets asserted here are the
ones the target actually produces, on each platform that compiles the struct.

Verified: Linux .21 fmt + both CI clippy steps rc=0 (CUDA + libav CUDA mirrors); Windows .47 full CI
clippy set rc=0 + pf-capture tests (D3D11VA pair, COM vtable); macOS `cargo check -p punktfunk-core`
(MsghdrX — the only platform that compiles it).
2026-07-29 08:48:42 +02:00
..

punktfunk-core

The shared protocol core — the one place where punktfunk's transport, forward error correction, and crypto live. It's linked into the host and every native client, so there's exactly one implementation of the wire format everywhere.

Written in Rust with no async on the per-frame path (native threads only). It exposes both a normal Rust API and a stable, versioned C ABI, so the Swift and Kotlin clients — and any C embedder — link the same code as the Rust ones.

What's in here

  • Transport & session (session.rs, transport/, packet.rs) — the punktfunk/1 data plane over raw UDP: packetization, reassembly (with attacker-bounded limits), pacing, and socket tuning.
  • FEC (fec/) — the wall-breaker. Two codes:
    • GF(2⁸) classic ReedSolomon with the Cauchy generator matrix — byte-identical to the nanors library Moonlight uses, so our parity is decodable by a stock Moonlight client.
    • GF(2¹⁶) Leopard-RS (SIMD, O(n log n)) — up to 65535 shards/block, which removes the ~1 Gbps FEC ceiling. punktfunk/1 negotiates this one.
  • Crypto (crypto.rs) — AES-128-GCM session encryption with per-direction nonce salts and sequence-as-AAD; SPAKE2 PIN pairing lives behind the quic feature.
  • QUIC control plane (quic.rs, client.rs, feature quic) — the Hello/Welcome/Start handshake, cert pinning/TOFU, reverse audio, and the embeddable NativeClient connector. This is the only place tokio/quinn are allowed; the feature is off by default so the core stays runtime-free.
  • C ABI (abi.rs) — the versioned surface (punktfunk_abi_version(), PunktfunkConfig carrying its own struct_size) that generates include/punktfunk_core.h via cbindgen at build time.

Build outputs

The crate builds three ways at once (crate-type = ["lib", "cdylib", "staticlib"]):

Output Used by
lib (rlib) the host, probe, and tools link it as a normal Rust crate
cdylib (.so/.dylib) the Swift / Kotlin clients via the C ABI
staticlib (.a) the C test harness and static embedding

Test

cargo test -p punktfunk-core                 # unit + proptest + loopback
cargo run  -p loss-harness                   # FEC loss-resilience sweep (no network needed)
bash crates/punktfunk-core/tests/c/run.sh    # standalone C-ABI link + round-trip proof

Design invariants (do not regress)

  • One core, linked everywhere — protocol/FEC/crypto live only here, behind the stable C ABI.
  • No async on the hot path — the per-frame pipeline is native threads only; quic (tokio/quinn) is control-plane only, feature-gated, off by default.
  • Security hardening stays intact — the reassembler bounds attacker-controlled fields before allocating; AES-GCM keeps per-direction nonce salts + seq-as-AAD; the ABI checks struct_size. Regression tests exist — keep them green.
  • punktfunk-host — the streaming host built on this core
  • Clients — the apps that link this core over the C ABI (or directly, in Rust)
  • punktfunk-planning: implementation-plan.md (internal planning repo) — why GF(2¹⁶) FEC, the latency budget, and the architecture thesis