use crate::utils::math::integer_sqrt;
use crate::TightBeamError;
pub trait JitterCalculator: Send + Sync + core::fmt::Debug {
fn calculate(&self, durations: &[u64]) -> Result<u64, TightBeamError>;
}
#[derive(Default, Debug, Clone, Copy)]
pub struct MinMaxJitter;
impl JitterCalculator for MinMaxJitter {
fn calculate(&self, durations: &[u64]) -> Result<u64, TightBeamError> {
if durations.is_empty() {
return Err(TightBeamError::InvalidMetadata);
}
if durations.len() < 2 {
return Ok(0);
}
let min = durations.iter().min().copied().unwrap_or(0);
let max = durations.iter().max().copied().unwrap_or(0);
Ok(max.saturating_sub(min))
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct VarianceJitter;
impl JitterCalculator for VarianceJitter {
fn calculate(&self, durations: &[u64]) -> Result<u64, TightBeamError> {
if durations.is_empty() {
return Err(TightBeamError::InvalidMetadata);
}
if durations.len() < 2 {
return Ok(0);
}
let sum: u64 = durations.iter().sum();
let count = durations.len() as u64;
let mean = sum / count;
let variance_sum: u128 = durations
.iter()
.map(|&d| {
let diff = d.abs_diff(mean);
(diff as u128).saturating_pow(2)
})
.sum();
let variance = (variance_sum / count as u128) as u64;
Ok(variance)
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct StdDevJitter;
impl JitterCalculator for StdDevJitter {
fn calculate(&self, durations: &[u64]) -> Result<u64, TightBeamError> {
if durations.is_empty() {
return Err(TightBeamError::InvalidMetadata);
}
if durations.len() < 2 {
return Ok(0);
}
let sum: u64 = durations.iter().sum();
let count = durations.len() as u64;
let mean = sum / count;
let variance_sum: u128 = durations
.iter()
.map(|&d| {
let diff = d.abs_diff(mean);
(diff as u128).saturating_pow(2)
})
.sum();
let variance = variance_sum / count as u128;
let std_dev = integer_sqrt(variance);
Ok(std_dev as u64)
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct DecorrelatedJitterCalculator;
impl JitterCalculator for DecorrelatedJitterCalculator {
fn calculate(&self, durations: &[u64]) -> Result<u64, TightBeamError> {
if durations.is_empty() {
return Err(TightBeamError::InvalidMetadata);
}
if durations.len() < 2 {
return Ok(0);
}
let max_duration = durations.iter().max().copied().unwrap_or(0);
if max_duration == 0 {
return Ok(0);
}
let min = max_duration / 3;
let range = max_duration.saturating_sub(min);
Ok(range)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_min_max_jitter() {
let calculator = MinMaxJitter;
assert!(matches!(calculator.calculate(&[]), Err(TightBeamError::InvalidMetadata)));
assert_eq!(calculator.calculate(&[100]).unwrap(), 0);
assert_eq!(calculator.calculate(&[100, 200]).unwrap(), 100);
assert_eq!(calculator.calculate(&[50, 100, 150, 200]).unwrap(), 150);
}
#[test]
fn test_variance_jitter() {
let calculator = VarianceJitter;
assert!(matches!(calculator.calculate(&[]), Err(TightBeamError::InvalidMetadata)));
assert_eq!(calculator.calculate(&[100]).unwrap(), 0);
let result = calculator.calculate(&[100, 200]).unwrap();
assert_eq!(result, 2500);
}
#[test]
fn test_std_dev_jitter() {
let calculator = StdDevJitter;
assert!(matches!(calculator.calculate(&[]), Err(TightBeamError::InvalidMetadata)));
assert_eq!(calculator.calculate(&[100]).unwrap(), 0);
let result = calculator.calculate(&[100, 200]).unwrap();
assert_eq!(result, 50);
}
#[test]
fn test_decorrelated_jitter() {
let calculator = DecorrelatedJitterCalculator;
assert!(matches!(calculator.calculate(&[]), Err(TightBeamError::InvalidMetadata)));
assert_eq!(calculator.calculate(&[100]).unwrap(), 0);
assert_eq!(calculator.calculate(&[100, 200, 300]).unwrap(), 200);
assert_eq!(calculator.calculate(&[50, 100, 150]).unwrap(), 100);
}
}