use crate::time::{Duration, Instant};
use crate::{Framework, Machine, TriggerAction, TriggerEvent};
use rand_core::RngCore;
use std::ops::Sub;
use std::time::Instant as StdInstant;
pub struct RateLimitedFramework<M, R, T = StdInstant>
where
M: AsRef<[Machine]>,
R: RngCore,
T: Instant,
T::Duration: Sub<Output = T::Duration>,
{
framework: Framework<M, R, T>,
prev: f64,
current: f64,
tick: T,
}
impl<M, R, T> RateLimitedFramework<M, R, T>
where
M: AsRef<[Machine]>,
R: RngCore,
T: Instant,
T::Duration: Sub<Output = T::Duration>,
{
pub fn new(framework: Framework<M, R, T>) -> Self {
let tick = framework.current_time;
Self {
framework,
prev: 0.0,
current: 0.0,
tick,
}
}
pub fn trigger_events(
&mut self,
events: &[TriggerEvent],
max_actions_per_second: f64,
current_time: T,
) -> impl Iterator<Item = &TriggerAction<T>> {
let window_1s = Duration::from_micros(1_000_000);
#[allow(unused_must_use)]
self.framework.trigger_events(events, current_time);
let delta = current_time.saturating_duration_since(self.tick);
if delta < window_1s {
let rate = (self.prev * (window_1s - delta).div_duration_f64(window_1s)) + self.current;
if rate >= max_actions_per_second {
self.framework.actions.fill(None);
}
} else {
if delta.div_duration_f64(window_1s) < 2.0 {
self.prev = self.current;
} else {
self.prev = 0.0;
}
self.tick = current_time;
self.current = 0.0;
}
self.current += self
.framework
.actions
.iter()
.filter(|a| a.is_some())
.count() as f64;
self.framework
.actions
.iter()
.filter_map(|action| action.as_ref())
}
pub fn framework(&self) -> &Framework<M, R, T> {
&self.framework
}
pub fn framework_mut(&mut self) -> &mut Framework<M, R, T> {
&mut self.framework
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::action::Action;
use crate::dist::{Dist, DistType};
use crate::event::Event;
use crate::state::{State, Trans};
use crate::{Framework, Machine, MachineId, TriggerEvent};
use enum_map::enum_map;
use std::time::Instant as StdInstant;
fn create_test_framework() -> Framework<Vec<Machine>, rand::rngs::ThreadRng, StdInstant> {
let mut state = State::new(enum_map! {
Event::PaddingSent => vec![Trans(0, 1.0)],
_ => vec![],
});
state.action = Some(Action::SendPadding {
bypass: false,
replace: false,
timeout: Dist {
dist: DistType::Uniform {
low: 0.0,
high: 0.0,
},
start: 0.0,
max: 0.0,
},
limit: None,
});
let m = Machine::new(1_000_000, 0.0, 0, 0.0, vec![state]).unwrap();
Framework::new(vec![m], 0.0, 0.0, StdInstant::now(), rand::rng()).unwrap()
}
#[test]
fn test_new() {
let framework = create_test_framework();
let rate_limited = RateLimitedFramework::new(framework);
assert_eq!(rate_limited.prev, 0.0);
assert_eq!(rate_limited.current, 0.0);
}
#[test]
fn test_framework_accessors() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
let _framework_ref = rate_limited.framework();
let _framework_mut_ref = rate_limited.framework_mut();
}
#[test]
fn test_rate_limiting_under_limit() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
let events = [TriggerEvent::PaddingSent {
machine: MachineId::from_raw(0),
}];
let max_rate = 10.0;
let current_time = StdInstant::now();
let _actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
assert_eq!(rate_limited.current, 1.0);
}
#[test]
fn test_rate_limiting_over_limit() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
let events = [TriggerEvent::PaddingSent {
machine: MachineId::from_raw(0),
}];
let max_rate = 0.5;
let current_time = StdInstant::now();
rate_limited.current = 1.0;
let _actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
assert_eq!(rate_limited.current, 1.0);
}
#[test]
fn test_sliding_window_within_current_window() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
rate_limited.prev = 2.0;
rate_limited.current = 1.0;
let events = [TriggerEvent::PaddingSent {
machine: MachineId::from_raw(0),
}];
let max_rate = 2.0;
let current_time = rate_limited.tick + std::time::Duration::from_millis(500);
let _actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
}
#[test]
fn test_sliding_window_next_window() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
rate_limited.current = 5.0;
let original_tick = rate_limited.tick;
let events = [TriggerEvent::PaddingSent {
machine: MachineId::from_raw(0),
}];
let max_rate = 10.0;
let current_time = rate_limited.tick + std::time::Duration::from_millis(1500);
let _actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
assert_eq!(rate_limited.prev, 5.0);
assert_eq!(rate_limited.current, 1.0);
assert!(rate_limited.tick > original_tick);
}
#[test]
fn test_sliding_window_long_duration_reset() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
rate_limited.current = 5.0;
let events = [TriggerEvent::PaddingSent {
machine: MachineId::from_raw(0),
}];
let max_rate = 10.0;
let current_time = rate_limited.tick + std::time::Duration::from_secs(3);
let _actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
assert_eq!(rate_limited.prev, 0.0);
assert_eq!(rate_limited.current, 1.0);
}
#[test]
fn test_multiple_events_increment_current() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
let events = [TriggerEvent::PaddingSent {
machine: MachineId::from_raw(0),
}];
let max_rate = 10.0;
let current_time = StdInstant::now();
rate_limited
.trigger_events(&events, max_rate, current_time)
.count();
rate_limited
.trigger_events(&events, max_rate, current_time)
.count();
rate_limited
.trigger_events(&events, max_rate, current_time)
.count();
assert_eq!(rate_limited.current, 3.0);
}
#[test]
fn test_actions_returned_when_under_rate_limit() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
let events = [TriggerEvent::PaddingSent {
machine: crate::MachineId::from_raw(0),
}];
let max_rate = 10.0;
let current_time = StdInstant::now();
let actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
assert!(!actions.is_empty());
assert_eq!(rate_limited.current, 1.0);
}
#[test]
fn test_actions_blocked_when_over_rate_limit() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
rate_limited.current = 2.0;
let events = [TriggerEvent::PaddingSent {
machine: crate::MachineId::from_raw(0),
}];
let max_rate = 1.0;
let current_time = StdInstant::now();
let actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
assert!(actions.is_empty());
assert_eq!(rate_limited.current, 2.0);
}
#[test]
fn test_rate_limiting_with_sliding_window_calculation() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
rate_limited.prev = 3.0;
rate_limited.current = 1.0;
let events = [TriggerEvent::PaddingSent {
machine: MachineId::from_raw(0),
}];
let max_rate = 2.5;
let current_time = rate_limited.tick + std::time::Duration::from_millis(250);
let actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
assert!(actions.is_empty());
assert_eq!(rate_limited.current, 1.0);
}
#[test]
fn test_repeated_triggers_with_rate_limit_5() {
let framework = create_test_framework();
let mut rate_limited = RateLimitedFramework::new(framework);
let events = [TriggerEvent::PaddingSent {
machine: MachineId::from_raw(0),
}];
let max_rate = 5.0;
let current_time = StdInstant::now();
for i in 1..=5 {
let actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
assert!(!actions.is_empty(), "Expected actions on iteration {}", i);
assert_eq!(rate_limited.current, i as f64);
}
let actions: Vec<_> = rate_limited
.trigger_events(&events, max_rate, current_time)
.collect();
assert!(actions.is_empty(), "Expected no actions when over limit");
assert_eq!(rate_limited.current, 5.0);
}
}