From 91d51e9f7382ea01d69b2e21452d4de56fefec8d Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 01:12:58 +0100 Subject: recording: reject a zero-length window --- src/config.rs | 7 +++++-- src/recording/mod.rs | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 95c1a18..e55226c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -205,8 +205,11 @@ impl Config { .parse() .unwrap_or(true), recording_window_seconds: env::var("RECORDING_WINDOW_SECONDS") - .unwrap_or_else(|_| "900".into()) - .parse() + .ok() + .and_then(|value| value.parse::().ok()) + // 0 would divide by zero in the mixer; cap the ring so a typo + // cannot allocate an unbounded amount of memory. + .filter(|seconds| (1..=3600).contains(seconds)) .unwrap_or(900), copyparty_url: env::var("COPYPARTY_URL") .unwrap_or_else(|_| "http://localhost:3939".into()), diff --git a/src/recording/mod.rs b/src/recording/mod.rs index 4cd2891..e03df32 100644 --- a/src/recording/mod.rs +++ b/src/recording/mod.rs @@ -57,6 +57,9 @@ struct Inner { impl Recorder { pub fn new(sample_rate: u32, window_seconds: u64) -> Self { let samples_per_frame = (sample_rate as usize) / TICKS_PER_SEC; + // Defensive: callers validate config, but a zero-length ring would + // panic on `% len` in `mix_tick`. + let window_seconds = window_seconds.max(1); let window_samples = (sample_rate as usize) * (window_seconds as usize); // `head` advances by `samples_per_frame` and wraps mod `window_samples`, // so the window length must be a whole number of frames. @@ -341,6 +344,14 @@ mod tests { assert!(rec.flush().is_empty()); } + #[test] + fn zero_window_is_clamped_and_does_not_panic() { + let rec = Recorder::new(48_000, 0); + let spf = rec.samples_per_frame; + rec.mix_tick(&[vec![1i16; spf]]); + assert!(!rec.flush().is_empty()); + } + #[test] fn single_speaker_values_survive() { let rec = Recorder::new(48_000, 1); -- cgit v1.2.3