1use crate::{Dodecet, DodecetError, Result};
6
7pub fn encode(dodecets: &[Dodecet]) -> String {
21 dodecets
22 .iter()
23 .map(|d| d.to_hex_string())
24 .collect::<Vec<_>>()
25 .join("")
26}
27
28pub fn decode(s: &str) -> Result<Vec<Dodecet>> {
47 if !s.len().is_multiple_of(3) {
48 return Err(DodecetError::InvalidHex);
49 }
50
51 s.as_bytes()
52 .chunks(3)
53 .map(|chunk| {
54 let chunk_str = std::str::from_utf8(chunk).map_err(|_| DodecetError::InvalidHex)?;
55 Dodecet::from_hex_str(chunk_str)
56 })
57 .collect()
58}
59
60pub fn encode_dodecet(d: Dodecet) -> String {
71 d.to_hex_string()
72}
73
74pub fn decode_dodecet(s: &str) -> Result<Dodecet> {
85 if s.len() != 3 {
86 return Err(DodecetError::InvalidHex);
87 }
88 Dodecet::from_hex_str(s)
89}
90
91pub fn is_valid(s: &str) -> bool {
105 if !s.len().is_multiple_of(3) {
106 return false;
107 }
108
109 s.chars()
110 .all(|c| c.is_ascii_hexdigit() && c.is_ascii_alphanumeric())
111}
112
113pub fn format_spaced(s: &str) -> String {
126 s.as_bytes()
127 .chunks(3)
128 .map(std::str::from_utf8)
129 .collect::<std::result::Result<Vec<_>, _>>()
130 .map_err(|_| DodecetError::InvalidHex)
131 .unwrap()
132 .join(" ")
133}
134
135pub fn remove_spaces(s: &str) -> String {
146 s.chars().filter(|c| !c.is_whitespace()).collect()
147}
148
149pub fn to_uppercase(s: &str) -> String {
159 s.to_uppercase()
160}
161
162pub fn to_lowercase(s: &str) -> String {
172 s.to_lowercase()
173}
174
175pub fn dodecet_count(s: &str) -> usize {
185 s.len() / 3
186}
187
188pub fn hex_view(s: &str) -> String {
201 let mut view = String::new();
202 let dodecets = decode(s).unwrap_or_default();
203
204 for (i, d) in dodecets.iter().enumerate() {
205 let offset = i * 3;
206 let hex_val = format!("{:03X}", d.value());
207 let ascii = dodecet_to_ascii(d);
208
209 view.push_str(&format!("{:08X} {} |{}|\n", offset, hex_val, ascii));
210 }
211
212 view
213}
214
215fn dodecet_to_ascii(d: &Dodecet) -> String {
219 let n0 = d.nibble(0).unwrap();
220 let n1 = d.nibble(1).unwrap();
221 let n2 = d.nibble(2).unwrap();
222
223 let to_char = |n: u8| -> char {
224 if (0x20..=0x7E).contains(&n) {
225 n as char
226 } else {
227 '.'
228 }
229 };
230
231 format!("{}{}{}", to_char(n2), to_char(n1), to_char(n0))
232}
233
234pub fn equal_ignore_case(a: &str, b: &str) -> bool {
244 a.to_lowercase() == b.to_lowercase()
245}
246
247pub fn xor(a: &str, b: &str) -> Result<String> {
258 if a.len() != b.len() {
259 return Err(DodecetError::InvalidHex);
260 }
261
262 let d1 = decode(a)?;
263 let d2 = decode(b)?;
264
265 let result: Vec<Dodecet> = d1
266 .iter()
267 .zip(d2.iter())
268 .map(|(d1, d2)| d1.xor(*d2))
269 .collect();
270
271 Ok(encode(&result))
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_encode_decode() {
280 let dodecets = vec![Dodecet::from_hex(0x123), Dodecet::from_hex(0x456)];
281 let hex_str = encode(&dodecets);
282 assert_eq!(hex_str, "123456");
283
284 let decoded = decode(&hex_str).unwrap();
285 assert_eq!(decoded.len(), 2);
286 assert_eq!(decoded[0].value(), 0x123);
287 assert_eq!(decoded[1].value(), 0x456);
288 }
289
290 #[test]
291 fn test_encode_decode_dodecet() {
292 let d = Dodecet::from_hex(0xAB0);
293 assert_eq!(encode_dodecet(d), "AB0");
294
295 let d2 = decode_dodecet("AB0").unwrap();
296 assert_eq!(d2.value(), 0xAB0);
297 }
298
299 #[test]
300 fn test_is_valid() {
301 assert!(is_valid("123456"));
302 assert!(!is_valid("12345")); assert!(is_valid("")); }
305
306 #[test]
307 fn test_format_spaced() {
308 assert_eq!(format_spaced("123456789"), "123 456 789");
309 }
310
311 #[test]
312 fn test_remove_spaces() {
313 assert_eq!(remove_spaces("123 456 789"), "123456789");
314 }
315
316 #[test]
317 fn test_uppercase_lowercase() {
318 assert_eq!(to_uppercase("abc"), "ABC");
319 assert_eq!(to_lowercase("ABC"), "abc");
320 }
321
322 #[test]
323 fn test_dodecet_count() {
324 assert_eq!(dodecet_count("123456789"), 3);
325 assert_eq!(dodecet_count(""), 0);
326 }
327
328 #[test]
329 fn test_equal_ignore_case() {
330 assert!(equal_ignore_case("ABC123", "abc123"));
331 assert!(!equal_ignore_case("ABC123", "ABC124"));
332 }
333
334 #[test]
335 fn test_xor() {
336 let result = xor("FFF", "123").unwrap();
337 assert_eq!(result, "EDC");
338
339 let result = xor("000", "000").unwrap();
340 assert_eq!(result, "000");
341 }
342
343 #[test]
344 fn test_invalid_hex() {
345 assert!(decode("GHI").is_err());
346 assert!(decode("12345").is_err());
347 }
348
349 #[test]
350 fn test_hex_view() {
351 let view = hex_view("123456");
352 assert!(view.contains("123"));
353 assert!(view.contains("456"));
354 }
355}