wickra_exchange_core/
clock.rs1use std::sync::atomic::{AtomicU64, Ordering};
14
15#[derive(Debug, Default)]
17pub struct ServerClock {
18 offset_ms: i64,
19}
20
21impl ServerClock {
22 #[must_use]
24 pub fn new() -> Self {
25 Self::default()
26 }
27
28 pub fn sync(&mut self, local_ms: i64, server_ms: i64) {
31 self.offset_ms = server_ms - local_ms;
32 }
33
34 #[must_use]
36 pub fn offset_ms(&self) -> i64 {
37 self.offset_ms
38 }
39
40 #[must_use]
42 pub fn server_time_ms(&self, local_ms: i64) -> i64 {
43 local_ms + self.offset_ms
44 }
45}
46
47#[derive(Debug)]
51pub struct NonceGenerator {
52 last: AtomicU64,
53}
54
55impl NonceGenerator {
56 #[must_use]
58 pub fn new(start: u64) -> Self {
59 Self {
60 last: AtomicU64::new(start),
61 }
62 }
63
64 pub fn next(&self, candidate_ms: u64) -> u64 {
66 let mut prev = self.last.load(Ordering::Relaxed);
67 loop {
68 let next = candidate_ms.max(prev + 1);
69 match self
70 .last
71 .compare_exchange_weak(prev, next, Ordering::Relaxed, Ordering::Relaxed)
72 {
73 Ok(_) => return next,
74 Err(actual) => prev = actual,
75 }
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct TokenTtl {
83 issued_at_ms: i64,
84 ttl_ms: i64,
85}
86
87impl TokenTtl {
88 #[must_use]
90 pub fn new(issued_at_ms: i64, ttl_ms: i64) -> Self {
91 Self {
92 issued_at_ms,
93 ttl_ms,
94 }
95 }
96
97 #[must_use]
99 pub fn expires_at_ms(&self) -> i64 {
100 self.issued_at_ms + self.ttl_ms
101 }
102
103 #[must_use]
105 pub fn is_expired(&self, now_ms: i64) -> bool {
106 now_ms >= self.expires_at_ms()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn server_clock_offsets_both_directions() {
116 let mut clock = ServerClock::new();
117 assert_eq!(clock.offset_ms(), 0);
118
119 clock.sync(1_000, 1_250);
121 assert_eq!(clock.offset_ms(), 250);
122 assert_eq!(clock.server_time_ms(2_000), 2_250);
123
124 clock.sync(5_000, 4_900);
126 assert_eq!(clock.offset_ms(), -100);
127 assert_eq!(clock.server_time_ms(5_000), 4_900);
128 }
129
130 #[test]
131 fn nonce_is_strictly_increasing_even_within_a_millisecond() {
132 let gen = NonceGenerator::new(0);
133 assert_eq!(gen.next(1_000), 1_000);
135 assert_eq!(gen.next(1_000), 1_001);
136 assert_eq!(gen.next(1_000), 1_002);
137 assert_eq!(gen.next(5_000), 5_000);
139 assert_eq!(gen.next(10), 5_001);
141 }
142
143 #[test]
144 fn nonce_starts_above_seed() {
145 let gen = NonceGenerator::new(100);
146 assert_eq!(gen.next(0), 101);
147 }
148
149 #[test]
150 fn token_ttl_expiry() {
151 let token = TokenTtl::new(1_000, 30_000);
152 assert_eq!(token.expires_at_ms(), 31_000);
153 assert!(!token.is_expired(30_999));
154 assert!(token.is_expired(31_000));
155 assert!(token.is_expired(40_000));
156 }
157}