#![no_std]
const UPPERCASEOFFSET: u8 = 65;
const LOWERCASEOFFSET: u8 = 71;
const DIGITOFFSET: u8 = 4;
#[inline]
fn index_to_char(index: u8) -> Result<u8, &'static str> {
let index = index as u8;
let ascii_index = match index {
0..=25 => index.saturating_add(UPPERCASEOFFSET), 26..=51 => index.saturating_add(LOWERCASEOFFSET), 52..=61 => index.saturating_sub(DIGITOFFSET), 62 => 43, 63 => 47,
_ => return Err("Invalid ascii character index encountered"),
} as u8;
Ok(ascii_index)
}
#[inline]
fn char_to_index(c: u8) -> Result<u8, &'static str> {
let base64_index = match c {
65..=90 => c.saturating_sub(UPPERCASEOFFSET), 97..=122 => c.saturating_sub(LOWERCASEOFFSET), 48..=57 => c.saturating_add(DIGITOFFSET), 43 => 62, 47 => 63,
_ => return Err("Invalid base64 char encountered"),
} as u8;
Ok(base64_index)
}
#[inline]
fn split_bytes(chunk: &[u8]) -> [u8; 4] {
[
&chunk[0] >> 2,
(&chunk[0] & 0b00000011) << 4 | &chunk[1] >> 4,
(&chunk[1] & 0b00001111) << 2 | &chunk[2] >> 6,
&chunk[2] & 0b00111111,
]
}
#[inline]
fn combine_bytes(chunk: &[u8]) -> [u8; 3] {
[
(chunk[0] & 0b00111111) << 2 | chunk[1] >> 4,
(chunk[1] & 0b00001111) << 4 | chunk[2] >> 2,
(chunk[2] & 0b00000011) << 6 | chunk[3] & 0b00111111,
]
}
#[inline]
pub fn encode(data: &[u8], out: &mut [u8]) -> Result<(), &'static str> {
let nin = data.len();
let nout = out.len();
let nchunks = nin / 3;
if nin % 3 != 0 {
return Err("Input data must be a multiple of 3 bytes");
} else if nout != (nin * 4 / 3) {
return Err("Output data length should be 4/3 input data length");
} else {
for j in 0..nchunks {
let d = &data[3 * j..3 * j + 3];
let o = &mut out[4 * j..4 * j + 4];
let expanded = split_bytes(d);
for i in 0..4 {
o[i] = index_to_char(expanded[i])?;
}
}
}
Ok(())
}
#[inline]
pub fn decode(data: &[u8], out: &mut [u8]) -> Result<(), &'static str> {
let nin = data.len();
let nout = out.len();
let nchunks = nin / 4;
if nin % 4 != 0 {
return Err("Input data must be a multiple of 4 bytes");
} else if nout != (nin * 3 / 4) {
return Err("Output data length should be 3/4 input data length");
} else {
let mut converted = [0_u8; 4];
for j in 0..nchunks {
let d = &data[4 * j..4 * j + 4];
let o = &mut out[3 * j..3 * j + 3];
for i in 0..4 {
converted[i] = char_to_index(d[i])?;
}
let combined: [u8; 3] = combine_bytes(&converted);
o.copy_from_slice(&combined);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_character_mapping() {
for i in 0..63_u8 {
assert_eq!(char_to_index(index_to_char(i).unwrap()).unwrap(), i);
}
}
#[test]
fn test_encode_decode() {
let input: &mut [u8; 258] = &mut [0_u8; 258];
for i in 0..258 {
input[i] = (i % 256) as u8;
}
let output = b"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/wAB";
let input_de_buf = &mut [0_u8; 258];
let output_ser_buf = &mut [0_u8; 344];
encode(input, output_ser_buf).unwrap();
decode(output, input_de_buf).unwrap();
assert_eq!(input, input_de_buf);
assert_eq!(output, output_ser_buf);
}
}