#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct MonoNs(pub(crate) u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct WallNs(pub(crate) u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct DurationNs(pub(crate) u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ClockOffset(pub(crate) i128);
impl MonoNs {
#[inline]
fn to_wall(self, offset: ClockOffset) -> WallNs {
offset
.checked_to_wall(self)
.expect("clock offset must be validated before conversion")
}
#[inline]
pub(crate) fn to_wall_or_raw(self, offset: Option<ClockOffset>) -> WallNs {
match offset {
Some(off) => self.to_wall(off),
None => WallNs(self.0),
}
}
#[inline]
pub(crate) fn saturating_sub(self, other: MonoNs) -> DurationNs {
DurationNs(self.0.saturating_sub(other.0))
}
#[inline]
pub(crate) fn raw(self) -> u64 {
self.0
}
}
impl WallNs {
#[inline]
pub(crate) fn raw(self) -> u64 {
self.0
}
#[inline]
pub(crate) fn saturating_sub(self, other: WallNs) -> DurationNs {
DurationNs(self.0.saturating_sub(other.0))
}
}
impl DurationNs {
pub(crate) const ZERO: Self = Self(0);
#[inline]
pub(crate) fn saturating_add(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
#[inline]
pub(crate) fn raw(self) -> u64 {
self.0
}
}
impl ClockOffset {
pub(crate) fn from_clock_sync(realtime_ns: u64, timestamp_ns: u64) -> Self {
Self(realtime_ns as i128 - timestamp_ns as i128)
}
#[inline]
pub(crate) fn checked_to_wall(self, timestamp: MonoNs) -> Option<WallNs> {
let wall = timestamp.0 as i128 + self.0;
u64::try_from(wall).ok().map(WallNs)
}
pub(crate) fn is_valid_for(self, min: MonoNs, max: MonoNs) -> bool {
self.checked_to_wall(min).is_some() && self.checked_to_wall(max).is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mono_to_wall_positive_offset() {
let offset = ClockOffset::from_clock_sync(1_700_000_000_000_000_100, 100);
let mono = MonoNs(500);
let wall = offset.checked_to_wall(mono).unwrap();
assert_eq!(wall.raw(), 1_700_000_000_000_000_500);
}
#[test]
fn mono_to_wall_no_offset_passthrough() {
let mono = MonoNs(42);
let wall = mono.to_wall_or_raw(None);
assert_eq!(wall.raw(), 42);
}
#[test]
fn wall_saturating_sub() {
let a = WallNs(100);
let b = WallNs(30);
assert_eq!(a.saturating_sub(b).raw(), 70);
assert_eq!(b.saturating_sub(a).raw(), 0);
}
#[test]
fn negative_conversion_is_rejected() {
let offset = ClockOffset(-(200i128));
let mono = MonoNs(100);
assert_eq!(offset.checked_to_wall(mono), None);
}
#[test]
fn overflow_conversion_is_rejected() {
let offset = ClockOffset(u64::MAX as i128);
let mono = MonoNs(u64::MAX);
assert_eq!(offset.checked_to_wall(mono), None);
}
#[test]
fn offset_validation_checks_both_ends_of_trace_range() {
assert!(ClockOffset(100).is_valid_for(MonoNs(0), MonoNs(1000)));
assert!(!ClockOffset(-100).is_valid_for(MonoNs(0), MonoNs(1000)));
assert!(!ClockOffset(u64::MAX as i128).is_valid_for(MonoNs(0), MonoNs(1000)));
}
}