Skip to main content

ping_core/
clock.rs

1//! Hybrid Logical Clock — Kulkarni et al. 2014.
2//!
3//! Used to order application messages within an MLS epoch when multiple devices send
4//! concurrently. Epoch order is the primary axis; HLC orders within an epoch.
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
9pub struct Hlc {
10    pub wall_ms: u64,
11    pub logical: u32,
12}
13
14impl Hlc {
15    pub const ZERO: Hlc = Hlc {
16        wall_ms: 0,
17        logical: 0,
18    };
19
20    /// Advance for a local event at wall-clock `now_ms`.
21    pub fn tick(self, now_ms: u64) -> Hlc {
22        let wall = now_ms.max(self.wall_ms);
23        let logical = if wall == self.wall_ms {
24            self.logical + 1
25        } else {
26            0
27        };
28        Hlc {
29            wall_ms: wall,
30            logical,
31        }
32    }
33
34    /// Merge with a remote HLC observed at local wall-clock `now_ms`.
35    pub fn merge(self, remote: Hlc, now_ms: u64) -> Hlc {
36        let wall = now_ms.max(self.wall_ms).max(remote.wall_ms);
37        let logical = match (wall == self.wall_ms, wall == remote.wall_ms) {
38            (true, true) => self.logical.max(remote.logical) + 1,
39            (true, false) => self.logical + 1,
40            (false, true) => remote.logical + 1,
41            (false, false) => 0,
42        };
43        Hlc {
44            wall_ms: wall,
45            logical,
46        }
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn tick_monotonic_when_clock_stalls() {
56        let a = Hlc::ZERO.tick(1000);
57        let b = a.tick(1000);
58        assert!(b > a);
59        assert_eq!(b.wall_ms, 1000);
60        assert_eq!(b.logical, 1);
61    }
62
63    #[test]
64    fn merge_dominates_both_inputs() {
65        let local = Hlc {
66            wall_ms: 100,
67            logical: 2,
68        };
69        let remote = Hlc {
70            wall_ms: 200,
71            logical: 5,
72        };
73        let merged = local.merge(remote, 150);
74        assert!(merged > local);
75        assert!(merged > remote);
76    }
77}