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 cleanup;
109mod profiles;
110
111pub use alphabet::{
112 Alphabet, AlphabetError, Bcrypt, Crypt, Standard, UrlSafe, decode_alphabet_byte,
113 validate_alphabet,
114};
115pub(crate) use alphabet::{encode_base64_value, encode_base64_value_runtime};
116pub(crate) use cleanup::{wipe_bytes, wipe_tail};
117#[cfg(feature = "alloc")]
118pub(crate) use cleanup::{wipe_vec_all, wipe_vec_spare_capacity};
119pub use profiles::{BCRYPT, CRYPT, MIME, PEM, PEM_CRLF, Profile};
120
121#[cfg(feature = "simd")]
122mod simd;
123
124pub mod runtime;
130
131#[cfg(feature = "stream")]
132pub mod stream;
133
134pub mod ct {
179 use super::{
180 Alphabet, DecodeError, DecodedBuffer, Standard, UrlSafe, ct_decode_in_place,
181 ct_decode_slice, ct_decode_slice_staged_clear_tail, ct_decoded_len, ct_validate_decode,
182 };
183 use core::marker::PhantomData;
184
185 pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
187
188 pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
190
191 pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
193
194 pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
196
197 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
210 pub struct CtEngine<A, const PAD: bool> {
211 alphabet: PhantomData<A>,
212 }
213
214 impl<A, const PAD: bool> CtEngine<A, PAD>
215 where
216 A: Alphabet,
217 {
218 #[must_use]
220 pub const fn new() -> Self {
221 Self {
222 alphabet: PhantomData,
223 }
224 }
225
226 #[must_use]
229 pub const fn is_padded(&self) -> bool {
230 PAD
231 }
232
233 pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
249 ct_validate_decode::<A, PAD>(input)
250 }
251
252 #[must_use]
266 pub fn validate(&self, input: &[u8]) -> bool {
267 self.validate_result(input).is_ok()
268 }
269
270 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
276 ct_decoded_len::<A, PAD>(input)
277 }
278
279 #[must_use = "handle decode errors; use decode_slice_staged_clear_tail for shared-memory or HSM-style threat models"]
313 pub fn decode_slice_clear_tail(
314 &self,
315 input: &[u8],
316 output: &mut [u8],
317 ) -> Result<usize, DecodeError> {
318 let written = match ct_decode_slice::<A, PAD>(input, output) {
319 Ok(written) => written,
320 Err(err) => {
321 crate::wipe_bytes(output);
322 return Err(err);
323 }
324 };
325 crate::wipe_tail(output, written);
326 Ok(written)
327 }
328
329 #[must_use = "handle decode errors; staged decode is for shared-memory or HSM-style threat models"]
343 pub fn decode_slice_staged_clear_tail(
344 &self,
345 input: &[u8],
346 output: &mut [u8],
347 staging: &mut [u8],
348 ) -> Result<usize, DecodeError> {
349 ct_decode_slice_staged_clear_tail::<A, PAD>(input, output, staging)
350 }
351
352 pub fn decode_buffer<const CAP: usize>(
368 &self,
369 input: &[u8],
370 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
371 let mut output = DecodedBuffer::new();
372 let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
373 Ok(written) => written,
374 Err(err) => {
375 output.clear();
376 return Err(err);
377 }
378 };
379 output.len = written;
380 Ok(output)
381 }
382
383 pub fn decode_in_place_clear_tail<'a>(
411 &self,
412 buffer: &'a mut [u8],
413 ) -> Result<&'a mut [u8], DecodeError> {
414 let len = match ct_decode_in_place::<A, PAD>(buffer) {
415 Ok(len) => len,
416 Err(err) => {
417 crate::wipe_bytes(buffer);
418 return Err(err);
419 }
420 };
421 crate::wipe_tail(buffer, len);
422 Ok(&mut buffer[..len])
423 }
424 }
425
426 impl<A, const PAD: bool> core::fmt::Display for CtEngine<A, PAD> {
427 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
428 write!(formatter, "ct padded={PAD}")
429 }
430 }
431}
432
433#[doc(alias = "ct")]
439#[doc(alias = "constant_time")]
440#[doc(alias = "sensitive")]
441pub const STANDARD: Engine<Standard, true> = Engine::new();
442
443#[doc(alias = "ct")]
450#[doc(alias = "constant_time")]
451#[doc(alias = "sensitive")]
452pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
453
454#[doc(alias = "ct")]
460#[doc(alias = "constant_time")]
461#[doc(alias = "sensitive")]
462pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
463
464#[doc(alias = "ct")]
471#[doc(alias = "constant_time")]
472#[doc(alias = "sensitive")]
473pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
474
475#[doc(alias = "ct")]
483#[doc(alias = "constant_time")]
484#[doc(alias = "sensitive")]
485pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
486
487#[doc(alias = "ct")]
495#[doc(alias = "constant_time")]
496#[doc(alias = "sensitive")]
497pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
498
499#[derive(Clone, Copy, Debug, Eq, PartialEq)]
501pub enum LineEnding {
502 Lf,
504 CrLf,
506}
507
508impl LineEnding {
509 #[must_use]
511 pub const fn name(self) -> &'static str {
512 match self {
513 Self::Lf => "LF",
514 Self::CrLf => "CRLF",
515 }
516 }
517
518 #[must_use]
520 pub const fn as_str(self) -> &'static str {
521 match self {
522 Self::Lf => "\n",
523 Self::CrLf => "\r\n",
524 }
525 }
526
527 #[must_use]
529 pub const fn as_bytes(self) -> &'static [u8] {
530 self.as_str().as_bytes()
531 }
532
533 #[must_use]
535 pub const fn byte_len(self) -> usize {
536 match self {
537 Self::Lf => 1,
538 Self::CrLf => 2,
539 }
540 }
541}
542
543impl core::fmt::Display for LineEnding {
544 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
545 formatter.write_str(self.name())
546 }
547}
548
549#[derive(Clone, Copy, Debug, Eq, PartialEq)]
555pub struct LineWrap {
556 pub line_len: usize,
558 pub line_ending: LineEnding,
560}
561
562impl LineWrap {
563 pub const MIME: Self = Self::new(76, LineEnding::CrLf);
565 pub const PEM: Self = Self::new(64, LineEnding::Lf);
567 pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
569
570 #[must_use]
585 pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
586 assert!(line_len != 0, "base64 line wrap length must be non-zero");
587 Self {
588 line_len,
589 line_ending,
590 }
591 }
592
593 #[must_use]
600 pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
601 if line_len == 0 {
602 None
603 } else {
604 Some(Self::new(line_len, line_ending))
605 }
606 }
607
608 #[must_use]
610 pub const fn line_len(self) -> usize {
611 self.line_len
612 }
613
614 #[must_use]
616 pub const fn line_ending(self) -> LineEnding {
617 self.line_ending
618 }
619
620 #[must_use]
622 pub const fn is_valid(self) -> bool {
623 self.line_len != 0
624 }
625}
626
627impl core::fmt::Display for LineWrap {
628 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
629 write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
630 }
631}
632
633pub struct EncodedBuffer<const CAP: usize> {
650 bytes: [u8; CAP],
651 len: usize,
652}
653
654pub struct ExposedEncodedArray<const CAP: usize> {
661 bytes: [u8; CAP],
662 len: usize,
663}
664
665impl<const CAP: usize> ExposedEncodedArray<CAP> {
666 #[must_use]
672 pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
673 assert!(len <= CAP, "visible length exceeds array capacity");
674 Self { bytes, len }
675 }
676
677 #[must_use]
679 pub fn as_bytes(&self) -> &[u8] {
680 &self.bytes[..self.len]
681 }
682
683 #[must_use]
685 pub const fn len(&self) -> usize {
686 self.len
687 }
688
689 #[must_use]
691 pub const fn is_empty(&self) -> bool {
692 self.len == 0
693 }
694
695 #[must_use]
697 pub const fn capacity(&self) -> usize {
698 CAP
699 }
700
701 #[must_use = "caller must zeroize the returned array"]
713 pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
714 let len = self.len;
715 self.len = 0;
716 (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
717 }
718}
719
720impl<const CAP: usize> Drop for ExposedEncodedArray<CAP> {
721 fn drop(&mut self) {
722 wipe_bytes(&mut self.bytes);
723 self.len = 0;
724 }
725}
726
727impl<const CAP: usize> core::fmt::Debug for ExposedEncodedArray<CAP> {
728 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
729 formatter
730 .debug_struct("ExposedEncodedArray")
731 .field("bytes", &"<redacted>")
732 .field("len", &self.len)
733 .field("capacity", &CAP)
734 .finish()
735 }
736}
737
738impl<const CAP: usize> EncodedBuffer<CAP> {
739 #[must_use]
741 pub const fn new() -> Self {
742 Self {
743 bytes: [0u8; CAP],
744 len: 0,
745 }
746 }
747
748 #[must_use]
750 pub const fn len(&self) -> usize {
751 self.len
752 }
753
754 #[must_use]
756 pub const fn is_empty(&self) -> bool {
757 self.len == 0
758 }
759
760 #[must_use]
762 pub const fn is_full(&self) -> bool {
763 self.len == CAP
764 }
765
766 #[must_use]
768 pub const fn capacity(&self) -> usize {
769 CAP
770 }
771
772 #[must_use]
774 pub const fn remaining_capacity(&self) -> usize {
775 CAP - self.len
776 }
777
778 #[must_use]
780 pub fn as_bytes(&self) -> &[u8] {
781 &self.bytes[..self.len]
782 }
783
784 pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
791 core::str::from_utf8(self.as_bytes())
792 }
793
794 #[must_use]
801 pub fn as_str(&self) -> &str {
802 match self.as_utf8() {
803 Ok(output) => output,
804 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
805 }
806 }
807
808 #[doc(alias = "constant_time_eq")]
827 #[must_use]
828 pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
829 constant_time_eq_public_len(self.as_bytes(), other)
830 }
831
832 #[must_use]
840 pub fn into_exposed_array(mut self) -> ExposedEncodedArray<CAP> {
841 let len = self.len;
842 self.len = 0;
843 ExposedEncodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
844 }
845
846 pub fn clear(&mut self) {
848 wipe_bytes(&mut self.bytes);
849 self.len = 0;
850 }
851
852 pub fn clear_tail(&mut self) {
854 wipe_tail(&mut self.bytes, self.len);
855 }
856}
857
858impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
859 fn as_ref(&self) -> &[u8] {
860 self.as_bytes()
861 }
862}
863
864impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
865 fn clone(&self) -> Self {
875 let mut output = Self::new();
876 output.bytes[..self.len].copy_from_slice(self.as_bytes());
877 output.len = self.len;
878 output
879 }
880}
881
882impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
883 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
884 formatter
885 .debug_struct("EncodedBuffer")
886 .field("bytes", &"<redacted>")
887 .field("len", &self.len)
888 .field("capacity", &CAP)
889 .finish()
890 }
891}
892
893impl<const CAP: usize> core::fmt::Display for EncodedBuffer<CAP> {
894 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
900 formatter.write_str(self.as_str())
901 }
902}
903
904impl<const CAP: usize> Default for EncodedBuffer<CAP> {
905 fn default() -> Self {
906 Self::new()
907 }
908}
909
910impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
911 fn drop(&mut self) {
912 self.clear();
913 }
914}
915
916impl<const CAP: usize> TryFrom<&[u8]> for EncodedBuffer<CAP> {
917 type Error = EncodeError;
918
919 fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
925 STANDARD.encode_buffer(input)
926 }
927}
928
929impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for EncodedBuffer<CAP> {
930 type Error = EncodeError;
931
932 fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
938 Self::try_from(&input[..])
939 }
940}
941
942impl<const CAP: usize> TryFrom<&str> for EncodedBuffer<CAP> {
943 type Error = EncodeError;
944
945 fn try_from(input: &str) -> Result<Self, Self::Error> {
952 Self::try_from(input.as_bytes())
953 }
954}
955
956pub struct DecodedBuffer<const CAP: usize> {
973 bytes: [u8; CAP],
974 len: usize,
975}
976
977pub struct ExposedDecodedArray<const CAP: usize> {
984 bytes: [u8; CAP],
985 len: usize,
986}
987
988impl<const CAP: usize> ExposedDecodedArray<CAP> {
989 #[must_use]
995 pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
996 assert!(len <= CAP, "visible length exceeds array capacity");
997 Self { bytes, len }
998 }
999
1000 #[must_use]
1002 pub fn as_bytes(&self) -> &[u8] {
1003 &self.bytes[..self.len]
1004 }
1005
1006 #[must_use]
1008 pub const fn len(&self) -> usize {
1009 self.len
1010 }
1011
1012 #[must_use]
1014 pub const fn is_empty(&self) -> bool {
1015 self.len == 0
1016 }
1017
1018 #[must_use]
1020 pub const fn capacity(&self) -> usize {
1021 CAP
1022 }
1023
1024 #[must_use = "caller must zeroize the returned array"]
1036 pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
1037 let len = self.len;
1038 self.len = 0;
1039 (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1040 }
1041}
1042
1043impl<const CAP: usize> Drop for ExposedDecodedArray<CAP> {
1044 fn drop(&mut self) {
1045 wipe_bytes(&mut self.bytes);
1046 self.len = 0;
1047 }
1048}
1049
1050impl<const CAP: usize> core::fmt::Debug for ExposedDecodedArray<CAP> {
1051 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1052 formatter
1053 .debug_struct("ExposedDecodedArray")
1054 .field("bytes", &"<redacted>")
1055 .field("len", &self.len)
1056 .field("capacity", &CAP)
1057 .finish()
1058 }
1059}
1060
1061impl<const CAP: usize> DecodedBuffer<CAP> {
1062 #[must_use]
1064 pub const fn new() -> Self {
1065 Self {
1066 bytes: [0u8; CAP],
1067 len: 0,
1068 }
1069 }
1070
1071 #[must_use]
1073 pub const fn len(&self) -> usize {
1074 self.len
1075 }
1076
1077 #[must_use]
1079 pub const fn is_empty(&self) -> bool {
1080 self.len == 0
1081 }
1082
1083 #[must_use]
1085 pub const fn is_full(&self) -> bool {
1086 self.len == CAP
1087 }
1088
1089 #[must_use]
1091 pub const fn capacity(&self) -> usize {
1092 CAP
1093 }
1094
1095 #[must_use]
1097 pub const fn remaining_capacity(&self) -> usize {
1098 CAP - self.len
1099 }
1100
1101 #[must_use]
1103 pub fn as_bytes(&self) -> &[u8] {
1104 &self.bytes[..self.len]
1105 }
1106
1107 pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1113 core::str::from_utf8(self.as_bytes())
1114 }
1115
1116 #[doc(alias = "constant_time_eq")]
1135 #[must_use]
1136 pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1137 constant_time_eq_public_len(self.as_bytes(), other)
1138 }
1139
1140 #[must_use]
1148 pub fn into_exposed_array(mut self) -> ExposedDecodedArray<CAP> {
1149 let len = self.len;
1150 self.len = 0;
1151 ExposedDecodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1152 }
1153
1154 pub fn clear(&mut self) {
1156 wipe_bytes(&mut self.bytes);
1157 self.len = 0;
1158 }
1159
1160 pub fn clear_tail(&mut self) {
1162 wipe_tail(&mut self.bytes, self.len);
1163 }
1164}
1165
1166impl<const CAP: usize> AsRef<[u8]> for DecodedBuffer<CAP> {
1167 fn as_ref(&self) -> &[u8] {
1168 self.as_bytes()
1169 }
1170}
1171
1172impl<const CAP: usize> Clone for DecodedBuffer<CAP> {
1173 fn clone(&self) -> Self {
1183 let mut output = Self::new();
1184 output.bytes[..self.len].copy_from_slice(self.as_bytes());
1185 output.len = self.len;
1186 output
1187 }
1188}
1189
1190impl<const CAP: usize> core::fmt::Debug for DecodedBuffer<CAP> {
1191 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1192 formatter
1193 .debug_struct("DecodedBuffer")
1194 .field("bytes", &"<redacted>")
1195 .field("len", &self.len)
1196 .field("capacity", &CAP)
1197 .finish()
1198 }
1199}
1200
1201impl<const CAP: usize> Default for DecodedBuffer<CAP> {
1202 fn default() -> Self {
1203 Self::new()
1204 }
1205}
1206
1207impl<const CAP: usize> Drop for DecodedBuffer<CAP> {
1208 fn drop(&mut self) {
1209 self.clear();
1210 }
1211}
1212
1213impl<const CAP: usize> TryFrom<&[u8]> for DecodedBuffer<CAP> {
1214 type Error = DecodeError;
1215
1216 fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1221 STANDARD.decode_buffer(input)
1222 }
1223}
1224
1225impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for DecodedBuffer<CAP> {
1226 type Error = DecodeError;
1227
1228 fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1234 Self::try_from(&input[..])
1235 }
1236}
1237
1238impl<const CAP: usize> TryFrom<&str> for DecodedBuffer<CAP> {
1239 type Error = DecodeError;
1240
1241 fn try_from(input: &str) -> Result<Self, Self::Error> {
1246 Self::try_from(input.as_bytes())
1247 }
1248}
1249
1250impl<const CAP: usize> core::str::FromStr for DecodedBuffer<CAP> {
1251 type Err = DecodeError;
1252
1253 fn from_str(input: &str) -> Result<Self, Self::Err> {
1258 Self::try_from(input)
1259 }
1260}
1261
1262#[cfg(feature = "alloc")]
1290pub struct SecretBuffer {
1291 bytes: alloc::vec::Vec<u8>,
1292}
1293
1294#[cfg(feature = "alloc")]
1302pub struct ExposedSecretVec {
1303 bytes: alloc::vec::Vec<u8>,
1304}
1305
1306#[cfg(feature = "alloc")]
1307impl ExposedSecretVec {
1308 #[must_use]
1310 pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
1311 wipe_vec_spare_capacity(&mut bytes);
1312 Self { bytes }
1313 }
1314
1315 #[must_use]
1317 pub fn len(&self) -> usize {
1318 self.bytes.len()
1319 }
1320
1321 #[must_use]
1323 pub fn is_empty(&self) -> bool {
1324 self.bytes.is_empty()
1325 }
1326
1327 #[must_use]
1332 pub fn expose_secret(&self) -> &[u8] {
1333 &self.bytes
1334 }
1335
1336 #[must_use]
1341 pub fn expose_secret_mut(&mut self) -> &mut [u8] {
1342 &mut self.bytes
1343 }
1344
1345 #[must_use = "caller must zeroize the returned Vec"]
1351 pub fn into_exposed_unprotected_vec_caller_must_zeroize(mut self) -> alloc::vec::Vec<u8> {
1352 core::mem::take(&mut self.bytes)
1353 }
1354}
1355
1356#[cfg(feature = "alloc")]
1357impl core::fmt::Debug for ExposedSecretVec {
1358 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1359 formatter
1360 .debug_struct("ExposedSecretVec")
1361 .field("bytes", &"<redacted>")
1362 .field("len", &self.len())
1363 .finish()
1364 }
1365}
1366
1367#[cfg(feature = "alloc")]
1368impl core::fmt::Display for ExposedSecretVec {
1369 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1370 formatter.write_str("<redacted>")
1371 }
1372}
1373
1374#[cfg(feature = "alloc")]
1375impl Drop for ExposedSecretVec {
1376 fn drop(&mut self) {
1377 wipe_vec_all(&mut self.bytes);
1378 }
1379}
1380
1381#[cfg(feature = "alloc")]
1382struct WipeVecGuard {
1383 bytes: alloc::vec::Vec<u8>,
1384}
1385
1386#[cfg(feature = "alloc")]
1387impl WipeVecGuard {
1388 fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
1389 Self { bytes }
1390 }
1391
1392 fn into_validated_secret_string(mut self) -> alloc::string::String {
1393 wipe_vec_spare_capacity(&mut self.bytes);
1394 let bytes = core::mem::take(&mut self.bytes);
1395 core::mem::forget(self);
1396 string_from_validated_secret_bytes(bytes)
1397 }
1398}
1399
1400#[cfg(feature = "alloc")]
1401impl Drop for WipeVecGuard {
1402 fn drop(&mut self) {
1403 wipe_vec_all(&mut self.bytes);
1404 }
1405}
1406
1407#[cfg(feature = "alloc")]
1408impl AsRef<[u8]> for ExposedSecretVec {
1409 fn as_ref(&self) -> &[u8] {
1410 self.expose_secret()
1411 }
1412}
1413
1414#[cfg(feature = "alloc")]
1415impl AsMut<[u8]> for ExposedSecretVec {
1416 fn as_mut(&mut self) -> &mut [u8] {
1417 self.expose_secret_mut()
1418 }
1419}
1420
1421#[cfg(feature = "alloc")]
1429pub struct ExposedSecretString {
1430 text: alloc::string::String,
1431}
1432
1433#[cfg(feature = "alloc")]
1434impl ExposedSecretString {
1435 #[must_use]
1437 pub fn from_string(text: alloc::string::String) -> Self {
1438 let mut bytes = text.into_bytes();
1439 wipe_vec_spare_capacity(&mut bytes);
1440 let text = string_from_validated_secret_bytes(bytes);
1441 Self { text }
1442 }
1443
1444 #[must_use]
1446 pub fn len(&self) -> usize {
1447 self.text.len()
1448 }
1449
1450 #[must_use]
1452 pub fn is_empty(&self) -> bool {
1453 self.text.is_empty()
1454 }
1455
1456 #[must_use]
1461 pub fn expose_secret(&self) -> &str {
1462 &self.text
1463 }
1464
1465 #[must_use]
1470 pub fn expose_secret_bytes(&self) -> &[u8] {
1471 self.text.as_bytes()
1472 }
1473
1474 #[must_use = "caller must zeroize the returned String"]
1480 pub fn into_exposed_unprotected_string_caller_must_zeroize(mut self) -> alloc::string::String {
1481 core::mem::take(&mut self.text)
1482 }
1483}
1484
1485#[cfg(feature = "alloc")]
1486impl core::fmt::Debug for ExposedSecretString {
1487 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1488 formatter
1489 .debug_struct("ExposedSecretString")
1490 .field("text", &"<redacted>")
1491 .field("len", &self.len())
1492 .finish()
1493 }
1494}
1495
1496#[cfg(feature = "alloc")]
1497impl core::fmt::Display for ExposedSecretString {
1498 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1499 formatter.write_str("<redacted>")
1500 }
1501}
1502
1503#[cfg(feature = "alloc")]
1504impl Drop for ExposedSecretString {
1505 fn drop(&mut self) {
1506 let mut bytes = core::mem::take(&mut self.text).into_bytes();
1507 wipe_vec_all(&mut bytes);
1508 }
1509}
1510
1511#[cfg(feature = "alloc")]
1512impl AsRef<str> for ExposedSecretString {
1513 fn as_ref(&self) -> &str {
1514 self.expose_secret()
1515 }
1516}
1517
1518#[cfg(feature = "alloc")]
1519impl SecretBuffer {
1520 #[must_use]
1522 pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
1523 wipe_vec_spare_capacity(&mut bytes);
1524 Self { bytes }
1525 }
1526
1527 #[must_use]
1529 pub fn from_slice(bytes: &[u8]) -> Self {
1530 Self::from_vec(bytes.to_vec())
1531 }
1532
1533 #[must_use]
1535 pub fn len(&self) -> usize {
1536 self.bytes.len()
1537 }
1538
1539 #[must_use]
1541 pub fn is_empty(&self) -> bool {
1542 self.bytes.is_empty()
1543 }
1544
1545 #[must_use]
1550 pub fn expose_secret(&self) -> &[u8] {
1551 &self.bytes
1552 }
1553
1554 pub fn expose_secret_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1560 core::str::from_utf8(self.expose_secret())
1561 }
1562
1563 #[must_use]
1568 pub fn expose_secret_mut(&mut self) -> &mut [u8] {
1569 &mut self.bytes
1570 }
1571
1572 #[must_use]
1578 pub fn into_exposed_vec(mut self) -> ExposedSecretVec {
1579 ExposedSecretVec::from_vec(core::mem::take(&mut self.bytes))
1580 }
1581
1582 #[must_use = "handle invalid UTF-8 errors and keep the returned wrapper protected"]
1591 pub fn try_into_exposed_string(self) -> Result<ExposedSecretString, Self> {
1592 if core::str::from_utf8(self.expose_secret()).is_err() {
1593 return Err(self);
1594 }
1595
1596 let mut exposed = self.into_exposed_vec();
1599 let guard = WipeVecGuard::from_vec(core::mem::take(&mut exposed.bytes));
1600 drop(exposed);
1601 Ok(ExposedSecretString::from_string(
1602 guard.into_validated_secret_string(),
1603 ))
1604 }
1605
1606 #[doc(alias = "constant_time_eq")]
1625 #[must_use]
1626 pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1627 constant_time_eq_public_len(self.expose_secret(), other)
1628 }
1629
1630 pub fn clear(&mut self) {
1632 wipe_vec_all(&mut self.bytes);
1633 self.bytes.clear();
1634 }
1635}
1636
1637#[cfg(feature = "alloc")]
1638impl core::fmt::Debug for SecretBuffer {
1639 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1640 formatter
1641 .debug_struct("SecretBuffer")
1642 .field("bytes", &"<redacted>")
1643 .field("len", &self.len())
1644 .finish()
1645 }
1646}
1647
1648#[cfg(feature = "alloc")]
1649impl core::fmt::Display for SecretBuffer {
1650 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1651 formatter.write_str("<redacted>")
1652 }
1653}
1654
1655#[cfg(feature = "alloc")]
1656impl Drop for SecretBuffer {
1657 fn drop(&mut self) {
1658 wipe_vec_all(&mut self.bytes);
1659 }
1660}
1661
1662#[cfg(feature = "alloc")]
1663impl From<alloc::vec::Vec<u8>> for SecretBuffer {
1664 fn from(bytes: alloc::vec::Vec<u8>) -> Self {
1669 Self::from_vec(bytes)
1670 }
1671}
1672
1673#[cfg(feature = "alloc")]
1674impl From<alloc::string::String> for SecretBuffer {
1675 fn from(text: alloc::string::String) -> Self {
1680 Self::from_vec(text.into_bytes())
1681 }
1682}
1683
1684#[cfg(feature = "alloc")]
1685impl<const CAP: usize> From<EncodedBuffer<CAP>> for SecretBuffer {
1686 fn from(buffer: EncodedBuffer<CAP>) -> Self {
1692 Self::from_slice(buffer.as_bytes())
1693 }
1694}
1695
1696#[cfg(feature = "alloc")]
1697impl<const CAP: usize> From<DecodedBuffer<CAP>> for SecretBuffer {
1698 fn from(buffer: DecodedBuffer<CAP>) -> Self {
1704 Self::from_slice(buffer.as_bytes())
1705 }
1706}
1707
1708#[cfg(feature = "alloc")]
1709impl TryFrom<&[u8]> for SecretBuffer {
1710 type Error = DecodeError;
1711
1712 fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1717 STANDARD.decode_secret(input)
1718 }
1719}
1720
1721#[cfg(feature = "alloc")]
1722impl<const N: usize> TryFrom<&[u8; N]> for SecretBuffer {
1723 type Error = DecodeError;
1724
1725 fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1731 Self::try_from(&input[..])
1732 }
1733}
1734
1735#[cfg(feature = "alloc")]
1736impl TryFrom<&str> for SecretBuffer {
1737 type Error = DecodeError;
1738
1739 fn try_from(input: &str) -> Result<Self, Self::Error> {
1744 Self::try_from(input.as_bytes())
1745 }
1746}
1747
1748#[cfg(feature = "alloc")]
1749impl core::str::FromStr for SecretBuffer {
1750 type Err = DecodeError;
1751
1752 fn from_str(input: &str) -> Result<Self, Self::Err> {
1757 Self::try_from(input)
1758 }
1759}
1760
1761pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
1776 match checked_encoded_len(input_len, padded) {
1777 Some(len) => Ok(len),
1778 None => Err(EncodeError::LengthOverflow),
1779 }
1780}
1781
1782pub const fn wrapped_encoded_len(
1796 input_len: usize,
1797 padded: bool,
1798 wrap: LineWrap,
1799) -> Result<usize, EncodeError> {
1800 if wrap.line_len == 0 {
1801 return Err(EncodeError::InvalidLineWrap { line_len: 0 });
1802 }
1803
1804 let Some(encoded) = checked_encoded_len(input_len, padded) else {
1805 return Err(EncodeError::LengthOverflow);
1806 };
1807 if encoded == 0 {
1808 return Ok(0);
1809 }
1810
1811 let breaks = (encoded - 1) / wrap.line_len;
1812 let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
1813 return Err(EncodeError::LengthOverflow);
1814 };
1815 match encoded.checked_add(line_ending_bytes) {
1816 Some(len) => Ok(len),
1817 None => Err(EncodeError::LengthOverflow),
1818 }
1819}
1820
1821#[must_use]
1837pub const fn checked_wrapped_encoded_len(
1838 input_len: usize,
1839 padded: bool,
1840 wrap: LineWrap,
1841) -> Option<usize> {
1842 if wrap.line_len == 0 {
1843 return None;
1844 }
1845
1846 let Some(encoded) = checked_encoded_len(input_len, padded) else {
1847 return None;
1848 };
1849 if encoded == 0 {
1850 return Some(0);
1851 }
1852
1853 let breaks = (encoded - 1) / wrap.line_len;
1854 let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
1855 return None;
1856 };
1857 encoded.checked_add(line_ending_bytes)
1858}
1859
1860#[must_use]
1871pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
1872 let groups = input_len / 3;
1873 if groups > usize::MAX / 4 {
1874 return None;
1875 }
1876 let full = groups * 4;
1877 let rem = input_len % 3;
1878 if rem == 0 {
1879 Some(full)
1880 } else if padded {
1881 full.checked_add(4)
1882 } else {
1883 full.checked_add(rem + 1)
1884 }
1885}
1886
1887#[must_use]
1905pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
1906 constant_time_eq_fixed_width_array(left, right)
1907}
1908
1909#[must_use]
1920pub const fn decoded_capacity(encoded_len: usize) -> usize {
1921 let rem = encoded_len % 4;
1922 encoded_len / 4 * 3
1923 + if rem == 2 {
1924 1
1925 } else if rem == 3 {
1926 2
1927 } else {
1928 0
1929 }
1930}
1931
1932pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
1946 if padded {
1947 decoded_len_padded(input)
1948 } else {
1949 decoded_len_unpadded(input)
1950 }
1951}
1952
1953#[inline]
1954pub(crate) const fn ct_mask_bit(bit: u8) -> u8 {
1955 0u8.wrapping_sub(bit & 1)
1956}
1957
1958#[inline]
1959pub(crate) const fn ct_mask_nonzero_u8(value: u8) -> u8 {
1960 let wide = value as u16;
1961 let negative = 0u16.wrapping_sub(wide);
1962 let nonzero = ((wide | negative) >> 8) as u8;
1963 ct_mask_bit(nonzero)
1964}
1965
1966#[inline]
1967pub(crate) const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
1968 !ct_mask_nonzero_u8(left ^ right)
1969}
1970
1971#[inline]
1972pub(crate) const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
1973 let diff = (left as u16).wrapping_sub(right as u16);
1974 ct_mask_bit((diff >> 8) as u8)
1975}
1976
1977#[inline(never)]
1978fn constant_time_eq_public_len(left: &[u8], right: &[u8]) -> bool {
1979 if left.len() != right.len() {
1980 return false;
1981 }
1982
1983 constant_time_eq_same_len(left, right)
1984}
1985
1986#[inline(never)]
1987fn constant_time_eq_fixed_width_array<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
1988 constant_time_eq_same_len(left, right)
1989}
1990
1991#[inline(never)]
1992#[allow(unsafe_code)]
1993fn constant_time_eq_same_len(left: &[u8], right: &[u8]) -> bool {
1994 let mut diff = 0u8;
1995 for (left, right) in left.iter().zip(right) {
1996 diff = core::hint::black_box(
1997 core::hint::black_box(diff) | core::hint::black_box(*left ^ *right),
1998 );
1999 diff = unsafe { core::ptr::read_volatile(&raw const diff) };
2003 }
2004 ct_error_gate_barrier(diff, 0);
2005 let result = unsafe { core::ptr::read_volatile(&raw const diff) };
2009 result == 0
2010}
2011
2012#[cfg(feature = "alloc")]
2013fn string_from_validated_secret_bytes(bytes: alloc::vec::Vec<u8>) -> alloc::string::String {
2014 match alloc::string::String::from_utf8(bytes) {
2015 Ok(string) => string,
2016 Err(error) => {
2017 let mut bytes = error.into_bytes();
2018 wipe_vec_all(&mut bytes);
2019 alloc::string::String::new()
2020 }
2021 }
2022}
2023
2024mod backend {
2025 use super::{
2026 Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
2027 encode_base64_value_runtime,
2028 };
2029
2030 pub(super) fn encode_slice<A, const PAD: bool>(
2031 input: &[u8],
2032 output: &mut [u8],
2033 ) -> Result<usize, EncodeError>
2034 where
2035 A: Alphabet,
2036 {
2037 #[cfg(feature = "simd")]
2038 match super::simd::active_backend() {
2039 super::simd::ActiveBackend::Scalar => {}
2040 }
2041
2042 scalar_encode_slice::<A, PAD>(input, output)
2043 }
2044
2045 pub(super) fn decode_slice<A, const PAD: bool>(
2046 input: &[u8],
2047 output: &mut [u8],
2048 ) -> Result<usize, DecodeError>
2049 where
2050 A: Alphabet,
2051 {
2052 #[cfg(feature = "simd")]
2053 match super::simd::active_backend() {
2054 super::simd::ActiveBackend::Scalar => {}
2055 }
2056
2057 scalar_decode_slice::<A, PAD>(input, output)
2058 }
2059
2060 #[cfg(test)]
2061 pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
2062 input: &[u8],
2063 output: &mut [u8],
2064 ) -> Result<usize, EncodeError>
2065 where
2066 A: Alphabet,
2067 {
2068 scalar_encode_slice::<A, PAD>(input, output)
2069 }
2070
2071 #[cfg(test)]
2072 pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
2073 input: &[u8],
2074 output: &mut [u8],
2075 ) -> Result<usize, DecodeError>
2076 where
2077 A: Alphabet,
2078 {
2079 scalar_decode_slice::<A, PAD>(input, output)
2080 }
2081
2082 fn scalar_encode_slice<A, const PAD: bool>(
2083 input: &[u8],
2084 output: &mut [u8],
2085 ) -> Result<usize, EncodeError>
2086 where
2087 A: Alphabet,
2088 {
2089 let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2090 if output.len() < required {
2091 return Err(EncodeError::OutputTooSmall {
2092 required,
2093 available: output.len(),
2094 });
2095 }
2096
2097 let mut read = 0;
2098 let mut write = 0;
2099 while read + 3 <= input.len() {
2100 let b0 = input[read];
2101 let b1 = input[read + 1];
2102 let b2 = input[read + 2];
2103
2104 output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2105 output[write + 1] =
2106 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2107 output[write + 2] =
2108 encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2109 output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
2110
2111 read += 3;
2112 write += 4;
2113 }
2114
2115 match input.len() - read {
2116 0 => {}
2117 1 => {
2118 let b0 = input[read];
2119 output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2120 output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
2121 write += 2;
2122 if PAD {
2123 output[write] = b'=';
2124 output[write + 1] = b'=';
2125 write += 2;
2126 }
2127 }
2128 2 => {
2129 let b0 = input[read];
2130 let b1 = input[read + 1];
2131 output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2132 output[write + 1] =
2133 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2134 output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
2135 write += 3;
2136 if PAD {
2137 output[write] = b'=';
2138 write += 1;
2139 }
2140 }
2141 _ => unreachable!(),
2142 }
2143
2144 Ok(write)
2145 }
2146
2147 fn scalar_decode_slice<A, const PAD: bool>(
2148 input: &[u8],
2149 output: &mut [u8],
2150 ) -> Result<usize, DecodeError>
2151 where
2152 A: Alphabet,
2153 {
2154 if input.is_empty() {
2155 return Ok(0);
2156 }
2157
2158 if PAD {
2159 decode_padded::<A>(input, output)
2160 } else {
2161 decode_unpadded::<A>(input, output)
2162 }
2163 }
2164}
2165
2166pub struct Engine<A, const PAD: bool> {
2168 alphabet: core::marker::PhantomData<A>,
2169}
2170
2171impl<A, const PAD: bool> Clone for Engine<A, PAD> {
2172 fn clone(&self) -> Self {
2173 *self
2174 }
2175}
2176
2177impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
2178
2179impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
2180 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2181 formatter
2182 .debug_struct("Engine")
2183 .field("padded", &PAD)
2184 .finish()
2185 }
2186}
2187
2188impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
2189 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2190 write!(formatter, "padded={PAD}")
2191 }
2192}
2193
2194impl<A, const PAD: bool> Default for Engine<A, PAD> {
2195 fn default() -> Self {
2196 Self {
2197 alphabet: core::marker::PhantomData,
2198 }
2199 }
2200}
2201
2202impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
2203
2204impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
2205 fn eq(&self, _other: &Self) -> bool {
2206 true
2207 }
2208}
2209
2210impl<A, const PAD: bool> Engine<A, PAD>
2211where
2212 A: Alphabet,
2213{
2214 #[must_use]
2216 pub const fn new() -> Self {
2217 Self {
2218 alphabet: core::marker::PhantomData,
2219 }
2220 }
2221
2222 #[must_use]
2224 pub const fn is_padded(&self) -> bool {
2225 PAD
2226 }
2227
2228 #[must_use]
2233 pub const fn profile(&self) -> Profile<A, PAD> {
2234 Profile::new(*self, None)
2235 }
2236
2237 #[must_use]
2243 pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
2244 ct::CtEngine::new()
2245 }
2246
2247 #[cfg(feature = "stream")]
2261 #[must_use]
2262 pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
2263 stream::Encoder::new(inner, *self)
2264 }
2265
2266 #[cfg(feature = "stream")]
2286 #[must_use]
2287 pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
2288 stream::Decoder::new(inner, *self)
2289 }
2290
2291 #[cfg(feature = "stream")]
2306 #[must_use]
2307 pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
2308 stream::EncoderReader::new(inner, *self)
2309 }
2310
2311 #[cfg(feature = "stream")]
2332 #[must_use]
2333 pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
2334 stream::DecoderReader::new(inner, *self)
2335 }
2336
2337 pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
2339 encoded_len(input_len, PAD)
2340 }
2341
2342 #[must_use]
2344 pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
2345 checked_encoded_len(input_len, PAD)
2346 }
2347
2348 pub const fn wrapped_encoded_len(
2353 &self,
2354 input_len: usize,
2355 wrap: LineWrap,
2356 ) -> Result<usize, EncodeError> {
2357 wrapped_encoded_len(input_len, PAD, wrap)
2358 }
2359
2360 #[must_use]
2363 pub const fn checked_wrapped_encoded_len(
2364 &self,
2365 input_len: usize,
2366 wrap: LineWrap,
2367 ) -> Option<usize> {
2368 checked_wrapped_encoded_len(input_len, PAD, wrap)
2369 }
2370
2371 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
2376 decoded_len(input, PAD)
2377 }
2378
2379 pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
2385 validate_legacy_decode::<A, PAD>(input)
2386 }
2387
2388 pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
2395 validate_wrapped_decode::<A, PAD>(input, wrap)
2396 }
2397
2398 pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2416 validate_decode::<A, PAD>(input).map(|_| ())
2417 }
2418
2419 #[must_use]
2435 pub fn validate(&self, input: &[u8]) -> bool {
2436 self.validate_result(input).is_ok()
2437 }
2438
2439 pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2454 validate_legacy_decode::<A, PAD>(input).map(|_| ())
2455 }
2456
2457 #[must_use]
2471 pub fn validate_legacy(&self, input: &[u8]) -> bool {
2472 self.validate_legacy_result(input).is_ok()
2473 }
2474
2475 pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
2491 validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
2492 }
2493
2494 #[must_use]
2508 pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
2509 self.validate_wrapped_result(input, wrap).is_ok()
2510 }
2511
2512 #[must_use]
2544 pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
2545 &self,
2546 input: &[u8; INPUT_LEN],
2547 ) -> [u8; OUTPUT_LEN] {
2548 let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
2549 panic!("encoded base64 length overflows usize");
2550 };
2551 assert!(
2552 required == OUTPUT_LEN,
2553 "base64 output array has incorrect length"
2554 );
2555
2556 let mut output = [0u8; OUTPUT_LEN];
2557 let mut read = 0;
2558 let mut write = 0;
2559 while INPUT_LEN - read >= 3 {
2560 let b0 = input[read];
2561 let b1 = input[read + 1];
2562 let b2 = input[read + 2];
2563
2564 output[write] = encode_base64_value::<A>(b0 >> 2);
2565 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2566 output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2567 output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
2568
2569 read += 3;
2570 write += 4;
2571 }
2572
2573 match INPUT_LEN - read {
2574 0 => {}
2575 1 => {
2576 let b0 = input[read];
2577 output[write] = encode_base64_value::<A>(b0 >> 2);
2578 output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
2579 write += 2;
2580 if PAD {
2581 output[write] = b'=';
2582 output[write + 1] = b'=';
2583 }
2584 }
2585 2 => {
2586 let b0 = input[read];
2587 let b1 = input[read + 1];
2588 output[write] = encode_base64_value::<A>(b0 >> 2);
2589 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2590 output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
2591 if PAD {
2592 output[write + 3] = b'=';
2593 }
2594 }
2595 _ => unreachable!(),
2596 }
2597
2598 output
2599 }
2600
2601 pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
2603 backend::encode_slice::<A, PAD>(input, output)
2604 }
2605
2606 pub fn encode_slice_wrapped(
2625 &self,
2626 input: &[u8],
2627 output: &mut [u8],
2628 wrap: LineWrap,
2629 ) -> Result<usize, EncodeError> {
2630 let required = self.wrapped_encoded_len(input.len(), wrap)?;
2631 if output.len() < required {
2632 return Err(EncodeError::OutputTooSmall {
2633 required,
2634 available: output.len(),
2635 });
2636 }
2637
2638 let encoded_len =
2639 checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2640 if encoded_len == 0 {
2641 return Ok(0);
2642 }
2643
2644 let combined_required = match required.checked_add(encoded_len) {
2647 Some(len) => len,
2648 None => usize::MAX,
2649 };
2650 if output.len() < combined_required {
2651 let mut scratch = [0u8; 1024];
2652 let mut input_offset = 0;
2653 let mut output_offset = 0;
2654 let mut column = 0;
2655
2656 while input_offset < input.len() {
2657 let remaining = input.len() - input_offset;
2658 let mut take = remaining.min(768);
2659 if remaining > take {
2660 take -= take % 3;
2661 }
2662 if take == 0 {
2663 take = remaining;
2664 }
2665
2666 let encoded = match self
2667 .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
2668 {
2669 Ok(encoded) => encoded,
2670 Err(err) => {
2671 wipe_bytes(&mut scratch);
2672 return Err(err);
2673 }
2674 };
2675 if let Err(err) = write_wrapped_bytes(
2676 &scratch[..encoded],
2677 output,
2678 &mut output_offset,
2679 &mut column,
2680 wrap,
2681 ) {
2682 wipe_bytes(&mut scratch);
2683 return Err(err);
2684 }
2685 wipe_bytes(&mut scratch[..encoded]);
2686 input_offset += take;
2687 }
2688
2689 Ok(output_offset)
2690 } else {
2691 let encoded =
2692 self.encode_slice(input, &mut output[required..required + encoded_len])?;
2693 let mut output_offset = 0;
2694 let mut column = 0;
2695 let mut read = required;
2696 while read < required + encoded {
2697 let byte = output[read];
2698 write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
2699 read += 1;
2700 }
2701 wipe_bytes(&mut output[required..required + encoded]);
2702 Ok(output_offset)
2703 }
2704 }
2705
2706 pub fn encode_slice_wrapped_clear_tail(
2712 &self,
2713 input: &[u8],
2714 output: &mut [u8],
2715 wrap: LineWrap,
2716 ) -> Result<usize, EncodeError> {
2717 let written = match self.encode_slice_wrapped(input, output, wrap) {
2718 Ok(written) => written,
2719 Err(err) => {
2720 wipe_bytes(output);
2721 return Err(err);
2722 }
2723 };
2724 wipe_tail(output, written);
2725 Ok(written)
2726 }
2727
2728 pub fn encode_wrapped_buffer<const CAP: usize>(
2734 &self,
2735 input: &[u8],
2736 wrap: LineWrap,
2737 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
2738 let mut output = EncodedBuffer::new();
2739 let written = match self.encode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
2740 Ok(written) => written,
2741 Err(err) => {
2742 output.clear();
2743 return Err(err);
2744 }
2745 };
2746 output.len = written;
2747 Ok(output)
2748 }
2749
2750 #[cfg(feature = "alloc")]
2752 #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
2753 pub fn encode_wrapped_vec(
2754 &self,
2755 input: &[u8],
2756 wrap: LineWrap,
2757 ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
2758 let required = self.wrapped_encoded_len(input.len(), wrap)?;
2759 let mut output = alloc::vec![0; required];
2760 let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
2761 output.truncate(written);
2762 Ok(output)
2763 }
2764
2765 #[cfg(feature = "alloc")]
2767 pub fn encode_wrapped_string(
2768 &self,
2769 input: &[u8],
2770 wrap: LineWrap,
2771 ) -> Result<alloc::string::String, EncodeError> {
2772 let output = self.encode_wrapped_vec(input, wrap)?;
2773 match alloc::string::String::from_utf8(output) {
2774 Ok(output) => Ok(output),
2775 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
2776 }
2777 }
2778
2779 #[cfg(feature = "alloc")]
2784 pub fn encode_wrapped_secret(
2785 &self,
2786 input: &[u8],
2787 wrap: LineWrap,
2788 ) -> Result<SecretBuffer, EncodeError> {
2789 self.encode_wrapped_vec(input, wrap)
2790 .map(SecretBuffer::from_vec)
2791 }
2792
2793 pub fn encode_slice_clear_tail(
2813 &self,
2814 input: &[u8],
2815 output: &mut [u8],
2816 ) -> Result<usize, EncodeError> {
2817 let written = match self.encode_slice(input, output) {
2818 Ok(written) => written,
2819 Err(err) => {
2820 wipe_bytes(output);
2821 return Err(err);
2822 }
2823 };
2824 wipe_tail(output, written);
2825 Ok(written)
2826 }
2827
2828 pub fn encode_buffer<const CAP: usize>(
2843 &self,
2844 input: &[u8],
2845 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
2846 let mut output = EncodedBuffer::new();
2847 let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
2848 Ok(written) => written,
2849 Err(err) => {
2850 output.clear();
2851 return Err(err);
2852 }
2853 };
2854 output.len = written;
2855 Ok(output)
2856 }
2857
2858 #[cfg(feature = "alloc")]
2860 #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
2861 pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
2862 let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
2863 let mut output = alloc::vec![0; required];
2864 let written = self.encode_slice(input, &mut output)?;
2865 output.truncate(written);
2866 Ok(output)
2867 }
2868
2869 #[cfg(feature = "alloc")]
2874 pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
2875 self.encode_vec(input).map(SecretBuffer::from_vec)
2876 }
2877
2878 #[cfg(feature = "alloc")]
2893 pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
2894 let output = self.encode_vec(input)?;
2895 match alloc::string::String::from_utf8(output) {
2896 Ok(output) => Ok(output),
2897 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
2898 }
2899 }
2900
2901 pub fn encode_in_place<'a>(
2918 &self,
2919 buffer: &'a mut [u8],
2920 input_len: usize,
2921 ) -> Result<&'a mut [u8], EncodeError> {
2922 if input_len > buffer.len() {
2923 return Err(EncodeError::InputTooLarge {
2924 input_len,
2925 buffer_len: buffer.len(),
2926 });
2927 }
2928
2929 let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
2930 if buffer.len() < required {
2931 return Err(EncodeError::OutputTooSmall {
2932 required,
2933 available: buffer.len(),
2934 });
2935 }
2936
2937 let mut read = input_len;
2938 let mut write = required;
2939
2940 match input_len % 3 {
2941 0 => {}
2942 1 => {
2943 read -= 1;
2944 let b0 = buffer[read];
2945 if PAD {
2946 write -= 4;
2947 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2948 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
2949 buffer[write + 2] = b'=';
2950 buffer[write + 3] = b'=';
2951 } else {
2952 write -= 2;
2953 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2954 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
2955 }
2956 }
2957 2 => {
2958 read -= 2;
2959 let b0 = buffer[read];
2960 let b1 = buffer[read + 1];
2961 if PAD {
2962 write -= 4;
2963 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2964 buffer[write + 1] =
2965 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2966 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
2967 buffer[write + 3] = b'=';
2968 } else {
2969 write -= 3;
2970 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2971 buffer[write + 1] =
2972 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2973 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
2974 }
2975 }
2976 _ => unreachable!(),
2977 }
2978
2979 while read > 0 {
2980 read -= 3;
2981 write -= 4;
2982 let b0 = buffer[read];
2983 let b1 = buffer[read + 1];
2984 let b2 = buffer[read + 2];
2985
2986 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
2987 buffer[write + 1] =
2988 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
2989 buffer[write + 2] =
2990 encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
2991 buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
2992 }
2993
2994 debug_assert_eq!(write, 0);
2998 Ok(&mut buffer[..required])
2999 }
3000
3001 pub fn encode_in_place_clear_tail<'a>(
3019 &self,
3020 buffer: &'a mut [u8],
3021 input_len: usize,
3022 ) -> Result<&'a mut [u8], EncodeError> {
3023 let len = match self.encode_in_place(buffer, input_len) {
3024 Ok(encoded) => encoded.len(),
3025 Err(err) => {
3026 wipe_bytes(buffer);
3027 return Err(err);
3028 }
3029 };
3030 wipe_tail(buffer, len);
3031 Ok(&mut buffer[..len])
3032 }
3033
3034 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
3052 pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
3053 backend::decode_slice::<A, PAD>(input, output)
3054 }
3055
3056 pub fn decode_slice_clear_tail(
3076 &self,
3077 input: &[u8],
3078 output: &mut [u8],
3079 ) -> Result<usize, DecodeError> {
3080 let written = match self.decode_slice(input, output) {
3081 Ok(written) => written,
3082 Err(err) => {
3083 wipe_bytes(output);
3084 return Err(err);
3085 }
3086 };
3087 wipe_tail(output, written);
3088 Ok(written)
3089 }
3090
3091 pub fn decode_buffer<const CAP: usize>(
3106 &self,
3107 input: &[u8],
3108 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
3109 let mut output = DecodedBuffer::new();
3110 let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
3111 Ok(written) => written,
3112 Err(err) => {
3113 output.clear();
3114 return Err(err);
3115 }
3116 };
3117 output.len = written;
3118 Ok(output)
3119 }
3120
3121 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
3134 pub fn decode_slice_legacy(
3135 &self,
3136 input: &[u8],
3137 output: &mut [u8],
3138 ) -> Result<usize, DecodeError> {
3139 let required = validate_legacy_decode::<A, PAD>(input)?;
3140 if output.len() < required {
3141 return Err(DecodeError::OutputTooSmall {
3142 required,
3143 available: output.len(),
3144 });
3145 }
3146 decode_legacy_to_slice::<A, PAD>(input, output)
3147 }
3148
3149 pub fn decode_slice_legacy_clear_tail(
3169 &self,
3170 input: &[u8],
3171 output: &mut [u8],
3172 ) -> Result<usize, DecodeError> {
3173 let written = match self.decode_slice_legacy(input, output) {
3174 Ok(written) => written,
3175 Err(err) => {
3176 wipe_bytes(output);
3177 return Err(err);
3178 }
3179 };
3180 wipe_tail(output, written);
3181 Ok(written)
3182 }
3183
3184 pub fn decode_buffer_legacy<const CAP: usize>(
3192 &self,
3193 input: &[u8],
3194 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
3195 let mut output = DecodedBuffer::new();
3196 let written = match self.decode_slice_legacy_clear_tail(input, &mut output.bytes) {
3197 Ok(written) => written,
3198 Err(err) => {
3199 output.clear();
3200 return Err(err);
3201 }
3202 };
3203 output.len = written;
3204 Ok(output)
3205 }
3206
3207 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
3221 pub fn decode_slice_wrapped(
3222 &self,
3223 input: &[u8],
3224 output: &mut [u8],
3225 wrap: LineWrap,
3226 ) -> Result<usize, DecodeError> {
3227 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
3228 if output.len() < required {
3229 return Err(DecodeError::OutputTooSmall {
3230 required,
3231 available: output.len(),
3232 });
3233 }
3234 decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
3235 }
3236
3237 pub fn decode_slice_wrapped_clear_tail(
3243 &self,
3244 input: &[u8],
3245 output: &mut [u8],
3246 wrap: LineWrap,
3247 ) -> Result<usize, DecodeError> {
3248 let written = match self.decode_slice_wrapped(input, output, wrap) {
3249 Ok(written) => written,
3250 Err(err) => {
3251 wipe_bytes(output);
3252 return Err(err);
3253 }
3254 };
3255 wipe_tail(output, written);
3256 Ok(written)
3257 }
3258
3259 pub fn decode_wrapped_buffer<const CAP: usize>(
3268 &self,
3269 input: &[u8],
3270 wrap: LineWrap,
3271 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
3272 let mut output = DecodedBuffer::new();
3273 let written = match self.decode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
3274 Ok(written) => written,
3275 Err(err) => {
3276 output.clear();
3277 return Err(err);
3278 }
3279 };
3280 output.len = written;
3281 Ok(output)
3282 }
3283
3284 #[cfg(feature = "alloc")]
3288 #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
3289 pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3290 let required = validate_decode::<A, PAD>(input)?;
3291 let mut output = alloc::vec![0; required];
3292 let written = match self.decode_slice(input, &mut output) {
3293 Ok(written) => written,
3294 Err(err) => {
3295 wipe_bytes(&mut output);
3296 return Err(err);
3297 }
3298 };
3299 output.truncate(written);
3300 Ok(output)
3301 }
3302
3303 #[cfg(feature = "alloc")]
3308 pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
3309 self.decode_vec(input).map(SecretBuffer::from_vec)
3310 }
3311
3312 #[cfg(feature = "alloc")]
3315 #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
3316 pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3317 let required = validate_legacy_decode::<A, PAD>(input)?;
3318 let mut output = alloc::vec![0; required];
3319 let written = match self.decode_slice_legacy(input, &mut output) {
3320 Ok(written) => written,
3321 Err(err) => {
3322 wipe_bytes(&mut output);
3323 return Err(err);
3324 }
3325 };
3326 output.truncate(written);
3327 Ok(output)
3328 }
3329
3330 #[cfg(feature = "alloc")]
3337 pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
3338 self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
3339 }
3340
3341 #[cfg(feature = "alloc")]
3343 #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
3344 pub fn decode_wrapped_vec(
3345 &self,
3346 input: &[u8],
3347 wrap: LineWrap,
3348 ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
3349 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
3350 let mut output = alloc::vec![0; required];
3351 let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
3352 Ok(written) => written,
3353 Err(err) => {
3354 wipe_bytes(&mut output);
3355 return Err(err);
3356 }
3357 };
3358 output.truncate(written);
3359 Ok(output)
3360 }
3361
3362 #[cfg(feature = "alloc")]
3369 pub fn decode_wrapped_secret(
3370 &self,
3371 input: &[u8],
3372 wrap: LineWrap,
3373 ) -> Result<SecretBuffer, DecodeError> {
3374 self.decode_wrapped_vec(input, wrap)
3375 .map(SecretBuffer::from_vec)
3376 }
3377
3378 pub fn decode_in_place_wrapped<'a>(
3409 &self,
3410 buffer: &'a mut [u8],
3411 wrap: LineWrap,
3412 ) -> Result<&'a mut [u8], DecodeError> {
3413 let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
3414 let compacted = compact_wrapped_input(buffer, wrap)?;
3415 let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
3416 Ok(&mut buffer[..len])
3417 }
3418
3419 pub fn decode_in_place_wrapped_clear_tail<'a>(
3440 &self,
3441 buffer: &'a mut [u8],
3442 wrap: LineWrap,
3443 ) -> Result<&'a mut [u8], DecodeError> {
3444 if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
3445 wipe_bytes(buffer);
3446 return Err(err);
3447 }
3448
3449 let compacted = match compact_wrapped_input(buffer, wrap) {
3450 Ok(compacted) => compacted,
3451 Err(err) => {
3452 wipe_bytes(buffer);
3453 return Err(err);
3454 }
3455 };
3456
3457 let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
3458 Ok(len) => len,
3459 Err(err) => {
3460 wipe_bytes(buffer);
3461 return Err(err);
3462 }
3463 };
3464 wipe_tail(buffer, len);
3465 Ok(&mut buffer[..len])
3466 }
3467
3468 pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
3493 let len = Self::decode_slice_to_start(buffer)?;
3494 Ok(&mut buffer[..len])
3495 }
3496
3497 pub fn decode_in_place_clear_tail<'a>(
3514 &self,
3515 buffer: &'a mut [u8],
3516 ) -> Result<&'a mut [u8], DecodeError> {
3517 let len = match Self::decode_slice_to_start(buffer) {
3518 Ok(len) => len,
3519 Err(err) => {
3520 wipe_bytes(buffer);
3521 return Err(err);
3522 }
3523 };
3524 wipe_tail(buffer, len);
3525 Ok(&mut buffer[..len])
3526 }
3527
3528 pub fn decode_in_place_legacy<'a>(
3536 &self,
3537 buffer: &'a mut [u8],
3538 ) -> Result<&'a mut [u8], DecodeError> {
3539 let _required = validate_legacy_decode::<A, PAD>(buffer)?;
3540 let mut write = 0;
3541 let mut read = 0;
3542 while read < buffer.len() {
3543 let byte = buffer[read];
3544 if !is_legacy_whitespace(byte) {
3545 buffer[write] = byte;
3546 write += 1;
3547 }
3548 read += 1;
3549 }
3550 let len = Self::decode_slice_to_start(&mut buffer[..write])?;
3551 Ok(&mut buffer[..len])
3552 }
3553
3554 pub fn decode_in_place_legacy_clear_tail<'a>(
3560 &self,
3561 buffer: &'a mut [u8],
3562 ) -> Result<&'a mut [u8], DecodeError> {
3563 if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
3564 wipe_bytes(buffer);
3565 return Err(err);
3566 }
3567
3568 let mut write = 0;
3569 let mut read = 0;
3570 while read < buffer.len() {
3571 let byte = buffer[read];
3572 if !is_legacy_whitespace(byte) {
3573 buffer[write] = byte;
3574 write += 1;
3575 }
3576 read += 1;
3577 }
3578
3579 let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
3580 Ok(len) => len,
3581 Err(err) => {
3582 wipe_bytes(buffer);
3583 return Err(err);
3584 }
3585 };
3586 wipe_tail(buffer, len);
3587 Ok(&mut buffer[..len])
3588 }
3589
3590 fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
3591 let _required = validate_decode::<A, PAD>(buffer)?;
3592 let input_len = buffer.len();
3593 let mut read = 0;
3594 let mut write = 0;
3595 while read + 4 <= input_len {
3596 let chunk = read_quad(buffer, read)?;
3597 let available = buffer.len();
3598 let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3599 required: write,
3600 available,
3601 })?;
3602 let written = decode_chunk::<A, PAD>(chunk, output_tail)
3603 .map_err(|err| err.with_index_offset(read))?;
3604 read += 4;
3605 write += written;
3606 if written < 3 {
3607 if read != input_len {
3608 return Err(DecodeError::InvalidPadding { index: read - 4 });
3609 }
3610 return Ok(write);
3611 }
3612 }
3613
3614 let rem = input_len - read;
3615 if rem == 0 {
3616 return Ok(write);
3617 }
3618 if PAD {
3619 return Err(DecodeError::InvalidLength);
3620 }
3621 let mut tail = [0u8; 3];
3622 tail[..rem].copy_from_slice(&buffer[read..input_len]);
3623 decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
3624 .map_err(|err| err.with_index_offset(read))
3625 .map(|n| write + n)
3626 }
3627}
3628
3629fn write_wrapped_bytes(
3630 input: &[u8],
3631 output: &mut [u8],
3632 output_offset: &mut usize,
3633 column: &mut usize,
3634 wrap: LineWrap,
3635) -> Result<(), EncodeError> {
3636 for byte in input {
3637 write_wrapped_byte(*byte, output, output_offset, column, wrap)?;
3638 }
3639 Ok(())
3640}
3641
3642fn write_wrapped_byte(
3643 byte: u8,
3644 output: &mut [u8],
3645 output_offset: &mut usize,
3646 column: &mut usize,
3647 wrap: LineWrap,
3648) -> Result<(), EncodeError> {
3649 if *column == wrap.line_len {
3650 let line_ending = wrap.line_ending.as_bytes();
3651 let mut index = 0;
3652 while index < line_ending.len() {
3653 if *output_offset >= output.len() {
3654 return Err(EncodeError::OutputTooSmall {
3655 required: *output_offset + 1,
3656 available: output.len(),
3657 });
3658 }
3659 output[*output_offset] = line_ending[index];
3660 *output_offset += 1;
3661 index += 1;
3662 }
3663 *column = 0;
3664 }
3665
3666 if *output_offset >= output.len() {
3667 return Err(EncodeError::OutputTooSmall {
3668 required: *output_offset + 1,
3669 available: output.len(),
3670 });
3671 }
3672 output[*output_offset] = byte;
3673 *output_offset += 1;
3674 *column += 1;
3675 Ok(())
3676}
3677
3678#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3680pub enum EncodeError {
3681 LengthOverflow,
3683 InvalidLineWrap {
3685 line_len: usize,
3687 },
3688 InputTooLarge {
3690 input_len: usize,
3692 buffer_len: usize,
3694 },
3695 OutputTooSmall {
3697 required: usize,
3699 available: usize,
3701 },
3702}
3703
3704impl core::fmt::Display for EncodeError {
3705 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3706 match self {
3707 Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
3708 Self::InvalidLineWrap { line_len } => {
3709 write!(f, "base64 line wrap length {line_len} is invalid")
3710 }
3711 Self::InputTooLarge {
3712 input_len,
3713 buffer_len,
3714 } => write!(
3715 f,
3716 "base64 input length {input_len} exceeds buffer length {buffer_len}"
3717 ),
3718 Self::OutputTooSmall {
3719 required,
3720 available,
3721 } => write!(
3722 f,
3723 "base64 output buffer too small: required {required}, available {available}"
3724 ),
3725 }
3726 }
3727}
3728
3729#[cfg(feature = "std")]
3730impl std::error::Error for EncodeError {}
3731
3732#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3734pub enum DecodeError {
3735 InvalidInput,
3738 InvalidLength,
3740 InvalidByte {
3742 index: usize,
3744 byte: u8,
3746 },
3747 InvalidPadding {
3749 index: usize,
3751 },
3752 InvalidLineWrap {
3754 index: usize,
3756 },
3757 OutputTooSmall {
3759 required: usize,
3761 available: usize,
3763 },
3764 StagingTooSmall {
3766 required: usize,
3768 available: usize,
3770 },
3771}
3772
3773impl core::fmt::Display for DecodeError {
3774 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3775 match self {
3776 Self::InvalidInput => f.write_str("malformed base64 input"),
3777 Self::InvalidLength => f.write_str("invalid base64 input length"),
3778 Self::InvalidByte { index, byte } => {
3779 write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
3780 }
3781 Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
3782 Self::InvalidLineWrap { index } => {
3783 write!(f, "invalid base64 line wrapping at index {index}")
3784 }
3785 Self::OutputTooSmall {
3786 required,
3787 available,
3788 } => write!(
3789 f,
3790 "base64 decode output buffer too small: required {required}, available {available}"
3791 ),
3792 Self::StagingTooSmall {
3793 required,
3794 available,
3795 } => write!(
3796 f,
3797 "base64 decode staging buffer too small: required {required}, available {available}"
3798 ),
3799 }
3800 }
3801}
3802
3803impl DecodeError {
3804 fn with_index_offset(self, offset: usize) -> Self {
3805 match self {
3806 Self::InvalidByte { index, byte } => Self::InvalidByte {
3807 index: index + offset,
3808 byte,
3809 },
3810 Self::InvalidPadding { index } => Self::InvalidPadding {
3811 index: index + offset,
3812 },
3813 Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
3814 index: index + offset,
3815 },
3816 Self::InvalidInput
3817 | Self::InvalidLength
3818 | Self::OutputTooSmall { .. }
3819 | Self::StagingTooSmall { .. } => self,
3820 }
3821 }
3822}
3823
3824#[cfg(feature = "std")]
3825impl std::error::Error for DecodeError {}
3826
3827struct LegacyBytes<'a> {
3828 input: &'a [u8],
3829 index: usize,
3830}
3831
3832impl<'a> LegacyBytes<'a> {
3833 const fn new(input: &'a [u8]) -> Self {
3834 Self { input, index: 0 }
3835 }
3836
3837 fn next_byte(&mut self) -> Option<(usize, u8)> {
3838 while self.index < self.input.len() {
3839 let index = self.index;
3840 let byte = self.input[index];
3841 self.index += 1;
3842 if !is_legacy_whitespace(byte) {
3843 return Some((index, byte));
3844 }
3845 }
3846 None
3847 }
3848}
3849
3850fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
3851 input: &[u8],
3852) -> Result<usize, DecodeError> {
3853 let mut bytes = LegacyBytes::new(input);
3854 let mut chunk = [0u8; 4];
3855 let mut indexes = [0usize; 4];
3856 let mut chunk_len = 0;
3857 let mut required = 0;
3858 let mut terminal_seen = false;
3859
3860 while let Some((index, byte)) = bytes.next_byte() {
3861 if terminal_seen {
3862 return Err(DecodeError::InvalidPadding { index });
3863 }
3864
3865 chunk[chunk_len] = byte;
3866 indexes[chunk_len] = index;
3867 chunk_len += 1;
3868
3869 if chunk_len == 4 {
3870 let written =
3871 validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
3872 required += written;
3873 terminal_seen = written < 3;
3874 chunk_len = 0;
3875 }
3876 }
3877
3878 if chunk_len == 0 {
3879 return Ok(required);
3880 }
3881 if PAD {
3882 return Err(DecodeError::InvalidLength);
3883 }
3884
3885 validate_tail_unpadded::<A>(&chunk[..chunk_len])
3886 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
3887 Ok(required + decoded_capacity(chunk_len))
3888}
3889
3890fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
3891 input: &[u8],
3892 output: &mut [u8],
3893) -> Result<usize, DecodeError> {
3894 let mut bytes = LegacyBytes::new(input);
3895 let mut chunk = [0u8; 4];
3896 let mut indexes = [0usize; 4];
3897 let mut chunk_len = 0;
3898 let mut write = 0;
3899 let mut terminal_seen = false;
3900
3901 while let Some((index, byte)) = bytes.next_byte() {
3902 if terminal_seen {
3903 return Err(DecodeError::InvalidPadding { index });
3904 }
3905
3906 chunk[chunk_len] = byte;
3907 indexes[chunk_len] = index;
3908 chunk_len += 1;
3909
3910 if chunk_len == 4 {
3911 let available = output.len();
3912 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
3913 required: write,
3914 available,
3915 })?;
3916 let written = decode_chunk::<A, PAD>(chunk, output_tail)
3917 .map_err(|err| map_chunk_error(err, &indexes))?;
3918 write += written;
3919 terminal_seen = written < 3;
3920 chunk_len = 0;
3921 }
3922 }
3923
3924 if chunk_len == 0 {
3925 return Ok(write);
3926 }
3927 if PAD {
3928 return Err(DecodeError::InvalidLength);
3929 }
3930
3931 decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
3932 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
3933 .map(|n| write + n)
3934}
3935
3936struct WrappedBytes<'a> {
3937 input: &'a [u8],
3938 wrap: LineWrap,
3939 index: usize,
3940 line_len: usize,
3941}
3942
3943impl<'a> WrappedBytes<'a> {
3944 const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
3945 if wrap.line_len == 0 {
3946 return Err(DecodeError::InvalidLineWrap { index: 0 });
3947 }
3948 Ok(Self {
3949 input,
3950 wrap,
3951 index: 0,
3952 line_len: 0,
3953 })
3954 }
3955
3956 fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
3957 loop {
3958 if self.index == self.input.len() {
3959 return Ok(None);
3960 }
3961
3962 if self.starts_with_line_ending() {
3963 let line_end_index = self.index;
3964 if self.line_len == 0 {
3965 return Err(DecodeError::InvalidLineWrap {
3966 index: line_end_index,
3967 });
3968 }
3969
3970 self.index += self.wrap.line_ending.byte_len();
3971 if self.index == self.input.len() {
3972 self.line_len = 0;
3973 return Ok(None);
3974 }
3975
3976 if self.line_len != self.wrap.line_len {
3977 return Err(DecodeError::InvalidLineWrap {
3978 index: line_end_index,
3979 });
3980 }
3981 self.line_len = 0;
3982 continue;
3983 }
3984
3985 let byte = self.input[self.index];
3986 if matches!(byte, b'\r' | b'\n') {
3987 return Err(DecodeError::InvalidLineWrap { index: self.index });
3988 }
3989
3990 self.line_len += 1;
3991 if self.line_len > self.wrap.line_len {
3992 return Err(DecodeError::InvalidLineWrap { index: self.index });
3993 }
3994
3995 let index = self.index;
3996 self.index += 1;
3997 return Ok(Some((index, byte)));
3998 }
3999 }
4000
4001 fn starts_with_line_ending(&self) -> bool {
4002 let line_ending = self.wrap.line_ending.as_bytes();
4003 let Some(end) = self.index.checked_add(line_ending.len()) else {
4004 return false;
4005 };
4006 end <= self.input.len() && &self.input[self.index..end] == line_ending
4007 }
4008}
4009
4010fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
4011 input: &[u8],
4012 wrap: LineWrap,
4013) -> Result<usize, DecodeError> {
4014 let mut bytes = WrappedBytes::new(input, wrap)?;
4015 let mut chunk = [0u8; 4];
4016 let mut indexes = [0usize; 4];
4017 let mut chunk_len = 0;
4018 let mut required = 0;
4019 let mut terminal_seen = false;
4020
4021 while let Some((index, byte)) = bytes.next_byte()? {
4022 if terminal_seen {
4023 return Err(DecodeError::InvalidPadding { index });
4024 }
4025
4026 chunk[chunk_len] = byte;
4027 indexes[chunk_len] = index;
4028 chunk_len += 1;
4029
4030 if chunk_len == 4 {
4031 let written =
4032 validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
4033 required += written;
4034 terminal_seen = written < 3;
4035 chunk_len = 0;
4036 }
4037 }
4038
4039 if chunk_len == 0 {
4040 return Ok(required);
4041 }
4042 if PAD {
4043 return Err(DecodeError::InvalidLength);
4044 }
4045
4046 validate_tail_unpadded::<A>(&chunk[..chunk_len])
4047 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
4048 Ok(required + decoded_capacity(chunk_len))
4049}
4050
4051fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
4052 input: &[u8],
4053 output: &mut [u8],
4054 wrap: LineWrap,
4055) -> Result<usize, DecodeError> {
4056 let mut bytes = WrappedBytes::new(input, wrap)?;
4057 let mut chunk = [0u8; 4];
4058 let mut indexes = [0usize; 4];
4059 let mut chunk_len = 0;
4060 let mut write = 0;
4061 let mut terminal_seen = false;
4062
4063 while let Some((index, byte)) = bytes.next_byte()? {
4064 if terminal_seen {
4065 return Err(DecodeError::InvalidPadding { index });
4066 }
4067
4068 chunk[chunk_len] = byte;
4069 indexes[chunk_len] = index;
4070 chunk_len += 1;
4071
4072 if chunk_len == 4 {
4073 let available = output.len();
4074 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4075 required: write,
4076 available,
4077 })?;
4078 let written = decode_chunk::<A, PAD>(chunk, output_tail)
4079 .map_err(|err| map_chunk_error(err, &indexes))?;
4080 write += written;
4081 terminal_seen = written < 3;
4082 chunk_len = 0;
4083 }
4084 }
4085
4086 if chunk_len == 0 {
4087 return Ok(write);
4088 }
4089 if PAD {
4090 return Err(DecodeError::InvalidLength);
4091 }
4092
4093 decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
4094 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
4095 .map(|n| write + n)
4096}
4097
4098fn compact_wrapped_input(buffer: &mut [u8], wrap: LineWrap) -> Result<usize, DecodeError> {
4099 if !wrap.is_valid() {
4100 return Err(DecodeError::InvalidLineWrap { index: 0 });
4101 }
4102
4103 let line_ending = wrap.line_ending.as_bytes();
4104 let line_ending_len = line_ending.len();
4105 let mut read = 0;
4106 let mut write = 0;
4107
4108 while read < buffer.len() {
4109 let line_end = read + line_ending_len;
4110 if buffer.get(read..line_end) == Some(line_ending) {
4111 read = line_end;
4112 continue;
4113 }
4114
4115 buffer[write] = buffer[read];
4116 write += 1;
4117 read += 1;
4118 }
4119
4120 Ok(write)
4121}
4122
4123#[inline]
4124const fn is_legacy_whitespace(byte: u8) -> bool {
4125 matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
4126}
4127
4128fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
4129 match err {
4130 DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
4131 index: indexes[index],
4132 byte,
4133 },
4134 DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
4135 index: indexes[index],
4136 },
4137 DecodeError::InvalidInput
4138 | DecodeError::InvalidLineWrap { .. }
4139 | DecodeError::InvalidLength
4140 | DecodeError::OutputTooSmall { .. }
4141 | DecodeError::StagingTooSmall { .. } => err,
4142 }
4143}
4144
4145fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
4146 match err {
4147 DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
4148 index: indexes[index],
4149 byte,
4150 },
4151 DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
4152 index: indexes[index],
4153 },
4154 DecodeError::InvalidByte { .. }
4155 | DecodeError::InvalidPadding { .. }
4156 | DecodeError::InvalidLineWrap { .. }
4157 | DecodeError::InvalidInput
4158 | DecodeError::InvalidLength
4159 | DecodeError::OutputTooSmall { .. }
4160 | DecodeError::StagingTooSmall { .. } => err,
4161 }
4162}
4163
4164fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4165 if !input.len().is_multiple_of(4) {
4166 return Err(DecodeError::InvalidLength);
4167 }
4168 let required = decoded_len_padded(input)?;
4169 if output.len() < required {
4170 return Err(DecodeError::OutputTooSmall {
4171 required,
4172 available: output.len(),
4173 });
4174 }
4175
4176 let mut read = 0;
4177 let mut write = 0;
4178 while read < input.len() {
4179 let chunk = read_quad(input, read)?;
4180 let available = output.len();
4181 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4182 required: write,
4183 available,
4184 })?;
4185 let written = decode_chunk::<A, true>(chunk, output_tail)
4186 .map_err(|err| err.with_index_offset(read))?;
4187 read += 4;
4188 write += written;
4189 if written < 3 && read != input.len() {
4190 return Err(DecodeError::InvalidPadding { index: read - 4 });
4191 }
4192 }
4193 Ok(write)
4194}
4195
4196fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
4197 if input.is_empty() {
4198 return Ok(0);
4199 }
4200
4201 if PAD {
4202 validate_padded::<A>(input)
4203 } else {
4204 validate_unpadded::<A>(input)
4205 }
4206}
4207
4208fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
4209 if !input.len().is_multiple_of(4) {
4210 return Err(DecodeError::InvalidLength);
4211 }
4212 let required = decoded_len_padded(input)?;
4213
4214 let mut read = 0;
4215 while read < input.len() {
4216 let chunk = read_quad(input, read)?;
4217 let written =
4218 validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
4219 read += 4;
4220 if written < 3 && read != input.len() {
4221 return Err(DecodeError::InvalidPadding { index: read - 4 });
4222 }
4223 }
4224
4225 Ok(required)
4226}
4227
4228fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
4229 let required = decoded_len_unpadded(input)?;
4230
4231 let mut read = 0;
4232 while read + 4 <= input.len() {
4233 let chunk = read_quad(input, read)?;
4234 validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
4235 read += 4;
4236 }
4237 validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
4238
4239 Ok(required)
4240}
4241
4242fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4243 let required = decoded_len_unpadded(input)?;
4244 if output.len() < required {
4245 return Err(DecodeError::OutputTooSmall {
4246 required,
4247 available: output.len(),
4248 });
4249 }
4250
4251 let mut read = 0;
4252 let mut write = 0;
4253 while read + 4 <= input.len() {
4254 let chunk = read_quad(input, read)?;
4255 let available = output.len();
4256 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
4257 required: write,
4258 available,
4259 })?;
4260 let written = decode_chunk::<A, false>(chunk, output_tail)
4261 .map_err(|err| err.with_index_offset(read))?;
4262 read += 4;
4263 write += written;
4264 }
4265 decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
4266 .map_err(|err| err.with_index_offset(read))
4267 .map(|n| write + n)
4268}
4269
4270fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
4271 if input.is_empty() {
4272 return Ok(0);
4273 }
4274 if !input.len().is_multiple_of(4) {
4275 return Err(DecodeError::InvalidLength);
4276 }
4277
4278 let Some((&last, before_last_prefix)) = input.split_last() else {
4279 return Ok(0);
4280 };
4281 let Some(&before_last) = before_last_prefix.last() else {
4282 return Err(DecodeError::InvalidLength);
4283 };
4284
4285 let mut padding = 0;
4286 if last == b'=' {
4287 padding += 1;
4288 }
4289 if before_last == b'=' {
4290 padding += 1;
4291 }
4292 if padding == 0
4293 && let Some(index) = input.iter().position(|byte| *byte == b'=')
4294 {
4295 return Err(DecodeError::InvalidPadding { index });
4296 }
4297 if padding > 0 {
4298 let first_pad = input.len() - padding;
4299 if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
4300 return Err(DecodeError::InvalidPadding { index });
4301 }
4302 }
4303 Ok(input.len() / 4 * 3 - padding)
4304}
4305
4306fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
4307 if input.len() % 4 == 1 {
4308 return Err(DecodeError::InvalidLength);
4309 }
4310 if let Some(index) = input.iter().position(|byte| *byte == b'=') {
4311 return Err(DecodeError::InvalidPadding { index });
4312 }
4313 Ok(decoded_capacity(input.len()))
4314}
4315
4316fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
4317 let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
4318 match input.get(offset..end) {
4319 Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
4320 _ => Err(DecodeError::InvalidLength),
4321 }
4322}
4323
4324fn first_padding_index_unchecked(input: [u8; 4]) -> usize {
4325 let [b0, b1, b2, b3] = input;
4326 if b0 == b'=' {
4327 0
4328 } else if b1 == b'=' {
4329 1
4330 } else if b2 == b'=' {
4331 2
4332 } else if b3 == b'=' {
4333 3
4334 } else {
4335 debug_assert!(
4336 false,
4337 "first_padding_index_unchecked called with no padding"
4338 );
4339 4
4340 }
4341}
4342
4343fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
4344 let [b0, b1, b2, b3] = input;
4345 let _v0 = decode_byte::<A>(b0, 0)?;
4346 let v1 = decode_byte::<A>(b1, 1)?;
4347
4348 match (b2, b3) {
4349 (b'=', b'=') if PAD => {
4350 if v1 & 0b0000_1111 != 0 {
4351 return Err(DecodeError::InvalidPadding { index: 1 });
4352 }
4353 Ok(1)
4354 }
4355 (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
4356 (_, b'=') if PAD => {
4357 let v2 = decode_byte::<A>(b2, 2)?;
4358 if v2 & 0b0000_0011 != 0 {
4359 return Err(DecodeError::InvalidPadding { index: 2 });
4360 }
4361 Ok(2)
4362 }
4363 (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
4364 index: first_padding_index_unchecked(input),
4365 }),
4366 _ => {
4367 decode_byte::<A>(b2, 2)?;
4368 decode_byte::<A>(b3, 3)?;
4369 Ok(3)
4370 }
4371 }
4372}
4373
4374fn decode_chunk<A: Alphabet, const PAD: bool>(
4375 input: [u8; 4],
4376 output: &mut [u8],
4377) -> Result<usize, DecodeError> {
4378 let [b0, b1, b2, b3] = input;
4379 let v0 = decode_byte::<A>(b0, 0)?;
4380 let v1 = decode_byte::<A>(b1, 1)?;
4381
4382 match (b2, b3) {
4383 (b'=', b'=') if PAD => {
4384 if output.is_empty() {
4385 return Err(DecodeError::OutputTooSmall {
4386 required: 1,
4387 available: output.len(),
4388 });
4389 }
4390 if v1 & 0b0000_1111 != 0 {
4391 return Err(DecodeError::InvalidPadding { index: 1 });
4392 }
4393 output[0] = (v0 << 2) | (v1 >> 4);
4394 Ok(1)
4395 }
4396 (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
4397 (_, b'=') if PAD => {
4398 if output.len() < 2 {
4399 return Err(DecodeError::OutputTooSmall {
4400 required: 2,
4401 available: output.len(),
4402 });
4403 }
4404 let v2 = decode_byte::<A>(b2, 2)?;
4405 if v2 & 0b0000_0011 != 0 {
4406 return Err(DecodeError::InvalidPadding { index: 2 });
4407 }
4408 output[0] = (v0 << 2) | (v1 >> 4);
4409 output[1] = (v1 << 4) | (v2 >> 2);
4410 Ok(2)
4411 }
4412 (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
4413 index: first_padding_index_unchecked(input),
4414 }),
4415 _ => {
4416 if output.len() < 3 {
4417 return Err(DecodeError::OutputTooSmall {
4418 required: 3,
4419 available: output.len(),
4420 });
4421 }
4422 let v2 = decode_byte::<A>(b2, 2)?;
4423 let v3 = decode_byte::<A>(b3, 3)?;
4424 output[0] = (v0 << 2) | (v1 >> 4);
4425 output[1] = (v1 << 4) | (v2 >> 2);
4426 output[2] = (v2 << 6) | v3;
4427 Ok(3)
4428 }
4429 }
4430}
4431
4432fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4433 match input {
4434 [] => Ok(()),
4435 [b0, b1] => {
4436 decode_byte::<A>(*b0, 0)?;
4437 let v1 = decode_byte::<A>(*b1, 1)?;
4438 if v1 & 0b0000_1111 != 0 {
4439 return Err(DecodeError::InvalidPadding { index: 1 });
4440 }
4441 Ok(())
4442 }
4443 [b0, b1, b2] => {
4444 decode_byte::<A>(*b0, 0)?;
4445 decode_byte::<A>(*b1, 1)?;
4446 let v2 = decode_byte::<A>(*b2, 2)?;
4447 if v2 & 0b0000_0011 != 0 {
4448 return Err(DecodeError::InvalidPadding { index: 2 });
4449 }
4450 Ok(())
4451 }
4452 _ => Err(DecodeError::InvalidLength),
4453 }
4454}
4455
4456fn decode_tail_unpadded<A: Alphabet>(
4457 input: &[u8],
4458 output: &mut [u8],
4459) -> Result<usize, DecodeError> {
4460 match input {
4461 [] => Ok(0),
4462 [b0, b1] => {
4463 let Some(out0) = output.first_mut() else {
4464 return Err(DecodeError::OutputTooSmall {
4465 required: 1,
4466 available: output.len(),
4467 });
4468 };
4469 let v0 = decode_byte::<A>(*b0, 0)?;
4470 let v1 = decode_byte::<A>(*b1, 1)?;
4471 if v1 & 0b0000_1111 != 0 {
4472 return Err(DecodeError::InvalidPadding { index: 1 });
4473 }
4474 *out0 = (v0 << 2) | (v1 >> 4);
4475 Ok(1)
4476 }
4477 [b0, b1, b2] => {
4478 let available = output.len();
4479 let Some([out0, out1]) = output.get_mut(..2) else {
4480 return Err(DecodeError::OutputTooSmall {
4481 required: 2,
4482 available,
4483 });
4484 };
4485 let v0 = decode_byte::<A>(*b0, 0)?;
4486 let v1 = decode_byte::<A>(*b1, 1)?;
4487 let v2 = decode_byte::<A>(*b2, 2)?;
4488 if v2 & 0b0000_0011 != 0 {
4489 return Err(DecodeError::InvalidPadding { index: 2 });
4490 }
4491 *out0 = (v0 << 2) | (v1 >> 4);
4492 *out1 = (v1 << 4) | (v2 >> 2);
4493 Ok(2)
4494 }
4495 _ => Err(DecodeError::InvalidLength),
4496 }
4497}
4498
4499fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
4500 A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
4501}
4502
4503fn ct_decode_slice<A: Alphabet, const PAD: bool>(
4504 input: &[u8],
4505 output: &mut [u8],
4506) -> Result<usize, DecodeError> {
4507 if input.is_empty() {
4508 return Ok(0);
4509 }
4510
4511 if PAD {
4512 ct_decode_padded::<A>(input, output)
4513 } else {
4514 ct_decode_unpadded::<A>(input, output)
4515 }
4516}
4517
4518fn ct_decode_slice_staged_clear_tail<A: Alphabet, const PAD: bool>(
4519 input: &[u8],
4520 output: &mut [u8],
4521 staging: &mut [u8],
4522) -> Result<usize, DecodeError> {
4523 let required = match ct_decoded_len::<A, PAD>(input) {
4524 Ok(required) => required,
4525 Err(err) => {
4526 wipe_bytes(output);
4527 wipe_bytes(staging);
4528 return Err(err);
4529 }
4530 };
4531
4532 if output.len() < required {
4533 wipe_bytes(output);
4534 wipe_bytes(staging);
4535 return Err(DecodeError::OutputTooSmall {
4536 required,
4537 available: output.len(),
4538 });
4539 }
4540
4541 if staging.len() < required {
4542 wipe_bytes(output);
4543 wipe_bytes(staging);
4544 return Err(DecodeError::StagingTooSmall {
4545 required,
4546 available: staging.len(),
4547 });
4548 }
4549
4550 let written = match ct_decode_slice::<A, PAD>(input, &mut staging[..required]) {
4551 Ok(written) => written,
4552 Err(err) => {
4553 wipe_bytes(output);
4554 wipe_bytes(staging);
4555 return Err(err);
4556 }
4557 };
4558
4559 output[..written].copy_from_slice(&staging[..written]);
4560 wipe_bytes(staging);
4561 wipe_tail(output, written);
4562 Ok(written)
4563}
4564
4565fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
4566 buffer: &mut [u8],
4567) -> Result<usize, DecodeError> {
4568 if buffer.is_empty() {
4569 return Ok(0);
4570 }
4571
4572 if PAD {
4573 ct_decode_padded_in_place::<A>(buffer)
4574 } else {
4575 ct_decode_unpadded_in_place::<A>(buffer)
4576 }
4577}
4578
4579#[inline(never)]
4580#[allow(unsafe_code)]
4581fn ct_error_gate_barrier(invalid_byte: u8, invalid_padding: u8) {
4582 core::hint::black_box(invalid_byte | invalid_padding);
4583 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
4584
4585 #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
4586 {
4587 unsafe {
4590 core::arch::asm!("lfence", options(nostack, preserves_flags, nomem));
4591 }
4592 }
4593
4594 #[cfg(all(not(miri), target_arch = "aarch64"))]
4595 {
4596 unsafe {
4600 core::arch::asm!("isb sy", "hint #20", options(nostack, preserves_flags));
4601 }
4602 }
4603
4604 #[cfg(all(not(miri), target_arch = "arm"))]
4605 {
4606 unsafe {
4609 core::arch::asm!("isb sy", options(nostack, preserves_flags));
4610 }
4611 }
4612
4613 #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
4614 {
4615 unsafe {
4620 core::arch::asm!("fence rw, rw", options(nostack, preserves_flags));
4621 }
4622 }
4623}
4624
4625fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
4626 if input.is_empty() {
4627 return Ok(());
4628 }
4629
4630 if PAD {
4631 ct_validate_padded::<A>(input)
4632 } else {
4633 ct_validate_unpadded::<A>(input)
4634 }
4635}
4636
4637fn ct_decoded_len<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
4638 ct_validate_decode::<A, PAD>(input)?;
4639 if input.is_empty() {
4640 return Ok(0);
4641 }
4642
4643 if PAD {
4644 Ok(input.len() / 4 * 3 - ct_padding_len(input))
4645 } else {
4646 let full_quads = input.len() / 4 * 3;
4647 match input.len() % 4 {
4648 0 => Ok(full_quads),
4649 2 => Ok(full_quads + 1),
4650 3 => Ok(full_quads + 2),
4651 _ => Err(DecodeError::InvalidLength),
4652 }
4653 }
4654}
4655
4656fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4657 if !input.len().is_multiple_of(4) {
4658 return Err(DecodeError::InvalidLength);
4659 }
4660
4661 let padding = ct_padding_len(input);
4662 let mut invalid_byte = 0u8;
4663 let mut invalid_padding = 0u8;
4664 let mut read = 0;
4665
4666 while read + 4 < input.len() {
4667 let [b0, b1, b2, b3] =
4668 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4669 let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
4670 let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
4671 let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
4672 let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
4673
4674 invalid_byte |= !valid0;
4675 invalid_byte |= !valid1;
4676 invalid_byte |= !valid2;
4677 invalid_byte |= !valid3;
4678 invalid_padding |= ct_mask_eq_u8(b2, b'=');
4679 invalid_padding |= ct_mask_eq_u8(b3, b'=');
4680 read += 4;
4681 }
4682
4683 let final_chunk =
4684 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4685 let (_, final_invalid_byte, final_invalid_padding, _) =
4686 ct_padded_final_quantum::<A>(final_chunk, padding);
4687 invalid_byte |= final_invalid_byte;
4688 invalid_padding |= final_invalid_padding;
4689
4690 report_ct_error(invalid_byte, invalid_padding)
4691}
4692
4693fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
4694 if input.len() % 4 == 1 {
4695 return Err(DecodeError::InvalidLength);
4696 }
4697
4698 let mut invalid_byte = 0u8;
4699 let mut invalid_padding = 0u8;
4700 let mut read = 0;
4701
4702 while read + 4 <= input.len() {
4703 let [b0, b1, b2, b3] =
4704 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4705 let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
4706 let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
4707 let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
4708 let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
4709
4710 invalid_byte |= !valid0;
4711 invalid_byte |= !valid1;
4712 invalid_byte |= !valid2;
4713 invalid_byte |= !valid3;
4714 invalid_padding |= ct_mask_eq_u8(b0, b'=');
4715 invalid_padding |= ct_mask_eq_u8(b1, b'=');
4716 invalid_padding |= ct_mask_eq_u8(b2, b'=');
4717 invalid_padding |= ct_mask_eq_u8(b3, b'=');
4718
4719 read += 4;
4720 }
4721
4722 match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
4723 [] => {}
4724 [b0, b1] => {
4725 let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
4726 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
4727 invalid_byte |= !valid0;
4728 invalid_byte |= !valid1;
4729 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
4730 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
4731 invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4732 }
4733 [b0, b1, b2] => {
4734 let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
4735 let (_, valid1) = ct_decode_alphabet_byte::<A>(*b1);
4736 let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
4737 invalid_byte |= !valid0;
4738 invalid_byte |= !valid1;
4739 invalid_byte |= !valid2;
4740 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
4741 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
4742 invalid_padding |= ct_mask_eq_u8(*b2, b'=');
4743 invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4744 }
4745 _ => {
4746 invalid_byte = 0xff;
4747 invalid_padding = 0xff;
4748 }
4749 }
4750
4751 report_ct_error(invalid_byte, invalid_padding)
4752}
4753
4754fn ct_padded_final_quantum<A: Alphabet>(
4755 input: [u8; 4],
4756 padding: usize,
4757) -> ([u8; 3], u8, u8, usize) {
4758 let [b0, b1, b2, b3] = input;
4759 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
4760 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
4761 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
4762 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
4763
4764 let padding_byte = match padding {
4765 0 => 0,
4766 1 => 1,
4767 2 => 2,
4768 _ => return ([0; 3], 0xff, 0xff, 0),
4769 };
4770 let no_padding = ct_mask_eq_u8(padding_byte, 0);
4771 let one_padding = ct_mask_eq_u8(padding_byte, 1);
4772 let two_padding = ct_mask_eq_u8(padding_byte, 2);
4773 let require_v2 = no_padding | one_padding;
4774 let require_v3 = no_padding;
4775
4776 let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
4777 let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
4778 | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
4779 | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
4780
4781 (
4782 [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
4783 invalid_byte,
4784 invalid_padding,
4785 3 - padding,
4786 )
4787}
4788
4789fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4790 if !input.len().is_multiple_of(4) {
4791 return Err(DecodeError::InvalidLength);
4792 }
4793
4794 let padding = ct_padding_len(input);
4795 let required = input.len() / 4 * 3 - padding;
4796 if output.len() < required {
4797 return Err(DecodeError::OutputTooSmall {
4798 required,
4799 available: output.len(),
4800 });
4801 }
4802
4803 let mut invalid_byte = 0u8;
4804 let mut invalid_padding = 0u8;
4805 let mut write = 0;
4806 let mut read = 0;
4807
4808 while read + 4 < input.len() {
4809 let [b0, b1, b2, b3] =
4810 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4811 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
4812 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
4813 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
4814 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
4815
4816 invalid_byte |= !valid0;
4817 invalid_byte |= !valid1;
4818 invalid_byte |= !valid2;
4819 invalid_byte |= !valid3;
4820 invalid_padding |= ct_mask_eq_u8(b2, b'=');
4821 invalid_padding |= ct_mask_eq_u8(b3, b'=');
4822 output[write] = (v0 << 2) | (v1 >> 4);
4823 output[write + 1] = (v1 << 4) | (v2 >> 2);
4824 output[write + 2] = (v2 << 6) | v3;
4825 write += 3;
4826 read += 4;
4827 }
4828
4829 let final_chunk =
4830 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4831 let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
4832 ct_padded_final_quantum::<A>(final_chunk, padding);
4833 invalid_byte |= final_invalid_byte;
4834 invalid_padding |= final_invalid_padding;
4835 output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
4836 write += final_written;
4837
4838 report_ct_error(invalid_byte, invalid_padding)?;
4839 Ok(write)
4840}
4841
4842fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
4843 if !buffer.len().is_multiple_of(4) {
4844 return Err(DecodeError::InvalidLength);
4845 }
4846
4847 let padding = ct_padding_len(buffer);
4848 let required = buffer.len() / 4 * 3 - padding;
4849 if required > buffer.len() {
4850 wipe_bytes(buffer);
4851 return Err(DecodeError::InvalidInput);
4852 }
4853
4854 let mut invalid_byte = 0u8;
4855 let mut invalid_padding = 0u8;
4856 let mut write = 0;
4857 let mut read = 0;
4858
4859 while read + 4 < buffer.len() {
4860 let [b0, b1, b2, b3] =
4861 read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
4862 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
4863 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
4864 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
4865 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
4866
4867 invalid_byte |= !valid0;
4868 invalid_byte |= !valid1;
4869 invalid_byte |= !valid2;
4870 invalid_byte |= !valid3;
4871 invalid_padding |= ct_mask_eq_u8(b2, b'=');
4872 invalid_padding |= ct_mask_eq_u8(b3, b'=');
4873 buffer[write] = (v0 << 2) | (v1 >> 4);
4874 buffer[write + 1] = (v1 << 4) | (v2 >> 2);
4875 buffer[write + 2] = (v2 << 6) | v3;
4876 write += 3;
4877 read += 4;
4878 }
4879
4880 let final_chunk =
4881 read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
4882 let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
4883 ct_padded_final_quantum::<A>(final_chunk, padding);
4884 invalid_byte |= final_invalid_byte;
4885 invalid_padding |= final_invalid_padding;
4886 buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
4887 write += final_written;
4888
4889 if write != required {
4890 ct_error_gate_barrier(invalid_byte, invalid_padding);
4891 wipe_bytes(buffer);
4892 return Err(DecodeError::InvalidInput);
4893 }
4894 if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
4895 wipe_bytes(buffer);
4896 return Err(err);
4897 }
4898 Ok(write)
4899}
4900
4901fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4902 if input.len() % 4 == 1 {
4903 return Err(DecodeError::InvalidLength);
4904 }
4905
4906 let required = decoded_capacity(input.len());
4907 if output.len() < required {
4908 return Err(DecodeError::OutputTooSmall {
4909 required,
4910 available: output.len(),
4911 });
4912 }
4913
4914 let mut invalid_byte = 0u8;
4915 let mut invalid_padding = 0u8;
4916 let mut write = 0;
4917 let mut read = 0;
4918
4919 while read + 4 <= input.len() {
4920 let [b0, b1, b2, b3] =
4921 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
4922 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
4923 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
4924 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
4925 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
4926
4927 invalid_byte |= !valid0;
4928 invalid_byte |= !valid1;
4929 invalid_byte |= !valid2;
4930 invalid_byte |= !valid3;
4931 invalid_padding |= ct_mask_eq_u8(b0, b'=');
4932 invalid_padding |= ct_mask_eq_u8(b1, b'=');
4933 invalid_padding |= ct_mask_eq_u8(b2, b'=');
4934 invalid_padding |= ct_mask_eq_u8(b3, b'=');
4935
4936 output[write] = (v0 << 2) | (v1 >> 4);
4937 output[write + 1] = (v1 << 4) | (v2 >> 2);
4938 output[write + 2] = (v2 << 6) | v3;
4939 read += 4;
4940 write += 3;
4941 }
4942
4943 match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
4944 [] => {}
4945 [b0, b1] => {
4946 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
4947 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
4948 invalid_byte |= !valid0;
4949 invalid_byte |= !valid1;
4950 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
4951 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
4952 invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
4953 output[write] = (v0 << 2) | (v1 >> 4);
4954 write += 1;
4955 }
4956 [b0, b1, b2] => {
4957 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
4958 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
4959 let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
4960 invalid_byte |= !valid0;
4961 invalid_byte |= !valid1;
4962 invalid_byte |= !valid2;
4963 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
4964 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
4965 invalid_padding |= ct_mask_eq_u8(*b2, b'=');
4966 invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
4967 output[write] = (v0 << 2) | (v1 >> 4);
4968 output[write + 1] = (v1 << 4) | (v2 >> 2);
4969 write += 2;
4970 }
4971 _ => {
4972 invalid_byte = 0xff;
4973 invalid_padding = 0xff;
4974 }
4975 }
4976
4977 report_ct_error(invalid_byte, invalid_padding)?;
4978 Ok(write)
4979}
4980
4981fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
4982 if buffer.len() % 4 == 1 {
4983 return Err(DecodeError::InvalidLength);
4984 }
4985
4986 let required = decoded_capacity(buffer.len());
4987 if required > buffer.len() {
4988 wipe_bytes(buffer);
4989 return Err(DecodeError::InvalidInput);
4990 }
4991
4992 let mut invalid_byte = 0u8;
4993 let mut invalid_padding = 0u8;
4994 let mut write = 0;
4995 let mut read = 0;
4996
4997 while read + 4 <= buffer.len() {
4998 let [b0, b1, b2, b3] =
4999 read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
5000 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
5001 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
5002 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
5003 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
5004
5005 invalid_byte |= !valid0;
5006 invalid_byte |= !valid1;
5007 invalid_byte |= !valid2;
5008 invalid_byte |= !valid3;
5009 invalid_padding |= ct_mask_eq_u8(b0, b'=');
5010 invalid_padding |= ct_mask_eq_u8(b1, b'=');
5011 invalid_padding |= ct_mask_eq_u8(b2, b'=');
5012 invalid_padding |= ct_mask_eq_u8(b3, b'=');
5013
5014 buffer[write] = (v0 << 2) | (v1 >> 4);
5015 buffer[write + 1] = (v1 << 4) | (v2 >> 2);
5016 buffer[write + 2] = (v2 << 6) | v3;
5017 read += 4;
5018 write += 3;
5019 }
5020
5021 let tail = read_tail_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
5022 match tail {
5023 [] => {}
5024 [b0, b1] => {
5025 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
5026 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
5027 invalid_byte |= !valid0;
5028 invalid_byte |= !valid1;
5029 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
5030 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
5031 invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
5032 buffer[write] = (v0 << 2) | (v1 >> 4);
5033 write += 1;
5034 }
5035 [b0, b1, b2] => {
5036 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
5037 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
5038 let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
5039 invalid_byte |= !valid0;
5040 invalid_byte |= !valid1;
5041 invalid_byte |= !valid2;
5042 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
5043 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
5044 invalid_padding |= ct_mask_eq_u8(*b2, b'=');
5045 invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
5046 buffer[write] = (v0 << 2) | (v1 >> 4);
5047 buffer[write + 1] = (v1 << 4) | (v2 >> 2);
5048 write += 2;
5049 }
5050 _ => {
5051 invalid_byte = 0xff;
5052 invalid_padding = 0xff;
5053 }
5054 }
5055
5056 if write != required {
5057 ct_error_gate_barrier(invalid_byte, invalid_padding);
5058 wipe_bytes(buffer);
5059 return Err(DecodeError::InvalidInput);
5060 }
5061 if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
5062 wipe_bytes(buffer);
5063 return Err(err);
5064 }
5065 Ok(write)
5066}
5067
5068fn read_tail(input: &[u8], offset: usize) -> Result<&[u8], DecodeError> {
5069 input.get(offset..).ok_or(DecodeError::InvalidLength)
5070}
5071
5072fn read_quad_or_mark_invalid(
5073 input: &[u8],
5074 offset: usize,
5075 invalid_byte: &mut u8,
5076 invalid_padding: &mut u8,
5077) -> [u8; 4] {
5078 if let Ok(quad) = read_quad(input, offset) {
5079 quad
5080 } else {
5081 debug_assert!(
5082 false,
5083 "read_quad failed inside length-validated constant-time decode loop"
5084 );
5085 *invalid_byte = 0xff;
5086 *invalid_padding = 0xff;
5087 [0; 4]
5088 }
5089}
5090
5091fn read_tail_or_mark_invalid<'a>(
5092 input: &'a [u8],
5093 offset: usize,
5094 invalid_byte: &mut u8,
5095 invalid_padding: &mut u8,
5096) -> &'a [u8] {
5097 if let Ok(tail) = read_tail(input, offset) {
5098 tail
5099 } else {
5100 debug_assert!(
5101 false,
5102 "read_tail failed inside length-validated constant-time decode loop"
5103 );
5104 *invalid_byte = 0xff;
5105 *invalid_padding = 0xff;
5106 &[]
5107 }
5108}
5109
5110#[inline(never)]
5111#[allow(unsafe_code)]
5112fn ct_decode_alphabet_byte<A: Alphabet>(byte: u8) -> (u8, u8) {
5113 let mut decoded = 0u8;
5114 let mut valid = 0u8;
5115 let mut candidate = 0u8;
5116
5117 while candidate < 64 {
5118 let matches = core::hint::black_box(ct_mask_eq_u8(
5119 core::hint::black_box(byte),
5120 core::hint::black_box(A::ENCODE[candidate as usize]),
5121 ));
5122 decoded = core::hint::black_box(
5123 core::hint::black_box(decoded) | core::hint::black_box(candidate & matches),
5124 );
5125 decoded = unsafe { core::ptr::read_volatile(&raw const decoded) };
5129 valid =
5130 core::hint::black_box(core::hint::black_box(valid) | core::hint::black_box(matches));
5131 valid = unsafe { core::ptr::read_volatile(&raw const valid) };
5135 candidate += 1;
5136 }
5137
5138 (decoded, valid)
5139}
5140
5141fn ct_padding_len(input: &[u8]) -> usize {
5142 let Some((&last, before_last_prefix)) = input.split_last() else {
5143 return 0;
5144 };
5145 let Some(&before_last) = before_last_prefix.last() else {
5146 return 0;
5147 };
5148 usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
5149}
5150
5151fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
5152 ct_error_gate_barrier(invalid_byte, invalid_padding);
5153
5154 if (invalid_byte | invalid_padding) != 0 {
5155 Err(DecodeError::InvalidInput)
5156 } else {
5157 Ok(())
5158 }
5159}
5160
5161#[cfg(kani)]
5162mod kani_proofs {
5163 use super::{
5164 STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
5165 decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
5166 };
5167
5168 #[kani::proof]
5169 fn checked_encoded_len_is_bounded_for_small_inputs() {
5170 let len = usize::from(kani::any::<u8>());
5171 let padded = kani::any::<bool>();
5172 let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
5173
5174 assert!(encoded >= len);
5175 assert!(encoded <= len / 3 * 4 + 4);
5176 }
5177
5178 #[kani::proof]
5179 fn decoded_capacity_is_bounded_for_small_inputs() {
5180 let len = usize::from(kani::any::<u8>());
5181 let capacity = decoded_capacity(len);
5182
5183 assert!(capacity <= len / 4 * 3 + 2);
5184 }
5185
5186 #[kani::proof]
5187 #[kani::unwind(3)]
5188 fn standard_in_place_decode_returns_prefix_within_buffer() {
5189 let mut buffer = kani::any::<[u8; 8]>();
5190 let result = STANDARD.decode_in_place(&mut buffer);
5191
5192 if let Ok(decoded) = result {
5193 assert!(decoded.len() <= 8);
5194 }
5195 }
5196
5197 #[kani::proof]
5198 #[kani::unwind(3)]
5199 fn standard_decode_slice_returns_written_within_output() {
5200 let input = kani::any::<[u8; 4]>();
5201 let mut output = kani::any::<[u8; 3]>();
5202 let result = STANDARD.decode_slice(&input, &mut output);
5203
5204 if let Ok(written) = result {
5205 assert!(written <= output.len());
5206 }
5207 }
5208
5209 #[kani::proof]
5210 #[kani::unwind(3)]
5211 fn standard_decode_chunk_returns_written_within_output() {
5212 let input = kani::any::<[u8; 4]>();
5213 let mut output = kani::any::<[u8; 3]>();
5214 let result = decode_chunk::<Standard, true>(input, &mut output);
5215
5216 if let Ok(written) = result {
5217 assert!(written <= output.len());
5218 assert!(written <= 3);
5219 }
5220 }
5221
5222 #[kani::proof]
5223 #[kani::unwind(3)]
5224 fn standard_decode_chunk_bit_packing_matches_decoded_values() {
5225 let input = kani::any::<[u8; 4]>();
5226 let mut output = kani::any::<[u8; 3]>();
5227 let result = decode_chunk::<Standard, true>(input, &mut output);
5228
5229 if let Ok(written) = result {
5230 let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
5231 let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
5232
5233 assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
5234
5235 if written >= 2 {
5236 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
5237 assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
5238 }
5239
5240 if written == 3 {
5241 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
5242 let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
5243 assert!(output[2] == ((v2 << 6) | v3));
5244 }
5245 }
5246 }
5247
5248 #[kani::proof]
5249 #[kani::unwind(3)]
5250 fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
5251 let input = kani::any::<[u8; 3]>();
5252 let len = usize::from(kani::any::<u8>() % 4);
5253 let result = validate_tail_unpadded::<Standard>(&input[..len]);
5254
5255 if result.is_ok() {
5256 assert!(len == 0 || len == 2 || len == 3);
5257 }
5258 }
5259
5260 #[kani::proof]
5261 #[kani::unwind(3)]
5262 fn standard_decode_two_byte_tail_returns_written_within_output() {
5263 let input = kani::any::<[u8; 2]>();
5264 let mut output = kani::any::<[u8; 1]>();
5265 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
5266
5267 if let Ok(written) = result {
5268 assert!(written <= output.len());
5269 assert!(written == 1);
5270 }
5271 }
5272
5273 #[kani::proof]
5274 #[kani::unwind(3)]
5275 fn standard_decode_three_byte_tail_returns_written_within_output() {
5276 let input = kani::any::<[u8; 3]>();
5277 let mut output = kani::any::<[u8; 2]>();
5278 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
5279
5280 if let Ok(written) = result {
5281 assert!(written <= output.len());
5282 assert!(written == 2);
5283 }
5284 }
5285
5286 #[kani::proof]
5287 #[kani::unwind(3)]
5288 fn standard_decode_slice_clear_tail_clears_output_on_error() {
5289 let input = kani::any::<[u8; 4]>();
5290 let mut output = kani::any::<[u8; 3]>();
5291 let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
5292
5293 if result.is_err() {
5294 assert!(output.iter().all(|byte| *byte == 0));
5295 }
5296 }
5297
5298 #[kani::proof]
5299 #[kani::unwind(3)]
5300 fn standard_encode_slice_returns_written_within_output() {
5301 let input = kani::any::<[u8; 3]>();
5302 let mut output = kani::any::<[u8; 4]>();
5303 let result = STANDARD.encode_slice(&input, &mut output);
5304
5305 if let Ok(written) = result {
5306 assert!(written <= output.len());
5307 }
5308 }
5309
5310 #[kani::proof]
5311 #[kani::unwind(4)]
5312 fn standard_encode_in_place_returns_prefix_within_buffer() {
5313 let mut buffer = kani::any::<[u8; 8]>();
5314 let input_len = usize::from(kani::any::<u8>() % 9);
5315 let result = STANDARD.encode_in_place(&mut buffer, input_len);
5316
5317 if let Ok(encoded) = result {
5318 assert!(encoded.len() <= 8);
5319 }
5320 }
5321
5322 #[kani::proof]
5323 #[kani::unwind(3)]
5324 fn standard_clear_tail_decode_clears_buffer_on_error() {
5325 let mut buffer = kani::any::<[u8; 4]>();
5326 let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
5327
5328 if result.is_err() {
5329 assert!(buffer.iter().all(|byte| *byte == 0));
5330 }
5331 }
5332
5333 #[kani::proof]
5334 #[kani::unwind(3)]
5335 fn ct_standard_decode_slice_returns_written_within_output() {
5336 let input = kani::any::<[u8; 4]>();
5337 let mut output = kani::any::<[u8; 3]>();
5338 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
5339
5340 if let Ok(written) = result {
5341 assert!(written <= output.len());
5342 }
5343 }
5344
5345 #[kani::proof]
5346 #[kani::unwind(3)]
5347 fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
5348 let input = kani::any::<[u8; 4]>();
5349 let mut output = kani::any::<[u8; 3]>();
5350 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
5351
5352 if result.is_err() {
5353 assert!(output.iter().all(|byte| *byte == 0));
5354 }
5355 }
5356
5357 #[kani::proof]
5358 #[kani::unwind(3)]
5359 fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
5360 let mut buffer = kani::any::<[u8; 4]>();
5361 let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
5362
5363 if result.is_err() {
5364 assert!(buffer.iter().all(|byte| *byte == 0));
5365 }
5366 }
5367
5368 #[kani::proof]
5369 #[kani::unwind(3)]
5370 fn ct_standard_validate_matches_decode_for_one_quantum() {
5371 let input = kani::any::<[u8; 4]>();
5372 let mut output = kani::any::<[u8; 3]>();
5373
5374 let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
5375 let decode_ok = ct::STANDARD
5376 .decode_slice_clear_tail(&input, &mut output)
5377 .is_ok();
5378
5379 assert!(validate_ok == decode_ok);
5380 }
5381}
5382
5383#[cfg(test)]
5384mod tests {
5385 use super::*;
5386
5387 fn fill_pattern(output: &mut [u8], seed: usize) {
5388 for (index, byte) in output.iter_mut().enumerate() {
5389 let value = (index * 73 + seed * 19) % 256;
5390 *byte = u8::try_from(value).unwrap();
5391 }
5392 }
5393
5394 fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
5395 where
5396 A: Alphabet,
5397 {
5398 let engine = Engine::<A, PAD>::new();
5399 let mut dispatched = [0x55; 256];
5400 let mut scalar = [0xaa; 256];
5401
5402 let dispatched_result = engine.encode_slice(input, &mut dispatched);
5403 let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
5404
5405 assert_eq!(dispatched_result, scalar_result);
5406 if let Ok(written) = dispatched_result {
5407 assert_eq!(&dispatched[..written], &scalar[..written]);
5408 }
5409
5410 let required = checked_encoded_len(input.len(), PAD).unwrap();
5411 if required > 0 {
5412 let mut dispatched_short = [0x55; 256];
5413 let mut scalar_short = [0xaa; 256];
5414 let available = required - 1;
5415
5416 assert_eq!(
5417 engine.encode_slice(input, &mut dispatched_short[..available]),
5418 backend::scalar_reference_encode_slice::<A, PAD>(
5419 input,
5420 &mut scalar_short[..available],
5421 )
5422 );
5423 }
5424 }
5425
5426 fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
5427 where
5428 A: Alphabet,
5429 {
5430 let engine = Engine::<A, PAD>::new();
5431 let mut dispatched = [0x55; 128];
5432 let mut scalar = [0xaa; 128];
5433
5434 let dispatched_result = engine.decode_slice(input, &mut dispatched);
5435 let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
5436
5437 assert_eq!(dispatched_result, scalar_result);
5438 if let Ok(written) = dispatched_result {
5439 assert_eq!(&dispatched[..written], &scalar[..written]);
5440
5441 if written > 0 {
5442 let mut dispatched_short = [0x55; 128];
5443 let mut scalar_short = [0xaa; 128];
5444 let available = written - 1;
5445
5446 assert_eq!(
5447 engine.decode_slice(input, &mut dispatched_short[..available]),
5448 backend::scalar_reference_decode_slice::<A, PAD>(
5449 input,
5450 &mut scalar_short[..available],
5451 )
5452 );
5453 }
5454 }
5455 }
5456
5457 fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
5458 where
5459 A: Alphabet,
5460 {
5461 assert_encode_backend_matches_scalar::<A, PAD>(input);
5462
5463 let mut encoded = [0; 256];
5464 let encoded_len =
5465 backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
5466 assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
5467 }
5468
5469 fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
5470 let mut encoded = [0u8; 4];
5471 let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
5472 assert_eq!(encoded_len, 4);
5473
5474 let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
5475 let mut decoded = [0u8; 3];
5476 let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
5477
5478 assert_eq!(decoded_len, input.len());
5479 assert_eq!(&decoded[..decoded_len], input);
5480 }
5481
5482 #[test]
5483 fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
5484 let mut input = [0; 128];
5485
5486 for input_len in 0..=input.len() {
5487 fill_pattern(&mut input[..input_len], input_len);
5488 let input = &input[..input_len];
5489
5490 assert_backend_round_trip_matches_scalar::<Standard, true>(input);
5491 assert_backend_round_trip_matches_scalar::<Standard, false>(input);
5492 assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
5493 assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
5494 }
5495 }
5496
5497 #[test]
5498 fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
5499 for input in [
5500 &b"Z"[..],
5501 b"====",
5502 b"AA=A",
5503 b"Zh==",
5504 b"Zm9=",
5505 b"Zm9v$g==",
5506 b"Zm9vZh==",
5507 ] {
5508 assert_decode_backend_matches_scalar::<Standard, true>(input);
5509 }
5510
5511 for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
5512 assert_decode_backend_matches_scalar::<Standard, false>(input);
5513 }
5514
5515 assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
5516 assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
5517 assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
5518 assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
5519 }
5520
5521 #[test]
5522 fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
5523 for byte in u8::MIN..=u8::MAX {
5524 assert_standard_decode_chunk_matches_input(&[byte]);
5525 }
5526
5527 for first in u8::MIN..=u8::MAX {
5528 for second in u8::MIN..=u8::MAX {
5529 assert_standard_decode_chunk_matches_input(&[first, second]);
5530 }
5531 }
5532 }
5533
5534 #[test]
5535 fn decode_chunk_bit_packing_matches_representative_full_quanta() {
5536 const SAMPLES: [u8; 16] = [
5537 0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
5538 ];
5539
5540 for first in SAMPLES {
5541 for second in SAMPLES {
5542 for third in SAMPLES {
5543 assert_standard_decode_chunk_matches_input(&[first, second, third]);
5544 }
5545 }
5546 }
5547 }
5548
5549 #[test]
5550 fn ct_padded_final_quantum_fails_closed_for_invalid_padding_count() {
5551 let (_, invalid_byte, invalid_padding, written) =
5552 ct_padded_final_quantum::<Standard>(*b"ABCD", 3);
5553
5554 assert_ne!(invalid_byte, 0);
5555 assert_ne!(invalid_padding, 0);
5556 assert_eq!(written, 0);
5557 assert_eq!(
5558 report_ct_error(invalid_byte, invalid_padding),
5559 Err(DecodeError::InvalidInput)
5560 );
5561 }
5562
5563 #[cfg(feature = "simd")]
5564 #[test]
5565 fn simd_dispatch_scaffold_keeps_scalar_active() {
5566 assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
5567 let _candidate = simd::detected_candidate();
5568 }
5569
5570 #[test]
5571 fn encodes_standard_vectors() {
5572 let vectors = [
5573 (&b""[..], &b""[..]),
5574 (&b"f"[..], &b"Zg=="[..]),
5575 (&b"fo"[..], &b"Zm8="[..]),
5576 (&b"foo"[..], &b"Zm9v"[..]),
5577 (&b"foob"[..], &b"Zm9vYg=="[..]),
5578 (&b"fooba"[..], &b"Zm9vYmE="[..]),
5579 (&b"foobar"[..], &b"Zm9vYmFy"[..]),
5580 ];
5581 for (input, expected) in vectors {
5582 let mut output = [0u8; 16];
5583 let written = STANDARD.encode_slice(input, &mut output).unwrap();
5584 assert_eq!(&output[..written], expected);
5585 }
5586 }
5587
5588 #[test]
5589 fn decodes_standard_vectors() {
5590 let vectors = [
5591 (&b""[..], &b""[..]),
5592 (&b"Zg=="[..], &b"f"[..]),
5593 (&b"Zm8="[..], &b"fo"[..]),
5594 (&b"Zm9v"[..], &b"foo"[..]),
5595 (&b"Zm9vYg=="[..], &b"foob"[..]),
5596 (&b"Zm9vYmE="[..], &b"fooba"[..]),
5597 (&b"Zm9vYmFy"[..], &b"foobar"[..]),
5598 ];
5599 for (input, expected) in vectors {
5600 let mut output = [0u8; 16];
5601 let written = STANDARD.decode_slice(input, &mut output).unwrap();
5602 assert_eq!(&output[..written], expected);
5603 }
5604 }
5605
5606 #[test]
5607 fn supports_unpadded_url_safe() {
5608 let mut encoded = [0u8; 16];
5609 let written = URL_SAFE_NO_PAD
5610 .encode_slice(b"\xfb\xff", &mut encoded)
5611 .unwrap();
5612 assert_eq!(&encoded[..written], b"-_8");
5613
5614 let mut decoded = [0u8; 2];
5615 let written = URL_SAFE_NO_PAD
5616 .decode_slice(&encoded[..written], &mut decoded)
5617 .unwrap();
5618 assert_eq!(&decoded[..written], b"\xfb\xff");
5619 }
5620
5621 #[test]
5622 fn decodes_in_place() {
5623 let mut buffer = *b"Zm9vYmFy";
5624 let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
5625 assert_eq!(decoded, b"foobar");
5626 }
5627
5628 #[test]
5629 fn rejects_non_canonical_padding_bits() {
5630 let mut output = [0u8; 4];
5631 assert_eq!(
5632 STANDARD.decode_slice(b"Zh==", &mut output),
5633 Err(DecodeError::InvalidPadding { index: 1 })
5634 );
5635 assert_eq!(
5636 STANDARD.decode_slice(b"Zm9=", &mut output),
5637 Err(DecodeError::InvalidPadding { index: 2 })
5638 );
5639 }
5640}