diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 01:12:58 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 01:12:58 +0100 |
| commit | 91d51e9f7382ea01d69b2e21452d4de56fefec8d (patch) | |
| tree | 87113f53f9b30631fc9697ba4ab939cb2b7fa386 | |
| parent | cron/triggers: keep pre-scoping rows manageable (diff) | |
| download | senator-sperma-91d51e9f7382ea01d69b2e21452d4de56fefec8d.tar.gz senator-sperma-91d51e9f7382ea01d69b2e21452d4de56fefec8d.zip | |
recording: reject a zero-length window
| -rw-r--r-- | src/config.rs | 7 | ||||
| -rw-r--r-- | 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 { | |||
| 205 | .parse() | 205 | .parse() |
| 206 | .unwrap_or(true), | 206 | .unwrap_or(true), |
| 207 | recording_window_seconds: env::var("RECORDING_WINDOW_SECONDS") | 207 | recording_window_seconds: env::var("RECORDING_WINDOW_SECONDS") |
| 208 | .unwrap_or_else(|_| "900".into()) | 208 | .ok() |
| 209 | .parse() | 209 | .and_then(|value| value.parse::<u64>().ok()) |
| 210 | // 0 would divide by zero in the mixer; cap the ring so a typo | ||
| 211 | // cannot allocate an unbounded amount of memory. | ||
| 212 | .filter(|seconds| (1..=3600).contains(seconds)) | ||
| 210 | .unwrap_or(900), | 213 | .unwrap_or(900), |
| 211 | copyparty_url: env::var("COPYPARTY_URL") | 214 | copyparty_url: env::var("COPYPARTY_URL") |
| 212 | .unwrap_or_else(|_| "http://localhost:3939".into()), | 215 | .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 { | |||
| 57 | impl Recorder { | 57 | impl Recorder { |
| 58 | pub fn new(sample_rate: u32, window_seconds: u64) -> Self { | 58 | pub fn new(sample_rate: u32, window_seconds: u64) -> Self { |
| 59 | let samples_per_frame = (sample_rate as usize) / TICKS_PER_SEC; | 59 | let samples_per_frame = (sample_rate as usize) / TICKS_PER_SEC; |
| 60 | // Defensive: callers validate config, but a zero-length ring would | ||
| 61 | // panic on `% len` in `mix_tick`. | ||
| 62 | let window_seconds = window_seconds.max(1); | ||
| 60 | let window_samples = (sample_rate as usize) * (window_seconds as usize); | 63 | let window_samples = (sample_rate as usize) * (window_seconds as usize); |
| 61 | // `head` advances by `samples_per_frame` and wraps mod `window_samples`, | 64 | // `head` advances by `samples_per_frame` and wraps mod `window_samples`, |
| 62 | // so the window length must be a whole number of frames. | 65 | // so the window length must be a whole number of frames. |
| @@ -342,6 +345,14 @@ mod tests { | |||
| 342 | } | 345 | } |
| 343 | 346 | ||
| 344 | #[test] | 347 | #[test] |
| 348 | fn zero_window_is_clamped_and_does_not_panic() { | ||
| 349 | let rec = Recorder::new(48_000, 0); | ||
| 350 | let spf = rec.samples_per_frame; | ||
| 351 | rec.mix_tick(&[vec![1i16; spf]]); | ||
| 352 | assert!(!rec.flush().is_empty()); | ||
| 353 | } | ||
| 354 | |||
| 355 | #[test] | ||
| 345 | fn single_speaker_values_survive() { | 356 | fn single_speaker_values_survive() { |
| 346 | let rec = Recorder::new(48_000, 1); | 357 | let rec = Recorder::new(48_000, 1); |
| 347 | let spf = rec.samples_per_frame; | 358 | let spf = rec.samples_per_frame; |
