1use std::sync::Mutex;
9
10use serde::{Deserialize, Serialize};
11
12use crate::clock::{Clock, TimestampMs};
13use crate::error::Error;
14
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
26pub struct Hlc {
27 pub wall_ms: TimestampMs,
29 pub counter: u32,
31 pub device_id: String,
33}
34
35impl Hlc {
36 pub fn new(wall_ms: TimestampMs, counter: u32, device_id: impl Into<String>) -> Self {
38 Self {
39 wall_ms,
40 counter,
41 device_id: device_id.into(),
42 }
43 }
44
45 pub fn encode(&self) -> String {
47 format!(
48 "{:016x}-{:08x}-{}",
49 self.wall_ms, self.counter, self.device_id
50 )
51 }
52}
53
54impl std::fmt::Display for Hlc {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 f.write_str(&self.encode())
57 }
58}
59
60impl std::str::FromStr for Hlc {
61 type Err = Error;
62
63 fn from_str(s: &str) -> Result<Self, Error> {
64 let bad = || Error::InvalidHlc(s.to_owned());
65 let (wall, rest) = s.split_at_checked(16).ok_or_else(bad)?;
66 let rest = rest.strip_prefix('-').ok_or_else(bad)?;
67 let (counter, rest) = rest.split_at_checked(8).ok_or_else(bad)?;
68 let device_id = rest.strip_prefix('-').ok_or_else(bad)?;
69 Ok(Self {
70 wall_ms: TimestampMs::from_str_radix(wall, 16).map_err(|_| bad())?,
71 counter: u32::from_str_radix(counter, 16).map_err(|_| bad())?,
72 device_id: device_id.to_owned(),
73 })
74 }
75}
76
77pub struct HlcGenerator {
80 device_id: String,
81 last: Mutex<(TimestampMs, u32)>,
82}
83
84impl HlcGenerator {
85 pub fn new(device_id: impl Into<String>) -> Self {
87 Self {
88 device_id: device_id.into(),
89 last: Mutex::new((0, 0)),
90 }
91 }
92
93 pub fn next(&self, clock: &dyn Clock) -> Hlc {
102 let now = clock.now_ms().max(0);
103 let mut last = self
104 .last
105 .lock()
106 .unwrap_or_else(std::sync::PoisonError::into_inner);
107 if now > last.0 {
108 *last = (now, 0);
109 } else if last.1 == u32::MAX {
110 *last = (last.0 + 1, 0);
111 } else {
112 last.1 += 1;
113 }
114 Hlc::new(last.0, last.1, self.device_id.clone())
115 }
116
117 pub fn observe(&self, remote: &Hlc) {
121 if remote.wall_ms < 0 {
122 return;
123 }
124 let mut last = self
125 .last
126 .lock()
127 .unwrap_or_else(std::sync::PoisonError::into_inner);
128 if (remote.wall_ms, remote.counter) > *last {
129 *last = (remote.wall_ms, remote.counter);
130 }
131 }
132
133 #[cfg(test)]
136 fn set_state(&self, wall_ms: TimestampMs, counter: u32) {
137 *self.last.lock().unwrap() = (wall_ms, counter);
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::clock::ManualClock;
145
146 #[test]
147 fn encode_order_matches_semantic_order() {
148 let hlcs = [
149 Hlc::new(1, 0, "a"),
150 Hlc::new(1, 1, "a"),
151 Hlc::new(1, 1, "b"),
152 Hlc::new(2, 0, "a"),
153 Hlc::new(0x1_0000_0000, 0, "a"),
154 ];
155 for pair in hlcs.windows(2) {
156 assert!(pair[0] < pair[1]);
157 assert!(
158 pair[0].encode() < pair[1].encode(),
159 "{} !< {}",
160 pair[0],
161 pair[1]
162 );
163 }
164 }
165
166 #[test]
167 fn roundtrip() {
168 let h = Hlc::new(123_456_789, 42, "device-x");
169 let parsed: Hlc = h.encode().parse().unwrap();
170 assert_eq!(h, parsed);
171 }
172
173 #[test]
174 fn generator_is_monotonic_under_stalled_clock() {
175 let clock = ManualClock::new(100);
176 let generator = HlcGenerator::new("dev");
177 let a = generator.next(&clock);
178 let b = generator.next(&clock);
179 clock.set_ms(50); let c = generator.next(&clock);
181 assert!(a < b && b < c);
182 }
183
184 #[test]
185 fn negative_clock_is_clamped() {
186 let clock = ManualClock::new(-5_000);
187 let generator = HlcGenerator::new("dev");
188 let a = generator.next(&clock);
189 assert!(
190 a.wall_ms >= 0,
191 "negative wall would break the sortable encoding"
192 );
193 let b = generator.next(&clock);
194 assert!(a < b);
195 assert!(a.encode() < b.encode());
196 generator.observe(&Hlc::new(-9_000, 7, "evil"));
199 assert!(generator.next(&clock).wall_ms >= 0);
200 }
201
202 #[test]
203 fn counter_exhaustion_advances_logical_wall() {
204 let clock = ManualClock::new(100);
205 let generator = HlcGenerator::new("dev");
206 let before = generator.next(&clock);
207 generator.set_state(100, u32::MAX);
208 let after = generator.next(&clock);
209 assert_eq!((after.wall_ms, after.counter), (101, 0));
210 assert!(before < after);
211 assert!(before.encode() < after.encode());
212 }
213
214 #[test]
215 fn generator_respects_observed_remote() {
216 let clock = ManualClock::new(100);
217 let generator = HlcGenerator::new("dev");
218 generator.observe(&Hlc::new(9_999, 3, "other"));
219 assert!(generator.next(&clock) > Hlc::new(9_999, 3, "other"));
220 }
221}