use core::convert::Infallible;
use core::fmt;
use core::future::Future;
use proxima_primitives::pipe::primitives::Pipe;
use crate::seq_u64s::SeqU64s;
use crate::ticks::Ticks;
use crate::unix_nanos::UnixNanos;
pub struct AnchorCell {
pair: SeqU64s<2>,
}
impl AnchorCell {
#[must_use]
pub fn new(ticks: Ticks, unix_nanos: UnixNanos) -> Self {
Self {
pair: SeqU64s::new([ticks.as_raw(), unix_nanos.as_nanos()]),
}
}
pub fn set(&self, ticks: Ticks, unix_nanos: UnixNanos) {
self.pair.store([ticks.as_raw(), unix_nanos.as_nanos()]);
}
#[must_use]
pub fn get(&self) -> (Ticks, UnixNanos) {
let [ticks, unix_nanos] = self.pair.load();
(Ticks::from_raw(ticks), UnixNanos::from_nanos(unix_nanos))
}
}
impl fmt::Debug for AnchorCell {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let (ticks, unix_nanos) = self.get();
formatter
.debug_struct("AnchorCell")
.field("ticks", &ticks)
.field("unix_nanos", &unix_nanos)
.finish()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ToUnixNanos<'anchor> {
anchor: &'anchor AnchorCell,
frequency_hz: u64,
}
impl<'anchor> ToUnixNanos<'anchor> {
#[must_use]
pub const fn new(anchor: &'anchor AnchorCell, frequency_hz: u64) -> Self {
Self {
anchor,
frequency_hz,
}
}
}
impl Pipe for ToUnixNanos<'_> {
type In = Ticks;
type Out = UnixNanos;
type Err = Infallible;
fn call(&self, ticks: Ticks) -> impl Future<Output = Result<UnixNanos, Infallible>> {
let (anchor_ticks, anchor_unix_nanos) = self.anchor.get();
let delta_ticks = ticks.wrapping_sub(anchor_ticks);
let frequency_hz = u128::from(self.frequency_hz.max(1));
let delta_nanos = u128::from(delta_ticks) * 1_000_000_000 / frequency_hz;
let unix_nanos = u128::from(anchor_unix_nanos.as_nanos()).saturating_add(delta_nanos);
let unix_nanos = UnixNanos::from_nanos(u64::try_from(unix_nanos).unwrap_or(u64::MAX));
async move { Ok(unix_nanos) }
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::{AnchorCell, ToUnixNanos};
use crate::ticks::Ticks;
use crate::unix_nanos::UnixNanos;
use proxima_primitives::block_on;
use proxima_primitives::pipe::primitives::Pipe;
const ARM_GENERIC_TIMER_HZ: u64 = 24_000_000;
const PTP_HARDWARE_CLOCK_HZ: u64 = 100_000_000;
const TSC_NOMINAL_HZ: u64 = 1_000_000_000;
const PRIME_WHEEL_HZ: u64 = 1_000;
fn convert(anchor: &AnchorCell, frequency_hz: u64, ticks: Ticks) -> UnixNanos {
block_on(Pipe::call(&ToUnixNanos::new(anchor, frequency_hz), ticks))
.expect("conversion never fails")
}
#[test]
fn at_the_anchor_point_wall_clock_equals_the_anchor() {
let anchor = AnchorCell::new(
Ticks::from_raw(0),
UnixNanos::from_nanos(1_753_500_000_000_000_000),
);
let now = convert(&anchor, ARM_GENERIC_TIMER_HZ, Ticks::from_raw(0));
assert_eq!(now, UnixNanos::from_nanos(1_753_500_000_000_000_000));
}
#[test]
fn arm_generic_timer_24mhz_one_second_of_ticks_advances_one_second() {
let anchor = AnchorCell::new(Ticks::from_raw(0), UnixNanos::from_nanos(0));
let one_second_later = convert(
&anchor,
ARM_GENERIC_TIMER_HZ,
Ticks::from_raw(ARM_GENERIC_TIMER_HZ),
);
assert_eq!(one_second_later, UnixNanos::from_nanos(1_000_000_000));
}
#[test]
fn arm_generic_timer_24mhz_rounding_is_exercised_not_hidden() {
let anchor = AnchorCell::new(Ticks::from_raw(0), UnixNanos::from_nanos(0));
let one_tick_later = convert(&anchor, ARM_GENERIC_TIMER_HZ, Ticks::from_raw(1));
assert_eq!(one_tick_later, UnixNanos::from_nanos(41));
}
#[test]
fn ptp_hardware_clock_100mhz_one_millisecond_of_ticks() {
let anchor = AnchorCell::new(Ticks::from_raw(0), UnixNanos::from_nanos(0));
let one_millisecond_later =
convert(&anchor, PTP_HARDWARE_CLOCK_HZ, Ticks::from_raw(100_000));
assert_eq!(one_millisecond_later, UnixNanos::from_nanos(1_000_000));
}
#[test]
fn tsc_nominal_1ghz_ticks_equal_nanos_1to1() {
let anchor = AnchorCell::new(Ticks::from_raw(0), UnixNanos::from_nanos(0));
let converted = convert(&anchor, TSC_NOMINAL_HZ, Ticks::from_raw(1_234_567));
assert_eq!(converted, UnixNanos::from_nanos(1_234_567));
}
#[test]
fn prime_wheel_1khz_matches_the_wheels_millisecond_tick() {
let anchor = AnchorCell::new(Ticks::from_raw(0), UnixNanos::from_nanos(0));
let converted = convert(&anchor, PRIME_WHEEL_HZ, Ticks::from_raw(5_000));
assert_eq!(
converted,
UnixNanos::from_nanos(5_000_000_000),
"5000 ticks at 1000 Hz is 5 seconds, matching prime's ms-resolution wheel"
);
}
#[test]
fn counter_wraparound_still_converts_correctly() {
let anchor = AnchorCell::new(Ticks::from_raw(u64::MAX - 9), UnixNanos::from_nanos(0));
let after_wrap = convert(&anchor, TSC_NOMINAL_HZ, Ticks::from_raw(10));
assert_eq!(after_wrap, UnixNanos::from_nanos(20));
}
#[test]
fn re_anchoring_disciplines_subsequent_reads_without_rebuilding_the_pipeline() {
let anchor = AnchorCell::new(Ticks::from_raw(0), UnixNanos::from_nanos(0));
let stage = ToUnixNanos::new(&anchor, TSC_NOMINAL_HZ);
let before_discipline =
block_on(Pipe::call(&stage, Ticks::from_raw(1_000))).expect("conversion never fails");
assert_eq!(before_discipline, UnixNanos::from_nanos(1_000));
anchor.set(Ticks::from_raw(1_000), UnixNanos::from_nanos(50_000_000));
let after_discipline =
block_on(Pipe::call(&stage, Ticks::from_raw(2_000))).expect("conversion never fails");
assert_eq!(after_discipline, UnixNanos::from_nanos(50_001_000));
}
#[test]
fn zero_frequency_clamps_instead_of_dividing_by_zero() {
let anchor = AnchorCell::new(Ticks::from_raw(0), UnixNanos::from_nanos(0));
let converted = convert(&anchor, 0, Ticks::from_raw(3));
assert_eq!(
converted,
UnixNanos::from_nanos(3_000_000_000),
"frequency_hz=0 clamps to 1 Hz rather than panicking on divide-by-zero"
);
}
#[cfg(feature = "std")]
mod properties {
use super::{
AnchorCell, Pipe, TSC_NOMINAL_HZ, Ticks, ToUnixNanos, UnixNanos, block_on, convert,
};
use proptest::prelude::{ProptestConfig, any, prop_assert, prop_assert_eq, proptest};
const PLAUSIBLE_COUNTER_HZ: core::ops::RangeInclusive<u64> = 1..=10_000_000_000;
proptest! {
#![proptest_config(ProptestConfig::with_cases(512))]
#[test]
fn the_anchor_point_is_a_fixed_point(
anchor_ticks in any::<u64>(),
anchor_nanos in any::<u64>(),
frequency_hz in PLAUSIBLE_COUNTER_HZ,
) {
let anchor = AnchorCell::new(
Ticks::from_raw(anchor_ticks),
UnixNanos::from_nanos(anchor_nanos),
);
let converted = convert(&anchor, frequency_hz, Ticks::from_raw(anchor_ticks));
prop_assert_eq!(converted, UnixNanos::from_nanos(anchor_nanos));
}
#[test]
fn a_reading_never_precedes_the_anchor(
anchor_ticks in any::<u64>(),
anchor_nanos in any::<u64>(),
frequency_hz in PLAUSIBLE_COUNTER_HZ,
elapsed_ticks in any::<u64>(),
) {
let anchor = AnchorCell::new(
Ticks::from_raw(anchor_ticks),
UnixNanos::from_nanos(anchor_nanos),
);
let read = Ticks::from_raw(anchor_ticks.wrapping_add(elapsed_ticks));
let converted = convert(&anchor, frequency_hz, read);
prop_assert!(converted >= UnixNanos::from_nanos(anchor_nanos));
}
#[test]
fn more_elapsed_ticks_never_convert_backwards(
anchor_ticks in any::<u64>(),
anchor_nanos in any::<u64>(),
frequency_hz in PLAUSIBLE_COUNTER_HZ,
first_elapsed in any::<u64>(),
second_elapsed in any::<u64>(),
) {
let (earlier, later) = if first_elapsed <= second_elapsed {
(first_elapsed, second_elapsed)
} else {
(second_elapsed, first_elapsed)
};
let anchor = AnchorCell::new(
Ticks::from_raw(anchor_ticks),
UnixNanos::from_nanos(anchor_nanos),
);
let earlier_reading = convert(
&anchor,
frequency_hz,
Ticks::from_raw(anchor_ticks.wrapping_add(earlier)),
);
let later_reading = convert(
&anchor,
frequency_hz,
Ticks::from_raw(anchor_ticks.wrapping_add(later)),
);
prop_assert!(earlier_reading <= later_reading);
}
#[test]
fn a_1ghz_counter_converts_by_plain_saturating_addition(
anchor_ticks in any::<u64>(),
anchor_nanos in any::<u64>(),
elapsed_ticks in any::<u64>(),
) {
let anchor = AnchorCell::new(
Ticks::from_raw(anchor_ticks),
UnixNanos::from_nanos(anchor_nanos),
);
let read = Ticks::from_raw(anchor_ticks.wrapping_add(elapsed_ticks));
let converted = convert(&anchor, TSC_NOMINAL_HZ, read);
prop_assert_eq!(
converted,
UnixNanos::from_nanos(anchor_nanos.saturating_add(elapsed_ticks))
);
}
#[test]
fn re_anchoring_is_observed_by_a_stage_built_beforehand(
first_nanos in any::<u64>(),
second_nanos in any::<u64>(),
) {
let anchor = AnchorCell::new(Ticks::ZERO, UnixNanos::from_nanos(first_nanos));
let stage = ToUnixNanos::new(&anchor, TSC_NOMINAL_HZ);
anchor.set(Ticks::ZERO, UnixNanos::from_nanos(second_nanos));
let reading = block_on(Pipe::call(&stage, Ticks::ZERO))
.expect("conversion never fails");
prop_assert_eq!(reading, UnixNanos::from_nanos(second_nanos));
}
}
}
#[cfg(feature = "std")]
mod rendering {
use super::{AnchorCell, Ticks, UnixNanos};
#[test]
fn debug_renders_the_live_anchor_not_the_constructed_one() {
let anchor = AnchorCell::new(Ticks::from_raw(0), UnixNanos::from_nanos(0));
anchor.set(Ticks::from_raw(1_000), UnixNanos::from_nanos(50_000_000));
assert_eq!(
format!("{anchor:?}"),
"AnchorCell { ticks: Ticks(1000), unix_nanos: UnixNanos(50000000) }"
);
}
}
}