Skip to main content

coven_merge/
timestamp.rs

1use crate::MergeError;
2use coven_foundation::id_source::DeviceId;
3
4/// ยง7.2: 48-bit milliseconds, a 16-bit counter, and a 64-bit device id,
5/// ordered in that order. No clock is read here.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct Timestamp {
8    milliseconds: u64,
9    counter: u16,
10    device: DeviceId,
11}
12
13impl Timestamp {
14    /// The greatest millisecond the timestamp can represent.
15    pub const MAX_MILLISECONDS: u64 = (1_u64 << 48) - 1;
16
17    /// Construct a timestamp, refusing milliseconds outside its 48-bit range.
18    pub fn new(milliseconds: u64, counter: u16, device: DeviceId) -> Result<Self, MergeError> {
19        if milliseconds > Self::MAX_MILLISECONDS {
20            return Err(MergeError::MillisecondsOutOfRange(milliseconds));
21        }
22        Ok(Self { milliseconds, counter, device })
23    }
24
25    /// Stamp a local write or entry after the latest timestamp from local work
26    /// and applied incoming work. The caller persists that latest timestamp.
27    /// A counter overflow advances the millisecond; exhaustion is an error.
28    pub fn next(latest: Option<Self>, clock_ms: u64, device: DeviceId) -> Result<Self, MergeError> {
29        if clock_ms > Self::MAX_MILLISECONDS {
30            return Err(MergeError::MillisecondsOutOfRange(clock_ms));
31        }
32        match latest {
33            None => Self::new(clock_ms, 0, device),
34            Some(last) if clock_ms > last.milliseconds => Self::new(clock_ms, 0, device),
35            Some(last) => match last.counter.checked_add(1) {
36                Some(counter) => Self::new(last.milliseconds, counter, device),
37                None if last.milliseconds < Self::MAX_MILLISECONDS => {
38                    Self::new(last.milliseconds + 1, 0, device)
39                }
40                None => Err(MergeError::TimestampExhausted),
41            },
42        }
43    }
44
45    /// The wall-clock part, in milliseconds.
46    pub fn milliseconds(self) -> u64 {
47        self.milliseconds
48    }
49
50    /// The logical counter within the millisecond.
51    pub fn counter(self) -> u16 {
52        self.counter
53    }
54
55    /// The writing device's id.
56    pub fn device(self) -> DeviceId {
57        self.device
58    }
59}
60
61#[cfg(test)]
62#[path = "timestamp_tests.rs"]
63mod tests;