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        "Decodes a hexadecimal string into its ASCII representation."
26    }
27
28    fn category(&self) -> TransformerCategory {
29        TransformerCategory::Decoder
30    }
31
32    fn default_test_input(&self) -> &'static str {
33        "48656c6c6f"
34    }
35
36    fn transform(&self, input: &str) -> Result<String, TransformError> {
37        // Ensure input has an even number of characters
38        if !input.len().is_multiple_of(2) {
39            return Err(TransformError::InvalidArgument(
40                "Input hex string must have an even number of characters".into(),
41            ));
42        }
43
44        // Remove common prefixes like 0x or spaces
45        let cleaned_input = input.trim().trim_start_matches("0x");
46
47        let mut bytes = Vec::with_capacity(cleaned_input.len() / 2);
48        let mut chars = cleaned_input.chars();
49
50        while let (Some(h), Some(l)) = (chars.next(), chars.next()) {
51            let hex_pair = format!("{}{}", h, l);
52            match u8::from_str_radix(&hex_pair, 16) {
53                Ok(byte) => bytes.push(byte),
54                Err(_) => {
55                    return Err(TransformError::InvalidArgument(
56                        format!("Invalid hex character sequence found: '{}'", hex_pair).into(),
57                    ))
58                }
59            }
60        }
61
62        String::from_utf8(bytes).map_err(|_| TransformError::Utf8Error)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_hex_to_ascii() {
72        let transformer = HexToAscii;
73        assert_eq!(
74            transformer
75                .transform(transformer.default_test_input())
76                .unwrap(),
77            "Hello"
78        );
79        assert_eq!(
80            transformer.transform("68656c6c6f20776f726c64").unwrap(),
81            "hello world"
82        );
83        assert_eq!(transformer.transform("").unwrap(), "");
84        assert_eq!(transformer.transform("313233").unwrap(), "123");
85    }
86
87    #[test]
88    fn test_invalid_hex() {
89        let transformer = HexToAscii;
90        // Invalid hex character 'G'
91        assert!(matches!(
92            transformer.transform("48656c6c6G"),
93            Err(TransformError::InvalidArgument(_))
94        ));
95        // Odd number of characters
96        assert!(matches!(
97            transformer.transform("48656c6c6"),
98            Err(TransformError::InvalidArgument(_))
99        ));
100    }
101
102    #[test]
103    fn test_non_utf8_output() {
104        let transformer = HexToAscii;
105        // Represents invalid UTF-8 sequence (e.g., lone continuation byte)
106        assert!(matches!(
107            transformer.transform("80"),
108            Err(TransformError::Utf8Error)
109        ));
110        assert!(matches!(
111            transformer.transform("c0"),
112            Err(TransformError::Utf8Error)
113        )); // Overlong encoding start
114    }
115
116    #[test]
117    fn test_properties() {
118        let transformer = HexToAscii;
119        assert_eq!(transformer.name(), "Hex to ASCII");
120        assert_eq!(transformer.id(), "hex_to_ascii");
121        assert_eq!(
122            transformer.description(),
123            "Decodes a hexadecimal string into its ASCII representation."
124        );
125        assert_eq!(transformer.category(), TransformerCategory::Decoder);
126    }
127}