use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::time::{Duration, Instant};
use crate::utils::gen_ping_id;
use crate::time::*;
#[derive(Clone, Debug)]
pub struct RequestQueue<T> {
ping_map: HashMap<u64, (Instant, T)>,
timeout: Duration,
}
impl<T> RequestQueue<T> {
pub fn new(timeout: Duration) -> Self {
RequestQueue {
ping_map: HashMap::new(),
timeout,
}
}
fn generate_ping_id(&self) -> u64 {
loop {
let ping_id = gen_ping_id();
if !self.ping_map.contains_key(&ping_id) {
return ping_id;
}
}
}
pub fn new_ping_id(&mut self, data: T) -> u64 {
let ping_id = self.generate_ping_id();
self.ping_map.insert(ping_id, (clock_now(), data));
ping_id
}
pub fn check_ping_id<F: FnOnce(&T) -> bool>(&mut self, ping_id: u64, cond: F) -> Option<T> {
if ping_id == 0 {
return None;
}
if let Entry::Occupied(entry) = self.ping_map.entry(ping_id) {
let (time, data) = entry.get();
if clock_elapsed(*time) <= self.timeout && cond(data) {
let (_ping_id, (_time, data)) = entry.remove_entry();
Some(data)
} else {
None
}
} else {
None
}
}
pub fn clear_timed_out(&mut self) {
let timeout = self.timeout;
self.ping_map.retain(|&_, &mut (time, _)|
clock_elapsed(time) <= timeout
);
}
pub fn get_values(&self) -> impl Iterator<Item = (Instant, &T)> {
self.ping_map
.values()
.filter(move |(time, _)| clock_elapsed(*time) <= self.timeout)
.map(|(time, data)| (*time, data))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tox_crypto::*;
#[test]
fn insert_new_ping_id() {
crypto_init().unwrap();
let mut queue = RequestQueue::new(Duration::from_secs(42));
let ping_id = queue.new_ping_id(7);
assert_eq!(queue.ping_map[&ping_id].1, 7);
}
#[test]
fn check_ping_id() {
crypto_init().unwrap();
let mut queue = RequestQueue::new(Duration::from_secs(42));
let ping_id = queue.new_ping_id(7);
assert_eq!(queue.check_ping_id(ping_id, |&data| data == 6), None);
assert_eq!(queue.check_ping_id(ping_id, |&data| data == 7), Some(7));
assert_eq!(queue.check_ping_id(ping_id, |&data| data == 7), None);
}
#[test]
fn check_ping_id_zero() {
let mut queue = RequestQueue::<()>::new(Duration::from_secs(42));
assert_eq!(queue.check_ping_id(0, |_| true), None);
}
#[test]
fn check_ping_id_nonexistent() {
crypto_init().unwrap();
let mut queue = RequestQueue::new(Duration::from_secs(42));
let ping_id = queue.new_ping_id(());
assert_eq!(queue.check_ping_id(ping_id.overflowing_add(1).0, |_| true), None);
assert_eq!(queue.check_ping_id(ping_id.overflowing_sub(1).0, |_| true), None);
}
#[tokio::test]
async fn check_ping_id_timed_out() {
crypto_init().unwrap();
let mut queue = RequestQueue::new(Duration::from_secs(42));
let ping_id = queue.new_ping_id(());
tokio::time::pause();
let now = clock_now();
let time = queue.ping_map[&ping_id].0 + Duration::from_secs(43);
tokio::time::advance(time - now).await;
assert_eq!(queue.check_ping_id(ping_id, |_| true), None);
}
#[tokio::test]
async fn clear_timed_out() {
crypto_init().unwrap();
let mut queue = RequestQueue::new(Duration::from_secs(42));
let ping_id_1 = queue.new_ping_id(());
tokio::time::pause();
let now = clock_now();
let time = queue.ping_map[&ping_id_1].0;
tokio::time::advance((time + Duration::from_secs(21)) - now).await;
let ping_id_2 = queue.new_ping_id(());
tokio::time::advance(Duration::from_secs(43 - 21)).await;
queue.clear_timed_out();
assert!(!queue.ping_map.contains_key(&ping_id_1));
assert!(queue.ping_map.contains_key(&ping_id_2));
}
#[test]
fn get_values() {
crypto_init().unwrap();
let mut queue = RequestQueue::new(Duration::from_secs(42));
let _ping_id_1 = queue.new_ping_id(1);
let _ping_id_2 = queue.new_ping_id(2);
let _ping_id_3 = queue.new_ping_id(3);
let values = queue.get_values().map(|(_, &data)| data).collect::<Vec<_>>();
assert!(values.contains(&1));
assert!(values.contains(&2));
assert!(values.contains(&3));
assert!(!values.contains(&4));
}
}