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#[cfg(feature = "alloc")]
78extern crate alloc;
79
80#[cfg(all(target_arch = "wasm32", not(feature = "allow-wasm32-best-effort-wipe")))]
81compile_error!(
82 "base64-ng: wasm32 builds use a compiler-fence-only wipe barrier that cannot \
83 constrain downstream wasm runtime JITs. Enable \
84 `allow-wasm32-best-effort-wipe` to accept this limitation and use \
85 caller-owned, platform-approved zeroization for high-assurance wasm deployments."
86);
87
88#[cfg(all(
89 not(miri),
90 not(feature = "allow-compiler-fence-only-wipe"),
91 not(any(
92 target_arch = "aarch64",
93 target_arch = "arm",
94 target_arch = "riscv32",
95 target_arch = "riscv64",
96 target_arch = "wasm32",
97 target_arch = "x86",
98 target_arch = "x86_64",
99 ))
100))]
101compile_error!(
102 "base64-ng: this architecture has no native hardware wipe barrier in \
103 base64-ng. Enable `allow-compiler-fence-only-wipe` only after reviewing \
104 docs/UNSAFE.md and applying platform-approved memory hygiene controls."
105);
106
107mod alphabet;
108mod buffers;
109mod cleanup;
110mod profiles;
111
112pub use alphabet::{
113 Alphabet, AlphabetError, Bcrypt, Crypt, Standard, UrlSafe, decode_alphabet_byte,
114 validate_alphabet,
115};
116pub(crate) use alphabet::{encode_base64_value, encode_base64_value_runtime};
117pub use buffers::{DecodedBuffer, EncodedBuffer, ExposedDecodedArray, ExposedEncodedArray};
118#[cfg(feature = "alloc")]
119pub use buffers::{ExposedSecretString, ExposedSecretVec, SecretBuffer};
120pub(crate) use cleanup::{wipe_bytes, wipe_tail};
121#[cfg(feature = "alloc")]
122pub(crate) use cleanup::{wipe_vec_all, wipe_vec_spare_capacity};
123pub use profiles::{BCRYPT, CRYPT, MIME, PEM, PEM_CRLF, Profile};
124
125#[cfg(feature = "simd")]
126mod simd;
127
128pub mod runtime;
134
135#[cfg(feature = "stream")]
136pub mod stream;
137
138pub mod ct {
183 use super::{
184 Alphabet, DecodeError, DecodedBuffer, Standard, UrlSafe, ct_decode_in_place,
185 ct_decode_slice, ct_decode_slice_staged_clear_tail, ct_decoded_len, ct_validate_decode,
186 };
187 use core::marker::PhantomData;
188
189 pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
191
192 pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
194
195 pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
197
198 pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
200
201 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
214 pub struct CtEngine<A, const PAD: bool> {
215 alphabet: PhantomData<A>,
216 }
217
218 impl<A, const PAD: bool> CtEngine<A, PAD>
219 where
220 A: Alphabet,
221 {
222 #[must_use]
224 pub const fn new() -> Self {
225 Self {
226 alphabet: PhantomData,
227 }
228 }
229
230 #[must_use]
233 pub const fn is_padded(&self) -> bool {
234 PAD
235 }
236
237 pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
253 ct_validate_decode::<A, PAD>(input)
254 }
255
256 #[must_use]
270 pub fn validate(&self, input: &[u8]) -> bool {
271 self.validate_result(input).is_ok()
272 }
273
274 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
280 ct_decoded_len::<A, PAD>(input)
281 }
282
283 #[must_use = "handle decode errors; use decode_slice_staged_clear_tail for shared-memory or HSM-style threat models"]
317 pub fn decode_slice_clear_tail(
318 &self,
319 input: &[u8],
320 output: &mut [u8],
321 ) -> Result<usize, DecodeError> {
322 let written = match ct_decode_slice::<A, PAD>(input, output) {
323 Ok(written) => written,
324 Err(err) => {
325 crate::wipe_bytes(output);
326 return Err(err);
327 }
328 };
329 crate::wipe_tail(output, written);
330 Ok(written)
331 }
332
333 #[must_use = "handle decode errors; staged decode is for shared-memory or HSM-style threat models"]
347 pub fn decode_slice_staged_clear_tail(
348 &self,
349 input: &[u8],
350 output: &mut [u8],
351 staging: &mut [u8],
352 ) -> Result<usize, DecodeError> {
353 ct_decode_slice_staged_clear_tail::<A, PAD>(input, output, staging)
354 }
355
356 pub fn decode_buffer<const CAP: usize>(
372 &self,
373 input: &[u8],
374 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
375 let mut output = DecodedBuffer::new();
376 let written = match self.decode_slice_clear_tail(input, output.as_mut_capacity()) {
377 Ok(written) => written,
378 Err(err) => {
379 output.clear();
380 return Err(err);
381 }
382 };
383 output.set_filled(written)?;
384 Ok(output)
385 }
386
387 pub fn decode_in_place_clear_tail<'a>(
415 &self,
416 buffer: &'a mut [u8],
417 ) -> Result<&'a mut [u8], DecodeError> {
418 let len = match ct_decode_in_place::<A, PAD>(buffer) {
419 Ok(len) => len,
420 Err(err) => {
421 crate::wipe_bytes(buffer);
422 return Err(err);
423 }
424 };
425 crate::wipe_tail(buffer, len);
426 Ok(&mut buffer[..len])
427 }
428 }
429
430 impl<A, const PAD: bool> core::fmt::Display for CtEngine<A, PAD> {
431 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
432 write!(formatter, "ct padded={PAD}")
433 }
434 }
435}
436
437#[doc(alias = "ct")]
443#[doc(alias = "constant_time")]
444#[doc(alias = "sensitive")]
445pub const STANDARD: Engine<Standard, true> = Engine::new();
446
447#[doc(alias = "ct")]
454#[doc(alias = "constant_time")]
455#[doc(alias = "sensitive")]
456pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
457
458#[doc(alias = "ct")]
464#[doc(alias = "constant_time")]
465#[doc(alias = "sensitive")]
466pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
467
468#[doc(alias = "ct")]
475#[doc(alias = "constant_time")]
476#[doc(alias = "sensitive")]
477pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
478
479#[doc(alias = "ct")]
487#[doc(alias = "constant_time")]
488#[doc(alias = "sensitive")]
489pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
490
491#[doc(alias = "ct")]
499#[doc(alias = "constant_time")]
500#[doc(alias = "sensitive")]
501pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
502
503#[derive(Clone, Copy, Debug, Eq, PartialEq)]
505pub enum LineEnding {
506 Lf,
508 CrLf,
510}
511
512impl LineEnding {
513 #[must_use]
515 pub const fn name(self) -> &'static str {
516 match self {
517 Self::Lf => "LF",
518 Self::CrLf => "CRLF",
519 }
520 }
521
522 #[must_use]
524 pub const fn as_str(self) -> &'static str {
525 match self {
526 Self::Lf => "\n",
527 Self::CrLf => "\r\n",
528 }
529 }
530
531 #[must_use]
533 pub const fn as_bytes(self) -> &'static [u8] {
534 self.as_str().as_bytes()
535 }
536
537 #[must_use]
539 pub const fn byte_len(self) -> usize {
540 match self {
541 Self::Lf => 1,
542 Self::CrLf => 2,
543 }
544 }
545}
546
547impl core::fmt::Display for LineEnding {
548 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
549 formatter.write_str(self.name())
550 }
551}
552
553#[derive(Clone, Copy, Debug, Eq, PartialEq)]
559pub struct LineWrap {
560 pub line_len: usize,
562 pub line_ending: LineEnding,
564}
565
566impl LineWrap {
567 pub const MIME: Self = Self::new(76, LineEnding::CrLf);
569 pub const PEM: Self = Self::new(64, LineEnding::Lf);
571 pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
573
574 #[must_use]
589 pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
590 assert!(line_len != 0, "base64 line wrap length must be non-zero");
591 Self {
592 line_len,
593 line_ending,
594 }
595 }
596
597 #[must_use]
604 pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
605 if line_len == 0 {
606 None
607 } else {
608 Some(Self::new(line_len, line_ending))
609 }
610 }
611
612 #[must_use]
614 pub const fn line_len(self) -> usize {
615 self.line_len
616 }
617
618 #[must_use]
620 pub const fn line_ending(self) -> LineEnding {
621 self.line_ending
622 }
623
624 #[must_use]
626 pub const fn is_valid(self) -> bool {
627 self.line_len != 0
628 }
629}
630
631impl core::fmt::Display for LineWrap {
632 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
633 write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
634 }
635}
636
637pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
652 match checked_encoded_len(input_len, padded) {
653 Some(len) => Ok(len),
654 None => Err(EncodeError::LengthOverflow),
655 }
656}
657
658pub const fn wrapped_encoded_len(
672 input_len: usize,
673 padded: bool,
674 wrap: LineWrap,
675) -> Result<usize, EncodeError> {
676 if wrap.line_len == 0 {
677 return Err(EncodeError::InvalidLineWrap { line_len: 0 });
678 }
679
680 let Some(encoded) = checked_encoded_len(input_len, padded) else {
681 return Err(EncodeError::LengthOverflow);
682 };
683 if encoded == 0 {
684 return Ok(0);
685 }
686
687 let breaks = (encoded - 1) / wrap.line_len;
688 let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
689 return Err(EncodeError::LengthOverflow);
690 };
691 match encoded.checked_add(line_ending_bytes) {
692 Some(len) => Ok(len),
693 None => Err(EncodeError::LengthOverflow),
694 }
695}
696
697#[must_use]
713pub const fn checked_wrapped_encoded_len(
714 input_len: usize,
715 padded: bool,
716 wrap: LineWrap,
717) -> Option<usize> {
718 if wrap.line_len == 0 {
719 return None;
720 }
721
722 let Some(encoded) = checked_encoded_len(input_len, padded) else {
723 return None;
724 };
725 if encoded == 0 {
726 return Some(0);
727 }
728
729 let breaks = (encoded - 1) / wrap.line_len;
730 let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
731 return None;
732 };
733 encoded.checked_add(line_ending_bytes)
734}
735
736#[must_use]
747pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
748 let groups = input_len / 3;
749 if groups > usize::MAX / 4 {
750 return None;
751 }
752 let full = groups * 4;
753 let rem = input_len % 3;
754 if rem == 0 {
755 Some(full)
756 } else if padded {
757 full.checked_add(4)
758 } else {
759 full.checked_add(rem + 1)
760 }
761}
762
763#[must_use]
781pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
782 constant_time_eq_fixed_width_array(left, right)
783}
784
785#[must_use]
796pub const fn decoded_capacity(encoded_len: usize) -> usize {
797 let rem = encoded_len % 4;
798 encoded_len / 4 * 3
799 + if rem == 2 {
800 1
801 } else if rem == 3 {
802 2
803 } else {
804 0
805 }
806}
807
808pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
822 if padded {
823 decoded_len_padded(input)
824 } else {
825 decoded_len_unpadded(input)
826 }
827}
828
829#[inline]
830pub(crate) const fn ct_mask_bit(bit: u8) -> u8 {
831 0u8.wrapping_sub(bit & 1)
832}
833
834#[inline]
835pub(crate) const fn ct_mask_nonzero_u8(value: u8) -> u8 {
836 let wide = value as u16;
837 let negative = 0u16.wrapping_sub(wide);
838 let nonzero = ((wide | negative) >> 8) as u8;
839 ct_mask_bit(nonzero)
840}
841
842#[inline]
843pub(crate) const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
844 !ct_mask_nonzero_u8(left ^ right)
845}
846
847#[inline]
848pub(crate) const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
849 let diff = (left as u16).wrapping_sub(right as u16);
850 ct_mask_bit((diff >> 8) as u8)
851}
852
853#[inline(never)]
854fn constant_time_eq_public_len(left: &[u8], right: &[u8]) -> bool {
855 if left.len() != right.len() {
856 return false;
857 }
858
859 constant_time_eq_same_len(left, right)
860}
861
862#[inline(never)]
863fn constant_time_eq_fixed_width_array<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
864 constant_time_eq_same_len(left, right)
865}
866
867#[inline(never)]
868#[allow(unsafe_code)]
869fn constant_time_eq_same_len(left: &[u8], right: &[u8]) -> bool {
870 let mut diff = 0u8;
871 for (left, right) in left.iter().zip(right) {
872 diff = core::hint::black_box(
873 core::hint::black_box(diff) | core::hint::black_box(*left ^ *right),
874 );
875 diff = unsafe { core::ptr::read_volatile(&raw const diff) };
879 }
880 ct_error_gate_barrier(diff, 0);
881 let result = unsafe { core::ptr::read_volatile(&raw const diff) };
885 result == 0
886}
887
888mod backend {
889 use super::{
890 Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
891 encode_base64_value_runtime,
892 };
893
894 pub(super) fn encode_slice<A, const PAD: bool>(
895 input: &[u8],
896 output: &mut [u8],
897 ) -> Result<usize, EncodeError>
898 where
899 A: Alphabet,
900 {
901 #[cfg(feature = "simd")]
902 match super::simd::active_backend() {
903 super::simd::ActiveBackend::Scalar => {}
904 }
905
906 scalar_encode_slice::<A, PAD>(input, output)
907 }
908
909 pub(super) fn decode_slice<A, const PAD: bool>(
910 input: &[u8],
911 output: &mut [u8],
912 ) -> Result<usize, DecodeError>
913 where
914 A: Alphabet,
915 {
916 #[cfg(feature = "simd")]
917 match super::simd::active_backend() {
918 super::simd::ActiveBackend::Scalar => {}
919 }
920
921 scalar_decode_slice::<A, PAD>(input, output)
922 }
923
924 #[cfg(test)]
925 pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
926 input: &[u8],
927 output: &mut [u8],
928 ) -> Result<usize, EncodeError>
929 where
930 A: Alphabet,
931 {
932 scalar_encode_slice::<A, PAD>(input, output)
933 }
934
935 #[cfg(test)]
936 pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
937 input: &[u8],
938 output: &mut [u8],
939 ) -> Result<usize, DecodeError>
940 where
941 A: Alphabet,
942 {
943 scalar_decode_slice::<A, PAD>(input, output)
944 }
945
946 fn scalar_encode_slice<A, const PAD: bool>(
947 input: &[u8],
948 output: &mut [u8],
949 ) -> Result<usize, EncodeError>
950 where
951 A: Alphabet,
952 {
953 let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
954 if output.len() < required {
955 return Err(EncodeError::OutputTooSmall {
956 required,
957 available: output.len(),
958 });
959 }
960
961 let mut read = 0;
962 let mut write = 0;
963 while read + 3 <= input.len() {
964 let b0 = input[read];
965 let b1 = input[read + 1];
966 let b2 = input[read + 2];
967
968 output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
969 output[write + 1] =
970 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
971 output[write + 2] =
972 encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
973 output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
974
975 read += 3;
976 write += 4;
977 }
978
979 match input.len() - read {
980 0 => {}
981 1 => {
982 let b0 = input[read];
983 output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
984 output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
985 write += 2;
986 if PAD {
987 output[write] = b'=';
988 output[write + 1] = b'=';
989 write += 2;
990 }
991 }
992 2 => {
993 let b0 = input[read];
994 let b1 = input[read + 1];
995 output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
996 output[write + 1] =
997 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
998 output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
999 write += 3;
1000 if PAD {
1001 output[write] = b'=';
1002 write += 1;
1003 }
1004 }
1005 _ => unreachable!(),
1006 }
1007
1008 Ok(write)
1009 }
1010
1011 fn scalar_decode_slice<A, const PAD: bool>(
1012 input: &[u8],
1013 output: &mut [u8],
1014 ) -> Result<usize, DecodeError>
1015 where
1016 A: Alphabet,
1017 {
1018 if input.is_empty() {
1019 return Ok(0);
1020 }
1021
1022 if PAD {
1023 decode_padded::<A>(input, output)
1024 } else {
1025 decode_unpadded::<A>(input, output)
1026 }
1027 }
1028}
1029
1030pub struct Engine<A, const PAD: bool> {
1032 alphabet: core::marker::PhantomData<A>,
1033}
1034
1035impl<A, const PAD: bool> Clone for Engine<A, PAD> {
1036 fn clone(&self) -> Self {
1037 *self
1038 }
1039}
1040
1041impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
1042
1043impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
1044 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1045 formatter
1046 .debug_struct("Engine")
1047 .field("padded", &PAD)
1048 .finish()
1049 }
1050}
1051
1052impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
1053 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1054 write!(formatter, "padded={PAD}")
1055 }
1056}
1057
1058impl<A, const PAD: bool> Default for Engine<A, PAD> {
1059 fn default() -> Self {
1060 Self {
1061 alphabet: core::marker::PhantomData,
1062 }
1063 }
1064}
1065
1066impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
1067
1068impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
1069 fn eq(&self, _other: &Self) -> bool {
1070 true
1071 }
1072}
1073
1074impl<A, const PAD: bool> Engine<A, PAD>
1075where
1076 A: Alphabet,
1077{
1078 #[must_use]
1080 pub const fn new() -> Self {
1081 Self {
1082 alphabet: core::marker::PhantomData,
1083 }
1084 }
1085
1086 #[must_use]
1088 pub const fn is_padded(&self) -> bool {
1089 PAD
1090 }
1091
1092 #[must_use]
1097 pub const fn profile(&self) -> Profile<A, PAD> {
1098 Profile::new(*self, None)
1099 }
1100
1101 #[must_use]
1107 pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
1108 ct::CtEngine::new()
1109 }
1110
1111 #[cfg(feature = "stream")]
1125 #[must_use]
1126 pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
1127 stream::Encoder::new(inner, *self)
1128 }
1129
1130 #[cfg(feature = "stream")]
1150 #[must_use]
1151 pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
1152 stream::Decoder::new(inner, *self)
1153 }
1154
1155 #[cfg(feature = "stream")]
1170 #[must_use]
1171 pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
1172 stream::EncoderReader::new(inner, *self)
1173 }
1174
1175 #[cfg(feature = "stream")]
1196 #[must_use]
1197 pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
1198 stream::DecoderReader::new(inner, *self)
1199 }
1200
1201 pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
1203 encoded_len(input_len, PAD)
1204 }
1205
1206 #[must_use]
1208 pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
1209 checked_encoded_len(input_len, PAD)
1210 }
1211
1212 pub const fn wrapped_encoded_len(
1217 &self,
1218 input_len: usize,
1219 wrap: LineWrap,
1220 ) -> Result<usize, EncodeError> {
1221 wrapped_encoded_len(input_len, PAD, wrap)
1222 }
1223
1224 #[must_use]
1227 pub const fn checked_wrapped_encoded_len(
1228 &self,
1229 input_len: usize,
1230 wrap: LineWrap,
1231 ) -> Option<usize> {
1232 checked_wrapped_encoded_len(input_len, PAD, wrap)
1233 }
1234
1235 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
1240 decoded_len(input, PAD)
1241 }
1242
1243 pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
1249 validate_legacy_decode::<A, PAD>(input)
1250 }
1251
1252 pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
1259 validate_wrapped_decode::<A, PAD>(input, wrap)
1260 }
1261
1262 pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
1280 validate_decode::<A, PAD>(input).map(|_| ())
1281 }
1282
1283 #[must_use]
1299 pub fn validate(&self, input: &[u8]) -> bool {
1300 self.validate_result(input).is_ok()
1301 }
1302
1303 pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
1318 validate_legacy_decode::<A, PAD>(input).map(|_| ())
1319 }
1320
1321 #[must_use]
1335 pub fn validate_legacy(&self, input: &[u8]) -> bool {
1336 self.validate_legacy_result(input).is_ok()
1337 }
1338
1339 pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
1355 validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
1356 }
1357
1358 #[must_use]
1372 pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
1373 self.validate_wrapped_result(input, wrap).is_ok()
1374 }
1375
1376 #[must_use]
1408 pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
1409 &self,
1410 input: &[u8; INPUT_LEN],
1411 ) -> [u8; OUTPUT_LEN] {
1412 let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
1413 panic!("encoded base64 length overflows usize");
1414 };
1415 assert!(
1416 required == OUTPUT_LEN,
1417 "base64 output array has incorrect length"
1418 );
1419
1420 let mut output = [0u8; OUTPUT_LEN];
1421 let mut read = 0;
1422 let mut write = 0;
1423 while INPUT_LEN - read >= 3 {
1424 let b0 = input[read];
1425 let b1 = input[read + 1];
1426 let b2 = input[read + 2];
1427
1428 output[write] = encode_base64_value::<A>(b0 >> 2);
1429 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1430 output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
1431 output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
1432
1433 read += 3;
1434 write += 4;
1435 }
1436
1437 match INPUT_LEN - read {
1438 0 => {}
1439 1 => {
1440 let b0 = input[read];
1441 output[write] = encode_base64_value::<A>(b0 >> 2);
1442 output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
1443 write += 2;
1444 if PAD {
1445 output[write] = b'=';
1446 output[write + 1] = b'=';
1447 }
1448 }
1449 2 => {
1450 let b0 = input[read];
1451 let b1 = input[read + 1];
1452 output[write] = encode_base64_value::<A>(b0 >> 2);
1453 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1454 output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
1455 if PAD {
1456 output[write + 3] = b'=';
1457 }
1458 }
1459 _ => unreachable!(),
1460 }
1461
1462 output
1463 }
1464
1465 pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
1467 backend::encode_slice::<A, PAD>(input, output)
1468 }
1469
1470 pub fn encode_slice_wrapped(
1489 &self,
1490 input: &[u8],
1491 output: &mut [u8],
1492 wrap: LineWrap,
1493 ) -> Result<usize, EncodeError> {
1494 let required = self.wrapped_encoded_len(input.len(), wrap)?;
1495 if output.len() < required {
1496 return Err(EncodeError::OutputTooSmall {
1497 required,
1498 available: output.len(),
1499 });
1500 }
1501
1502 let encoded_len =
1503 checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
1504 if encoded_len == 0 {
1505 return Ok(0);
1506 }
1507
1508 let combined_required = match required.checked_add(encoded_len) {
1511 Some(len) => len,
1512 None => usize::MAX,
1513 };
1514 if output.len() < combined_required {
1515 let mut scratch = [0u8; 1024];
1516 let mut input_offset = 0;
1517 let mut output_offset = 0;
1518 let mut column = 0;
1519
1520 while input_offset < input.len() {
1521 let remaining = input.len() - input_offset;
1522 let mut take = remaining.min(768);
1523 if remaining > take {
1524 take -= take % 3;
1525 }
1526 if take == 0 {
1527 take = remaining;
1528 }
1529
1530 let encoded = match self
1531 .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
1532 {
1533 Ok(encoded) => encoded,
1534 Err(err) => {
1535 wipe_bytes(&mut scratch);
1536 return Err(err);
1537 }
1538 };
1539 if let Err(err) = write_wrapped_bytes(
1540 &scratch[..encoded],
1541 output,
1542 &mut output_offset,
1543 &mut column,
1544 wrap,
1545 ) {
1546 wipe_bytes(&mut scratch);
1547 return Err(err);
1548 }
1549 wipe_bytes(&mut scratch[..encoded]);
1550 input_offset += take;
1551 }
1552
1553 Ok(output_offset)
1554 } else {
1555 let encoded =
1556 self.encode_slice(input, &mut output[required..required + encoded_len])?;
1557 let mut output_offset = 0;
1558 let mut column = 0;
1559 let mut read = required;
1560 while read < required + encoded {
1561 let byte = output[read];
1562 write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
1563 read += 1;
1564 }
1565 wipe_bytes(&mut output[required..required + encoded]);
1566 Ok(output_offset)
1567 }
1568 }
1569
1570 pub fn encode_slice_wrapped_clear_tail(
1576 &self,
1577 input: &[u8],
1578 output: &mut [u8],
1579 wrap: LineWrap,
1580 ) -> Result<usize, EncodeError> {
1581 let written = match self.encode_slice_wrapped(input, output, wrap) {
1582 Ok(written) => written,
1583 Err(err) => {
1584 wipe_bytes(output);
1585 return Err(err);
1586 }
1587 };
1588 wipe_tail(output, written);
1589 Ok(written)
1590 }
1591
1592 pub fn encode_wrapped_buffer<const CAP: usize>(
1598 &self,
1599 input: &[u8],
1600 wrap: LineWrap,
1601 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
1602 let mut output = EncodedBuffer::new();
1603 let written =
1604 match self.encode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
1605 Ok(written) => written,
1606 Err(err) => {
1607 output.clear();
1608 return Err(err);
1609 }
1610 };
1611 output.set_filled(written)?;
1612 Ok(output)
1613 }
1614
1615 #[cfg(feature = "alloc")]
1617 #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
1618 pub fn encode_wrapped_vec(
1619 &self,
1620 input: &[u8],
1621 wrap: LineWrap,
1622 ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
1623 let required = self.wrapped_encoded_len(input.len(), wrap)?;
1624 let mut output = alloc::vec![0; required];
1625 let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
1626 output.truncate(written);
1627 Ok(output)
1628 }
1629
1630 #[cfg(feature = "alloc")]
1632 pub fn encode_wrapped_string(
1633 &self,
1634 input: &[u8],
1635 wrap: LineWrap,
1636 ) -> Result<alloc::string::String, EncodeError> {
1637 let output = self.encode_wrapped_vec(input, wrap)?;
1638 match alloc::string::String::from_utf8(output) {
1639 Ok(output) => Ok(output),
1640 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1641 }
1642 }
1643
1644 #[cfg(feature = "alloc")]
1649 pub fn encode_wrapped_secret(
1650 &self,
1651 input: &[u8],
1652 wrap: LineWrap,
1653 ) -> Result<SecretBuffer, EncodeError> {
1654 self.encode_wrapped_vec(input, wrap)
1655 .map(SecretBuffer::from_vec)
1656 }
1657
1658 pub fn encode_slice_clear_tail(
1678 &self,
1679 input: &[u8],
1680 output: &mut [u8],
1681 ) -> Result<usize, EncodeError> {
1682 let written = match self.encode_slice(input, output) {
1683 Ok(written) => written,
1684 Err(err) => {
1685 wipe_bytes(output);
1686 return Err(err);
1687 }
1688 };
1689 wipe_tail(output, written);
1690 Ok(written)
1691 }
1692
1693 pub fn encode_buffer<const CAP: usize>(
1708 &self,
1709 input: &[u8],
1710 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
1711 let mut output = EncodedBuffer::new();
1712 let written = match self.encode_slice_clear_tail(input, output.as_mut_capacity()) {
1713 Ok(written) => written,
1714 Err(err) => {
1715 output.clear();
1716 return Err(err);
1717 }
1718 };
1719 output.set_filled(written)?;
1720 Ok(output)
1721 }
1722
1723 #[cfg(feature = "alloc")]
1725 #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
1726 pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
1727 let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
1728 let mut output = alloc::vec![0; required];
1729 let written = self.encode_slice(input, &mut output)?;
1730 output.truncate(written);
1731 Ok(output)
1732 }
1733
1734 #[cfg(feature = "alloc")]
1739 pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
1740 self.encode_vec(input).map(SecretBuffer::from_vec)
1741 }
1742
1743 #[cfg(feature = "alloc")]
1758 pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
1759 let output = self.encode_vec(input)?;
1760 match alloc::string::String::from_utf8(output) {
1761 Ok(output) => Ok(output),
1762 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1763 }
1764 }
1765
1766 pub fn encode_in_place<'a>(
1783 &self,
1784 buffer: &'a mut [u8],
1785 input_len: usize,
1786 ) -> Result<&'a mut [u8], EncodeError> {
1787 if input_len > buffer.len() {
1788 return Err(EncodeError::InputTooLarge {
1789 input_len,
1790 buffer_len: buffer.len(),
1791 });
1792 }
1793
1794 let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
1795 if buffer.len() < required {
1796 return Err(EncodeError::OutputTooSmall {
1797 required,
1798 available: buffer.len(),
1799 });
1800 }
1801
1802 let mut read = input_len;
1803 let mut write = required;
1804
1805 match input_len % 3 {
1806 0 => {}
1807 1 => {
1808 read -= 1;
1809 let b0 = buffer[read];
1810 if PAD {
1811 write -= 4;
1812 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1813 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
1814 buffer[write + 2] = b'=';
1815 buffer[write + 3] = b'=';
1816 } else {
1817 write -= 2;
1818 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1819 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
1820 }
1821 }
1822 2 => {
1823 read -= 2;
1824 let b0 = buffer[read];
1825 let b1 = buffer[read + 1];
1826 if PAD {
1827 write -= 4;
1828 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1829 buffer[write + 1] =
1830 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1831 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
1832 buffer[write + 3] = b'=';
1833 } else {
1834 write -= 3;
1835 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1836 buffer[write + 1] =
1837 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1838 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
1839 }
1840 }
1841 _ => unreachable!(),
1842 }
1843
1844 while read > 0 {
1845 read -= 3;
1846 write -= 4;
1847 let b0 = buffer[read];
1848 let b1 = buffer[read + 1];
1849 let b2 = buffer[read + 2];
1850
1851 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
1852 buffer[write + 1] =
1853 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
1854 buffer[write + 2] =
1855 encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
1856 buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
1857 }
1858
1859 debug_assert_eq!(write, 0);
1863 Ok(&mut buffer[..required])
1864 }
1865
1866 pub fn encode_in_place_clear_tail<'a>(
1884 &self,
1885 buffer: &'a mut [u8],
1886 input_len: usize,
1887 ) -> Result<&'a mut [u8], EncodeError> {
1888 let len = match self.encode_in_place(buffer, input_len) {
1889 Ok(encoded) => encoded.len(),
1890 Err(err) => {
1891 wipe_bytes(buffer);
1892 return Err(err);
1893 }
1894 };
1895 wipe_tail(buffer, len);
1896 Ok(&mut buffer[..len])
1897 }
1898
1899 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1917 pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
1918 backend::decode_slice::<A, PAD>(input, output)
1919 }
1920
1921 pub fn decode_slice_clear_tail(
1941 &self,
1942 input: &[u8],
1943 output: &mut [u8],
1944 ) -> Result<usize, DecodeError> {
1945 let written = match self.decode_slice(input, output) {
1946 Ok(written) => written,
1947 Err(err) => {
1948 wipe_bytes(output);
1949 return Err(err);
1950 }
1951 };
1952 wipe_tail(output, written);
1953 Ok(written)
1954 }
1955
1956 pub fn decode_buffer<const CAP: usize>(
1971 &self,
1972 input: &[u8],
1973 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
1974 let mut output = DecodedBuffer::new();
1975 let written = match self.decode_slice_clear_tail(input, output.as_mut_capacity()) {
1976 Ok(written) => written,
1977 Err(err) => {
1978 output.clear();
1979 return Err(err);
1980 }
1981 };
1982 output.set_filled(written)?;
1983 Ok(output)
1984 }
1985
1986 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
1999 pub fn decode_slice_legacy(
2000 &self,
2001 input: &[u8],
2002 output: &mut [u8],
2003 ) -> Result<usize, DecodeError> {
2004 let required = validate_legacy_decode::<A, PAD>(input)?;
2005 if output.len() < required {
2006 return Err(DecodeError::OutputTooSmall {
2007 required,
2008 available: output.len(),
2009 });
2010 }
2011 decode_legacy_to_slice::<A, PAD>(input, output)
2012 }
2013
2014 pub fn decode_slice_legacy_clear_tail(
2034 &self,
2035 input: &[u8],
2036 output: &mut [u8],
2037 ) -> Result<usize, DecodeError> {
2038 let written = match self.decode_slice_legacy(input, output) {
2039 Ok(written) => written,
2040 Err(err) => {
2041 wipe_bytes(output);
2042 return Err(err);
2043 }
2044 };
2045 wipe_tail(output, written);
2046 Ok(written)
2047 }
2048
2049 pub fn decode_buffer_legacy<const CAP: usize>(
2057 &self,
2058 input: &[u8],
2059 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
2060 let mut output = DecodedBuffer::new();
2061 let written = match self.decode_slice_legacy_clear_tail(input, output.as_mut_capacity()) {
2062 Ok(written) => written,
2063 Err(err) => {
2064 output.clear();
2065 return Err(err);
2066 }
2067 };
2068 output.set_filled(written)?;
2069 Ok(output)
2070 }
2071
2072 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
2086 pub fn decode_slice_wrapped(
2087 &self,
2088 input: &[u8],
2089 output: &mut [u8],
2090 wrap: LineWrap,
2091 ) -> Result<usize, DecodeError> {
2092 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
2093 if output.len() < required {
2094 return Err(DecodeError::OutputTooSmall {
2095 required,
2096 available: output.len(),
2097 });
2098 }
2099 decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
2100 }
2101
2102 pub fn decode_slice_wrapped_clear_tail(
2108 &self,
2109 input: &[u8],
2110 output: &mut [u8],
2111 wrap: LineWrap,
2112 ) -> Result<usize, DecodeError> {
2113 let written = match self.decode_slice_wrapped(input, output, wrap) {
2114 Ok(written) => written,
2115 Err(err) => {
2116 wipe_bytes(output);
2117 return Err(err);
2118 }
2119 };
2120 wipe_tail(output, written);
2121 Ok(written)
2122 }
2123
2124 pub fn decode_wrapped_buffer<const CAP: usize>(
2133 &self,
2134 input: &[u8],
2135 wrap: LineWrap,
2136 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
2137 let mut output = DecodedBuffer::new();
2138 let written =
2139 match self.decode_slice_wrapped_clear_tail(input, output.as_mut_capacity(), wrap) {
2140 Ok(written) => written,
2141 Err(err) => {
2142 output.clear();
2143 return Err(err);
2144 }
2145 };
2146 output.set_filled(written)?;
2147 Ok(output)
2148 }
2149
2150 #[cfg(feature = "alloc")]
2154 #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
2155 pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2156 let required = validate_decode::<A, PAD>(input)?;
2157 let mut output = alloc::vec![0; required];
2158 let written = match self.decode_slice(input, &mut output) {
2159 Ok(written) => written,
2160 Err(err) => {
2161 wipe_bytes(&mut output);
2162 return Err(err);
2163 }
2164 };
2165 output.truncate(written);
2166 Ok(output)
2167 }
2168
2169 #[cfg(feature = "alloc")]
2174 pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
2175 self.decode_vec(input).map(SecretBuffer::from_vec)
2176 }
2177
2178 #[cfg(feature = "alloc")]
2181 #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
2182 pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2183 let required = validate_legacy_decode::<A, PAD>(input)?;
2184 let mut output = alloc::vec![0; required];
2185 let written = match self.decode_slice_legacy(input, &mut output) {
2186 Ok(written) => written,
2187 Err(err) => {
2188 wipe_bytes(&mut output);
2189 return Err(err);
2190 }
2191 };
2192 output.truncate(written);
2193 Ok(output)
2194 }
2195
2196 #[cfg(feature = "alloc")]
2203 pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
2204 self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
2205 }
2206
2207 #[cfg(feature = "alloc")]
2209 #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
2210 pub fn decode_wrapped_vec(
2211 &self,
2212 input: &[u8],
2213 wrap: LineWrap,
2214 ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2215 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
2216 let mut output = alloc::vec![0; required];
2217 let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
2218 Ok(written) => written,
2219 Err(err) => {
2220 wipe_bytes(&mut output);
2221 return Err(err);
2222 }
2223 };
2224 output.truncate(written);
2225 Ok(output)
2226 }
2227
2228 #[cfg(feature = "alloc")]
2235 pub fn decode_wrapped_secret(
2236 &self,
2237 input: &[u8],
2238 wrap: LineWrap,
2239 ) -> Result<SecretBuffer, DecodeError> {
2240 self.decode_wrapped_vec(input, wrap)
2241 .map(SecretBuffer::from_vec)
2242 }
2243
2244 pub fn decode_in_place_wrapped<'a>(
2275 &self,
2276 buffer: &'a mut [u8],
2277 wrap: LineWrap,
2278 ) -> Result<&'a mut [u8], DecodeError> {
2279 let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
2280 let compacted = compact_wrapped_input(buffer, wrap)?;
2281 let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
2282 Ok(&mut buffer[..len])
2283 }
2284
2285 pub fn decode_in_place_wrapped_clear_tail<'a>(
2306 &self,
2307 buffer: &'a mut [u8],
2308 wrap: LineWrap,
2309 ) -> Result<&'a mut [u8], DecodeError> {
2310 if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
2311 wipe_bytes(buffer);
2312 return Err(err);
2313 }
2314
2315 let compacted = match compact_wrapped_input(buffer, wrap) {
2316 Ok(compacted) => compacted,
2317 Err(err) => {
2318 wipe_bytes(buffer);
2319 return Err(err);
2320 }
2321 };
2322
2323 let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
2324 Ok(len) => len,
2325 Err(err) => {
2326 wipe_bytes(buffer);
2327 return Err(err);
2328 }
2329 };
2330 wipe_tail(buffer, len);
2331 Ok(&mut buffer[..len])
2332 }
2333
2334 pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
2359 let len = Self::decode_slice_to_start(buffer)?;
2360 Ok(&mut buffer[..len])
2361 }
2362
2363 pub fn decode_in_place_clear_tail<'a>(
2380 &self,
2381 buffer: &'a mut [u8],
2382 ) -> Result<&'a mut [u8], DecodeError> {
2383 let len = match Self::decode_slice_to_start(buffer) {
2384 Ok(len) => len,
2385 Err(err) => {
2386 wipe_bytes(buffer);
2387 return Err(err);
2388 }
2389 };
2390 wipe_tail(buffer, len);
2391 Ok(&mut buffer[..len])
2392 }
2393
2394 pub fn decode_in_place_legacy<'a>(
2402 &self,
2403 buffer: &'a mut [u8],
2404 ) -> Result<&'a mut [u8], DecodeError> {
2405 let _required = validate_legacy_decode::<A, PAD>(buffer)?;
2406 let mut write = 0;
2407 let mut read = 0;
2408 while read < buffer.len() {
2409 let byte = buffer[read];
2410 if !is_legacy_whitespace(byte) {
2411 buffer[write] = byte;
2412 write += 1;
2413 }
2414 read += 1;
2415 }
2416 let len = Self::decode_slice_to_start(&mut buffer[..write])?;
2417 Ok(&mut buffer[..len])
2418 }
2419
2420 pub fn decode_in_place_legacy_clear_tail<'a>(
2426 &self,
2427 buffer: &'a mut [u8],
2428 ) -> Result<&'a mut [u8], DecodeError> {
2429 if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
2430 wipe_bytes(buffer);
2431 return Err(err);
2432 }
2433
2434 let mut write = 0;
2435 let mut read = 0;
2436 while read < buffer.len() {
2437 let byte = buffer[read];
2438 if !is_legacy_whitespace(byte) {
2439 buffer[write] = byte;
2440 write += 1;
2441 }
2442 read += 1;
2443 }
2444
2445 let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
2446 Ok(len) => len,
2447 Err(err) => {
2448 wipe_bytes(buffer);
2449 return Err(err);
2450 }
2451 };
2452 wipe_tail(buffer, len);
2453 Ok(&mut buffer[..len])
2454 }
2455
2456 fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
2457 let _required = validate_decode::<A, PAD>(buffer)?;
2458 let input_len = buffer.len();
2459 let mut read = 0;
2460 let mut write = 0;
2461 while read + 4 <= input_len {
2462 let chunk = read_quad(buffer, read)?;
2463 let available = buffer.len();
2464 let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
2465 required: write,
2466 available,
2467 })?;
2468 let written = decode_chunk::<A, PAD>(chunk, output_tail)
2469 .map_err(|err| err.with_index_offset(read))?;
2470 read += 4;
2471 write += written;
2472 if written < 3 {
2473 if read != input_len {
2474 return Err(DecodeError::InvalidPadding { index: read - 4 });
2475 }
2476 return Ok(write);
2477 }
2478 }
2479
2480 let rem = input_len - read;
2481 if rem == 0 {
2482 return Ok(write);
2483 }
2484 if PAD {
2485 return Err(DecodeError::InvalidLength);
2486 }
2487 let mut tail = [0u8; 3];
2488 tail[..rem].copy_from_slice(&buffer[read..input_len]);
2489 decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
2490 .map_err(|err| err.with_index_offset(read))
2491 .map(|n| write + n)
2492 }
2493}
2494
2495fn write_wrapped_bytes(
2496 input: &[u8],
2497 output: &mut [u8],
2498 output_offset: &mut usize,
2499 column: &mut usize,
2500 wrap: LineWrap,
2501) -> Result<(), EncodeError> {
2502 for byte in input {
2503 write_wrapped_byte(*byte, output, output_offset, column, wrap)?;
2504 }
2505 Ok(())
2506}
2507
2508fn write_wrapped_byte(
2509 byte: u8,
2510 output: &mut [u8],
2511 output_offset: &mut usize,
2512 column: &mut usize,
2513 wrap: LineWrap,
2514) -> Result<(), EncodeError> {
2515 if *column == wrap.line_len {
2516 let line_ending = wrap.line_ending.as_bytes();
2517 let mut index = 0;
2518 while index < line_ending.len() {
2519 if *output_offset >= output.len() {
2520 return Err(EncodeError::OutputTooSmall {
2521 required: *output_offset + 1,
2522 available: output.len(),
2523 });
2524 }
2525 output[*output_offset] = line_ending[index];
2526 *output_offset += 1;
2527 index += 1;
2528 }
2529 *column = 0;
2530 }
2531
2532 if *output_offset >= output.len() {
2533 return Err(EncodeError::OutputTooSmall {
2534 required: *output_offset + 1,
2535 available: output.len(),
2536 });
2537 }
2538 output[*output_offset] = byte;
2539 *output_offset += 1;
2540 *column += 1;
2541 Ok(())
2542}
2543
2544#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2546pub enum EncodeError {
2547 LengthOverflow,
2549 InvalidLineWrap {
2551 line_len: usize,
2553 },
2554 InputTooLarge {
2556 input_len: usize,
2558 buffer_len: usize,
2560 },
2561 OutputTooSmall {
2563 required: usize,
2565 available: usize,
2567 },
2568}
2569
2570impl core::fmt::Display for EncodeError {
2571 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2572 match self {
2573 Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
2574 Self::InvalidLineWrap { line_len } => {
2575 write!(f, "base64 line wrap length {line_len} is invalid")
2576 }
2577 Self::InputTooLarge {
2578 input_len,
2579 buffer_len,
2580 } => write!(
2581 f,
2582 "base64 input length {input_len} exceeds buffer length {buffer_len}"
2583 ),
2584 Self::OutputTooSmall {
2585 required,
2586 available,
2587 } => write!(
2588 f,
2589 "base64 output buffer too small: required {required}, available {available}"
2590 ),
2591 }
2592 }
2593}
2594
2595#[cfg(feature = "std")]
2596impl std::error::Error for EncodeError {}
2597
2598#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2600pub enum DecodeError {
2601 InvalidInput,
2604 InvalidLength,
2606 InvalidByte {
2608 index: usize,
2610 byte: u8,
2612 },
2613 InvalidPadding {
2615 index: usize,
2617 },
2618 InvalidLineWrap {
2620 index: usize,
2622 },
2623 OutputTooSmall {
2625 required: usize,
2627 available: usize,
2629 },
2630 StagingTooSmall {
2632 required: usize,
2634 available: usize,
2636 },
2637}
2638
2639impl core::fmt::Display for DecodeError {
2640 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2641 match self {
2642 Self::InvalidInput => f.write_str("malformed base64 input"),
2643 Self::InvalidLength => f.write_str("invalid base64 input length"),
2644 Self::InvalidByte { index, byte } => {
2645 write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
2646 }
2647 Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
2648 Self::InvalidLineWrap { index } => {
2649 write!(f, "invalid base64 line wrapping at index {index}")
2650 }
2651 Self::OutputTooSmall {
2652 required,
2653 available,
2654 } => write!(
2655 f,
2656 "base64 decode output buffer too small: required {required}, available {available}"
2657 ),
2658 Self::StagingTooSmall {
2659 required,
2660 available,
2661 } => write!(
2662 f,
2663 "base64 decode staging buffer too small: required {required}, available {available}"
2664 ),
2665 }
2666 }
2667}
2668
2669impl DecodeError {
2670 fn with_index_offset(self, offset: usize) -> Self {
2671 match self {
2672 Self::InvalidByte { index, byte } => Self::InvalidByte {
2673 index: index + offset,
2674 byte,
2675 },
2676 Self::InvalidPadding { index } => Self::InvalidPadding {
2677 index: index + offset,
2678 },
2679 Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
2680 index: index + offset,
2681 },
2682 Self::InvalidInput
2683 | Self::InvalidLength
2684 | Self::OutputTooSmall { .. }
2685 | Self::StagingTooSmall { .. } => self,
2686 }
2687 }
2688}
2689
2690#[cfg(feature = "std")]
2691impl std::error::Error for DecodeError {}
2692
2693struct LegacyBytes<'a> {
2694 input: &'a [u8],
2695 index: usize,
2696}
2697
2698impl<'a> LegacyBytes<'a> {
2699 const fn new(input: &'a [u8]) -> Self {
2700 Self { input, index: 0 }
2701 }
2702
2703 fn next_byte(&mut self) -> Option<(usize, u8)> {
2704 while self.index < self.input.len() {
2705 let index = self.index;
2706 let byte = self.input[index];
2707 self.index += 1;
2708 if !is_legacy_whitespace(byte) {
2709 return Some((index, byte));
2710 }
2711 }
2712 None
2713 }
2714}
2715
2716fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
2717 input: &[u8],
2718) -> Result<usize, DecodeError> {
2719 let mut bytes = LegacyBytes::new(input);
2720 let mut chunk = [0u8; 4];
2721 let mut indexes = [0usize; 4];
2722 let mut chunk_len = 0;
2723 let mut required = 0;
2724 let mut terminal_seen = false;
2725
2726 while let Some((index, byte)) = bytes.next_byte() {
2727 if terminal_seen {
2728 return Err(DecodeError::InvalidPadding { index });
2729 }
2730
2731 chunk[chunk_len] = byte;
2732 indexes[chunk_len] = index;
2733 chunk_len += 1;
2734
2735 if chunk_len == 4 {
2736 let written =
2737 validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
2738 required += written;
2739 terminal_seen = written < 3;
2740 chunk_len = 0;
2741 }
2742 }
2743
2744 if chunk_len == 0 {
2745 return Ok(required);
2746 }
2747 if PAD {
2748 return Err(DecodeError::InvalidLength);
2749 }
2750
2751 validate_tail_unpadded::<A>(&chunk[..chunk_len])
2752 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
2753 Ok(required + decoded_capacity(chunk_len))
2754}
2755
2756fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
2757 input: &[u8],
2758 output: &mut [u8],
2759) -> Result<usize, DecodeError> {
2760 let mut bytes = LegacyBytes::new(input);
2761 let mut chunk = [0u8; 4];
2762 let mut indexes = [0usize; 4];
2763 let mut chunk_len = 0;
2764 let mut write = 0;
2765 let mut terminal_seen = false;
2766
2767 while let Some((index, byte)) = bytes.next_byte() {
2768 if terminal_seen {
2769 return Err(DecodeError::InvalidPadding { index });
2770 }
2771
2772 chunk[chunk_len] = byte;
2773 indexes[chunk_len] = index;
2774 chunk_len += 1;
2775
2776 if chunk_len == 4 {
2777 let available = output.len();
2778 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
2779 required: write,
2780 available,
2781 })?;
2782 let written = decode_chunk::<A, PAD>(chunk, output_tail)
2783 .map_err(|err| map_chunk_error(err, &indexes))?;
2784 write += written;
2785 terminal_seen = written < 3;
2786 chunk_len = 0;
2787 }
2788 }
2789
2790 if chunk_len == 0 {
2791 return Ok(write);
2792 }
2793 if PAD {
2794 return Err(DecodeError::InvalidLength);
2795 }
2796
2797 decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
2798 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
2799 .map(|n| write + n)
2800}
2801
2802struct WrappedBytes<'a> {
2803 input: &'a [u8],
2804 wrap: LineWrap,
2805 index: usize,
2806 line_len: usize,
2807}
2808
2809impl<'a> WrappedBytes<'a> {
2810 const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
2811 if wrap.line_len == 0 {
2812 return Err(DecodeError::InvalidLineWrap { index: 0 });
2813 }
2814 Ok(Self {
2815 input,
2816 wrap,
2817 index: 0,
2818 line_len: 0,
2819 })
2820 }
2821
2822 fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
2823 loop {
2824 if self.index == self.input.len() {
2825 return Ok(None);
2826 }
2827
2828 if self.starts_with_line_ending() {
2829 let line_end_index = self.index;
2830 if self.line_len == 0 {
2831 return Err(DecodeError::InvalidLineWrap {
2832 index: line_end_index,
2833 });
2834 }
2835
2836 self.index += self.wrap.line_ending.byte_len();
2837 if self.index == self.input.len() {
2838 self.line_len = 0;
2839 return Ok(None);
2840 }
2841
2842 if self.line_len != self.wrap.line_len {
2843 return Err(DecodeError::InvalidLineWrap {
2844 index: line_end_index,
2845 });
2846 }
2847 self.line_len = 0;
2848 continue;
2849 }
2850
2851 let byte = self.input[self.index];
2852 if matches!(byte, b'\r' | b'\n') {
2853 return Err(DecodeError::InvalidLineWrap { index: self.index });
2854 }
2855
2856 self.line_len += 1;
2857 if self.line_len > self.wrap.line_len {
2858 return Err(DecodeError::InvalidLineWrap { index: self.index });
2859 }
2860
2861 let index = self.index;
2862 self.index += 1;
2863 return Ok(Some((index, byte)));
2864 }
2865 }
2866
2867 fn starts_with_line_ending(&self) -> bool {
2868 let line_ending = self.wrap.line_ending.as_bytes();
2869 let Some(end) = self.index.checked_add(line_ending.len()) else {
2870 return false;
2871 };
2872 end <= self.input.len() && &self.input[self.index..end] == line_ending
2873 }
2874}
2875
2876fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
2877 input: &[u8],
2878 wrap: LineWrap,
2879) -> Result<usize, DecodeError> {
2880 let mut bytes = WrappedBytes::new(input, wrap)?;
2881 let mut chunk = [0u8; 4];
2882 let mut indexes = [0usize; 4];
2883 let mut chunk_len = 0;
2884 let mut required = 0;
2885 let mut terminal_seen = false;
2886
2887 while let Some((index, byte)) = bytes.next_byte()? {
2888 if terminal_seen {
2889 return Err(DecodeError::InvalidPadding { index });
2890 }
2891
2892 chunk[chunk_len] = byte;
2893 indexes[chunk_len] = index;
2894 chunk_len += 1;
2895
2896 if chunk_len == 4 {
2897 let written =
2898 validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
2899 required += written;
2900 terminal_seen = written < 3;
2901 chunk_len = 0;
2902 }
2903 }
2904
2905 if chunk_len == 0 {
2906 return Ok(required);
2907 }
2908 if PAD {
2909 return Err(DecodeError::InvalidLength);
2910 }
2911
2912 validate_tail_unpadded::<A>(&chunk[..chunk_len])
2913 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
2914 Ok(required + decoded_capacity(chunk_len))
2915}
2916
2917fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
2918 input: &[u8],
2919 output: &mut [u8],
2920 wrap: LineWrap,
2921) -> Result<usize, DecodeError> {
2922 let mut bytes = WrappedBytes::new(input, wrap)?;
2923 let mut chunk = [0u8; 4];
2924 let mut indexes = [0usize; 4];
2925 let mut chunk_len = 0;
2926 let mut write = 0;
2927 let mut terminal_seen = false;
2928
2929 while let Some((index, byte)) = bytes.next_byte()? {
2930 if terminal_seen {
2931 return Err(DecodeError::InvalidPadding { index });
2932 }
2933
2934 chunk[chunk_len] = byte;
2935 indexes[chunk_len] = index;
2936 chunk_len += 1;
2937
2938 if chunk_len == 4 {
2939 let available = output.len();
2940 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
2941 required: write,
2942 available,
2943 })?;
2944 let written = decode_chunk::<A, PAD>(chunk, output_tail)
2945 .map_err(|err| map_chunk_error(err, &indexes))?;
2946 write += written;
2947 terminal_seen = written < 3;
2948 chunk_len = 0;
2949 }
2950 }
2951
2952 if chunk_len == 0 {
2953 return Ok(write);
2954 }
2955 if PAD {
2956 return Err(DecodeError::InvalidLength);
2957 }
2958
2959 decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
2960 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
2961 .map(|n| write + n)
2962}
2963
2964fn compact_wrapped_input(buffer: &mut [u8], wrap: LineWrap) -> Result<usize, DecodeError> {
2965 if !wrap.is_valid() {
2966 return Err(DecodeError::InvalidLineWrap { index: 0 });
2967 }
2968
2969 let line_ending = wrap.line_ending.as_bytes();
2970 let line_ending_len = line_ending.len();
2971 let mut read = 0;
2972 let mut write = 0;
2973
2974 while read < buffer.len() {
2975 let line_end = read + line_ending_len;
2976 if buffer.get(read..line_end) == Some(line_ending) {
2977 read = line_end;
2978 continue;
2979 }
2980
2981 buffer[write] = buffer[read];
2982 write += 1;
2983 read += 1;
2984 }
2985
2986 Ok(write)
2987}
2988
2989#[inline]
2990const fn is_legacy_whitespace(byte: u8) -> bool {
2991 matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
2992}
2993
2994fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
2995 match err {
2996 DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
2997 index: indexes[index],
2998 byte,
2999 },
3000 DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
3001 index: indexes[index],
3002 },
3003 DecodeError::InvalidInput
3004 | DecodeError::InvalidLineWrap { .. }
3005 | DecodeError::InvalidLength
3006 | DecodeError::OutputTooSmall { .. }
3007 | DecodeError::StagingTooSmall { .. } => err,
3008 }
3009}
3010
3011fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
3012 match err {
3013 DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
3014 index: indexes[index],
3015 byte,
3016 },
3017 DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
3018 index: indexes[index],
3019 },
3020 DecodeError::InvalidByte { .. }
3021 | DecodeError::InvalidPadding { .. }
3022 | DecodeError::InvalidLineWrap { .. }
3023 | DecodeError::InvalidInput
3024 | DecodeError::InvalidLength
3025 | DecodeError::OutputTooSmall { .. }
3026 | DecodeError::StagingTooSmall { .. } => err,
3027 }
3028}
3029
3030fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3031 if !input.len().is_multiple_of(4) {
3032 return Err(DecodeError::InvalidLength);
3033 }
3034 let required = decoded_len_padded(input)?;
3035 if output.len() < required {
3036 return Err(DecodeError::OutputTooSmall {
3037 required,
3038 available: output.len(),
3039 });
3040 }
3041
3042 let mut read = 0;
3043 let mut write = 0;
3044 while read < input.len() {
3045 let chunk = read_quad(input, read)?;
3046 let available = output.len();
3047 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3048 required: write,
3049 available,
3050 })?;
3051 let written = decode_chunk::<A, true>(chunk, output_tail)
3052 .map_err(|err| err.with_index_offset(read))?;
3053 read += 4;
3054 write += written;
3055 if written < 3 && read != input.len() {
3056 return Err(DecodeError::InvalidPadding { index: read - 4 });
3057 }
3058 }
3059 Ok(write)
3060}
3061
3062fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
3063 if input.is_empty() {
3064 return Ok(0);
3065 }
3066
3067 if PAD {
3068 validate_padded::<A>(input)
3069 } else {
3070 validate_unpadded::<A>(input)
3071 }
3072}
3073
3074fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
3075 if !input.len().is_multiple_of(4) {
3076 return Err(DecodeError::InvalidLength);
3077 }
3078 let required = decoded_len_padded(input)?;
3079
3080 let mut read = 0;
3081 while read < input.len() {
3082 let chunk = read_quad(input, read)?;
3083 let written =
3084 validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
3085 read += 4;
3086 if written < 3 && read != input.len() {
3087 return Err(DecodeError::InvalidPadding { index: read - 4 });
3088 }
3089 }
3090
3091 Ok(required)
3092}
3093
3094fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
3095 let required = decoded_len_unpadded(input)?;
3096
3097 let mut read = 0;
3098 while read + 4 <= input.len() {
3099 let chunk = read_quad(input, read)?;
3100 validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
3101 read += 4;
3102 }
3103 validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
3104
3105 Ok(required)
3106}
3107
3108fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3109 let required = decoded_len_unpadded(input)?;
3110 if output.len() < required {
3111 return Err(DecodeError::OutputTooSmall {
3112 required,
3113 available: output.len(),
3114 });
3115 }
3116
3117 let mut read = 0;
3118 let mut write = 0;
3119 while read + 4 <= input.len() {
3120 let chunk = read_quad(input, read)?;
3121 let available = output.len();
3122 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3123 required: write,
3124 available,
3125 })?;
3126 let written = decode_chunk::<A, false>(chunk, output_tail)
3127 .map_err(|err| err.with_index_offset(read))?;
3128 read += 4;
3129 write += written;
3130 }
3131 decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
3132 .map_err(|err| err.with_index_offset(read))
3133 .map(|n| write + n)
3134}
3135
3136fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
3137 if input.is_empty() {
3138 return Ok(0);
3139 }
3140 if !input.len().is_multiple_of(4) {
3141 return Err(DecodeError::InvalidLength);
3142 }
3143
3144 let Some((&last, before_last_prefix)) = input.split_last() else {
3145 return Ok(0);
3146 };
3147 let Some(&before_last) = before_last_prefix.last() else {
3148 return Err(DecodeError::InvalidLength);
3149 };
3150
3151 let mut padding = 0;
3152 if last == b'=' {
3153 padding += 1;
3154 }
3155 if before_last == b'=' {
3156 padding += 1;
3157 }
3158 if padding == 0
3159 && let Some(index) = input.iter().position(|byte| *byte == b'=')
3160 {
3161 return Err(DecodeError::InvalidPadding { index });
3162 }
3163 if padding > 0 {
3164 let first_pad = input.len() - padding;
3165 if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
3166 return Err(DecodeError::InvalidPadding { index });
3167 }
3168 }
3169 Ok(input.len() / 4 * 3 - padding)
3170}
3171
3172fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
3173 if input.len() % 4 == 1 {
3174 return Err(DecodeError::InvalidLength);
3175 }
3176 if let Some(index) = input.iter().position(|byte| *byte == b'=') {
3177 return Err(DecodeError::InvalidPadding { index });
3178 }
3179 Ok(decoded_capacity(input.len()))
3180}
3181
3182fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
3183 let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
3184 match input.get(offset..end) {
3185 Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
3186 _ => Err(DecodeError::InvalidLength),
3187 }
3188}
3189
3190fn first_padding_index_unchecked(input: [u8; 4]) -> usize {
3191 let [b0, b1, b2, b3] = input;
3192 if b0 == b'=' {
3193 0
3194 } else if b1 == b'=' {
3195 1
3196 } else if b2 == b'=' {
3197 2
3198 } else if b3 == b'=' {
3199 3
3200 } else {
3201 debug_assert!(
3202 false,
3203 "first_padding_index_unchecked called with no padding"
3204 );
3205 4
3206 }
3207}
3208
3209fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
3210 let [b0, b1, b2, b3] = input;
3211 let _v0 = decode_byte::<A>(b0, 0)?;
3212 let v1 = decode_byte::<A>(b1, 1)?;
3213
3214 match (b2, b3) {
3215 (b'=', b'=') if PAD => {
3216 if v1 & 0b0000_1111 != 0 {
3217 return Err(DecodeError::InvalidPadding { index: 1 });
3218 }
3219 Ok(1)
3220 }
3221 (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
3222 (_, b'=') if PAD => {
3223 let v2 = decode_byte::<A>(b2, 2)?;
3224 if v2 & 0b0000_0011 != 0 {
3225 return Err(DecodeError::InvalidPadding { index: 2 });
3226 }
3227 Ok(2)
3228 }
3229 (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
3230 index: first_padding_index_unchecked(input),
3231 }),
3232 _ => {
3233 decode_byte::<A>(b2, 2)?;
3234 decode_byte::<A>(b3, 3)?;
3235 Ok(3)
3236 }
3237 }
3238}
3239
3240fn decode_chunk<A: Alphabet, const PAD: bool>(
3241 input: [u8; 4],
3242 output: &mut [u8],
3243) -> Result<usize, DecodeError> {
3244 let [b0, b1, b2, b3] = input;
3245 let v0 = decode_byte::<A>(b0, 0)?;
3246 let v1 = decode_byte::<A>(b1, 1)?;
3247
3248 match (b2, b3) {
3249 (b'=', b'=') if PAD => {
3250 if output.is_empty() {
3251 return Err(DecodeError::OutputTooSmall {
3252 required: 1,
3253 available: output.len(),
3254 });
3255 }
3256 if v1 & 0b0000_1111 != 0 {
3257 return Err(DecodeError::InvalidPadding { index: 1 });
3258 }
3259 output[0] = (v0 << 2) | (v1 >> 4);
3260 Ok(1)
3261 }
3262 (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
3263 (_, b'=') if PAD => {
3264 if output.len() < 2 {
3265 return Err(DecodeError::OutputTooSmall {
3266 required: 2,
3267 available: output.len(),
3268 });
3269 }
3270 let v2 = decode_byte::<A>(b2, 2)?;
3271 if v2 & 0b0000_0011 != 0 {
3272 return Err(DecodeError::InvalidPadding { index: 2 });
3273 }
3274 output[0] = (v0 << 2) | (v1 >> 4);
3275 output[1] = (v1 << 4) | (v2 >> 2);
3276 Ok(2)
3277 }
3278 (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
3279 index: first_padding_index_unchecked(input),
3280 }),
3281 _ => {
3282 if output.len() < 3 {
3283 return Err(DecodeError::OutputTooSmall {
3284 required: 3,
3285 available: output.len(),
3286 });
3287 }
3288 let v2 = decode_byte::<A>(b2, 2)?;
3289 let v3 = decode_byte::<A>(b3, 3)?;
3290 output[0] = (v0 << 2) | (v1 >> 4);
3291 output[1] = (v1 << 4) | (v2 >> 2);
3292 output[2] = (v2 << 6) | v3;
3293 Ok(3)
3294 }
3295 }
3296}
3297
3298fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
3299 match input {
3300 [] => Ok(()),
3301 [b0, b1] => {
3302 decode_byte::<A>(*b0, 0)?;
3303 let v1 = decode_byte::<A>(*b1, 1)?;
3304 if v1 & 0b0000_1111 != 0 {
3305 return Err(DecodeError::InvalidPadding { index: 1 });
3306 }
3307 Ok(())
3308 }
3309 [b0, b1, b2] => {
3310 decode_byte::<A>(*b0, 0)?;
3311 decode_byte::<A>(*b1, 1)?;
3312 let v2 = decode_byte::<A>(*b2, 2)?;
3313 if v2 & 0b0000_0011 != 0 {
3314 return Err(DecodeError::InvalidPadding { index: 2 });
3315 }
3316 Ok(())
3317 }
3318 _ => Err(DecodeError::InvalidLength),
3319 }
3320}
3321
3322fn decode_tail_unpadded<A: Alphabet>(
3323 input: &[u8],
3324 output: &mut [u8],
3325) -> Result<usize, DecodeError> {
3326 match input {
3327 [] => Ok(0),
3328 [b0, b1] => {
3329 let Some(out0) = output.first_mut() else {
3330 return Err(DecodeError::OutputTooSmall {
3331 required: 1,
3332 available: output.len(),
3333 });
3334 };
3335 let v0 = decode_byte::<A>(*b0, 0)?;
3336 let v1 = decode_byte::<A>(*b1, 1)?;
3337 if v1 & 0b0000_1111 != 0 {
3338 return Err(DecodeError::InvalidPadding { index: 1 });
3339 }
3340 *out0 = (v0 << 2) | (v1 >> 4);
3341 Ok(1)
3342 }
3343 [b0, b1, b2] => {
3344 let available = output.len();
3345 let Some([out0, out1]) = output.get_mut(..2) else {
3346 return Err(DecodeError::OutputTooSmall {
3347 required: 2,
3348 available,
3349 });
3350 };
3351 let v0 = decode_byte::<A>(*b0, 0)?;
3352 let v1 = decode_byte::<A>(*b1, 1)?;
3353 let v2 = decode_byte::<A>(*b2, 2)?;
3354 if v2 & 0b0000_0011 != 0 {
3355 return Err(DecodeError::InvalidPadding { index: 2 });
3356 }
3357 *out0 = (v0 << 2) | (v1 >> 4);
3358 *out1 = (v1 << 4) | (v2 >> 2);
3359 Ok(2)
3360 }
3361 _ => Err(DecodeError::InvalidLength),
3362 }
3363}
3364
3365fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
3366 A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
3367}
3368
3369fn ct_decode_slice<A: Alphabet, const PAD: bool>(
3370 input: &[u8],
3371 output: &mut [u8],
3372) -> Result<usize, DecodeError> {
3373 if input.is_empty() {
3374 return Ok(0);
3375 }
3376
3377 if PAD {
3378 ct_decode_padded::<A>(input, output)
3379 } else {
3380 ct_decode_unpadded::<A>(input, output)
3381 }
3382}
3383
3384fn ct_decode_slice_staged_clear_tail<A: Alphabet, const PAD: bool>(
3385 input: &[u8],
3386 output: &mut [u8],
3387 staging: &mut [u8],
3388) -> Result<usize, DecodeError> {
3389 let required = match ct_decoded_len::<A, PAD>(input) {
3390 Ok(required) => required,
3391 Err(err) => {
3392 wipe_bytes(output);
3393 wipe_bytes(staging);
3394 return Err(err);
3395 }
3396 };
3397
3398 if output.len() < required {
3399 wipe_bytes(output);
3400 wipe_bytes(staging);
3401 return Err(DecodeError::OutputTooSmall {
3402 required,
3403 available: output.len(),
3404 });
3405 }
3406
3407 if staging.len() < required {
3408 wipe_bytes(output);
3409 wipe_bytes(staging);
3410 return Err(DecodeError::StagingTooSmall {
3411 required,
3412 available: staging.len(),
3413 });
3414 }
3415
3416 let written = match ct_decode_slice::<A, PAD>(input, &mut staging[..required]) {
3417 Ok(written) => written,
3418 Err(err) => {
3419 wipe_bytes(output);
3420 wipe_bytes(staging);
3421 return Err(err);
3422 }
3423 };
3424
3425 output[..written].copy_from_slice(&staging[..written]);
3426 wipe_bytes(staging);
3427 wipe_tail(output, written);
3428 Ok(written)
3429}
3430
3431fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
3432 buffer: &mut [u8],
3433) -> Result<usize, DecodeError> {
3434 if buffer.is_empty() {
3435 return Ok(0);
3436 }
3437
3438 if PAD {
3439 ct_decode_padded_in_place::<A>(buffer)
3440 } else {
3441 ct_decode_unpadded_in_place::<A>(buffer)
3442 }
3443}
3444
3445#[inline(never)]
3446#[allow(unsafe_code)]
3447fn ct_error_gate_barrier(invalid_byte: u8, invalid_padding: u8) {
3448 core::hint::black_box(invalid_byte | invalid_padding);
3449 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
3450
3451 #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
3452 {
3453 unsafe {
3456 core::arch::asm!("lfence", options(nostack, preserves_flags, nomem));
3457 }
3458 }
3459
3460 #[cfg(all(not(miri), target_arch = "aarch64"))]
3461 {
3462 unsafe {
3466 core::arch::asm!("isb sy", "hint #20", options(nostack, preserves_flags));
3467 }
3468 }
3469
3470 #[cfg(all(not(miri), target_arch = "arm"))]
3471 {
3472 unsafe {
3475 core::arch::asm!("isb sy", options(nostack, preserves_flags));
3476 }
3477 }
3478
3479 #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
3480 {
3481 unsafe {
3486 core::arch::asm!("fence rw, rw", options(nostack, preserves_flags));
3487 }
3488 }
3489}
3490
3491fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
3492 if input.is_empty() {
3493 return Ok(());
3494 }
3495
3496 if PAD {
3497 ct_validate_padded::<A>(input)
3498 } else {
3499 ct_validate_unpadded::<A>(input)
3500 }
3501}
3502
3503fn ct_decoded_len<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
3504 ct_validate_decode::<A, PAD>(input)?;
3505 if input.is_empty() {
3506 return Ok(0);
3507 }
3508
3509 if PAD {
3510 Ok(input.len() / 4 * 3 - ct_padding_len(input))
3511 } else {
3512 let full_quads = input.len() / 4 * 3;
3513 match input.len() % 4 {
3514 0 => Ok(full_quads),
3515 2 => Ok(full_quads + 1),
3516 3 => Ok(full_quads + 2),
3517 _ => Err(DecodeError::InvalidLength),
3518 }
3519 }
3520}
3521
3522fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
3523 if !input.len().is_multiple_of(4) {
3524 return Err(DecodeError::InvalidLength);
3525 }
3526
3527 let padding = ct_padding_len(input);
3528 let mut invalid_byte = 0u8;
3529 let mut invalid_padding = 0u8;
3530 let mut read = 0;
3531
3532 while read + 4 < input.len() {
3533 let [b0, b1, b2, b3] =
3534 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3535 let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
3536 let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
3537 let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
3538 let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
3539
3540 invalid_byte |= !valid0;
3541 invalid_byte |= !valid1;
3542 invalid_byte |= !valid2;
3543 invalid_byte |= !valid3;
3544 invalid_padding |= ct_mask_eq_u8(b2, b'=');
3545 invalid_padding |= ct_mask_eq_u8(b3, b'=');
3546 read += 4;
3547 }
3548
3549 let final_chunk =
3550 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3551 let (_, final_invalid_byte, final_invalid_padding, _) =
3552 ct_padded_final_quantum::<A>(final_chunk, padding);
3553 invalid_byte |= final_invalid_byte;
3554 invalid_padding |= final_invalid_padding;
3555
3556 report_ct_error(invalid_byte, invalid_padding)
3557}
3558
3559fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
3560 if input.len() % 4 == 1 {
3561 return Err(DecodeError::InvalidLength);
3562 }
3563
3564 let mut invalid_byte = 0u8;
3565 let mut invalid_padding = 0u8;
3566 let mut read = 0;
3567
3568 while read + 4 <= input.len() {
3569 let [b0, b1, b2, b3] =
3570 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3571 let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
3572 let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
3573 let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
3574 let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
3575
3576 invalid_byte |= !valid0;
3577 invalid_byte |= !valid1;
3578 invalid_byte |= !valid2;
3579 invalid_byte |= !valid3;
3580 invalid_padding |= ct_mask_eq_u8(b0, b'=');
3581 invalid_padding |= ct_mask_eq_u8(b1, b'=');
3582 invalid_padding |= ct_mask_eq_u8(b2, b'=');
3583 invalid_padding |= ct_mask_eq_u8(b3, b'=');
3584
3585 read += 4;
3586 }
3587
3588 match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
3589 [] => {}
3590 [b0, b1] => {
3591 let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3592 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3593 invalid_byte |= !valid0;
3594 invalid_byte |= !valid1;
3595 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3596 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3597 invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
3598 }
3599 [b0, b1, b2] => {
3600 let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3601 let (_, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3602 let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
3603 invalid_byte |= !valid0;
3604 invalid_byte |= !valid1;
3605 invalid_byte |= !valid2;
3606 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3607 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3608 invalid_padding |= ct_mask_eq_u8(*b2, b'=');
3609 invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
3610 }
3611 _ => {
3612 invalid_byte = 0xff;
3613 invalid_padding = 0xff;
3614 }
3615 }
3616
3617 report_ct_error(invalid_byte, invalid_padding)
3618}
3619
3620fn ct_padded_final_quantum<A: Alphabet>(
3621 input: [u8; 4],
3622 padding: usize,
3623) -> ([u8; 3], u8, u8, usize) {
3624 let [b0, b1, b2, b3] = input;
3625 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3626 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3627 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3628 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3629
3630 let padding_byte = match padding {
3631 0 => 0,
3632 1 => 1,
3633 2 => 2,
3634 _ => return ([0; 3], 0xff, 0xff, 0),
3635 };
3636 let no_padding = ct_mask_eq_u8(padding_byte, 0);
3637 let one_padding = ct_mask_eq_u8(padding_byte, 1);
3638 let two_padding = ct_mask_eq_u8(padding_byte, 2);
3639 let require_v2 = no_padding | one_padding;
3640 let require_v3 = no_padding;
3641
3642 let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
3643 let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
3644 | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
3645 | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
3646
3647 (
3648 [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
3649 invalid_byte,
3650 invalid_padding,
3651 3 - padding,
3652 )
3653}
3654
3655fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3656 if !input.len().is_multiple_of(4) {
3657 return Err(DecodeError::InvalidLength);
3658 }
3659
3660 let padding = ct_padding_len(input);
3661 let required = input.len() / 4 * 3 - padding;
3662 if output.len() < required {
3663 return Err(DecodeError::OutputTooSmall {
3664 required,
3665 available: output.len(),
3666 });
3667 }
3668
3669 let mut invalid_byte = 0u8;
3670 let mut invalid_padding = 0u8;
3671 let mut write = 0;
3672 let mut read = 0;
3673
3674 while read + 4 < input.len() {
3675 let [b0, b1, b2, b3] =
3676 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3677 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3678 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3679 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3680 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3681
3682 invalid_byte |= !valid0;
3683 invalid_byte |= !valid1;
3684 invalid_byte |= !valid2;
3685 invalid_byte |= !valid3;
3686 invalid_padding |= ct_mask_eq_u8(b2, b'=');
3687 invalid_padding |= ct_mask_eq_u8(b3, b'=');
3688 output[write] = (v0 << 2) | (v1 >> 4);
3689 output[write + 1] = (v1 << 4) | (v2 >> 2);
3690 output[write + 2] = (v2 << 6) | v3;
3691 write += 3;
3692 read += 4;
3693 }
3694
3695 let final_chunk =
3696 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3697 let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
3698 ct_padded_final_quantum::<A>(final_chunk, padding);
3699 invalid_byte |= final_invalid_byte;
3700 invalid_padding |= final_invalid_padding;
3701 output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
3702 write += final_written;
3703
3704 report_ct_error(invalid_byte, invalid_padding)?;
3705 Ok(write)
3706}
3707
3708fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
3709 if !buffer.len().is_multiple_of(4) {
3710 return Err(DecodeError::InvalidLength);
3711 }
3712
3713 let padding = ct_padding_len(buffer);
3714 let required = buffer.len() / 4 * 3 - padding;
3715 if required > buffer.len() {
3716 wipe_bytes(buffer);
3717 return Err(DecodeError::InvalidInput);
3718 }
3719
3720 let mut invalid_byte = 0u8;
3721 let mut invalid_padding = 0u8;
3722 let mut write = 0;
3723 let mut read = 0;
3724
3725 while read + 4 < buffer.len() {
3726 let [b0, b1, b2, b3] =
3727 read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
3728 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3729 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3730 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3731 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3732
3733 invalid_byte |= !valid0;
3734 invalid_byte |= !valid1;
3735 invalid_byte |= !valid2;
3736 invalid_byte |= !valid3;
3737 invalid_padding |= ct_mask_eq_u8(b2, b'=');
3738 invalid_padding |= ct_mask_eq_u8(b3, b'=');
3739 buffer[write] = (v0 << 2) | (v1 >> 4);
3740 buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3741 buffer[write + 2] = (v2 << 6) | v3;
3742 write += 3;
3743 read += 4;
3744 }
3745
3746 let final_chunk =
3747 read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
3748 let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
3749 ct_padded_final_quantum::<A>(final_chunk, padding);
3750 invalid_byte |= final_invalid_byte;
3751 invalid_padding |= final_invalid_padding;
3752 buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
3753 write += final_written;
3754
3755 if write != required {
3756 ct_error_gate_barrier(invalid_byte, invalid_padding);
3757 wipe_bytes(buffer);
3758 return Err(DecodeError::InvalidInput);
3759 }
3760 if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
3761 wipe_bytes(buffer);
3762 return Err(err);
3763 }
3764 Ok(write)
3765}
3766
3767fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3768 if input.len() % 4 == 1 {
3769 return Err(DecodeError::InvalidLength);
3770 }
3771
3772 let required = decoded_capacity(input.len());
3773 if output.len() < required {
3774 return Err(DecodeError::OutputTooSmall {
3775 required,
3776 available: output.len(),
3777 });
3778 }
3779
3780 let mut invalid_byte = 0u8;
3781 let mut invalid_padding = 0u8;
3782 let mut write = 0;
3783 let mut read = 0;
3784
3785 while read + 4 <= input.len() {
3786 let [b0, b1, b2, b3] =
3787 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
3788 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3789 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3790 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3791 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3792
3793 invalid_byte |= !valid0;
3794 invalid_byte |= !valid1;
3795 invalid_byte |= !valid2;
3796 invalid_byte |= !valid3;
3797 invalid_padding |= ct_mask_eq_u8(b0, b'=');
3798 invalid_padding |= ct_mask_eq_u8(b1, b'=');
3799 invalid_padding |= ct_mask_eq_u8(b2, b'=');
3800 invalid_padding |= ct_mask_eq_u8(b3, b'=');
3801
3802 output[write] = (v0 << 2) | (v1 >> 4);
3803 output[write + 1] = (v1 << 4) | (v2 >> 2);
3804 output[write + 2] = (v2 << 6) | v3;
3805 read += 4;
3806 write += 3;
3807 }
3808
3809 match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
3810 [] => {}
3811 [b0, b1] => {
3812 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3813 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3814 invalid_byte |= !valid0;
3815 invalid_byte |= !valid1;
3816 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3817 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3818 invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
3819 output[write] = (v0 << 2) | (v1 >> 4);
3820 write += 1;
3821 }
3822 [b0, b1, b2] => {
3823 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3824 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3825 let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
3826 invalid_byte |= !valid0;
3827 invalid_byte |= !valid1;
3828 invalid_byte |= !valid2;
3829 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3830 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3831 invalid_padding |= ct_mask_eq_u8(*b2, b'=');
3832 invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
3833 output[write] = (v0 << 2) | (v1 >> 4);
3834 output[write + 1] = (v1 << 4) | (v2 >> 2);
3835 write += 2;
3836 }
3837 _ => {
3838 invalid_byte = 0xff;
3839 invalid_padding = 0xff;
3840 }
3841 }
3842
3843 report_ct_error(invalid_byte, invalid_padding)?;
3844 Ok(write)
3845}
3846
3847fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
3848 if buffer.len() % 4 == 1 {
3849 return Err(DecodeError::InvalidLength);
3850 }
3851
3852 let required = decoded_capacity(buffer.len());
3853 if required > buffer.len() {
3854 wipe_bytes(buffer);
3855 return Err(DecodeError::InvalidInput);
3856 }
3857
3858 let mut invalid_byte = 0u8;
3859 let mut invalid_padding = 0u8;
3860 let mut write = 0;
3861 let mut read = 0;
3862
3863 while read + 4 <= buffer.len() {
3864 let [b0, b1, b2, b3] =
3865 read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
3866 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
3867 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
3868 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
3869 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
3870
3871 invalid_byte |= !valid0;
3872 invalid_byte |= !valid1;
3873 invalid_byte |= !valid2;
3874 invalid_byte |= !valid3;
3875 invalid_padding |= ct_mask_eq_u8(b0, b'=');
3876 invalid_padding |= ct_mask_eq_u8(b1, b'=');
3877 invalid_padding |= ct_mask_eq_u8(b2, b'=');
3878 invalid_padding |= ct_mask_eq_u8(b3, b'=');
3879
3880 buffer[write] = (v0 << 2) | (v1 >> 4);
3881 buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3882 buffer[write + 2] = (v2 << 6) | v3;
3883 read += 4;
3884 write += 3;
3885 }
3886
3887 let tail = read_tail_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
3888 match tail {
3889 [] => {}
3890 [b0, b1] => {
3891 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3892 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3893 invalid_byte |= !valid0;
3894 invalid_byte |= !valid1;
3895 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3896 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3897 invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
3898 buffer[write] = (v0 << 2) | (v1 >> 4);
3899 write += 1;
3900 }
3901 [b0, b1, b2] => {
3902 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
3903 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
3904 let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
3905 invalid_byte |= !valid0;
3906 invalid_byte |= !valid1;
3907 invalid_byte |= !valid2;
3908 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
3909 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
3910 invalid_padding |= ct_mask_eq_u8(*b2, b'=');
3911 invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
3912 buffer[write] = (v0 << 2) | (v1 >> 4);
3913 buffer[write + 1] = (v1 << 4) | (v2 >> 2);
3914 write += 2;
3915 }
3916 _ => {
3917 invalid_byte = 0xff;
3918 invalid_padding = 0xff;
3919 }
3920 }
3921
3922 if write != required {
3923 ct_error_gate_barrier(invalid_byte, invalid_padding);
3924 wipe_bytes(buffer);
3925 return Err(DecodeError::InvalidInput);
3926 }
3927 if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
3928 wipe_bytes(buffer);
3929 return Err(err);
3930 }
3931 Ok(write)
3932}
3933
3934fn read_tail(input: &[u8], offset: usize) -> Result<&[u8], DecodeError> {
3935 input.get(offset..).ok_or(DecodeError::InvalidLength)
3936}
3937
3938fn read_quad_or_mark_invalid(
3939 input: &[u8],
3940 offset: usize,
3941 invalid_byte: &mut u8,
3942 invalid_padding: &mut u8,
3943) -> [u8; 4] {
3944 if let Ok(quad) = read_quad(input, offset) {
3945 quad
3946 } else {
3947 debug_assert!(
3948 false,
3949 "read_quad failed inside length-validated constant-time decode loop"
3950 );
3951 *invalid_byte = 0xff;
3952 *invalid_padding = 0xff;
3953 [0; 4]
3954 }
3955}
3956
3957fn read_tail_or_mark_invalid<'a>(
3958 input: &'a [u8],
3959 offset: usize,
3960 invalid_byte: &mut u8,
3961 invalid_padding: &mut u8,
3962) -> &'a [u8] {
3963 if let Ok(tail) = read_tail(input, offset) {
3964 tail
3965 } else {
3966 debug_assert!(
3967 false,
3968 "read_tail failed inside length-validated constant-time decode loop"
3969 );
3970 *invalid_byte = 0xff;
3971 *invalid_padding = 0xff;
3972 &[]
3973 }
3974}
3975
3976#[inline(never)]
3977#[allow(unsafe_code)]
3978fn ct_decode_alphabet_byte<A: Alphabet>(byte: u8) -> (u8, u8) {
3979 let mut decoded = 0u8;
3980 let mut valid = 0u8;
3981 let mut candidate = 0u8;
3982
3983 while candidate < 64 {
3984 let matches = core::hint::black_box(ct_mask_eq_u8(
3985 core::hint::black_box(byte),
3986 core::hint::black_box(A::ENCODE[candidate as usize]),
3987 ));
3988 decoded = core::hint::black_box(
3989 core::hint::black_box(decoded) | core::hint::black_box(candidate & matches),
3990 );
3991 decoded = unsafe { core::ptr::read_volatile(&raw const decoded) };
3995 valid =
3996 core::hint::black_box(core::hint::black_box(valid) | core::hint::black_box(matches));
3997 valid = unsafe { core::ptr::read_volatile(&raw const valid) };
4001 candidate += 1;
4002 }
4003
4004 (decoded, valid)
4005}
4006
4007fn ct_padding_len(input: &[u8]) -> usize {
4008 let Some((&last, before_last_prefix)) = input.split_last() else {
4009 return 0;
4010 };
4011 let Some(&before_last) = before_last_prefix.last() else {
4012 return 0;
4013 };
4014 usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
4015}
4016
4017fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
4018 ct_error_gate_barrier(invalid_byte, invalid_padding);
4019
4020 if (invalid_byte | invalid_padding) != 0 {
4021 Err(DecodeError::InvalidInput)
4022 } else {
4023 Ok(())
4024 }
4025}
4026
4027#[cfg(kani)]
4028mod kani_proofs {
4029 use super::{
4030 STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
4031 decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
4032 };
4033
4034 #[kani::proof]
4035 fn checked_encoded_len_is_bounded_for_small_inputs() {
4036 let len = usize::from(kani::any::<u8>());
4037 let padded = kani::any::<bool>();
4038 let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
4039
4040 assert!(encoded >= len);
4041 assert!(encoded <= len / 3 * 4 + 4);
4042 }
4043
4044 #[kani::proof]
4045 fn decoded_capacity_is_bounded_for_small_inputs() {
4046 let len = usize::from(kani::any::<u8>());
4047 let capacity = decoded_capacity(len);
4048
4049 assert!(capacity <= len / 4 * 3 + 2);
4050 }
4051
4052 #[kani::proof]
4053 #[kani::unwind(3)]
4054 fn standard_in_place_decode_returns_prefix_within_buffer() {
4055 let mut buffer = kani::any::<[u8; 8]>();
4056 let result = STANDARD.decode_in_place(&mut buffer);
4057
4058 if let Ok(decoded) = result {
4059 assert!(decoded.len() <= 8);
4060 }
4061 }
4062
4063 #[kani::proof]
4064 #[kani::unwind(3)]
4065 fn standard_decode_slice_returns_written_within_output() {
4066 let input = kani::any::<[u8; 4]>();
4067 let mut output = kani::any::<[u8; 3]>();
4068 let result = STANDARD.decode_slice(&input, &mut output);
4069
4070 if let Ok(written) = result {
4071 assert!(written <= output.len());
4072 }
4073 }
4074
4075 #[kani::proof]
4076 #[kani::unwind(3)]
4077 fn standard_decode_chunk_returns_written_within_output() {
4078 let input = kani::any::<[u8; 4]>();
4079 let mut output = kani::any::<[u8; 3]>();
4080 let result = decode_chunk::<Standard, true>(input, &mut output);
4081
4082 if let Ok(written) = result {
4083 assert!(written <= output.len());
4084 assert!(written <= 3);
4085 }
4086 }
4087
4088 #[kani::proof]
4089 #[kani::unwind(3)]
4090 fn standard_decode_chunk_bit_packing_matches_decoded_values() {
4091 let input = kani::any::<[u8; 4]>();
4092 let mut output = kani::any::<[u8; 3]>();
4093 let result = decode_chunk::<Standard, true>(input, &mut output);
4094
4095 if let Ok(written) = result {
4096 let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
4097 let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
4098
4099 assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
4100
4101 if written >= 2 {
4102 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
4103 assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
4104 }
4105
4106 if written == 3 {
4107 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
4108 let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
4109 assert!(output[2] == ((v2 << 6) | v3));
4110 }
4111 }
4112 }
4113
4114 #[kani::proof]
4115 #[kani::unwind(3)]
4116 fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
4117 let input = kani::any::<[u8; 3]>();
4118 let len = usize::from(kani::any::<u8>() % 4);
4119 let result = validate_tail_unpadded::<Standard>(&input[..len]);
4120
4121 if result.is_ok() {
4122 assert!(len == 0 || len == 2 || len == 3);
4123 }
4124 }
4125
4126 #[kani::proof]
4127 #[kani::unwind(3)]
4128 fn standard_decode_two_byte_tail_returns_written_within_output() {
4129 let input = kani::any::<[u8; 2]>();
4130 let mut output = kani::any::<[u8; 1]>();
4131 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
4132
4133 if let Ok(written) = result {
4134 assert!(written <= output.len());
4135 assert!(written == 1);
4136 }
4137 }
4138
4139 #[kani::proof]
4140 #[kani::unwind(3)]
4141 fn standard_decode_three_byte_tail_returns_written_within_output() {
4142 let input = kani::any::<[u8; 3]>();
4143 let mut output = kani::any::<[u8; 2]>();
4144 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
4145
4146 if let Ok(written) = result {
4147 assert!(written <= output.len());
4148 assert!(written == 2);
4149 }
4150 }
4151
4152 #[kani::proof]
4153 #[kani::unwind(3)]
4154 fn standard_decode_slice_clear_tail_clears_output_on_error() {
4155 let input = kani::any::<[u8; 4]>();
4156 let mut output = kani::any::<[u8; 3]>();
4157 let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
4158
4159 if result.is_err() {
4160 assert!(output.iter().all(|byte| *byte == 0));
4161 }
4162 }
4163
4164 #[kani::proof]
4165 #[kani::unwind(3)]
4166 fn standard_encode_slice_returns_written_within_output() {
4167 let input = kani::any::<[u8; 3]>();
4168 let mut output = kani::any::<[u8; 4]>();
4169 let result = STANDARD.encode_slice(&input, &mut output);
4170
4171 if let Ok(written) = result {
4172 assert!(written <= output.len());
4173 }
4174 }
4175
4176 #[kani::proof]
4177 #[kani::unwind(4)]
4178 fn standard_encode_in_place_returns_prefix_within_buffer() {
4179 let mut buffer = kani::any::<[u8; 8]>();
4180 let input_len = usize::from(kani::any::<u8>() % 9);
4181 let result = STANDARD.encode_in_place(&mut buffer, input_len);
4182
4183 if let Ok(encoded) = result {
4184 assert!(encoded.len() <= 8);
4185 }
4186 }
4187
4188 #[kani::proof]
4189 #[kani::unwind(3)]
4190 fn standard_clear_tail_decode_clears_buffer_on_error() {
4191 let mut buffer = kani::any::<[u8; 4]>();
4192 let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
4193
4194 if result.is_err() {
4195 assert!(buffer.iter().all(|byte| *byte == 0));
4196 }
4197 }
4198
4199 #[kani::proof]
4200 #[kani::unwind(3)]
4201 fn ct_standard_decode_slice_returns_written_within_output() {
4202 let input = kani::any::<[u8; 4]>();
4203 let mut output = kani::any::<[u8; 3]>();
4204 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
4205
4206 if let Ok(written) = result {
4207 assert!(written <= output.len());
4208 }
4209 }
4210
4211 #[kani::proof]
4212 #[kani::unwind(3)]
4213 fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
4214 let input = kani::any::<[u8; 4]>();
4215 let mut output = kani::any::<[u8; 3]>();
4216 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
4217
4218 if result.is_err() {
4219 assert!(output.iter().all(|byte| *byte == 0));
4220 }
4221 }
4222
4223 #[kani::proof]
4224 #[kani::unwind(3)]
4225 fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
4226 let mut buffer = kani::any::<[u8; 4]>();
4227 let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
4228
4229 if result.is_err() {
4230 assert!(buffer.iter().all(|byte| *byte == 0));
4231 }
4232 }
4233
4234 #[kani::proof]
4235 #[kani::unwind(3)]
4236 fn ct_standard_validate_matches_decode_for_one_quantum() {
4237 let input = kani::any::<[u8; 4]>();
4238 let mut output = kani::any::<[u8; 3]>();
4239
4240 let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
4241 let decode_ok = ct::STANDARD
4242 .decode_slice_clear_tail(&input, &mut output)
4243 .is_ok();
4244
4245 assert!(validate_ok == decode_ok);
4246 }
4247}
4248
4249#[cfg(test)]
4250mod tests {
4251 use super::*;
4252
4253 fn fill_pattern(output: &mut [u8], seed: usize) {
4254 for (index, byte) in output.iter_mut().enumerate() {
4255 let value = (index * 73 + seed * 19) % 256;
4256 *byte = u8::try_from(value).unwrap();
4257 }
4258 }
4259
4260 fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
4261 where
4262 A: Alphabet,
4263 {
4264 let engine = Engine::<A, PAD>::new();
4265 let mut dispatched = [0x55; 256];
4266 let mut scalar = [0xaa; 256];
4267
4268 let dispatched_result = engine.encode_slice(input, &mut dispatched);
4269 let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
4270
4271 assert_eq!(dispatched_result, scalar_result);
4272 if let Ok(written) = dispatched_result {
4273 assert_eq!(&dispatched[..written], &scalar[..written]);
4274 }
4275
4276 let required = checked_encoded_len(input.len(), PAD).unwrap();
4277 if required > 0 {
4278 let mut dispatched_short = [0x55; 256];
4279 let mut scalar_short = [0xaa; 256];
4280 let available = required - 1;
4281
4282 assert_eq!(
4283 engine.encode_slice(input, &mut dispatched_short[..available]),
4284 backend::scalar_reference_encode_slice::<A, PAD>(
4285 input,
4286 &mut scalar_short[..available],
4287 )
4288 );
4289 }
4290 }
4291
4292 fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
4293 where
4294 A: Alphabet,
4295 {
4296 let engine = Engine::<A, PAD>::new();
4297 let mut dispatched = [0x55; 128];
4298 let mut scalar = [0xaa; 128];
4299
4300 let dispatched_result = engine.decode_slice(input, &mut dispatched);
4301 let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
4302
4303 assert_eq!(dispatched_result, scalar_result);
4304 if let Ok(written) = dispatched_result {
4305 assert_eq!(&dispatched[..written], &scalar[..written]);
4306
4307 if written > 0 {
4308 let mut dispatched_short = [0x55; 128];
4309 let mut scalar_short = [0xaa; 128];
4310 let available = written - 1;
4311
4312 assert_eq!(
4313 engine.decode_slice(input, &mut dispatched_short[..available]),
4314 backend::scalar_reference_decode_slice::<A, PAD>(
4315 input,
4316 &mut scalar_short[..available],
4317 )
4318 );
4319 }
4320 }
4321 }
4322
4323 fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
4324 where
4325 A: Alphabet,
4326 {
4327 assert_encode_backend_matches_scalar::<A, PAD>(input);
4328
4329 let mut encoded = [0; 256];
4330 let encoded_len =
4331 backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
4332 assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
4333 }
4334
4335 fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
4336 let mut encoded = [0u8; 4];
4337 let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
4338 assert_eq!(encoded_len, 4);
4339
4340 let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
4341 let mut decoded = [0u8; 3];
4342 let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
4343
4344 assert_eq!(decoded_len, input.len());
4345 assert_eq!(&decoded[..decoded_len], input);
4346 }
4347
4348 #[test]
4349 fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
4350 let mut input = [0; 128];
4351
4352 for input_len in 0..=input.len() {
4353 fill_pattern(&mut input[..input_len], input_len);
4354 let input = &input[..input_len];
4355
4356 assert_backend_round_trip_matches_scalar::<Standard, true>(input);
4357 assert_backend_round_trip_matches_scalar::<Standard, false>(input);
4358 assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
4359 assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
4360 }
4361 }
4362
4363 #[test]
4364 fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
4365 for input in [
4366 &b"Z"[..],
4367 b"====",
4368 b"AA=A",
4369 b"Zh==",
4370 b"Zm9=",
4371 b"Zm9v$g==",
4372 b"Zm9vZh==",
4373 ] {
4374 assert_decode_backend_matches_scalar::<Standard, true>(input);
4375 }
4376
4377 for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
4378 assert_decode_backend_matches_scalar::<Standard, false>(input);
4379 }
4380
4381 assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
4382 assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
4383 assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
4384 assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
4385 }
4386
4387 #[test]
4388 fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
4389 for byte in u8::MIN..=u8::MAX {
4390 assert_standard_decode_chunk_matches_input(&[byte]);
4391 }
4392
4393 for first in u8::MIN..=u8::MAX {
4394 for second in u8::MIN..=u8::MAX {
4395 assert_standard_decode_chunk_matches_input(&[first, second]);
4396 }
4397 }
4398 }
4399
4400 #[test]
4401 fn decode_chunk_bit_packing_matches_representative_full_quanta() {
4402 const SAMPLES: [u8; 16] = [
4403 0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
4404 ];
4405
4406 for first in SAMPLES {
4407 for second in SAMPLES {
4408 for third in SAMPLES {
4409 assert_standard_decode_chunk_matches_input(&[first, second, third]);
4410 }
4411 }
4412 }
4413 }
4414
4415 #[test]
4416 fn ct_padded_final_quantum_fails_closed_for_invalid_padding_count() {
4417 let (_, invalid_byte, invalid_padding, written) =
4418 ct_padded_final_quantum::<Standard>(*b"ABCD", 3);
4419
4420 assert_ne!(invalid_byte, 0);
4421 assert_ne!(invalid_padding, 0);
4422 assert_eq!(written, 0);
4423 assert_eq!(
4424 report_ct_error(invalid_byte, invalid_padding),
4425 Err(DecodeError::InvalidInput)
4426 );
4427 }
4428
4429 #[cfg(feature = "simd")]
4430 #[test]
4431 fn simd_dispatch_scaffold_keeps_scalar_active() {
4432 assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
4433 let _candidate = simd::detected_candidate();
4434 }
4435
4436 #[test]
4437 fn encodes_standard_vectors() {
4438 let vectors = [
4439 (&b""[..], &b""[..]),
4440 (&b"f"[..], &b"Zg=="[..]),
4441 (&b"fo"[..], &b"Zm8="[..]),
4442 (&b"foo"[..], &b"Zm9v"[..]),
4443 (&b"foob"[..], &b"Zm9vYg=="[..]),
4444 (&b"fooba"[..], &b"Zm9vYmE="[..]),
4445 (&b"foobar"[..], &b"Zm9vYmFy"[..]),
4446 ];
4447 for (input, expected) in vectors {
4448 let mut output = [0u8; 16];
4449 let written = STANDARD.encode_slice(input, &mut output).unwrap();
4450 assert_eq!(&output[..written], expected);
4451 }
4452 }
4453
4454 #[test]
4455 fn decodes_standard_vectors() {
4456 let vectors = [
4457 (&b""[..], &b""[..]),
4458 (&b"Zg=="[..], &b"f"[..]),
4459 (&b"Zm8="[..], &b"fo"[..]),
4460 (&b"Zm9v"[..], &b"foo"[..]),
4461 (&b"Zm9vYg=="[..], &b"foob"[..]),
4462 (&b"Zm9vYmE="[..], &b"fooba"[..]),
4463 (&b"Zm9vYmFy"[..], &b"foobar"[..]),
4464 ];
4465 for (input, expected) in vectors {
4466 let mut output = [0u8; 16];
4467 let written = STANDARD.decode_slice(input, &mut output).unwrap();
4468 assert_eq!(&output[..written], expected);
4469 }
4470 }
4471
4472 #[test]
4473 fn supports_unpadded_url_safe() {
4474 let mut encoded = [0u8; 16];
4475 let written = URL_SAFE_NO_PAD
4476 .encode_slice(b"\xfb\xff", &mut encoded)
4477 .unwrap();
4478 assert_eq!(&encoded[..written], b"-_8");
4479
4480 let mut decoded = [0u8; 2];
4481 let written = URL_SAFE_NO_PAD
4482 .decode_slice(&encoded[..written], &mut decoded)
4483 .unwrap();
4484 assert_eq!(&decoded[..written], b"\xfb\xff");
4485 }
4486
4487 #[test]
4488 fn decodes_in_place() {
4489 let mut buffer = *b"Zm9vYmFy";
4490 let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
4491 assert_eq!(decoded, b"foobar");
4492 }
4493
4494 #[test]
4495 fn rejects_non_canonical_padding_bits() {
4496 let mut output = [0u8; 4];
4497 assert_eq!(
4498 STANDARD.decode_slice(b"Zh==", &mut output),
4499 Err(DecodeError::InvalidPadding { index: 1 })
4500 );
4501 assert_eq!(
4502 STANDARD.decode_slice(b"Zm9=", &mut output),
4503 Err(DecodeError::InvalidPadding { index: 2 })
4504 );
4505 }
4506}