use crate::{Error, ASCII_OFFSET, MASK_SIX_BITS};
#[inline(always)]
pub fn decode(bytes: &[u8], len: usize) -> Result<String, Error> {
if bytes.len() != (len * 6 + 7) / 8 {
return Err(Error::InvalidBytesLength);
}
Ok(decode_core(bytes, len))
}
#[inline(always)]
pub fn decode_unchecked(bytes: &[u8], len: usize) -> String {
decode_core(bytes, len)
}
#[inline(always)]
fn decode_core(bytes: &[u8], len: usize) -> String {
if len == 0 {
return String::new();
}
let mut result = vec![0u8; len];
let full_chunks = len / 4;
let remaining_chars = len % 4;
let bytes_ptr = bytes.as_ptr();
let result_ptr: *mut u8 = result.as_mut_ptr();
unsafe {
for chunk_idx in 0..full_chunks {
let byte_idx = chunk_idx * 3;
let str_idx = chunk_idx * 4;
let bytes = ((*bytes_ptr.add(byte_idx) as u32) << 16)
| ((*bytes_ptr.add(byte_idx + 1) as u32) << 8)
| (*bytes_ptr.add(byte_idx + 2) as u32);
let char1 = ((bytes >> 18) as u8 & MASK_SIX_BITS) + ASCII_OFFSET;
let char2 = ((bytes >> 12) as u8 & MASK_SIX_BITS) + ASCII_OFFSET;
let char3 = ((bytes >> 6) as u8 & MASK_SIX_BITS) + ASCII_OFFSET;
let char4 = (bytes as u8 & MASK_SIX_BITS) + ASCII_OFFSET;
*result_ptr.add(str_idx) = char1;
*result_ptr.add(str_idx + 1) = char2;
*result_ptr.add(str_idx + 2) = char3;
*result_ptr.add(str_idx + 3) = char4;
}
match remaining_chars {
0 => {},
1 => {
let byte0 = *bytes_ptr.add(full_chunks * 3);
let char1 = (byte0 >> 2) + ASCII_OFFSET;
*result_ptr.add(full_chunks * 4) = char1;
},
2 => {
let byte0 = *bytes_ptr.add(full_chunks * 3);
let byte1 = *bytes_ptr.add(full_chunks * 3 + 1);
let char1 = (byte0 >> 2) + ASCII_OFFSET;
let char2 = (((byte0 & 0b00000011) << 4) | (byte1 >> 4)) + ASCII_OFFSET;
*result_ptr.add(full_chunks * 4) = char1;
*result_ptr.add(full_chunks * 4 + 1) = char2;
},
3 => {
let byte0 = *bytes_ptr.add(full_chunks * 3);
let byte1 = *bytes_ptr.add(full_chunks * 3 + 1);
let byte2 = *bytes_ptr.add(full_chunks * 3 + 2);
let char1 = (byte0 >> 2) + ASCII_OFFSET;
let char2 = (((byte0 & 0b00000011) << 4) | (byte1 >> 4)) + ASCII_OFFSET;
let char3 = (((byte1 & 0b00001111) << 2) | (byte2 >> 6)) + ASCII_OFFSET;
*result_ptr.add(full_chunks * 4) = char1;
*result_ptr.add(full_chunks * 4 + 1) = char2;
*result_ptr.add(full_chunks * 4 + 2) = char3;
},
_ => unreachable!(),
}
}
unsafe { String::from_utf8_unchecked(result) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_empty() {
let bytes = [];
let decoded = decode(&bytes, 0).unwrap();
assert_eq!(decoded, "");
}
#[test]
fn test_decode_basic() {
let input = "HELLO";
let (encoded_bytes, length) = crate::encode(input).unwrap();
let decoded = decode(&encoded_bytes, length).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn test_decode_unchecked() {
let input = "WORLD";
let (encoded_bytes, length) = crate::encode(input).unwrap();
let decoded = decode_unchecked(&encoded_bytes, length);
assert_eq!(decoded, input);
}
#[test]
fn test_invalid_length() {
let bytes = [0u8; 2];
assert!(decode(&bytes, 3).is_err());
}
#[test]
fn test_not_zero_len_but_empty() {
let bytes = [0u8; 0];
let decoded = decode(&bytes, 1);
assert!(decoded.is_err());
}
}