1#![no_std]
17#![forbid(unsafe_op_in_unsafe_fn)]
18#![deny(missing_docs)]
19
20#[cfg(feature = "std")]
21extern crate std;
22
23mod alphabet;
24mod backend;
25mod codec;
26mod error;
27mod scalar;
28mod stream;
29
30#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
31mod neon;
32
33#[cfg(target_arch = "x86_64")]
34mod x86;
35
36#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
37mod wasm;
38
39#[cfg(any(target_arch = "x86_64", test))]
40mod wide;
41
42pub use error::{DecodeError, EncodeError};
43pub use stream::{Decoder, Encoder};
44
45use alphabet::{Standard, UrlSafe};
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49enum Kind {
50 Standard,
51 UrlSafe,
52}
53
54#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub struct Codec {
57 kind: Kind,
58 is_padded: bool,
59}
60
61pub const STANDARD: Codec = Codec {
63 kind: Kind::Standard,
64 is_padded: true,
65};
66
67pub const STANDARD_NO_PAD: Codec = Codec {
69 kind: Kind::Standard,
70 is_padded: false,
71};
72
73pub const URL_SAFE: Codec = Codec {
75 kind: Kind::UrlSafe,
76 is_padded: true,
77};
78
79pub const URL_SAFE_NO_PAD: Codec = Codec {
81 kind: Kind::UrlSafe,
82 is_padded: false,
83};
84
85impl Codec {
86 #[inline]
88 pub const fn encoded_len(&self, len: usize) -> Option<usize> {
89 let groups = len / 3;
90 self.length(groups, len % 3)
91 }
92
93 #[inline]
95 const fn length(&self, groups: usize, rest: usize) -> Option<usize> {
96 if groups > MAX_GROUPS {
97 return None;
98 }
99 let tail = if rest == 0 {
100 0
101 } else if self.is_padded {
102 4
103 } else {
104 rest + 1
105 };
106 Some(groups * 4 + tail)
107 }
108
109 #[inline]
111 pub const fn decoded_len_max(&self, len: usize) -> usize {
112 decoded_len(len)
113 }
114
115 #[inline]
117 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
118 Ok(decoded_len(self.payload(input)?.len()))
119 }
120
121 #[inline]
123 pub fn encode(&self, input: &[u8], out: &mut [u8]) -> Result<usize, EncodeError> {
124 let groups = input.len() / 3;
125 let needed = match self.length(groups, input.len() - groups * 3) {
126 Some(needed) => needed,
127 None => return Err(EncodeError::TooLong),
128 };
129 if out.len() < needed {
130 return Err(EncodeError::BufferTooSmall { needed });
131 }
132 let out = &mut out[..needed];
133 match self.kind {
134 Kind::Standard => codec::encode::<Standard>(input, out, groups, self.is_padded),
135 Kind::UrlSafe => codec::encode::<UrlSafe>(input, out, groups, self.is_padded),
136 }
137 Ok(needed)
138 }
139
140 #[inline]
142 pub fn decode(&self, input: &[u8], out: &mut [u8]) -> Result<usize, DecodeError> {
143 let payload = self.payload(input)?;
144 let quads = payload.len() / 4;
145 let needed = decoded_len(payload.len());
146 if out.len() < needed {
147 return Err(DecodeError::BufferTooSmall { needed });
148 }
149 let out = &mut out[..needed];
150 match self.kind {
151 Kind::Standard => codec::decode::<Standard>(payload, out, quads),
152 Kind::UrlSafe => codec::decode::<UrlSafe>(payload, out, quads),
153 }
154 }
155
156 #[inline]
158 fn payload<'input>(&self, input: &'input [u8]) -> Result<&'input [u8], DecodeError> {
159 let len = input.len();
160 if !length_ok(len, self.is_padded) {
161 return Err(DecodeError::InvalidLength { length: len });
162 }
163 if !self.is_padded || len < 4 {
165 return Ok(input);
166 }
167
168 let one = usize::from(input[len - 1] == b'=');
170 let two = one & usize::from(input[len - 2] == b'=');
171 let three = two & usize::from(input[len - 3] == b'=');
172 if three != 0 {
174 return Err(DecodeError::InvalidPadding { position: len - 3 });
175 }
176 Ok(&input[..len - one - two])
177 }
178}
179
180pub(crate) const fn decoded_len(len: usize) -> usize {
182 len / 4 * 3 + (len % 4).saturating_sub(1)
183}
184
185pub(crate) const fn length_ok(len: usize, is_padded: bool) -> bool {
187 if is_padded {
188 len.is_multiple_of(4)
189 } else {
190 len % 4 != 1
191 }
192}
193
194const MAX_GROUPS: usize = (usize::MAX - 4) / 4;
196
197#[inline]
199pub fn encode(input: &[u8], out: &mut [u8]) -> Result<usize, EncodeError> {
200 STANDARD.encode(input, out)
201}
202
203#[inline]
205pub fn decode(input: &[u8], out: &mut [u8]) -> Result<usize, DecodeError> {
206 STANDARD.decode(input, out)
207}
208
209#[inline]
211pub const fn encoded_len(len: usize) -> Option<usize> {
212 STANDARD.encoded_len(len)
213}
214
215#[cfg(feature = "internals")]
217#[doc(hidden)]
218pub mod testing {
219 use crate::alphabet::{Standard, UrlSafe};
220 use crate::backend;
221 use crate::error::DecodeError;
222 use crate::scalar;
223
224 #[cfg(target_arch = "x86_64")]
225 use crate::x86;
226
227 pub fn portable_encode(input: &[u8], out: &mut [u8], is_padded: bool) {
228 let codec = if is_padded {
229 crate::STANDARD
230 } else {
231 crate::STANDARD_NO_PAD
232 };
233 assert!(
234 out.len() >= codec.encoded_len(input.len()).expect("a length this small"),
235 "the walk wants room for the whole encoding"
236 );
237 let groups = scalar::encode_bulk::<Standard>(input, out, input.len() / 3);
238 scalar::encode_rest::<Standard>(&input[groups * 3..], &mut out[groups * 4..], is_padded);
239 }
240
241 pub fn portable_decode(input: &[u8], out: &mut [u8]) -> Result<usize, DecodeError> {
242 assert!(
243 out.len() >= crate::decoded_len(input.len()),
244 "the walk wants room for the whole decoding"
245 );
246 let quads = scalar::decode_bulk::<Standard>(input, out, input.len() / 4);
247 let (read, written) = (quads * 4, quads * 3);
248 if input.len() - read >= 4 {
249 return Err(scalar::find_invalid::<Standard>(&input[read..], read));
250 }
251 Ok(written + scalar::decode_rest::<Standard>(&input[read..], &mut out[written..], read)?)
252 }
253
254 pub fn vector_encode_bulk(input: &[u8], out: &mut [u8], is_url_safe: bool) -> (usize, usize) {
255 let groups = core::cmp::min(input.len() / 3, out.len() / 4);
256 let done = if is_url_safe {
257 backend::encode_bulk::<UrlSafe>(input, out, groups)
258 } else {
259 backend::encode_bulk::<Standard>(input, out, groups)
260 };
261 (done * 3, done * 4)
262 }
263
264 pub fn vector_decode_bulk(input: &[u8], out: &mut [u8], is_url_safe: bool) -> (usize, usize) {
265 let quads = core::cmp::min(input.len() / 4, out.len() / 3);
266 let done = if is_url_safe {
267 backend::decode_bulk::<UrlSafe>(input, out, quads)
268 } else {
269 backend::decode_bulk::<Standard>(input, out, quads)
270 };
271 (done * 4, done * 3)
272 }
273
274 pub fn has_vector_path() -> bool {
275 #[cfg(target_arch = "x86_64")]
276 {
277 x86::available() != x86::PORTABLE
278 }
279 #[cfg(not(target_arch = "x86_64"))]
280 {
281 cfg!(any(
282 all(target_arch = "aarch64", target_feature = "neon"),
283 all(target_arch = "wasm32", target_feature = "simd128")
284 ))
285 }
286 }
287
288 #[cfg(target_arch = "x86_64")]
290 pub use crate::x86::{available, force, force_stream_floor, AVX2, PORTABLE, UNKNOWN, WIDE};
291}