86 commits of main, including the whole M1-M12 haptics sweep. Twelve conflicting files; three of them were more than textual. **The capability bits collided.** Both branches allocated the SAME wire bits for DIFFERENT features: `client_caps 0x04` and `host_caps 0x20` are redundant desktop audio on main and pad audio here. Merged naively, a peer would negotiate one and get the other. Pad audio moves to the next free bits — `CLIENT_CAP_PAD_AUDIO = 0x08`, `HOST_CAP_PAD_AUDIO = 0x40` — and the `abi.rs` mirrors move with them (their compile-time equality assertions caught the mismatch, which is exactly what they are for). **Both branches also claimed ABI v15.** Main's shipped (the rumble-policy floor), so the pad-audio surface becomes **v16**. **`native/input.rs` would have reintroduced a fixed bug.** This branch resets `rumble_seq[idx]` on pad removal; M1 established that the client's reorder gate is per-connection with no reset path, so restarting the host counter strands every later envelope until it climbs back. Took main's seq-preserving `clear_pad_feedback` and kept only the branch's `pad_streams.stop(idx)`. The rest: `wiring_plan::plan` now delegates to main's `plan_with_formats`, so the pad-endpoint filter moved into that body and the predicate behind it is factored out as `is_pad_render` (also what B10 needs); `Ds5Feedback::AUDIO` derives from main's `REPORT_ID_LEN` like its siblings; `AudioCtl` joins the explicitly-listed unhandled variants so the guard-false case is covered rather than swept up by a `_`; `include/punktfunk_core.h` regenerated rather than hand-merged.
150 lines
9.6 KiB
Rust
150 lines
9.6 KiB
Rust
//! # punktfunk-core
|
||
//!
|
||
//! The shared protocol / transport / FEC core for the punktfunk low-latency streaming
|
||
//! stack. It is compiled exactly once and linked by every host and client — directly
|
||
//! as a Rust `lib`, or across the [C ABI](crate::abi) by Swift / Kotlin / C clients.
|
||
//!
|
||
//! Everything platform-specific (capture, encode, decode, present, input injection)
|
||
//! lives *outside* this crate. What lives *here*:
|
||
//!
|
||
//! - [`fec`] — erasure coding. GF(2⁸) for GameStream/Moonlight compatibility (P1) and
|
||
//! GF(2¹⁶) Leopard-RS (P2) which removes the ~1 Gbps per-frame shard-count ceiling.
|
||
//! - [`packet`] — `#[repr(C)]` zero-copy wire framing: splitting an access unit into
|
||
//! FEC blocks of MTU-sized shards and reassembling them on the far side.
|
||
//! - [`crypto`] — AES-128-GCM session sealing, matching GameStream in P1.
|
||
//! - [`session`] — the host (submit frame → FEC → packetize → seal → send) and client
|
||
//! (recv → open → reorder → FEC recover → reassemble) state machines.
|
||
//! - [`transport`] — pluggable packet I/O (in-process loopback for tests; UDP for real).
|
||
//! - [`abi`] — the `extern "C"` surface and `cbindgen`-generated `punktfunk_core.h`.
|
||
//! - [`config`] / [`error`] / [`stats`] — session configuration, the shared error/status
|
||
//! vocabulary, and the counters snapshot.
|
||
//! - [`input`] — the wire input-event vocabulary (keyboard/mouse/touch, gamepad snapshots).
|
||
//! - [`reject`] — typed application-close rejection codes · [`reanchor`] — the post-loss
|
||
//! freeze-until-reanchor client gate · [`render_scale`] — the shared render-scale setting ·
|
||
//! [`audio`] — Opus PCM decode for C-ABI embedders · [`wol`] — Wake-on-LAN.
|
||
//! - `quic` (feature `quic`) — the punktfunk/1 control plane: handshake, typed control
|
||
//! messages, pairing (SPAKE2), the datagram plane codecs, and clock sync. With it come
|
||
//! `client` (the embeddable NativeClient worker), `abr` (the adaptive-bitrate
|
||
//! controller), and `clipboard` (the shared-clipboard transport task). `tls`
|
||
//! (feature `tls`) — the pinned-fingerprint certificate verifier.
|
||
//!
|
||
//! ## Threading contract
|
||
//!
|
||
//! Nothing in the per-frame path touches an async runtime. `tokio`/`quinn` are gated
|
||
//! behind the off-by-default `quic` feature and used only for the control plane.
|
||
|
||
// Unsafe-proof program: every `unsafe {}` / `unsafe impl` in this crate carries a `// SAFETY:`
|
||
// proof. The bulk lives in `abi.rs`, whose sites are instances of the ABI contract stated once at
|
||
// the top of that file rather than 141 independent arguments.
|
||
#![deny(clippy::undocumented_unsafe_blocks)]
|
||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||
|
||
pub mod abi;
|
||
#[cfg(feature = "quic")]
|
||
mod abr;
|
||
pub mod audio;
|
||
#[cfg(feature = "quic")]
|
||
pub mod client;
|
||
/// Client-side shared-clipboard transport: the per-session task that runs the fetch-stream accept
|
||
/// loop, drives outbound fetches, and serves inbound ones — surfaced to the embedder as poll
|
||
/// events. Wire codecs live in [`quic`]; the OS pasteboard integration lives in the native client.
|
||
#[cfg(feature = "quic")]
|
||
pub mod clipboard;
|
||
pub mod config;
|
||
pub mod crypto;
|
||
pub mod error;
|
||
pub mod fec;
|
||
pub mod input;
|
||
pub mod packet;
|
||
pub mod phase;
|
||
#[cfg(feature = "quic")]
|
||
pub mod quic;
|
||
pub mod reanchor;
|
||
pub mod reject;
|
||
pub mod render_scale;
|
||
pub mod session;
|
||
pub mod stats;
|
||
#[cfg(feature = "tls")]
|
||
pub mod tls;
|
||
pub mod transport;
|
||
pub mod wol;
|
||
|
||
pub use config::{CompositorPref, Config, FecConfig, FecScheme, Mode, ProtocolPhase, Role};
|
||
pub use error::{PunktfunkError, PunktfunkStatus, Result};
|
||
pub use session::{Frame, Session};
|
||
pub use stats::Stats;
|
||
|
||
/// Bump on any breaking change to the [C ABI](crate::abi). Mirrors
|
||
/// `punktfunk_abi_version()` and is checked by clients before use.
|
||
///
|
||
/// v2: `punktfunk_connect` gained `client_cert_pem`/`client_key_pem` (pairing identities);
|
||
/// added `punktfunk_pair` / `punktfunk_generate_identity` / `punktfunk_connection_request_mode`.
|
||
/// v3: added `punktfunk_wake_on_lan` (Wake-on-LAN magic packet; the host's wake MAC(s) reach
|
||
/// clients out-of-band via the mDNS `mac` TXT record, so no connection is required to wake).
|
||
/// v4: added `punktfunk_probe` (bounded, trust-agnostic, mDNS-independent reachability handshake —
|
||
/// the display-side companion to dial-first, so saved-host "online" pips reflect real reachability).
|
||
/// v5: added `punktfunk_connection_next_rumble2` (rumble pull that also yields the self-terminating
|
||
/// TTL of a v2 envelope; `punktfunk_connection_next_rumble` is unchanged and drops it). Additive —
|
||
/// the wire is backward-compatible (the envelope is a length-tolerant tail on 0xCA), so
|
||
/// [`WIRE_VERSION`] is unchanged.
|
||
/// v6: added the `punktfunk_reanchor_gate_*` surface (post-loss freeze-until-reanchor gate for the
|
||
/// Swift client; Rust embedders use [`reanchor::ReanchorGate`] directly). Additive, client-local —
|
||
/// no wire change, so [`WIRE_VERSION`] is unchanged.
|
||
/// v7: added `punktfunk_connect_ex8` (`status_out` — typed connect-failure reporting, including
|
||
/// the host-rejection block `PUNKTFUNK_STATUS_REJECTED_*` decoded from the host's QUIC
|
||
/// application close) and the `PunktfunkStatus` −20 block itself. Additive — the close codes are
|
||
/// new application-close vocabulary an old peer simply never sends/reads, so [`WIRE_VERSION`] is
|
||
/// unchanged.
|
||
/// v8: added the shared-clipboard client surface — `punktfunk_connection_host_caps` and
|
||
/// `punktfunk_connection_clipboard_{control,offer,fetch,serve,cancel}` +
|
||
/// `punktfunk_connection_next_clipboard`. Additive; the wire grows only backward-compatible control
|
||
/// messages (0x40-0x44) and a new `Welcome::host_caps` bit, so [`WIRE_VERSION`] is unchanged.
|
||
/// v9: `PunktfunkFrame` grew `received_ns` — the reassembly-completion receipt stamp, so
|
||
/// embedders stop stamping receipt at the hand-off pull (which folds the pre-decode queue wait
|
||
/// into apparent network latency). Struct-size change on the frame poll surface = a hard ABI
|
||
/// break for embedders reading `PunktfunkFrame`; nothing on the wire moved, so [`WIRE_VERSION`]
|
||
/// is unchanged.
|
||
/// v10: added `punktfunk_connection_clock_offset_now_ns` — the LIVE (mid-stream re-synced)
|
||
/// clock offset ongoing latency math must use; the connect-time getter stays frozen by
|
||
/// contract. Additive, client-local — no wire change, so [`WIRE_VERSION`] is unchanged.
|
||
/// v11: added `punktfunk_connect_ex9` — `connect_ex8` plus a `client_caps` bitfield
|
||
/// (`PUNKTFUNK_CLIENT_CAP_CURSOR`, later `…_PHASE_LOCK`), which is how a client tells the host it
|
||
/// renders the pointer itself. Additive; the caps ride the existing Hello, so [`WIRE_VERSION`] is
|
||
/// unchanged. (Documented late — the bump shipped without its line here.)
|
||
/// v12: added `punktfunk_connection_set_cursor_render` — the mid-stream cursor-render flip
|
||
/// (design/remote-desktop-sweep.md §8): the client's mouse-model chord tells the host who
|
||
/// renders the pointer. Additive; rides the existing control stream (a new message TYPE, which
|
||
/// pre-§8 hosts ignore), so [`WIRE_VERSION`] is unchanged.
|
||
/// v13: added `punktfunk_connection_send_pen` — the stylus wire plane
|
||
/// (design/pen-tablet-input.md): a client sends `RICH_PEN` sample batches once the host
|
||
/// advertises `HOST_CAP_PEN`. Additive and capability-gated, so [`WIRE_VERSION`] is unchanged.
|
||
/// v14: added `punktfunk_connection_report_phase` + the `PUNKTFUNK_CLIENT_CAP_PHASE_LOCK` mirror
|
||
/// — the phase-locked capture reporter (design/phase-locked-capture.md): a client that advertises
|
||
/// the cap reports its next display latch (already converted to host clock), the panel period, an
|
||
/// uncertainty and the circular arrival-lead statistic the host's controller steers on. Additive;
|
||
/// the wire grows only a new control message (`PhaseReport`, 0x32) an old host never reads and a
|
||
/// strict-prefix append on the 0xCF host-timing tail, so [`WIRE_VERSION`] is unchanged.
|
||
/// v15: versions the shared rumble policy engine's C surface —
|
||
/// `punktfunk_connection_next_rumble_cmd`, `punktfunk_connection_set_rumble_quirks` and the
|
||
/// `PUNKTFUNK_RUMBLE_QUIRK_*` bits. These symbols are NOT new: they landed while this constant
|
||
/// still read 7 and no bump was made, so every core since has exported them while advertising a
|
||
/// version that never promised them. That cannot be corrected retroactively — a shipped binary
|
||
/// says what it says — so v15 is the floor that *guarantees* them: at or above it the surface is
|
||
/// present, below it an embedder must probe for the symbol. Purely a version statement; no code
|
||
/// changed with this bump, and no wire change, so [`WIRE_VERSION`] is unchanged.
|
||
/// v16: added the pad-audio client surface — `punktfunk_connection_next_pad_audio` (the 0xD1
|
||
/// per-gamepad DualSense haptics/speaker plane) + `punktfunk_connection_set_pad_audio_caps` and
|
||
/// the `PUNKTFUNK_CLIENT_CAP_PAD_AUDIO` / `PUNKTFUNK_HOST_CAP_PAD_AUDIO` mirrors. Additive and
|
||
/// capability-gated end to end: the wire grows a new datagram tag (0xD1) an old client never
|
||
/// receives (double-gated caps), a new 0xCD kind (0x06, dropped as unknown by old clients) and
|
||
/// arrival flag bits 8/9 sent only toward a capable host, so [`WIRE_VERSION`] is unchanged.
|
||
pub const ABI_VERSION: u32 = 16;
|
||
|
||
/// The punktfunk/1 **wire** version — what `Hello`/`Welcome` carry and hosts equality-check.
|
||
/// Deliberately its own constant: [`ABI_VERSION`] tracks the embeddable **C surface**
|
||
/// (functions a client links), which can grow without changing a single wire byte — v3's
|
||
/// `punktfunk_wake_on_lan` is client-local, and riding the C-ABI bump onto the wire locked
|
||
/// every new client out of every deployed host ("ABI mismatch: client 3 host 2", observed
|
||
/// live). Bump this ONLY when the handshake/planes actually change incompatibly.
|
||
pub const WIRE_VERSION: u32 = 2;
|