Skip to content

Commit a1d76c2

Browse files
feat: add base16 cipher implentation
1 parent a82ee7d commit a1d76c2

File tree

3 files changed

+230
-0
lines changed

3 files changed

+230
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
* [Affine Cipher](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/affine_cipher.rs)
3939
* [Another ROT13](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/another_rot13.rs)
4040
* [Baconian Cipher](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/baconian_cipher.rs)
41+
* [Base16](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/base16.rs)
4142
* [Base64](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/base64.rs)
4243
* [Blake2B](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/blake2b.rs)
4344
* [Caesar](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/caesar.rs)

src/ciphers/base16.rs

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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+
}

src/ciphers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod aes;
22
mod affine_cipher;
33
mod another_rot13;
44
mod baconian_cipher;
5+
mod base16;
56
mod base64;
67
mod blake2b;
78
mod caesar;
@@ -28,6 +29,7 @@ pub use self::aes::{aes_decrypt, aes_encrypt, AesKey};
2829
pub use self::affine_cipher::{affine_decrypt, affine_encrypt, affine_generate_key};
2930
pub use self::another_rot13::another_rot13;
3031
pub use self::baconian_cipher::{baconian_decode, baconian_encode};
32+
pub use self::base16::{base16_decode, base16_encode};
3133
pub use self::base64::{base64_decode, base64_encode};
3234
pub use self::blake2b::blake2b;
3335
pub use self::caesar::caesar;

0 commit comments

Comments
 (0)