|
| 1 | +//! Base16 encoding and decoding implementation. |
| 2 | +//! |
| 3 | +//! Base16, also known as hexadecimal encoding, represents binary data using 16 ASCII characters |
| 4 | +//! (0-9 and A-F). Each byte is represented by exactly two hexadecimal digits. |
| 5 | +//! |
| 6 | +//! This implementation follows RFC 3548 Section 6 specifications: |
| 7 | +//! - Uses uppercase characters (A-F) for encoding |
| 8 | +//! - Requires uppercase input for decoding |
| 9 | +//! - Validates that encoded data has an even number of characters |
| 10 | +
|
| 11 | +/// Encodes the given bytes into base16 (hexadecimal) format. |
| 12 | +/// |
| 13 | +/// Each byte is converted to two uppercase hexadecimal characters. |
| 14 | +/// |
| 15 | +/// # Arguments |
| 16 | +/// |
| 17 | +/// * `data` - A byte slice to encode |
| 18 | +/// |
| 19 | +/// # Returns |
| 20 | +/// |
| 21 | +/// A `String` containing the uppercase hexadecimal representation of the input data. |
| 22 | +/// |
| 23 | +/// # Examples |
| 24 | +/// |
| 25 | +/// ``` |
| 26 | +/// use the_algorithms_rust::ciphers::base16_encode; |
| 27 | +/// assert_eq!(base16_encode(b"Hello World!"), "48656C6C6F20576F726C6421"); |
| 28 | +/// assert_eq!(base16_encode(b"HELLO WORLD!"), "48454C4C4F20574F524C4421"); |
| 29 | +/// assert_eq!(base16_encode(b""), ""); |
| 30 | +/// ``` |
| 31 | +pub fn base16_encode(data: &[u8]) -> String { |
| 32 | + use std::fmt::Write; |
| 33 | + data.iter().fold(String::new(), |mut output, byte| { |
| 34 | + write!(output, "{byte:02X}").unwrap(); |
| 35 | + output |
| 36 | + }) |
| 37 | +} |
| 38 | + |
| 39 | +/// Decodes base16 (hexadecimal) encoded data into bytes. |
| 40 | +/// |
| 41 | +/// This function validates the input according to RFC 3548 Section 6: |
| 42 | +/// - The data must have an even number of characters |
| 43 | +/// - The data must only contain uppercase hexadecimal characters (0-9, A-F) |
| 44 | +/// |
| 45 | +/// # Arguments |
| 46 | +/// |
| 47 | +/// * `data` - A string slice containing uppercase hexadecimal characters |
| 48 | +/// |
| 49 | +/// # Returns |
| 50 | +/// |
| 51 | +/// * `Ok(Vec<u8>)` - Successfully decoded bytes |
| 52 | +/// * `Err(String)` - Error message if the input is invalid |
| 53 | +/// |
| 54 | +/// # Errors |
| 55 | +/// |
| 56 | +/// Returns an error if: |
| 57 | +/// - The input has an odd number of characters |
| 58 | +/// - The input contains characters other than 0-9 and A-F |
| 59 | +/// - The input contains lowercase hexadecimal characters (a-f) |
| 60 | +/// |
| 61 | +/// # Examples |
| 62 | +/// |
| 63 | +/// ``` |
| 64 | +/// use the_algorithms_rust::ciphers::base16_decode; |
| 65 | +/// assert_eq!(base16_decode("48656C6C6F20576F726C6421").unwrap(), b"Hello World!"); |
| 66 | +/// assert_eq!(base16_decode("48454C4C4F20574F524C4421").unwrap(), b"HELLO WORLD!"); |
| 67 | +/// assert_eq!(base16_decode("").unwrap(), b""); |
| 68 | +/// ``` |
| 69 | +/// |
| 70 | +/// Invalid inputs return errors: |
| 71 | +/// |
| 72 | +/// ``` |
| 73 | +/// use the_algorithms_rust::ciphers::base16_decode; |
| 74 | +/// assert!(base16_decode("486").is_err()); // Odd number of characters |
| 75 | +/// assert!(base16_decode("48656c6c6f20576f726c6421").is_err()); // Lowercase hex |
| 76 | +/// assert!(base16_decode("This is not base16 encoded data.").is_err()); // Invalid characters |
| 77 | +/// ``` |
| 78 | +pub fn base16_decode(data: &str) -> Result<Vec<u8>, String> { |
| 79 | + // Check if data has an even number of characters |
| 80 | + if !data.len().is_multiple_of(2) { |
| 81 | + return Err("Base16 encoded data is invalid:\n\ |
| 82 | + Data does not have an even number of hex digits." |
| 83 | + .to_string()); |
| 84 | + } |
| 85 | + |
| 86 | + // Check if all characters are valid uppercase hexadecimal (0-9, A-F) |
| 87 | + // This follows RFC 3548 section 6 which specifies uppercase |
| 88 | + if !data |
| 89 | + .chars() |
| 90 | + .all(|c| c.is_ascii_hexdigit() && !c.is_lowercase()) |
| 91 | + { |
| 92 | + return Err("Base16 encoded data is invalid:\n\ |
| 93 | + Data is not uppercase hex or it contains invalid characters." |
| 94 | + .to_string()); |
| 95 | + } |
| 96 | + |
| 97 | + // Decode pairs of hexadecimal characters into bytes |
| 98 | + let mut result = Vec::with_capacity(data.len() / 2); |
| 99 | + for i in (0..data.len()).step_by(2) { |
| 100 | + let hex_pair = &data[i..i + 2]; |
| 101 | + match u8::from_str_radix(hex_pair, 16) { |
| 102 | + Ok(byte) => result.push(byte), |
| 103 | + Err(_) => { |
| 104 | + return Err("Base16 encoded data is invalid:\n\ |
| 105 | + Failed to decode hex pair." |
| 106 | + .to_string()) |
| 107 | + } |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + Ok(result) |
| 112 | +} |
| 113 | + |
| 114 | +#[cfg(test)] |
| 115 | +mod tests { |
| 116 | + use super::*; |
| 117 | + |
| 118 | + #[test] |
| 119 | + fn test_encode_hello_world() { |
| 120 | + assert_eq!(base16_encode(b"Hello World!"), "48656C6C6F20576F726C6421"); |
| 121 | + } |
| 122 | + |
| 123 | + #[test] |
| 124 | + fn test_encode_hello_world_uppercase() { |
| 125 | + assert_eq!(base16_encode(b"HELLO WORLD!"), "48454C4C4F20574F524C4421"); |
| 126 | + } |
| 127 | + |
| 128 | + #[test] |
| 129 | + fn test_encode_empty() { |
| 130 | + assert_eq!(base16_encode(b""), ""); |
| 131 | + } |
| 132 | + |
| 133 | + #[test] |
| 134 | + fn test_encode_special_characters() { |
| 135 | + assert_eq!(base16_encode(b"\x00\x01\xFF"), "0001FF"); |
| 136 | + } |
| 137 | + |
| 138 | + #[test] |
| 139 | + fn test_encode_all_bytes() { |
| 140 | + let data: Vec<u8> = (0..=255).collect(); |
| 141 | + let encoded = base16_encode(&data); |
| 142 | + assert_eq!(encoded.len(), 512); // 256 bytes * 2 hex chars each |
| 143 | + } |
| 144 | + |
| 145 | + #[test] |
| 146 | + fn test_decode_hello_world() { |
| 147 | + assert_eq!( |
| 148 | + base16_decode("48656C6C6F20576F726C6421").unwrap(), |
| 149 | + b"Hello World!" |
| 150 | + ); |
| 151 | + } |
| 152 | + |
| 153 | + #[test] |
| 154 | + fn test_decode_hello_world_uppercase() { |
| 155 | + assert_eq!( |
| 156 | + base16_decode("48454C4C4F20574F524C4421").unwrap(), |
| 157 | + b"HELLO WORLD!" |
| 158 | + ); |
| 159 | + } |
| 160 | + |
| 161 | + #[test] |
| 162 | + fn test_decode_empty() { |
| 163 | + assert_eq!(base16_decode("").unwrap(), b""); |
| 164 | + } |
| 165 | + |
| 166 | + #[test] |
| 167 | + fn test_decode_special_characters() { |
| 168 | + assert_eq!(base16_decode("0001FF").unwrap(), b"\x00\x01\xFF"); |
| 169 | + } |
| 170 | + |
| 171 | + #[test] |
| 172 | + fn test_decode_odd_length() { |
| 173 | + let result = base16_decode("486"); |
| 174 | + assert!(result.is_err()); |
| 175 | + assert!(result |
| 176 | + .unwrap_err() |
| 177 | + .contains("does not have an even number of hex digits")); |
| 178 | + } |
| 179 | + |
| 180 | + #[test] |
| 181 | + fn test_decode_lowercase_hex() { |
| 182 | + let result = base16_decode("48656c6c6f20576f726c6421"); |
| 183 | + assert!(result.is_err()); |
| 184 | + assert!(result |
| 185 | + .unwrap_err() |
| 186 | + .contains("not uppercase hex or it contains invalid characters")); |
| 187 | + } |
| 188 | + |
| 189 | + #[test] |
| 190 | + fn test_decode_invalid_characters() { |
| 191 | + let result = base16_decode("This is not base16 encoded data."); |
| 192 | + assert!(result.is_err()); |
| 193 | + assert!(result |
| 194 | + .unwrap_err() |
| 195 | + .contains("not uppercase hex or it contains invalid characters")); |
| 196 | + } |
| 197 | + |
| 198 | + #[test] |
| 199 | + fn test_decode_mixed_case() { |
| 200 | + let result = base16_decode("48656C6c6F"); // Mixed upper and lowercase |
| 201 | + assert!(result.is_err()); |
| 202 | + } |
| 203 | + |
| 204 | + #[test] |
| 205 | + fn test_roundtrip() { |
| 206 | + let original = b"The quick brown fox jumps over the lazy dog"; |
| 207 | + let encoded = base16_encode(original); |
| 208 | + let decoded = base16_decode(&encoded).unwrap(); |
| 209 | + assert_eq!(decoded, original); |
| 210 | + } |
| 211 | + |
| 212 | + #[test] |
| 213 | + fn test_roundtrip_all_bytes() { |
| 214 | + let original: Vec<u8> = (0..=255).collect(); |
| 215 | + let encoded = base16_encode(&original); |
| 216 | + let decoded = base16_decode(&encoded).unwrap(); |
| 217 | + assert_eq!(decoded, original); |
| 218 | + } |
| 219 | + |
| 220 | + #[test] |
| 221 | + fn test_roundtrip_empty() { |
| 222 | + let original = b""; |
| 223 | + let encoded = base16_encode(original); |
| 224 | + let decoded = base16_decode(&encoded).unwrap(); |
| 225 | + assert_eq!(decoded, original); |
| 226 | + } |
| 227 | +} |
0 commit comments