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 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 15:40:05 -04:00
parent 035aa4b256
commit 32131b0ccb
+33 -1
View File
@@ -65,9 +65,41 @@ pub fn measure_upstream_blocking() -> Result<Measurement> {
/// 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);
}
}