buup/transformers/
hex_to_ascii.rs

1use crate::{Transform, TransformError, TransformerCategory};
2
3/// Transformer to convert hexadecimal representation back to ASCII characters.
4///
5/// # Example
6/// ```rust
7/// use buup::{Transform, transformers::HexToAscii};
8/// let transformer = HexToAscii;
9/// assert_eq!(transformer.transform("48656c6c6f").unwrap(), "Hello");
10/// assert_eq!(transformer.transform("").unwrap(), "");
11/// ```
12#[derive(Debug, Clone, PartialEq, Eq, Default)]
13pub struct HexToAscii;
14
15impl Transform for HexToAscii {
16    fn name(&self) -> &'static str {
17        "Hex to ASCII"
18    }
19
20    fn id(&self) -> &'static str {
21        "hex_to_ascii"
22    }
23
24    fn description(&self) -> &'static str {
25        "Convert hexadecimal representation back to ASCII characters."
26    }
27
28    fn category(&self) -> TransformerCategory {
29        TransformerCategory::Decoder
30    }
31
32    fn transform(&self, input: &str) -> Result<String, TransformError> {
33        // Ensure input has an even number of characters
34        if input.len() % 2 != 0 {
35            return Err(TransformError::InvalidArgument(
36                "Input hex string must have an even number of characters".into(),
37            ));
38        }
39
40        // Remove common prefixes like 0x or spaces
41        let cleaned_input = input.trim().trim_start_matches("0x");
42
43        let mut bytes = Vec::with_capacity(cleaned_input.len() / 2);
44        let mut chars = cleaned_input.chars();
45
46        while let (Some(h), Some(l)) = (chars.next(), chars.next()) {
47            let hex_pair = format!("{}{}", h, l);
48            match u8::from_str_radix(&hex_pair, 16) {
49                Ok(byte) => bytes.push(byte),
50                Err(_) => {
51                    return Err(TransformError::InvalidArgument(
52                        format!("Invalid hex character sequence found: '{}'", hex_pair).into(),
53                    ))
54                }
55            }
56        }
57
58        String::from_utf8(bytes).map_err(|_| TransformError::Utf8Error)
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_hex_to_ascii() {
68        let transformer = HexToAscii;
69        assert_eq!(transformer.transform("48656c6c6f").unwrap(), "Hello");
70        assert_eq!(transformer.transform("576f726c64").unwrap(), "World");
71        assert_eq!(transformer.transform("313233").unwrap(), "123");
72        assert_eq!(transformer.transform("20").unwrap(), " "); // Space character
73        assert_eq!(transformer.transform("").unwrap(), ""); // Empty string
74        assert_eq!(transformer.transform("214023").unwrap(), "!@#");
75    }
76
77    #[test]
78    fn test_invalid_hex() {
79        let transformer = HexToAscii;
80        // Invalid hex character 'G'
81        assert!(matches!(
82            transformer.transform("48656c6c6G"),
83            Err(TransformError::InvalidArgument(_))
84        ));
85        // Odd number of characters
86        assert!(matches!(
87            transformer.transform("48656c6c6"),
88            Err(TransformError::InvalidArgument(_))
89        ));
90    }
91
92    #[test]
93    fn test_non_utf8_output() {
94        let transformer = HexToAscii;
95        // Represents invalid UTF-8 sequence (e.g., lone continuation byte)
96        assert!(matches!(
97            transformer.transform("80"),
98            Err(TransformError::Utf8Error)
99        ));
100        assert!(matches!(
101            transformer.transform("c0"),
102            Err(TransformError::Utf8Error)
103        )); // Overlong encoding start
104    }
105
106    #[test]
107    fn test_properties() {
108        let transformer = HexToAscii;
109        assert_eq!(transformer.name(), "Hex to ASCII");
110        assert_eq!(transformer.id(), "hex_to_ascii");
111        assert_eq!(
112            transformer.description(),
113            "Convert hexadecimal representation back to ASCII characters."
114        );
115        assert_eq!(transformer.category(), TransformerCategory::Decoder);
116    }
117}