use crate::Error::InsufficientTargetSpace;
use crate::base_64::encode;
use crate::base_64::encode::EncodingTable;
use crate::{Encoder, Error, StringEncoder, data};
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
pub struct Base64Encoder {
table: EncodingTable,
padding: Option<u8>,
}
impl Base64Encoder {
pub const fn is_valid_config(v63: u8, v64: u8, padding: Option<u8>) -> bool {
(v63.is_ascii_punctuation() && v64.is_ascii_punctuation())
&& v63 != v64
&& (if let Some(padding) = padding {
padding.is_ascii_punctuation() && padding != v63 && padding != v64
} else {
true
})
}
}
impl Base64Encoder {
pub fn new(v63: u8, v64: u8, padding: Option<u8>) -> Option<Self> {
if Self::is_valid_config(v63, v64, padding) {
Some(Self {
table: EncodingTable::get_encoding_table(v63, v64),
padding,
})
} else {
None
}
}
}
impl Default for Base64Encoder {
fn default() -> Self {
Self {
table: EncodingTable::default(),
padding: Self::DEFAULT_PADDING,
}
}
}
impl Base64Encoder {
pub fn url_safe_encoder() -> Self {
Self::new(
Self::URL_SAFE_V63,
Self::URL_SAFE_V64,
Self::URL_SAFE_PADDING,
)
.unwrap()
}
}
impl Encoder for Base64Encoder {
fn encoded_len(&self, data: &[u8]) -> Result<usize, Error> {
encode::encoded_len(data.len(), self.padding.is_some())
}
fn encode_to_slice(&self, data: &[u8], target: &mut [u8]) -> Result<usize, Error> {
let encoded_len: usize = self.encoded_len(data)?;
if encoded_len > target.len() {
Err(InsufficientTargetSpace)
} else {
let target: &mut [u8] = &mut target[..encoded_len];
let table: &[u8; 64] = self.table.encoding_table();
let div: usize = data.len() / 3;
let rem: usize = data.len() % 3;
let mut d: usize = 0;
let mut t: usize = 0;
for _ in 0..div {
encode::encode_block(table, &data[d..], &mut target[t..]);
d += 3;
t += 4;
}
match rem {
0 => {}
1 => {
t += encode::encode_last_block_1(
table,
self.padding,
&data[d..],
&mut target[t..],
);
}
2 => {
t += encode::encode_last_block_2(
table,
self.padding,
&data[d..],
&mut target[t..],
);
}
_ => unreachable!(),
}
debug_assert_eq!(encoded_len, t);
Ok(encoded_len)
}
}
}
impl StringEncoder for Base64Encoder {
fn append_to_string(&self, data: &[u8], target: &mut String) -> Result<usize, Error> {
unsafe { data::util::append_to_string_unchecked(self, data, target) }
}
}
#[cfg(test)]
#[cfg(feature = "dev")]
mod tests {
use crate::base_64::Base64Encoder;
use crate::test::test_string_encoder;
#[test]
fn encode() {
let test_cases: &[(&[u8], &str)] = &[
(b"\x00\x10\x83", "ABCD"),
(b"\x10\x51\x87", "EFGH"),
(b"\x20\x92\x8B", "IJKL"),
(b"\x30\xD3\x8F", "MNOP"),
(b"\x41\x14\x93", "QRST"),
(b"\x51\x55\x97", "UVWX"),
(b"\x61\x96\x9B", "YZab"),
(b"\x71\xD7\x9F", "cdef"),
(b"\x82\x18\xA3", "ghij"),
(b"\x92\x59\xA7", "klmn"),
(b"\xA2\x9A\xAB", "opqr"),
(b"\xB2\xDB\xAF", "stuv"),
(b"\xC3\x1C\xB3", "wxyz"),
(b"\xD3\x5D\xB7", "0123"),
(b"\xE3\x9E\xBB", "4567"),
(b"\xF3\xDF\xBF", "89-_"),
(b"", ""),
(b"\x00", "AA"),
(b"\xFF", "_w"),
(b"\x00\x00", "AAA"),
(b"\xFF\xFF", "__8"),
(b"\x00\x00\x00", "AAAA"),
(b"\xFF\xFF\xFF", "____"),
(b"\x00\x00\x00\x00", "AAAAAA"),
(b"\xFF\xFF\xFF\xFF", "_____w"),
(b"\x00\x00\x00\x00\x00", "AAAAAAA"),
(b"\xFF\xFF\xFF\xFF\xFF", "______8"),
(b"\x00\x00\x00\x00\x00\x00", "AAAAAAAA"),
(b"\xFF\xFF\xFF\xFF\xFF\xFF", "________"),
];
let encoder: Base64Encoder = Base64Encoder::url_safe_encoder();
test_string_encoder(&encoder, test_cases);
}
#[test]
fn encode_default() {
let test_cases: &[(&[u8], &str)] = &[
(b"", ""),
(b"\x00\x10\x83", "ABCD"),
(b"\xF3\xDF\xBF", "89+/"),
(b"\x00", "AA=="),
(b"\xFF", "/w=="),
(b"\x00\x00", "AAA="),
(b"\xFF\xFF", "//8="),
(b"\x00\x00\x00", "AAAA"),
(b"\xFF\xFF\xFF", "////"),
(b"\x00\x00\x00\x00", "AAAAAA=="),
(b"\xFF\xFF\xFF\xFF", "/////w=="),
(b"\x00\x00\x00\x00\x00", "AAAAAAA="),
(b"\xFF\xFF\xFF\xFF\xFF", "//////8="),
(b"\x00\x00\x00\x00\x00\x00", "AAAAAAAA"),
(b"\xFF\xFF\xFF\xFF\xFF\xFF", "////////"),
];
let encoder: Base64Encoder = Base64Encoder::default();
test_string_encoder(&encoder, test_cases);
}
}