Skip to main content

trueno/tuner/
helpers.rs

1//! Helper Functions for Tuner
2//!
3//! Utility functions: CRC32, timestamp, string formatting.
4
5// ============================================================================
6// CRC32 Implementation
7// ============================================================================
8
9/// Generate CRC32 lookup table at compile time.
10const fn crc32_table() -> [u32; 256] {
11    let mut table = [0u32; 256];
12    let mut i = 0;
13    while i < 256 {
14        let mut crc = i as u32;
15        let mut j = 0;
16        while j < 8 {
17            if crc & 1 != 0 {
18                crc = 0xEDB8_8320 ^ (crc >> 1);
19            } else {
20                crc >>= 1;
21            }
22            j += 1;
23        }
24        table[i] = crc;
25        i += 1;
26    }
27    table
28}
29
30/// Simple CRC32 implementation (IEEE polynomial).
31/// Used for .apr file checksum verification.
32pub(crate) fn crc32_update(crc: u32, data: &[u8]) -> u32 {
33    const CRC32_TABLE: [u32; 256] = crc32_table();
34    let mut crc = !crc;
35    for &byte in data {
36        crc = CRC32_TABLE[((crc ^ u32::from(byte)) & 0xFF) as usize] ^ (crc >> 8);
37    }
38    !crc
39}
40
41/// Compute CRC32 hash for given data (convenience wrapper)
42pub(crate) fn crc32_hash(data: &[u8]) -> u32 {
43    crc32_update(0, data)
44}
45
46// ============================================================================
47// Timestamp
48// ============================================================================
49
50/// Simple timestamp (avoids chrono dependency)
51pub(crate) fn chrono_lite_now() -> String {
52    use std::time::{SystemTime, UNIX_EPOCH};
53    let duration = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default();
54    format!("{}", duration.as_secs())
55}
56
57// ============================================================================
58// String Formatting
59// ============================================================================
60
61/// Pad string to fixed width
62pub(crate) fn pad_right(s: &str, width: usize) -> String {
63    if s.len() >= width {
64        s[..width].to_string()
65    } else {
66        format!("{}{}", s, " ".repeat(width - s.len()))
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    // =========================================================================
75    // CRC32 Table Tests
76    // =========================================================================
77
78    #[test]
79    fn test_crc32_table_length() {
80        let table = crc32_table();
81        assert_eq!(table.len(), 256, "CRC32 table must have exactly 256 entries");
82    }
83
84    #[test]
85    fn test_crc32_table_first_entry_is_zero() {
86        let table = crc32_table();
87        assert_eq!(table[0], 0x0000_0000, "CRC32 table[0] must be 0 (identity)");
88    }
89
90    #[test]
91    fn test_crc32_table_known_entries() {
92        // Well-known CRC32 IEEE (0xEDB88320 reflected polynomial) table values
93        let table = crc32_table();
94        assert_eq!(table[1], 0x7707_3096, "table[1] mismatch for IEEE CRC32");
95        assert_eq!(table[2], 0xEE0E_612C, "table[2] mismatch for IEEE CRC32");
96        assert_eq!(table[3], 0x9909_51BA, "table[3] mismatch for IEEE CRC32");
97        assert_eq!(table[4], 0x076D_C419, "table[4] mismatch for IEEE CRC32");
98        assert_eq!(table[128], 0xEDB8_8320, "table[128] must equal the polynomial");
99        assert_eq!(table[255], 0x2D02_EF8D, "table[255] mismatch for IEEE CRC32");
100    }
101
102    #[test]
103    fn test_crc32_table_no_duplicate_nonzero_entries() {
104        let table = crc32_table();
105        // Every nonzero entry should be unique (CRC32 table is a permutation-like mapping)
106        let mut seen = std::collections::HashSet::new();
107        for (i, &val) in table.iter().enumerate() {
108            if val != 0 {
109                assert!(seen.insert(val), "Duplicate nonzero entry 0x{:08X} at index {}", val, i);
110            }
111        }
112    }
113
114    #[test]
115    fn test_crc32_table_polynomial_property() {
116        // For the IEEE polynomial 0xEDB88320, table[128] == polynomial
117        // because 128 == 0x80 (only MSB set), after 8 iterations of the
118        // reflected algorithm, the result is the polynomial itself.
119        let table = crc32_table();
120        assert_eq!(table[128], 0xEDB8_8320, "table[128] must equal the reflected CRC32 polynomial");
121    }
122
123    #[test]
124    fn test_crc32_table_is_const_deterministic() {
125        // Calling crc32_table() twice must produce identical results
126        let table1 = crc32_table();
127        let table2 = crc32_table();
128        assert_eq!(table1, table2, "crc32_table() must be deterministic");
129    }
130
131    // =========================================================================
132    // CRC32 Hash Tests
133    // =========================================================================
134
135    #[test]
136    fn test_crc32_hash_empty() {
137        // CRC32 of empty data should be 0x00000000
138        let result = crc32_hash(&[]);
139        assert_eq!(result, 0x0000_0000);
140    }
141
142    #[test]
143    fn test_crc32_hash_known_value() {
144        // CRC32 of "123456789" is a well-known test vector: 0xCBF43926
145        let result = crc32_hash(b"123456789");
146        assert_eq!(result, 0xCBF4_3926);
147    }
148
149    #[test]
150    fn test_crc32_hash_single_byte() {
151        // CRC32 of a single byte should produce a non-zero result
152        let result = crc32_hash(&[0x00]);
153        assert_ne!(result, 0);
154        // CRC32(0x00) = 0xD202EF8D
155        assert_eq!(result, 0xD202_EF8D);
156    }
157
158    #[test]
159    fn test_crc32_hash_different_inputs_differ() {
160        let hash_a = crc32_hash(b"hello");
161        let hash_b = crc32_hash(b"world");
162        assert_ne!(hash_a, hash_b, "Different inputs should produce different hashes");
163    }
164
165    #[test]
166    fn test_crc32_hash_deterministic() {
167        let hash1 = crc32_hash(b"test data");
168        let hash2 = crc32_hash(b"test data");
169        assert_eq!(hash1, hash2, "Same input should produce same hash");
170    }
171
172    #[test]
173    fn test_crc32_update_incremental() {
174        // Incremental update should match single-pass hash
175        let data = b"hello world";
176        let single_pass = crc32_hash(data);
177
178        let part1 = crc32_update(0, b"hello ");
179        let incremental = crc32_update(part1, b"world");
180        assert_eq!(single_pass, incremental, "Incremental CRC32 must match single-pass");
181    }
182
183    #[test]
184    fn test_crc32_update_from_nonzero_initial() {
185        // Starting from a non-zero CRC should differ from starting at 0
186        let from_zero = crc32_update(0, b"test");
187        let from_nonzero = crc32_update(0x1234_5678, b"test");
188        assert_ne!(
189            from_zero, from_nonzero,
190            "Different initial CRC should produce different result"
191        );
192    }
193
194    #[test]
195    fn test_crc32_hash_all_zeros() {
196        let result = crc32_hash(&[0u8; 16]);
197        assert_ne!(result, 0, "CRC32 of all-zero data should not be zero");
198    }
199
200    #[test]
201    fn test_crc32_hash_all_ones() {
202        let result = crc32_hash(&[0xFFu8; 16]);
203        assert_ne!(result, 0, "CRC32 of all-0xFF data should not be zero");
204    }
205
206    #[test]
207    fn test_crc32_hash_large_data() {
208        // Test with 1KB of data
209        let data: Vec<u8> = (0..1024).map(|i| (i % 256) as u8).collect();
210        let result = crc32_hash(&data);
211        assert_ne!(result, 0);
212        // Should be deterministic
213        let result2 = crc32_hash(&data);
214        assert_eq!(result, result2);
215    }
216
217    // =========================================================================
218    // chrono_lite_now Tests
219    // =========================================================================
220
221    #[test]
222    fn test_chrono_lite_now_is_numeric() {
223        let ts = chrono_lite_now();
224        let parsed: u64 = ts.parse().expect("Timestamp should be a parseable number");
225        assert!(parsed > 0, "Timestamp should be positive");
226    }
227
228    #[test]
229    fn test_chrono_lite_now_is_reasonable() {
230        let ts = chrono_lite_now();
231        let secs: u64 = ts.parse().expect("Should be parseable");
232        // Should be after Jan 1, 2020 (1577836800) and before Jan 1, 2100 (4102444800)
233        assert!(secs > 1_577_836_800, "Timestamp should be after 2020");
234        assert!(secs < 4_102_444_800, "Timestamp should be before 2100");
235    }
236
237    #[test]
238    fn test_chrono_lite_now_monotonic() {
239        let ts1 = chrono_lite_now();
240        let ts2 = chrono_lite_now();
241        let secs1: u64 = ts1.parse().expect("Should be parseable");
242        let secs2: u64 = ts2.parse().expect("Should be parseable");
243        assert!(secs2 >= secs1, "Timestamps should be monotonically non-decreasing");
244    }
245
246    // =========================================================================
247    // pad_right Tests
248    // =========================================================================
249
250    #[test]
251    fn test_pad_right_shorter_than_width() {
252        let result = pad_right("hi", 10);
253        assert_eq!(result, "hi        ");
254        assert_eq!(result.len(), 10);
255    }
256
257    #[test]
258    fn test_pad_right_exact_width() {
259        let result = pad_right("exact", 5);
260        assert_eq!(result, "exact");
261        assert_eq!(result.len(), 5);
262    }
263
264    #[test]
265    fn test_pad_right_longer_than_width() {
266        let result = pad_right("toolong", 4);
267        assert_eq!(result, "tool");
268        assert_eq!(result.len(), 4);
269    }
270
271    #[test]
272    fn test_pad_right_empty_string() {
273        let result = pad_right("", 5);
274        assert_eq!(result, "     ");
275        assert_eq!(result.len(), 5);
276    }
277
278    #[test]
279    fn test_pad_right_zero_width() {
280        let result = pad_right("test", 0);
281        assert_eq!(result, "");
282        assert_eq!(result.len(), 0);
283    }
284
285    #[test]
286    fn test_pad_right_width_one() {
287        let result = pad_right("abc", 1);
288        assert_eq!(result, "a");
289
290        let result2 = pad_right("x", 1);
291        assert_eq!(result2, "x");
292    }
293
294    #[test]
295    fn test_pad_right_single_char() {
296        let result = pad_right("x", 5);
297        assert_eq!(result, "x    ");
298        assert_eq!(result.len(), 5);
299    }
300}