kanidmd_lib/repl/
cid.rs

1use std::fmt;
2use std::time::Duration;
3use time::OffsetDateTime;
4
5use crate::be::dbvalue::DbCidV1;
6use crate::prelude::*;
7use serde::{Deserialize, Serialize};
8
9#[derive(Serialize, Deserialize, PartialEq, Clone, Eq, PartialOrd, Ord, Hash)]
10pub struct Cid {
11    // Mental note: Derive ord always checks in order of struct fields.
12    pub ts: Duration,
13    pub s_uuid: Uuid,
14}
15
16impl From<DbCidV1> for Cid {
17    fn from(
18        DbCidV1 {
19            server_id,
20            timestamp,
21        }: DbCidV1,
22    ) -> Self {
23        Cid {
24            ts: timestamp,
25            s_uuid: server_id,
26        }
27    }
28}
29
30impl fmt::Debug for Cid {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "{:032}-{}", self.ts.as_nanos(), self.s_uuid)
33    }
34}
35
36impl fmt::Display for Cid {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(f, "{:032}-{}", self.ts.as_nanos(), self.s_uuid)
39    }
40}
41
42impl From<&Cid> for OffsetDateTime {
43    fn from(cid: &Cid) -> Self {
44        OffsetDateTime::UNIX_EPOCH + cid.ts
45    }
46}
47
48impl Cid {
49    pub(crate) fn new(s_uuid: Uuid, ts: Duration) -> Self {
50        Cid { s_uuid, ts }
51    }
52
53    pub fn new_lamport(s_uuid: Uuid, ts: Duration, max_ts: &Duration) -> Self {
54        let ts = if ts > *max_ts {
55            ts
56        } else {
57            *max_ts + Duration::from_nanos(1)
58        };
59        Cid { ts, s_uuid }
60    }
61
62    /// ⚠️  - Create a new cid at timestamp zero.
63    /// This is a TEST ONLY method and will never be exposed in production.
64    #[cfg(test)]
65    pub fn new_zero() -> Self {
66        Self::new_count(0)
67    }
68
69    /// ⚠️  - Create a new cid with a manually defined timestamp.
70    /// This is a TEST ONLY method and will never be exposed in production.
71    #[cfg(test)]
72    pub fn new_count(c: u64) -> Self {
73        Cid {
74            s_uuid: uuid!("00000000-0000-0000-0000-000000000000"),
75            ts: Duration::new(c, 0),
76        }
77    }
78
79    pub fn sub_secs(&self, secs: u64) -> Result<Self, OperationError> {
80        self.ts
81            .checked_sub(Duration::from_secs(secs))
82            .map(|r| Cid {
83                s_uuid: uuid!("00000000-0000-0000-0000-000000000000"),
84                ts: r,
85            })
86            .ok_or(OperationError::InvalidReplChangeId)
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use crate::prelude::*;
93    use std::cmp::Ordering;
94    use std::time::Duration;
95
96    use crate::repl::cid::Cid;
97
98    #[test]
99    fn test_cid_ordering() {
100        // Check diff ts
101        let cid_a = Cid::new(
102            uuid!("00000000-0000-0000-0000-000000000001"),
103            Duration::new(5, 0),
104        );
105        let cid_b = Cid::new(
106            uuid!("00000000-0000-0000-0000-000000000001"),
107            Duration::new(15, 0),
108        );
109
110        assert_eq!(cid_a.cmp(&cid_a), Ordering::Equal);
111        assert_eq!(cid_a.cmp(&cid_b), Ordering::Less);
112        assert_eq!(cid_b.cmp(&cid_a), Ordering::Greater);
113
114        // check same ts, d_uuid, diff s_uuid
115        let cid_e = Cid::new(
116            uuid!("00000000-0000-0000-0000-000000000000"),
117            Duration::new(5, 0),
118        );
119        let cid_f = Cid::new(
120            uuid!("00000000-0000-0000-0000-000000000001"),
121            Duration::new(5, 0),
122        );
123
124        assert_eq!(cid_e.cmp(&cid_e), Ordering::Equal);
125        assert_eq!(cid_e.cmp(&cid_f), Ordering::Less);
126        assert_eq!(cid_f.cmp(&cid_e), Ordering::Greater);
127    }
128
129    #[test]
130    fn test_cid_lamport() {
131        let s_uuid = uuid!("00000000-0000-0000-0000-000000000001");
132
133        let ts5 = Duration::new(5, 0);
134        let ts10 = Duration::new(10, 0);
135        let ts15 = Duration::new(15, 0);
136
137        let cid_z = Cid::new_zero();
138
139        let cid_a = Cid::new_lamport(s_uuid, ts5, &ts5);
140        assert_eq!(cid_a.cmp(&cid_z), Ordering::Greater);
141        let cid_b = Cid::new_lamport(s_uuid, ts15, &ts10);
142        assert_eq!(cid_b.cmp(&cid_a), Ordering::Greater);
143        // Even with an older ts, we should still step forward.
144        let cid_c = Cid::new_lamport(s_uuid, ts10, &ts15);
145        assert_eq!(cid_c.cmp(&cid_b), Ordering::Greater);
146    }
147}