diff --git a/scripts/ci/hygiene_gate_baseline.json b/scripts/ci/hygiene_gate_baseline.json index a13220d..08621f9 100644 --- a/scripts/ci/hygiene_gate_baseline.json +++ b/scripts/ci/hygiene_gate_baseline.json @@ -903,12 +903,12 @@ "server/src/bin/lesavka_uvc/coverage_model.rs": { "clippy_warnings": 0, "doc_debt": 0, - "loc": 141 + "loc": 286 }, "server/src/bin/lesavka_uvc/coverage_startup.rs": { "clippy_warnings": 0, - "doc_debt": 5, - "loc": 229 + "doc_debt": 0, + "loc": 447 }, "server/src/bin/lesavka_uvc/payload_limits.rs": { "clippy_warnings": 0, @@ -1078,7 +1078,7 @@ "server/src/main/relay_service_coverage/relay_trait_impl.rs": { "clippy_warnings": 0, "doc_debt": 0, - "loc": 372 + "loc": 373 }, "server/src/main/relay_service_tests.rs": { "clippy_warnings": 0, diff --git a/server/src/bin/lesavka_uvc/coverage_model.rs b/server/src/bin/lesavka_uvc/coverage_model.rs index d6a7cba..bba353d 100644 --- a/server/src/bin/lesavka_uvc/coverage_model.rs +++ b/server/src/bin/lesavka_uvc/coverage_model.rs @@ -193,6 +193,11 @@ struct UvcVideoStats { #[cfg(coverage)] impl UvcVideoStream { + /// Builds an inert coverage-mode stream seeded with the idle JPEG frame. + /// + /// Inputs: the already-open UVC file descriptor, currently unused in this + /// harness. Output: stream state that mirrors production defaults. Why: + /// coverage tests need the stream bookkeeping without touching hardware. fn new(_fd: i32) -> Self { Self { buffers: Vec::new(), @@ -207,6 +212,11 @@ impl UvcVideoStream { } } + /// Reloads the latest spooled MJPEG frame when it is fresh and safe to use. + /// + /// Inputs: the configured frame path and current buffer limits. Output: + /// updated cached frame plus rejection counters. Why: stale or oversized + /// frames should degrade to known-safe idle content instead of freezing RCT. fn refresh_latest_frame(&mut self) { let stale = frame_spool_is_stale(&self.frame_path, frame_spool_max_age()); if stale && looks_like_mjpeg_frame(&self.latest_frame) { @@ -246,6 +256,10 @@ impl UvcVideoStream { .min(self.frame_max_bytes) } + /// Chooses the safest frame payload for the active UVC buffer. + /// + /// Inputs: one buffer length. Output: current, idle, or minimal JPEG bytes. + /// Why: the gadget must always queue valid MJPEG, even with tiny buffers. fn frame_for_buffer(&self, buffer_len: usize) -> &[u8] { if self.latest_frame.len() <= buffer_len && looks_like_mjpeg_frame(&self.latest_frame) { &self.latest_frame diff --git a/server/src/bin/lesavka_uvc/coverage_startup.rs b/server/src/bin/lesavka_uvc/coverage_startup.rs index ce86eff..a118a38 100644 --- a/server/src/bin/lesavka_uvc/coverage_startup.rs +++ b/server/src/bin/lesavka_uvc/coverage_startup.rs @@ -1,3 +1,7 @@ +/// Runs only the coverage-mode startup probes and refuses the live control loop. +/// +/// Inputs: CLI/env device settings. Output: successful validation until the +/// disabled loop guard. Why: contracts need startup coverage without driving USB. fn main() -> Result<()> { let (dev, _cfg) = parse_args()?; let _ = load_interfaces(); @@ -18,6 +22,10 @@ fn parse_args() -> Result<(String, UvcConfig)> { } #[cfg(coverage)] +/// Finds the UVC device from `--device`, `-d`, or a positional path argument. +/// +/// Inputs: raw CLI arguments. Output: the selected device path when present. +/// Why: manual probes and contract tests both exercise the same device syntax. fn parse_device_arg(args: &[String]) -> Option { args .windows(2) @@ -34,6 +42,11 @@ fn parse_device_arg(args: &[String]) -> Option { #[cfg(coverage)] impl UvcConfig { + /// Builds a bounded UVC mode from environment overrides. + /// + /// Inputs: `LESAVKA_UVC_*` mode variables. Output: a packet-safe mode + /// snapshot. Why: coverage contracts must pin descriptor math without + /// requiring the real kernel gadget. fn from_env() -> Self { let width = env_u32("LESAVKA_UVC_WIDTH", 1280); let height = env_u32("LESAVKA_UVC_HEIGHT", 720); @@ -72,6 +85,10 @@ impl UvcConfig { #[cfg(coverage)] impl UvcState { + /// Creates the control-state mirrors used by UVC request handling tests. + /// + /// Inputs: one validated UVC mode. Output: default/probe/commit buffers. + /// Why: tests need deterministic state transitions for host negotiation. fn new(cfg: UvcConfig) -> Self { let ctrl_len = stream_ctrl_len(); let default = build_streaming_control(&cfg, ctrl_len); @@ -101,6 +118,10 @@ fn read_interface(path: &str) -> Option { } #[cfg(coverage)] +/// Opens the UVC control device with safe coverage defaults. +/// +/// Inputs: device path and read/write env knobs. Output: opened file handle. +/// Why: tests should default to non-blocking read-only access unless opted in. fn open_with_retry(path: &str) -> Result { let read_only = uvc_control_read_only(); let mut opts = OpenOptions::new(); @@ -202,6 +223,10 @@ fn derived_uvc_frame_max_bytes_for_transport(fps: u32, max_packet: u32, bulk: bo } #[cfg(coverage)] +/// Returns the effective MJPEG byte budget for the selected USB transport. +/// +/// Inputs: negotiated max packet size and bulk/isochronous mode. Output: bytes +/// per second. Why: frame-size checks should mirror the transport budget. fn effective_uvc_mjpeg_budget_bytes_per_sec(max_packet: u32, bulk: bool) -> u32 { let configured = env_u32( "LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC", @@ -218,6 +243,10 @@ fn effective_uvc_mjpeg_budget_bytes_per_sec(max_packet: u32, bulk: bool) -> u32 } #[cfg(coverage)] +/// Computes the high-speed isochronous payload budget. +/// +/// Inputs: endpoint packet size and `LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT`. +/// Output: capped bytes per second. Why: coverage locks the safety margin. fn uvc_isochronous_budget_bytes_per_sec(max_packet: u32) -> u32 { let pct = env_u32( "LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT", @@ -232,6 +261,10 @@ fn uvc_isochronous_budget_bytes_per_sec(max_packet: u32) -> u32 { } #[cfg(coverage)] +/// Parses a boolean environment flag with a default fallback. +/// +/// Inputs: variable name and default value. Output: parsed boolean. Why: +/// UVC safety switches must handle common true/false spellings consistently. fn env_flag_enabled(name: &str, default: bool) -> bool { env::var(name) .ok() @@ -262,6 +295,10 @@ fn uvc_frame_size_guard_enabled() -> bool { } #[cfg(coverage)] +/// Decides whether the coverage helper may use bulk transport. +/// +/// Inputs: env override plus configfs capabilities. Output: effective mode. +/// Why: tests should not advertise bulk if the gadget tree lacks support. fn uvc_bulk_transfer_enabled() -> bool { if !env_flag_enabled("LESAVKA_UVC_BULK", true) { return false; @@ -290,6 +327,10 @@ fn uvc_frame_size_for_active_mode( } #[cfg(coverage)] +/// Returns the optional JSON stats path for the UVC coverage helper. +/// +/// Inputs: `LESAVKA_UVC_STATS_PATH`. Output: path or disabled state. Why: +/// tests and probes need a cheap health artifact without forcing writes. fn uvc_stats_path() -> Option { match std::env::var("LESAVKA_UVC_STATS_PATH") { Ok(value) => { @@ -305,6 +346,10 @@ fn uvc_stats_path() -> Option { } #[cfg(coverage)] +/// Writes a text artifact through a temporary file and atomic rename. +/// +/// Inputs: target path and payload. Output: persisted file. Why: readers should +/// never observe partially-written UVC stats JSON. fn write_atomic_text(path: &std::path::Path, text: &str) -> Result<()> { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent)?; @@ -316,6 +361,10 @@ fn write_atomic_text(path: &std::path::Path, text: &str) -> Result<()> { } #[cfg(coverage)] +/// Serializes the compact UVC video-stat snapshot consumed by probes. +/// +/// Inputs: current counters and frame cap. Output: one JSON object string. Why: +/// shell probes need stable fields without depending on Rust serialization. fn uvc_stats_snapshot_json(stats: &UvcVideoStats, frame_cap: usize) -> String { format!( "{{\"queued\":{},\"reloaded\":{},\"stale_replay\":{},\"rejected_oversize\":{},\"rejected_invalid\":{},\"fallback_idle\":{},\"latest_bytes\":{},\"frame_cap\":{},\"last_rejected_oversize_bytes\":{},\"last_rejected_oversize_cap\":{},\"paced_sleeps\":{},\"paced_sleep_ms\":{}}}\n", @@ -335,6 +384,10 @@ fn uvc_stats_snapshot_json(stats: &UvcVideoStats, frame_cap: usize) -> String { } #[cfg(coverage)] +/// Returns the optional periodic stats write interval. +/// +/// Inputs: `LESAVKA_UVC_STATS_INTERVAL_MS`. Output: duration or disabled state. +/// Why: probes can ask for telemetry without slowing every coverage test. fn uvc_stats_interval() -> Option { match env_u64("LESAVKA_UVC_STATS_INTERVAL_MS", DEFAULT_UVC_STATS_INTERVAL_MS) { 0 => None,