static URL_SAFE_TABLE: [u8; 64] = [
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2d, 0x5f,
];
static STANDARD_TABLE: [u8; 64] = [
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f,
];
const ____: u8 = 0xff;
const _EXT: u8 = 0xfe;
static URL_SAFE_DECODE_TABLE: [u8; 256] = [
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 0x3e, ____, ____,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, ____, ____, ____, _EXT, ____, ____,
____, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, ____, ____, ____, ____, 0x3f,
____, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
];
static STANDARD_DECODE_TABLE: [u8; 256] = [
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 0x3e, ____, ____, ____, 0x3f,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, ____, ____, ____, _EXT, ____, ____,
____, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, ____, ____, ____, ____, ____,
____, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
];
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum ErrorKind {
InvalidCodedCharacter,
InvalidPaddingCharacter,
InvalidPaddingLength,
TrailingUnPaddedBits,
TrailingNonZeroBits,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct Error {
pub(crate) pos: usize,
pub(crate) byte: u8,
pub(crate) kind: ErrorKind,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self.kind {
ErrorKind::InvalidCodedCharacter => {
write!(
f,
"invalid character `{}`({:#x}) at input postion {}",
self.byte as char, self.byte, self.pos
)
}
ErrorKind::InvalidPaddingCharacter => {
write!(
f,
"invalid padding character `{}`({:#x}) at input postion {}",
self.byte as char, self.byte, self.pos
)
}
_ => {
write!(f, "invalid data")
}
}
}
}
impl std::error::Error for Error {}
#[derive(Debug, Copy, Clone)]
pub struct Config {
pub no_padding: bool,
pub allow_trailing_non_zero_bits: bool,
}
pub const DEFAULT_CONFIG: Config = Config {
no_padding: false,
allow_trailing_non_zero_bits: false,
};
#[inline]
fn encode_buffer_len(ilen: usize, config: Config) -> usize {
let n = ilen / 3;
let r = ilen % 3;
if config.no_padding {
match r {
0 => n * 4,
1 => n * 4 + 2,
2 => n * 4 + 3,
_ => unreachable!(),
}
} else {
if r > 0 {
n * 4 + 4
} else {
n * 4
}
}
}
#[inline]
fn decode_buffer_len(ilen: usize) -> usize {
let n = ilen / 4;
let r = ilen % 4;
let olen = if r > 0 { n * 3 + 3 } else { n * 3 };
olen
}
pub fn encode<D: AsRef<[u8]>>(input: D) -> String {
encode_with_config(input, DEFAULT_CONFIG)
}
pub fn encode_with_config<D: AsRef<[u8]>>(input: D, config: Config) -> String {
let input = input.as_ref();
if input.is_empty() {
return String::new();
}
let ilen = input.len();
let olen = encode_buffer_len(ilen, config);
let mut output = vec![0u8; olen];
let amt = encode_to_slice_with_config(input, &mut output, config);
output.truncate(amt);
unsafe { String::from_utf8_unchecked(output) }
}
pub fn urlsafe_encode<D: AsRef<[u8]>>(input: D) -> String {
urlsafe_encode_with_config(input, DEFAULT_CONFIG)
}
pub fn urlsafe_encode_with_config<D: AsRef<[u8]>>(input: D, config: Config) -> String {
let input = input.as_ref();
if input.is_empty() {
return String::new();
}
let ilen = input.len();
let olen = encode_buffer_len(ilen, config);
let mut output = vec![0u8; olen];
let amt = urlsafe_encode_to_slice_with_config(input, &mut output, config);
output.truncate(amt);
unsafe { String::from_utf8_unchecked(output) }
}
#[inline]
pub fn encode_to_slice<D: AsRef<[u8]>, W: AsMut<[u8]>>(input: D, output: &mut W) -> usize {
encode_to_slice_with_config(input, output, DEFAULT_CONFIG)
}
#[inline]
pub fn encode_to_slice_with_config<D: AsRef<[u8]>, W: AsMut<[u8]>>(
input: D,
output: &mut W,
config: Config,
) -> usize {
encode_to_slice_inner(&STANDARD_TABLE, input, output, config)
}
#[inline]
pub fn urlsafe_encode_to_slice<D: AsRef<[u8]>, W: AsMut<[u8]>>(input: D, output: &mut W) -> usize {
urlsafe_encode_to_slice_with_config(input, output, DEFAULT_CONFIG)
}
#[inline]
pub fn urlsafe_encode_to_slice_with_config<D: AsRef<[u8]>, W: AsMut<[u8]>>(
input: D,
output: &mut W,
config: Config,
) -> usize {
encode_to_slice_inner(&URL_SAFE_TABLE, input, output, config)
}
#[inline]
fn encode_to_slice_inner<R: AsRef<[u8]>, W: AsMut<[u8]>>(
table: &[u8; 64],
input: R,
output: &mut W,
config: Config,
) -> usize {
let input = input.as_ref();
let output = output.as_mut();
let ilen = input.len();
let n = ilen / 3;
let r = ilen % 3;
let mut ipos = 0usize;
let mut opos = 0usize;
while ipos < n * 3 {
let group = u32::from_be_bytes([input[ipos + 0], input[ipos + 1], input[ipos + 2], 0]);
output[opos + 0] = table[((group >> 26) & 0x3F) as usize];
output[opos + 1] = table[((group >> 20) & 0x3F) as usize];
output[opos + 2] = table[((group >> 14) & 0x3F) as usize];
output[opos + 3] = table[((group >> 8) & 0x3F) as usize];
ipos += 3;
opos += 4;
}
match r {
0 => {}
1 => {
let group = u32::from_be_bytes([input[ipos + 0], 0, 0, 0]);
output[opos + 0] = table[((group >> 26) & 0x3F) as usize];
output[opos + 1] = table[((group >> 20) & 0x3F) as usize];
if config.no_padding {
opos += 2;
} else {
output[opos + 2] = b'=';
output[opos + 3] = b'=';
opos += 4;
}
}
2 => {
let group = u32::from_be_bytes([input[ipos + 0], input[ipos + 1], 0, 0]);
output[opos + 0] = table[((group >> 26) & 0x3F) as usize];
output[opos + 1] = table[((group >> 20) & 0x3F) as usize];
output[opos + 2] = table[((group >> 14) & 0x3F) as usize];
if config.no_padding {
opos += 3;
} else {
output[opos + 3] = b'=';
opos += 4;
}
}
_ => unreachable!(),
}
opos
}
pub fn decode<D: AsRef<[u8]>>(input: D) -> Result<Vec<u8>, Error> {
decode_with_config(input, DEFAULT_CONFIG)
}
pub fn decode_with_config<D: AsRef<[u8]>>(input: D, config: Config) -> Result<Vec<u8>, Error> {
let input = input.as_ref();
if input.is_empty() {
return Ok(Vec::new());
}
let ilen = input.len();
let olen = decode_buffer_len(ilen);
let mut output = vec![0u8; olen];
let amt = decode_to_slice_with_config(input, &mut output, config)?;
if amt < olen {
output.truncate(amt);
}
Ok(output)
}
pub fn urlsafe_decode<D: AsRef<[u8]>>(input: D) -> Result<Vec<u8>, Error> {
urlsafe_decode_with_config(input, DEFAULT_CONFIG)
}
pub fn urlsafe_decode_with_config<D: AsRef<[u8]>>(
input: D,
config: Config,
) -> Result<Vec<u8>, Error> {
let input = input.as_ref();
if input.is_empty() {
return Ok(Vec::new());
}
let ilen = input.len();
let olen = decode_buffer_len(ilen);
let mut output = vec![0u8; olen];
let amt = urlsafe_decode_to_slice_with_config(input, &mut output, config)?;
if amt < olen {
output.truncate(amt);
}
Ok(output)
}
#[inline]
pub fn decode_to_slice<R: AsRef<[u8]>, W: AsMut<[u8]>>(
input: R,
output: &mut W,
) -> Result<usize, Error> {
decode_to_slice_with_config(input, output, DEFAULT_CONFIG)
}
#[inline]
pub fn decode_to_slice_with_config<R: AsRef<[u8]>, W: AsMut<[u8]>>(
input: R,
output: &mut W,
config: Config,
) -> Result<usize, Error> {
decode_to_slice_inner(&STANDARD_DECODE_TABLE, input, output, config)
}
#[inline]
pub fn urlsafe_decode_to_slice<R: AsRef<[u8]>, W: AsMut<[u8]>>(
input: R,
output: &mut W,
) -> Result<usize, Error> {
urlsafe_decode_to_slice_with_config(input, output, DEFAULT_CONFIG)
}
#[inline]
pub fn urlsafe_decode_to_slice_with_config<R: AsRef<[u8]>, W: AsMut<[u8]>>(
input: R,
output: &mut W,
config: Config,
) -> Result<usize, Error> {
decode_to_slice_inner(&URL_SAFE_DECODE_TABLE, input, output, config)
}
#[inline]
fn decode_to_slice_inner<R: AsRef<[u8]>, W: AsMut<[u8]>>(
table: &[u8; 256],
input: R,
output: &mut W,
config: Config,
) -> Result<usize, Error> {
let input = input.as_ref();
let output = output.as_mut();
let ilen = input.len();
let mut ipos = 0usize; let mut opos = 0usize;
let mut group = 0u32; let mut gpos = 0u8;
let mut plen = 0usize;
while ipos < ilen {
let val = table[input[ipos] as usize];
match val {
____ => {
return Err(Error {
pos: ipos,
byte: input[ipos],
kind: ErrorKind::InvalidCodedCharacter,
});
}
_EXT => {
plen = 1;
ipos += 1;
if ipos < ilen {
let val = table[input[ipos] as usize];
if val != _EXT {
return Err(Error {
pos: ipos,
byte: input[ipos],
kind: ErrorKind::InvalidPaddingCharacter,
});
}
plen = 2;
ipos += 1;
}
break;
}
_ => {
match gpos {
0 => {
group = (val as u32) << 26;
gpos = 6;
}
6 => {
group |= (val as u32) << 20;
gpos = 12;
}
12 => {
group |= (val as u32) << 14;
gpos = 18;
}
18 => {
group |= (val as u32) << 8;
let [b1, b2, b3, _] = group.to_be_bytes();
output[opos + 0] = b1;
output[opos + 1] = b2;
output[opos + 2] = b3;
opos += 3;
gpos = 0;
}
_ => unreachable!(),
}
ipos += 1;
}
}
}
match gpos {
0 => {}
6 => {
ipos -= 1;
return Err(Error {
pos: ipos,
byte: input[ipos],
kind: ErrorKind::InvalidPaddingLength,
});
}
12 => {
let [b1, b2, _, _] = group.to_be_bytes();
output[opos + 0] = b1;
opos += 1;
if !config.no_padding {
if plen != 2 {
ipos -= 1;
return Err(Error {
pos: ipos,
byte: input[ipos],
kind: ErrorKind::InvalidPaddingLength,
});
}
}
if !config.allow_trailing_non_zero_bits && (b2 << 4) > 0 {
ipos -= 1;
return Err(Error {
pos: ipos,
byte: input[ipos],
kind: ErrorKind::TrailingNonZeroBits,
});
}
}
18 => {
let [b1, b2, b3, _] = group.to_be_bytes();
output[opos + 0] = b1;
output[opos + 1] = b2;
opos += 2;
if !config.no_padding {
if plen != 1 {
ipos -= 1;
return Err(Error {
pos: ipos,
byte: input[ipos],
kind: ErrorKind::InvalidPaddingLength,
});
}
}
if !config.allow_trailing_non_zero_bits && (b3 << 2) > 0 {
ipos -= 1;
return Err(Error {
pos: ipos,
byte: input[ipos],
kind: ErrorKind::TrailingNonZeroBits,
});
}
}
_ => unreachable!(),
}
Ok(opos)
}
const SKIP: u8 = 0xfd;
const PADB: u8 = 0x3d; static FORGIVING_TABLE_INV: [u8; 256] = [
____, ____, ____, ____, ____, ____, ____, ____, ____, SKIP, SKIP, ____, SKIP, SKIP, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
SKIP, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, 0x3e, ____, ____, ____, 0x3f,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, ____, ____, ____, ____, ____, ____,
____, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, ____, ____, ____, ____, ____,
____, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____,
];
#[inline]
pub fn forgiving_decode<R: AsRef<[u8]>>(input: R) -> Result<Vec<u8>, Error> {
let input = input.as_ref();
if input.is_empty() {
return Ok(Vec::new());
}
let ilen = input.len();
let olen = decode_buffer_len(ilen);
let mut output = vec![0u8; olen];
let amt = forgiving_decode_to_slice(input, &mut output)?;
if amt < olen {
output.truncate(amt);
}
Ok(output)
}
#[inline]
pub fn forgiving_decode_to_slice<R: AsRef<[u8]>, W: AsMut<[u8]>>(
input: R,
output: &mut W,
) -> Result<usize, Error> {
let input = input.as_ref();
let output = output.as_mut();
let ilen = input.len();
let mut ipos = 0usize; let mut opos = 0usize;
let mut group = 0u32; let mut gpos = 0u8;
let mut sp_len = 0usize;
for i in 0..ilen {
let v = FORGIVING_TABLE_INV[input[i] as usize];
if v == SKIP {
sp_len += 1;
}
}
let pad_len = {
let mut len = 0usize;
let mut i = ilen;
while i > 0 {
if input[i - 1] == PADB {
len += 1;
i -= 1;
} else {
break;
}
}
len
};
if pad_len > 2 {
return Err(Error {
pos: 0,
byte: input[0],
kind: ErrorKind::InvalidPaddingLength,
});
}
let mut dlen = ilen - sp_len;
if dlen % 4 == 0 {
dlen -= pad_len;
}
if dlen % 4 == 1 {
return Err(Error {
pos: 0,
byte: input[0],
kind: ErrorKind::TrailingUnPaddedBits,
});
}
let input = &input[..ilen - pad_len];
let ilen = input.len();
while ipos < ilen {
let val = FORGIVING_TABLE_INV[input[ipos] as usize];
match val {
____ => {
return Err(Error {
pos: ipos,
byte: input[ipos],
kind: ErrorKind::InvalidCodedCharacter,
});
}
SKIP => {
ipos += 1;
}
_ => {
match gpos {
0 => {
group = (val as u32) << 26;
gpos = 6;
}
6 => {
group |= (val as u32) << 20;
gpos = 12;
}
12 => {
group |= (val as u32) << 14;
gpos = 18;
}
18 => {
group |= (val as u32) << 8;
let [b1, b2, b3, _] = group.to_be_bytes();
output[opos + 0] = b1;
output[opos + 1] = b2;
output[opos + 2] = b3;
opos += 3;
gpos = 0;
}
_ => unreachable!(),
}
ipos += 1;
}
}
}
match gpos {
0 => {}
6 => {
unreachable!()
}
12 => {
let [b1, _, _, _] = group.to_be_bytes();
output[opos + 0] = b1;
opos += 1;
}
18 => {
let [b1, b2, _, _] = group.to_be_bytes();
output[opos + 0] = b1;
output[opos + 1] = b2;
opos += 2;
}
_ => unreachable!(),
}
Ok(opos)
}
#[test]
fn test_base64() {
assert_eq!(encode(""), "");
assert_eq!(encode("f"), "Zg==");
assert_eq!(encode("fo"), "Zm8=");
assert_eq!(encode("foo"), "Zm9v");
assert_eq!(encode("foob"), "Zm9vYg==");
assert_eq!(encode("fooba"), "Zm9vYmE=");
assert_eq!(encode("foobar"), "Zm9vYmFy");
assert_eq!(decode("").unwrap(), b"");
assert_eq!(decode("Zg==").unwrap(), b"f");
assert_eq!(decode("Zm8=").unwrap(), b"fo");
assert_eq!(decode("Zm9v").unwrap(), b"foo");
assert_eq!(decode("Zm9vYg==").unwrap(), b"foob");
assert_eq!(decode("Zm9vYmE=").unwrap(), b"fooba");
assert_eq!(decode("Zm9vYmFy").unwrap(), b"foobar");
assert_eq!(urlsafe_decode(urlsafe_encode("")).unwrap(), b"");
assert_eq!(urlsafe_decode(urlsafe_encode("f")).unwrap(), b"f");
assert_eq!(urlsafe_decode(urlsafe_encode("fo")).unwrap(), b"fo");
assert_eq!(urlsafe_decode(urlsafe_encode("foo")).unwrap(), b"foo");
assert_eq!(urlsafe_decode(urlsafe_encode("foob")).unwrap(), b"foob");
assert_eq!(urlsafe_decode(urlsafe_encode("fooba")).unwrap(), b"fooba");
assert_eq!(urlsafe_decode(urlsafe_encode("foobar")).unwrap(), b"foobar");
let input = "Man is distinguished, not only by his reason, but by this singular passion from other animals, \
which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable \
generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
let output = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\
IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\
dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\
dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\
ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=";
assert_eq!(encode(input), output);
}
#[test]
fn test_base64_trailing_bits() {
assert_eq!(encode("a"), "YQ==");
assert_eq!(encode("a\u{10}"), "YRA=");
assert!(decode("YQ").is_err());
assert!(decode("YQ=").is_err());
assert_eq!(std::str::from_utf8(&decode("YQ==").unwrap()), Ok("a"));
assert!(decode("YR").is_err());
assert!(decode("YR=").is_err());
assert!(decode("YRA").is_err());
assert_eq!(std::str::from_utf8(&decode("YR==").unwrap()), Ok("a"));
assert_eq!(std::str::from_utf8(&decode("YRA=").unwrap()), Ok("a\u{10}"));
}
#[test]
fn test_forgiving_decode() {
assert_eq!(
std::str::from_utf8(&forgiving_decode("YQ").unwrap()),
Ok("a")
);
assert_eq!(
std::str::from_utf8(&forgiving_decode("YR").unwrap()),
Ok("a")
);
}