From f33f59ad6887e061457b16935f51ac83b043e131 Mon Sep 17 00:00:00 2001 From: kingchenc Date: Fri, 22 May 2026 12:21:25 +0200 Subject: [PATCH] C9: saturate Timeframe::floor instead of overflowing at i64::MIN Timeframe::floor computed `ts - ts.rem_euclid(bucket)`. For a timestamp within one bucket of i64::MIN the subtrahend is a positive remainder and the true boundary lies below i64::MIN, so the subtraction overflowed and panicked in debug builds. Switch to saturating_sub: the result clamps to i64::MIN in that practically unreachable case and stays exact everywhere else. floor keeps its infallible `-> i64` signature, so neither push path changes. --- crates/wickra-data/src/aggregator.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/wickra-data/src/aggregator.rs b/crates/wickra-data/src/aggregator.rs index ac20e764..321af6da 100644 --- a/crates/wickra-data/src/aggregator.rs +++ b/crates/wickra-data/src/aggregator.rs @@ -48,8 +48,14 @@ impl Timeframe { } /// Floor a raw timestamp to this timeframe's bucket boundary. + /// + /// For a timestamp within one bucket of [`i64::MIN`] the mathematically + /// exact boundary lies below `i64::MIN` and cannot be represented; in that + /// (practically unreachable) case the result saturates at `i64::MIN` + /// rather than overflowing and panicking in debug builds. `bucket` is + /// always positive, so `rem_euclid` itself cannot panic. pub fn floor(self, ts: i64) -> i64 { - ts - ts.rem_euclid(self.bucket) + ts.saturating_sub(ts.rem_euclid(self.bucket)) } } @@ -238,6 +244,20 @@ mod tests { assert_eq!(tf.floor(100), 100); assert_eq!(tf.floor(150), 100); assert_eq!(tf.floor(250), 200); + // Negative timestamps still floor toward negative infinity. + assert_eq!(tf.floor(-1), -100); + assert_eq!(tf.floor(-100), -100); + assert_eq!(tf.floor(-101), -200); + } + + #[test] + fn floor_saturates_instead_of_overflowing_at_min() { + let tf = Timeframe::new(100).unwrap(); + // The exact boundary lies below i64::MIN — must not panic. + assert_eq!(tf.floor(i64::MIN), i64::MIN); + // i64::MAX must not overflow either (subtracting a non-negative). + let hi = tf.floor(i64::MAX); + assert!(hi > i64::MAX - 100 && hi % 100 == 0); } #[test]