#![deny(missing_docs)]
#![deny(unsafe_code)]
const ILLEGALS: [u8; 6] = [
0, 10, 13, 34, 38, 92, ];
const SHORTENED: u8 = 0b111;
pub fn encode(data: &[u8]) -> String {
if data.is_empty() {
return String::new();
}
let mut cur_index = 0;
let mut cur_bit = 0;
let mut result = Vec::new();
let mut get7 = || -> Option<u8> {
if cur_index >= data.len() {
return None;
}
let first_byte = data[cur_index];
let first_part = ((0b11111110 >> cur_bit) & first_byte) << cur_bit;
let first_part = first_part >> 1;
cur_bit += 7;
if cur_bit < 8 {
return Some(first_part);
}
cur_bit -= 8;
cur_index += 1;
if cur_index >= data.len() {
return Some(first_part);
}
let second_byte = data[cur_index] as u16;
let mut second_part = ((0xFF00u16 >> cur_bit) & second_byte) & 0xFF;
if cur_bit < 8 {
second_part >>= 8 - cur_bit;
}
let second_part = second_part as u8;
Some(first_part | second_part)
};
while let Some(bits) = get7() {
if let Some(illegal_index) = ILLEGALS.iter().position(|&x| x == bits) {
let next_bits = get7();
let mut b1 = 0b11000010; let mut b2 = 0b10000000;
if let Some(next_bits_value) = next_bits {
b1 |= ((illegal_index as u8) & 0b111) << 2;
let first_bit = if (next_bits_value & 0b01000000) > 0 {
1
} else {
0
};
b1 |= first_bit;
b2 |= next_bits_value & 0b00111111;
} else {
b1 |= (SHORTENED & 0b111) << 2;
let final_bits = bits;
let first_bit = if (final_bits & 0b01000000) > 0 { 1 } else { 0 };
b1 |= first_bit;
b2 |= final_bits & 0b00111111;
}
result.push(b1);
result.push(b2);
} else {
result.push(bits);
}
}
String::from_utf8(result).unwrap_or_else(|_| String::new())
}
pub fn decode(encoded: &str) -> Result<Vec<u8>, String> {
if encoded.is_empty() {
return Ok(Vec::new());
}
let mut decoded = Vec::new();
let mut cur_byte = 0u8;
let mut bit_of_byte = 0;
let mut push7 = |byte: u8| {
let byte = byte << 1;
cur_byte |= byte >> bit_of_byte;
bit_of_byte += 7;
if bit_of_byte >= 8 {
decoded.push(cur_byte);
bit_of_byte -= 8;
cur_byte = byte << (7 - bit_of_byte);
}
};
let chars: Vec<char> = encoded.chars().collect();
let mut i = 0;
while i < chars.len() {
let c = chars[i] as u32;
if c > 127 {
let illegal_index = (c >> 8) & 7;
if illegal_index != SHORTENED as u32 {
push7(ILLEGALS[illegal_index as usize]);
}
push7((c & 127) as u8);
} else {
push7(c as u8);
}
i += 1;
}
Ok(decoded)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_input() {
assert_eq!(encode(&[]), "");
assert_eq!(decode("").unwrap(), Vec::<u8>::new());
}
#[test]
fn test_single_byte() {
let data = [0x42];
let encoded = encode(&data);
let decoded = decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_dangerous_characters() {
for &dangerous_char in &ILLEGALS {
let data = [dangerous_char];
let encoded = encode(&data);
let decoded = decode(&encoded).unwrap();
assert_eq!(
decoded, data,
"Failed for dangerous character: {dangerous_char}"
);
}
}
#[test]
fn test_mixed_data() {
let data = b"Hello\nWorld\0Test\"Data&More\\Path";
let encoded = encode(data);
let decoded = decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_binary_data() {
let data: Vec<u8> = (0..=255).collect();
let encoded = encode(&data);
let decoded = decode(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn test_round_trip_various_sizes() {
let test_cases = vec![
vec![],
vec![0],
vec![65], b"Hello".to_vec(),
b"Hello, World!".to_vec(),
(0u8..100).collect(),
];
for data in test_cases {
let encoded = encode(&data);
let decoded = decode(&encoded).unwrap();
assert_eq!(decoded, data, "Round-trip failed for: {data:?}");
}
}
#[test]
fn test_efficiency() {
let large_data: Vec<u8> = (0..1000).map(|i| (i % 256) as u8).collect();
let encoded = encode(&large_data);
let input_bits = large_data.len() * 8;
let output_bits = encoded.len() * 8;
let efficiency = input_bits as f64 / output_bits as f64;
assert!(
efficiency > 0.85,
"Efficiency too low: {:.1}%",
efficiency * 100.0
);
assert!(
efficiency <= 0.875,
"Efficiency impossibly high: {:.1}%",
efficiency * 100.0
);
}
#[test]
fn test_vs_base64_efficiency() {
let test_data = b"The quick brown fox jumps over the lazy dog. This is a test of Base122 efficiency vs Base64.";
let base122_encoded = encode(test_data);
let base64_size = (test_data.len() * 4).div_ceil(3);
assert!(
base122_encoded.len() < base64_size,
"Base122 ({} bytes) should be smaller than Base64 ({} bytes)",
base122_encoded.len(),
base64_size
);
}
#[test]
fn test_decode_invalid_input() {
assert!(decode("valid ascii").is_ok());
}
}