aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 01:12:58 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 01:12:58 +0100
commit91d51e9f7382ea01d69b2e21452d4de56fefec8d (patch)
tree87113f53f9b30631fc9697ba4ab939cb2b7fa386
parentcron/triggers: keep pre-scoping rows manageable (diff)
downloadsenator-sperma-91d51e9f7382ea01d69b2e21452d4de56fefec8d.tar.gz
senator-sperma-91d51e9f7382ea01d69b2e21452d4de56fefec8d.zip
recording: reject a zero-length window
-rw-r--r--src/config.rs7
-rw-r--r--src/recording/mod.rs11
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 {
57impl Recorder { 57impl 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;