use crate::alphabet::{Alphabet, INVALID};
use crate::error::DecodeError;
pub(crate) fn encode_bulk<A: Alphabet>(input: &[u8], out: &mut [u8], groups: usize) -> usize {
if groups == 0 {
return 0;
}
if groups < 4 {
let mut at = 0;
while at < groups && at * 3 + 3 <= input.len() && at * 4 + 4 <= out.len() {
let group = [input[at * 3], input[at * 3 + 1], input[at * 3 + 2]];
encode_group::<A>(group, &mut out[at * 4..]);
at += 1;
}
return at;
}
let chars = A::ENCODE;
let (taken, room) = (input.len(), out.len());
debug_assert!(groups * 3 <= taken && groups * 4 <= room);
let mut done = 0;
let pairs = groups / 2;
for (group, slot) in input[..(pairs * 6).min(taken)]
.as_chunks::<6>()
.0
.iter()
.zip(out[..(pairs * 8).min(room)].as_chunks_mut::<8>().0)
{
let mut word = [0u8; 8];
word[..6].copy_from_slice(group);
let word = u64::from_be_bytes(word) >> 16;
for (place, spelled) in slot.iter_mut().enumerate() {
*spelled = chars[((word >> (42 - place * 6)) & 0x3F) as usize];
}
done += 2;
}
if done == pairs * 2 {
let (read, written) = (done * 3, done * 4);
let rest = groups - done;
for (group, slot) in input[read.min(taken)..(read + rest * 3).min(taken)]
.as_chunks::<3>()
.0
.iter()
.zip(
out[written.min(room)..(written + rest * 4).min(room)]
.as_chunks_mut::<4>()
.0,
)
{
let value =
(u32::from(group[0]) << 16) | (u32::from(group[1]) << 8) | u32::from(group[2]);
for (place, spelled) in slot.iter_mut().enumerate() {
*spelled = chars[((value >> (18 - place * 6)) & 0x3F) as usize];
}
done += 1;
}
}
done
}
pub(crate) fn encode_group<A: Alphabet>(group: [u8; 3], out: &mut [u8]) {
let value = (u32::from(group[0]) << 16) | (u32::from(group[1]) << 8) | u32::from(group[2]);
for (place, spelled) in out[..4].iter_mut().enumerate() {
*spelled = A::ENCODE[((value >> (18 - place * 6)) & 0x3F) as usize];
}
}
pub(crate) fn encode_rest<A: Alphabet>(input: &[u8], out: &mut [u8], is_padded: bool) -> usize {
let chars = A::ENCODE;
let (spelled, count) = match input {
[byte] => {
let value = *byte as usize;
let spelled = [chars[value >> 2], chars[(value << 4) & 0x3F], b'=', b'='];
(spelled, if is_padded { 4 } else { 2 })
}
[high, low] => {
let value = ((*high as usize) << 8) | (*low as usize);
let spelled = [
chars[value >> 10],
chars[(value >> 4) & 0x3F],
chars[(value << 2) & 0x3F],
b'=',
];
(spelled, if is_padded { 4 } else { 3 })
}
rest => {
debug_assert!(rest.is_empty(), "a whole group reached the tail");
return 0;
}
};
out[..count].copy_from_slice(&spelled[..count]);
count
}
pub(crate) fn decode_bulk<A: Alphabet>(input: &[u8], out: &mut [u8], quads: usize) -> usize {
if quads == 0 {
return 0;
}
let values = A::DECODE;
let (taken, room) = (input.len(), out.len());
debug_assert!(quads * 4 <= taken && quads * 3 <= room);
let mut done = 0;
for (group, slot) in input[..(quads * 4).min(taken)]
.as_chunks::<4>()
.0
.iter()
.zip(out[..(quads * 3).min(room)].as_chunks_mut::<3>().0)
{
let first = values[group[0] as usize];
let second = values[group[1] as usize];
let third = values[group[2] as usize];
let fourth = values[group[3] as usize];
if (first | second | third | fourth) == INVALID {
break;
}
let value = (u32::from(first) << 18)
| (u32::from(second) << 12)
| (u32::from(third) << 6)
| u32::from(fourth);
for (place, byte) in slot.iter_mut().enumerate() {
*byte = (value >> (16 - place * 8)) as u8;
}
done += 1;
}
done
}
pub(crate) fn find_invalid<A: Alphabet>(input: &[u8], offset: usize) -> DecodeError {
let values = A::DECODE;
for (index, &byte) in input.iter().enumerate() {
if values[byte as usize] == INVALID {
return DecodeError::InvalidByte {
position: offset + index,
byte,
};
}
}
DecodeError::InvalidLength {
length: offset + input.len(),
}
}
pub(crate) fn decode_rest<A: Alphabet>(
input: &[u8],
out: &mut [u8],
offset: usize,
) -> Result<usize, DecodeError> {
let values = A::DECODE;
match input {
[first, second] => {
let (high, low) = (values[*first as usize], values[*second as usize]);
if (high | low) & 0x80 != 0 {
return Err(find_invalid::<A>(input, offset));
}
if low & 0x0F != 0 {
return Err(DecodeError::InvalidLastSymbol {
position: offset + 1,
byte: *second,
});
}
out[0] = (high << 2) | (low >> 4);
Ok(1)
}
[first, second, third] => {
let (high, middle, low) = (
values[*first as usize],
values[*second as usize],
values[*third as usize],
);
if (high | middle | low) & 0x80 != 0 {
return Err(find_invalid::<A>(input, offset));
}
if low & 0x03 != 0 {
return Err(DecodeError::InvalidLastSymbol {
position: offset + 2,
byte: *third,
});
}
out[0] = (high << 2) | (middle >> 4);
out[1] = (middle << 4) | (low >> 2);
Ok(2)
}
rest => {
debug_assert!(rest.is_empty(), "a whole group reached the tail");
Ok(0)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::alphabet::Standard;
#[test]
fn a_short_walk_counts_what_it_spelled() {
let input = [1u8; 9];
let mut out = [0u8; 4];
assert_eq!(encode_bulk::<Standard>(&input, &mut out, 3), 1);
let mut out = [0u8; 12];
assert_eq!(encode_bulk::<Standard>(&input, &mut out, 3), 3);
assert_eq!(encode_bulk::<Standard>(&input[..3], &mut out, 1), 1);
}
#[test]
fn the_tails_count_what_they_wrote() {
let mut out = [0u8; 4];
assert_eq!(encode_rest::<Standard>(b"a", &mut out, true), 4);
assert_eq!(encode_rest::<Standard>(b"ab", &mut out, false), 3);
assert_eq!(encode_rest::<Standard>(b"", &mut out, true), 0);
let mut bytes = [0u8; 2];
assert_eq!(decode_rest::<Standard>(b"YQ", &mut bytes, 0), Ok(1));
assert_eq!(bytes[0], b'a');
assert_eq!(decode_rest::<Standard>(b"YWI", &mut bytes, 0), Ok(2));
assert_eq!(&bytes, b"ab");
assert_eq!(decode_rest::<Standard>(b"", &mut bytes, 0), Ok(0));
}
}