use crate::pv::link::InvalidSlotNumber;
use crate::pv::SlotCounter;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone)]
pub struct SlotClock {
times: [SystemTime; 48],
last_index: usize,
last_time: SystemTime,
}
const NOMINAL_DURATION_PER_SLOT: Duration = Duration::from_millis(5);
const NOMINAL_DURATION_PER_INDEX: Duration = Duration::from_millis(5 * 1000);
impl SlotClock {
pub fn new(slot_counter: SlotCounter, time: SystemTime) -> Result<Self, InvalidSlotNumber> {
let (index, offset) = Self::index_and_offset(slot_counter)?;
let index_time = time - offset;
let mut table = Self {
times: [index_time; 48],
last_index: index,
last_time: time,
};
let mut index_time = index_time;
let mut i = index;
loop {
i = (47 + i) % 48;
if i == index {
break;
}
index_time -= NOMINAL_DURATION_PER_INDEX;
table.times[i] = index_time;
}
Ok(table)
}
fn index_and_offset(slot_counter: SlotCounter) -> Result<(usize, Duration), InvalidSlotNumber> {
slot_counter.slot_number().map(|n| {
let absolute_slot =
(slot_counter.epoch() as u8 as usize) * 12000 + (u16::from(n)) as usize;
let index = absolute_slot / 1000;
let offset = NOMINAL_DURATION_PER_SLOT * (absolute_slot % 1000) as u32;
(index, offset)
})
}
pub fn set(
&mut self,
slot_counter: SlotCounter,
time: SystemTime,
) -> Result<(), InvalidSlotNumber> {
let (index, offset) = Self::index_and_offset(slot_counter)?;
if self.last_time > time {
log::warn!("time went backwards: {:?} => {:?}", self.last_time, time);
*self = Self::new(slot_counter, time)?;
return Ok(());
} else if self.last_index != index {
let index_time = time - offset;
self.times[index] = index_time;
let mut index_time = index_time;
let mut i = index;
loop {
i = (47 + i) % 48;
if i == self.last_index {
break;
}
index_time -= NOMINAL_DURATION_PER_INDEX;
self.times[i] = index_time;
}
} else {
}
self.last_index = index;
self.last_time = time;
Ok(())
}
pub fn get(&self, slot_counter: SlotCounter) -> Result<SystemTime, InvalidSlotNumber> {
let (index, offset) = Self::index_and_offset(slot_counter)?;
Ok(self.times[index] + offset)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke() {
let x = SystemTime::UNIX_EPOCH + Duration::from_secs(1723500000);
let mut clock = SlotClock::new(SlotCounter::from(0xc000), x).unwrap();
assert_eq!(
clock.get(SlotCounter::from(0x8000)),
Ok(x - Duration::from_secs(60))
);
assert_eq!(
clock.get(SlotCounter::from(0x4000)),
Ok(x - Duration::from_secs(120))
);
assert_eq!(
clock.get(SlotCounter::from(0x0000)),
Ok(x - Duration::from_secs(180))
);
assert_eq!(
clock.get(SlotCounter::from(0xc000 + 1000)),
Ok(x - Duration::from_secs(180 + 55))
);
let later = x + Duration::from_secs(5);
clock.set(SlotCounter::from(0xc000 + 1000), later).unwrap();
assert_eq!(
clock.get(SlotCounter::from(0x8000)),
Ok(x - Duration::from_secs(60))
);
assert_eq!(
clock.get(SlotCounter::from(0x4000)),
Ok(x - Duration::from_secs(120))
);
assert_eq!(
clock.get(SlotCounter::from(0x0000)),
Ok(x - Duration::from_secs(180))
);
assert_eq!(
clock.get(SlotCounter::from(0xc000 + 1000)),
Ok(x + Duration::from_secs(5))
);
}
#[test]
fn index_and_offset() {
assert_eq!(
SlotClock::index_and_offset(SlotCounter::ZERO),
Ok((0, Duration::from_millis(0)))
);
assert_eq!(
SlotClock::index_and_offset(SlotCounter(999.into())),
Ok((0, Duration::from_millis(999 * 5)))
);
assert_eq!(
SlotClock::index_and_offset(SlotCounter(1000.into())),
Ok((1, Duration::from_millis(0)))
);
assert_eq!(
SlotClock::index_and_offset(SlotCounter(1999.into())),
Ok((1, Duration::from_millis(999 * 5)))
);
assert_eq!(
SlotClock::index_and_offset(SlotCounter(2000.into())),
Ok((2, Duration::from_millis(0)))
);
assert_eq!(
SlotClock::index_and_offset(SlotCounter(11999.into())),
Ok((11, Duration::from_millis(999 * 5)))
);
assert_eq!(
SlotClock::index_and_offset(SlotCounter(12000.into())),
Err(InvalidSlotNumber(12000))
);
assert_eq!(
SlotClock::index_and_offset(SlotCounter(0x4000.into())),
Ok((12, Duration::from_millis(0)))
);
assert_eq!(
SlotClock::index_and_offset(SlotCounter((0x4000 + 999).into())),
Ok((12, Duration::from_millis(999 * 5)))
);
assert_eq!(
SlotClock::index_and_offset(SlotCounter((0x4000 + 1000).into())),
Ok((13, Duration::from_millis(0)))
);
}
}