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
107#[cfg(feature = "simd")]
108mod simd;
109
110pub mod runtime {
116 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
118 #[non_exhaustive]
119 pub enum Backend {
120 Scalar,
122 Avx512Vbmi,
124 Avx2,
126 Ssse3Sse41,
128 Neon,
130 WasmSimd128,
132 }
133
134 impl Backend {
135 #[must_use]
141 pub const fn as_str(self) -> &'static str {
142 match self {
143 Self::Scalar => "scalar",
144 Self::Avx512Vbmi => "avx512-vbmi",
145 Self::Avx2 => "avx2",
146 Self::Ssse3Sse41 => "ssse3-sse4.1",
147 Self::Neon => "neon",
148 Self::WasmSimd128 => "wasm-simd128",
149 }
150 }
151
152 #[must_use]
165 pub const fn required_cpu_features(self) -> &'static [&'static str] {
166 match self {
167 Self::Scalar => &[],
168 Self::Avx512Vbmi => &["avx512f", "avx512bw", "avx512vl", "avx512vbmi"],
169 Self::Avx2 => &["avx2"],
170 Self::Ssse3Sse41 => &["ssse3", "sse4.1"],
171 Self::Neon => &["neon"],
172 Self::WasmSimd128 => &["simd128"],
173 }
174 }
175 }
176
177 impl core::fmt::Display for Backend {
178 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
179 formatter.write_str(self.as_str())
180 }
181 }
182
183 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
185 #[non_exhaustive]
186 pub enum CandidateDetectionMode {
187 SimdFeatureDisabled,
190 RuntimeCpuFeatures,
192 CompileTimeTargetFeatures,
197 }
198
199 impl CandidateDetectionMode {
200 #[must_use]
209 pub const fn as_str(self) -> &'static str {
210 match self {
211 Self::SimdFeatureDisabled => "simd-feature-disabled",
212 Self::RuntimeCpuFeatures => "runtime-cpu-features",
213 Self::CompileTimeTargetFeatures => "compile-time-target-features",
214 }
215 }
216 }
217
218 impl core::fmt::Display for CandidateDetectionMode {
219 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
220 formatter.write_str(self.as_str())
221 }
222 }
223
224 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
226 #[non_exhaustive]
227 pub enum SecurityPosture {
228 ScalarOnly,
230 SimdCandidateScalarActive,
232 Accelerated,
234 }
235
236 impl SecurityPosture {
237 #[must_use]
246 pub const fn as_str(self) -> &'static str {
247 match self {
248 Self::ScalarOnly => "scalar-only",
249 Self::SimdCandidateScalarActive => "simd-candidate-scalar-active",
250 Self::Accelerated => "accelerated",
251 }
252 }
253 }
254
255 impl core::fmt::Display for SecurityPosture {
256 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
257 formatter.write_str(self.as_str())
258 }
259 }
260
261 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
263 #[non_exhaustive]
264 pub enum WipePosture {
265 HardwareFence,
272 CompilerFenceOnly,
274 }
275
276 impl WipePosture {
277 #[must_use]
279 pub const fn as_str(self) -> &'static str {
280 match self {
281 Self::HardwareFence => "hardware-fence",
282 Self::CompilerFenceOnly => "compiler-fence-only",
283 }
284 }
285 }
286
287 impl core::fmt::Display for WipePosture {
288 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
289 formatter.write_str(self.as_str())
290 }
291 }
292
293 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
295 #[non_exhaustive]
296 pub enum CtGatePosture {
297 HardwareSpeculationBarrier,
304 OrderingFence,
307 CompilerFenceOnly,
309 }
310
311 impl CtGatePosture {
312 #[must_use]
314 pub const fn as_str(self) -> &'static str {
315 match self {
316 Self::HardwareSpeculationBarrier => "hardware-speculation-barrier",
317 Self::OrderingFence => "ordering-fence",
318 Self::CompilerFenceOnly => "compiler-fence-only",
319 }
320 }
321 }
322
323 impl core::fmt::Display for CtGatePosture {
324 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
325 formatter.write_str(self.as_str())
326 }
327 }
328
329 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
331 #[non_exhaustive]
332 pub enum BackendPolicy {
333 ScalarExecutionOnly,
335 SimdFeatureDisabled,
337 NoDetectedSimdCandidate,
339 HighAssuranceScalarOnly,
349 }
350
351 impl BackendPolicy {
352 #[must_use]
361 pub const fn as_str(self) -> &'static str {
362 match self {
363 Self::ScalarExecutionOnly => "scalar-execution-only",
364 Self::SimdFeatureDisabled => "simd-feature-disabled",
365 Self::NoDetectedSimdCandidate => "no-detected-simd-candidate",
366 Self::HighAssuranceScalarOnly => "high-assurance-scalar-only",
367 }
368 }
369 }
370
371 impl core::fmt::Display for BackendPolicy {
372 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
373 formatter.write_str(self.as_str())
374 }
375 }
376
377 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
379 pub struct BackendPolicyError {
380 pub policy: BackendPolicy,
382 pub report: BackendReport,
384 }
385
386 impl core::fmt::Display for BackendPolicyError {
387 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
388 write!(
389 formatter,
390 "runtime backend policy `{}` was not satisfied ({})",
391 self.policy, self.report,
392 )
393 }
394 }
395
396 #[cfg(feature = "std")]
397 impl std::error::Error for BackendPolicyError {}
398
399 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
401 pub struct BackendReport {
402 pub active: Backend,
404 pub candidate: Backend,
406 pub candidate_detection_mode: CandidateDetectionMode,
409 pub simd_feature_enabled: bool,
411 pub accelerated_backend_active: bool,
413 pub unsafe_boundary_enforced: bool,
420 pub security_posture: SecurityPosture,
422 pub wipe_posture: WipePosture,
424 pub ct_gate_posture: CtGatePosture,
426 }
427
428 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
430 pub struct BackendSnapshot {
431 pub active: &'static str,
433 pub candidate: &'static str,
435 pub candidate_detection_mode: &'static str,
437 pub candidate_required_cpu_features: &'static [&'static str],
439 pub simd_feature_enabled: bool,
441 pub accelerated_backend_active: bool,
443 pub unsafe_boundary_enforced: bool,
449 pub security_posture: &'static str,
451 pub wipe_posture: &'static str,
453 pub ct_gate_posture: &'static str,
455 }
456
457 impl core::fmt::Display for BackendReport {
458 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
459 write!(
460 formatter,
461 "active={} candidate={} candidate_detection_mode={} candidate_required_cpu_features=",
462 self.active, self.candidate, self.candidate_detection_mode,
463 )?;
464 write_feature_list(formatter, self.candidate_required_cpu_features())?;
465 write!(
466 formatter,
467 " simd_feature_enabled={} accelerated_backend_active={} unsafe_boundary_enforced={} security_posture={} wipe_posture={} ct_gate_posture={}",
468 self.simd_feature_enabled,
469 self.accelerated_backend_active,
470 self.unsafe_boundary_enforced,
471 self.security_posture,
472 self.wipe_posture,
473 self.ct_gate_posture,
474 )
475 }
476 }
477
478 impl BackendReport {
479 #[must_use]
489 pub const fn satisfies(self, policy: BackendPolicy) -> bool {
490 match policy {
491 BackendPolicy::ScalarExecutionOnly => {
492 matches!(self.active, Backend::Scalar) && !self.accelerated_backend_active
493 }
494 BackendPolicy::SimdFeatureDisabled => !self.simd_feature_enabled,
495 BackendPolicy::NoDetectedSimdCandidate => matches!(self.candidate, Backend::Scalar),
496 BackendPolicy::HighAssuranceScalarOnly => {
497 matches!(self.active, Backend::Scalar)
498 && matches!(self.candidate, Backend::Scalar)
499 && !self.simd_feature_enabled
500 && !self.accelerated_backend_active
501 && self.unsafe_boundary_enforced
502 && matches!(
503 self.ct_gate_posture,
504 CtGatePosture::HardwareSpeculationBarrier
505 )
506 }
507 }
508 }
509
510 #[must_use]
521 pub const fn candidate_required_cpu_features(self) -> &'static [&'static str] {
522 self.candidate.required_cpu_features()
523 }
524
525 #[must_use]
534 pub const fn snapshot(self) -> BackendSnapshot {
535 BackendSnapshot {
536 active: self.active.as_str(),
537 candidate: self.candidate.as_str(),
538 candidate_detection_mode: self.candidate_detection_mode.as_str(),
539 candidate_required_cpu_features: self.candidate_required_cpu_features(),
540 simd_feature_enabled: self.simd_feature_enabled,
541 accelerated_backend_active: self.accelerated_backend_active,
542 unsafe_boundary_enforced: self.unsafe_boundary_enforced,
543 security_posture: self.security_posture.as_str(),
544 wipe_posture: self.wipe_posture.as_str(),
545 ct_gate_posture: self.ct_gate_posture.as_str(),
546 }
547 }
548 }
549
550 #[must_use]
559 pub fn backend_report() -> BackendReport {
560 let active = active_backend();
561 let candidate = detected_candidate();
562 let candidate_detection_mode = candidate_detection_mode();
563 let accelerated_backend_active = active != Backend::Scalar;
564 let unsafe_boundary_enforced = !cfg!(feature = "simd");
565 let security_posture = if accelerated_backend_active {
566 SecurityPosture::Accelerated
567 } else if candidate != Backend::Scalar {
568 SecurityPosture::SimdCandidateScalarActive
569 } else {
570 SecurityPosture::ScalarOnly
571 };
572
573 BackendReport {
574 active,
575 candidate,
576 candidate_detection_mode,
577 simd_feature_enabled: cfg!(feature = "simd"),
578 accelerated_backend_active,
579 unsafe_boundary_enforced,
580 security_posture,
581 wipe_posture: wipe_posture(),
582 ct_gate_posture: ct_gate_posture(),
583 }
584 }
585
586 const fn wipe_posture() -> WipePosture {
587 if cfg!(any(
588 target_arch = "aarch64",
589 target_arch = "arm",
590 target_arch = "riscv32",
591 target_arch = "riscv64",
592 target_arch = "x86",
593 target_arch = "x86_64",
594 )) {
595 WipePosture::HardwareFence
596 } else {
597 WipePosture::CompilerFenceOnly
598 }
599 }
600
601 const fn ct_gate_posture() -> CtGatePosture {
602 if cfg!(any(
603 target_arch = "aarch64",
604 target_arch = "x86",
605 target_arch = "x86_64"
606 )) {
607 CtGatePosture::HardwareSpeculationBarrier
608 } else if cfg!(any(
609 target_arch = "arm",
610 target_arch = "riscv32",
611 target_arch = "riscv64"
612 )) {
613 CtGatePosture::OrderingFence
614 } else {
615 CtGatePosture::CompilerFenceOnly
616 }
617 }
618
619 pub fn require_backend_policy(policy: BackendPolicy) -> Result<(), BackendPolicyError> {
628 let report = backend_report();
629 if report.satisfies(policy) {
630 Ok(())
631 } else {
632 Err(BackendPolicyError { policy, report })
633 }
634 }
635
636 fn write_feature_list(
637 formatter: &mut core::fmt::Formatter<'_>,
638 features: &[&str],
639 ) -> core::fmt::Result {
640 formatter.write_str("[")?;
641 let mut index = 0;
642 while index < features.len() {
643 if index != 0 {
644 formatter.write_str(",")?;
645 }
646 formatter.write_str(features[index])?;
647 index += 1;
648 }
649 formatter.write_str("]")
650 }
651
652 #[cfg(feature = "simd")]
653 fn active_backend() -> Backend {
654 match super::simd::active_backend() {
655 super::simd::ActiveBackend::Scalar => Backend::Scalar,
656 }
657 }
658
659 #[cfg(not(feature = "simd"))]
660 const fn active_backend() -> Backend {
661 Backend::Scalar
662 }
663
664 #[cfg(feature = "simd")]
665 fn detected_candidate() -> Backend {
666 match super::simd::detected_candidate() {
667 super::simd::Candidate::Scalar => Backend::Scalar,
668 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
669 super::simd::Candidate::Avx512Vbmi => Backend::Avx512Vbmi,
670 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
671 super::simd::Candidate::Avx2 => Backend::Avx2,
672 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
673 super::simd::Candidate::Ssse3Sse41 => Backend::Ssse3Sse41,
674 #[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
675 super::simd::Candidate::Neon => Backend::Neon,
676 #[cfg(target_arch = "wasm32")]
677 super::simd::Candidate::WasmSimd128 => Backend::WasmSimd128,
678 }
679 }
680
681 #[cfg(not(feature = "simd"))]
682 const fn detected_candidate() -> Backend {
683 Backend::Scalar
684 }
685
686 #[cfg(all(
687 feature = "simd",
688 feature = "std",
689 any(target_arch = "x86", target_arch = "x86_64")
690 ))]
691 const fn candidate_detection_mode() -> CandidateDetectionMode {
692 CandidateDetectionMode::RuntimeCpuFeatures
693 }
694
695 #[cfg(all(
696 feature = "simd",
697 not(all(feature = "std", any(target_arch = "x86", target_arch = "x86_64")))
698 ))]
699 const fn candidate_detection_mode() -> CandidateDetectionMode {
700 CandidateDetectionMode::CompileTimeTargetFeatures
701 }
702
703 #[cfg(not(feature = "simd"))]
704 const fn candidate_detection_mode() -> CandidateDetectionMode {
705 CandidateDetectionMode::SimdFeatureDisabled
706 }
707}
708
709#[cfg(feature = "stream")]
710pub mod stream;
711
712pub mod ct {
757 use super::{
758 Alphabet, DecodeError, DecodedBuffer, Standard, UrlSafe, ct_decode_in_place,
759 ct_decode_slice, ct_decode_slice_staged_clear_tail, ct_decoded_len, ct_validate_decode,
760 };
761 use core::marker::PhantomData;
762
763 pub const STANDARD: CtEngine<Standard, true> = CtEngine::new();
765
766 pub const STANDARD_NO_PAD: CtEngine<Standard, false> = CtEngine::new();
768
769 pub const URL_SAFE: CtEngine<UrlSafe, true> = CtEngine::new();
771
772 pub const URL_SAFE_NO_PAD: CtEngine<UrlSafe, false> = CtEngine::new();
774
775 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
788 pub struct CtEngine<A, const PAD: bool> {
789 alphabet: PhantomData<A>,
790 }
791
792 impl<A, const PAD: bool> CtEngine<A, PAD>
793 where
794 A: Alphabet,
795 {
796 #[must_use]
798 pub const fn new() -> Self {
799 Self {
800 alphabet: PhantomData,
801 }
802 }
803
804 #[must_use]
807 pub const fn is_padded(&self) -> bool {
808 PAD
809 }
810
811 pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
827 ct_validate_decode::<A, PAD>(input)
828 }
829
830 #[must_use]
844 pub fn validate(&self, input: &[u8]) -> bool {
845 self.validate_result(input).is_ok()
846 }
847
848 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
854 ct_decoded_len::<A, PAD>(input)
855 }
856
857 #[must_use = "handle decode errors; use decode_slice_staged_clear_tail for shared-memory or HSM-style threat models"]
891 pub fn decode_slice_clear_tail(
892 &self,
893 input: &[u8],
894 output: &mut [u8],
895 ) -> Result<usize, DecodeError> {
896 let written = match ct_decode_slice::<A, PAD>(input, output) {
897 Ok(written) => written,
898 Err(err) => {
899 crate::wipe_bytes(output);
900 return Err(err);
901 }
902 };
903 crate::wipe_tail(output, written);
904 Ok(written)
905 }
906
907 #[must_use = "handle decode errors; staged decode is for shared-memory or HSM-style threat models"]
921 pub fn decode_slice_staged_clear_tail(
922 &self,
923 input: &[u8],
924 output: &mut [u8],
925 staging: &mut [u8],
926 ) -> Result<usize, DecodeError> {
927 ct_decode_slice_staged_clear_tail::<A, PAD>(input, output, staging)
928 }
929
930 pub fn decode_buffer<const CAP: usize>(
946 &self,
947 input: &[u8],
948 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
949 let mut output = DecodedBuffer::new();
950 let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
951 Ok(written) => written,
952 Err(err) => {
953 output.clear();
954 return Err(err);
955 }
956 };
957 output.len = written;
958 Ok(output)
959 }
960
961 pub fn decode_in_place_clear_tail<'a>(
989 &self,
990 buffer: &'a mut [u8],
991 ) -> Result<&'a mut [u8], DecodeError> {
992 let len = match ct_decode_in_place::<A, PAD>(buffer) {
993 Ok(len) => len,
994 Err(err) => {
995 crate::wipe_bytes(buffer);
996 return Err(err);
997 }
998 };
999 crate::wipe_tail(buffer, len);
1000 Ok(&mut buffer[..len])
1001 }
1002 }
1003
1004 impl<A, const PAD: bool> core::fmt::Display for CtEngine<A, PAD> {
1005 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1006 write!(formatter, "ct padded={PAD}")
1007 }
1008 }
1009}
1010
1011#[doc(alias = "ct")]
1017#[doc(alias = "constant_time")]
1018#[doc(alias = "sensitive")]
1019pub const STANDARD: Engine<Standard, true> = Engine::new();
1020
1021#[doc(alias = "ct")]
1028#[doc(alias = "constant_time")]
1029#[doc(alias = "sensitive")]
1030pub const STANDARD_NO_PAD: Engine<Standard, false> = Engine::new();
1031
1032#[doc(alias = "ct")]
1038#[doc(alias = "constant_time")]
1039#[doc(alias = "sensitive")]
1040pub const URL_SAFE: Engine<UrlSafe, true> = Engine::new();
1041
1042#[doc(alias = "ct")]
1049#[doc(alias = "constant_time")]
1050#[doc(alias = "sensitive")]
1051pub const URL_SAFE_NO_PAD: Engine<UrlSafe, false> = Engine::new();
1052
1053#[doc(alias = "ct")]
1061#[doc(alias = "constant_time")]
1062#[doc(alias = "sensitive")]
1063pub const BCRYPT_NO_PAD: Engine<Bcrypt, false> = Engine::new();
1064
1065#[doc(alias = "ct")]
1073#[doc(alias = "constant_time")]
1074#[doc(alias = "sensitive")]
1075pub const CRYPT_NO_PAD: Engine<Crypt, false> = Engine::new();
1076
1077#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1079pub enum LineEnding {
1080 Lf,
1082 CrLf,
1084}
1085
1086impl LineEnding {
1087 #[must_use]
1089 pub const fn name(self) -> &'static str {
1090 match self {
1091 Self::Lf => "LF",
1092 Self::CrLf => "CRLF",
1093 }
1094 }
1095
1096 #[must_use]
1098 pub const fn as_str(self) -> &'static str {
1099 match self {
1100 Self::Lf => "\n",
1101 Self::CrLf => "\r\n",
1102 }
1103 }
1104
1105 #[must_use]
1107 pub const fn as_bytes(self) -> &'static [u8] {
1108 self.as_str().as_bytes()
1109 }
1110
1111 #[must_use]
1113 pub const fn byte_len(self) -> usize {
1114 match self {
1115 Self::Lf => 1,
1116 Self::CrLf => 2,
1117 }
1118 }
1119}
1120
1121impl core::fmt::Display for LineEnding {
1122 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1123 formatter.write_str(self.name())
1124 }
1125}
1126
1127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1133pub struct LineWrap {
1134 pub line_len: usize,
1136 pub line_ending: LineEnding,
1138}
1139
1140impl LineWrap {
1141 pub const MIME: Self = Self::new(76, LineEnding::CrLf);
1143 pub const PEM: Self = Self::new(64, LineEnding::Lf);
1145 pub const PEM_CRLF: Self = Self::new(64, LineEnding::CrLf);
1147
1148 #[must_use]
1163 pub const fn new(line_len: usize, line_ending: LineEnding) -> Self {
1164 assert!(line_len != 0, "base64 line wrap length must be non-zero");
1165 Self {
1166 line_len,
1167 line_ending,
1168 }
1169 }
1170
1171 #[must_use]
1178 pub const fn checked_new(line_len: usize, line_ending: LineEnding) -> Option<Self> {
1179 if line_len == 0 {
1180 None
1181 } else {
1182 Some(Self::new(line_len, line_ending))
1183 }
1184 }
1185
1186 #[must_use]
1188 pub const fn line_len(self) -> usize {
1189 self.line_len
1190 }
1191
1192 #[must_use]
1194 pub const fn line_ending(self) -> LineEnding {
1195 self.line_ending
1196 }
1197
1198 #[must_use]
1200 pub const fn is_valid(self) -> bool {
1201 self.line_len != 0
1202 }
1203}
1204
1205impl core::fmt::Display for LineWrap {
1206 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1207 write!(formatter, "{}:{}", self.line_len, self.line_ending.name())
1208 }
1209}
1210
1211#[inline(never)]
1212#[allow(unsafe_code)]
1213fn wipe_bytes(bytes: &mut [u8]) {
1214 for byte in bytes.iter_mut() {
1215 unsafe {
1219 core::ptr::write_volatile(byte, 0);
1220 }
1221 }
1222 wipe_barrier(bytes.as_mut_ptr(), bytes.len());
1223}
1224
1225#[inline(never)]
1226#[allow(unsafe_code)]
1227fn wipe_barrier(ptr: *mut u8, len: usize) {
1228 let _ = (ptr, len);
1229
1230 #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
1231 {
1232 unsafe {
1236 core::arch::asm!(
1237 "mfence",
1238 "/* {0} {1} */",
1239 in(reg) ptr,
1240 in(reg) len,
1241 options(nostack, preserves_flags)
1242 );
1243 }
1244 }
1245
1246 #[cfg(all(not(miri), target_arch = "aarch64"))]
1247 {
1248 unsafe {
1252 core::arch::asm!(
1253 "dsb sy",
1254 "isb sy",
1255 "hint #20",
1256 "/* {0} {1} */",
1257 in(reg) ptr,
1258 in(reg) len,
1259 options(nostack, preserves_flags)
1260 );
1261 }
1262 }
1263
1264 #[cfg(all(not(miri), target_arch = "arm"))]
1265 {
1266 unsafe {
1270 core::arch::asm!(
1271 "dsb sy",
1272 "isb sy",
1273 "/* {0} {1} */",
1274 in(reg) ptr,
1275 in(reg) len,
1276 options(nostack, preserves_flags)
1277 );
1278 }
1279 }
1280
1281 #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
1282 {
1283 unsafe {
1286 core::arch::asm!(
1287 "fence rw, rw",
1288 "/* {0} {1} */",
1289 in(reg) ptr,
1290 in(reg) len,
1291 options(nostack, preserves_flags)
1292 );
1293 }
1294 }
1295
1296 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
1297}
1298
1299fn wipe_tail(bytes: &mut [u8], start: usize) {
1300 wipe_bytes(&mut bytes[start..]);
1301}
1302
1303#[cfg(feature = "alloc")]
1304#[allow(unsafe_code)]
1305fn wipe_vec_spare_capacity(bytes: &mut alloc::vec::Vec<u8>) {
1306 let ptr = bytes.as_mut_ptr();
1307 let len = bytes.len();
1308 let capacity = bytes.capacity();
1309 let spare = capacity - len;
1310 if spare == 0 {
1311 return;
1312 }
1313
1314 let mut offset = len;
1315 while offset < capacity {
1316 unsafe {
1320 core::ptr::write_volatile(ptr.add(offset), 0);
1321 }
1322 offset += 1;
1323 }
1324 let spare_ptr = unsafe { ptr.add(len) };
1327 wipe_barrier(spare_ptr, spare);
1328}
1329
1330#[cfg(feature = "alloc")]
1331fn wipe_vec_all(bytes: &mut alloc::vec::Vec<u8>) {
1332 wipe_bytes(bytes);
1333 wipe_vec_spare_capacity(bytes);
1334}
1335
1336pub struct EncodedBuffer<const CAP: usize> {
1353 bytes: [u8; CAP],
1354 len: usize,
1355}
1356
1357pub struct ExposedEncodedArray<const CAP: usize> {
1364 bytes: [u8; CAP],
1365 len: usize,
1366}
1367
1368impl<const CAP: usize> ExposedEncodedArray<CAP> {
1369 #[must_use]
1375 pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
1376 assert!(len <= CAP, "visible length exceeds array capacity");
1377 Self { bytes, len }
1378 }
1379
1380 #[must_use]
1382 pub fn as_bytes(&self) -> &[u8] {
1383 &self.bytes[..self.len]
1384 }
1385
1386 #[must_use]
1388 pub const fn len(&self) -> usize {
1389 self.len
1390 }
1391
1392 #[must_use]
1394 pub const fn is_empty(&self) -> bool {
1395 self.len == 0
1396 }
1397
1398 #[must_use]
1400 pub const fn capacity(&self) -> usize {
1401 CAP
1402 }
1403
1404 #[must_use = "caller must zeroize the returned array"]
1416 pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
1417 let len = self.len;
1418 self.len = 0;
1419 (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1420 }
1421}
1422
1423impl<const CAP: usize> Drop for ExposedEncodedArray<CAP> {
1424 fn drop(&mut self) {
1425 wipe_bytes(&mut self.bytes);
1426 self.len = 0;
1427 }
1428}
1429
1430impl<const CAP: usize> core::fmt::Debug for ExposedEncodedArray<CAP> {
1431 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1432 formatter
1433 .debug_struct("ExposedEncodedArray")
1434 .field("bytes", &"<redacted>")
1435 .field("len", &self.len)
1436 .field("capacity", &CAP)
1437 .finish()
1438 }
1439}
1440
1441impl<const CAP: usize> EncodedBuffer<CAP> {
1442 #[must_use]
1444 pub const fn new() -> Self {
1445 Self {
1446 bytes: [0u8; CAP],
1447 len: 0,
1448 }
1449 }
1450
1451 #[must_use]
1453 pub const fn len(&self) -> usize {
1454 self.len
1455 }
1456
1457 #[must_use]
1459 pub const fn is_empty(&self) -> bool {
1460 self.len == 0
1461 }
1462
1463 #[must_use]
1465 pub const fn is_full(&self) -> bool {
1466 self.len == CAP
1467 }
1468
1469 #[must_use]
1471 pub const fn capacity(&self) -> usize {
1472 CAP
1473 }
1474
1475 #[must_use]
1477 pub const fn remaining_capacity(&self) -> usize {
1478 CAP - self.len
1479 }
1480
1481 #[must_use]
1483 pub fn as_bytes(&self) -> &[u8] {
1484 &self.bytes[..self.len]
1485 }
1486
1487 pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1494 core::str::from_utf8(self.as_bytes())
1495 }
1496
1497 #[must_use]
1504 pub fn as_str(&self) -> &str {
1505 match self.as_utf8() {
1506 Ok(output) => output,
1507 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
1508 }
1509 }
1510
1511 #[doc(alias = "constant_time_eq")]
1530 #[must_use]
1531 pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1532 constant_time_eq_public_len(self.as_bytes(), other)
1533 }
1534
1535 #[must_use]
1543 pub fn into_exposed_array(mut self) -> ExposedEncodedArray<CAP> {
1544 let len = self.len;
1545 self.len = 0;
1546 ExposedEncodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1547 }
1548
1549 pub fn clear(&mut self) {
1551 wipe_bytes(&mut self.bytes);
1552 self.len = 0;
1553 }
1554
1555 pub fn clear_tail(&mut self) {
1557 wipe_tail(&mut self.bytes, self.len);
1558 }
1559}
1560
1561impl<const CAP: usize> AsRef<[u8]> for EncodedBuffer<CAP> {
1562 fn as_ref(&self) -> &[u8] {
1563 self.as_bytes()
1564 }
1565}
1566
1567impl<const CAP: usize> Clone for EncodedBuffer<CAP> {
1568 fn clone(&self) -> Self {
1578 let mut output = Self::new();
1579 output.bytes[..self.len].copy_from_slice(self.as_bytes());
1580 output.len = self.len;
1581 output
1582 }
1583}
1584
1585impl<const CAP: usize> core::fmt::Debug for EncodedBuffer<CAP> {
1586 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1587 formatter
1588 .debug_struct("EncodedBuffer")
1589 .field("bytes", &"<redacted>")
1590 .field("len", &self.len)
1591 .field("capacity", &CAP)
1592 .finish()
1593 }
1594}
1595
1596impl<const CAP: usize> core::fmt::Display for EncodedBuffer<CAP> {
1597 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1603 formatter.write_str(self.as_str())
1604 }
1605}
1606
1607impl<const CAP: usize> Default for EncodedBuffer<CAP> {
1608 fn default() -> Self {
1609 Self::new()
1610 }
1611}
1612
1613impl<const CAP: usize> Drop for EncodedBuffer<CAP> {
1614 fn drop(&mut self) {
1615 self.clear();
1616 }
1617}
1618
1619impl<const CAP: usize> TryFrom<&[u8]> for EncodedBuffer<CAP> {
1620 type Error = EncodeError;
1621
1622 fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1628 STANDARD.encode_buffer(input)
1629 }
1630}
1631
1632impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for EncodedBuffer<CAP> {
1633 type Error = EncodeError;
1634
1635 fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1641 Self::try_from(&input[..])
1642 }
1643}
1644
1645impl<const CAP: usize> TryFrom<&str> for EncodedBuffer<CAP> {
1646 type Error = EncodeError;
1647
1648 fn try_from(input: &str) -> Result<Self, Self::Error> {
1655 Self::try_from(input.as_bytes())
1656 }
1657}
1658
1659pub struct DecodedBuffer<const CAP: usize> {
1676 bytes: [u8; CAP],
1677 len: usize,
1678}
1679
1680pub struct ExposedDecodedArray<const CAP: usize> {
1687 bytes: [u8; CAP],
1688 len: usize,
1689}
1690
1691impl<const CAP: usize> ExposedDecodedArray<CAP> {
1692 #[must_use]
1698 pub const fn from_array(bytes: [u8; CAP], len: usize) -> Self {
1699 assert!(len <= CAP, "visible length exceeds array capacity");
1700 Self { bytes, len }
1701 }
1702
1703 #[must_use]
1705 pub fn as_bytes(&self) -> &[u8] {
1706 &self.bytes[..self.len]
1707 }
1708
1709 #[must_use]
1711 pub const fn len(&self) -> usize {
1712 self.len
1713 }
1714
1715 #[must_use]
1717 pub const fn is_empty(&self) -> bool {
1718 self.len == 0
1719 }
1720
1721 #[must_use]
1723 pub const fn capacity(&self) -> usize {
1724 CAP
1725 }
1726
1727 #[must_use = "caller must zeroize the returned array"]
1739 pub fn into_exposed_unprotected_array_caller_must_zeroize(mut self) -> ([u8; CAP], usize) {
1740 let len = self.len;
1741 self.len = 0;
1742 (core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1743 }
1744}
1745
1746impl<const CAP: usize> Drop for ExposedDecodedArray<CAP> {
1747 fn drop(&mut self) {
1748 wipe_bytes(&mut self.bytes);
1749 self.len = 0;
1750 }
1751}
1752
1753impl<const CAP: usize> core::fmt::Debug for ExposedDecodedArray<CAP> {
1754 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1755 formatter
1756 .debug_struct("ExposedDecodedArray")
1757 .field("bytes", &"<redacted>")
1758 .field("len", &self.len)
1759 .field("capacity", &CAP)
1760 .finish()
1761 }
1762}
1763
1764impl<const CAP: usize> DecodedBuffer<CAP> {
1765 #[must_use]
1767 pub const fn new() -> Self {
1768 Self {
1769 bytes: [0u8; CAP],
1770 len: 0,
1771 }
1772 }
1773
1774 #[must_use]
1776 pub const fn len(&self) -> usize {
1777 self.len
1778 }
1779
1780 #[must_use]
1782 pub const fn is_empty(&self) -> bool {
1783 self.len == 0
1784 }
1785
1786 #[must_use]
1788 pub const fn is_full(&self) -> bool {
1789 self.len == CAP
1790 }
1791
1792 #[must_use]
1794 pub const fn capacity(&self) -> usize {
1795 CAP
1796 }
1797
1798 #[must_use]
1800 pub const fn remaining_capacity(&self) -> usize {
1801 CAP - self.len
1802 }
1803
1804 #[must_use]
1806 pub fn as_bytes(&self) -> &[u8] {
1807 &self.bytes[..self.len]
1808 }
1809
1810 pub fn as_utf8(&self) -> Result<&str, core::str::Utf8Error> {
1816 core::str::from_utf8(self.as_bytes())
1817 }
1818
1819 #[doc(alias = "constant_time_eq")]
1838 #[must_use]
1839 pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
1840 constant_time_eq_public_len(self.as_bytes(), other)
1841 }
1842
1843 #[must_use]
1851 pub fn into_exposed_array(mut self) -> ExposedDecodedArray<CAP> {
1852 let len = self.len;
1853 self.len = 0;
1854 ExposedDecodedArray::from_array(core::mem::replace(&mut self.bytes, [0u8; CAP]), len)
1855 }
1856
1857 pub fn clear(&mut self) {
1859 wipe_bytes(&mut self.bytes);
1860 self.len = 0;
1861 }
1862
1863 pub fn clear_tail(&mut self) {
1865 wipe_tail(&mut self.bytes, self.len);
1866 }
1867}
1868
1869impl<const CAP: usize> AsRef<[u8]> for DecodedBuffer<CAP> {
1870 fn as_ref(&self) -> &[u8] {
1871 self.as_bytes()
1872 }
1873}
1874
1875impl<const CAP: usize> Clone for DecodedBuffer<CAP> {
1876 fn clone(&self) -> Self {
1886 let mut output = Self::new();
1887 output.bytes[..self.len].copy_from_slice(self.as_bytes());
1888 output.len = self.len;
1889 output
1890 }
1891}
1892
1893impl<const CAP: usize> core::fmt::Debug for DecodedBuffer<CAP> {
1894 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1895 formatter
1896 .debug_struct("DecodedBuffer")
1897 .field("bytes", &"<redacted>")
1898 .field("len", &self.len)
1899 .field("capacity", &CAP)
1900 .finish()
1901 }
1902}
1903
1904impl<const CAP: usize> Default for DecodedBuffer<CAP> {
1905 fn default() -> Self {
1906 Self::new()
1907 }
1908}
1909
1910impl<const CAP: usize> Drop for DecodedBuffer<CAP> {
1911 fn drop(&mut self) {
1912 self.clear();
1913 }
1914}
1915
1916impl<const CAP: usize> TryFrom<&[u8]> for DecodedBuffer<CAP> {
1917 type Error = DecodeError;
1918
1919 fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
1924 STANDARD.decode_buffer(input)
1925 }
1926}
1927
1928impl<const CAP: usize, const N: usize> TryFrom<&[u8; N]> for DecodedBuffer<CAP> {
1929 type Error = DecodeError;
1930
1931 fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
1937 Self::try_from(&input[..])
1938 }
1939}
1940
1941impl<const CAP: usize> TryFrom<&str> for DecodedBuffer<CAP> {
1942 type Error = DecodeError;
1943
1944 fn try_from(input: &str) -> Result<Self, Self::Error> {
1949 Self::try_from(input.as_bytes())
1950 }
1951}
1952
1953impl<const CAP: usize> core::str::FromStr for DecodedBuffer<CAP> {
1954 type Err = DecodeError;
1955
1956 fn from_str(input: &str) -> Result<Self, Self::Err> {
1961 Self::try_from(input)
1962 }
1963}
1964
1965#[cfg(feature = "alloc")]
1993pub struct SecretBuffer {
1994 bytes: alloc::vec::Vec<u8>,
1995}
1996
1997#[cfg(feature = "alloc")]
2005pub struct ExposedSecretVec {
2006 bytes: alloc::vec::Vec<u8>,
2007}
2008
2009#[cfg(feature = "alloc")]
2010impl ExposedSecretVec {
2011 #[must_use]
2013 pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
2014 wipe_vec_spare_capacity(&mut bytes);
2015 Self { bytes }
2016 }
2017
2018 #[must_use]
2020 pub fn len(&self) -> usize {
2021 self.bytes.len()
2022 }
2023
2024 #[must_use]
2026 pub fn is_empty(&self) -> bool {
2027 self.bytes.is_empty()
2028 }
2029
2030 #[must_use]
2035 pub fn expose_secret(&self) -> &[u8] {
2036 &self.bytes
2037 }
2038
2039 #[must_use]
2044 pub fn expose_secret_mut(&mut self) -> &mut [u8] {
2045 &mut self.bytes
2046 }
2047
2048 #[must_use = "caller must zeroize the returned Vec"]
2054 pub fn into_exposed_unprotected_vec_caller_must_zeroize(mut self) -> alloc::vec::Vec<u8> {
2055 core::mem::take(&mut self.bytes)
2056 }
2057}
2058
2059#[cfg(feature = "alloc")]
2060impl core::fmt::Debug for ExposedSecretVec {
2061 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2062 formatter
2063 .debug_struct("ExposedSecretVec")
2064 .field("bytes", &"<redacted>")
2065 .field("len", &self.len())
2066 .finish()
2067 }
2068}
2069
2070#[cfg(feature = "alloc")]
2071impl core::fmt::Display for ExposedSecretVec {
2072 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2073 formatter.write_str("<redacted>")
2074 }
2075}
2076
2077#[cfg(feature = "alloc")]
2078impl Drop for ExposedSecretVec {
2079 fn drop(&mut self) {
2080 wipe_vec_all(&mut self.bytes);
2081 }
2082}
2083
2084#[cfg(feature = "alloc")]
2085struct WipeVecGuard {
2086 bytes: alloc::vec::Vec<u8>,
2087}
2088
2089#[cfg(feature = "alloc")]
2090impl WipeVecGuard {
2091 fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
2092 Self { bytes }
2093 }
2094
2095 fn into_validated_secret_string(mut self) -> alloc::string::String {
2096 wipe_vec_spare_capacity(&mut self.bytes);
2097 let bytes = core::mem::take(&mut self.bytes);
2098 core::mem::forget(self);
2099 string_from_validated_secret_bytes(bytes)
2100 }
2101}
2102
2103#[cfg(feature = "alloc")]
2104impl Drop for WipeVecGuard {
2105 fn drop(&mut self) {
2106 wipe_vec_all(&mut self.bytes);
2107 }
2108}
2109
2110#[cfg(feature = "alloc")]
2111impl AsRef<[u8]> for ExposedSecretVec {
2112 fn as_ref(&self) -> &[u8] {
2113 self.expose_secret()
2114 }
2115}
2116
2117#[cfg(feature = "alloc")]
2118impl AsMut<[u8]> for ExposedSecretVec {
2119 fn as_mut(&mut self) -> &mut [u8] {
2120 self.expose_secret_mut()
2121 }
2122}
2123
2124#[cfg(feature = "alloc")]
2132pub struct ExposedSecretString {
2133 text: alloc::string::String,
2134}
2135
2136#[cfg(feature = "alloc")]
2137impl ExposedSecretString {
2138 #[must_use]
2140 pub fn from_string(text: alloc::string::String) -> Self {
2141 let mut bytes = text.into_bytes();
2142 wipe_vec_spare_capacity(&mut bytes);
2143 let text = string_from_validated_secret_bytes(bytes);
2144 Self { text }
2145 }
2146
2147 #[must_use]
2149 pub fn len(&self) -> usize {
2150 self.text.len()
2151 }
2152
2153 #[must_use]
2155 pub fn is_empty(&self) -> bool {
2156 self.text.is_empty()
2157 }
2158
2159 #[must_use]
2164 pub fn expose_secret(&self) -> &str {
2165 &self.text
2166 }
2167
2168 #[must_use]
2173 pub fn expose_secret_bytes(&self) -> &[u8] {
2174 self.text.as_bytes()
2175 }
2176
2177 #[must_use = "caller must zeroize the returned String"]
2183 pub fn into_exposed_unprotected_string_caller_must_zeroize(mut self) -> alloc::string::String {
2184 core::mem::take(&mut self.text)
2185 }
2186}
2187
2188#[cfg(feature = "alloc")]
2189impl core::fmt::Debug for ExposedSecretString {
2190 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2191 formatter
2192 .debug_struct("ExposedSecretString")
2193 .field("text", &"<redacted>")
2194 .field("len", &self.len())
2195 .finish()
2196 }
2197}
2198
2199#[cfg(feature = "alloc")]
2200impl core::fmt::Display for ExposedSecretString {
2201 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2202 formatter.write_str("<redacted>")
2203 }
2204}
2205
2206#[cfg(feature = "alloc")]
2207impl Drop for ExposedSecretString {
2208 fn drop(&mut self) {
2209 let mut bytes = core::mem::take(&mut self.text).into_bytes();
2210 wipe_vec_all(&mut bytes);
2211 }
2212}
2213
2214#[cfg(feature = "alloc")]
2215impl AsRef<str> for ExposedSecretString {
2216 fn as_ref(&self) -> &str {
2217 self.expose_secret()
2218 }
2219}
2220
2221#[cfg(feature = "alloc")]
2222impl SecretBuffer {
2223 #[must_use]
2225 pub fn from_vec(mut bytes: alloc::vec::Vec<u8>) -> Self {
2226 wipe_vec_spare_capacity(&mut bytes);
2227 Self { bytes }
2228 }
2229
2230 #[must_use]
2232 pub fn from_slice(bytes: &[u8]) -> Self {
2233 Self::from_vec(bytes.to_vec())
2234 }
2235
2236 #[must_use]
2238 pub fn len(&self) -> usize {
2239 self.bytes.len()
2240 }
2241
2242 #[must_use]
2244 pub fn is_empty(&self) -> bool {
2245 self.bytes.is_empty()
2246 }
2247
2248 #[must_use]
2253 pub fn expose_secret(&self) -> &[u8] {
2254 &self.bytes
2255 }
2256
2257 pub fn expose_secret_utf8(&self) -> Result<&str, core::str::Utf8Error> {
2263 core::str::from_utf8(self.expose_secret())
2264 }
2265
2266 #[must_use]
2271 pub fn expose_secret_mut(&mut self) -> &mut [u8] {
2272 &mut self.bytes
2273 }
2274
2275 #[must_use]
2281 pub fn into_exposed_vec(mut self) -> ExposedSecretVec {
2282 ExposedSecretVec::from_vec(core::mem::take(&mut self.bytes))
2283 }
2284
2285 #[must_use = "handle invalid UTF-8 errors and keep the returned wrapper protected"]
2294 pub fn try_into_exposed_string(self) -> Result<ExposedSecretString, Self> {
2295 if core::str::from_utf8(self.expose_secret()).is_err() {
2296 return Err(self);
2297 }
2298
2299 let mut exposed = self.into_exposed_vec();
2302 let guard = WipeVecGuard::from_vec(core::mem::take(&mut exposed.bytes));
2303 drop(exposed);
2304 Ok(ExposedSecretString::from_string(
2305 guard.into_validated_secret_string(),
2306 ))
2307 }
2308
2309 #[doc(alias = "constant_time_eq")]
2328 #[must_use]
2329 pub fn constant_time_eq_public_len(&self, other: &[u8]) -> bool {
2330 constant_time_eq_public_len(self.expose_secret(), other)
2331 }
2332
2333 pub fn clear(&mut self) {
2335 self.bytes.clear();
2336 wipe_vec_all(&mut self.bytes);
2337 }
2338}
2339
2340#[cfg(feature = "alloc")]
2341impl core::fmt::Debug for SecretBuffer {
2342 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2343 formatter
2344 .debug_struct("SecretBuffer")
2345 .field("bytes", &"<redacted>")
2346 .field("len", &self.len())
2347 .finish()
2348 }
2349}
2350
2351#[cfg(feature = "alloc")]
2352impl core::fmt::Display for SecretBuffer {
2353 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2354 formatter.write_str("<redacted>")
2355 }
2356}
2357
2358#[cfg(feature = "alloc")]
2359impl Drop for SecretBuffer {
2360 fn drop(&mut self) {
2361 wipe_vec_all(&mut self.bytes);
2362 }
2363}
2364
2365#[cfg(feature = "alloc")]
2366impl From<alloc::vec::Vec<u8>> for SecretBuffer {
2367 fn from(bytes: alloc::vec::Vec<u8>) -> Self {
2372 Self::from_vec(bytes)
2373 }
2374}
2375
2376#[cfg(feature = "alloc")]
2377impl From<alloc::string::String> for SecretBuffer {
2378 fn from(text: alloc::string::String) -> Self {
2383 Self::from_vec(text.into_bytes())
2384 }
2385}
2386
2387#[cfg(feature = "alloc")]
2388impl<const CAP: usize> From<EncodedBuffer<CAP>> for SecretBuffer {
2389 fn from(buffer: EncodedBuffer<CAP>) -> Self {
2395 Self::from_slice(buffer.as_bytes())
2396 }
2397}
2398
2399#[cfg(feature = "alloc")]
2400impl<const CAP: usize> From<DecodedBuffer<CAP>> for SecretBuffer {
2401 fn from(buffer: DecodedBuffer<CAP>) -> Self {
2407 Self::from_slice(buffer.as_bytes())
2408 }
2409}
2410
2411#[cfg(feature = "alloc")]
2412impl TryFrom<&[u8]> for SecretBuffer {
2413 type Error = DecodeError;
2414
2415 fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
2420 STANDARD.decode_secret(input)
2421 }
2422}
2423
2424#[cfg(feature = "alloc")]
2425impl<const N: usize> TryFrom<&[u8; N]> for SecretBuffer {
2426 type Error = DecodeError;
2427
2428 fn try_from(input: &[u8; N]) -> Result<Self, Self::Error> {
2434 Self::try_from(&input[..])
2435 }
2436}
2437
2438#[cfg(feature = "alloc")]
2439impl TryFrom<&str> for SecretBuffer {
2440 type Error = DecodeError;
2441
2442 fn try_from(input: &str) -> Result<Self, Self::Error> {
2447 Self::try_from(input.as_bytes())
2448 }
2449}
2450
2451#[cfg(feature = "alloc")]
2452impl core::str::FromStr for SecretBuffer {
2453 type Err = DecodeError;
2454
2455 fn from_str(input: &str) -> Result<Self, Self::Err> {
2460 Self::try_from(input)
2461 }
2462}
2463
2464#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2470pub struct Profile<A, const PAD: bool> {
2471 engine: Engine<A, PAD>,
2472 wrap: Option<LineWrap>,
2473}
2474
2475impl<A, const PAD: bool> Profile<A, PAD>
2476where
2477 A: Alphabet,
2478{
2479 #[must_use]
2481 pub const fn new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Self {
2482 Self { engine, wrap }
2483 }
2484
2485 #[must_use]
2491 pub const fn checked_new(engine: Engine<A, PAD>, wrap: Option<LineWrap>) -> Option<Self> {
2492 match wrap {
2493 Some(wrap) if !wrap.is_valid() => None,
2494 _ => Some(Self::new(engine, wrap)),
2495 }
2496 }
2497
2498 #[must_use]
2500 pub const fn is_valid(&self) -> bool {
2501 match self.wrap {
2502 Some(wrap) => wrap.is_valid(),
2503 None => true,
2504 }
2505 }
2506
2507 #[must_use]
2509 pub const fn engine(&self) -> Engine<A, PAD> {
2510 self.engine
2511 }
2512
2513 #[must_use]
2515 pub const fn is_padded(&self) -> bool {
2516 PAD
2517 }
2518
2519 #[must_use]
2521 pub const fn is_wrapped(&self) -> bool {
2522 self.wrap.is_some()
2523 }
2524
2525 #[must_use]
2527 pub const fn line_wrap(&self) -> Option<LineWrap> {
2528 self.wrap
2529 }
2530
2531 #[must_use]
2533 pub const fn line_len(&self) -> Option<usize> {
2534 match self.wrap {
2535 Some(wrap) => Some(wrap.line_len()),
2536 None => None,
2537 }
2538 }
2539
2540 #[must_use]
2542 pub const fn line_ending(&self) -> Option<LineEnding> {
2543 match self.wrap {
2544 Some(wrap) => Some(wrap.line_ending()),
2545 None => None,
2546 }
2547 }
2548
2549 pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
2551 match self.wrap {
2552 Some(wrap) => wrapped_encoded_len(input_len, PAD, wrap),
2553 None => encoded_len(input_len, PAD),
2554 }
2555 }
2556
2557 #[must_use]
2560 pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
2561 match self.wrap {
2562 Some(wrap) => checked_wrapped_encoded_len(input_len, PAD, wrap),
2563 None => checked_encoded_len(input_len, PAD),
2564 }
2565 }
2566
2567 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
2569 match self.wrap {
2570 Some(wrap) => self.engine.decoded_len_wrapped(input, wrap),
2571 None => self.engine.decoded_len(input),
2572 }
2573 }
2574
2575 pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
2577 match self.wrap {
2578 Some(wrap) => self.engine.validate_wrapped_result(input, wrap),
2579 None => self.engine.validate_result(input),
2580 }
2581 }
2582
2583 #[must_use]
2585 pub fn validate(&self, input: &[u8]) -> bool {
2586 self.validate_result(input).is_ok()
2587 }
2588
2589 pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
2591 match self.wrap {
2592 Some(wrap) => self.engine.encode_slice_wrapped(input, output, wrap),
2593 None => self.engine.encode_slice(input, output),
2594 }
2595 }
2596
2597 pub fn encode_slice_clear_tail(
2600 &self,
2601 input: &[u8],
2602 output: &mut [u8],
2603 ) -> Result<usize, EncodeError> {
2604 match self.wrap {
2605 Some(wrap) => self
2606 .engine
2607 .encode_slice_wrapped_clear_tail(input, output, wrap),
2608 None => self.engine.encode_slice_clear_tail(input, output),
2609 }
2610 }
2611
2612 pub fn encode_buffer<const CAP: usize>(
2618 &self,
2619 input: &[u8],
2620 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
2621 let mut output = EncodedBuffer::new();
2622 let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
2623 Ok(written) => written,
2624 Err(err) => {
2625 output.clear();
2626 return Err(err);
2627 }
2628 };
2629 output.len = written;
2630 Ok(output)
2631 }
2632
2633 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
2646 pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
2647 match self.wrap {
2648 Some(wrap) => self.engine.decode_slice_wrapped(input, output, wrap),
2649 None => self.engine.decode_slice(input, output),
2650 }
2651 }
2652
2653 pub fn decode_slice_clear_tail(
2656 &self,
2657 input: &[u8],
2658 output: &mut [u8],
2659 ) -> Result<usize, DecodeError> {
2660 match self.wrap {
2661 Some(wrap) => self
2662 .engine
2663 .decode_slice_wrapped_clear_tail(input, output, wrap),
2664 None => self.engine.decode_slice_clear_tail(input, output),
2665 }
2666 }
2667
2668 pub fn decode_buffer<const CAP: usize>(
2674 &self,
2675 input: &[u8],
2676 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
2677 let mut output = DecodedBuffer::new();
2678 let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
2679 Ok(written) => written,
2680 Err(err) => {
2681 output.clear();
2682 return Err(err);
2683 }
2684 };
2685 output.len = written;
2686 Ok(output)
2687 }
2688
2689 pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
2718 match self.wrap {
2719 Some(wrap) => self.engine.decode_in_place_wrapped(buffer, wrap),
2720 None => self.engine.decode_in_place(buffer),
2721 }
2722 }
2723
2724 pub fn decode_in_place_clear_tail<'a>(
2743 &self,
2744 buffer: &'a mut [u8],
2745 ) -> Result<&'a mut [u8], DecodeError> {
2746 match self.wrap {
2747 Some(wrap) => self.engine.decode_in_place_wrapped_clear_tail(buffer, wrap),
2748 None => self.engine.decode_in_place_clear_tail(buffer),
2749 }
2750 }
2751
2752 #[cfg(feature = "alloc")]
2754 #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
2755 pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
2756 match self.wrap {
2757 Some(wrap) => self.engine.encode_wrapped_vec(input, wrap),
2758 None => self.engine.encode_vec(input),
2759 }
2760 }
2761
2762 #[cfg(feature = "alloc")]
2764 pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
2765 self.encode_vec(input).map(SecretBuffer::from_vec)
2766 }
2767
2768 #[cfg(feature = "alloc")]
2770 pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
2771 match self.wrap {
2772 Some(wrap) => self.engine.encode_wrapped_string(input, wrap),
2773 None => self.engine.encode_string(input),
2774 }
2775 }
2776
2777 #[cfg(feature = "alloc")]
2779 #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
2780 pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
2781 match self.wrap {
2782 Some(wrap) => self.engine.decode_wrapped_vec(input, wrap),
2783 None => self.engine.decode_vec(input),
2784 }
2785 }
2786
2787 #[cfg(feature = "alloc")]
2789 pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
2790 self.decode_vec(input).map(SecretBuffer::from_vec)
2791 }
2792}
2793
2794impl<A, const PAD: bool> Default for Profile<A, PAD>
2795where
2796 A: Alphabet,
2797{
2798 fn default() -> Self {
2799 Self::new(Engine::new(), None)
2800 }
2801}
2802
2803impl<A, const PAD: bool> core::fmt::Display for Profile<A, PAD>
2804where
2805 A: Alphabet,
2806{
2807 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2808 match self.wrap {
2809 Some(wrap) => write!(formatter, "padded={PAD} wrap={wrap}"),
2810 None => write!(formatter, "padded={PAD} wrap=none"),
2811 }
2812 }
2813}
2814
2815impl<A, const PAD: bool> From<Engine<A, PAD>> for Profile<A, PAD>
2816where
2817 A: Alphabet,
2818{
2819 fn from(engine: Engine<A, PAD>) -> Self {
2820 Self::new(engine, None)
2821 }
2822}
2823
2824#[doc(alias = "ct")]
2830#[doc(alias = "constant_time")]
2831#[doc(alias = "sensitive")]
2832pub const MIME: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::MIME));
2833
2834#[doc(alias = "ct")]
2840#[doc(alias = "constant_time")]
2841#[doc(alias = "sensitive")]
2842pub const PEM: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM));
2843
2844#[doc(alias = "ct")]
2850#[doc(alias = "constant_time")]
2851#[doc(alias = "sensitive")]
2852pub const PEM_CRLF: Profile<Standard, true> = Profile::new(STANDARD, Some(LineWrap::PEM_CRLF));
2853
2854#[doc(alias = "ct")]
2862#[doc(alias = "constant_time")]
2863#[doc(alias = "sensitive")]
2864pub const BCRYPT: Profile<Bcrypt, false> = Profile::new(BCRYPT_NO_PAD, None);
2865
2866#[doc(alias = "ct")]
2874#[doc(alias = "constant_time")]
2875#[doc(alias = "sensitive")]
2876pub const CRYPT: Profile<Crypt, false> = Profile::new(CRYPT_NO_PAD, None);
2877
2878pub const fn encoded_len(input_len: usize, padded: bool) -> Result<usize, EncodeError> {
2893 match checked_encoded_len(input_len, padded) {
2894 Some(len) => Ok(len),
2895 None => Err(EncodeError::LengthOverflow),
2896 }
2897}
2898
2899pub const fn wrapped_encoded_len(
2913 input_len: usize,
2914 padded: bool,
2915 wrap: LineWrap,
2916) -> Result<usize, EncodeError> {
2917 if wrap.line_len == 0 {
2918 return Err(EncodeError::InvalidLineWrap { line_len: 0 });
2919 }
2920
2921 let Some(encoded) = checked_encoded_len(input_len, padded) else {
2922 return Err(EncodeError::LengthOverflow);
2923 };
2924 if encoded == 0 {
2925 return Ok(0);
2926 }
2927
2928 let breaks = (encoded - 1) / wrap.line_len;
2929 let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
2930 return Err(EncodeError::LengthOverflow);
2931 };
2932 match encoded.checked_add(line_ending_bytes) {
2933 Some(len) => Ok(len),
2934 None => Err(EncodeError::LengthOverflow),
2935 }
2936}
2937
2938#[must_use]
2954pub const fn checked_wrapped_encoded_len(
2955 input_len: usize,
2956 padded: bool,
2957 wrap: LineWrap,
2958) -> Option<usize> {
2959 if wrap.line_len == 0 {
2960 return None;
2961 }
2962
2963 let Some(encoded) = checked_encoded_len(input_len, padded) else {
2964 return None;
2965 };
2966 if encoded == 0 {
2967 return Some(0);
2968 }
2969
2970 let breaks = (encoded - 1) / wrap.line_len;
2971 let Some(line_ending_bytes) = breaks.checked_mul(wrap.line_ending.byte_len()) else {
2972 return None;
2973 };
2974 encoded.checked_add(line_ending_bytes)
2975}
2976
2977#[must_use]
2988pub const fn checked_encoded_len(input_len: usize, padded: bool) -> Option<usize> {
2989 let groups = input_len / 3;
2990 if groups > usize::MAX / 4 {
2991 return None;
2992 }
2993 let full = groups * 4;
2994 let rem = input_len % 3;
2995 if rem == 0 {
2996 Some(full)
2997 } else if padded {
2998 full.checked_add(4)
2999 } else {
3000 full.checked_add(rem + 1)
3001 }
3002}
3003
3004#[must_use]
3022pub fn constant_time_eq_fixed_width<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
3023 constant_time_eq_fixed_width_array(left, right)
3024}
3025
3026#[must_use]
3037pub const fn decoded_capacity(encoded_len: usize) -> usize {
3038 let rem = encoded_len % 4;
3039 encoded_len / 4 * 3
3040 + if rem == 2 {
3041 1
3042 } else if rem == 3 {
3043 2
3044 } else {
3045 0
3046 }
3047}
3048
3049pub fn decoded_len(input: &[u8], padded: bool) -> Result<usize, DecodeError> {
3063 if padded {
3064 decoded_len_padded(input)
3065 } else {
3066 decoded_len_unpadded(input)
3067 }
3068}
3069
3070#[macro_export]
3107macro_rules! define_alphabet {
3108 ($(#[$meta:meta])* $vis:vis struct $name:ident = $encode:expr;) => {
3109 $(#[$meta])*
3110 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3111 $vis struct $name;
3112
3113 impl $crate::Alphabet for $name {
3114 const ENCODE: [u8; 64] = *$encode;
3115
3116 #[inline]
3117 fn decode(byte: u8) -> Option<u8> {
3118 $crate::decode_alphabet_byte(byte, &Self::ENCODE)
3119 }
3120 }
3121
3122 const _: [(); 1] = [(); match $crate::validate_alphabet(
3123 &<$name as $crate::Alphabet>::ENCODE,
3124 ) {
3125 Ok(()) => 1,
3126 Err(_) => 0,
3127 }];
3128 };
3129}
3130
3131pub const fn validate_alphabet(encode: &[u8; 64]) -> Result<(), AlphabetError> {
3144 let mut index = 0;
3145 while index < encode.len() {
3146 let byte = encode[index];
3147 if !is_visible_ascii(byte) {
3148 return Err(AlphabetError::InvalidByte { index, byte });
3149 }
3150 if byte == b'=' {
3151 return Err(AlphabetError::PaddingByte { index });
3152 }
3153
3154 let mut duplicate = index + 1;
3155 while duplicate < encode.len() {
3156 if encode[duplicate] == byte {
3157 return Err(AlphabetError::DuplicateByte {
3158 first: index,
3159 second: duplicate,
3160 byte,
3161 });
3162 }
3163 duplicate += 1;
3164 }
3165
3166 index += 1;
3167 }
3168
3169 Ok(())
3170}
3171
3172#[must_use]
3200pub const fn decode_alphabet_byte(byte: u8, encode: &[u8; 64]) -> Option<u8> {
3201 let mut index = 0;
3202 let mut candidate = 0;
3203 let mut decoded = 0;
3204 let mut valid = 0;
3205 while index < encode.len() {
3206 let matches = ct_mask_eq_u8(byte, encode[index]);
3207 decoded |= candidate & matches;
3208 valid |= matches;
3209 index += 1;
3210 candidate += 1;
3211 }
3212
3213 if valid == 0 { None } else { Some(decoded) }
3214}
3215
3216pub trait Alphabet {
3233 const ENCODE: [u8; 64];
3235
3236 #[must_use]
3247 fn encode(value: u8) -> u8 {
3248 encode_alphabet_value(value, &Self::ENCODE)
3249 }
3250
3251 fn decode(byte: u8) -> Option<u8>;
3258}
3259
3260const fn is_visible_ascii(byte: u8) -> bool {
3261 byte >= 0x21 && byte <= 0x7e
3262}
3263
3264#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3266pub struct Standard;
3267
3268impl Alphabet for Standard {
3269 const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3270
3271 #[inline]
3272 fn encode(value: u8) -> u8 {
3273 encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
3274 }
3275
3276 #[inline]
3277 fn decode(byte: u8) -> Option<u8> {
3278 decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
3279 }
3280}
3281
3282#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3284pub struct UrlSafe;
3285
3286impl Alphabet for UrlSafe {
3287 const ENCODE: [u8; 64] = *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
3288
3289 #[inline]
3290 fn encode(value: u8) -> u8 {
3291 encode_ascii_base64(value, Self::ENCODE[62], Self::ENCODE[63])
3292 }
3293
3294 #[inline]
3295 fn decode(byte: u8) -> Option<u8> {
3296 decode_ascii_base64(byte, Self::ENCODE[62], Self::ENCODE[63])
3297 }
3298}
3299
3300#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3306pub struct Bcrypt;
3307
3308impl Alphabet for Bcrypt {
3309 const ENCODE: [u8; 64] = *b"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
3310
3311 #[inline]
3312 fn decode(byte: u8) -> Option<u8> {
3313 decode_alphabet_byte(byte, &Self::ENCODE)
3314 }
3315}
3316
3317#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
3322pub struct Crypt;
3323
3324impl Alphabet for Crypt {
3325 const ENCODE: [u8; 64] = *b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
3326
3327 #[inline]
3328 fn decode(byte: u8) -> Option<u8> {
3329 decode_alphabet_byte(byte, &Self::ENCODE)
3330 }
3331}
3332
3333#[inline]
3334const fn encode_base64_value<A: Alphabet>(value: u8) -> u8 {
3335 encode_alphabet_value(value, &A::ENCODE)
3336}
3337
3338#[inline]
3339fn encode_base64_value_runtime<A: Alphabet>(value: u8) -> u8 {
3340 A::encode(value)
3341}
3342
3343#[inline]
3344const fn encode_alphabet_value(value: u8, encode: &[u8; 64]) -> u8 {
3345 let mut output = 0;
3346 let mut index = 0;
3347 let mut candidate = 0;
3348 while index < encode.len() {
3349 output |= encode[index] & ct_mask_eq_u8(value, candidate);
3350 index += 1;
3351 candidate += 1;
3352 }
3353 output
3354}
3355
3356#[inline]
3357const fn encode_ascii_base64(value: u8, value_62_byte: u8, value_63_byte: u8) -> u8 {
3358 let upper = ct_mask_lt_u8(value, 26);
3359 let lower = ct_mask_lt_u8(value.wrapping_sub(26), 26);
3360 let digit = ct_mask_lt_u8(value.wrapping_sub(52), 10);
3361 let value_62 = ct_mask_eq_u8(value, 0x3e);
3362 let value_63 = ct_mask_eq_u8(value, 0x3f);
3363
3364 (value.wrapping_add(b'A') & upper)
3365 | (value.wrapping_sub(26).wrapping_add(b'a') & lower)
3366 | (value.wrapping_sub(52).wrapping_add(b'0') & digit)
3367 | (value_62_byte & value_62)
3368 | (value_63_byte & value_63)
3369}
3370
3371#[inline]
3372fn decode_ascii_base64(byte: u8, value_62_byte: u8, value_63_byte: u8) -> Option<u8> {
3373 let upper = ct_mask_lt_u8(byte.wrapping_sub(b'A'), 26);
3374 let lower = ct_mask_lt_u8(byte.wrapping_sub(b'a'), 26);
3375 let digit = ct_mask_lt_u8(byte.wrapping_sub(b'0'), 10);
3376 let value_62 = ct_mask_eq_u8(byte, value_62_byte);
3377 let value_63 = ct_mask_eq_u8(byte, value_63_byte);
3378 let valid = upper | lower | digit | value_62 | value_63;
3379
3380 let decoded = (byte.wrapping_sub(b'A') & upper)
3381 | (byte.wrapping_sub(b'a').wrapping_add(26) & lower)
3382 | (byte.wrapping_sub(b'0').wrapping_add(52) & digit)
3383 | (0x3e & value_62)
3384 | (0x3f & value_63);
3385
3386 if valid == 0 { None } else { Some(decoded) }
3387}
3388
3389#[inline]
3390const fn ct_mask_bit(bit: u8) -> u8 {
3391 0u8.wrapping_sub(bit & 1)
3392}
3393
3394#[inline]
3395const fn ct_mask_nonzero_u8(value: u8) -> u8 {
3396 let wide = value as u16;
3397 let negative = 0u16.wrapping_sub(wide);
3398 let nonzero = ((wide | negative) >> 8) as u8;
3399 ct_mask_bit(nonzero)
3400}
3401
3402#[inline]
3403const fn ct_mask_eq_u8(left: u8, right: u8) -> u8 {
3404 !ct_mask_nonzero_u8(left ^ right)
3405}
3406
3407#[inline]
3408const fn ct_mask_lt_u8(left: u8, right: u8) -> u8 {
3409 let diff = (left as u16).wrapping_sub(right as u16);
3410 ct_mask_bit((diff >> 8) as u8)
3411}
3412
3413#[inline(never)]
3414fn constant_time_eq_public_len(left: &[u8], right: &[u8]) -> bool {
3415 if left.len() != right.len() {
3416 return false;
3417 }
3418
3419 constant_time_eq_same_len(left, right)
3420}
3421
3422#[inline(never)]
3423fn constant_time_eq_fixed_width_array<const N: usize>(left: &[u8; N], right: &[u8; N]) -> bool {
3424 constant_time_eq_same_len(left, right)
3425}
3426
3427#[inline(never)]
3428#[allow(unsafe_code)]
3429fn constant_time_eq_same_len(left: &[u8], right: &[u8]) -> bool {
3430 let mut diff = 0u8;
3431 for (left, right) in left.iter().zip(right) {
3432 diff = core::hint::black_box(
3433 core::hint::black_box(diff) | core::hint::black_box(*left ^ *right),
3434 );
3435 diff = unsafe { core::ptr::read_volatile(&raw const diff) };
3439 }
3440 ct_error_gate_barrier(diff, 0);
3441 let result = unsafe { core::ptr::read_volatile(&raw const diff) };
3445 result == 0
3446}
3447
3448#[cfg(feature = "alloc")]
3449#[allow(unsafe_code)]
3450fn string_from_validated_secret_bytes(bytes: alloc::vec::Vec<u8>) -> alloc::string::String {
3451 debug_assert!(
3452 core::str::from_utf8(&bytes).is_ok(),
3453 "string_from_validated_secret_bytes called with invalid UTF-8",
3454 );
3455 unsafe { alloc::string::String::from_utf8_unchecked(bytes) }
3460}
3461
3462mod backend {
3463 use super::{
3464 Alphabet, DecodeError, EncodeError, checked_encoded_len, decode_padded, decode_unpadded,
3465 encode_base64_value_runtime,
3466 };
3467
3468 pub(super) fn encode_slice<A, const PAD: bool>(
3469 input: &[u8],
3470 output: &mut [u8],
3471 ) -> Result<usize, EncodeError>
3472 where
3473 A: Alphabet,
3474 {
3475 #[cfg(feature = "simd")]
3476 match super::simd::active_backend() {
3477 super::simd::ActiveBackend::Scalar => {}
3478 }
3479
3480 scalar_encode_slice::<A, PAD>(input, output)
3481 }
3482
3483 pub(super) fn decode_slice<A, const PAD: bool>(
3484 input: &[u8],
3485 output: &mut [u8],
3486 ) -> Result<usize, DecodeError>
3487 where
3488 A: Alphabet,
3489 {
3490 #[cfg(feature = "simd")]
3491 match super::simd::active_backend() {
3492 super::simd::ActiveBackend::Scalar => {}
3493 }
3494
3495 scalar_decode_slice::<A, PAD>(input, output)
3496 }
3497
3498 #[cfg(test)]
3499 pub(super) fn scalar_reference_encode_slice<A, const PAD: bool>(
3500 input: &[u8],
3501 output: &mut [u8],
3502 ) -> Result<usize, EncodeError>
3503 where
3504 A: Alphabet,
3505 {
3506 scalar_encode_slice::<A, PAD>(input, output)
3507 }
3508
3509 #[cfg(test)]
3510 pub(super) fn scalar_reference_decode_slice<A, const PAD: bool>(
3511 input: &[u8],
3512 output: &mut [u8],
3513 ) -> Result<usize, DecodeError>
3514 where
3515 A: Alphabet,
3516 {
3517 scalar_decode_slice::<A, PAD>(input, output)
3518 }
3519
3520 fn scalar_encode_slice<A, const PAD: bool>(
3521 input: &[u8],
3522 output: &mut [u8],
3523 ) -> Result<usize, EncodeError>
3524 where
3525 A: Alphabet,
3526 {
3527 let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
3528 if output.len() < required {
3529 return Err(EncodeError::OutputTooSmall {
3530 required,
3531 available: output.len(),
3532 });
3533 }
3534
3535 let mut read = 0;
3536 let mut write = 0;
3537 while read + 3 <= input.len() {
3538 let b0 = input[read];
3539 let b1 = input[read + 1];
3540 let b2 = input[read + 2];
3541
3542 output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3543 output[write + 1] =
3544 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3545 output[write + 2] =
3546 encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
3547 output[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
3548
3549 read += 3;
3550 write += 4;
3551 }
3552
3553 match input.len() - read {
3554 0 => {}
3555 1 => {
3556 let b0 = input[read];
3557 output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3558 output[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
3559 write += 2;
3560 if PAD {
3561 output[write] = b'=';
3562 output[write + 1] = b'=';
3563 write += 2;
3564 }
3565 }
3566 2 => {
3567 let b0 = input[read];
3568 let b1 = input[read + 1];
3569 output[write] = encode_base64_value_runtime::<A>(b0 >> 2);
3570 output[write + 1] =
3571 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
3572 output[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
3573 write += 3;
3574 if PAD {
3575 output[write] = b'=';
3576 write += 1;
3577 }
3578 }
3579 _ => unreachable!(),
3580 }
3581
3582 Ok(write)
3583 }
3584
3585 fn scalar_decode_slice<A, const PAD: bool>(
3586 input: &[u8],
3587 output: &mut [u8],
3588 ) -> Result<usize, DecodeError>
3589 where
3590 A: Alphabet,
3591 {
3592 if input.is_empty() {
3593 return Ok(0);
3594 }
3595
3596 if PAD {
3597 decode_padded::<A>(input, output)
3598 } else {
3599 decode_unpadded::<A>(input, output)
3600 }
3601 }
3602}
3603
3604pub struct Engine<A, const PAD: bool> {
3606 alphabet: core::marker::PhantomData<A>,
3607}
3608
3609impl<A, const PAD: bool> Clone for Engine<A, PAD> {
3610 fn clone(&self) -> Self {
3611 *self
3612 }
3613}
3614
3615impl<A, const PAD: bool> Copy for Engine<A, PAD> {}
3616
3617impl<A, const PAD: bool> core::fmt::Debug for Engine<A, PAD> {
3618 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3619 formatter
3620 .debug_struct("Engine")
3621 .field("padded", &PAD)
3622 .finish()
3623 }
3624}
3625
3626impl<A, const PAD: bool> core::fmt::Display for Engine<A, PAD> {
3627 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3628 write!(formatter, "padded={PAD}")
3629 }
3630}
3631
3632impl<A, const PAD: bool> Default for Engine<A, PAD> {
3633 fn default() -> Self {
3634 Self {
3635 alphabet: core::marker::PhantomData,
3636 }
3637 }
3638}
3639
3640impl<A, const PAD: bool> Eq for Engine<A, PAD> {}
3641
3642impl<A, const PAD: bool> PartialEq for Engine<A, PAD> {
3643 fn eq(&self, _other: &Self) -> bool {
3644 true
3645 }
3646}
3647
3648impl<A, const PAD: bool> Engine<A, PAD>
3649where
3650 A: Alphabet,
3651{
3652 #[must_use]
3654 pub const fn new() -> Self {
3655 Self {
3656 alphabet: core::marker::PhantomData,
3657 }
3658 }
3659
3660 #[must_use]
3662 pub const fn is_padded(&self) -> bool {
3663 PAD
3664 }
3665
3666 #[must_use]
3671 pub const fn profile(&self) -> Profile<A, PAD> {
3672 Profile::new(*self, None)
3673 }
3674
3675 #[must_use]
3681 pub const fn ct_decoder(&self) -> ct::CtEngine<A, PAD> {
3682 ct::CtEngine::new()
3683 }
3684
3685 #[cfg(feature = "stream")]
3699 #[must_use]
3700 pub fn encoder_writer<W>(&self, inner: W) -> stream::Encoder<W, A, PAD> {
3701 stream::Encoder::new(inner, *self)
3702 }
3703
3704 #[cfg(feature = "stream")]
3724 #[must_use]
3725 pub fn decoder_writer<W>(&self, inner: W) -> stream::Decoder<W, A, PAD> {
3726 stream::Decoder::new(inner, *self)
3727 }
3728
3729 #[cfg(feature = "stream")]
3744 #[must_use]
3745 pub fn encoder_reader<R>(&self, inner: R) -> stream::EncoderReader<R, A, PAD> {
3746 stream::EncoderReader::new(inner, *self)
3747 }
3748
3749 #[cfg(feature = "stream")]
3770 #[must_use]
3771 pub fn decoder_reader<R>(&self, inner: R) -> stream::DecoderReader<R, A, PAD> {
3772 stream::DecoderReader::new(inner, *self)
3773 }
3774
3775 pub const fn encoded_len(&self, input_len: usize) -> Result<usize, EncodeError> {
3777 encoded_len(input_len, PAD)
3778 }
3779
3780 #[must_use]
3782 pub const fn checked_encoded_len(&self, input_len: usize) -> Option<usize> {
3783 checked_encoded_len(input_len, PAD)
3784 }
3785
3786 pub const fn wrapped_encoded_len(
3791 &self,
3792 input_len: usize,
3793 wrap: LineWrap,
3794 ) -> Result<usize, EncodeError> {
3795 wrapped_encoded_len(input_len, PAD, wrap)
3796 }
3797
3798 #[must_use]
3801 pub const fn checked_wrapped_encoded_len(
3802 &self,
3803 input_len: usize,
3804 wrap: LineWrap,
3805 ) -> Option<usize> {
3806 checked_wrapped_encoded_len(input_len, PAD, wrap)
3807 }
3808
3809 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, DecodeError> {
3814 decoded_len(input, PAD)
3815 }
3816
3817 pub fn decoded_len_legacy(&self, input: &[u8]) -> Result<usize, DecodeError> {
3823 validate_legacy_decode::<A, PAD>(input)
3824 }
3825
3826 pub fn decoded_len_wrapped(&self, input: &[u8], wrap: LineWrap) -> Result<usize, DecodeError> {
3833 validate_wrapped_decode::<A, PAD>(input, wrap)
3834 }
3835
3836 pub fn validate_result(&self, input: &[u8]) -> Result<(), DecodeError> {
3854 validate_decode::<A, PAD>(input).map(|_| ())
3855 }
3856
3857 #[must_use]
3873 pub fn validate(&self, input: &[u8]) -> bool {
3874 self.validate_result(input).is_ok()
3875 }
3876
3877 pub fn validate_legacy_result(&self, input: &[u8]) -> Result<(), DecodeError> {
3892 validate_legacy_decode::<A, PAD>(input).map(|_| ())
3893 }
3894
3895 #[must_use]
3909 pub fn validate_legacy(&self, input: &[u8]) -> bool {
3910 self.validate_legacy_result(input).is_ok()
3911 }
3912
3913 pub fn validate_wrapped_result(&self, input: &[u8], wrap: LineWrap) -> Result<(), DecodeError> {
3929 validate_wrapped_decode::<A, PAD>(input, wrap).map(|_| ())
3930 }
3931
3932 #[must_use]
3946 pub fn validate_wrapped(&self, input: &[u8], wrap: LineWrap) -> bool {
3947 self.validate_wrapped_result(input, wrap).is_ok()
3948 }
3949
3950 #[must_use]
3982 pub const fn encode_array<const INPUT_LEN: usize, const OUTPUT_LEN: usize>(
3983 &self,
3984 input: &[u8; INPUT_LEN],
3985 ) -> [u8; OUTPUT_LEN] {
3986 let Some(required) = checked_encoded_len(INPUT_LEN, PAD) else {
3987 panic!("encoded base64 length overflows usize");
3988 };
3989 assert!(
3990 required == OUTPUT_LEN,
3991 "base64 output array has incorrect length"
3992 );
3993
3994 let mut output = [0u8; OUTPUT_LEN];
3995 let mut read = 0;
3996 let mut write = 0;
3997 while INPUT_LEN - read >= 3 {
3998 let b0 = input[read];
3999 let b1 = input[read + 1];
4000 let b2 = input[read + 2];
4001
4002 output[write] = encode_base64_value::<A>(b0 >> 2);
4003 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4004 output[write + 2] = encode_base64_value::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
4005 output[write + 3] = encode_base64_value::<A>(b2 & 0b0011_1111);
4006
4007 read += 3;
4008 write += 4;
4009 }
4010
4011 match INPUT_LEN - read {
4012 0 => {}
4013 1 => {
4014 let b0 = input[read];
4015 output[write] = encode_base64_value::<A>(b0 >> 2);
4016 output[write + 1] = encode_base64_value::<A>((b0 & 0b0000_0011) << 4);
4017 write += 2;
4018 if PAD {
4019 output[write] = b'=';
4020 output[write + 1] = b'=';
4021 }
4022 }
4023 2 => {
4024 let b0 = input[read];
4025 let b1 = input[read + 1];
4026 output[write] = encode_base64_value::<A>(b0 >> 2);
4027 output[write + 1] = encode_base64_value::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4028 output[write + 2] = encode_base64_value::<A>((b1 & 0b0000_1111) << 2);
4029 if PAD {
4030 output[write + 3] = b'=';
4031 }
4032 }
4033 _ => unreachable!(),
4034 }
4035
4036 output
4037 }
4038
4039 pub fn encode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, EncodeError> {
4041 backend::encode_slice::<A, PAD>(input, output)
4042 }
4043
4044 pub fn encode_slice_wrapped(
4063 &self,
4064 input: &[u8],
4065 output: &mut [u8],
4066 wrap: LineWrap,
4067 ) -> Result<usize, EncodeError> {
4068 let required = self.wrapped_encoded_len(input.len(), wrap)?;
4069 if output.len() < required {
4070 return Err(EncodeError::OutputTooSmall {
4071 required,
4072 available: output.len(),
4073 });
4074 }
4075
4076 let encoded_len =
4077 checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
4078 if encoded_len == 0 {
4079 return Ok(0);
4080 }
4081
4082 let combined_required = match required.checked_add(encoded_len) {
4085 Some(len) => len,
4086 None => usize::MAX,
4087 };
4088 if output.len() < combined_required {
4089 let mut scratch = [0u8; 1024];
4090 let mut input_offset = 0;
4091 let mut output_offset = 0;
4092 let mut column = 0;
4093
4094 while input_offset < input.len() {
4095 let remaining = input.len() - input_offset;
4096 let mut take = remaining.min(768);
4097 if remaining > take {
4098 take -= take % 3;
4099 }
4100 if take == 0 {
4101 take = remaining;
4102 }
4103
4104 let encoded = match self
4105 .encode_slice(&input[input_offset..input_offset + take], &mut scratch)
4106 {
4107 Ok(encoded) => encoded,
4108 Err(err) => {
4109 wipe_bytes(&mut scratch);
4110 return Err(err);
4111 }
4112 };
4113 if let Err(err) = write_wrapped_bytes(
4114 &scratch[..encoded],
4115 output,
4116 &mut output_offset,
4117 &mut column,
4118 wrap,
4119 ) {
4120 wipe_bytes(&mut scratch);
4121 return Err(err);
4122 }
4123 wipe_bytes(&mut scratch[..encoded]);
4124 input_offset += take;
4125 }
4126
4127 Ok(output_offset)
4128 } else {
4129 let encoded =
4130 self.encode_slice(input, &mut output[required..required + encoded_len])?;
4131 let mut output_offset = 0;
4132 let mut column = 0;
4133 let mut read = required;
4134 while read < required + encoded {
4135 let byte = output[read];
4136 write_wrapped_byte(byte, output, &mut output_offset, &mut column, wrap)?;
4137 read += 1;
4138 }
4139 wipe_bytes(&mut output[required..required + encoded]);
4140 Ok(output_offset)
4141 }
4142 }
4143
4144 pub fn encode_slice_wrapped_clear_tail(
4150 &self,
4151 input: &[u8],
4152 output: &mut [u8],
4153 wrap: LineWrap,
4154 ) -> Result<usize, EncodeError> {
4155 let written = match self.encode_slice_wrapped(input, output, wrap) {
4156 Ok(written) => written,
4157 Err(err) => {
4158 wipe_bytes(output);
4159 return Err(err);
4160 }
4161 };
4162 wipe_tail(output, written);
4163 Ok(written)
4164 }
4165
4166 pub fn encode_wrapped_buffer<const CAP: usize>(
4172 &self,
4173 input: &[u8],
4174 wrap: LineWrap,
4175 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
4176 let mut output = EncodedBuffer::new();
4177 let written = match self.encode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
4178 Ok(written) => written,
4179 Err(err) => {
4180 output.clear();
4181 return Err(err);
4182 }
4183 };
4184 output.len = written;
4185 Ok(output)
4186 }
4187
4188 #[cfg(feature = "alloc")]
4190 #[must_use = "for secret-bearing payloads use encode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
4191 pub fn encode_wrapped_vec(
4192 &self,
4193 input: &[u8],
4194 wrap: LineWrap,
4195 ) -> Result<alloc::vec::Vec<u8>, EncodeError> {
4196 let required = self.wrapped_encoded_len(input.len(), wrap)?;
4197 let mut output = alloc::vec![0; required];
4198 let written = self.encode_slice_wrapped(input, &mut output, wrap)?;
4199 output.truncate(written);
4200 Ok(output)
4201 }
4202
4203 #[cfg(feature = "alloc")]
4205 pub fn encode_wrapped_string(
4206 &self,
4207 input: &[u8],
4208 wrap: LineWrap,
4209 ) -> Result<alloc::string::String, EncodeError> {
4210 let output = self.encode_wrapped_vec(input, wrap)?;
4211 match alloc::string::String::from_utf8(output) {
4212 Ok(output) => Ok(output),
4213 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
4214 }
4215 }
4216
4217 #[cfg(feature = "alloc")]
4222 pub fn encode_wrapped_secret(
4223 &self,
4224 input: &[u8],
4225 wrap: LineWrap,
4226 ) -> Result<SecretBuffer, EncodeError> {
4227 self.encode_wrapped_vec(input, wrap)
4228 .map(SecretBuffer::from_vec)
4229 }
4230
4231 pub fn encode_slice_clear_tail(
4251 &self,
4252 input: &[u8],
4253 output: &mut [u8],
4254 ) -> Result<usize, EncodeError> {
4255 let written = match self.encode_slice(input, output) {
4256 Ok(written) => written,
4257 Err(err) => {
4258 wipe_bytes(output);
4259 return Err(err);
4260 }
4261 };
4262 wipe_tail(output, written);
4263 Ok(written)
4264 }
4265
4266 pub fn encode_buffer<const CAP: usize>(
4281 &self,
4282 input: &[u8],
4283 ) -> Result<EncodedBuffer<CAP>, EncodeError> {
4284 let mut output = EncodedBuffer::new();
4285 let written = match self.encode_slice_clear_tail(input, &mut output.bytes) {
4286 Ok(written) => written,
4287 Err(err) => {
4288 output.clear();
4289 return Err(err);
4290 }
4291 };
4292 output.len = written;
4293 Ok(output)
4294 }
4295
4296 #[cfg(feature = "alloc")]
4298 #[must_use = "for secret-bearing payloads use encode_secret, which returns a redacted buffer with drop-time cleanup"]
4299 pub fn encode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
4300 let required = checked_encoded_len(input.len(), PAD).ok_or(EncodeError::LengthOverflow)?;
4301 let mut output = alloc::vec![0; required];
4302 let written = self.encode_slice(input, &mut output)?;
4303 output.truncate(written);
4304 Ok(output)
4305 }
4306
4307 #[cfg(feature = "alloc")]
4312 pub fn encode_secret(&self, input: &[u8]) -> Result<SecretBuffer, EncodeError> {
4313 self.encode_vec(input).map(SecretBuffer::from_vec)
4314 }
4315
4316 #[cfg(feature = "alloc")]
4331 pub fn encode_string(&self, input: &[u8]) -> Result<alloc::string::String, EncodeError> {
4332 let output = self.encode_vec(input)?;
4333 match alloc::string::String::from_utf8(output) {
4334 Ok(output) => Ok(output),
4335 Err(_) => unreachable!("base64 encoder produced non-UTF-8 output"),
4336 }
4337 }
4338
4339 pub fn encode_in_place<'a>(
4356 &self,
4357 buffer: &'a mut [u8],
4358 input_len: usize,
4359 ) -> Result<&'a mut [u8], EncodeError> {
4360 if input_len > buffer.len() {
4361 return Err(EncodeError::InputTooLarge {
4362 input_len,
4363 buffer_len: buffer.len(),
4364 });
4365 }
4366
4367 let required = checked_encoded_len(input_len, PAD).ok_or(EncodeError::LengthOverflow)?;
4368 if buffer.len() < required {
4369 return Err(EncodeError::OutputTooSmall {
4370 required,
4371 available: buffer.len(),
4372 });
4373 }
4374
4375 let mut read = input_len;
4376 let mut write = required;
4377
4378 match input_len % 3 {
4379 0 => {}
4380 1 => {
4381 read -= 1;
4382 let b0 = buffer[read];
4383 if PAD {
4384 write -= 4;
4385 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4386 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
4387 buffer[write + 2] = b'=';
4388 buffer[write + 3] = b'=';
4389 } else {
4390 write -= 2;
4391 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4392 buffer[write + 1] = encode_base64_value_runtime::<A>((b0 & 0b0000_0011) << 4);
4393 }
4394 }
4395 2 => {
4396 read -= 2;
4397 let b0 = buffer[read];
4398 let b1 = buffer[read + 1];
4399 if PAD {
4400 write -= 4;
4401 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4402 buffer[write + 1] =
4403 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4404 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
4405 buffer[write + 3] = b'=';
4406 } else {
4407 write -= 3;
4408 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4409 buffer[write + 1] =
4410 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4411 buffer[write + 2] = encode_base64_value_runtime::<A>((b1 & 0b0000_1111) << 2);
4412 }
4413 }
4414 _ => unreachable!(),
4415 }
4416
4417 while read > 0 {
4418 read -= 3;
4419 write -= 4;
4420 let b0 = buffer[read];
4421 let b1 = buffer[read + 1];
4422 let b2 = buffer[read + 2];
4423
4424 buffer[write] = encode_base64_value_runtime::<A>(b0 >> 2);
4425 buffer[write + 1] =
4426 encode_base64_value_runtime::<A>(((b0 & 0b0000_0011) << 4) | (b1 >> 4));
4427 buffer[write + 2] =
4428 encode_base64_value_runtime::<A>(((b1 & 0b0000_1111) << 2) | (b2 >> 6));
4429 buffer[write + 3] = encode_base64_value_runtime::<A>(b2 & 0b0011_1111);
4430 }
4431
4432 debug_assert_eq!(write, 0);
4436 Ok(&mut buffer[..required])
4437 }
4438
4439 pub fn encode_in_place_clear_tail<'a>(
4457 &self,
4458 buffer: &'a mut [u8],
4459 input_len: usize,
4460 ) -> Result<&'a mut [u8], EncodeError> {
4461 let len = match self.encode_in_place(buffer, input_len) {
4462 Ok(encoded) => encoded.len(),
4463 Err(err) => {
4464 wipe_bytes(buffer);
4465 return Err(err);
4466 }
4467 };
4468 wipe_tail(buffer, len);
4469 Ok(&mut buffer[..len])
4470 }
4471
4472 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
4490 pub fn decode_slice(&self, input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
4491 backend::decode_slice::<A, PAD>(input, output)
4492 }
4493
4494 pub fn decode_slice_clear_tail(
4514 &self,
4515 input: &[u8],
4516 output: &mut [u8],
4517 ) -> Result<usize, DecodeError> {
4518 let written = match self.decode_slice(input, output) {
4519 Ok(written) => written,
4520 Err(err) => {
4521 wipe_bytes(output);
4522 return Err(err);
4523 }
4524 };
4525 wipe_tail(output, written);
4526 Ok(written)
4527 }
4528
4529 pub fn decode_buffer<const CAP: usize>(
4544 &self,
4545 input: &[u8],
4546 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
4547 let mut output = DecodedBuffer::new();
4548 let written = match self.decode_slice_clear_tail(input, &mut output.bytes) {
4549 Ok(written) => written,
4550 Err(err) => {
4551 output.clear();
4552 return Err(err);
4553 }
4554 };
4555 output.len = written;
4556 Ok(output)
4557 }
4558
4559 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
4572 pub fn decode_slice_legacy(
4573 &self,
4574 input: &[u8],
4575 output: &mut [u8],
4576 ) -> Result<usize, DecodeError> {
4577 let required = validate_legacy_decode::<A, PAD>(input)?;
4578 if output.len() < required {
4579 return Err(DecodeError::OutputTooSmall {
4580 required,
4581 available: output.len(),
4582 });
4583 }
4584 decode_legacy_to_slice::<A, PAD>(input, output)
4585 }
4586
4587 pub fn decode_slice_legacy_clear_tail(
4607 &self,
4608 input: &[u8],
4609 output: &mut [u8],
4610 ) -> Result<usize, DecodeError> {
4611 let written = match self.decode_slice_legacy(input, output) {
4612 Ok(written) => written,
4613 Err(err) => {
4614 wipe_bytes(output);
4615 return Err(err);
4616 }
4617 };
4618 wipe_tail(output, written);
4619 Ok(written)
4620 }
4621
4622 pub fn decode_buffer_legacy<const CAP: usize>(
4630 &self,
4631 input: &[u8],
4632 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
4633 let mut output = DecodedBuffer::new();
4634 let written = match self.decode_slice_legacy_clear_tail(input, &mut output.bytes) {
4635 Ok(written) => written,
4636 Err(err) => {
4637 output.clear();
4638 return Err(err);
4639 }
4640 };
4641 output.len = written;
4642 Ok(output)
4643 }
4644
4645 #[must_use = "handle decode errors; use crate::ct for secret-bearing payloads"]
4659 pub fn decode_slice_wrapped(
4660 &self,
4661 input: &[u8],
4662 output: &mut [u8],
4663 wrap: LineWrap,
4664 ) -> Result<usize, DecodeError> {
4665 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
4666 if output.len() < required {
4667 return Err(DecodeError::OutputTooSmall {
4668 required,
4669 available: output.len(),
4670 });
4671 }
4672 decode_wrapped_to_slice::<A, PAD>(input, output, wrap)
4673 }
4674
4675 pub fn decode_slice_wrapped_clear_tail(
4681 &self,
4682 input: &[u8],
4683 output: &mut [u8],
4684 wrap: LineWrap,
4685 ) -> Result<usize, DecodeError> {
4686 let written = match self.decode_slice_wrapped(input, output, wrap) {
4687 Ok(written) => written,
4688 Err(err) => {
4689 wipe_bytes(output);
4690 return Err(err);
4691 }
4692 };
4693 wipe_tail(output, written);
4694 Ok(written)
4695 }
4696
4697 pub fn decode_wrapped_buffer<const CAP: usize>(
4706 &self,
4707 input: &[u8],
4708 wrap: LineWrap,
4709 ) -> Result<DecodedBuffer<CAP>, DecodeError> {
4710 let mut output = DecodedBuffer::new();
4711 let written = match self.decode_slice_wrapped_clear_tail(input, &mut output.bytes, wrap) {
4712 Ok(written) => written,
4713 Err(err) => {
4714 output.clear();
4715 return Err(err);
4716 }
4717 };
4718 output.len = written;
4719 Ok(output)
4720 }
4721
4722 #[cfg(feature = "alloc")]
4726 #[must_use = "for secret-bearing payloads use decode_secret, which returns a redacted buffer with drop-time cleanup"]
4727 pub fn decode_vec(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
4728 let required = validate_decode::<A, PAD>(input)?;
4729 let mut output = alloc::vec![0; required];
4730 let written = match self.decode_slice(input, &mut output) {
4731 Ok(written) => written,
4732 Err(err) => {
4733 wipe_bytes(&mut output);
4734 return Err(err);
4735 }
4736 };
4737 output.truncate(written);
4738 Ok(output)
4739 }
4740
4741 #[cfg(feature = "alloc")]
4746 pub fn decode_secret(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
4747 self.decode_vec(input).map(SecretBuffer::from_vec)
4748 }
4749
4750 #[cfg(feature = "alloc")]
4753 #[must_use = "for secret-bearing payloads use decode_secret_legacy, which returns a redacted buffer with drop-time cleanup"]
4754 pub fn decode_vec_legacy(&self, input: &[u8]) -> Result<alloc::vec::Vec<u8>, DecodeError> {
4755 let required = validate_legacy_decode::<A, PAD>(input)?;
4756 let mut output = alloc::vec![0; required];
4757 let written = match self.decode_slice_legacy(input, &mut output) {
4758 Ok(written) => written,
4759 Err(err) => {
4760 wipe_bytes(&mut output);
4761 return Err(err);
4762 }
4763 };
4764 output.truncate(written);
4765 Ok(output)
4766 }
4767
4768 #[cfg(feature = "alloc")]
4775 pub fn decode_secret_legacy(&self, input: &[u8]) -> Result<SecretBuffer, DecodeError> {
4776 self.decode_vec_legacy(input).map(SecretBuffer::from_vec)
4777 }
4778
4779 #[cfg(feature = "alloc")]
4781 #[must_use = "for secret-bearing payloads use decode_wrapped_secret, which returns a redacted buffer with drop-time cleanup"]
4782 pub fn decode_wrapped_vec(
4783 &self,
4784 input: &[u8],
4785 wrap: LineWrap,
4786 ) -> Result<alloc::vec::Vec<u8>, DecodeError> {
4787 let required = validate_wrapped_decode::<A, PAD>(input, wrap)?;
4788 let mut output = alloc::vec![0; required];
4789 let written = match self.decode_slice_wrapped(input, &mut output, wrap) {
4790 Ok(written) => written,
4791 Err(err) => {
4792 wipe_bytes(&mut output);
4793 return Err(err);
4794 }
4795 };
4796 output.truncate(written);
4797 Ok(output)
4798 }
4799
4800 #[cfg(feature = "alloc")]
4807 pub fn decode_wrapped_secret(
4808 &self,
4809 input: &[u8],
4810 wrap: LineWrap,
4811 ) -> Result<SecretBuffer, DecodeError> {
4812 self.decode_wrapped_vec(input, wrap)
4813 .map(SecretBuffer::from_vec)
4814 }
4815
4816 pub fn decode_in_place_wrapped<'a>(
4840 &self,
4841 buffer: &'a mut [u8],
4842 wrap: LineWrap,
4843 ) -> Result<&'a mut [u8], DecodeError> {
4844 let _required = validate_wrapped_decode::<A, PAD>(buffer, wrap)?;
4845 let compacted = compact_wrapped_input(buffer, wrap)?;
4846 let len = Self::decode_slice_to_start(&mut buffer[..compacted])?;
4847 Ok(&mut buffer[..len])
4848 }
4849
4850 pub fn decode_in_place_wrapped_clear_tail<'a>(
4871 &self,
4872 buffer: &'a mut [u8],
4873 wrap: LineWrap,
4874 ) -> Result<&'a mut [u8], DecodeError> {
4875 if let Err(err) = validate_wrapped_decode::<A, PAD>(buffer, wrap) {
4876 wipe_bytes(buffer);
4877 return Err(err);
4878 }
4879
4880 let compacted = match compact_wrapped_input(buffer, wrap) {
4881 Ok(compacted) => compacted,
4882 Err(err) => {
4883 wipe_bytes(buffer);
4884 return Err(err);
4885 }
4886 };
4887
4888 let len = match Self::decode_slice_to_start(&mut buffer[..compacted]) {
4889 Ok(len) => len,
4890 Err(err) => {
4891 wipe_bytes(buffer);
4892 return Err(err);
4893 }
4894 };
4895 wipe_tail(buffer, len);
4896 Ok(&mut buffer[..len])
4897 }
4898
4899 pub fn decode_in_place<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a mut [u8], DecodeError> {
4924 let len = Self::decode_slice_to_start(buffer)?;
4925 Ok(&mut buffer[..len])
4926 }
4927
4928 pub fn decode_in_place_clear_tail<'a>(
4945 &self,
4946 buffer: &'a mut [u8],
4947 ) -> Result<&'a mut [u8], DecodeError> {
4948 let len = match Self::decode_slice_to_start(buffer) {
4949 Ok(len) => len,
4950 Err(err) => {
4951 wipe_bytes(buffer);
4952 return Err(err);
4953 }
4954 };
4955 wipe_tail(buffer, len);
4956 Ok(&mut buffer[..len])
4957 }
4958
4959 pub fn decode_in_place_legacy<'a>(
4967 &self,
4968 buffer: &'a mut [u8],
4969 ) -> Result<&'a mut [u8], DecodeError> {
4970 let _required = validate_legacy_decode::<A, PAD>(buffer)?;
4971 let mut write = 0;
4972 let mut read = 0;
4973 while read < buffer.len() {
4974 let byte = buffer[read];
4975 if !is_legacy_whitespace(byte) {
4976 buffer[write] = byte;
4977 write += 1;
4978 }
4979 read += 1;
4980 }
4981 let len = Self::decode_slice_to_start(&mut buffer[..write])?;
4982 Ok(&mut buffer[..len])
4983 }
4984
4985 pub fn decode_in_place_legacy_clear_tail<'a>(
4991 &self,
4992 buffer: &'a mut [u8],
4993 ) -> Result<&'a mut [u8], DecodeError> {
4994 if let Err(err) = validate_legacy_decode::<A, PAD>(buffer) {
4995 wipe_bytes(buffer);
4996 return Err(err);
4997 }
4998
4999 let mut write = 0;
5000 let mut read = 0;
5001 while read < buffer.len() {
5002 let byte = buffer[read];
5003 if !is_legacy_whitespace(byte) {
5004 buffer[write] = byte;
5005 write += 1;
5006 }
5007 read += 1;
5008 }
5009
5010 let len = match Self::decode_slice_to_start(&mut buffer[..write]) {
5011 Ok(len) => len,
5012 Err(err) => {
5013 wipe_bytes(buffer);
5014 return Err(err);
5015 }
5016 };
5017 wipe_tail(buffer, len);
5018 Ok(&mut buffer[..len])
5019 }
5020
5021 fn decode_slice_to_start(buffer: &mut [u8]) -> Result<usize, DecodeError> {
5022 let _required = validate_decode::<A, PAD>(buffer)?;
5023 let input_len = buffer.len();
5024 let mut read = 0;
5025 let mut write = 0;
5026 while read + 4 <= input_len {
5027 let chunk = read_quad(buffer, read)?;
5028 let available = buffer.len();
5029 let output_tail = buffer.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5030 required: write,
5031 available,
5032 })?;
5033 let written = decode_chunk::<A, PAD>(chunk, output_tail)
5034 .map_err(|err| err.with_index_offset(read))?;
5035 read += 4;
5036 write += written;
5037 if written < 3 {
5038 if read != input_len {
5039 return Err(DecodeError::InvalidPadding { index: read - 4 });
5040 }
5041 return Ok(write);
5042 }
5043 }
5044
5045 let rem = input_len - read;
5046 if rem == 0 {
5047 return Ok(write);
5048 }
5049 if PAD {
5050 return Err(DecodeError::InvalidLength);
5051 }
5052 let mut tail = [0u8; 3];
5053 tail[..rem].copy_from_slice(&buffer[read..input_len]);
5054 decode_tail_unpadded::<A>(&tail[..rem], &mut buffer[write..])
5055 .map_err(|err| err.with_index_offset(read))
5056 .map(|n| write + n)
5057 }
5058}
5059
5060fn write_wrapped_bytes(
5061 input: &[u8],
5062 output: &mut [u8],
5063 output_offset: &mut usize,
5064 column: &mut usize,
5065 wrap: LineWrap,
5066) -> Result<(), EncodeError> {
5067 for byte in input {
5068 write_wrapped_byte(*byte, output, output_offset, column, wrap)?;
5069 }
5070 Ok(())
5071}
5072
5073fn write_wrapped_byte(
5074 byte: u8,
5075 output: &mut [u8],
5076 output_offset: &mut usize,
5077 column: &mut usize,
5078 wrap: LineWrap,
5079) -> Result<(), EncodeError> {
5080 if *column == wrap.line_len {
5081 let line_ending = wrap.line_ending.as_bytes();
5082 let mut index = 0;
5083 while index < line_ending.len() {
5084 if *output_offset >= output.len() {
5085 return Err(EncodeError::OutputTooSmall {
5086 required: *output_offset + 1,
5087 available: output.len(),
5088 });
5089 }
5090 output[*output_offset] = line_ending[index];
5091 *output_offset += 1;
5092 index += 1;
5093 }
5094 *column = 0;
5095 }
5096
5097 if *output_offset >= output.len() {
5098 return Err(EncodeError::OutputTooSmall {
5099 required: *output_offset + 1,
5100 available: output.len(),
5101 });
5102 }
5103 output[*output_offset] = byte;
5104 *output_offset += 1;
5105 *column += 1;
5106 Ok(())
5107}
5108
5109#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5111pub enum EncodeError {
5112 LengthOverflow,
5114 InvalidLineWrap {
5116 line_len: usize,
5118 },
5119 InputTooLarge {
5121 input_len: usize,
5123 buffer_len: usize,
5125 },
5126 OutputTooSmall {
5128 required: usize,
5130 available: usize,
5132 },
5133}
5134
5135impl core::fmt::Display for EncodeError {
5136 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5137 match self {
5138 Self::LengthOverflow => f.write_str("base64 output length overflows usize"),
5139 Self::InvalidLineWrap { line_len } => {
5140 write!(f, "base64 line wrap length {line_len} is invalid")
5141 }
5142 Self::InputTooLarge {
5143 input_len,
5144 buffer_len,
5145 } => write!(
5146 f,
5147 "base64 input length {input_len} exceeds buffer length {buffer_len}"
5148 ),
5149 Self::OutputTooSmall {
5150 required,
5151 available,
5152 } => write!(
5153 f,
5154 "base64 output buffer too small: required {required}, available {available}"
5155 ),
5156 }
5157 }
5158}
5159
5160#[cfg(feature = "std")]
5161impl std::error::Error for EncodeError {}
5162
5163#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5165pub enum AlphabetError {
5166 InvalidByte {
5168 index: usize,
5170 byte: u8,
5172 },
5173 PaddingByte {
5175 index: usize,
5177 },
5178 DuplicateByte {
5180 first: usize,
5182 second: usize,
5184 byte: u8,
5186 },
5187}
5188
5189impl core::fmt::Display for AlphabetError {
5190 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5191 match self {
5192 Self::InvalidByte { index, byte } => {
5193 write!(
5194 f,
5195 "invalid base64 alphabet byte 0x{byte:02x} at index {index}"
5196 )
5197 }
5198 Self::PaddingByte { index } => {
5199 write!(f, "base64 alphabet contains padding byte at index {index}")
5200 }
5201 Self::DuplicateByte {
5202 first,
5203 second,
5204 byte,
5205 } => write!(
5206 f,
5207 "base64 alphabet byte 0x{byte:02x} is duplicated at indexes {first} and {second}"
5208 ),
5209 }
5210 }
5211}
5212
5213#[cfg(feature = "std")]
5214impl std::error::Error for AlphabetError {}
5215
5216#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5218pub enum DecodeError {
5219 InvalidInput,
5222 InvalidLength,
5224 InvalidByte {
5226 index: usize,
5228 byte: u8,
5230 },
5231 InvalidPadding {
5233 index: usize,
5235 },
5236 InvalidLineWrap {
5238 index: usize,
5240 },
5241 OutputTooSmall {
5243 required: usize,
5245 available: usize,
5247 },
5248 StagingTooSmall {
5250 required: usize,
5252 available: usize,
5254 },
5255}
5256
5257impl core::fmt::Display for DecodeError {
5258 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5259 match self {
5260 Self::InvalidInput => f.write_str("malformed base64 input"),
5261 Self::InvalidLength => f.write_str("invalid base64 input length"),
5262 Self::InvalidByte { index, byte } => {
5263 write!(f, "invalid base64 byte 0x{byte:02x} at index {index}")
5264 }
5265 Self::InvalidPadding { index } => write!(f, "invalid base64 padding at index {index}"),
5266 Self::InvalidLineWrap { index } => {
5267 write!(f, "invalid base64 line wrapping at index {index}")
5268 }
5269 Self::OutputTooSmall {
5270 required,
5271 available,
5272 } => write!(
5273 f,
5274 "base64 decode output buffer too small: required {required}, available {available}"
5275 ),
5276 Self::StagingTooSmall {
5277 required,
5278 available,
5279 } => write!(
5280 f,
5281 "base64 decode staging buffer too small: required {required}, available {available}"
5282 ),
5283 }
5284 }
5285}
5286
5287impl DecodeError {
5288 fn with_index_offset(self, offset: usize) -> Self {
5289 match self {
5290 Self::InvalidByte { index, byte } => Self::InvalidByte {
5291 index: index + offset,
5292 byte,
5293 },
5294 Self::InvalidPadding { index } => Self::InvalidPadding {
5295 index: index + offset,
5296 },
5297 Self::InvalidLineWrap { index } => Self::InvalidLineWrap {
5298 index: index + offset,
5299 },
5300 Self::InvalidInput
5301 | Self::InvalidLength
5302 | Self::OutputTooSmall { .. }
5303 | Self::StagingTooSmall { .. } => self,
5304 }
5305 }
5306}
5307
5308#[cfg(feature = "std")]
5309impl std::error::Error for DecodeError {}
5310
5311struct LegacyBytes<'a> {
5312 input: &'a [u8],
5313 index: usize,
5314}
5315
5316impl<'a> LegacyBytes<'a> {
5317 const fn new(input: &'a [u8]) -> Self {
5318 Self { input, index: 0 }
5319 }
5320
5321 fn next_byte(&mut self) -> Option<(usize, u8)> {
5322 while self.index < self.input.len() {
5323 let index = self.index;
5324 let byte = self.input[index];
5325 self.index += 1;
5326 if !is_legacy_whitespace(byte) {
5327 return Some((index, byte));
5328 }
5329 }
5330 None
5331 }
5332}
5333
5334fn validate_legacy_decode<A: Alphabet, const PAD: bool>(
5335 input: &[u8],
5336) -> Result<usize, DecodeError> {
5337 let mut bytes = LegacyBytes::new(input);
5338 let mut chunk = [0u8; 4];
5339 let mut indexes = [0usize; 4];
5340 let mut chunk_len = 0;
5341 let mut required = 0;
5342 let mut terminal_seen = false;
5343
5344 while let Some((index, byte)) = bytes.next_byte() {
5345 if terminal_seen {
5346 return Err(DecodeError::InvalidPadding { index });
5347 }
5348
5349 chunk[chunk_len] = byte;
5350 indexes[chunk_len] = index;
5351 chunk_len += 1;
5352
5353 if chunk_len == 4 {
5354 let written =
5355 validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
5356 required += written;
5357 terminal_seen = written < 3;
5358 chunk_len = 0;
5359 }
5360 }
5361
5362 if chunk_len == 0 {
5363 return Ok(required);
5364 }
5365 if PAD {
5366 return Err(DecodeError::InvalidLength);
5367 }
5368
5369 validate_tail_unpadded::<A>(&chunk[..chunk_len])
5370 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
5371 Ok(required + decoded_capacity(chunk_len))
5372}
5373
5374fn decode_legacy_to_slice<A: Alphabet, const PAD: bool>(
5375 input: &[u8],
5376 output: &mut [u8],
5377) -> Result<usize, DecodeError> {
5378 let mut bytes = LegacyBytes::new(input);
5379 let mut chunk = [0u8; 4];
5380 let mut indexes = [0usize; 4];
5381 let mut chunk_len = 0;
5382 let mut write = 0;
5383 let mut terminal_seen = false;
5384
5385 while let Some((index, byte)) = bytes.next_byte() {
5386 if terminal_seen {
5387 return Err(DecodeError::InvalidPadding { index });
5388 }
5389
5390 chunk[chunk_len] = byte;
5391 indexes[chunk_len] = index;
5392 chunk_len += 1;
5393
5394 if chunk_len == 4 {
5395 let available = output.len();
5396 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5397 required: write,
5398 available,
5399 })?;
5400 let written = decode_chunk::<A, PAD>(chunk, output_tail)
5401 .map_err(|err| map_chunk_error(err, &indexes))?;
5402 write += written;
5403 terminal_seen = written < 3;
5404 chunk_len = 0;
5405 }
5406 }
5407
5408 if chunk_len == 0 {
5409 return Ok(write);
5410 }
5411 if PAD {
5412 return Err(DecodeError::InvalidLength);
5413 }
5414
5415 decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
5416 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
5417 .map(|n| write + n)
5418}
5419
5420struct WrappedBytes<'a> {
5421 input: &'a [u8],
5422 wrap: LineWrap,
5423 index: usize,
5424 line_len: usize,
5425}
5426
5427impl<'a> WrappedBytes<'a> {
5428 const fn new(input: &'a [u8], wrap: LineWrap) -> Result<Self, DecodeError> {
5429 if wrap.line_len == 0 {
5430 return Err(DecodeError::InvalidLineWrap { index: 0 });
5431 }
5432 Ok(Self {
5433 input,
5434 wrap,
5435 index: 0,
5436 line_len: 0,
5437 })
5438 }
5439
5440 fn next_byte(&mut self) -> Result<Option<(usize, u8)>, DecodeError> {
5441 loop {
5442 if self.index == self.input.len() {
5443 return Ok(None);
5444 }
5445
5446 if self.starts_with_line_ending() {
5447 let line_end_index = self.index;
5448 if self.line_len == 0 {
5449 return Err(DecodeError::InvalidLineWrap {
5450 index: line_end_index,
5451 });
5452 }
5453
5454 self.index += self.wrap.line_ending.byte_len();
5455 if self.index == self.input.len() {
5456 self.line_len = 0;
5457 return Ok(None);
5458 }
5459
5460 if self.line_len != self.wrap.line_len {
5461 return Err(DecodeError::InvalidLineWrap {
5462 index: line_end_index,
5463 });
5464 }
5465 self.line_len = 0;
5466 continue;
5467 }
5468
5469 let byte = self.input[self.index];
5470 if matches!(byte, b'\r' | b'\n') {
5471 return Err(DecodeError::InvalidLineWrap { index: self.index });
5472 }
5473
5474 self.line_len += 1;
5475 if self.line_len > self.wrap.line_len {
5476 return Err(DecodeError::InvalidLineWrap { index: self.index });
5477 }
5478
5479 let index = self.index;
5480 self.index += 1;
5481 return Ok(Some((index, byte)));
5482 }
5483 }
5484
5485 fn starts_with_line_ending(&self) -> bool {
5486 let line_ending = self.wrap.line_ending.as_bytes();
5487 let Some(end) = self.index.checked_add(line_ending.len()) else {
5488 return false;
5489 };
5490 end <= self.input.len() && &self.input[self.index..end] == line_ending
5491 }
5492}
5493
5494fn validate_wrapped_decode<A: Alphabet, const PAD: bool>(
5495 input: &[u8],
5496 wrap: LineWrap,
5497) -> Result<usize, DecodeError> {
5498 let mut bytes = WrappedBytes::new(input, wrap)?;
5499 let mut chunk = [0u8; 4];
5500 let mut indexes = [0usize; 4];
5501 let mut chunk_len = 0;
5502 let mut required = 0;
5503 let mut terminal_seen = false;
5504
5505 while let Some((index, byte)) = bytes.next_byte()? {
5506 if terminal_seen {
5507 return Err(DecodeError::InvalidPadding { index });
5508 }
5509
5510 chunk[chunk_len] = byte;
5511 indexes[chunk_len] = index;
5512 chunk_len += 1;
5513
5514 if chunk_len == 4 {
5515 let written =
5516 validate_chunk::<A, PAD>(chunk).map_err(|err| map_chunk_error(err, &indexes))?;
5517 required += written;
5518 terminal_seen = written < 3;
5519 chunk_len = 0;
5520 }
5521 }
5522
5523 if chunk_len == 0 {
5524 return Ok(required);
5525 }
5526 if PAD {
5527 return Err(DecodeError::InvalidLength);
5528 }
5529
5530 validate_tail_unpadded::<A>(&chunk[..chunk_len])
5531 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))?;
5532 Ok(required + decoded_capacity(chunk_len))
5533}
5534
5535fn decode_wrapped_to_slice<A: Alphabet, const PAD: bool>(
5536 input: &[u8],
5537 output: &mut [u8],
5538 wrap: LineWrap,
5539) -> Result<usize, DecodeError> {
5540 let mut bytes = WrappedBytes::new(input, wrap)?;
5541 let mut chunk = [0u8; 4];
5542 let mut indexes = [0usize; 4];
5543 let mut chunk_len = 0;
5544 let mut write = 0;
5545 let mut terminal_seen = false;
5546
5547 while let Some((index, byte)) = bytes.next_byte()? {
5548 if terminal_seen {
5549 return Err(DecodeError::InvalidPadding { index });
5550 }
5551
5552 chunk[chunk_len] = byte;
5553 indexes[chunk_len] = index;
5554 chunk_len += 1;
5555
5556 if chunk_len == 4 {
5557 let available = output.len();
5558 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5559 required: write,
5560 available,
5561 })?;
5562 let written = decode_chunk::<A, PAD>(chunk, output_tail)
5563 .map_err(|err| map_chunk_error(err, &indexes))?;
5564 write += written;
5565 terminal_seen = written < 3;
5566 chunk_len = 0;
5567 }
5568 }
5569
5570 if chunk_len == 0 {
5571 return Ok(write);
5572 }
5573 if PAD {
5574 return Err(DecodeError::InvalidLength);
5575 }
5576
5577 decode_tail_unpadded::<A>(&chunk[..chunk_len], &mut output[write..])
5578 .map_err(|err| map_partial_chunk_error(err, &indexes, chunk_len))
5579 .map(|n| write + n)
5580}
5581
5582fn compact_wrapped_input(buffer: &mut [u8], wrap: LineWrap) -> Result<usize, DecodeError> {
5583 if !wrap.is_valid() {
5584 return Err(DecodeError::InvalidLineWrap { index: 0 });
5585 }
5586
5587 let line_ending = wrap.line_ending.as_bytes();
5588 let line_ending_len = line_ending.len();
5589 let mut read = 0;
5590 let mut write = 0;
5591
5592 while read < buffer.len() {
5593 let line_end = read + line_ending_len;
5594 if buffer.get(read..line_end) == Some(line_ending) {
5595 read = line_end;
5596 continue;
5597 }
5598
5599 buffer[write] = buffer[read];
5600 write += 1;
5601 read += 1;
5602 }
5603
5604 Ok(write)
5605}
5606
5607#[inline]
5608const fn is_legacy_whitespace(byte: u8) -> bool {
5609 matches!(byte, b' ' | b'\t' | b'\r' | b'\n')
5610}
5611
5612fn map_chunk_error(err: DecodeError, indexes: &[usize; 4]) -> DecodeError {
5613 match err {
5614 DecodeError::InvalidByte { index, byte } => DecodeError::InvalidByte {
5615 index: indexes[index],
5616 byte,
5617 },
5618 DecodeError::InvalidPadding { index } => DecodeError::InvalidPadding {
5619 index: indexes[index],
5620 },
5621 DecodeError::InvalidInput
5622 | DecodeError::InvalidLineWrap { .. }
5623 | DecodeError::InvalidLength
5624 | DecodeError::OutputTooSmall { .. }
5625 | DecodeError::StagingTooSmall { .. } => err,
5626 }
5627}
5628
5629fn map_partial_chunk_error(err: DecodeError, indexes: &[usize; 4], len: usize) -> DecodeError {
5630 match err {
5631 DecodeError::InvalidByte { index, byte } if index < len => DecodeError::InvalidByte {
5632 index: indexes[index],
5633 byte,
5634 },
5635 DecodeError::InvalidPadding { index } if index < len => DecodeError::InvalidPadding {
5636 index: indexes[index],
5637 },
5638 DecodeError::InvalidByte { .. }
5639 | DecodeError::InvalidPadding { .. }
5640 | DecodeError::InvalidLineWrap { .. }
5641 | DecodeError::InvalidInput
5642 | DecodeError::InvalidLength
5643 | DecodeError::OutputTooSmall { .. }
5644 | DecodeError::StagingTooSmall { .. } => err,
5645 }
5646}
5647
5648fn decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
5649 if !input.len().is_multiple_of(4) {
5650 return Err(DecodeError::InvalidLength);
5651 }
5652 let required = decoded_len_padded(input)?;
5653 if output.len() < required {
5654 return Err(DecodeError::OutputTooSmall {
5655 required,
5656 available: output.len(),
5657 });
5658 }
5659
5660 let mut read = 0;
5661 let mut write = 0;
5662 while read < input.len() {
5663 let chunk = read_quad(input, read)?;
5664 let available = output.len();
5665 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5666 required: write,
5667 available,
5668 })?;
5669 let written = decode_chunk::<A, true>(chunk, output_tail)
5670 .map_err(|err| err.with_index_offset(read))?;
5671 read += 4;
5672 write += written;
5673 if written < 3 && read != input.len() {
5674 return Err(DecodeError::InvalidPadding { index: read - 4 });
5675 }
5676 }
5677 Ok(write)
5678}
5679
5680fn validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
5681 if input.is_empty() {
5682 return Ok(0);
5683 }
5684
5685 if PAD {
5686 validate_padded::<A>(input)
5687 } else {
5688 validate_unpadded::<A>(input)
5689 }
5690}
5691
5692fn validate_padded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
5693 if !input.len().is_multiple_of(4) {
5694 return Err(DecodeError::InvalidLength);
5695 }
5696 let required = decoded_len_padded(input)?;
5697
5698 let mut read = 0;
5699 while read < input.len() {
5700 let chunk = read_quad(input, read)?;
5701 let written =
5702 validate_chunk::<A, true>(chunk).map_err(|err| err.with_index_offset(read))?;
5703 read += 4;
5704 if written < 3 && read != input.len() {
5705 return Err(DecodeError::InvalidPadding { index: read - 4 });
5706 }
5707 }
5708
5709 Ok(required)
5710}
5711
5712fn validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<usize, DecodeError> {
5713 let required = decoded_len_unpadded(input)?;
5714
5715 let mut read = 0;
5716 while read + 4 <= input.len() {
5717 let chunk = read_quad(input, read)?;
5718 validate_chunk::<A, false>(chunk).map_err(|err| err.with_index_offset(read))?;
5719 read += 4;
5720 }
5721 validate_tail_unpadded::<A>(&input[read..]).map_err(|err| err.with_index_offset(read))?;
5722
5723 Ok(required)
5724}
5725
5726fn decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
5727 let required = decoded_len_unpadded(input)?;
5728 if output.len() < required {
5729 return Err(DecodeError::OutputTooSmall {
5730 required,
5731 available: output.len(),
5732 });
5733 }
5734
5735 let mut read = 0;
5736 let mut write = 0;
5737 while read + 4 <= input.len() {
5738 let chunk = read_quad(input, read)?;
5739 let available = output.len();
5740 let output_tail = output.get_mut(write..).ok_or(DecodeError::OutputTooSmall {
5741 required: write,
5742 available,
5743 })?;
5744 let written = decode_chunk::<A, false>(chunk, output_tail)
5745 .map_err(|err| err.with_index_offset(read))?;
5746 read += 4;
5747 write += written;
5748 }
5749 decode_tail_unpadded::<A>(&input[read..], &mut output[write..])
5750 .map_err(|err| err.with_index_offset(read))
5751 .map(|n| write + n)
5752}
5753
5754fn decoded_len_padded(input: &[u8]) -> Result<usize, DecodeError> {
5755 if input.is_empty() {
5756 return Ok(0);
5757 }
5758 if !input.len().is_multiple_of(4) {
5759 return Err(DecodeError::InvalidLength);
5760 }
5761
5762 let Some((&last, before_last_prefix)) = input.split_last() else {
5763 return Ok(0);
5764 };
5765 let Some(&before_last) = before_last_prefix.last() else {
5766 return Err(DecodeError::InvalidLength);
5767 };
5768
5769 let mut padding = 0;
5770 if last == b'=' {
5771 padding += 1;
5772 }
5773 if before_last == b'=' {
5774 padding += 1;
5775 }
5776 if padding == 0
5777 && let Some(index) = input.iter().position(|byte| *byte == b'=')
5778 {
5779 return Err(DecodeError::InvalidPadding { index });
5780 }
5781 if padding > 0 {
5782 let first_pad = input.len() - padding;
5783 if let Some(index) = input[..first_pad].iter().position(|byte| *byte == b'=') {
5784 return Err(DecodeError::InvalidPadding { index });
5785 }
5786 }
5787 Ok(input.len() / 4 * 3 - padding)
5788}
5789
5790fn decoded_len_unpadded(input: &[u8]) -> Result<usize, DecodeError> {
5791 if input.len() % 4 == 1 {
5792 return Err(DecodeError::InvalidLength);
5793 }
5794 if let Some(index) = input.iter().position(|byte| *byte == b'=') {
5795 return Err(DecodeError::InvalidPadding { index });
5796 }
5797 Ok(decoded_capacity(input.len()))
5798}
5799
5800fn read_quad(input: &[u8], offset: usize) -> Result<[u8; 4], DecodeError> {
5801 let end = offset.checked_add(4).ok_or(DecodeError::InvalidLength)?;
5802 match input.get(offset..end) {
5803 Some([b0, b1, b2, b3]) => Ok([*b0, *b1, *b2, *b3]),
5804 _ => Err(DecodeError::InvalidLength),
5805 }
5806}
5807
5808fn first_padding_index_unchecked(input: [u8; 4]) -> usize {
5809 let [b0, b1, b2, b3] = input;
5810 if b0 == b'=' {
5811 0
5812 } else if b1 == b'=' {
5813 1
5814 } else if b2 == b'=' {
5815 2
5816 } else if b3 == b'=' {
5817 3
5818 } else {
5819 debug_assert!(
5820 false,
5821 "first_padding_index_unchecked called with no padding"
5822 );
5823 4
5824 }
5825}
5826
5827fn validate_chunk<A: Alphabet, const PAD: bool>(input: [u8; 4]) -> Result<usize, DecodeError> {
5828 let [b0, b1, b2, b3] = input;
5829 let _v0 = decode_byte::<A>(b0, 0)?;
5830 let v1 = decode_byte::<A>(b1, 1)?;
5831
5832 match (b2, b3) {
5833 (b'=', b'=') if PAD => {
5834 if v1 & 0b0000_1111 != 0 {
5835 return Err(DecodeError::InvalidPadding { index: 1 });
5836 }
5837 Ok(1)
5838 }
5839 (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
5840 (_, b'=') if PAD => {
5841 let v2 = decode_byte::<A>(b2, 2)?;
5842 if v2 & 0b0000_0011 != 0 {
5843 return Err(DecodeError::InvalidPadding { index: 2 });
5844 }
5845 Ok(2)
5846 }
5847 (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
5848 index: first_padding_index_unchecked(input),
5849 }),
5850 _ => {
5851 decode_byte::<A>(b2, 2)?;
5852 decode_byte::<A>(b3, 3)?;
5853 Ok(3)
5854 }
5855 }
5856}
5857
5858fn decode_chunk<A: Alphabet, const PAD: bool>(
5859 input: [u8; 4],
5860 output: &mut [u8],
5861) -> Result<usize, DecodeError> {
5862 let [b0, b1, b2, b3] = input;
5863 let v0 = decode_byte::<A>(b0, 0)?;
5864 let v1 = decode_byte::<A>(b1, 1)?;
5865
5866 match (b2, b3) {
5867 (b'=', b'=') if PAD => {
5868 if output.is_empty() {
5869 return Err(DecodeError::OutputTooSmall {
5870 required: 1,
5871 available: output.len(),
5872 });
5873 }
5874 if v1 & 0b0000_1111 != 0 {
5875 return Err(DecodeError::InvalidPadding { index: 1 });
5876 }
5877 output[0] = (v0 << 2) | (v1 >> 4);
5878 Ok(1)
5879 }
5880 (b'=', _) if PAD => Err(DecodeError::InvalidPadding { index: 2 }),
5881 (_, b'=') if PAD => {
5882 if output.len() < 2 {
5883 return Err(DecodeError::OutputTooSmall {
5884 required: 2,
5885 available: output.len(),
5886 });
5887 }
5888 let v2 = decode_byte::<A>(b2, 2)?;
5889 if v2 & 0b0000_0011 != 0 {
5890 return Err(DecodeError::InvalidPadding { index: 2 });
5891 }
5892 output[0] = (v0 << 2) | (v1 >> 4);
5893 output[1] = (v1 << 4) | (v2 >> 2);
5894 Ok(2)
5895 }
5896 (b'=', _) | (_, b'=') => Err(DecodeError::InvalidPadding {
5897 index: first_padding_index_unchecked(input),
5898 }),
5899 _ => {
5900 if output.len() < 3 {
5901 return Err(DecodeError::OutputTooSmall {
5902 required: 3,
5903 available: output.len(),
5904 });
5905 }
5906 let v2 = decode_byte::<A>(b2, 2)?;
5907 let v3 = decode_byte::<A>(b3, 3)?;
5908 output[0] = (v0 << 2) | (v1 >> 4);
5909 output[1] = (v1 << 4) | (v2 >> 2);
5910 output[2] = (v2 << 6) | v3;
5911 Ok(3)
5912 }
5913 }
5914}
5915
5916fn validate_tail_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
5917 match input {
5918 [] => Ok(()),
5919 [b0, b1] => {
5920 decode_byte::<A>(*b0, 0)?;
5921 let v1 = decode_byte::<A>(*b1, 1)?;
5922 if v1 & 0b0000_1111 != 0 {
5923 return Err(DecodeError::InvalidPadding { index: 1 });
5924 }
5925 Ok(())
5926 }
5927 [b0, b1, b2] => {
5928 decode_byte::<A>(*b0, 0)?;
5929 decode_byte::<A>(*b1, 1)?;
5930 let v2 = decode_byte::<A>(*b2, 2)?;
5931 if v2 & 0b0000_0011 != 0 {
5932 return Err(DecodeError::InvalidPadding { index: 2 });
5933 }
5934 Ok(())
5935 }
5936 _ => Err(DecodeError::InvalidLength),
5937 }
5938}
5939
5940fn decode_tail_unpadded<A: Alphabet>(
5941 input: &[u8],
5942 output: &mut [u8],
5943) -> Result<usize, DecodeError> {
5944 match input {
5945 [] => Ok(0),
5946 [b0, b1] => {
5947 let Some(out0) = output.first_mut() else {
5948 return Err(DecodeError::OutputTooSmall {
5949 required: 1,
5950 available: output.len(),
5951 });
5952 };
5953 let v0 = decode_byte::<A>(*b0, 0)?;
5954 let v1 = decode_byte::<A>(*b1, 1)?;
5955 if v1 & 0b0000_1111 != 0 {
5956 return Err(DecodeError::InvalidPadding { index: 1 });
5957 }
5958 *out0 = (v0 << 2) | (v1 >> 4);
5959 Ok(1)
5960 }
5961 [b0, b1, b2] => {
5962 let available = output.len();
5963 let Some([out0, out1]) = output.get_mut(..2) else {
5964 return Err(DecodeError::OutputTooSmall {
5965 required: 2,
5966 available,
5967 });
5968 };
5969 let v0 = decode_byte::<A>(*b0, 0)?;
5970 let v1 = decode_byte::<A>(*b1, 1)?;
5971 let v2 = decode_byte::<A>(*b2, 2)?;
5972 if v2 & 0b0000_0011 != 0 {
5973 return Err(DecodeError::InvalidPadding { index: 2 });
5974 }
5975 *out0 = (v0 << 2) | (v1 >> 4);
5976 *out1 = (v1 << 4) | (v2 >> 2);
5977 Ok(2)
5978 }
5979 _ => Err(DecodeError::InvalidLength),
5980 }
5981}
5982
5983fn decode_byte<A: Alphabet>(byte: u8, index: usize) -> Result<u8, DecodeError> {
5984 A::decode(byte).ok_or(DecodeError::InvalidByte { index, byte })
5985}
5986
5987fn ct_decode_slice<A: Alphabet, const PAD: bool>(
5988 input: &[u8],
5989 output: &mut [u8],
5990) -> Result<usize, DecodeError> {
5991 if input.is_empty() {
5992 return Ok(0);
5993 }
5994
5995 if PAD {
5996 ct_decode_padded::<A>(input, output)
5997 } else {
5998 ct_decode_unpadded::<A>(input, output)
5999 }
6000}
6001
6002fn ct_decode_slice_staged_clear_tail<A: Alphabet, const PAD: bool>(
6003 input: &[u8],
6004 output: &mut [u8],
6005 staging: &mut [u8],
6006) -> Result<usize, DecodeError> {
6007 let required = match ct_decoded_len::<A, PAD>(input) {
6008 Ok(required) => required,
6009 Err(err) => {
6010 wipe_bytes(output);
6011 wipe_bytes(staging);
6012 return Err(err);
6013 }
6014 };
6015
6016 if output.len() < required {
6017 wipe_bytes(output);
6018 wipe_bytes(staging);
6019 return Err(DecodeError::OutputTooSmall {
6020 required,
6021 available: output.len(),
6022 });
6023 }
6024
6025 if staging.len() < required {
6026 wipe_bytes(output);
6027 wipe_bytes(staging);
6028 return Err(DecodeError::StagingTooSmall {
6029 required,
6030 available: staging.len(),
6031 });
6032 }
6033
6034 let written = match ct_decode_slice::<A, PAD>(input, &mut staging[..required]) {
6035 Ok(written) => written,
6036 Err(err) => {
6037 wipe_bytes(output);
6038 wipe_bytes(staging);
6039 return Err(err);
6040 }
6041 };
6042
6043 output[..written].copy_from_slice(&staging[..written]);
6044 wipe_bytes(staging);
6045 wipe_tail(output, written);
6046 Ok(written)
6047}
6048
6049fn ct_decode_in_place<A: Alphabet, const PAD: bool>(
6050 buffer: &mut [u8],
6051) -> Result<usize, DecodeError> {
6052 if buffer.is_empty() {
6053 return Ok(0);
6054 }
6055
6056 if PAD {
6057 ct_decode_padded_in_place::<A>(buffer)
6058 } else {
6059 ct_decode_unpadded_in_place::<A>(buffer)
6060 }
6061}
6062
6063#[inline(never)]
6064#[allow(unsafe_code)]
6065fn ct_error_gate_barrier(invalid_byte: u8, invalid_padding: u8) {
6066 core::hint::black_box(invalid_byte | invalid_padding);
6067 core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
6068
6069 #[cfg(all(not(miri), any(target_arch = "x86", target_arch = "x86_64")))]
6070 {
6071 unsafe {
6074 core::arch::asm!("lfence", options(nostack, preserves_flags, nomem));
6075 }
6076 }
6077
6078 #[cfg(all(not(miri), target_arch = "aarch64"))]
6079 {
6080 unsafe {
6084 core::arch::asm!("isb sy", "hint #20", options(nostack, preserves_flags));
6085 }
6086 }
6087
6088 #[cfg(all(not(miri), target_arch = "arm"))]
6089 {
6090 unsafe {
6093 core::arch::asm!("isb sy", options(nostack, preserves_flags));
6094 }
6095 }
6096
6097 #[cfg(all(not(miri), any(target_arch = "riscv32", target_arch = "riscv64")))]
6098 {
6099 unsafe {
6104 core::arch::asm!("fence rw, rw", options(nostack, preserves_flags));
6105 }
6106 }
6107}
6108
6109fn ct_validate_decode<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<(), DecodeError> {
6110 if input.is_empty() {
6111 return Ok(());
6112 }
6113
6114 if PAD {
6115 ct_validate_padded::<A>(input)
6116 } else {
6117 ct_validate_unpadded::<A>(input)
6118 }
6119}
6120
6121fn ct_decoded_len<A: Alphabet, const PAD: bool>(input: &[u8]) -> Result<usize, DecodeError> {
6122 ct_validate_decode::<A, PAD>(input)?;
6123 if input.is_empty() {
6124 return Ok(0);
6125 }
6126
6127 if PAD {
6128 Ok(input.len() / 4 * 3 - ct_padding_len(input))
6129 } else {
6130 let full_quads = input.len() / 4 * 3;
6131 match input.len() % 4 {
6132 0 => Ok(full_quads),
6133 2 => Ok(full_quads + 1),
6134 3 => Ok(full_quads + 2),
6135 _ => Err(DecodeError::InvalidLength),
6136 }
6137 }
6138}
6139
6140fn ct_validate_padded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
6141 if !input.len().is_multiple_of(4) {
6142 return Err(DecodeError::InvalidLength);
6143 }
6144
6145 let padding = ct_padding_len(input);
6146 let mut invalid_byte = 0u8;
6147 let mut invalid_padding = 0u8;
6148 let mut read = 0;
6149
6150 while read + 4 < input.len() {
6151 let [b0, b1, b2, b3] =
6152 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6153 let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
6154 let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
6155 let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
6156 let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
6157
6158 invalid_byte |= !valid0;
6159 invalid_byte |= !valid1;
6160 invalid_byte |= !valid2;
6161 invalid_byte |= !valid3;
6162 invalid_padding |= ct_mask_eq_u8(b2, b'=');
6163 invalid_padding |= ct_mask_eq_u8(b3, b'=');
6164 read += 4;
6165 }
6166
6167 let final_chunk =
6168 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6169 let (_, final_invalid_byte, final_invalid_padding, _) =
6170 ct_padded_final_quantum::<A>(final_chunk, padding);
6171 invalid_byte |= final_invalid_byte;
6172 invalid_padding |= final_invalid_padding;
6173
6174 report_ct_error(invalid_byte, invalid_padding)
6175}
6176
6177fn ct_validate_unpadded<A: Alphabet>(input: &[u8]) -> Result<(), DecodeError> {
6178 if input.len() % 4 == 1 {
6179 return Err(DecodeError::InvalidLength);
6180 }
6181
6182 let mut invalid_byte = 0u8;
6183 let mut invalid_padding = 0u8;
6184 let mut read = 0;
6185
6186 while read + 4 <= input.len() {
6187 let [b0, b1, b2, b3] =
6188 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6189 let (_, valid0) = ct_decode_alphabet_byte::<A>(b0);
6190 let (_, valid1) = ct_decode_alphabet_byte::<A>(b1);
6191 let (_, valid2) = ct_decode_alphabet_byte::<A>(b2);
6192 let (_, valid3) = ct_decode_alphabet_byte::<A>(b3);
6193
6194 invalid_byte |= !valid0;
6195 invalid_byte |= !valid1;
6196 invalid_byte |= !valid2;
6197 invalid_byte |= !valid3;
6198 invalid_padding |= ct_mask_eq_u8(b0, b'=');
6199 invalid_padding |= ct_mask_eq_u8(b1, b'=');
6200 invalid_padding |= ct_mask_eq_u8(b2, b'=');
6201 invalid_padding |= ct_mask_eq_u8(b3, b'=');
6202
6203 read += 4;
6204 }
6205
6206 match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
6207 [] => {}
6208 [b0, b1] => {
6209 let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6210 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6211 invalid_byte |= !valid0;
6212 invalid_byte |= !valid1;
6213 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6214 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6215 invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
6216 }
6217 [b0, b1, b2] => {
6218 let (_, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6219 let (_, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6220 let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
6221 invalid_byte |= !valid0;
6222 invalid_byte |= !valid1;
6223 invalid_byte |= !valid2;
6224 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6225 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6226 invalid_padding |= ct_mask_eq_u8(*b2, b'=');
6227 invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
6228 }
6229 _ => {
6230 invalid_byte = 0xff;
6231 invalid_padding = 0xff;
6232 }
6233 }
6234
6235 report_ct_error(invalid_byte, invalid_padding)
6236}
6237
6238fn ct_padded_final_quantum<A: Alphabet>(
6239 input: [u8; 4],
6240 padding: usize,
6241) -> ([u8; 3], u8, u8, usize) {
6242 let [b0, b1, b2, b3] = input;
6243 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6244 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6245 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6246 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6247
6248 let padding_byte = match padding {
6249 0 => 0,
6250 1 => 1,
6251 2 => 2,
6252 _ => return ([0; 3], 0xff, 0xff, 0),
6253 };
6254 let no_padding = ct_mask_eq_u8(padding_byte, 0);
6255 let one_padding = ct_mask_eq_u8(padding_byte, 1);
6256 let two_padding = ct_mask_eq_u8(padding_byte, 2);
6257 let require_v2 = no_padding | one_padding;
6258 let require_v3 = no_padding;
6259
6260 let invalid_byte = !valid0 | !valid1 | (!valid2 & require_v2) | (!valid3 & require_v3);
6261 let invalid_padding = (ct_mask_nonzero_u8(v1 & 0b0000_1111) & two_padding)
6262 | ((ct_mask_eq_u8(b2, b'=') | ct_mask_nonzero_u8(v2 & 0b0000_0011)) & one_padding)
6263 | ((ct_mask_eq_u8(b2, b'=') | ct_mask_eq_u8(b3, b'=')) & no_padding);
6264
6265 (
6266 [(v0 << 2) | (v1 >> 4), (v1 << 4) | (v2 >> 2), (v2 << 6) | v3],
6267 invalid_byte,
6268 invalid_padding,
6269 3 - padding,
6270 )
6271}
6272
6273fn ct_decode_padded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
6274 if !input.len().is_multiple_of(4) {
6275 return Err(DecodeError::InvalidLength);
6276 }
6277
6278 let padding = ct_padding_len(input);
6279 let required = input.len() / 4 * 3 - padding;
6280 if output.len() < required {
6281 return Err(DecodeError::OutputTooSmall {
6282 required,
6283 available: output.len(),
6284 });
6285 }
6286
6287 let mut invalid_byte = 0u8;
6288 let mut invalid_padding = 0u8;
6289 let mut write = 0;
6290 let mut read = 0;
6291
6292 while read + 4 < input.len() {
6293 let [b0, b1, b2, b3] =
6294 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6295 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6296 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6297 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6298 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6299
6300 invalid_byte |= !valid0;
6301 invalid_byte |= !valid1;
6302 invalid_byte |= !valid2;
6303 invalid_byte |= !valid3;
6304 invalid_padding |= ct_mask_eq_u8(b2, b'=');
6305 invalid_padding |= ct_mask_eq_u8(b3, b'=');
6306 output[write] = (v0 << 2) | (v1 >> 4);
6307 output[write + 1] = (v1 << 4) | (v2 >> 2);
6308 output[write + 2] = (v2 << 6) | v3;
6309 write += 3;
6310 read += 4;
6311 }
6312
6313 let final_chunk =
6314 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6315 let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
6316 ct_padded_final_quantum::<A>(final_chunk, padding);
6317 invalid_byte |= final_invalid_byte;
6318 invalid_padding |= final_invalid_padding;
6319 output[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
6320 write += final_written;
6321
6322 report_ct_error(invalid_byte, invalid_padding)?;
6323 Ok(write)
6324}
6325
6326fn ct_decode_padded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
6327 if !buffer.len().is_multiple_of(4) {
6328 return Err(DecodeError::InvalidLength);
6329 }
6330
6331 let padding = ct_padding_len(buffer);
6332 let required = buffer.len() / 4 * 3 - padding;
6333 if required > buffer.len() {
6334 wipe_bytes(buffer);
6335 return Err(DecodeError::InvalidInput);
6336 }
6337
6338 let mut invalid_byte = 0u8;
6339 let mut invalid_padding = 0u8;
6340 let mut write = 0;
6341 let mut read = 0;
6342
6343 while read + 4 < buffer.len() {
6344 let [b0, b1, b2, b3] =
6345 read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
6346 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6347 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6348 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6349 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6350
6351 invalid_byte |= !valid0;
6352 invalid_byte |= !valid1;
6353 invalid_byte |= !valid2;
6354 invalid_byte |= !valid3;
6355 invalid_padding |= ct_mask_eq_u8(b2, b'=');
6356 invalid_padding |= ct_mask_eq_u8(b3, b'=');
6357 buffer[write] = (v0 << 2) | (v1 >> 4);
6358 buffer[write + 1] = (v1 << 4) | (v2 >> 2);
6359 buffer[write + 2] = (v2 << 6) | v3;
6360 write += 3;
6361 read += 4;
6362 }
6363
6364 let final_chunk =
6365 read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
6366 let (final_bytes, final_invalid_byte, final_invalid_padding, final_written) =
6367 ct_padded_final_quantum::<A>(final_chunk, padding);
6368 invalid_byte |= final_invalid_byte;
6369 invalid_padding |= final_invalid_padding;
6370 buffer[write..write + final_written].copy_from_slice(&final_bytes[..final_written]);
6371 write += final_written;
6372
6373 if write != required {
6374 ct_error_gate_barrier(invalid_byte, invalid_padding);
6375 wipe_bytes(buffer);
6376 return Err(DecodeError::InvalidInput);
6377 }
6378 if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
6379 wipe_bytes(buffer);
6380 return Err(err);
6381 }
6382 Ok(write)
6383}
6384
6385fn ct_decode_unpadded<A: Alphabet>(input: &[u8], output: &mut [u8]) -> Result<usize, DecodeError> {
6386 if input.len() % 4 == 1 {
6387 return Err(DecodeError::InvalidLength);
6388 }
6389
6390 let required = decoded_capacity(input.len());
6391 if output.len() < required {
6392 return Err(DecodeError::OutputTooSmall {
6393 required,
6394 available: output.len(),
6395 });
6396 }
6397
6398 let mut invalid_byte = 0u8;
6399 let mut invalid_padding = 0u8;
6400 let mut write = 0;
6401 let mut read = 0;
6402
6403 while read + 4 <= input.len() {
6404 let [b0, b1, b2, b3] =
6405 read_quad_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding);
6406 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6407 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6408 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6409 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6410
6411 invalid_byte |= !valid0;
6412 invalid_byte |= !valid1;
6413 invalid_byte |= !valid2;
6414 invalid_byte |= !valid3;
6415 invalid_padding |= ct_mask_eq_u8(b0, b'=');
6416 invalid_padding |= ct_mask_eq_u8(b1, b'=');
6417 invalid_padding |= ct_mask_eq_u8(b2, b'=');
6418 invalid_padding |= ct_mask_eq_u8(b3, b'=');
6419
6420 output[write] = (v0 << 2) | (v1 >> 4);
6421 output[write + 1] = (v1 << 4) | (v2 >> 2);
6422 output[write + 2] = (v2 << 6) | v3;
6423 read += 4;
6424 write += 3;
6425 }
6426
6427 match read_tail_or_mark_invalid(input, read, &mut invalid_byte, &mut invalid_padding) {
6428 [] => {}
6429 [b0, b1] => {
6430 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6431 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6432 invalid_byte |= !valid0;
6433 invalid_byte |= !valid1;
6434 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6435 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6436 invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
6437 output[write] = (v0 << 2) | (v1 >> 4);
6438 write += 1;
6439 }
6440 [b0, b1, b2] => {
6441 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6442 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6443 let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
6444 invalid_byte |= !valid0;
6445 invalid_byte |= !valid1;
6446 invalid_byte |= !valid2;
6447 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6448 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6449 invalid_padding |= ct_mask_eq_u8(*b2, b'=');
6450 invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
6451 output[write] = (v0 << 2) | (v1 >> 4);
6452 output[write + 1] = (v1 << 4) | (v2 >> 2);
6453 write += 2;
6454 }
6455 _ => {
6456 invalid_byte = 0xff;
6457 invalid_padding = 0xff;
6458 }
6459 }
6460
6461 report_ct_error(invalid_byte, invalid_padding)?;
6462 Ok(write)
6463}
6464
6465fn ct_decode_unpadded_in_place<A: Alphabet>(buffer: &mut [u8]) -> Result<usize, DecodeError> {
6466 if buffer.len() % 4 == 1 {
6467 return Err(DecodeError::InvalidLength);
6468 }
6469
6470 let required = decoded_capacity(buffer.len());
6471 if required > buffer.len() {
6472 wipe_bytes(buffer);
6473 return Err(DecodeError::InvalidInput);
6474 }
6475
6476 let mut invalid_byte = 0u8;
6477 let mut invalid_padding = 0u8;
6478 let mut write = 0;
6479 let mut read = 0;
6480
6481 while read + 4 <= buffer.len() {
6482 let [b0, b1, b2, b3] =
6483 read_quad_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
6484 let (v0, valid0) = ct_decode_alphabet_byte::<A>(b0);
6485 let (v1, valid1) = ct_decode_alphabet_byte::<A>(b1);
6486 let (v2, valid2) = ct_decode_alphabet_byte::<A>(b2);
6487 let (v3, valid3) = ct_decode_alphabet_byte::<A>(b3);
6488
6489 invalid_byte |= !valid0;
6490 invalid_byte |= !valid1;
6491 invalid_byte |= !valid2;
6492 invalid_byte |= !valid3;
6493 invalid_padding |= ct_mask_eq_u8(b0, b'=');
6494 invalid_padding |= ct_mask_eq_u8(b1, b'=');
6495 invalid_padding |= ct_mask_eq_u8(b2, b'=');
6496 invalid_padding |= ct_mask_eq_u8(b3, b'=');
6497
6498 buffer[write] = (v0 << 2) | (v1 >> 4);
6499 buffer[write + 1] = (v1 << 4) | (v2 >> 2);
6500 buffer[write + 2] = (v2 << 6) | v3;
6501 read += 4;
6502 write += 3;
6503 }
6504
6505 let tail = read_tail_or_mark_invalid(buffer, read, &mut invalid_byte, &mut invalid_padding);
6506 match tail {
6507 [] => {}
6508 [b0, b1] => {
6509 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6510 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6511 invalid_byte |= !valid0;
6512 invalid_byte |= !valid1;
6513 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6514 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6515 invalid_padding |= ct_mask_nonzero_u8(v1 & 0b0000_1111);
6516 buffer[write] = (v0 << 2) | (v1 >> 4);
6517 write += 1;
6518 }
6519 [b0, b1, b2] => {
6520 let (v0, valid0) = ct_decode_alphabet_byte::<A>(*b0);
6521 let (v1, valid1) = ct_decode_alphabet_byte::<A>(*b1);
6522 let (v2, valid2) = ct_decode_alphabet_byte::<A>(*b2);
6523 invalid_byte |= !valid0;
6524 invalid_byte |= !valid1;
6525 invalid_byte |= !valid2;
6526 invalid_padding |= ct_mask_eq_u8(*b0, b'=');
6527 invalid_padding |= ct_mask_eq_u8(*b1, b'=');
6528 invalid_padding |= ct_mask_eq_u8(*b2, b'=');
6529 invalid_padding |= ct_mask_nonzero_u8(v2 & 0b0000_0011);
6530 buffer[write] = (v0 << 2) | (v1 >> 4);
6531 buffer[write + 1] = (v1 << 4) | (v2 >> 2);
6532 write += 2;
6533 }
6534 _ => {
6535 invalid_byte = 0xff;
6536 invalid_padding = 0xff;
6537 }
6538 }
6539
6540 if write != required {
6541 ct_error_gate_barrier(invalid_byte, invalid_padding);
6542 wipe_bytes(buffer);
6543 return Err(DecodeError::InvalidInput);
6544 }
6545 if let Err(err) = report_ct_error(invalid_byte, invalid_padding) {
6546 wipe_bytes(buffer);
6547 return Err(err);
6548 }
6549 Ok(write)
6550}
6551
6552fn read_tail(input: &[u8], offset: usize) -> Result<&[u8], DecodeError> {
6553 input.get(offset..).ok_or(DecodeError::InvalidLength)
6554}
6555
6556fn read_quad_or_mark_invalid(
6557 input: &[u8],
6558 offset: usize,
6559 invalid_byte: &mut u8,
6560 invalid_padding: &mut u8,
6561) -> [u8; 4] {
6562 if let Ok(quad) = read_quad(input, offset) {
6563 quad
6564 } else {
6565 debug_assert!(
6566 false,
6567 "read_quad failed inside length-validated constant-time decode loop"
6568 );
6569 *invalid_byte = 0xff;
6570 *invalid_padding = 0xff;
6571 [0; 4]
6572 }
6573}
6574
6575fn read_tail_or_mark_invalid<'a>(
6576 input: &'a [u8],
6577 offset: usize,
6578 invalid_byte: &mut u8,
6579 invalid_padding: &mut u8,
6580) -> &'a [u8] {
6581 if let Ok(tail) = read_tail(input, offset) {
6582 tail
6583 } else {
6584 debug_assert!(
6585 false,
6586 "read_tail failed inside length-validated constant-time decode loop"
6587 );
6588 *invalid_byte = 0xff;
6589 *invalid_padding = 0xff;
6590 &[]
6591 }
6592}
6593
6594#[inline(never)]
6595#[allow(unsafe_code)]
6596fn ct_decode_alphabet_byte<A: Alphabet>(byte: u8) -> (u8, u8) {
6597 let mut decoded = 0u8;
6598 let mut valid = 0u8;
6599 let mut candidate = 0u8;
6600
6601 while candidate < 64 {
6602 let matches = core::hint::black_box(ct_mask_eq_u8(
6603 core::hint::black_box(byte),
6604 core::hint::black_box(A::ENCODE[candidate as usize]),
6605 ));
6606 decoded = core::hint::black_box(
6607 core::hint::black_box(decoded) | core::hint::black_box(candidate & matches),
6608 );
6609 decoded = unsafe { core::ptr::read_volatile(&raw const decoded) };
6613 valid =
6614 core::hint::black_box(core::hint::black_box(valid) | core::hint::black_box(matches));
6615 valid = unsafe { core::ptr::read_volatile(&raw const valid) };
6619 candidate += 1;
6620 }
6621
6622 (decoded, valid)
6623}
6624
6625fn ct_padding_len(input: &[u8]) -> usize {
6626 let Some((&last, before_last_prefix)) = input.split_last() else {
6627 return 0;
6628 };
6629 let Some(&before_last) = before_last_prefix.last() else {
6630 return 0;
6631 };
6632 usize::from(ct_mask_eq_u8(last, b'=') & 1) + usize::from(ct_mask_eq_u8(before_last, b'=') & 1)
6633}
6634
6635fn report_ct_error(invalid_byte: u8, invalid_padding: u8) -> Result<(), DecodeError> {
6636 ct_error_gate_barrier(invalid_byte, invalid_padding);
6637
6638 if (invalid_byte | invalid_padding) != 0 {
6639 Err(DecodeError::InvalidInput)
6640 } else {
6641 Ok(())
6642 }
6643}
6644
6645#[cfg(kani)]
6646mod kani_proofs {
6647 use super::{
6648 STANDARD, Standard, checked_encoded_len, ct, decode_byte, decode_chunk,
6649 decode_tail_unpadded, decoded_capacity, validate_tail_unpadded,
6650 };
6651
6652 #[kani::proof]
6653 fn checked_encoded_len_is_bounded_for_small_inputs() {
6654 let len = usize::from(kani::any::<u8>());
6655 let padded = kani::any::<bool>();
6656 let encoded = checked_encoded_len(len, padded).expect("u8 input length cannot overflow");
6657
6658 assert!(encoded >= len);
6659 assert!(encoded <= len / 3 * 4 + 4);
6660 }
6661
6662 #[kani::proof]
6663 fn decoded_capacity_is_bounded_for_small_inputs() {
6664 let len = usize::from(kani::any::<u8>());
6665 let capacity = decoded_capacity(len);
6666
6667 assert!(capacity <= len / 4 * 3 + 2);
6668 }
6669
6670 #[kani::proof]
6671 #[kani::unwind(3)]
6672 fn standard_in_place_decode_returns_prefix_within_buffer() {
6673 let mut buffer = kani::any::<[u8; 8]>();
6674 let result = STANDARD.decode_in_place(&mut buffer);
6675
6676 if let Ok(decoded) = result {
6677 assert!(decoded.len() <= 8);
6678 }
6679 }
6680
6681 #[kani::proof]
6682 #[kani::unwind(3)]
6683 fn standard_decode_slice_returns_written_within_output() {
6684 let input = kani::any::<[u8; 4]>();
6685 let mut output = kani::any::<[u8; 3]>();
6686 let result = STANDARD.decode_slice(&input, &mut output);
6687
6688 if let Ok(written) = result {
6689 assert!(written <= output.len());
6690 }
6691 }
6692
6693 #[kani::proof]
6694 #[kani::unwind(3)]
6695 fn standard_decode_chunk_returns_written_within_output() {
6696 let input = kani::any::<[u8; 4]>();
6697 let mut output = kani::any::<[u8; 3]>();
6698 let result = decode_chunk::<Standard, true>(input, &mut output);
6699
6700 if let Ok(written) = result {
6701 assert!(written <= output.len());
6702 assert!(written <= 3);
6703 }
6704 }
6705
6706 #[kani::proof]
6707 #[kani::unwind(3)]
6708 fn standard_decode_chunk_bit_packing_matches_decoded_values() {
6709 let input = kani::any::<[u8; 4]>();
6710 let mut output = kani::any::<[u8; 3]>();
6711 let result = decode_chunk::<Standard, true>(input, &mut output);
6712
6713 if let Ok(written) = result {
6714 let v0 = decode_byte::<Standard>(input[0], 0).expect("successful chunk has v0");
6715 let v1 = decode_byte::<Standard>(input[1], 1).expect("successful chunk has v1");
6716
6717 assert!(output[0] == ((v0 << 2) | (v1 >> 4)));
6718
6719 if written >= 2 {
6720 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
6721 assert!(output[1] == ((v1 << 4) | (v2 >> 2)));
6722 }
6723
6724 if written == 3 {
6725 let v2 = decode_byte::<Standard>(input[2], 2).expect("successful chunk has v2");
6726 let v3 = decode_byte::<Standard>(input[3], 3).expect("successful chunk has v3");
6727 assert!(output[2] == ((v2 << 6) | v3));
6728 }
6729 }
6730 }
6731
6732 #[kani::proof]
6733 #[kani::unwind(3)]
6734 fn standard_validate_tail_unpadded_accepts_or_rejects_without_panic() {
6735 let input = kani::any::<[u8; 3]>();
6736 let len = usize::from(kani::any::<u8>() % 4);
6737 let result = validate_tail_unpadded::<Standard>(&input[..len]);
6738
6739 if result.is_ok() {
6740 assert!(len == 0 || len == 2 || len == 3);
6741 }
6742 }
6743
6744 #[kani::proof]
6745 #[kani::unwind(3)]
6746 fn standard_decode_two_byte_tail_returns_written_within_output() {
6747 let input = kani::any::<[u8; 2]>();
6748 let mut output = kani::any::<[u8; 1]>();
6749 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
6750
6751 if let Ok(written) = result {
6752 assert!(written <= output.len());
6753 assert!(written == 1);
6754 }
6755 }
6756
6757 #[kani::proof]
6758 #[kani::unwind(3)]
6759 fn standard_decode_three_byte_tail_returns_written_within_output() {
6760 let input = kani::any::<[u8; 3]>();
6761 let mut output = kani::any::<[u8; 2]>();
6762 let result = decode_tail_unpadded::<Standard>(&input, &mut output);
6763
6764 if let Ok(written) = result {
6765 assert!(written <= output.len());
6766 assert!(written == 2);
6767 }
6768 }
6769
6770 #[kani::proof]
6771 #[kani::unwind(3)]
6772 fn standard_decode_slice_clear_tail_clears_output_on_error() {
6773 let input = kani::any::<[u8; 4]>();
6774 let mut output = kani::any::<[u8; 3]>();
6775 let result = STANDARD.decode_slice_clear_tail(&input, &mut output);
6776
6777 if result.is_err() {
6778 assert!(output.iter().all(|byte| *byte == 0));
6779 }
6780 }
6781
6782 #[kani::proof]
6783 #[kani::unwind(3)]
6784 fn standard_encode_slice_returns_written_within_output() {
6785 let input = kani::any::<[u8; 3]>();
6786 let mut output = kani::any::<[u8; 4]>();
6787 let result = STANDARD.encode_slice(&input, &mut output);
6788
6789 if let Ok(written) = result {
6790 assert!(written <= output.len());
6791 }
6792 }
6793
6794 #[kani::proof]
6795 #[kani::unwind(4)]
6796 fn standard_encode_in_place_returns_prefix_within_buffer() {
6797 let mut buffer = kani::any::<[u8; 8]>();
6798 let input_len = usize::from(kani::any::<u8>() % 9);
6799 let result = STANDARD.encode_in_place(&mut buffer, input_len);
6800
6801 if let Ok(encoded) = result {
6802 assert!(encoded.len() <= 8);
6803 }
6804 }
6805
6806 #[kani::proof]
6807 #[kani::unwind(3)]
6808 fn standard_clear_tail_decode_clears_buffer_on_error() {
6809 let mut buffer = kani::any::<[u8; 4]>();
6810 let result = STANDARD.decode_in_place_clear_tail(&mut buffer);
6811
6812 if result.is_err() {
6813 assert!(buffer.iter().all(|byte| *byte == 0));
6814 }
6815 }
6816
6817 #[kani::proof]
6818 #[kani::unwind(3)]
6819 fn ct_standard_decode_slice_returns_written_within_output() {
6820 let input = kani::any::<[u8; 4]>();
6821 let mut output = kani::any::<[u8; 3]>();
6822 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
6823
6824 if let Ok(written) = result {
6825 assert!(written <= output.len());
6826 }
6827 }
6828
6829 #[kani::proof]
6830 #[kani::unwind(3)]
6831 fn ct_standard_decode_slice_clear_tail_clears_output_on_error() {
6832 let input = kani::any::<[u8; 4]>();
6833 let mut output = kani::any::<[u8; 3]>();
6834 let result = ct::STANDARD.decode_slice_clear_tail(&input, &mut output);
6835
6836 if result.is_err() {
6837 assert!(output.iter().all(|byte| *byte == 0));
6838 }
6839 }
6840
6841 #[kani::proof]
6842 #[kani::unwind(3)]
6843 fn ct_standard_decode_in_place_clear_tail_clears_buffer_on_error() {
6844 let mut buffer = kani::any::<[u8; 4]>();
6845 let result = ct::STANDARD.decode_in_place_clear_tail(&mut buffer);
6846
6847 if result.is_err() {
6848 assert!(buffer.iter().all(|byte| *byte == 0));
6849 }
6850 }
6851
6852 #[kani::proof]
6853 #[kani::unwind(3)]
6854 fn ct_standard_validate_matches_decode_for_one_quantum() {
6855 let input = kani::any::<[u8; 4]>();
6856 let mut output = kani::any::<[u8; 3]>();
6857
6858 let validate_ok = ct::STANDARD.validate_result(&input).is_ok();
6859 let decode_ok = ct::STANDARD
6860 .decode_slice_clear_tail(&input, &mut output)
6861 .is_ok();
6862
6863 assert!(validate_ok == decode_ok);
6864 }
6865}
6866
6867#[cfg(test)]
6868mod tests {
6869 use super::*;
6870
6871 fn fill_pattern(output: &mut [u8], seed: usize) {
6872 for (index, byte) in output.iter_mut().enumerate() {
6873 let value = (index * 73 + seed * 19) % 256;
6874 *byte = u8::try_from(value).unwrap();
6875 }
6876 }
6877
6878 fn assert_encode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
6879 where
6880 A: Alphabet,
6881 {
6882 let engine = Engine::<A, PAD>::new();
6883 let mut dispatched = [0x55; 256];
6884 let mut scalar = [0xaa; 256];
6885
6886 let dispatched_result = engine.encode_slice(input, &mut dispatched);
6887 let scalar_result = backend::scalar_reference_encode_slice::<A, PAD>(input, &mut scalar);
6888
6889 assert_eq!(dispatched_result, scalar_result);
6890 if let Ok(written) = dispatched_result {
6891 assert_eq!(&dispatched[..written], &scalar[..written]);
6892 }
6893
6894 let required = checked_encoded_len(input.len(), PAD).unwrap();
6895 if required > 0 {
6896 let mut dispatched_short = [0x55; 256];
6897 let mut scalar_short = [0xaa; 256];
6898 let available = required - 1;
6899
6900 assert_eq!(
6901 engine.encode_slice(input, &mut dispatched_short[..available]),
6902 backend::scalar_reference_encode_slice::<A, PAD>(
6903 input,
6904 &mut scalar_short[..available],
6905 )
6906 );
6907 }
6908 }
6909
6910 fn assert_decode_backend_matches_scalar<A, const PAD: bool>(input: &[u8])
6911 where
6912 A: Alphabet,
6913 {
6914 let engine = Engine::<A, PAD>::new();
6915 let mut dispatched = [0x55; 128];
6916 let mut scalar = [0xaa; 128];
6917
6918 let dispatched_result = engine.decode_slice(input, &mut dispatched);
6919 let scalar_result = backend::scalar_reference_decode_slice::<A, PAD>(input, &mut scalar);
6920
6921 assert_eq!(dispatched_result, scalar_result);
6922 if let Ok(written) = dispatched_result {
6923 assert_eq!(&dispatched[..written], &scalar[..written]);
6924
6925 if written > 0 {
6926 let mut dispatched_short = [0x55; 128];
6927 let mut scalar_short = [0xaa; 128];
6928 let available = written - 1;
6929
6930 assert_eq!(
6931 engine.decode_slice(input, &mut dispatched_short[..available]),
6932 backend::scalar_reference_decode_slice::<A, PAD>(
6933 input,
6934 &mut scalar_short[..available],
6935 )
6936 );
6937 }
6938 }
6939 }
6940
6941 fn assert_backend_round_trip_matches_scalar<A, const PAD: bool>(input: &[u8])
6942 where
6943 A: Alphabet,
6944 {
6945 assert_encode_backend_matches_scalar::<A, PAD>(input);
6946
6947 let mut encoded = [0; 256];
6948 let encoded_len =
6949 backend::scalar_reference_encode_slice::<A, PAD>(input, &mut encoded).unwrap();
6950 assert_decode_backend_matches_scalar::<A, PAD>(&encoded[..encoded_len]);
6951 }
6952
6953 fn assert_standard_decode_chunk_matches_input(input: &[u8]) {
6954 let mut encoded = [0u8; 4];
6955 let encoded_len = STANDARD.encode_slice(input, &mut encoded).unwrap();
6956 assert_eq!(encoded_len, 4);
6957
6958 let chunk = [encoded[0], encoded[1], encoded[2], encoded[3]];
6959 let mut decoded = [0u8; 3];
6960 let decoded_len = decode_chunk::<Standard, true>(chunk, &mut decoded).unwrap();
6961
6962 assert_eq!(decoded_len, input.len());
6963 assert_eq!(&decoded[..decoded_len], input);
6964 }
6965
6966 #[test]
6967 fn backend_dispatch_matches_scalar_reference_for_canonical_inputs() {
6968 let mut input = [0; 128];
6969
6970 for input_len in 0..=input.len() {
6971 fill_pattern(&mut input[..input_len], input_len);
6972 let input = &input[..input_len];
6973
6974 assert_backend_round_trip_matches_scalar::<Standard, true>(input);
6975 assert_backend_round_trip_matches_scalar::<Standard, false>(input);
6976 assert_backend_round_trip_matches_scalar::<UrlSafe, true>(input);
6977 assert_backend_round_trip_matches_scalar::<UrlSafe, false>(input);
6978 }
6979 }
6980
6981 #[test]
6982 fn backend_dispatch_matches_scalar_reference_for_malformed_inputs() {
6983 for input in [
6984 &b"Z"[..],
6985 b"====",
6986 b"AA=A",
6987 b"Zh==",
6988 b"Zm9=",
6989 b"Zm9v$g==",
6990 b"Zm9vZh==",
6991 ] {
6992 assert_decode_backend_matches_scalar::<Standard, true>(input);
6993 }
6994
6995 for input in [&b"Z"[..], b"AA=A", b"Zh", b"Zm9", b"Zm9vYg$"] {
6996 assert_decode_backend_matches_scalar::<Standard, false>(input);
6997 }
6998
6999 assert_decode_backend_matches_scalar::<UrlSafe, true>(b"AA+A");
7000 assert_decode_backend_matches_scalar::<UrlSafe, false>(b"AA/A");
7001 assert_decode_backend_matches_scalar::<Standard, true>(b"AA-A");
7002 assert_decode_backend_matches_scalar::<Standard, false>(b"AA_A");
7003 }
7004
7005 #[test]
7006 fn decode_chunk_bit_packing_matches_exhaustive_small_inputs() {
7007 for byte in u8::MIN..=u8::MAX {
7008 assert_standard_decode_chunk_matches_input(&[byte]);
7009 }
7010
7011 for first in u8::MIN..=u8::MAX {
7012 for second in u8::MIN..=u8::MAX {
7013 assert_standard_decode_chunk_matches_input(&[first, second]);
7014 }
7015 }
7016 }
7017
7018 #[test]
7019 fn decode_chunk_bit_packing_matches_representative_full_quanta() {
7020 const SAMPLES: [u8; 16] = [
7021 0, 1, 2, 15, 16, 31, 32, 63, 64, 95, 127, 128, 191, 192, 254, 255,
7022 ];
7023
7024 for first in SAMPLES {
7025 for second in SAMPLES {
7026 for third in SAMPLES {
7027 assert_standard_decode_chunk_matches_input(&[first, second, third]);
7028 }
7029 }
7030 }
7031 }
7032
7033 #[test]
7034 fn ct_padded_final_quantum_fails_closed_for_invalid_padding_count() {
7035 let (_, invalid_byte, invalid_padding, written) =
7036 ct_padded_final_quantum::<Standard>(*b"ABCD", 3);
7037
7038 assert_ne!(invalid_byte, 0);
7039 assert_ne!(invalid_padding, 0);
7040 assert_eq!(written, 0);
7041 assert_eq!(
7042 report_ct_error(invalid_byte, invalid_padding),
7043 Err(DecodeError::InvalidInput)
7044 );
7045 }
7046
7047 #[cfg(feature = "simd")]
7048 #[test]
7049 fn simd_dispatch_scaffold_keeps_scalar_active() {
7050 assert_eq!(simd::active_backend(), simd::ActiveBackend::Scalar);
7051 let _candidate = simd::detected_candidate();
7052 }
7053
7054 #[test]
7055 fn encodes_standard_vectors() {
7056 let vectors = [
7057 (&b""[..], &b""[..]),
7058 (&b"f"[..], &b"Zg=="[..]),
7059 (&b"fo"[..], &b"Zm8="[..]),
7060 (&b"foo"[..], &b"Zm9v"[..]),
7061 (&b"foob"[..], &b"Zm9vYg=="[..]),
7062 (&b"fooba"[..], &b"Zm9vYmE="[..]),
7063 (&b"foobar"[..], &b"Zm9vYmFy"[..]),
7064 ];
7065 for (input, expected) in vectors {
7066 let mut output = [0u8; 16];
7067 let written = STANDARD.encode_slice(input, &mut output).unwrap();
7068 assert_eq!(&output[..written], expected);
7069 }
7070 }
7071
7072 #[test]
7073 fn decodes_standard_vectors() {
7074 let vectors = [
7075 (&b""[..], &b""[..]),
7076 (&b"Zg=="[..], &b"f"[..]),
7077 (&b"Zm8="[..], &b"fo"[..]),
7078 (&b"Zm9v"[..], &b"foo"[..]),
7079 (&b"Zm9vYg=="[..], &b"foob"[..]),
7080 (&b"Zm9vYmE="[..], &b"fooba"[..]),
7081 (&b"Zm9vYmFy"[..], &b"foobar"[..]),
7082 ];
7083 for (input, expected) in vectors {
7084 let mut output = [0u8; 16];
7085 let written = STANDARD.decode_slice(input, &mut output).unwrap();
7086 assert_eq!(&output[..written], expected);
7087 }
7088 }
7089
7090 #[test]
7091 fn supports_unpadded_url_safe() {
7092 let mut encoded = [0u8; 16];
7093 let written = URL_SAFE_NO_PAD
7094 .encode_slice(b"\xfb\xff", &mut encoded)
7095 .unwrap();
7096 assert_eq!(&encoded[..written], b"-_8");
7097
7098 let mut decoded = [0u8; 2];
7099 let written = URL_SAFE_NO_PAD
7100 .decode_slice(&encoded[..written], &mut decoded)
7101 .unwrap();
7102 assert_eq!(&decoded[..written], b"\xfb\xff");
7103 }
7104
7105 #[test]
7106 fn decodes_in_place() {
7107 let mut buffer = *b"Zm9vYmFy";
7108 let decoded = STANDARD_NO_PAD.decode_in_place(&mut buffer).unwrap();
7109 assert_eq!(decoded, b"foobar");
7110 }
7111
7112 #[test]
7113 fn rejects_non_canonical_padding_bits() {
7114 let mut output = [0u8; 4];
7115 assert_eq!(
7116 STANDARD.decode_slice(b"Zh==", &mut output),
7117 Err(DecodeError::InvalidPadding { index: 1 })
7118 );
7119 assert_eq!(
7120 STANDARD.decode_slice(b"Zm9=", &mut output),
7121 Err(DecodeError::InvalidPadding { index: 2 })
7122 );
7123 }
7124}