Turn the 1446-line packet.rs into a packet/ directory module (mod.rs facade + header/packetize/reassemble/tests) behind glob re-exports, so every crate::packet::X path stays byte-stable. Pure move: the header consts + PacketHeader -> header.rs; Packetizer -> packetize.rs; the Reassembler cluster (kept WHOLE -- disjoint-borrow hot path) + loss-window consts -> reassemble.rs; the inline #[cfg(test)] block -> tests.rs. Sole visibility change: LOSS_WINDOW_NS -> pub(super) (a test imports it). No behavior change. Verified on both platforms from a clean HEAD snapshot: Linux clippy (--features quic and --no-default-features, --all-targets -D warnings) + full cargo test; Windows clippy (both feature sets) + cargo test --lib (156 pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
87 lines
4.7 KiB
Rust
87 lines
4.7 KiB
Rust
//! Wire packet header: the fixed [`PacketHeader`] + the flag/geometry consts every
|
|
//! packet carries. Zero-copy (de)serializable; 40 bytes, unpadded.
|
|
|
|
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
|
|
|
/// Identifies a punktfunk video packet (vs. an input datagram, see [`crate::input`]).
|
|
pub const PUNKTFUNK_MAGIC: u8 = 0xC9;
|
|
|
|
// Frame flags (mirroring GameStream's FLAG_*).
|
|
pub const FLAG_PIC: u8 = 0x1;
|
|
pub const FLAG_EOF: u8 = 0x2;
|
|
pub const FLAG_SOF: u8 = 0x4;
|
|
/// Bandwidth-probe filler, not decodable video: a [`crate::quic::ProbeRequest`] speed test makes
|
|
/// the host burst access units carrying this flag so the client measures throughput/loss without
|
|
/// feeding them to the decoder. Punktfunk/1 only (GameStream never sets it).
|
|
pub const FLAG_PROBE: u8 = 0x8;
|
|
|
|
/// Application `user_flags` bit (the u32 [`PacketHeader::user_flags`] word, surfaced to the client
|
|
/// as [`crate::session::Frame::flags`]) — NOT a transport packet flag. Marks the access unit that
|
|
/// **completes an intra-refresh wave**: the picture is loss-free from here even though the frame is
|
|
/// a coded `P` (no IDR, so the decoder never sets `AV_FRAME_FLAG_KEY`). The client lifts its
|
|
/// post-loss display freeze on this bit as well as on a real keyframe — the only bitstream-invisible
|
|
/// clean point it can honor without forcing a full IDR. Lives above the low nibble because the host
|
|
/// reuses `FLAG_PIC`/`FLAG_SOF`/`FLAG_PROBE` bit values inside `user_flags`; `0x10` clears all four.
|
|
pub const USER_FLAG_RECOVERY_POINT: u32 = 0x10;
|
|
|
|
/// Application `user_flags` bit — a **definitive single-frame clean re-anchor**. Unlike
|
|
/// [`USER_FLAG_RECOVERY_POINT`] (an intra-refresh wave boundary, where the first boundary after a loss
|
|
/// is only half-healed so the client waits for the second), this marks an access unit the host coded
|
|
/// to reference a **known-good** picture on purpose — an AMD **LTR reference-frame-invalidation**
|
|
/// recovery frame (`ForceLTRReferenceBitfield`): a clean P-frame off a long-term reference the client
|
|
/// already has, not an IDR. The picture is loss-free the instant this AU decodes, so the client lifts
|
|
/// its post-loss freeze on the **first** such mark. Coded `P` (no IDR), so the decoder never sets
|
|
/// `AV_FRAME_FLAG_KEY` — this host flag is the only signal.
|
|
pub const USER_FLAG_RECOVERY_ANCHOR: u32 = 0x20;
|
|
|
|
/// `user_flags` bit: the AU's content is **shard-aligned self-delimiting chunks** — every
|
|
/// `shard_payload`-sized window of the frame buffer starts a fresh codec packet, padded to the
|
|
/// window with zeros (PyroWave datagram-aligned mode, design/pyrowave-codec-plan.md §4.4). Two
|
|
/// consequences: a receiver that opted into partial delivery can use an aged-out frame's buffer
|
|
/// AS-IS (missing shards stay zeroed; the codec's block walk skips zero windows), and even a
|
|
/// COMPLETE frame must be consumed window-by-window (the padding is not part of the stream).
|
|
pub const USER_FLAG_CHUNK_ALIGNED: u32 = 0x40;
|
|
|
|
/// Widest lost-frame range (frames, wrapping `last - first`) a reference-frame-invalidation
|
|
/// recovery may be asked to repair; anything wider goes straight to the keyframe path on BOTH
|
|
/// ends. RFI can only re-reference history the encoder still holds — NVENC keeps a 5-frame DPB,
|
|
/// AMD LTR ~1 s of marks — and a genuine loss this wide (>1 s even at 240 fps) has no valid
|
|
/// reference anywhere, so an RFI request for it is either hopeless or (worse) a phantom range
|
|
/// from a desynced counter. Shared by the host's RFI dispatch (range → keyframe fallback) and the
|
|
/// client-side gap detectors (huge gap → resync + keyframe request, no RFI).
|
|
pub const RFI_MAX_RANGE: u32 = 256;
|
|
|
|
/// Crypto framing overhead [`Session`](crate::session::Session) adds when encrypting:
|
|
/// an 8-byte sequence prefix plus the GCM tag.
|
|
pub const CRYPTO_OVERHEAD: usize = 8 + crate::crypto::TAG_LEN;
|
|
|
|
/// Largest UDP datagram the core will send or accept. `Config::validate` bounds
|
|
/// `shard_payload` so `HEADER_LEN + shard_payload + CRYPTO_OVERHEAD ≤ MAX_DATAGRAM_BYTES`.
|
|
pub const MAX_DATAGRAM_BYTES: usize = 2048;
|
|
|
|
/// Fixed per-packet header. `#[repr(C)]`, no padding, zero-copy (de)serializable.
|
|
#[repr(C)]
|
|
#[derive(Clone, Copy, Debug, FromBytes, IntoBytes, KnownLayout, Immutable)]
|
|
pub struct PacketHeader {
|
|
pub pts_ns: u64,
|
|
pub frame_index: u32,
|
|
pub stream_seq: u32,
|
|
pub frame_bytes: u32,
|
|
pub user_flags: u32,
|
|
pub block_index: u16,
|
|
pub block_count: u16,
|
|
pub data_shards: u16,
|
|
pub recovery_shards: u16,
|
|
pub shard_index: u16,
|
|
pub shard_bytes: u16,
|
|
pub magic: u8,
|
|
pub version: u8,
|
|
pub fec_scheme: u8,
|
|
pub flags: u8,
|
|
}
|
|
|
|
/// Size of [`PacketHeader`] on the wire (40 bytes).
|
|
pub const HEADER_LEN: usize = std::mem::size_of::<PacketHeader>();
|
|
|
|
const _: () = assert!(HEADER_LEN == 40, "PacketHeader must be 40 bytes / unpadded");
|