base64_ng/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2#![deny(unsafe_code)]
3#![deny(missing_docs)]
4#![deny(clippy::all)]
5#![deny(clippy::pedantic)]
6#![allow(clippy::missing_errors_doc)]
7
8//! `base64-ng` is a `no_std`-first Base64 encoder and decoder.
9//!
10//! This initial release provides strict scalar RFC 4648-style behavior and
11//! caller-owned output buffers. Future SIMD fast paths, including AVX, NEON,
12//! and wasm `simd128` candidates, will be required to match this scalar module
13//! byte-for-byte.
14//!
15//! # Examples
16//!
17//! Encode and decode with caller-owned buffers:
18//!
19//! ```
20//! use base64_ng::{STANDARD, checked_encoded_len};
21//!
22//! let input = b"hello";
23//! const ENCODED_CAPACITY: usize = match checked_encoded_len(5, true) {
24//! Some(len) => len,
25//! None => panic!("encoded length overflow"),
26//! };
27//! let mut encoded = [0u8; ENCODED_CAPACITY];
28//! let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
29//! assert_eq!(&encoded[..encoded_len], b"aGVsbG8=");
30//!
31//! let mut decoded = [0u8; 5];
32//! let decoded_len = STANDARD.decode_slice(&encoded, &mut decoded).unwrap();
33//! assert_eq!(&decoded[..decoded_len], input);
34//! ```
35//!
36//! Use the URL-safe no-padding engine:
37//!
38//! ```
39//! use base64_ng::URL_SAFE_NO_PAD;
40//!
41//! let mut encoded = [0u8; 3];
42//! let encoded_len = URL_SAFE_NO_PAD.encode_slice(b"\xfb\xff", &mut encoded).unwrap();
43//! assert_eq!(&encoded[..encoded_len], b"-_8");
44//! ```
45//!
46//! # Sensitive Decode Policy
47//!
48//! The default engines such as [`STANDARD`] and [`URL_SAFE_NO_PAD`] are strict
49//! scalar encoders/decoders with localized diagnostics. They are not
50//! constant-time token validators or key-material decoders: strict decode and
51//! validation may branch or return early based on malformed input, and strict
52//! [`DecodeError`] values can include input-derived bytes and indexes. Do not
53//! log strict decode errors verbatim for secret-bearing input; log
54//! [`DecodeError::kind`] instead. Use [`ct::STANDARD`],
55//! [`ct::URL_SAFE_NO_PAD`], or [`Engine::ct_decoder`] for secret-bearing
56//! payloads where decode timing posture matters more than exact error indexes.
57//!
58//! Recommended heap-owning pattern for secret-bearing standard Base64:
59//!
60//! ```
61//! # #[cfg(feature = "alloc")]
62//! # {
63//! use base64_ng::ct;
64//!
65//! let expected = b"session-key";
66//! let decoded = ct::STANDARD.decode_secret(b"c2Vzc2lvbi1rZXk=").unwrap();
67//!
68//! assert!(decoded.constant_time_eq_public_len(expected));
69//! # }
70//! ```
71//!
72//! For shared-memory, enclave-adjacent, HSM-style, or multi-principal
73//! deployments where even transient writes into caller-owned output are
74//! unacceptable, use [`ct::CtEngine::decode_slice_staged_clear_tail`] with a
75//! private staging buffer.
76//!
77//! # Zeroization Caveat
78//!
79//! Cleanup APIs and redacted buffers use dependency-free best-effort wiping:
80//! byte-wise volatile zero writes followed by an architecture-gated inline
81//! assembly barrier plus a hardware store-ordering fence where stable Rust
82//! supports it, and a compiler fence on all targets. This resists common
83//! compiler dead-store elimination and orders the issued zero stores on native
84//! supported architectures, but it is not a formal zeroization guarantee and
85//! cannot clear historical copies, registers, cache lines, write buffers, swap,
86//! hibernation images, core dumps, cold-boot remanence, or OS-level memory
87//! snapshots.
88//! High-assurance applications should apply their own approved zeroization
89//! policy to caller-owned buffers at the protocol boundary. Architectures
90//! without a native wipe barrier fail closed by default unless
91//! `allow-compiler-fence-only-wipe` is enabled after platform review. On
92//! `wasm32`, the wipe barrier is compiler-fence-only and cannot constrain
93//! downstream wasm runtime JITs. For that reason, `wasm32` builds fail closed
94//! by default. Enable `allow-wasm32-best-effort-wipe` only when the deployment
95//! explicitly accepts compiler-fence-only cleanup and applies its own memory
96//! strategy.
97
98#[cfg(feature = "alloc")]
99extern crate alloc;
100
101#[cfg(all(target_arch = "wasm32", not(feature = "allow-wasm32-best-effort-wipe")))]
102compile_error!(
103 "base64-ng: wasm32 builds use a compiler-fence-only wipe barrier that cannot \
104 constrain downstream wasm runtime JITs. Enable \
105 `allow-wasm32-best-effort-wipe` to accept this limitation and use \
106 caller-owned, platform-approved zeroization for high-assurance wasm deployments."
107);
108
109#[cfg(all(
110 not(miri),
111 not(feature = "allow-compiler-fence-only-wipe"),
112 not(any(
113 target_arch = "aarch64",
114 target_arch = "arm",
115 target_arch = "riscv32",
116 target_arch = "riscv64",
117 target_arch = "wasm32",
118 target_arch = "x86",
119 target_arch = "x86_64",
120 ))
121))]
122compile_error!(
123 "base64-ng: this architecture has no native hardware wipe barrier in \
124 base64-ng. Enable `allow-compiler-fence-only-wipe` only after reviewing \
125 docs/UNSAFE.md and applying platform-approved memory hygiene controls."
126);
127
128mod alphabet;
129mod buffers;
130mod cleanup;
131pub mod ct;
132mod errors;
133mod length;
134mod profiles;
135mod scalar;
136mod wrap;
137
138pub use alphabet::{
139 Alphabet, AlphabetError, Bcrypt, Crypt, Standard, UrlSafe, decode_alphabet_byte,
140 validate_alphabet,
141};
142pub(crate) use alphabet::{encode_base64_value, encode_base64_value_runtime};
143pub use buffers::{DecodedBuffer, EncodedBuffer, ExposedDecodedArray, ExposedEncodedArray};
144#[cfg(feature = "alloc")]
145pub use buffers::{ExposedSecretString, ExposedSecretVec, SecretBuffer};
146pub(crate) use cleanup::{wipe_bytes, wipe_tail};
147#[cfg(feature = "alloc")]
148pub(crate) use cleanup::{wipe_vec_all, wipe_vec_spare_capacity};
149pub(crate) use ct::{
150 constant_time_eq_fixed_width_array, constant_time_eq_public_len, ct_mask_eq_u8, ct_mask_lt_u8,
151};
152#[cfg(test)]
153pub(crate) use ct::{ct_padded_final_quantum, report_ct_error};
154pub use errors::{DecodeError, DecodeErrorKind, EncodeError};
155pub use length::{
156 LineEnding, LineWrap, checked_encoded_len, checked_wrapped_encoded_len, decoded_capacity,
157 decoded_len, encoded_len, wrapped_encoded_len,
158};
159pub(crate) use length::{decoded_len_padded, decoded_len_unpadded};
160pub use profiles::{BCRYPT, CRYPT, MIME, PEM, PEM_CRLF, Profile};
161#[cfg(kani)]
162pub(crate) use scalar::decode_byte;
163pub(crate) use scalar::{
164 decode_chunk, decode_tail_unpadded, read_quad, validate_chunk, validate_decode,
165 validate_tail_unpadded,
166};
167pub(crate) use wrap::{
168 compact_wrapped_input, decode_legacy_to_slice, decode_wrapped_to_slice, is_legacy_whitespace,
169 validate_legacy_decode, validate_wrapped_decode, write_wrapped_byte, write_wrapped_bytes,
170};
171
172#[cfg(feature = "simd")]
173mod simd;
174
175/// Runtime backend reporting for security-sensitive deployments.
176///
177/// This module does not enable acceleration. It exposes the backend posture so
178/// callers can log, assert, or audit whether execution is scalar-only or merely
179/// detecting future SIMD candidates.
180pub mod runtime;
181
182#[cfg(feature = "stream")]
183pub mod stream;
184
185/// Standard Base64 engine with padding.
186///
187/// This default strict engine is not a constant-time token validator or
188/// key-material decoder. Use [`ct::STANDARD`] or [`Engine::ct_decoder`] for the
189/// matching constant-time-oriented decoder when timing posture matters.
190#[doc(alias = "ct")]
191#[doc(alias = "constant_time")]
192#[doc(alias = "sensitive")]
193pub const STANDARD: Engine<Standard, true> = Engine::new();
194
195/// Standard Base64 engine without padding.
196///
197/// This default strict engine is not a constant-time token validator or
198/// key-material decoder. Use [`ct::STANDARD_NO_PAD`] or [`Engine::ct_decoder`]
199/// for the matching constant-time-oriented decoder when timing posture
200/// matters.
201#[doc(alias = "ct")]
202#[doc(alias = "constant_time")]
203#[doc(alias = "sensitive")]
204pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
205
206/// URL-safe Base64 engine with padding.
207///
208/// This default strict engine is not a constant-time token validator or
209/// key-material decoder. Use [`ct::URL_SAFE`] or [`Engine::ct_decoder`] for the
210/// matching constant-time-oriented decoder when timing posture matters.
211#[doc(alias = "ct")]
212#[doc(alias = "constant_time")]
213#[doc(alias = "sensitive")]
214pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
215
216/// URL-safe Base64 engine without padding.
217///
218/// This default strict engine is not a constant-time token validator or
219/// key-material decoder. Use [`ct::URL_SAFE_NO_PAD`] or [`Engine::ct_decoder`]
220/// for the matching constant-time-oriented decoder when timing posture
221/// matters.
222#[doc(alias = "ct")]
223#[doc(alias = "constant_time")]
224#[doc(alias = "sensitive")]
225pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
226
227/// bcrypt-style Base64 engine without padding.
228///
229/// This uses the bcrypt alphabet with the crate's normal Base64 bit packing.
230/// It does not parse complete bcrypt password-hash strings. This default strict
231/// engine is not a constant-time token validator or key-material decoder; use
232/// [`Engine::ct_decoder`] for the matching constant-time-oriented decoder when
233/// timing posture matters.
234#[doc(alias = "ct")]
235#[doc(alias = "constant_time")]
236#[doc(alias = "sensitive")]
237pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
238
239/// Unix `crypt(3)`-style Base64 engine without padding.
240///
241/// This uses the `crypt(3)` alphabet with the crate's normal Base64 bit
242/// packing. It does not parse complete password-hash strings. This default
243/// strict engine is not a constant-time token validator or key-material
244/// decoder; use [`Engine::ct_decoder`] for the matching constant-time-oriented
245/// decoder when timing posture matters.
246#[doc(alias = "ct")]
247#[doc(alias = "constant_time")]
248#[doc(alias = "sensitive")]
249pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
250
251/// Encodes `input` as strict standard padded Base64.
252///
253/// This is a convenience wrapper around [`Engine::encode_string`] on
254/// [`STANDARD`] for callers migrating from simpler Base64 APIs. It requires
255/// the `alloc` feature because it returns an owned string.
256///
257/// # Examples
258///
259/// ```
260/// assert_eq!(base64_ng::encode(b"hello").unwrap(), "aGVsbG8=");
261/// ```
262#[cfg(feature = "alloc")]
263pub fn encode(input: &[u8]) -> Result<alloc::string::String, EncodeError> {
264 STANDARD.encode_string(input)
265}
266
267/// Decodes strict standard padded Base64 into an owned byte vector.
268///
269/// This is a convenience wrapper around [`Engine::decode_vec`] on
270/// [`STANDARD`].
271/// It uses the normal strict decoder, not the [`crate::ct`] module, and may
272/// branch or return early on malformed input. For secret-bearing payloads where
273/// malformed-input timing matters, use
274/// [`crate::ct::CtEngine::decode_secret`] through [`crate::ct::STANDARD`]
275/// instead.
276///
277/// # Examples
278///
279/// ```
280/// assert_eq!(base64_ng::decode("aGVsbG8=").unwrap(), b"hello");
281/// ```
282#[cfg(feature = "alloc")]
283#[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
284pub fn decode(input: impl AsRef<[u8]>) -> Result<alloc::vec::Vec<u8>, DecodeError> {
285 STANDARD.decode_vec(input.as_ref())
286}
287
288/// Compares two fixed-width byte arrays without a length-mismatch branch.
289///
290/// Use this helper when the value length itself should not be represented as a
291/// timing-distinct branch in the comparison API. The array length `N` is a
292/// compile-time public type fact, and the helper scans exactly `N` bytes before
293/// returning. The final equality result remains public. This is still a
294/// dependency-free, constant-time-oriented best-effort helper, not a formally
295/// verified cryptographic comparison primitive.
296///
297/// # Examples
298///
299/// ```
300/// use base64_ng::constant_time_eq_fixed_width;
301///
302/// assert!(constant_time_eq_fixed_width(b"token", b"token"));
303/// assert!(!constant_time_eq_fixed_width(b"token", b"Token"));
304/// ```
305#[must_use]
306pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
307 constant_time_eq_fixed_width_array(left, right)
308}
309
310/// Compares two byte slices with a public length-mismatch branch.
311///
312/// Equal-length inputs are scanned fully before returning. Different lengths
313/// return `false` immediately because length is treated as public. This is a
314/// dependency-free, constant-time-oriented best-effort helper, not a formally
315/// verified cryptographic MAC, password, or bearer-token comparison primitive.
316///
317/// # Security
318///
319/// This helper is intended to avoid ordinary early-exit equality on values
320/// whose length is public. It is not a formal constant-time guarantee and
321/// should not be the sole primitive admitted at MAC, password, or bearer-token
322/// protocol boundaries in high-assurance systems. Use a reviewed comparison
323/// primitive at that boundary when your dependency policy allows one.
324///
325/// # Examples
326///
327/// ```
328/// assert!(base64_ng::constant_time_eq(b"token", b"token"));
329/// assert!(!base64_ng::constant_time_eq(b"token", b"Token"));
330/// assert!(!base64_ng::constant_time_eq(b"token", b"token2"));
331/// ```
332#[must_use]
333pub fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
334 constant_time_eq_public_len(left, right)
335}
336
337/// Clears caller-owned bytes with this crate's best-effort cleanup primitive.
338///
339/// This helper exposes the same dependency-free cleanup path used by
340/// `base64-ng` stack-backed buffers: byte-wise volatile zero writes followed by
341/// the target-specific wipe barrier documented in the crate-level
342/// zeroization caveat. It is intended for companion crates and applications
343/// that need a small reviewed cleanup primitive without pulling cleanup logic
344/// into generated code.
345///
346/// # Security
347///
348/// This is data-retention reduction, not a formal zeroization guarantee. It
349/// cannot clear historical copies, registers, cache lines, swap, hibernation
350/// images, core dumps, or platform snapshots. High-assurance deployments
351/// should pair it with their approved platform memory controls.
352pub fn clear_bytes(bytes: &mut [u8]) {
353 wipe_bytes(bytes);
354}
355
356/// A zero-sized Base64 engine parameterized by alphabet and padding policy.
357pub struct Engine<A, const PAD: bool> {
358 alphabet: core::marker::PhantomData<A>,
359}
360
361impl<A, const PAD: bool> Clone for Engine<A, PAD> {
362 fn clone(&self) -> Self {
363 *self
364 }
365}
366
367impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
368
369impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
370 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
371 formatter
372 .debug_struct("Engine")
373 .field("padded", &PAD)
374 .finish()
375 }
376}
377
378impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
379 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
380 write!(formatter, "padded={PAD}")
381 }
382}
383
384impl<A, const PAD: bool> Default for Engine<A, PAD> {
385 fn default() -> Self {
386 Self {
387 alphabet: core::marker::PhantomData,
388 }
389 }
390}
391
392impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
393
394impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
395 fn eq(&self, _other: &Self) -> bool {
396 true
397 }
398}
399
400impl<A, const PAD: bool> Engine<A, PAD>
401where
402 A: Alphabet,
403{
404 /// Creates a new engine value.
405 #[must_use]
406 pub const fn new() -> Self {
407 Self {
408 alphabet: core::marker::PhantomData,
409 }
410 }
411
412 /// Returns whether this engine uses padded Base64.
413 #[must_use]
414 pub const fn is_padded(&self) -> bool {
415 PAD
416 }
417
418 /// Returns this engine as an unwrapped profile.
419 ///
420 /// Use [`Profile::new`] or [`Profile::checked_new`] when a strict
421 /// line-wrapping policy should travel with the profile.
422 #[must_use]
423 pub const fn profile(&self) -> Profile<A, PAD> {
424 Profile::new(*self, None)
425 }
426
427 /// Returns the matching constant-time-oriented decoder for this engine's
428 /// alphabet and padding policy.
429 ///
430 /// The returned decoder is still an explicit opt-in to the [`ct`] module's
431 /// slower, opaque-error, constant-time-oriented scalar path.
432 #[must_use]
433 pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
434 ct::CtEngine::new()
435 }
436
437 /// Wraps a `std::io::Write` value in a streaming Base64 encoder.
438 ///
439 /// This is a convenience constructor for [`stream::Encoder::new`] that
440 /// keeps the selected engine attached to the call site.
441 ///
442 /// ```
443 /// use std::io::Write;
444 /// use base64_ng::STANDARD;
445 ///
446 /// let mut encoder = STANDARD.encoder_writer(Vec::new());
447 /// encoder.write_all(b"hello").unwrap();
448 /// assert_eq!(encoder.finish().unwrap(), b"aGVsbG8=");
449 /// ```
450 #[cfg(feature = "stream")]
451 #[must_use]
452 pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
453 stream::Encoder::new(inner, *self)
454 }
455
456 /// Wraps a `std::io::Write` value in a streaming Base64 decoder.
457 ///
458 /// This is a convenience constructor for [`stream::Decoder::new`] that
459 /// keeps the selected engine attached to the call site.
460 ///
461 /// ```
462 /// use std::io::Write;
463 /// use base64_ng::STANDARD;
464 ///
465 /// let mut decoder = STANDARD.decoder_writer(Vec::new());
466 /// decoder.write_all(b"aGVsbG8=").unwrap();
467 /// assert_eq!(decoder.finish().unwrap(), b"hello");
468 /// ```
469 ///
470 /// # Security
471 ///
472 /// Streaming decoders use the normal strict decode path, not the
473 /// [`crate::ct`] module. Do not use this adapter for secret-bearing
474 /// payloads when malformed-input timing matters.
475 #[cfg(feature = "stream")]
476 #[must_use]
477 pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
478 stream::Decoder::new(inner, *self)
479 }
480
481 /// Wraps a `std::io::Read` value in a streaming Base64 encoder.
482 ///
483 /// This is a convenience constructor for [`stream::EncoderReader::new`]
484 /// that keeps the selected engine attached to the call site.
485 ///
486 /// ```
487 /// use std::io::Read;
488 /// use base64_ng::STANDARD;
489 ///
490 /// let mut reader = STANDARD.encoder_reader(&b"hello"[..]);
491 /// let mut encoded = String::new();
492 /// reader.read_to_string(&mut encoded).unwrap();
493 /// assert_eq!(encoded, "aGVsbG8=");
494 /// ```
495 #[cfg(feature = "stream")]
496 #[must_use]
497 pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
498 stream::EncoderReader::new(inner, *self)
499 }
500
501 /// Wraps a `std::io::Read` value in a streaming Base64 decoder.
502 ///
503 /// This is a convenience constructor for [`stream::DecoderReader::new`]
504 /// that keeps the selected engine attached to the call site.
505 ///
506 /// ```
507 /// use std::io::Read;
508 /// use base64_ng::STANDARD;
509 ///
510 /// let mut reader = STANDARD.decoder_reader(&b"aGVsbG8="[..]);
511 /// let mut decoded = Vec::new();
512 /// reader.read_to_end(&mut decoded).unwrap();
513 /// assert_eq!(decoded, b"hello");
514 /// ```
515 ///
516 /// # Security
517 ///
518 /// Streaming decoder readers use the normal strict decode path, not the
519 /// [`crate::ct`] module. Do not use this adapter for secret-bearing
520 /// payloads when malformed-input timing matters.
521 #[cfg(feature = "stream")]
522 #[must_use]
523 pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
524 stream::DecoderReader::new(inner, *self)
525 }
526
527 /// Returns the encoded length for this engine's padding policy.
528 pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
529 encoded_len(input_len, PAD)
530 }
531
532 /// Returns the encoded length for this engine, or `None` on overflow.
533 #[must_use]
534 pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
535 checked_encoded_len(input_len, PAD)
536 }
537
538 /// Returns the encoded length after applying a line wrapping policy.
539 ///
540 /// The returned length includes inserted line endings but does not include
541 /// a trailing line ending after the final encoded line.
542 pub const fn wrapped_encoded_len(
543 &self,
544 input_len: usize,
545 wrap: LineWrap,
546 ) -> Result<usize, EncodeError> {
547 wrapped_encoded_len(input_len, PAD, wrap)
548 }
549
550 /// Returns the encoded length after line wrapping, or `None` on overflow or
551 /// invalid line wrapping.
552 #[must_use]
553 pub const fn checked_wrapped_encoded_len(
554 &self,
555 input_len: usize,
556 wrap: LineWrap,
557 ) -> Option<usize> {
558 checked_wrapped_encoded_len(input_len, PAD, wrap)
559 }
560
561 /// Returns the exact decoded length implied by input length and padding.
562 ///
563 /// This validates padding placement and impossible lengths, but it does not
564 /// validate alphabet membership or non-canonical trailing bits.
565 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
566 decoded_len(input, PAD)
567 }
568
569 /// Returns the exact decoded length for the explicit legacy profile.
570 ///
571 /// The legacy profile ignores ASCII space, tab, carriage return, and line
572 /// feed bytes before applying the same alphabet, padding, and canonical-bit
573 /// checks as strict decoding.
574 pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
575 validate_legacy_decode::<A, PAD>(input)
576 }
577
578 /// Returns the exact decoded length for a line-wrapped profile.
579 ///
580 /// The wrapped profile accepts only the configured line ending. Non-final
581 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
582 /// may be shorter. A single trailing line ending after the final line is
583 /// accepted.
584 pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
585 validate_wrapped_decode::<A, PAD>(input, wrap)
586 }
587
588 /// Validates strict Base64 input without writing decoded bytes.
589 ///
590 /// This applies the same alphabet, padding, and canonical-bit checks as
591 /// [`Self::decode_slice`]. Use this method when malformed-input
592 /// diagnostics matter; use [`Self::validate`] when a boolean is enough.
593 /// This default validator is not constant-time; use
594 /// [`crate::ct::CtEngine::validate_result`] through [`Self::ct_decoder`]
595 /// for secret-bearing payloads where timing posture matters.
596 ///
597 /// # Examples
598 ///
599 /// ```
600 /// use base64_ng::STANDARD;
601 ///
602 /// STANDARD.validate_result(b"aGVsbG8=").unwrap();
603 /// assert!(STANDARD.validate_result(b"aGVsbG8").is_err());
604 /// ```
605 pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
606 validate_decode::<A, PAD>(input).map(|_| ())
607 }
608
609 /// Returns whether `input` is valid strict Base64 for this engine.
610 ///
611 /// This is a convenience wrapper around [`Self::validate_result`] and is
612 /// not constant-time. Use [`crate::ct::CtEngine::validate`] through
613 /// [`Self::ct_decoder`] for secret-bearing payloads where timing posture
614 /// matters.
615 ///
616 /// # Examples
617 ///
618 /// ```
619 /// use base64_ng::URL_SAFE_NO_PAD;
620 ///
621 /// assert!(URL_SAFE_NO_PAD.validate(b"-_8"));
622 /// assert!(!URL_SAFE_NO_PAD.validate(b"+/8"));
623 /// ```
624 #[must_use]
625 pub fn validate(&self, input: &[u8]) -> bool {
626 self.validate_result(input).is_ok()
627 }
628
629 /// Validates input using the explicit legacy whitespace profile.
630 ///
631 /// ASCII space, tab, carriage return, and line feed bytes are ignored
632 /// before applying the same alphabet, padding, and canonical-bit checks as
633 /// strict decoding.
634 ///
635 /// # Examples
636 ///
637 /// ```
638 /// use base64_ng::STANDARD;
639 ///
640 /// STANDARD.validate_legacy_result(b" aG\r\nVsbG8= ").unwrap();
641 /// assert!(STANDARD.validate_legacy_result(b" aG-=").is_err());
642 /// ```
643 pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
644 validate_legacy_decode::<A, PAD>(input).map(|_| ())
645 }
646
647 /// Returns whether `input` is valid for the explicit legacy whitespace
648 /// profile.
649 ///
650 /// This is a convenience wrapper around [`Self::validate_legacy_result`].
651 ///
652 /// # Examples
653 ///
654 /// ```
655 /// use base64_ng::STANDARD;
656 ///
657 /// assert!(STANDARD.validate_legacy(b" aG\r\nVsbG8= "));
658 /// assert!(!STANDARD.validate_legacy(b"aG-V"));
659 /// ```
660 #[must_use]
661 pub fn validate_legacy(&self, input: &[u8]) -> bool {
662 self.validate_legacy_result(input).is_ok()
663 }
664
665 /// Validates input using a strict line-wrapped profile.
666 ///
667 /// This is stricter than [`Self::validate_legacy_result`]: it accepts only
668 /// the configured line ending and enforces the configured line length for
669 /// every non-final line.
670 ///
671 /// # Examples
672 ///
673 /// ```
674 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
675 ///
676 /// let wrap = LineWrap::new(4, LineEnding::Lf);
677 /// STANDARD.validate_wrapped_result(b"aGVs\nbG8=", wrap).unwrap();
678 /// assert!(STANDARD.validate_wrapped_result(b"aG\nVsbG8=", wrap).is_err());
679 /// ```
680 pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
681 validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
682 }
683
684 /// Returns whether `input` is valid for a strict line-wrapped profile.
685 ///
686 /// This is a convenience wrapper around [`Self::validate_wrapped_result`].
687 ///
688 /// # Examples
689 ///
690 /// ```
691 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
692 ///
693 /// let wrap = LineWrap::new(4, LineEnding::Lf);
694 /// assert!(STANDARD.validate_wrapped(b"aGVs\nbG8=", wrap));
695 /// assert!(!STANDARD.validate_wrapped(b"aG\nVsbG8=", wrap));
696 /// ```
697 #[must_use]
698 pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
699 self.validate_wrapped_result(input, wrap).is_ok()
700 }
701
702 /// Encodes a fixed-size input into a fixed-size output array in const contexts.
703 ///
704 /// Stable Rust does not yet allow this API to return an array whose length
705 /// is computed from `INPUT_LEN` directly. Instead, the caller supplies the
706 /// output length through the destination type and this function panics
707 /// during const evaluation if the length is wrong.
708 ///
709 /// # Panics
710 ///
711 /// Panics if `OUTPUT_LEN` is not exactly the encoded length for `INPUT_LEN`
712 /// and this engine's padding policy, or if that length overflows `usize`.
713 ///
714 /// # Examples
715 ///
716 /// ```
717 /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
718 ///
719 /// const HELLO: [u8; 8] = STANDARD.encode_array(b"hello");
720 /// const URL_SAFE: [u8; 3] = URL_SAFE_NO_PAD.encode_array(b"\xfb\xff");
721 ///
722 /// assert_eq!(&HELLO, b"aGVsbG8=");
723 /// assert_eq!(&URL_SAFE, b"-_8");
724 /// ```
725 ///
726 /// Incorrect output lengths fail during const evaluation:
727 ///
728 /// ```compile_fail
729 /// use base64_ng::STANDARD;
730 ///
731 /// const TOO_SHORT: [u8; 7] = STANDARD.encode_array(b"hello");
732 /// ```
733 #[must_use]
734 pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
735 &self,
736 input: &[u8; INPUT_LEN],
737 ) -> [u8; OUTPUT_LEN] {
738 let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
739 panic!("encoded base64 length overflows usize");
740 };
741 assert!(
742 required == OUTPUT_LEN,
743 "base64 output array has incorrect length"
744 );
745
746 let mut output = [0u8; OUTPUT_LEN];
747 let mut read = 0;
748 let mut write = 0;
749 while INPUT_LEN - read >= 3 {
750 let b0 = input[read];
751 let b1 = input[read + 1];
752 let b2 = input[read + 2];
753
754 output[write] = encode_base64_value::<A>(b0 >> 2);
755 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
756 output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
757 output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
758
759 read += 3;
760 write += 4;
761 }
762
763 match INPUT_LEN - read {
764 0 => {}
765 1 => {
766 let b0 = input[read];
767 output[write] = encode_base64_value::<A>(b0 >> 2);
768 output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
769 write += 2;
770 if PAD {
771 output[write] = b'=';
772 output[write + 1] = b'=';
773 }
774 }
775 2 => {
776 let b0 = input[read];
777 let b1 = input[read + 1];
778 output[write] = encode_base64_value::<A>(b0 >> 2);
779 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
780 output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
781 if PAD {
782 output[write + 3] = b'=';
783 }
784 }
785 _ => unreachable!(),
786 }
787
788 output
789 }
790
791 /// Encodes `input` into `output`, returning the number of bytes written.
792 pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
793 scalar::encode_slice::<A, PAD>(input, output)
794 }
795
796 /// Encodes `input` into `output` with line wrapping.
797 ///
798 /// The wrapping policy inserts line endings between encoded lines and does
799 /// not append a trailing line ending after the final line.
800 ///
801 /// # Examples
802 ///
803 /// ```
804 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
805 ///
806 /// let wrap = LineWrap::new(4, LineEnding::Lf);
807 /// let mut output = [0u8; 9];
808 /// let written = STANDARD
809 /// .encode_slice_wrapped(b"hello", &mut output, wrap)
810 /// .unwrap();
811 ///
812 /// assert_eq!(&output[..written], b"aGVs\nbG8=");
813 /// ```
814 pub fn encode_slice_wrapped(
815 &self,
816 input: &[u8],
817 output: &mut [u8],
818 wrap: LineWrap,
819 ) -> Result<usize, EncodeError> {
820 let required = self.wrapped_encoded_len(input.len(), wrap)?;
821 if output.len() < required {
822 return Err(EncodeError::OutputTooSmall {
823 required,
824 available: output.len(),
825 });
826 }
827
828 let encoded_len =
829 checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
830 if encoded_len == 0 {
831 return Ok(0);
832 }
833
834 // If the temporary in-buffer layout size overflows, fall back to the
835 // fixed scratch buffer path rather than relying on saturated arithmetic.
836 let combined_required = match required.checked_add(encoded_len) {
837 Some(len) => len,
838 None => usize::MAX,
839 };
840 if output.len() < combined_required {
841 let mut scratch = [0u8; 1024];
842 let mut input_offset = 0;
843 let mut output_offset = 0;
844 let mut column = 0;
845
846 while input_offset < input.len() {
847 let remaining = input.len() - input_offset;
848 let mut take = remaining.min(768);
849 if remaining > take {
850 take -= take % 3;
851 }
852 if take == 0 {
853 take = remaining;
854 }
855
856 let encoded = match self
857 .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
858 {
859 Ok(encoded) => encoded,
860 Err(err) => {
861 wipe_bytes(&mut scratch);
862 return Err(err);
863 }
864 };
865 if let Err(err) = write_wrapped_bytes(
866 &scratch[..encoded],
867 output,
868 &mut output_offset,
869 &mut column,
870 wrap,
871 ) {
872 wipe_bytes(&mut scratch);
873 return Err(err);
874 }
875 wipe_bytes(&mut scratch[..encoded]);
876 input_offset += take;
877 }
878
879 Ok(output_offset)
880 } else {
881 let encoded =
882 self.encode_slice(input, &mut output[required..required + encoded_len])?;
883 let mut output_offset = 0;
884 let mut column = 0;
885 let mut read = required;
886 while read < required + encoded {
887 let byte = output[read];
888 write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
889 read += 1;
890 }
891 wipe_bytes(&mut output[required..required + encoded]);
892 Ok(output_offset)
893 }
894 }
895
896 /// Encodes `input` with line wrapping and clears all bytes after the
897 /// encoded prefix.
898 ///
899 /// If encoding fails, the entire output buffer is cleared before the error
900 /// is returned.
901 pub fn encode_slice_wrapped_clear_tail(
902 &self,
903 input: &[u8],
904 output: &mut [u8],
905 wrap: LineWrap,
906 ) -> Result<usize, EncodeError> {
907 let written = match self.encode_slice_wrapped(input, output, wrap) {
908 Ok(written) => written,
909 Err(err) => {
910 wipe_bytes(output);
911 return Err(err);
912 }
913 };
914 wipe_tail(output, written);
915 Ok(written)
916 }
917
918 /// Encodes `input` with line wrapping into a stack-backed buffer.
919 ///
920 /// This is useful for MIME/PEM-style protocols where heap allocation is
921 /// unnecessary. If encoding fails, the internal backing array is cleared
922 /// before the error is returned.
923 pub fn encode_wrapped_buffer<const CAP: usize>(
924 &self,
925 input: &[u8],
926 wrap: LineWrap,
927 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
928 let mut output = EncodedBuffer::new();
929 let written =
930 match self.encode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
931 Ok(written) => written,
932 Err(err) => {
933 output.clear();
934 return Err(err);
935 }
936 };
937 output.set_filled(written)?;
938 Ok(output)
939 }
940
941 /// Encodes `input` with line wrapping into a newly allocated byte vector.
942 #[cfg(feature = "alloc")]
943 #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
944 pub fn encode_wrapped_vec(
945 &self,
946 input: &[u8],
947 wrap: LineWrap,
948 ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
949 let required = self.wrapped_encoded_len(input.len(), wrap)?;
950 let mut output = alloc::vec![0; required];
951 let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
952 output.truncate(written);
953 Ok(output)
954 }
955
956 /// Encodes `input` with line wrapping into a newly allocated UTF-8 string.
957 #[cfg(feature = "alloc")]
958 pub fn encode_wrapped_string(
959 &self,
960 input: &[u8],
961 wrap: LineWrap,
962 ) -> Result<alloc::string::String, EncodeError> {
963 let output = self.encode_wrapped_vec(input, wrap)?;
964 match alloc::string::String::from_utf8(output) {
965 Ok(output) => Ok(output),
966 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
967 }
968 }
969
970 /// Encodes `input` with line wrapping into a redacted owned secret buffer.
971 ///
972 /// This is useful when the wrapped encoded representation itself is
973 /// sensitive and should not be accidentally logged through formatting.
974 #[cfg(feature = "alloc")]
975 pub fn encode_wrapped_secret(
976 &self,
977 input: &[u8],
978 wrap: LineWrap,
979 ) -> Result<SecretBuffer, EncodeError> {
980 self.encode_wrapped_vec(input, wrap)
981 .map(SecretBuffer::from_vec)
982 }
983
984 /// Encodes `input` into `output` and clears all bytes after the encoded
985 /// prefix.
986 ///
987 /// If encoding fails, the entire output buffer is cleared before the error
988 /// is returned.
989 ///
990 /// # Examples
991 ///
992 /// ```
993 /// use base64_ng::STANDARD;
994 ///
995 /// let mut output = [0xff; 12];
996 /// let written = STANDARD
997 /// .encode_slice_clear_tail(b"hello", &mut output)
998 /// .unwrap();
999 ///
1000 /// assert_eq!(&output[..written], b"aGVsbG8=");
1001 /// assert!(output[written..].iter().all(|byte| *byte == 0));
1002 /// ```
1003 pub fn encode_slice_clear_tail(
1004 &self,
1005 input: &[u8],
1006 output: &mut [u8],
1007 ) -> Result<usize, EncodeError> {
1008 let written = match self.encode_slice(input, output) {
1009 Ok(written) => written,
1010 Err(err) => {
1011 wipe_bytes(output);
1012 return Err(err);
1013 }
1014 };
1015 wipe_tail(output, written);
1016 Ok(written)
1017 }
1018
1019 /// Encodes `input` into a stack-backed buffer.
1020 ///
1021 /// This helper is useful for short values where callers want the
1022 /// convenience of an owned result without enabling `alloc`.
1023 ///
1024 /// # Examples
1025 ///
1026 /// ```
1027 /// use base64_ng::STANDARD;
1028 ///
1029 /// let encoded = STANDARD.encode_buffer::<8>(b"hello").unwrap();
1030 ///
1031 /// assert_eq!(encoded.as_str(), "aGVsbG8=");
1032 /// ```
1033 pub fn encode_buffer<const CAP: usize>(
1034 &self,
1035 input: &[u8],
1036 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
1037 let mut output = EncodedBuffer::new();
1038 let written = match self.encode_slice_clear_tail(input, output.as_mut_capacity()) {
1039 Ok(written) => written,
1040 Err(err) => {
1041 output.clear();
1042 return Err(err);
1043 }
1044 };
1045 output.set_filled(written)?;
1046 Ok(output)
1047 }
1048
1049 /// Encodes `input` into a newly allocated byte vector.
1050 #[cfg(feature = "alloc")]
1051 #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
1052 pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
1053 let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
1054 let mut output = alloc::vec![0; required];
1055 let written = self.encode_slice(input, &mut output)?;
1056 output.truncate(written);
1057 Ok(output)
1058 }
1059
1060 /// Encodes `input` into a redacted owned secret buffer.
1061 ///
1062 /// This is useful when the encoded representation itself is sensitive and
1063 /// should not be accidentally logged through formatting.
1064 #[cfg(feature = "alloc")]
1065 pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
1066 self.encode_vec(input).map(SecretBuffer::from_vec)
1067 }
1068
1069 /// Encodes `input` into a newly allocated UTF-8 string.
1070 ///
1071 /// Base64 output is ASCII by construction. This helper is available with
1072 /// the `alloc` feature and has the same encoding semantics as
1073 /// [`Self::encode_slice`].
1074 ///
1075 /// # Examples
1076 ///
1077 /// ```
1078 /// use base64_ng::{STANDARD, URL_SAFE_NO_PAD};
1079 ///
1080 /// assert_eq!(STANDARD.encode_string(b"hello").unwrap(), "aGVsbG8=");
1081 /// assert_eq!(URL_SAFE_NO_PAD.encode_string(b"\xfb\xff").unwrap(), "-_8");
1082 /// ```
1083 #[cfg(feature = "alloc")]
1084 pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
1085 let output = self.encode_vec(input)?;
1086 match alloc::string::String::from_utf8(output) {
1087 Ok(output) => Ok(output),
1088 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1089 }
1090 }
1091
1092 /// Encodes the first `input_len` bytes of `buffer` in place.
1093 ///
1094 /// The buffer must have enough spare capacity for the encoded output. The
1095 /// implementation writes from right to left, so unread input bytes are not
1096 /// overwritten before they are encoded.
1097 ///
1098 /// # Panics
1099 ///
1100 /// Panics only if an internal right-to-left encode invariant is violated.
1101 /// This indicates a bug in `base64-ng`; valid or malformed caller input is
1102 /// reported through [`EncodeError`] instead.
1103 ///
1104 /// # Examples
1105 ///
1106 /// ```
1107 /// use base64_ng::STANDARD;
1108 ///
1109 /// let mut buffer = [0u8; 8];
1110 /// buffer[..5].copy_from_slice(b"hello");
1111 /// let encoded = STANDARD.encode_in_place(&mut buffer, 5).unwrap();
1112 /// assert_eq!(encoded, b"aGVsbG8=");
1113 /// ```
1114 pub fn encode_in_place<'a>(
1115 &self,
1116 buffer: &'a mut [u8],
1117 input_len: usize,
1118 ) -> Result<&'a mut [u8], EncodeError> {
1119 if input_len > buffer.len() {
1120 return Err(EncodeError::InputTooLarge {
1121 input_len,
1122 buffer_len: buffer.len(),
1123 });
1124 }
1125
1126 let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
1127 if buffer.len() < required {
1128 return Err(EncodeError::OutputTooSmall {
1129 required,
1130 available: buffer.len(),
1131 });
1132 }
1133
1134 let mut read = input_len;
1135 let mut write = required;
1136
1137 match input_len % 3 {
1138 0 => {}
1139 1 => {
1140 read -= 1;
1141 let b0 = buffer[read];
1142 if PAD {
1143 write -= 4;
1144 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1145 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
1146 buffer[write + 2] = b'=';
1147 buffer[write + 3] = b'=';
1148 } else {
1149 write -= 2;
1150 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1151 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
1152 }
1153 }
1154 2 => {
1155 read -= 2;
1156 let b0 = buffer[read];
1157 let b1 = buffer[read + 1];
1158 if PAD {
1159 write -= 4;
1160 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1161 buffer[write + 1] =
1162 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1163 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
1164 buffer[write + 3] = b'=';
1165 } else {
1166 write -= 3;
1167 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1168 buffer[write + 1] =
1169 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1170 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
1171 }
1172 }
1173 _ => unreachable!(),
1174 }
1175
1176 while read > 0 {
1177 read -= 3;
1178 write -= 4;
1179 let b0 = buffer[read];
1180 let b1 = buffer[read + 1];
1181 let b2 = buffer[read + 2];
1182
1183 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1184 buffer[write + 1] =
1185 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1186 buffer[write + 2] =
1187 encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
1188 buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
1189 }
1190
1191 // The right-to-left loop consumes exactly three input bytes for every
1192 // four output bytes. If this invariant changes, returning a shifted
1193 // slice would silently corrupt the in-place output.
1194 assert_eq!(
1195 write, 0,
1196 "encode_in_place invariant violated: right-to-left loop did not complete"
1197 );
1198 Ok(&mut buffer[..required])
1199 }
1200
1201 /// Encodes the first `input_len` bytes of `buffer` in place and clears all
1202 /// bytes after the encoded prefix.
1203 ///
1204 /// If encoding fails because `input_len` is too large, the output buffer is
1205 /// too small, or the encoded length overflows `usize`, the entire buffer is
1206 /// cleared before the error is returned.
1207 ///
1208 /// # Examples
1209 ///
1210 /// ```
1211 /// use base64_ng::STANDARD;
1212 ///
1213 /// let mut buffer = [0xff; 12];
1214 /// buffer[..5].copy_from_slice(b"hello");
1215 /// let encoded = STANDARD.encode_in_place_clear_tail(&mut buffer, 5).unwrap();
1216 /// assert_eq!(encoded, b"aGVsbG8=");
1217 /// ```
1218 pub fn encode_in_place_clear_tail<'a>(
1219 &self,
1220 buffer: &'a mut [u8],
1221 input_len: usize,
1222 ) -> Result<&'a mut [u8], EncodeError> {
1223 let len = match self.encode_in_place(buffer, input_len) {
1224 Ok(encoded) => encoded.len(),
1225 Err(err) => {
1226 wipe_bytes(buffer);
1227 return Err(err);
1228 }
1229 };
1230 wipe_tail(buffer, len);
1231 Ok(&mut buffer[..len])
1232 }
1233
1234 /// Decodes `input` into `output`, returning the number of bytes written.
1235 ///
1236 /// This is strict decoding. Whitespace, mixed alphabets, malformed padding,
1237 /// and trailing non-padding data are rejected.
1238 ///
1239 /// # Security
1240 ///
1241 /// This default scalar decoder prioritizes strict validation, exact error
1242 /// reporting, and ordinary throughput. It may branch or return early based
1243 /// on byte validity, malformed input, padding position, and output
1244 /// capacity. It also reports exact failure positions and invalid byte
1245 /// values through [`DecodeError`]. Do not use this method for token
1246 /// comparison, key-material decoding, or secret-bearing validation where
1247 /// malformed-input timing matters. Do not log strict decode errors
1248 /// verbatim for secret-bearing input; log [`DecodeError::kind`] instead.
1249 /// Use [`crate::ct`],
1250 /// [`crate::ct::STANDARD`], [`crate::ct::URL_SAFE_NO_PAD`], or
1251 /// [`Self::ct_decoder`] with `decode_slice_clear_tail` for
1252 /// constant-time-oriented secret decoding.
1253 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1254 pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
1255 scalar::decode_slice::<A, PAD>(input, output)
1256 }
1257
1258 /// Decodes `input` into `output` and clears all bytes after the decoded
1259 /// prefix.
1260 ///
1261 /// If decoding fails, the entire output buffer is cleared before the error
1262 /// is returned.
1263 ///
1264 /// # Examples
1265 ///
1266 /// ```
1267 /// use base64_ng::STANDARD;
1268 ///
1269 /// let mut output = [0xff; 8];
1270 /// let written = STANDARD
1271 /// .decode_slice_clear_tail(b"aGk=", &mut output)
1272 /// .unwrap();
1273 ///
1274 /// assert_eq!(&output[..written], b"hi");
1275 /// assert!(output[written..].iter().all(|byte| *byte == 0));
1276 /// ```
1277 pub fn decode_slice_clear_tail(
1278 &self,
1279 input: &[u8],
1280 output: &mut [u8],
1281 ) -> Result<usize, DecodeError> {
1282 let written = match self.decode_slice(input, output) {
1283 Ok(written) => written,
1284 Err(err) => {
1285 wipe_bytes(output);
1286 return Err(err);
1287 }
1288 };
1289 wipe_tail(output, written);
1290 Ok(written)
1291 }
1292
1293 /// Decodes `input` into a stack-backed buffer.
1294 ///
1295 /// This helper is useful for short decoded values where callers want the
1296 /// convenience of an owned result without enabling `alloc`.
1297 ///
1298 /// # Examples
1299 ///
1300 /// ```
1301 /// use base64_ng::STANDARD;
1302 ///
1303 /// let decoded = STANDARD.decode_buffer::<5>(b"aGVsbG8=").unwrap();
1304 ///
1305 /// assert_eq!(decoded.as_bytes(), b"hello");
1306 /// ```
1307 pub fn decode_buffer<const CAP: usize>(
1308 &self,
1309 input: &[u8],
1310 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
1311 let mut output = DecodedBuffer::new();
1312 let written = match self.decode_slice_clear_tail(input, output.as_mut_capacity()) {
1313 Ok(written) => written,
1314 Err(err) => {
1315 output.clear();
1316 return Err(err);
1317 }
1318 };
1319 output.set_filled(written)?;
1320 Ok(output)
1321 }
1322
1323 /// Decodes `input` using the explicit legacy whitespace profile.
1324 ///
1325 /// ASCII space, tab, carriage return, and line feed bytes are ignored.
1326 /// Alphabet selection, padding placement, trailing data after padding, and
1327 /// non-canonical trailing bits remain strict.
1328 ///
1329 /// # Security
1330 ///
1331 /// This method uses the normal strict decode path after legacy whitespace
1332 /// handling. It may branch or return early based on malformed input and is
1333 /// not a constant-time token validator or key-material decoder. Use
1334 /// [`crate::ct`] for secret-bearing payloads.
1335 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1336 pub fn decode_slice_legacy(
1337 &self,
1338 input: &[u8],
1339 output: &mut [u8],
1340 ) -> Result<usize, DecodeError> {
1341 let required = validate_legacy_decode::<A, PAD>(input)?;
1342 if output.len() < required {
1343 return Err(DecodeError::OutputTooSmall {
1344 required,
1345 available: output.len(),
1346 });
1347 }
1348 decode_legacy_to_slice::<A, PAD>(input, output)
1349 }
1350
1351 /// Decodes `input` using the explicit legacy whitespace profile and clears
1352 /// all bytes after the decoded prefix.
1353 ///
1354 /// If validation or decoding fails, the entire output buffer is cleared
1355 /// before the error is returned.
1356 ///
1357 /// # Examples
1358 ///
1359 /// ```
1360 /// use base64_ng::STANDARD;
1361 ///
1362 /// let mut output = [0xff; 8];
1363 /// let written = STANDARD
1364 /// .decode_slice_legacy_clear_tail(b" aG\r\nk= ", &mut output)
1365 /// .unwrap();
1366 ///
1367 /// assert_eq!(&output[..written], b"hi");
1368 /// assert!(output[written..].iter().all(|byte| *byte == 0));
1369 /// ```
1370 pub fn decode_slice_legacy_clear_tail(
1371 &self,
1372 input: &[u8],
1373 output: &mut [u8],
1374 ) -> Result<usize, DecodeError> {
1375 let written = match self.decode_slice_legacy(input, output) {
1376 Ok(written) => written,
1377 Err(err) => {
1378 wipe_bytes(output);
1379 return Err(err);
1380 }
1381 };
1382 wipe_tail(output, written);
1383 Ok(written)
1384 }
1385
1386 /// Decodes `input` into a stack-backed buffer using the explicit legacy
1387 /// whitespace profile.
1388 ///
1389 /// ASCII space, tab, carriage return, and line feed bytes are ignored.
1390 /// Alphabet selection, padding placement, trailing data after padding, and
1391 /// non-canonical trailing bits remain strict. If decoding fails, the
1392 /// internal backing array is cleared before the error is returned.
1393 pub fn decode_buffer_legacy<const CAP: usize>(
1394 &self,
1395 input: &[u8],
1396 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
1397 let mut output = DecodedBuffer::new();
1398 let written = match self.decode_slice_legacy_clear_tail(input, output.as_mut_capacity()) {
1399 Ok(written) => written,
1400 Err(err) => {
1401 output.clear();
1402 return Err(err);
1403 }
1404 };
1405 output.set_filled(written)?;
1406 Ok(output)
1407 }
1408
1409 /// Decodes `input` using a strict line-wrapped profile.
1410 ///
1411 /// The wrapped profile accepts only the configured line ending. Non-final
1412 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
1413 /// may be shorter. A single trailing line ending after the final line is
1414 /// accepted.
1415 ///
1416 /// # Security
1417 ///
1418 /// This method uses the normal strict decode path after line-profile
1419 /// validation. It may branch or return early based on malformed input and
1420 /// is not a constant-time token validator or key-material decoder. Use
1421 /// [`crate::ct`] for secret-bearing payloads.
1422 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1423 pub fn decode_slice_wrapped(
1424 &self,
1425 input: &[u8],
1426 output: &mut [u8],
1427 wrap: LineWrap,
1428 ) -> Result<usize, DecodeError> {
1429 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
1430 if output.len() < required {
1431 return Err(DecodeError::OutputTooSmall {
1432 required,
1433 available: output.len(),
1434 });
1435 }
1436 decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
1437 }
1438
1439 /// Decodes `input` using a strict line-wrapped profile and clears all bytes
1440 /// after the decoded prefix.
1441 ///
1442 /// If validation or decoding fails, the entire output buffer is cleared
1443 /// before the error is returned.
1444 pub fn decode_slice_wrapped_clear_tail(
1445 &self,
1446 input: &[u8],
1447 output: &mut [u8],
1448 wrap: LineWrap,
1449 ) -> Result<usize, DecodeError> {
1450 let written = match self.decode_slice_wrapped(input, output, wrap) {
1451 Ok(written) => written,
1452 Err(err) => {
1453 wipe_bytes(output);
1454 return Err(err);
1455 }
1456 };
1457 wipe_tail(output, written);
1458 Ok(written)
1459 }
1460
1461 /// Decodes `input` using a strict line-wrapped profile into a stack-backed
1462 /// buffer.
1463 ///
1464 /// The wrapped profile accepts only the configured line ending. Non-final
1465 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
1466 /// may be shorter. A single trailing line ending after the final line is
1467 /// accepted. If decoding fails, the internal backing array is cleared
1468 /// before the error is returned.
1469 pub fn decode_wrapped_buffer<const CAP: usize>(
1470 &self,
1471 input: &[u8],
1472 wrap: LineWrap,
1473 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
1474 let mut output = DecodedBuffer::new();
1475 let written =
1476 match self.decode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
1477 Ok(written) => written,
1478 Err(err) => {
1479 output.clear();
1480 return Err(err);
1481 }
1482 };
1483 output.set_filled(written)?;
1484 Ok(output)
1485 }
1486
1487 /// Decodes `input` into a newly allocated byte vector.
1488 ///
1489 /// This is strict decoding with the same semantics as [`Self::decode_slice`].
1490 #[cfg(feature = "alloc")]
1491 #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
1492 pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
1493 let required = validate_decode::<A, PAD>(input)?;
1494 let mut output = alloc::vec![0; required];
1495 let written = match self.decode_slice(input, &mut output) {
1496 Ok(written) => written,
1497 Err(err) => {
1498 wipe_bytes(&mut output);
1499 return Err(err);
1500 }
1501 };
1502 output.truncate(written);
1503 Ok(output)
1504 }
1505
1506 /// Decodes `input` into a redacted owned secret buffer.
1507 ///
1508 /// On malformed input, the intermediate output buffer is cleared before the
1509 /// error is returned by [`Self::decode_vec`].
1510 #[cfg(feature = "alloc")]
1511 pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
1512 self.decode_vec(input).map(SecretBuffer::from_vec)
1513 }
1514
1515 /// Decodes `input` into a newly allocated byte vector using the explicit
1516 /// legacy whitespace profile.
1517 #[cfg(feature = "alloc")]
1518 #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
1519 pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
1520 let required = validate_legacy_decode::<A, PAD>(input)?;
1521 let mut output = alloc::vec![0; required];
1522 let written = match self.decode_slice_legacy(input, &mut output) {
1523 Ok(written) => written,
1524 Err(err) => {
1525 wipe_bytes(&mut output);
1526 return Err(err);
1527 }
1528 };
1529 output.truncate(written);
1530 Ok(output)
1531 }
1532
1533 /// Decodes `input` into a redacted owned secret buffer using the explicit
1534 /// legacy whitespace profile.
1535 ///
1536 /// ASCII space, tab, carriage return, and line feed bytes are ignored.
1537 /// Alphabet selection, padding placement, trailing data after padding, and
1538 /// non-canonical trailing bits remain strict.
1539 #[cfg(feature = "alloc")]
1540 pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
1541 self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
1542 }
1543
1544 /// Decodes line-wrapped input into a newly allocated byte vector.
1545 #[cfg(feature = "alloc")]
1546 #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
1547 pub fn decode_wrapped_vec(
1548 &self,
1549 input: &[u8],
1550 wrap: LineWrap,
1551 ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
1552 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
1553 let mut output = alloc::vec![0; required];
1554 let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
1555 Ok(written) => written,
1556 Err(err) => {
1557 wipe_bytes(&mut output);
1558 return Err(err);
1559 }
1560 };
1561 output.truncate(written);
1562 Ok(output)
1563 }
1564
1565 /// Decodes line-wrapped input into a redacted owned secret buffer.
1566 ///
1567 /// The wrapped profile accepts only the configured line ending. Non-final
1568 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
1569 /// may be shorter. A single trailing line ending after the final line is
1570 /// accepted.
1571 #[cfg(feature = "alloc")]
1572 pub fn decode_wrapped_secret(
1573 &self,
1574 input: &[u8],
1575 wrap: LineWrap,
1576 ) -> Result<SecretBuffer, DecodeError> {
1577 self.decode_wrapped_vec(input, wrap)
1578 .map(SecretBuffer::from_vec)
1579 }
1580
1581 /// Decodes `buffer` in place using a strict line-wrapped profile.
1582 ///
1583 /// The wrapped profile accepts only the configured line ending. Non-final
1584 /// lines must contain exactly `wrap.line_len` encoded bytes; the final line
1585 /// may be shorter. A single trailing line ending after the final line is
1586 /// accepted.
1587 ///
1588 /// # Security
1589 ///
1590 /// This method compacts line endings in place before decoding. If
1591 /// validation or decoding fails, the buffer contents are unspecified and
1592 /// may contain a compacted encoded prefix. On success, bytes after the
1593 /// returned decoded prefix may retain the compacted encoded representation.
1594 /// Use
1595 /// [`Self::decode_in_place_wrapped_clear_tail`] when the buffer may be
1596 /// reused or freed without a caller-managed wipe; treat that clear-tail
1597 /// variant as the default for secret-bearing wrapped payloads.
1598 ///
1599 /// # Examples
1600 ///
1601 /// ```
1602 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
1603 ///
1604 /// let mut buffer = *b"aGVs\nbG8=";
1605 /// let decoded = STANDARD
1606 /// .decode_in_place_wrapped(&mut buffer, LineWrap::new(4, LineEnding::Lf))
1607 /// .unwrap();
1608 ///
1609 /// assert_eq!(decoded, b"hello");
1610 /// ```
1611 pub fn decode_in_place_wrapped<'a>(
1612 &self,
1613 buffer: &'a mut [u8],
1614 wrap: LineWrap,
1615 ) -> Result<&'a mut [u8], DecodeError> {
1616 let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
1617 let compacted = compact_wrapped_input(buffer, wrap)?;
1618 let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
1619 Ok(&mut buffer[..len])
1620 }
1621
1622 /// Decodes `buffer` in place using a strict line-wrapped profile and clears
1623 /// all bytes after the decoded prefix.
1624 ///
1625 /// If validation or decoding fails, the entire buffer is cleared before the
1626 /// error is returned.
1627 ///
1628 /// # Examples
1629 ///
1630 /// ```
1631 /// use base64_ng::{LineEnding, LineWrap, STANDARD};
1632 ///
1633 /// let mut buffer = *b"aGVs\nbG8=";
1634 /// let len = STANDARD
1635 /// .decode_in_place_wrapped_clear_tail(&mut buffer, LineWrap::new(4, LineEnding::Lf))
1636 /// .unwrap()
1637 /// .len();
1638 ///
1639 /// assert_eq!(&buffer[..len], b"hello");
1640 /// assert!(buffer[len..].iter().all(|byte| *byte == 0));
1641 /// ```
1642 pub fn decode_in_place_wrapped_clear_tail<'a>(
1643 &self,
1644 buffer: &'a mut [u8],
1645 wrap: LineWrap,
1646 ) -> Result<&'a mut [u8], DecodeError> {
1647 if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
1648 wipe_bytes(buffer);
1649 return Err(err);
1650 }
1651
1652 let compacted = match compact_wrapped_input(buffer, wrap) {
1653 Ok(compacted) => compacted,
1654 Err(err) => {
1655 wipe_bytes(buffer);
1656 return Err(err);
1657 }
1658 };
1659
1660 let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
1661 Ok(len) => len,
1662 Err(err) => {
1663 wipe_bytes(buffer);
1664 return Err(err);
1665 }
1666 };
1667 wipe_tail(buffer, len);
1668 Ok(&mut buffer[..len])
1669 }
1670
1671 /// Decodes the buffer in place and returns the decoded prefix.
1672 ///
1673 /// On success, bytes after the returned decoded prefix may retain encoded
1674 /// input bytes. Use [`Self::decode_in_place_clear_tail`] when the buffer
1675 /// may be reused or freed without a caller-managed wipe.
1676 ///
1677 /// # Security
1678 ///
1679 /// This default scalar decoder prioritizes strict validation, exact error
1680 /// reporting, and ordinary throughput. It may branch or return early based
1681 /// on malformed input and reports exact failure positions and invalid byte
1682 /// values through [`DecodeError`]. Do not use this method for token
1683 /// comparison, key-material decoding, or secret-bearing validation where
1684 /// malformed-input timing matters. Do not log strict decode errors
1685 /// verbatim for secret-bearing input; log [`DecodeError::kind`] instead.
1686 ///
1687 /// # Examples
1688 ///
1689 /// ```
1690 /// use base64_ng::STANDARD_NO_PAD;
1691 ///
1692 /// let mut buffer = *b"Zm9vYmFy";
1693 /// let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
1694 /// assert_eq!(decoded, b"foobar");
1695 /// ```
1696 pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
1697 let len = Self::decode_slice_to_start(buffer)?;
1698 Ok(&mut buffer[..len])
1699 }
1700
1701 /// Decodes the buffer in place and clears all bytes after the decoded prefix.
1702 ///
1703 /// If decoding fails, the entire buffer is cleared before the error is
1704 /// returned. Use this variant when the encoded or partially decoded data is
1705 /// sensitive and the caller wants best-effort cleanup without adding a
1706 /// dependency.
1707 ///
1708 /// # Examples
1709 ///
1710 /// ```
1711 /// use base64_ng::STANDARD;
1712 ///
1713 /// let mut buffer = *b"aGk=";
1714 /// let decoded = STANDARD.decode_in_place_clear_tail(&mut buffer).unwrap();
1715 /// assert_eq!(decoded, b"hi");
1716 /// ```
1717 pub fn decode_in_place_clear_tail<'a>(
1718 &self,
1719 buffer: &'a mut [u8],
1720 ) -> Result<&'a mut [u8], DecodeError> {
1721 let len = match Self::decode_slice_to_start(buffer) {
1722 Ok(len) => len,
1723 Err(err) => {
1724 wipe_bytes(buffer);
1725 return Err(err);
1726 }
1727 };
1728 wipe_tail(buffer, len);
1729 Ok(&mut buffer[..len])
1730 }
1731
1732 /// Decodes `buffer` in place using the explicit legacy whitespace profile.
1733 ///
1734 /// Ignored whitespace is compacted out before decoding. If validation
1735 /// fails, the buffer contents are unspecified. On success, bytes after the
1736 /// returned decoded prefix may retain the compacted encoded
1737 /// representation. Use [`Self::decode_in_place_legacy_clear_tail`] when the
1738 /// buffer may be reused or freed without a caller-managed wipe.
1739 pub fn decode_in_place_legacy<'a>(
1740 &self,
1741 buffer: &'a mut [u8],
1742 ) -> Result<&'a mut [u8], DecodeError> {
1743 let _required = validate_legacy_decode::<A, PAD>(buffer)?;
1744 let mut write = 0;
1745 let mut read = 0;
1746 while read < buffer.len() {
1747 let byte = buffer[read];
1748 if !is_legacy_whitespace(byte) {
1749 buffer[write] = byte;
1750 write += 1;
1751 }
1752 read += 1;
1753 }
1754 let len = Self::decode_slice_to_start(&mut buffer[..write])?;
1755 Ok(&mut buffer[..len])
1756 }
1757
1758 /// Decodes `buffer` in place using the explicit legacy whitespace profile
1759 /// and clears all bytes after the decoded prefix.
1760 ///
1761 /// If validation or decoding fails, the entire buffer is cleared before the
1762 /// error is returned.
1763 pub fn decode_in_place_legacy_clear_tail<'a>(
1764 &self,
1765 buffer: &'a mut [u8],
1766 ) -> Result<&'a mut [u8], DecodeError> {
1767 if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
1768 wipe_bytes(buffer);
1769 return Err(err);
1770 }
1771
1772 let mut write = 0;
1773 let mut read = 0;
1774 while read < buffer.len() {
1775 let byte = buffer[read];
1776 if !is_legacy_whitespace(byte) {
1777 buffer[write] = byte;
1778 write += 1;
1779 }
1780 read += 1;
1781 }
1782
1783 let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
1784 Ok(len) => len,
1785 Err(err) => {
1786 wipe_bytes(buffer);
1787 return Err(err);
1788 }
1789 };
1790 wipe_tail(buffer, len);
1791 Ok(&mut buffer[..len])
1792 }
1793
1794 fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
1795 let _required = validate_decode::<A, PAD>(buffer)?;
1796 let input_len = buffer.len();
1797 let mut read = 0;
1798 let mut write = 0;
1799 while read + 4 <= input_len {
1800 let chunk = read_quad(buffer, read)?;
1801 let available = buffer.len();
1802 let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
1803 required: write,
1804 available,
1805 })?;
1806 let written = decode_chunk::<A, PAD>(chunk, output_tail)
1807 .map_err(|err| err.with_index_offset(read))?;
1808 read += 4;
1809 write += written;
1810 if written < 3 {
1811 if read != input_len {
1812 return Err(DecodeError::InvalidPadding { index: read - 4 });
1813 }
1814 return Ok(write);
1815 }
1816 }
1817
1818 let rem = input_len - read;
1819 if rem == 0 {
1820 return Ok(write);
1821 }
1822 if PAD {
1823 return Err(DecodeError::InvalidLength);
1824 }
1825 let mut tail = [0u8; 3];
1826 tail[..rem].copy_from_slice(&buffer[read..input_len]);
1827 decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
1828 .map_err(|err| err.with_index_offset(read))
1829 .map(|n| write + n)
1830 }
1831}
1832
1833#[cfg(kani)]
1834mod kani_proofs {
1835 use super::{
1836 STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
1837 decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
1838 };
1839
1840 #[kani::proof]
1841 fn checked_encoded_len_is_bounded_for_small_inputs() {
1842 let len = usize::from(kani::any::<u8>());
1843 let padded = kani::any::<bool>();
1844 let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
1845
1846 assert!(encoded >= len);
1847 assert!(encoded <= len / 3 * 4 + 4);
1848 }
1849
1850 #[kani::proof]
1851 fn decoded_capacity_is_bounded_for_small_inputs() {
1852 let len = usize::from(kani::any::<u8>());
1853 let capacity = decoded_capacity(len);
1854
1855 assert!(capacity <= len / 4 * 3 + 2);
1856 }
1857
1858 #[kani::proof]
1859 #[kani::unwind(70)]
1860 fn standard_in_place_decode_returns_prefix_within_buffer() {
1861 let mut buffer = kani::any::<[u8; 8]>();
1862 let result = STANDARD.decode_in_place(&mut buffer);
1863
1864 if let Ok(decoded) = result {
1865 assert!(decoded.len() <= 8);
1866 }
1867 }
1868
1869 #[kani::proof]
1870 #[kani::unwind(70)]
1871 fn standard_decode_slice_returns_written_within_output() {
1872 let input = kani::any::<[u8; 4]>();
1873 let mut output = kani::any::<[u8; 3]>();
1874 let result = STANDARD.decode_slice(&input, &mut output);
1875
1876 if let Ok(written) = result {
1877 assert!(written <= output.len());
1878 }
1879 }
1880
1881 #[kani::proof]
1882 #[kani::unwind(70)]
1883 fn standard_decode_chunk_returns_written_within_output() {
1884 let input = kani::any::<[u8; 4]>();
1885 let mut output = kani::any::<[u8; 3]>();
1886 let result = decode_chunk::<Standard, true>(input, &mut output);
1887
1888 if let Ok(written) = result {
1889 assert!(written <= output.len());
1890 assert!(written <= 3);
1891 }
1892 }
1893
1894 #[kani::proof]
1895 #[kani::unwind(70)]
1896 fn standard_decode_chunk_bit_packing_matches_decoded_values() {
1897 let input = kani::any::<[u8; 4]>();
1898 let mut output = kani::any::<[u8; 3]>();
1899 let result = decode_chunk::<Standard, true>(input, &mut output);
1900
1901 if let Ok(written) = result {
1902 let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
1903 let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
1904
1905 assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
1906
1907 if written >= 2 {
1908 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
1909 assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
1910 }
1911
1912 if written == 3 {
1913 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
1914 let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
1915 assert!(output[2] == ((v2 << 6) | v3));
1916 }
1917 }
1918 }
1919
1920 #[kani::proof]
1921 #[kani::unwind(70)]
1922 fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
1923 let input = kani::any::<[u8; 3]>();
1924 let len = usize::from(kani::any::<u8>() % 4);
1925 let result = validate_tail_unpadded::<Standard>(&input[..len]);
1926
1927 if result.is_ok() {
1928 assert!(len == 0 || len == 2 || len == 3);
1929 }
1930 }
1931
1932 #[kani::proof]
1933 #[kani::unwind(70)]
1934 fn standard_decode_two_byte_tail_returns_written_within_output() {
1935 let input = kani::any::<[u8; 2]>();
1936 let mut output = kani::any::<[u8; 1]>();
1937 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
1938
1939 if let Ok(written) = result {
1940 assert!(written <= output.len());
1941 assert!(written == 1);
1942 }
1943 }
1944
1945 #[kani::proof]
1946 #[kani::unwind(70)]
1947 fn standard_decode_three_byte_tail_returns_written_within_output() {
1948 let input = kani::any::<[u8; 3]>();
1949 let mut output = kani::any::<[u8; 2]>();
1950 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
1951
1952 if let Ok(written) = result {
1953 assert!(written <= output.len());
1954 assert!(written == 2);
1955 }
1956 }
1957
1958 #[kani::proof]
1959 #[kani::unwind(70)]
1960 fn standard_decode_slice_clear_tail_clears_output_on_error() {
1961 let input = kani::any::<[u8; 4]>();
1962 let mut output = kani::any::<[u8; 3]>();
1963 let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
1964
1965 if result.is_err() {
1966 assert!(output.iter().all(|byte| *byte == 0));
1967 }
1968 }
1969
1970 #[kani::proof]
1971 #[kani::unwind(70)]
1972 fn standard_encode_slice_returns_written_within_output() {
1973 let input = kani::any::<[u8; 3]>();
1974 let mut output = kani::any::<[u8; 4]>();
1975 let result = STANDARD.encode_slice(&input, &mut output);
1976
1977 if let Ok(written) = result {
1978 assert!(written <= output.len());
1979 }
1980 }
1981
1982 #[kani::proof]
1983 #[kani::unwind(70)]
1984 fn standard_encode_in_place_returns_prefix_within_buffer() {
1985 let mut buffer = kani::any::<[u8; 8]>();
1986 let input_len = usize::from(kani::any::<u8>() % 9);
1987 let result = STANDARD.encode_in_place(&mut buffer, input_len);
1988
1989 if let Ok(encoded) = result {
1990 assert!(encoded.len() <= 8);
1991 }
1992 }
1993
1994 #[kani::proof]
1995 #[kani::unwind(70)]
1996 fn standard_clear_tail_decode_clears_buffer_on_error() {
1997 let mut buffer = kani::any::<[u8; 4]>();
1998 let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
1999
2000 if result.is_err() {
2001 assert!(buffer.iter().all(|byte| *byte == 0));
2002 }
2003 }
2004
2005 #[kani::proof]
2006 #[kani::unwind(70)]
2007 fn ct_standard_decode_slice_returns_written_within_output() {
2008 let input = kani::any::<[u8; 4]>();
2009 let mut output = kani::any::<[u8; 3]>();
2010 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
2011
2012 if let Ok(written) = result {
2013 assert!(written <= output.len());
2014 }
2015 }
2016
2017 #[kani::proof]
2018 #[kani::unwind(70)]
2019 fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
2020 let input = kani::any::<[u8; 4]>();
2021 let mut output = kani::any::<[u8; 3]>();
2022 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
2023
2024 if result.is_err() {
2025 assert!(output.iter().all(|byte| *byte == 0));
2026 }
2027 }
2028
2029 #[kani::proof]
2030 #[kani::unwind(70)]
2031 fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
2032 let mut buffer = kani::any::<[u8; 4]>();
2033 let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
2034
2035 if result.is_err() {
2036 assert!(buffer.iter().all(|byte| *byte == 0));
2037 }
2038 }
2039
2040 #[kani::proof]
2041 #[kani::unwind(70)]
2042 fn ct_standard_validate_matches_decode_for_one_quantum() {
2043 let input = kani::any::<[u8; 4]>();
2044 let mut output = kani::any::<[u8; 3]>();
2045
2046 let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
2047 let decode_ok = ct::STANDARD
2048 .decode_slice_clear_tail(&input, &mut output)
2049 .is_ok();
2050
2051 assert!(validate_ok == decode_ok);
2052 }
2053}
2054
2055#[cfg(test)]
2056mod tests {
2057 use super::*;
2058
2059 fn fill_pattern(output: &mut [u8], seed: usize) {
2060 for (index, byte) in output.iter_mut().enumerate() {
2061 let value = (index * 73 + seed * 19) % 256;
2062 *byte = u8::try_from(value).unwrap();
2063 }
2064 }
2065
2066 fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
2067 where
2068 A: Alphabet,
2069 {
2070 let engine = Engine::<A, PAD>::new();
2071 let mut dispatched = [0x55; 256];
2072 let mut scalar = [0xaa; 256];
2073
2074 let dispatched_result = engine.encode_slice(input, &mut dispatched);
2075 let scalar_result = scalar::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
2076
2077 assert_eq!(dispatched_result, scalar_result);
2078 if let Ok(written) = dispatched_result {
2079 assert_eq!(&dispatched[..written], &scalar[..written]);
2080 }
2081
2082 let required = checked_encoded_len(input.len(), PAD).unwrap();
2083 if required > 0 {
2084 let mut dispatched_short = [0x55; 256];
2085 let mut scalar_short = [0xaa; 256];
2086 let available = required - 1;
2087
2088 assert_eq!(
2089 engine.encode_slice(input, &mut dispatched_short[..available]),
2090 scalar::scalar_reference_encode_slice::<A, PAD>(
2091 input,
2092 &mut scalar_short[..available],
2093 )
2094 );
2095 }
2096 }
2097
2098 fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
2099 where
2100 A: Alphabet,
2101 {
2102 let engine = Engine::<A, PAD>::new();
2103 let mut dispatched = [0x55; 128];
2104 let mut scalar = [0xaa; 128];
2105
2106 let dispatched_result = engine.decode_slice(input, &mut dispatched);
2107 let scalar_result = scalar::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
2108
2109 assert_eq!(dispatched_result, scalar_result);
2110 if let Ok(written) = dispatched_result {
2111 assert_eq!(&dispatched[..written], &scalar[..written]);
2112
2113 if written > 0 {
2114 let mut dispatched_short = [0x55; 128];
2115 let mut scalar_short = [0xaa; 128];
2116 let available = written - 1;
2117
2118 assert_eq!(
2119 engine.decode_slice(input, &mut dispatched_short[..available]),
2120 scalar::scalar_reference_decode_slice::<A, PAD>(
2121 input,
2122 &mut scalar_short[..available],
2123 )
2124 );
2125 }
2126 }
2127 }
2128
2129 fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
2130 where
2131 A: Alphabet,
2132 {
2133 assert_encode_backend_matches_scalar::<A, PAD>(input);
2134
2135 let mut encoded = [0; 256];
2136 let encoded_len =
2137 scalar::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
2138 assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
2139 }
2140
2141 fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
2142 let mut encoded = [0u8; 4];
2143 let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
2144 assert_eq!(encoded_len, 4);
2145
2146 let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
2147 let mut decoded = [0u8; 3];
2148 let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
2149
2150 assert_eq!(decoded_len, input.len());
2151 assert_eq!(&decoded[..decoded_len], input);
2152 }
2153
2154 #[test]
2155 fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
2156 let mut input = [0; 128];
2157
2158 for input_len in 0..=input.len() {
2159 fill_pattern(&mut input[..input_len], input_len);
2160 let input = &input[..input_len];
2161
2162 assert_backend_round_trip_matches_scalar::<Standard, true>(input);
2163 assert_backend_round_trip_matches_scalar::<Standard, false>(input);
2164 assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
2165 assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
2166 }
2167 }
2168
2169 #[test]
2170 fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
2171 for input in [
2172 &b"Z"[..],
2173 b"====",
2174 b"AA=A",
2175 b"Zh==",
2176 b"Zm9=",
2177 b"Zm9v$g==",
2178 b"Zm9vZh==",
2179 ] {
2180 assert_decode_backend_matches_scalar::<Standard, true>(input);
2181 }
2182
2183 for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
2184 assert_decode_backend_matches_scalar::<Standard, false>(input);
2185 }
2186
2187 assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
2188 assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
2189 assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
2190 assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
2191 }
2192
2193 #[test]
2194 fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
2195 for byte in u8::MIN..=u8::MAX {
2196 assert_standard_decode_chunk_matches_input(&[byte]);
2197 }
2198
2199 for first in u8::MIN..=u8::MAX {
2200 for second in u8::MIN..=u8::MAX {
2201 assert_standard_decode_chunk_matches_input(&[first, second]);
2202 }
2203 }
2204 }
2205
2206 #[test]
2207 fn decode_chunk_bit_packing_matches_representative_full_quanta() {
2208 const SAMPLES: [u8; 16] = [
2209 0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
2210 ];
2211
2212 for first in SAMPLES {
2213 for second in SAMPLES {
2214 for third in SAMPLES {
2215 assert_standard_decode_chunk_matches_input(&[first, second, third]);
2216 }
2217 }
2218 }
2219 }
2220
2221 #[test]
2222 fn ct_padded_final_quantum_fails_closed_for_invalid_padding_count() {
2223 let (_, invalid_byte, invalid_padding, written) =
2224 ct_padded_final_quantum::<Standard>(*b"ABCD", 3);
2225
2226 assert_ne!(invalid_byte, 0);
2227 assert_ne!(invalid_padding, 0);
2228 assert_eq!(written, 0);
2229 assert_eq!(
2230 report_ct_error(invalid_byte, invalid_padding),
2231 Err(DecodeError::InvalidInput)
2232 );
2233 }
2234
2235 #[cfg(feature = "simd")]
2236 #[test]
2237 fn simd_dispatch_scaffold_keeps_scalar_active() {
2238 assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
2239 let _candidate = simd::detected_candidate();
2240 }
2241
2242 #[test]
2243 fn encodes_standard_vectors() {
2244 let vectors = [
2245 (&b""[..], &b""[..]),
2246 (&b"f"[..], &b"Zg=="[..]),
2247 (&b"fo"[..], &b"Zm8="[..]),
2248 (&b"foo"[..], &b"Zm9v"[..]),
2249 (&b"foob"[..], &b"Zm9vYg=="[..]),
2250 (&b"fooba"[..], &b"Zm9vYmE="[..]),
2251 (&b"foobar"[..], &b"Zm9vYmFy"[..]),
2252 ];
2253 for (input, expected) in vectors {
2254 let mut output = [0u8; 16];
2255 let written = STANDARD.encode_slice(input, &mut output).unwrap();
2256 assert_eq!(&output[..written], expected);
2257 }
2258 }
2259
2260 #[test]
2261 fn decodes_standard_vectors() {
2262 let vectors = [
2263 (&b""[..], &b""[..]),
2264 (&b"Zg=="[..], &b"f"[..]),
2265 (&b"Zm8="[..], &b"fo"[..]),
2266 (&b"Zm9v"[..], &b"foo"[..]),
2267 (&b"Zm9vYg=="[..], &b"foob"[..]),
2268 (&b"Zm9vYmE="[..], &b"fooba"[..]),
2269 (&b"Zm9vYmFy"[..], &b"foobar"[..]),
2270 ];
2271 for (input, expected) in vectors {
2272 let mut output = [0u8; 16];
2273 let written = STANDARD.decode_slice(input, &mut output).unwrap();
2274 assert_eq!(&output[..written], expected);
2275 }
2276 }
2277
2278 #[test]
2279 fn supports_unpadded_url_safe() {
2280 let mut encoded = [0u8; 16];
2281 let written = URL_SAFE_NO_PAD
2282 .encode_slice(b"\xfb\xff", &mut encoded)
2283 .unwrap();
2284 assert_eq!(&encoded[..written], b"-_8");
2285
2286 let mut decoded = [0u8; 2];
2287 let written = URL_SAFE_NO_PAD
2288 .decode_slice(&encoded[..written], &mut decoded)
2289 .unwrap();
2290 assert_eq!(&decoded[..written], b"\xfb\xff");
2291 }
2292
2293 #[test]
2294 fn decodes_in_place() {
2295 let mut buffer = *b"Zm9vYmFy";
2296 let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
2297 assert_eq!(decoded, b"foobar");
2298 }
2299
2300 #[test]
2301 fn rejects_non_canonical_padding_bits() {
2302 let mut output = [0u8; 4];
2303 assert_eq!(
2304 STANDARD.decode_slice(b"Zh==", &mut output),
2305 Err(DecodeError::InvalidPadding { index: 1 })
2306 );
2307 assert_eq!(
2308 STANDARD.decode_slice(b"Zm9=", &mut output),
2309 Err(DecodeError::InvalidPadding { index: 2 })
2310 );
2311 }
2312}