use crate::{Dodecet, DodecetError, Result};
pub fn encode(dodecets: &[Dodecet]) -> String {
dodecets
.iter()
.map(|d| d.to_hex_string())
.collect::<Vec<_>>()
.join("")
}
pub fn decode(s: &str) -> Result<Vec<Dodecet>> {
if !s.len().is_multiple_of(3) {
return Err(DodecetError::InvalidHex);
}
s.as_bytes()
.chunks(3)
.map(|chunk| {
let chunk_str = std::str::from_utf8(chunk).map_err(|_| DodecetError::InvalidHex)?;
Dodecet::from_hex_str(chunk_str)
})
.collect()
}
pub fn encode_dodecet(d: Dodecet) -> String {
d.to_hex_string()
}
pub fn decode_dodecet(s: &str) -> Result<Dodecet> {
if s.len() != 3 {
return Err(DodecetError::InvalidHex);
}
Dodecet::from_hex_str(s)
}
pub fn is_valid(s: &str) -> bool {
if !s.len().is_multiple_of(3) {
return false;
}
s.chars()
.all(|c| c.is_ascii_hexdigit() && c.is_ascii_alphanumeric())
}
pub fn format_spaced(s: &str) -> String {
s.as_bytes()
.chunks(3)
.map(std::str::from_utf8)
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|_| DodecetError::InvalidHex)
.unwrap()
.join(" ")
}
pub fn remove_spaces(s: &str) -> String {
s.chars().filter(|c| !c.is_whitespace()).collect()
}
pub fn to_uppercase(s: &str) -> String {
s.to_uppercase()
}
pub fn to_lowercase(s: &str) -> String {
s.to_lowercase()
}
pub fn dodecet_count(s: &str) -> usize {
s.len() / 3
}
pub fn hex_view(s: &str) -> String {
let mut view = String::new();
let dodecets = decode(s).unwrap_or_default();
for (i, d) in dodecets.iter().enumerate() {
let offset = i * 3;
let hex_val = format!("{:03X}", d.value());
let ascii = dodecet_to_ascii(d);
view.push_str(&format!("{:08X} {} |{}|\n", offset, hex_val, ascii));
}
view
}
fn dodecet_to_ascii(d: &Dodecet) -> String {
let n0 = d.nibble(0).unwrap();
let n1 = d.nibble(1).unwrap();
let n2 = d.nibble(2).unwrap();
let to_char = |n: u8| -> char {
if (0x20..=0x7E).contains(&n) {
n as char
} else {
'.'
}
};
format!("{}{}{}", to_char(n2), to_char(n1), to_char(n0))
}
pub fn equal_ignore_case(a: &str, b: &str) -> bool {
a.to_lowercase() == b.to_lowercase()
}
pub fn xor(a: &str, b: &str) -> Result<String> {
if a.len() != b.len() {
return Err(DodecetError::InvalidHex);
}
let d1 = decode(a)?;
let d2 = decode(b)?;
let result: Vec<Dodecet> = d1
.iter()
.zip(d2.iter())
.map(|(d1, d2)| d1.xor(*d2))
.collect();
Ok(encode(&result))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_decode() {
let dodecets = vec![Dodecet::from_hex(0x123), Dodecet::from_hex(0x456)];
let hex_str = encode(&dodecets);
assert_eq!(hex_str, "123456");
let decoded = decode(&hex_str).unwrap();
assert_eq!(decoded.len(), 2);
assert_eq!(decoded[0].value(), 0x123);
assert_eq!(decoded[1].value(), 0x456);
}
#[test]
fn test_encode_decode_dodecet() {
let d = Dodecet::from_hex(0xAB0);
assert_eq!(encode_dodecet(d), "AB0");
let d2 = decode_dodecet("AB0").unwrap();
assert_eq!(d2.value(), 0xAB0);
}
#[test]
fn test_is_valid() {
assert!(is_valid("123456"));
assert!(!is_valid("12345")); assert!(is_valid("")); }
#[test]
fn test_format_spaced() {
assert_eq!(format_spaced("123456789"), "123 456 789");
}
#[test]
fn test_remove_spaces() {
assert_eq!(remove_spaces("123 456 789"), "123456789");
}
#[test]
fn test_uppercase_lowercase() {
assert_eq!(to_uppercase("abc"), "ABC");
assert_eq!(to_lowercase("ABC"), "abc");
}
#[test]
fn test_dodecet_count() {
assert_eq!(dodecet_count("123456789"), 3);
assert_eq!(dodecet_count(""), 0);
}
#[test]
fn test_equal_ignore_case() {
assert!(equal_ignore_case("ABC123", "abc123"));
assert!(!equal_ignore_case("ABC123", "ABC124"));
}
#[test]
fn test_xor() {
let result = xor("FFF", "123").unwrap();
assert_eq!(result, "EDC");
let result = xor("000", "000").unwrap();
assert_eq!(result, "000");
}
#[test]
fn test_invalid_hex() {
assert!(decode("GHI").is_err());
assert!(decode("12345").is_err());
}
#[test]
fn test_hex_view() {
let view = hex_view("123456");
assert!(view.contains("123"));
assert!(view.contains("456"));
}
}