1use crate::error::{AmateRSError, ErrorContext, Result};
6use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8pub fn current_timestamp_micros() -> u64 {
10 SystemTime::now()
11 .duration_since(UNIX_EPOCH)
12 .expect("Time went backwards")
13 .as_micros() as u64
14}
15
16pub fn current_timestamp_millis() -> u64 {
18 SystemTime::now()
19 .duration_since(UNIX_EPOCH)
20 .expect("Time went backwards")
21 .as_millis() as u64
22}
23
24pub fn exponential_backoff(attempt: usize, base_delay_ms: u64, max_delay_ms: u64) -> Duration {
26 let delay_ms = base_delay_ms * 2_u64.pow(attempt as u32);
27 let delay_ms = delay_ms.min(max_delay_ms);
28 Duration::from_millis(delay_ms)
29}
30
31pub fn linear_backoff(attempt: usize, delay_ms: u64, max_attempts: usize) -> Option<Duration> {
33 if attempt < max_attempts {
34 Some(Duration::from_millis(delay_ms))
35 } else {
36 None
37 }
38}
39
40pub fn format_bytes(bytes: usize) -> String {
42 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
43 let mut size = bytes as f64;
44 let mut unit_idx = 0;
45
46 while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
47 size /= 1024.0;
48 unit_idx += 1;
49 }
50
51 if unit_idx == 0 {
52 format!("{} {}", bytes, UNITS[unit_idx])
53 } else {
54 format!("{:.2} {}", size, UNITS[unit_idx])
55 }
56}
57
58pub fn format_duration(duration: Duration) -> String {
60 let secs = duration.as_secs();
61 let nanos = duration.subsec_nanos();
62
63 if secs == 0 {
64 if nanos < 1_000 {
65 format!("{}ns", nanos)
66 } else if nanos < 1_000_000 {
67 format!("{:.2}µs", nanos as f64 / 1_000.0)
68 } else {
69 format!("{:.2}ms", nanos as f64 / 1_000_000.0)
70 }
71 } else if secs < 60 {
72 format!("{:.2}s", secs as f64 + nanos as f64 / 1_000_000_000.0)
73 } else if secs < 3600 {
74 let mins = secs / 60;
75 let secs = secs % 60;
76 format!("{}m {}s", mins, secs)
77 } else {
78 let hours = secs / 3600;
79 let mins = (secs % 3600) / 60;
80 format!("{}h {}m", hours, mins)
81 }
82}
83
84pub fn next_power_of_two(n: usize) -> usize {
86 if n == 0 {
87 return 1;
88 }
89 let mut power = 1;
90 while power < n {
91 power *= 2;
92 }
93 power
94}
95
96pub fn is_power_of_two(n: usize) -> bool {
98 n != 0 && (n & (n - 1)) == 0
99}
100
101pub fn align_to(value: usize, alignment: usize) -> usize {
103 (value + alignment - 1) & !(alignment - 1)
104}
105
106pub fn calculate_checksum(data: &[u8]) -> u32 {
108 crc32fast::hash(data)
109}
110
111pub fn verify_checksum(data: &[u8], expected: u32) -> Result<()> {
113 let actual = calculate_checksum(data);
114 if actual == expected {
115 Ok(())
116 } else {
117 Err(AmateRSError::StorageIntegrity(ErrorContext::new(format!(
118 "Checksum mismatch: expected {}, got {}",
119 expected, actual
120 ))))
121 }
122}
123
124pub async fn retry_with_backoff<F, T, E>(
126 mut f: F,
127 max_attempts: usize,
128 base_delay_ms: u64,
129) -> std::result::Result<T, E>
130where
131 F: FnMut() -> std::result::Result<T, E>,
132{
133 let mut attempt = 0;
134 loop {
135 match f() {
136 Ok(result) => return Ok(result),
137 Err(e) => {
138 attempt += 1;
139 if attempt >= max_attempts {
140 return Err(e);
141 }
142 let delay = exponential_backoff(attempt, base_delay_ms, 30_000);
143 tokio::time::sleep(delay).await;
144 }
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_current_timestamp() {
155 let ts_micros = current_timestamp_micros();
156 let ts_millis = current_timestamp_millis();
157
158 assert!(ts_micros > 0);
159 assert!(ts_millis > 0);
160 assert!(ts_micros > ts_millis * 1000);
161 }
162
163 #[test]
164 fn test_exponential_backoff() {
165 let delay1 = exponential_backoff(0, 100, 10_000);
166 let delay2 = exponential_backoff(1, 100, 10_000);
167 let delay3 = exponential_backoff(2, 100, 10_000);
168
169 assert!(delay2 > delay1);
170 assert!(delay3 > delay2);
171
172 let delay_max = exponential_backoff(20, 100, 1_000);
174 assert_eq!(delay_max, Duration::from_millis(1_000));
175 }
176
177 #[test]
178 fn test_format_bytes() {
179 assert_eq!(format_bytes(500), "500 B");
180 assert_eq!(format_bytes(1024), "1.00 KB");
181 assert_eq!(format_bytes(1024 * 1024), "1.00 MB");
182 assert_eq!(format_bytes(1536 * 1024), "1.50 MB");
183 }
184
185 #[test]
186 fn test_format_duration() {
187 assert_eq!(format_duration(Duration::from_nanos(500)), "500ns");
188 assert_eq!(format_duration(Duration::from_micros(1500)), "1.50ms");
189 assert_eq!(format_duration(Duration::from_millis(2500)), "2.50s");
190 assert_eq!(format_duration(Duration::from_secs(90)), "1m 30s");
191 assert_eq!(format_duration(Duration::from_secs(3665)), "1h 1m");
192 }
193
194 #[test]
195 fn test_power_of_two() {
196 assert!(is_power_of_two(1));
197 assert!(is_power_of_two(2));
198 assert!(is_power_of_two(4));
199 assert!(is_power_of_two(1024));
200 assert!(!is_power_of_two(3));
201 assert!(!is_power_of_two(1000));
202
203 assert_eq!(next_power_of_two(0), 1);
204 assert_eq!(next_power_of_two(1), 1);
205 assert_eq!(next_power_of_two(3), 4);
206 assert_eq!(next_power_of_two(1000), 1024);
207 }
208
209 #[test]
210 fn test_align_to() {
211 assert_eq!(align_to(0, 4), 0);
212 assert_eq!(align_to(1, 4), 4);
213 assert_eq!(align_to(4, 4), 4);
214 assert_eq!(align_to(5, 4), 8);
215 assert_eq!(align_to(100, 64), 128);
216 }
217
218 #[test]
219 fn test_checksum() -> Result<()> {
220 let data = b"test data";
221 let checksum = calculate_checksum(data);
222
223 verify_checksum(data, checksum)?;
225
226 assert!(verify_checksum(data, checksum + 1).is_err());
228
229 Ok(())
230 }
231
232 #[tokio::test]
233 async fn test_retry_with_backoff() {
234 let mut attempts = 0;
235 let result = retry_with_backoff(
236 || {
237 attempts += 1;
238 if attempts < 3 { Err("fail") } else { Ok(42) }
239 },
240 5,
241 1, )
243 .await;
244
245 assert_eq!(result, Ok(42));
246 assert_eq!(attempts, 3);
247 }
248
249 #[tokio::test]
250 async fn test_retry_with_backoff_max_attempts() {
251 let mut attempts = 0;
252 let result: std::result::Result<i32, &str> = retry_with_backoff(
253 || {
254 attempts += 1;
255 Err("always fail")
256 },
257 3,
258 1,
259 )
260 .await;
261
262 assert_eq!(result, Err("always fail"));
263 assert_eq!(attempts, 3);
264 }
265}