#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
html_root_url = "https://docs.rs/base64ct/0.1.1"
)]
#![warn(missing_docs, rust_2018_idioms)]
#[cfg(feature = "alloc")]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
mod errors;
pub use errors::{Error, InvalidEncodingError, InvalidLengthError};
use core::{ops::Range, str};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
const PAD: u8 = b'=';
const STD_HI_BYTES: (u8, u8) = (b'+', b'/');
pub mod padded {
use crate::{Error, InvalidEncodingError, InvalidLengthError, STD_HI_BYTES};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
crate::decode(src, dst, true, STD_HI_BYTES)
}
pub fn decode_in_place(buf: &mut [u8]) -> Result<&[u8], InvalidEncodingError> {
crate::decode_in_place(buf, true, STD_HI_BYTES)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn decode_vec(input: &str) -> Result<Vec<u8>, Error> {
crate::decode_vec(input, true, STD_HI_BYTES)
}
pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, InvalidLengthError> {
crate::encode(src, dst, true, STD_HI_BYTES)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn encode_string(input: &[u8]) -> String {
crate::encode_string(input, true, STD_HI_BYTES)
}
pub fn encoded_len(bytes: &[u8]) -> usize {
crate::encoded_len(bytes, true)
}
}
pub mod unpadded {
use crate::{Error, InvalidEncodingError, InvalidLengthError, STD_HI_BYTES};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
crate::decode(src, dst, false, STD_HI_BYTES)
}
pub fn decode_in_place(buf: &mut [u8]) -> Result<&[u8], InvalidEncodingError> {
crate::decode_in_place(buf, false, STD_HI_BYTES)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn decode_vec(input: &str) -> Result<Vec<u8>, Error> {
crate::decode_vec(input, false, STD_HI_BYTES)
}
pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, InvalidLengthError> {
crate::encode(src, dst, false, STD_HI_BYTES)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn encode_string(input: &[u8]) -> String {
crate::encode_string(input, false, STD_HI_BYTES)
}
pub fn encoded_len(bytes: &[u8]) -> usize {
crate::encoded_len(bytes, false)
}
}
pub mod url {
const URL_HI_BYTES: (u8, u8) = (b'-', b'_');
pub mod padded {
use super::URL_HI_BYTES;
use crate::{Error, InvalidEncodingError, InvalidLengthError};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
crate::decode(src, dst, true, URL_HI_BYTES)
}
pub fn decode_in_place(buf: &mut [u8]) -> Result<&[u8], InvalidEncodingError> {
crate::decode_in_place(buf, true, URL_HI_BYTES)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn decode_vec(input: &str) -> Result<Vec<u8>, Error> {
crate::decode_vec(input, true, URL_HI_BYTES)
}
pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, InvalidLengthError> {
crate::encode(src, dst, true, URL_HI_BYTES)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn encode_string(input: &[u8]) -> String {
crate::encode_string(input, true, URL_HI_BYTES)
}
pub fn encoded_len(bytes: &[u8]) -> usize {
crate::encoded_len(bytes, true)
}
}
pub mod unpadded {
use super::URL_HI_BYTES;
use crate::{Error, InvalidEncodingError, InvalidLengthError};
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
pub fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> {
crate::decode(src, dst, false, URL_HI_BYTES)
}
pub fn decode_in_place(buf: &mut [u8]) -> Result<&[u8], InvalidEncodingError> {
crate::decode_in_place(buf, false, URL_HI_BYTES)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn decode_vec(input: &str) -> Result<Vec<u8>, Error> {
crate::decode_vec(input, false, URL_HI_BYTES)
}
pub fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, InvalidLengthError> {
crate::encode(src, dst, false, URL_HI_BYTES)
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn encode_string(input: &[u8]) -> String {
crate::encode_string(input, false, URL_HI_BYTES)
}
pub fn encoded_len(bytes: &[u8]) -> usize {
crate::encoded_len(bytes, false)
}
}
}
#[inline(always)]
fn encode<'a>(
src: &[u8],
dst: &'a mut [u8],
padded: bool,
hi_bytes: (u8, u8),
) -> Result<&'a str, InvalidLengthError> {
let elen = match encoded_len_inner(src.len(), padded) {
Some(v) => v,
None => return Err(InvalidLengthError),
};
if elen > dst.len() {
return Err(InvalidLengthError);
}
let dst = &mut dst[..elen];
let mut src_chunks = src.chunks_exact(3);
let mut dst_chunks = dst.chunks_exact_mut(4);
for (s, d) in (&mut src_chunks).zip(&mut dst_chunks) {
encode_3bytes(s, d, hi_bytes);
}
let src_rem = src_chunks.remainder();
if padded {
if let Some(dst_rem) = dst_chunks.next() {
let mut tmp = [0u8; 3];
tmp[..src_rem.len()].copy_from_slice(&src_rem);
encode_3bytes(&tmp, dst_rem, hi_bytes);
let flag = src_rem.len() == 1;
let mask = (flag as u8).wrapping_sub(1);
dst_rem[2] = (dst_rem[2] & mask) | (PAD & !mask);
dst_rem[3] = PAD;
}
} else {
let dst_rem = dst_chunks.into_remainder();
let mut tmp_in = [0u8; 3];
let mut tmp_out = [0u8; 4];
tmp_in[..src_rem.len()].copy_from_slice(src_rem);
encode_3bytes(&tmp_in, &mut tmp_out, hi_bytes);
dst_rem.copy_from_slice(&tmp_out[..dst_rem.len()]);
}
debug_assert!(str::from_utf8(dst).is_ok());
Ok(unsafe { str::from_utf8_unchecked(dst) })
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[inline(always)]
fn encode_string(input: &[u8], padded: bool, hi_bytes: (u8, u8)) -> String {
let elen = encoded_len_inner(input.len(), padded).expect("input is too big");
let mut dst = vec![0u8; elen];
let res = encode(input, &mut dst, padded, hi_bytes).expect("encoding error");
debug_assert_eq!(elen, res.len());
debug_assert!(str::from_utf8(&dst).is_ok());
unsafe { String::from_utf8_unchecked(dst) }
}
#[inline(always)]
const fn encoded_len(bytes: &[u8], padded: bool) -> usize {
match encoded_len_inner(bytes.len(), padded) {
Some(v) => v,
None => 0,
}
}
#[inline(always)]
const fn encoded_len_inner(n: usize, padded: bool) -> Option<usize> {
if n > usize::MAX / 4 {
return None;
}
let q = 4 * n;
if padded {
Some(((q / 3) + 3) & !3)
} else {
Some((q / 3) + (q % 3 != 0) as usize)
}
}
#[inline(always)]
fn decode(
src: impl AsRef<[u8]>,
dst: &mut [u8],
padded: bool,
hi_bytes: (u8, u8),
) -> Result<&[u8], Error> {
let mut src = src.as_ref();
let mut err = if padded {
let (unpadded_len, e) = decode_padding(src)?;
src = &src[..unpadded_len];
e
} else {
0
};
let dlen = decoded_len(src.len());
if dlen > dst.len() {
return Err(Error::InvalidLength);
}
let dst = &mut dst[..dlen];
let mut src_chunks = src.chunks_exact(4);
let mut dst_chunks = dst.chunks_exact_mut(3);
for (s, d) in (&mut src_chunks).zip(&mut dst_chunks) {
err |= decode_3bytes(s, d, hi_bytes);
}
let src_rem = src_chunks.remainder();
let dst_rem = dst_chunks.into_remainder();
err |= !(src_rem.is_empty() || src_rem.len() >= 2) as i16;
let mut tmp_out = [0u8; 3];
let mut tmp_in = [b'A'; 4];
tmp_in[..src_rem.len()].copy_from_slice(src_rem);
err |= decode_3bytes(&tmp_in, &mut tmp_out, hi_bytes);
dst_rem.copy_from_slice(&tmp_out[..dst_rem.len()]);
if err == 0 {
Ok(dst)
} else {
Err(Error::InvalidEncoding)
}
}
#[inline(always)]
fn decode_in_place(
mut buf: &mut [u8],
padded: bool,
hi_bytes: (u8, u8),
) -> Result<&[u8], InvalidEncodingError> {
let mut err = if padded {
let (unpadded_len, e) = decode_padding(buf)?;
buf = &mut buf[..unpadded_len];
e
} else {
0
};
let dlen = decoded_len(buf.len());
let full_chunks = buf.len() / 4;
for chunk in 0..full_chunks {
unsafe {
debug_assert!(3 * chunk + 3 <= buf.len());
debug_assert!(4 * chunk + 4 <= buf.len());
let p3 = buf.as_mut_ptr().add(3 * chunk) as *mut [u8; 3];
let p4 = buf.as_ptr().add(4 * chunk) as *const [u8; 4];
let mut tmp_out = [0u8; 3];
err |= decode_3bytes(&*p4, &mut tmp_out, hi_bytes);
*p3 = tmp_out;
}
}
let src_rem_pos = 4 * full_chunks;
let src_rem_len = buf.len() - src_rem_pos;
let dst_rem_pos = 3 * full_chunks;
let dst_rem_len = dlen - dst_rem_pos;
err |= !(src_rem_len == 0 || src_rem_len >= 2) as i16;
let mut tmp_in = [b'A'; 4];
tmp_in[..src_rem_len].copy_from_slice(&buf[src_rem_pos..]);
let mut tmp_out = [0u8; 3];
err |= decode_3bytes(&tmp_in, &mut tmp_out, hi_bytes);
if err == 0 {
unsafe {
debug_assert!(dst_rem_pos + dst_rem_len <= buf.len());
debug_assert!(dst_rem_len <= tmp_out.len());
debug_assert!(dlen <= buf.len());
core::ptr::copy_nonoverlapping(
tmp_out.as_ptr(),
buf.as_mut_ptr().add(dst_rem_pos),
dst_rem_len,
);
Ok(buf.get_unchecked(..dlen))
}
} else {
Err(InvalidEncodingError)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[inline(always)]
fn decode_vec(input: &str, padded: bool, hi_bytes: (u8, u8)) -> Result<Vec<u8>, Error> {
let mut output = vec![0u8; decoded_len(input.len())];
let len = decode(input, &mut output, padded, hi_bytes)?.len();
if len <= output.len() {
output.truncate(len);
Ok(output)
} else {
Err(Error::InvalidLength)
}
}
#[inline(always)]
fn decoded_len(input_len: usize) -> usize {
let k = input_len / 4;
let l = input_len - 4 * k;
3 * k + (3 * l) / 4
}
#[inline(always)]
fn encode_3bytes(src: &[u8], dst: &mut [u8], hi_bytes: (u8, u8)) {
debug_assert_eq!(src.len(), 3);
debug_assert!(dst.len() >= 4, "dst too short: {}", dst.len());
let b0 = src[0] as i16;
let b1 = src[1] as i16;
let b2 = src[2] as i16;
dst[0] = encode_6bits(b0 >> 2, hi_bytes);
dst[1] = encode_6bits(((b0 << 4) | (b1 >> 4)) & 63, hi_bytes);
dst[2] = encode_6bits(((b1 << 2) | (b2 >> 6)) & 63, hi_bytes);
dst[3] = encode_6bits(b2 & 63, hi_bytes);
}
#[inline(always)]
fn encode_6bits(src: i16, hi_bytes: (u8, u8)) -> u8 {
let hi_off = 0x1c + (hi_bytes.0 & 4);
let mut diff = 0x41i16;
diff += match_gt_ct(src, 25, 6);
diff -= match_gt_ct(src, 51, 75);
diff -= match_gt_ct(src, 61, hi_bytes.0 as i16 - hi_off as i16);
diff += match_gt_ct(src, 62, hi_bytes.1 as i16 - hi_bytes.0 as i16 - 1);
(src + diff) as u8
}
#[inline(always)]
fn decode_3bytes(src: &[u8], dst: &mut [u8], hi_bytes: (u8, u8)) -> i16 {
debug_assert_eq!(src.len(), 4);
debug_assert!(dst.len() >= 3, "dst too short: {}", dst.len());
let c0 = decode_6bits(src[0], hi_bytes);
let c1 = decode_6bits(src[1], hi_bytes);
let c2 = decode_6bits(src[2], hi_bytes);
let c3 = decode_6bits(src[3], hi_bytes);
dst[0] = ((c0 << 2) | (c1 >> 4)) as u8;
dst[1] = ((c1 << 4) | (c2 >> 2)) as u8;
dst[2] = ((c2 << 6) | c3) as u8;
((c0 | c1 | c2 | c3) >> 8) & 1
}
#[inline(always)]
fn decode_6bits(src: u8, hi_bytes: (u8, u8)) -> i16 {
let mut res: i16 = -1;
res += match_range_ct(src, 0x41..0x5a, src as i16 - 64);
res += match_range_ct(src, 0x61..0x7a, src as i16 - 70);
res += match_range_ct(src, 0x30..0x39, src as i16 + 5);
res += match_eq_ct(src, hi_bytes.0, 63);
res + match_eq_ct(src, hi_bytes.1, 64)
}
#[inline(always)]
fn match_gt_ct(input: i16, threshold: u8, ret_on_match: i16) -> i16 {
((threshold as i16 - input) >> 8) & ret_on_match
}
#[inline(always)]
fn match_range_ct(input: u8, range: Range<u8>, ret_on_match: i16) -> i16 {
let start = range.start as i16 - 1;
let end = range.end as i16 + 1;
(((start - input as i16) & (input as i16 - end)) >> 8) & ret_on_match
}
#[inline(always)]
fn match_eq_ct(input: u8, expected: u8, ret_on_match: i16) -> i16 {
match_range_ct(input, expected..expected, ret_on_match)
}
#[inline(always)]
fn decode_padding(input: &[u8]) -> Result<(usize, i16), InvalidEncodingError> {
if input.len() % 4 != 0 {
return Err(InvalidEncodingError);
}
let unpadded_len = match *input {
[.., b0, b1] => {
let pad_len = match_eq_ct(b0, PAD, 1) + match_eq_ct(b1, PAD, 1);
input.len() - pad_len as usize
}
_ => input.len(),
};
let padding_len = input.len() - unpadded_len;
let err = match *input {
[.., b0] if padding_len == 1 => match_eq_ct(b0, PAD, 1) ^ 1,
[.., b0, b1] if padding_len == 2 => (match_eq_ct(b0, PAD, 1) & match_eq_ct(b1, PAD, 1)) ^ 1,
_ => {
if padding_len == 0 {
0
} else {
return Err(InvalidEncodingError);
}
}
};
Ok((unpadded_len, err))
}