roowho2_lib/proto/rwhod_protocol/
time_codec.rs1use chrono::{DateTime, Utc};
8
9const RWHOD_TIMESTAMP_CORRECTION_WINDOW: i64 = 0x40000000_i64;
10const RWHOD_TIMESTAMP_WRAP_INCREMENT: i64 = 0x1_0000_0000_i64;
11
12const RWHOD_TIMESTAMP_FORWARD_ALLOWANCE: i64 = RWHOD_TIMESTAMP_CORRECTION_WINDOW;
13
14#[allow(dead_code)]
17pub fn decode_rwhod_timestamp(raw: i32, correction: i64) -> Result<DateTime<Utc>, String> {
18 DateTime::from_timestamp_secs(i64::from(raw) + correction).ok_or(format!(
19 "Invalid timestamp: {} with correction {}",
20 raw, correction
21 ))
22}
23
24#[allow(dead_code)]
38pub fn rwhod_time_correction(now: DateTime<Utc>, recvtime: i32) -> i64 {
39 let delta = now.timestamp() - i64::from(recvtime);
40
41 if delta <= RWHOD_TIMESTAMP_CORRECTION_WINDOW {
42 return 0;
43 }
44
45 let wrap_count = (delta - RWHOD_TIMESTAMP_CORRECTION_WINDOW - 1)
46 .div_euclid(RWHOD_TIMESTAMP_WRAP_INCREMENT)
47 + 1;
48
49 wrap_count * RWHOD_TIMESTAMP_WRAP_INCREMENT
50}
51
52pub fn decode_rwhod_timestamp_near_time_with_forward_allowance(
60 raw: i32,
61 time: DateTime<Utc>,
62 forward_allowance: i64,
63) -> Result<DateTime<Utc>, String> {
64 debug_assert!(forward_allowance >= 0);
65 debug_assert!(forward_allowance <= RWHOD_TIMESTAMP_WRAP_INCREMENT);
66
67 let raw = i64::from(raw);
68 let candidate = raw
69 + (time.timestamp() + forward_allowance - raw).div_euclid(RWHOD_TIMESTAMP_WRAP_INCREMENT)
70 * RWHOD_TIMESTAMP_WRAP_INCREMENT;
71
72 DateTime::from_timestamp_secs(candidate).ok_or(format!(
73 "Invalid timestamp: {} near {} with forward allowance {}",
74 raw, time, forward_allowance
75 ))
76}
77
78pub fn decode_rwhod_timestamp_near_time(
81 raw: i32,
82 time: DateTime<Utc>,
83) -> Result<DateTime<Utc>, String> {
84 decode_rwhod_timestamp_near_time_with_forward_allowance(
85 raw,
86 time,
87 RWHOD_TIMESTAMP_FORWARD_ALLOWANCE,
88 )
89}
90
91pub struct RwhodTimestamps {
92 pub boottime: DateTime<Utc>,
93 pub sendtime: DateTime<Utc>,
94 pub recvtime: Option<DateTime<Utc>>,
95}
96
97pub fn decode_rwhod_timestamps(
98 wd_boottime: i32,
99 wd_sendtime: i32,
100 wd_recvtime: i32,
101 now: DateTime<Utc>,
102) -> Result<RwhodTimestamps, String> {
103 let recvtime = if wd_recvtime == 0 {
104 None
105 } else {
106 Some(decode_rwhod_timestamp_near_time(wd_recvtime, now)?)
107 };
108
109 let sendtime = decode_rwhod_timestamp_near_time(wd_sendtime, now)?;
110 let boottime = decode_rwhod_timestamp_near_time(wd_boottime, now)?;
111
112 Ok(RwhodTimestamps {
113 boottime,
114 sendtime,
115 recvtime,
116 })
117}
118
119pub fn encode_rwhod_timestamp(timestamp: DateTime<Utc>) -> i32 {
121 timestamp.timestamp() as i32
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_rwhod_time_correction_no_wrap() {
130 let now = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
131 let recvtime = i32::MAX;
132 let correction = rwhod_time_correction(now, recvtime);
133
134 assert_eq!(correction, 0);
135 }
136
137 #[test]
138 fn test_rwhod_time_correction_with_single_wrap() {
139 let now = DateTime::from_timestamp(i32::MAX as i64 + 2, 0).unwrap();
140 let recvtime = i32::MAX.wrapping_add(1);
141 let correction = rwhod_time_correction(now, recvtime);
142
143 assert_eq!(correction, RWHOD_TIMESTAMP_WRAP_INCREMENT * 1);
144 }
145
146 #[test]
147 fn test_rwhod_time_correction_with_multiple_wraps() {
148 let now = DateTime::from_timestamp(i32::MAX as i64 + RWHOD_TIMESTAMP_WRAP_INCREMENT + 1, 0)
149 .unwrap();
150 let recvtime = i32::MAX.wrapping_add(1);
151 let correction = rwhod_time_correction(now, recvtime);
152
153 assert_eq!(correction, RWHOD_TIMESTAMP_WRAP_INCREMENT * 2);
154 }
155
156 #[test]
157 fn test_rwhod_time_correction_with_negative_delta() {
158 let now = DateTime::from_timestamp(i32::MAX as i64 - 1, 0).unwrap();
159 let recvtime = i32::MAX;
160 let correction = rwhod_time_correction(now, recvtime);
161
162 assert_eq!(correction, 0);
163 }
164
165 #[test]
166 fn test_rwhod_time_correction_with_large_negative_delta() {
167 let now = DateTime::from_timestamp(i32::MIN as i64 - 1, 0).unwrap();
168 let recvtime = i32::MIN;
169 let correction = rwhod_time_correction(now, recvtime);
170
171 assert_eq!(correction, 0);
172 }
173
174 #[test]
175 fn test_decode_rwhod_timestamp_near_time_no_wrap() {
176 let now = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
177 let expected = now - chrono::Duration::days(1);
178 let raw = expected.timestamp() as i32;
179
180 let decoded = decode_rwhod_timestamp_near_time(raw, now).unwrap();
181 assert_eq!(decoded, expected);
182 }
183
184 #[test]
185 fn test_decode_rwhod_timestamp_near_time_with_wrap() {
186 let now = DateTime::from_timestamp(i32::MAX as i64 + RWHOD_TIMESTAMP_WRAP_INCREMENT + 1, 0)
187 .unwrap();
188 let expected = now - chrono::Duration::days(1);
189 let raw = expected.timestamp() as i32;
190
191 let decoded = decode_rwhod_timestamp_near_time(raw, now).unwrap();
192 assert_eq!(decoded, expected);
193 }
194
195 #[test]
196 fn test_decode_rwhod_timestamp_near_time_with_negative_delta() {
197 let time = DateTime::from_timestamp(-(RWHOD_TIMESTAMP_WRAP_INCREMENT / 2) - 10, 0).unwrap();
198 let raw = 0;
199
200 let decoded = decode_rwhod_timestamp_near_time_with_forward_allowance(
201 raw,
202 time,
203 RWHOD_TIMESTAMP_WRAP_INCREMENT / 2,
204 )
205 .unwrap();
206 assert_eq!(
207 decoded,
208 DateTime::from_timestamp(-RWHOD_TIMESTAMP_WRAP_INCREMENT, 0).unwrap()
209 );
210 }
211
212 #[test]
213 fn test_decode_rwhod_timestamp_near_time_uses_forward_allowance() {
214 let time = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
215 let expected = time + chrono::Duration::seconds(5);
216 let raw = expected.timestamp() as i32;
217
218 let decoded =
219 decode_rwhod_timestamp_near_time_with_forward_allowance(raw, time, 5).unwrap();
220 assert_eq!(decoded, expected);
221 }
222
223 #[test]
224 fn test_decode_rwhod_timestamp_near_time_small_forward_allowance_prefers_previous_wrap() {
225 let time = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
226 let future = time + chrono::Duration::seconds(RWHOD_TIMESTAMP_WRAP_INCREMENT / 4);
227 let raw = future.timestamp() as i32;
228
229 let decoded = decode_rwhod_timestamp_near_time_with_forward_allowance(
230 raw,
231 time,
232 RWHOD_TIMESTAMP_WRAP_INCREMENT / 10,
233 )
234 .unwrap();
235 assert_eq!(
236 decoded,
237 future - chrono::Duration::seconds(RWHOD_TIMESTAMP_WRAP_INCREMENT)
238 );
239 }
240
241 #[test]
242 fn test_encode_rwhod_timestamp_no_wrap() {
243 let timestamp = DateTime::from_timestamp(i32::MAX as i64, 0).unwrap();
244 let encoded = encode_rwhod_timestamp(timestamp);
245 assert_eq!(encoded, i32::MAX);
246 }
247
248 #[test]
249 fn test_encode_rwhod_timestamp_with_wrap() {
250 let timestamp = DateTime::from_timestamp(i32::MAX as i64 + 1, 0).unwrap();
251 let encoded = encode_rwhod_timestamp(timestamp);
252 assert_eq!(encoded, i32::MIN);
253 }
254}