pub fn format_hex_bytes(bytes: &[u8]) -> String { bytes.chunks(2) .map(|chunk| chunk.iter().map(|b| format!("{:02X}", b)).collect::()) .collect::>() .join(" ") } pub fn parse_access_code(code: &String) -> Option<[u8; 10]> { let matrix_code = code.replace(" ", ""); if code.chars().any(|c| !c.is_ascii_hexdigit() && !c.is_whitespace()) && matrix_code.len() != 20 { return None; } let mut bytes: [u8; 10] = [0; 10]; for i in 0..bytes.len() { let slice_str = &matrix_code[i*2..i*2+2]; let slice_val = u8::from_str_radix(slice_str, 16).ok()?; bytes[i] = slice_val; } Some(bytes) } pub fn parse_idm_hex(idm_hex: &String) -> Option<[u8; 8]> { if idm_hex.len() != 16 || idm_hex.chars().any(|c| !c.is_ascii_hexdigit()) { return None; } let mut idm_bytes: [u8; 8] = [0; 8]; for i in 0..idm_bytes.len() { let slice_str = &idm_hex[i*2..i*2+2]; let slice_val = u8::from_str_radix(slice_str, 16).ok()?; idm_bytes[i] = slice_val; } Some(idm_bytes) } #[cfg(test)] mod tests { use super::*; mod format_hex_bytes_tests { use super::*; #[test] fn formats_empty_bytes() { assert_eq!(format_hex_bytes(&[]), ""); } #[test] fn formats_access_code_bytes() { let bytes = [0x00, 0x10, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56]; assert_eq!(format_hex_bytes(&bytes), "0010 1234 5678 9012 3456"); } } mod parse_access_code_tests { use super::*; #[test] fn parses_full_20_digit_code() { let code = "00101234567890123456".to_string(); let result = parse_access_code(&code); assert!(result.is_some()); assert_eq!(result.unwrap(), [0x00, 0x10, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56]); } #[test] fn parses_code_with_spaces() { let code = "0010 1234 5678 9012 3456".to_string(); let result = parse_access_code(&code); assert!(result.is_some()); assert_eq!(result.unwrap(), [0x00, 0x10, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56]); } #[test] fn parses_code_with_leading_zeros() { let code = "00000000000000001234".to_string(); // Valid 20-digit code with leading zeros let result = parse_access_code(&code); assert!(result.is_some()); assert_eq!(result.unwrap(), [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34]); } #[test] fn returns_none_for_invalid_hex() { let code = "GHIJKLMNOPQRSTUVWXYZ".to_string(); let result = parse_access_code(&code); assert!(result.is_none()); } #[test] fn parses_code_with_all_zeros() { let code = "00000000000000000000".to_string(); let result = parse_access_code(&code); assert!(result.is_some()); assert_eq!(result.unwrap(), [0x00; 10]); } } mod parse_idm_hex_tests { use super::*; #[test] fn parses_full_16_char_idm() { let idm = "0102030405060708".to_string(); let result = parse_idm_hex(&idm); assert!(result.is_some()); assert_eq!(result.unwrap(), [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); } #[test] fn parses_short_idm_returns_none() { let idm = "1234".to_string(); let result = parse_idm_hex(&idm); // IDm is too short - should fail assert!(result.is_none()); } #[test] fn returns_none_for_too_long_idm() { let idm = "01020304050607080910".to_string(); // 20 chars let result = parse_idm_hex(&idm); assert!(result.is_none()); } #[test] fn returns_none_for_non_hex_characters() { let idm = "0102GHIJ".to_string(); let result = parse_idm_hex(&idm); assert!(result.is_none()); } #[test] fn parses_empty_idm_returns_none() { let idm = "".to_string(); let result = parse_idm_hex(&idm); // Empty IDm should fail assert!(result.is_none()); } } }