From 32131b0ccb62a343f6436950727464a8addde633 Mon Sep 17 00:00:00 2001 From: Mollusk Date: Thu, 28 May 2026 15:40:05 -0400 Subject: [PATCH] fix(bandwidth): floor recommended viewers to 1 on non-finite input recommended_max_viewers() promises "at least 1", but a NaN safe_mbps cast to 0 and an infinite one to u32::MAX. Guard non-finite / non-positive inputs up front. Add unit tests covering the normal path, the floor, and the NaN/Inf/negative degenerate cases. Co-Authored-By: Claude Opus 4.8 --- src/common/bandwidth.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/common/bandwidth.rs b/src/common/bandwidth.rs index 5fcc080..afc573d 100644 --- a/src/common/bandwidth.rs +++ b/src/common/bandwidth.rs @@ -65,9 +65,41 @@ pub fn measure_upstream_blocking() -> Result { /// via SAFETY_FACTOR) into a recommended viewer count. Floors to at least 1. pub fn recommended_max_viewers(safe_mbps: f64, bitrate_kbps: u32) -> u32 { let per_viewer_mbps = (bitrate_kbps as f64) / 1000.0; - if per_viewer_mbps <= 0.0 { + // Guard non-finite / non-positive inputs (only reachable from a corrupted + // config): a NaN safe_mbps would cast to 0 and an infinite one to u32::MAX, + // both of which break the "at least 1" contract. + if !safe_mbps.is_finite() || safe_mbps <= 0.0 || per_viewer_mbps <= 0.0 { return 1; } let n = (safe_mbps / per_viewer_mbps).floor(); if n < 1.0 { 1 } else { n as u32 } } + +#[cfg(test)] +mod tests { + use super::recommended_max_viewers; + + #[test] + fn divides_bandwidth_by_per_viewer_bitrate() { + // 8 Mbps safe / 2 Mbps each = 4 viewers. + assert_eq!(recommended_max_viewers(8.0, 2000), 4); + // Floors the fractional part: 7.9 / 2 = 3.95 -> 3. + assert_eq!(recommended_max_viewers(7.9, 2000), 3); + } + + #[test] + fn floors_to_at_least_one() { + // Not even enough for one viewer still allows one (best effort). + assert_eq!(recommended_max_viewers(0.5, 2000), 1); + // Zero / unknown bitrate can't size a budget; floor to one. + assert_eq!(recommended_max_viewers(8.0, 0), 1); + } + + #[test] + fn degenerate_inputs_floor_to_one() { + // A corrupted config must not yield 0 (NaN) or u32::MAX (Inf). + assert_eq!(recommended_max_viewers(f64::NAN, 2000), 1); + assert_eq!(recommended_max_viewers(f64::INFINITY, 2000), 1); + assert_eq!(recommended_max_viewers(-5.0, 2000), 1); + } +}