base64url_nopad

base64url without padding library.
git clone https://git.philomathiclife.com/repos/base64url_nopad
Log | Files | Refs | README

lib.rs (65446B)


      1 //! [![git]](https://git.philomathiclife.com/base64url_nopad/log.html) [![crates-io]](https://crates.io/crates/base64url_nopad) [![docs-rs]](crate)
      2 //!
      3 //! [git]: https://git.philomathiclife.com/git_badge.svg
      4 //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
      5 //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
      6 //!
      7 //! `base64url_nopad` is a library for efficient and correct encoding and decoding of base64url without padding
      8 //! data. All functions that can be `const` are `const`. Great care is made to ensure _all_ arithmetic is free
      9 //! from "side effects" (e.g., overflow). `panic`s are avoided at all costs unless explicitly documented
     10 //! _including_ `panic`s related to memory allocations.
     11 //!
     12 //! ## `base64url_nopad` in action
     13 //!
     14 //! ```
     15 //! # use base64url_nopad::DecodeErr;
     16 //! /// Length of our input to encode.
     17 //! const INPUT_LEN: usize = 259;
     18 //! /// The base64url encoded value without padding of our input.
     19 //! const ENCODED_VAL: &str = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_1MH0Q";
     20 //! let mut input = [0; INPUT_LEN];
     21 //! for i in 0..=255 {
     22 //!     input[usize::from(i)] = i;
     23 //! }
     24 //! input[256] = 83;
     25 //! input[257] = 7;
     26 //! input[258] = 209;
     27 //! let mut output = [0; base64url_nopad::encode_len(INPUT_LEN)];
     28 //! assert_eq!(base64url_nopad::encode_buffer(&input, &mut output), ENCODED_VAL);
     29 //! assert_eq!(base64url_nopad::decode_len(output.len()), Some(INPUT_LEN));
     30 //! base64url_nopad::decode_buffer(ENCODED_VAL.as_bytes(), &mut output[..INPUT_LEN])?;
     31 //! assert_eq!(input, output[..INPUT_LEN]);
     32 //! # Ok::<_, DecodeErr>(())
     33 //! ```
     34 //!
     35 //! ## Cargo "features"
     36 //!
     37 //! ### `alloc`
     38 //!
     39 //! Enables support for memory allocations via [`alloc`].
     40 //!
     41 //! ## Correctness of code
     42 //!
     43 //! This library is written in a way that is free from any overflow, underflow, or other kinds of
     44 //! "arithmetic side effects". All functions that can `panic` are explicitly documented as such; and all
     45 //! possible `panic`s are isolated to convenience functions that `panic` instead of error. Strict encoding and
     46 //! decoding is performed; thus if an input contains _any_ invalid data, it is guaranteed to fail when decoding
     47 //! it (e.g., trailing non-zero bits).
     48 #![expect(
     49     clippy::doc_paragraphs_missing_punctuation,
     50     reason = "false positive for crate documentation having image links"
     51 )]
     52 #![cfg_attr(
     53     all(
     54         test,
     55         target_os = "macos",
     56         any(
     57             target_pointer_width = "16",
     58             target_pointer_width = "32",
     59             target_pointer_width = "64"
     60         )
     61     ),
     62     allow(
     63         linker_messages,
     64         reason = "getrandom produces a linker message on macos"
     65     )
     66 )]
     67 #![no_std]
     68 #![cfg_attr(docsrs, feature(doc_cfg))]
     69 #[cfg(any(doc, feature = "alloc"))]
     70 extern crate alloc;
     71 #[cfg(any(doc, feature = "alloc"))]
     72 use alloc::{collections::TryReserveError, string::String, vec::Vec};
     73 use core::{
     74     error::Error,
     75     fmt::{self, Display, Formatter, Write},
     76     hint::cold_path,
     77     mem,
     78 };
     79 /// The base64url alphabet.
     80 #[expect(
     81     non_camel_case_types,
     82     reason = "want to use a variant as close to what the value is"
     83 )]
     84 #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
     85 #[repr(u8)]
     86 pub enum Alphabet {
     87     /// A.
     88     #[default]
     89     A,
     90     /// B.
     91     B,
     92     /// C.
     93     C,
     94     /// D.
     95     D,
     96     /// E.
     97     E,
     98     /// F.
     99     F,
    100     /// G.
    101     G,
    102     /// H.
    103     H,
    104     /// I.
    105     I,
    106     /// J.
    107     J,
    108     /// K.
    109     K,
    110     /// L.
    111     L,
    112     /// M.
    113     M,
    114     /// N.
    115     N,
    116     /// O.
    117     O,
    118     /// P.
    119     P,
    120     /// Q.
    121     Q,
    122     /// R.
    123     R,
    124     /// S.
    125     S,
    126     /// T.
    127     T,
    128     /// U.
    129     U,
    130     /// V.
    131     V,
    132     /// W.
    133     W,
    134     /// X.
    135     X,
    136     /// Y.
    137     Y,
    138     /// Z.
    139     Z,
    140     /// a.
    141     a,
    142     /// b.
    143     b,
    144     /// c.
    145     c,
    146     /// d.
    147     d,
    148     /// e.
    149     e,
    150     /// f.
    151     f,
    152     /// g.
    153     g,
    154     /// h.
    155     h,
    156     /// i.
    157     i,
    158     /// j.
    159     j,
    160     /// k.
    161     k,
    162     /// l.
    163     l,
    164     /// m.
    165     m,
    166     /// n.
    167     n,
    168     /// o.
    169     o,
    170     /// p.
    171     p,
    172     /// q.
    173     q,
    174     /// r.
    175     r,
    176     /// s.
    177     s,
    178     /// t.
    179     t,
    180     /// u.
    181     u,
    182     /// v.
    183     v,
    184     /// w.
    185     w,
    186     /// x.
    187     x,
    188     /// y.
    189     y,
    190     /// z.
    191     z,
    192     /// 0.
    193     Zero,
    194     /// 1.
    195     One,
    196     /// 2.
    197     Two,
    198     /// 3.
    199     Three,
    200     /// 4.
    201     Four,
    202     /// 5.
    203     Five,
    204     /// 6.
    205     Six,
    206     /// 7.
    207     Seven,
    208     /// 8.
    209     Eight,
    210     /// 9.
    211     Nine,
    212     /// -.
    213     Hyphen,
    214     /// _.
    215     Underscore,
    216 }
    217 /// Sorted ASCII `u8`s for [`Alphabet`].
    218 const ASCII: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
    219 /// Sorted `char`s for [`Alphabet`].
    220 const CHARS: &[char; 64] = &[
    221     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
    222     'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
    223     'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4',
    224     '5', '6', '7', '8', '9', '-', '_',
    225 ];
    226 /// [`Alphabet`] variants indexed based on the their ASCII representation.
    227 const FROM_ASCII: &[Option<Alphabet>; 256] = &[
    228     None,
    229     None,
    230     None,
    231     None,
    232     None,
    233     None,
    234     None,
    235     None,
    236     None,
    237     None,
    238     None,
    239     None,
    240     None,
    241     None,
    242     None,
    243     None,
    244     None,
    245     None,
    246     None,
    247     None,
    248     None,
    249     None,
    250     None,
    251     None,
    252     None,
    253     None,
    254     None,
    255     None,
    256     None,
    257     None,
    258     None,
    259     None,
    260     None,
    261     None,
    262     None,
    263     None,
    264     None,
    265     None,
    266     None,
    267     None,
    268     None,
    269     None,
    270     None,
    271     None,
    272     None,
    273     Some(Alphabet::Hyphen),
    274     None,
    275     None,
    276     Some(Alphabet::Zero),
    277     Some(Alphabet::One),
    278     Some(Alphabet::Two),
    279     Some(Alphabet::Three),
    280     Some(Alphabet::Four),
    281     Some(Alphabet::Five),
    282     Some(Alphabet::Six),
    283     Some(Alphabet::Seven),
    284     Some(Alphabet::Eight),
    285     Some(Alphabet::Nine),
    286     None,
    287     None,
    288     None,
    289     None,
    290     None,
    291     None,
    292     None,
    293     Some(Alphabet::A),
    294     Some(Alphabet::B),
    295     Some(Alphabet::C),
    296     Some(Alphabet::D),
    297     Some(Alphabet::E),
    298     Some(Alphabet::F),
    299     Some(Alphabet::G),
    300     Some(Alphabet::H),
    301     Some(Alphabet::I),
    302     Some(Alphabet::J),
    303     Some(Alphabet::K),
    304     Some(Alphabet::L),
    305     Some(Alphabet::M),
    306     Some(Alphabet::N),
    307     Some(Alphabet::O),
    308     Some(Alphabet::P),
    309     Some(Alphabet::Q),
    310     Some(Alphabet::R),
    311     Some(Alphabet::S),
    312     Some(Alphabet::T),
    313     Some(Alphabet::U),
    314     Some(Alphabet::V),
    315     Some(Alphabet::W),
    316     Some(Alphabet::X),
    317     Some(Alphabet::Y),
    318     Some(Alphabet::Z),
    319     None,
    320     None,
    321     None,
    322     None,
    323     Some(Alphabet::Underscore),
    324     None,
    325     Some(Alphabet::a),
    326     Some(Alphabet::b),
    327     Some(Alphabet::c),
    328     Some(Alphabet::d),
    329     Some(Alphabet::e),
    330     Some(Alphabet::f),
    331     Some(Alphabet::g),
    332     Some(Alphabet::h),
    333     Some(Alphabet::i),
    334     Some(Alphabet::j),
    335     Some(Alphabet::k),
    336     Some(Alphabet::l),
    337     Some(Alphabet::m),
    338     Some(Alphabet::n),
    339     Some(Alphabet::o),
    340     Some(Alphabet::p),
    341     Some(Alphabet::q),
    342     Some(Alphabet::r),
    343     Some(Alphabet::s),
    344     Some(Alphabet::t),
    345     Some(Alphabet::u),
    346     Some(Alphabet::v),
    347     Some(Alphabet::w),
    348     Some(Alphabet::x),
    349     Some(Alphabet::y),
    350     Some(Alphabet::z),
    351     None,
    352     None,
    353     None,
    354     None,
    355     None,
    356     None,
    357     None,
    358     None,
    359     None,
    360     None,
    361     None,
    362     None,
    363     None,
    364     None,
    365     None,
    366     None,
    367     None,
    368     None,
    369     None,
    370     None,
    371     None,
    372     None,
    373     None,
    374     None,
    375     None,
    376     None,
    377     None,
    378     None,
    379     None,
    380     None,
    381     None,
    382     None,
    383     None,
    384     None,
    385     None,
    386     None,
    387     None,
    388     None,
    389     None,
    390     None,
    391     None,
    392     None,
    393     None,
    394     None,
    395     None,
    396     None,
    397     None,
    398     None,
    399     None,
    400     None,
    401     None,
    402     None,
    403     None,
    404     None,
    405     None,
    406     None,
    407     None,
    408     None,
    409     None,
    410     None,
    411     None,
    412     None,
    413     None,
    414     None,
    415     None,
    416     None,
    417     None,
    418     None,
    419     None,
    420     None,
    421     None,
    422     None,
    423     None,
    424     None,
    425     None,
    426     None,
    427     None,
    428     None,
    429     None,
    430     None,
    431     None,
    432     None,
    433     None,
    434     None,
    435     None,
    436     None,
    437     None,
    438     None,
    439     None,
    440     None,
    441     None,
    442     None,
    443     None,
    444     None,
    445     None,
    446     None,
    447     None,
    448     None,
    449     None,
    450     None,
    451     None,
    452     None,
    453     None,
    454     None,
    455     None,
    456     None,
    457     None,
    458     None,
    459     None,
    460     None,
    461     None,
    462     None,
    463     None,
    464     None,
    465     None,
    466     None,
    467     None,
    468     None,
    469     None,
    470     None,
    471     None,
    472     None,
    473     None,
    474     None,
    475     None,
    476     None,
    477     None,
    478     None,
    479     None,
    480     None,
    481     None,
    482     None,
    483     None,
    484 ];
    485 impl Alphabet {
    486     /// Returns `Self` that corresponds to `b`.
    487     ///
    488     /// `Some` is returned iff `b` is in `0..=63`.
    489     ///
    490     /// # Examples
    491     ///
    492     /// ```
    493     /// # use base64url_nopad::Alphabet;
    494     /// assert_eq!(Alphabet::from_u8(25), Some(Alphabet::Z));
    495     /// for i in 0..=63 {
    496     ///     assert!(Alphabet::from_u8(i).is_some());
    497     /// }
    498     /// for i in 64..=255 {
    499     ///     assert!(Alphabet::from_u8(i).is_none());
    500     /// }
    501     /// ```
    502     #[expect(unsafe_code, reason = "comment justifies correctness")]
    503     #[expect(clippy::as_conversions, reason = "comment justifies correctness")]
    504     #[inline]
    505     #[must_use]
    506     pub const fn from_u8(b: u8) -> Option<Self> {
    507         // `Self` is `repr(u8)` and all `u8`s are valid from 0 until the maximum value
    508         // represented by `Self::Underscore`.
    509         if b <= Self::Underscore as u8 {
    510             // SAFETY:
    511             // Our safety precondition is that `b` is in-range.
    512             Some(unsafe { mem::transmute::<u8, Self>(b) })
    513         } else {
    514             None
    515         }
    516     }
    517     /// Returns the `u8` `self` represents.
    518     ///
    519     /// # Examples
    520     ///
    521     /// ```
    522     /// # use base64url_nopad::Alphabet;
    523     /// assert_eq!(Alphabet::Hyphen.to_u8(), 62);
    524     /// assert_eq!(Alphabet::Eight.to_u8(), Alphabet::Eight as u8);
    525     /// ```
    526     #[expect(clippy::as_conversions, reason = "comment justifies correctness")]
    527     #[inline]
    528     #[must_use]
    529     pub const fn to_u8(self) -> u8 {
    530         // `Self` is `repr(u8)`; thus this is correct.
    531         self as u8
    532     }
    533     /// Returns the ASCII representation of `self`.
    534     ///
    535     /// # Examples
    536     ///
    537     /// ```
    538     /// # use base64url_nopad::Alphabet;
    539     /// assert_eq!(Alphabet::c.to_ascii(), b'c');
    540     /// ```
    541     #[expect(
    542         clippy::as_conversions,
    543         clippy::indexing_slicing,
    544         reason = "comments justify correctness"
    545     )]
    546     #[inline]
    547     #[must_use]
    548     pub const fn to_ascii(self) -> u8 {
    549         // `u8 as usize` is always OK; and we want this to be `const` so can't rely on `usize::from`.
    550         // `self.to_u8() < 64` and `ASCII.len() == 64`, so indexing can't `panic`.
    551         ASCII[self.to_u8() as usize]
    552     }
    553     /// Returns `Some` iff `ascii` is the ASCII representation of `Self`.
    554     ///
    555     /// # Examples
    556     ///
    557     /// ```
    558     /// # use base64url_nopad::Alphabet;
    559     /// for i in 0u8..=255 {
    560     ///     if i.is_ascii_alphanumeric() || i == b'-' || i == b'_' {
    561     ///         assert!(Alphabet::from_ascii(i).is_some());
    562     ///     } else {
    563     ///         assert!(Alphabet::from_ascii(i).is_none());
    564     ///     }
    565     /// }
    566     /// ```
    567     #[expect(
    568         clippy::as_conversions,
    569         clippy::indexing_slicing,
    570         reason = "comments justify correctness"
    571     )]
    572     #[inline]
    573     #[must_use]
    574     pub const fn from_ascii(ascii: u8) -> Option<Self> {
    575         // `u8 as usize` is always OK; and we want this to be `const` so can't rely on `usize::from`.
    576         // `FROM_ASCII` has length 256, so indexing can't `panic`.
    577         FROM_ASCII[ascii as usize]
    578     }
    579     /// Same as [`Self::to_ascii`] except a `char` is returned.
    580     ///
    581     /// # Examples
    582     ///
    583     /// ```
    584     /// # use base64url_nopad::Alphabet;
    585     /// assert_eq!(Alphabet::J.to_char(), 'J');
    586     /// ```
    587     #[expect(
    588         clippy::as_conversions,
    589         clippy::indexing_slicing,
    590         reason = "comments justify correctness"
    591     )]
    592     #[inline]
    593     #[must_use]
    594     pub const fn to_char(self) -> char {
    595         // `u8 as usize` is always OK; and we want this to be `const` so can't rely on `usize::from`.
    596         // `self.to_u8() < 64` and `CHARS.len() == 64`, so indexing can't `panic`.
    597         CHARS[self.to_u8() as usize]
    598     }
    599     /// Same as [`Self::from_ascii`] except the input is a `char`.
    600     ///
    601     /// # Examples
    602     ///
    603     /// ```
    604     /// # use base64url_nopad::Alphabet;
    605     /// for i in char::MIN..=char::MAX {
    606     ///     if i.is_ascii_alphanumeric() || i == '-' || i == '_' {
    607     ///         assert!(Alphabet::from_char(i).is_some());
    608     ///     } else {
    609     ///         assert!(Alphabet::from_char(i).is_none());
    610     ///     }
    611     /// }
    612     /// ```
    613     #[expect(
    614         clippy::as_conversions,
    615         clippy::cast_possible_truncation,
    616         reason = "comments justify correctness"
    617     )]
    618     #[inline]
    619     #[must_use]
    620     pub const fn from_char(c: char) -> Option<Self> {
    621         // `char as u32` is always OK.
    622         let code_point = c as u32;
    623         if code_point < 256 {
    624             // We just verified `code_point` does not exceed `u8::MAX`, so `code_point as u8` is lossless.
    625             Self::from_ascii(code_point as u8)
    626         } else {
    627             None
    628         }
    629     }
    630 }
    631 impl Display for Alphabet {
    632     #[inline]
    633     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    634         f.write_char(self.to_char())
    635     }
    636 }
    637 impl From<Alphabet> for u8 {
    638     #[inline]
    639     fn from(value: Alphabet) -> Self {
    640         value.to_u8()
    641     }
    642 }
    643 impl From<Alphabet> for u16 {
    644     #[inline]
    645     fn from(value: Alphabet) -> Self {
    646         Self::from(value.to_u8())
    647     }
    648 }
    649 impl From<Alphabet> for u32 {
    650     #[inline]
    651     fn from(value: Alphabet) -> Self {
    652         Self::from(value.to_u8())
    653     }
    654 }
    655 impl From<Alphabet> for u64 {
    656     #[inline]
    657     fn from(value: Alphabet) -> Self {
    658         Self::from(value.to_u8())
    659     }
    660 }
    661 impl From<Alphabet> for u128 {
    662     #[inline]
    663     fn from(value: Alphabet) -> Self {
    664         Self::from(value.to_u8())
    665     }
    666 }
    667 impl From<Alphabet> for char {
    668     #[inline]
    669     fn from(value: Alphabet) -> Self {
    670         value.to_char()
    671     }
    672 }
    673 /// The maximum value [`encode_len_checked`] will accept before returning `None`.
    674 // This won't `panic` since `usize::MAX` ≢ 1 (mod 4).
    675 pub const MAX_ENCODE_INPUT_LEN: usize = decode_len(usize::MAX).unwrap();
    676 /// Returns the exact number of bytes needed to encode an input of length `input_length`.
    677 ///
    678 /// `Some` is returned iff the length needed does not exceed [`usize::MAX`].
    679 ///
    680 /// Note since Rust guarantees all memory allocations don't exceed [`isize::MAX`] bytes, then one can
    681 /// instead call [`encode_len`] when the argument passed corresponds to the length of an allocation since
    682 /// `isize::MAX <` [`MAX_ENCODE_INPUT_LEN`].
    683 ///
    684 /// # Examples
    685 ///
    686 /// ```
    687 /// # use base64url_nopad::MAX_ENCODE_INPUT_LEN;
    688 /// assert!(base64url_nopad::encode_len_checked(usize::MAX).is_none());
    689 /// assert!(base64url_nopad::encode_len_checked(MAX_ENCODE_INPUT_LEN + 1).is_none());
    690 /// assert_eq!(base64url_nopad::encode_len_checked(MAX_ENCODE_INPUT_LEN), Some(usize::MAX));
    691 /// assert_eq!(base64url_nopad::encode_len_checked(3), Some(4));
    692 /// assert_eq!(base64url_nopad::encode_len_checked(2), Some(3));
    693 /// assert_eq!(base64url_nopad::encode_len_checked(1), Some(2));
    694 /// assert_eq!(base64url_nopad::encode_len_checked(0), Some(0));
    695 /// ```
    696 #[expect(
    697     clippy::arithmetic_side_effects,
    698     clippy::integer_division,
    699     clippy::integer_division_remainder_used,
    700     reason = "proof and comment justifies their correctness"
    701 )]
    702 #[inline]
    703 #[must_use]
    704 pub const fn encode_len_checked(input_length: usize) -> Option<usize> {
    705     // 256^n is the number of distinct values of the input. Let the base64 encoding in a URL safe
    706     // way without padding of the input be O. There are 64 possible values each byte in O can be; thus we must find
    707     // the minimum nonnegative integer m such that:
    708     // 64^m = (2^6)^m = 2^(6m) >= 256^n = (2^8)^n = 2^(8n)
    709     // <==>
    710     // lg(2^(6m)) = 6m >= lg(2^(8n)) = 8n   lg is defined on all positive reals which 2^(6m) and 2^(8n) are
    711     // <==>
    712     // m >= 8n/6 = 4n/3
    713     // Clearly that corresponds to m = ⌈4n/3⌉.
    714     // We claim ⌈4n/3⌉ = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉.
    715     // Proof:
    716     // There are three partitions for n:
    717     // (1) 3i = n ≡ 0 (mod 3) for some integer i
    718     //   <==>
    719     //   ⌈4n/3⌉ = ⌈4(3i)/3⌉ = ⌈4i⌉ = 4i = 4⌊i⌋ = 4⌊3i/3⌋ = 4⌊n/3⌋ + 0 = 4⌊n/3⌋ + ⌈4(0)/3⌉ = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉
    720     // (2) 3i + 1 = n ≡ 1 (mod 3) for some integer i
    721     //   <==>
    722     //   ⌈4n/3⌉ = ⌈4(3i + 1)/3⌉ = ⌈4i + 4/3⌉ = 4i + ⌈4/3⌉ = 4i + 2 = 4⌊i + 1/3⌋ + ⌈4(1)/3⌉
    723     //                                                             = 4⌊(3i + 1)/3⌋ + ⌈4((3i + 1) mod 3)/3⌉
    724     //                                                             = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉
    725     // (3) 3i + 2 = n ≡ 2 (mod 3) for some integer i
    726     //   <==>
    727     //   ⌈4n/3⌉ = ⌈4(3i + 2)/3⌉ = ⌈4i + 8/3⌉ = 4i + ⌈8/3⌉ = 4i + 3 = 4⌊i + 2/3⌋ + ⌈4(2)/3⌉
    728     //                                                             = 4⌊(3i + 2)/3⌋ + ⌈4((3i + 2) mod 3)/3⌉
    729     //                                                             = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉
    730     // QED
    731     // Proof of no overflow:
    732     // `MAX_ENCODE_INPUT_LEN` = decode_len(usize::MAX).unwrap(); thus all values less than or equal to
    733     // `MAX_ENCODE_INPUT_LEN` won't overflow ignoring intermediate calcuations since ⌈4n/3⌉ is a
    734     // monotonically increasing function.
    735     // QED
    736     // Naively implementing ⌈4n/3⌉ as (4 * n).div_ceil(3) can cause overflow due to `4 * n`; thus
    737     // we implement the equivalent equation 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ instead:
    738     // `(4 * (n / 3)) + (4 * (n % 3)).div_ceil(3)` since none of the intermediate calculations suffer
    739     // from overflow.
    740     if input_length <= MAX_ENCODE_INPUT_LEN {
    741         // (n / 3) << 2 <= m <= usize::MAX; thus the left operand of + is fine.
    742         // n % 3 <= 2
    743         // <==>
    744         // 4(n % 3) <= 8 < usize::MAX; thus (n % 3) << 2 is fine.
    745         // <==>
    746         // ⌈4(n % 3)/3⌉ <= 4(n % 3), so the right operand of + is fine.
    747         // The sum is fine since
    748         // m = ⌈4n/3⌉ = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ = ((n / 3) << 2) + ((n % 3) << 2).div_ceil(3), and m <= usize::MAX.
    749         Some(((input_length / 3) << 2) + ((input_length % 3) << 2).div_ceil(3))
    750     } else {
    751         cold_path();
    752         None
    753     }
    754 }
    755 /// Same as [`encode_len_checked`] except a `panic` occurs instead of `None` being returned.
    756 ///
    757 /// One should prefer this function over `encode_len_checked` when passing the length of a memory allocation
    758 /// since such a length is guaranteed to succeed.
    759 ///
    760 /// # Panics
    761 ///
    762 /// `panic`s iff [`encode_len_checked`] returns `None`.
    763 ///
    764 /// # Examples
    765 ///
    766 /// ```
    767 /// # use base64url_nopad::MAX_ENCODE_INPUT_LEN;
    768 /// // Uncommenting below will cause a `panic`.
    769 /// // base64url_nopad::encode_len(usize::MAX - 4);
    770 /// // Uncommenting below will cause a `panic`.
    771 /// // base64url_nopad::encode_len(MAX_ENCODE_INPUT_LEN + 1);
    772 /// assert_eq!(base64url_nopad::encode_len(MAX_ENCODE_INPUT_LEN), usize::MAX);
    773 /// assert!(base64url_nopad::encode_len(isize::MAX as usize) > isize::MAX as usize);
    774 /// assert_eq!(base64url_nopad::encode_len(3), 4);
    775 /// assert_eq!(base64url_nopad::encode_len(2), 3);
    776 /// assert_eq!(base64url_nopad::encode_len(1), 2);
    777 /// assert_eq!(base64url_nopad::encode_len(0), 0);
    778 /// ```
    779 #[expect(clippy::unwrap_used, reason = "comment justifies correctness")]
    780 #[inline]
    781 #[must_use]
    782 pub const fn encode_len(input_length: usize) -> usize {
    783     // A precondition for calling this function is to ensure `encode_len_checked` can't return `None`.
    784     encode_len_checked(input_length).unwrap()
    785 }
    786 /// Encodes `input` into `output` re-interpreting the encoded subset of `output` as a `str` before returning it.
    787 ///
    788 /// `Some` is returned iff `output.len()` is large enough to write the encoded data into.
    789 ///
    790 /// Note since Rust guarantees all memory allocations don't exceed [`isize::MAX`] bytes, one can
    791 /// instead call [`encode_buffer`] using a buffer whose length is at least as large as the value returned from
    792 /// [`encode_len`] without fear of a `panic` and the benefit of getting a `str` instead of an `Option`.
    793 ///
    794 /// # Examples
    795 ///
    796 /// ```
    797 /// assert_eq!(
    798 ///     base64url_nopad::encode_buffer_checked([0; 0].as_slice(), [0; 0].as_mut_slice()).as_deref(), Some("")
    799 /// );
    800 /// assert_eq!(
    801 ///     base64url_nopad::encode_buffer_checked([0; 1].as_slice(), [0; 2].as_mut_slice()).as_deref(), Some("AA")
    802 /// );
    803 /// // A larger output buffer than necessary is OK.
    804 /// assert_eq!(
    805 ///     base64url_nopad::encode_buffer_checked([1; 1].as_slice(), [0; 128].as_mut_slice()).as_deref(), Some("AQ")
    806 /// );
    807 /// assert_eq!(
    808 ///     base64url_nopad::encode_buffer_checked(
    809 ///         [0xc9; 14].as_slice(),
    810 ///         [0; base64url_nopad::encode_len(14)].as_mut_slice()
    811 ///     ).as_deref(),
    812 ///     Some("ycnJycnJycnJycnJyck")
    813 /// );
    814 /// assert!(base64url_nopad::encode_buffer_checked([0; 1].as_slice(), [0; 1].as_mut_slice()).is_none());
    815 /// ```
    816 #[expect(unsafe_code, reason = "comments justify correctness")]
    817 #[expect(
    818     clippy::missing_panics_doc,
    819     clippy::panic,
    820     reason = "false positive in that the panic is impossible modulo bugs"
    821 )]
    822 #[expect(
    823     clippy::missing_asserts_for_indexing,
    824     reason = "trust the compiler to already optimize since we match on the length"
    825 )]
    826 #[expect(
    827     clippy::arithmetic_side_effects,
    828     clippy::as_conversions,
    829     clippy::indexing_slicing,
    830     reason = "comments justify correctness"
    831 )]
    832 #[inline]
    833 pub const fn encode_buffer_checked<'a>(input: &[u8], output: &'a mut [u8]) -> Option<&'a mut str> {
    834     // This won't `panic` since Rust guarantees that all memory allocations won't exceed `isize::MAX`.
    835     let final_len = encode_len(input.len());
    836     if output.len() >= final_len {
    837         let (mut chunks, rem) = input.as_chunks::<3>();
    838         let (mut fst, mut snd, mut third);
    839         let mut output_idx = 0;
    840         // There is a _substantial_ boost in performance if we chunk encode.
    841         while let [first, ref rest @ ..] = *chunks {
    842             (fst, snd, third) = (first[0], first[1], first[2]);
    843             // We trim the last two bits and interpret `fst` as a 6-bit integer.
    844             // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    845             // `ASCII.len() == 64 > fst >> 2`, so indexing won't `panic`.
    846             output[output_idx] = ASCII[(fst >> 2) as usize];
    847             // The two bits we trimmed are the first two bits of the next 6-bit integer.
    848             output_idx += 1;
    849             // We trim the last four bits and interpret `snd` as a 6-bit integer.
    850             // The first two bits are the trailing 2 bits from the previous value.
    851             // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    852             // `ASCII.len() == 64 > ((fst & 3) << 4) | (snd >> 4)`, so indexing won't `panic`.
    853             output[output_idx] = ASCII[(((fst & 3) << 4) | (snd >> 4)) as usize];
    854             output_idx += 1;
    855             // We trim the last six bits and interpret `third` as a 6-bit integer.
    856             // The first four bits are the trailing 4 bits from the previous value.
    857             // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    858             // `ASCII.len() == 64 > ((snd & 15) << 2) | (third >> 6)`, so indexing won't `panic`.
    859             output[output_idx] = ASCII[(((snd & 15) << 2) | (third >> 6)) as usize];
    860             // Every third `u8` corresponds to a fourth base64url `u8`.
    861             output_idx += 1;
    862             // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    863             // `ASCII.len() == 64 > (third & 63)`, so indexing won't `panic`.
    864             output[output_idx] = ASCII[(third & 63) as usize];
    865             output_idx += 1;
    866             chunks = rest;
    867         }
    868         match rem.len() {
    869             0 => {}
    870             1 => {
    871                 // `rem.len() == 1`, so indexing won't `panic`.
    872                 fst = rem[0];
    873                 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    874                 // `ASCII.len() == 64 > fst >> 2`, so indexing won't `panic`.
    875                 output[output_idx] = ASCII[(fst >> 2) as usize];
    876                 // `ASCII.len() == 64 > (fst & 3) << 4`, so indexing won't `panic`.
    877                 output[output_idx + 1] = ASCII[((fst & 3) << 4) as usize];
    878             }
    879             2 => {
    880                 // `rem.len() == 2`, so indexing won't `panic`.
    881                 (fst, snd) = (rem[0], rem[1]);
    882                 // `input.len()` is not a multiple of 3; thus we have to append a final `u8` containing the
    883                 // last bits.
    884                 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    885                 // `ASCII.len() == 64 > fst >> 2`, so indexing won't `panic`.
    886                 output[output_idx] = ASCII[(fst >> 2) as usize];
    887                 // `ASCII.len() == 64 > ((fst & 3) << 4) | (snd >> 4)`, so indexing won't `panic`.
    888                 output[output_idx + 1] = ASCII[(((fst & 3) << 4) | (snd >> 4)) as usize];
    889                 // `ASCII.len() == 64 > (snd & 15) << 2`, so indexing won't `panic`.
    890                 output[output_idx + 2] = ASCII[((snd & 15) << 2) as usize];
    891             }
    892             _ => {
    893                 cold_path();
    894                 panic!("there is a bug in core::slice::as_chunks");
    895             }
    896         }
    897         // SAFETY:
    898         // We verified `output.len() >= final_len`.
    899         let val = unsafe { output.split_at_mut_unchecked(final_len) }.0;
    900         // SAFETY:
    901         // `val` has the exact length needed to encode `input`, and all of the `u8`s in it
    902         // are from `Alphabet::to_ascii` which is a subset of UTF-8; thus this is safe.
    903         // Note the above is vacuously true when `val` is empty.
    904         Some(unsafe { str::from_utf8_unchecked_mut(val) })
    905     } else {
    906         cold_path();
    907         None
    908     }
    909 }
    910 /// Same as [`encode_buffer_checked`] except a `panic` occurs instead of `None` being returned.
    911 ///
    912 /// # Panics
    913 ///
    914 /// `panic`s iff [`encode_buffer_checked`] returns `None` (i.e., the length of the output buffer is too small).
    915 ///
    916 /// # Examples
    917 ///
    918 /// ```
    919 /// assert_eq!(
    920 ///     base64url_nopad::encode_buffer([0; 0].as_slice(), [0; 0].as_mut_slice()),
    921 ///     ""
    922 /// );
    923 /// assert_eq!(
    924 ///     base64url_nopad::encode_buffer([0; 1].as_slice(), [0; 2].as_mut_slice()),
    925 ///     "AA"
    926 /// );
    927 /// // A larger output buffer than necessary is OK.
    928 /// assert_eq!(
    929 ///     base64url_nopad::encode_buffer([255; 1].as_slice(), [0; 256].as_mut_slice()),
    930 ///     "_w"
    931 /// );
    932 /// assert_eq!(
    933 ///     base64url_nopad::encode_buffer(
    934 ///         [0xc9; 14].as_slice(),
    935 ///         [0; base64url_nopad::encode_len(14)].as_mut_slice()
    936 ///     ),
    937 ///     "ycnJycnJycnJycnJyck"
    938 /// );
    939 /// // The below will `panic` when uncommented since the supplied output buffer is too small.
    940 /// // _ = base64url_nopad::encode_buffer([0; 1].as_slice(), [0; 1].as_mut_slice());
    941 /// ```
    942 #[expect(clippy::unwrap_used, reason = "comment justifies correctness")]
    943 #[inline]
    944 pub const fn encode_buffer<'a>(input: &[u8], output: &'a mut [u8]) -> &'a mut str {
    945     // A precondition for calling this function is to ensure `encode_buffer_checked` can't return `None`.
    946     encode_buffer_checked(input, output).unwrap()
    947 }
    948 /// Similar to [`encode_buffer`] except a `String` is returned instead using its buffer to write to.
    949 ///
    950 /// # Errors
    951 ///
    952 /// Errors iff an error occurs from allocating the capacity needed to contain the encoded data.
    953 ///
    954 /// # Examples
    955 ///
    956 /// ```
    957 /// # extern crate alloc;
    958 /// # use alloc::collections::TryReserveError;
    959 /// assert_eq!(
    960 ///     base64url_nopad::try_encode([0; 0].as_slice())?,
    961 ///     ""
    962 /// );
    963 /// assert_eq!(
    964 ///     base64url_nopad::try_encode([0; 1].as_slice())?,
    965 ///     "AA"
    966 /// );
    967 /// assert_eq!(
    968 ///     base64url_nopad::try_encode([128, 40, 3].as_slice())?,
    969 ///     "gCgD"
    970 /// );
    971 /// assert_eq!(
    972 ///     base64url_nopad::try_encode([0x7b; 22].as_slice())?,
    973 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"
    974 /// );
    975 /// # Ok::<_, TryReserveError>(())
    976 /// ```
    977 #[cfg(feature = "alloc")]
    978 #[expect(unsafe_code, reason = "comment justifies correctness")]
    979 #[inline]
    980 pub fn try_encode(input: &[u8]) -> Result<String, TryReserveError> {
    981     let mut output = Vec::new();
    982     // `encode_len` won't `panic` since Rust guarantees `input.len()` will not return a value greater
    983     // than `isize::MAX`.
    984     let len = encode_len(input.len());
    985     output.try_reserve_exact(len).map(|()| {
    986         output.resize(len, 0);
    987         _ = encode_buffer(input, output.as_mut_slice());
    988         // SAFETY:
    989         // `output` has the exact length needed to encode `input`, and all of the `u8`s in it
    990         // are from `Alphabet` which is a subset of UTF-8; thus this is safe.
    991         // Note the above is vacuously true when `output` is empty.
    992         unsafe { String::from_utf8_unchecked(output) }
    993     })
    994 }
    995 /// Same as [`try_encode`] except a `panic` occurs on allocation failure.
    996 ///
    997 /// # Panics
    998 ///
    999 /// `panic`s iff [`try_encode`] errors.
   1000 ///
   1001 /// # Examples
   1002 ///
   1003 /// ```
   1004 /// assert_eq!(
   1005 ///     base64url_nopad::encode([0; 0].as_slice()),
   1006 ///     ""
   1007 /// );
   1008 /// assert_eq!(
   1009 ///     base64url_nopad::encode([0; 1].as_slice()),
   1010 ///     "AA"
   1011 /// );
   1012 /// assert_eq!(
   1013 ///     base64url_nopad::encode([128, 40, 3].as_slice()),
   1014 ///     "gCgD"
   1015 /// );
   1016 /// assert_eq!(
   1017 ///     base64url_nopad::encode([0x7b; 22].as_slice()),
   1018 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"
   1019 /// );
   1020 /// ```
   1021 #[cfg(feature = "alloc")]
   1022 #[expect(
   1023     clippy::unwrap_used,
   1024     reason = "purpose of function is to panic on allocation failure"
   1025 )]
   1026 #[inline]
   1027 #[must_use]
   1028 pub fn encode(input: &[u8]) -> String {
   1029     try_encode(input).unwrap()
   1030 }
   1031 /// Writes the base64url encoding of `input` using `writer`.
   1032 ///
   1033 /// Internally a buffer of at most 1024 bytes is used to write the encoded data. Smaller buffers may be used
   1034 /// for small inputs.
   1035 ///
   1036 /// # Errors
   1037 ///
   1038 /// Errors iff [`Write::write_str`] does.
   1039 ///
   1040 /// # Panics
   1041 ///
   1042 /// `panic`s iff [`Write::write_str`] does.
   1043 ///
   1044 /// # Examples
   1045 ///
   1046 /// ```
   1047 /// # extern crate alloc;
   1048 /// # use alloc::string::String;
   1049 /// # use core::fmt::Error;
   1050 /// let mut buffer = String::new();
   1051 /// base64url_nopad::encode_write([0; 0].as_slice(), &mut buffer)?;
   1052 /// assert_eq!(buffer, "");
   1053 /// buffer.clear();
   1054 /// base64url_nopad::encode_write([0; 1].as_slice(), &mut buffer)?;
   1055 /// assert_eq!(buffer, "AA");
   1056 /// buffer.clear();
   1057 /// base64url_nopad::encode_write(
   1058 ///     [0xc9; 14].as_slice(),
   1059 ///     &mut buffer,
   1060 /// )?;
   1061 /// assert_eq!(buffer, "ycnJycnJycnJycnJyck");
   1062 /// # Ok::<_, Error>(())
   1063 /// ```
   1064 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1065 #[expect(
   1066     clippy::arithmetic_side_effects,
   1067     clippy::as_conversions,
   1068     clippy::indexing_slicing,
   1069     reason = "comments justify correctness"
   1070 )]
   1071 #[inline]
   1072 pub fn encode_write<W: Write>(mut input: &[u8], writer: &mut W) -> fmt::Result {
   1073     /// The minimum buffer size we use.
   1074     const MIN_BUFFER_LEN: usize = 256;
   1075     /// The medium buffer size we use.
   1076     const MID_BUFFER_LEN: usize = MIN_BUFFER_LEN << 1;
   1077     /// The max buffer size.
   1078     ///
   1079     /// This must be at least 4, no more than `i16::MAX`, and must be a power of 2.
   1080     const MAX_BUFFER_LEN: usize = MID_BUFFER_LEN << 1;
   1081     /// The minimum length of the input until we must chunk encode the data.
   1082     const LOOP_LEN: usize = MAX_BUFFER_LEN + 1;
   1083     /// Want to ensure at compilation time that `MAX_BUFFER_LEN` upholds its invariants. Namely
   1084     /// that it's at least as large as 4, doesn't exceed [`i16::MAX`], and is always a power of 2.
   1085     const _: () = {
   1086         // `i16::MAX <= usize::MAX`, so this is fine.
   1087         /// `i16::MAX`.
   1088         const MAX_LEN: usize = i16::MAX as usize;
   1089         assert!(
   1090             4 <= MAX_BUFFER_LEN && MAX_BUFFER_LEN < MAX_LEN && MAX_BUFFER_LEN.is_power_of_two(),
   1091             "encode_write::MAX_BUFFER_LEN must be a power of two less than i16::MAX but at least as large as 4"
   1092         );
   1093     };
   1094     /// The input size that corresponds to an encoded value of length `MAX_BUFFER_LEN`.
   1095     // This will never `panic` since `MAX_BUFFER_LEN` is a power of two at least as large as 4
   1096     // (i.e., `MAX_BUFFER_LEN` ≢ 1 (mod 4)).
   1097     const INPUT_LEN: usize = decode_len(MAX_BUFFER_LEN).unwrap();
   1098     // This won't `panic` since `input.len()` is guaranteed to be no more than `isize::MAX`.
   1099     let len = encode_len(input.len());
   1100     match len {
   1101         0 => Ok(()),
   1102         1..=MIN_BUFFER_LEN => {
   1103             let mut buffer = [0; MIN_BUFFER_LEN];
   1104             // `buffer.len() == MIN_BUFFER_LEN >= len`, so indexing is fine.
   1105             // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode
   1106             // the data.
   1107             writer.write_str(encode_buffer(input, &mut buffer[..len]))
   1108         }
   1109         257..=MID_BUFFER_LEN => {
   1110             let mut buffer = [0; MID_BUFFER_LEN];
   1111             // `buffer.len() == MID_BUFFER_LEN >= len`, so indexing is fine.
   1112             // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode
   1113             // the data.
   1114             writer.write_str(encode_buffer(input, &mut buffer[..len]))
   1115         }
   1116         513..=MAX_BUFFER_LEN => {
   1117             let mut buffer = [0; MAX_BUFFER_LEN];
   1118             // `buffer.len() == MAX_BUFFER_LEN >= len`, so indexing is fine.
   1119             // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode
   1120             // the data.
   1121             writer.write_str(encode_buffer(input, &mut buffer[..len]))
   1122         }
   1123         LOOP_LEN.. => {
   1124             let mut buffer = [0; MAX_BUFFER_LEN];
   1125             let mut counter = 0;
   1126             // `len / MAX_BUFFER_LEN` is equal to ⌊len / MAX_BUFFER_LEN⌋ since `MAX_BUFFER_LEN` is a power of two.
   1127             // We can safely encode `term` chunks of `INPUT_LEN` length into `buffer`.
   1128             let term = len >> MAX_BUFFER_LEN.trailing_zeros();
   1129             let mut input_buffer;
   1130             while counter < term {
   1131                 // SAFETY:
   1132                 // `input.len() >= INPUT_LEN`.
   1133                 input_buffer = unsafe { input.split_at_unchecked(INPUT_LEN) };
   1134                 // `encode_buffer` won't `panic` since `buffer` has length `MAX_BUFFER_LEN` which
   1135                 // is the exact length needed for `INPUT_LEN` length inputs which `input_buffer.0` is.
   1136                 writer.write_str(encode_buffer(input_buffer.0, buffer.as_mut_slice()))?;
   1137                 input = input_buffer.1;
   1138                 // `counter < term`, so overflow cannot happen.
   1139                 counter += 1;
   1140             }
   1141             // `encode_len` won't `panic` since `input.len() < MAX_ENCODE_INPUT_LEN`.
   1142             // `input.len() < INPUT_LEN`; thus `encode_len(input.len()) < MAX_BUFFER_LEN = buffer.len()` so
   1143             // indexing is fine.
   1144             // `encode_buffer` won't `panic` since the buffer is the exact length needed to encode `input`.
   1145             writer.write_str(encode_buffer(input, &mut buffer[..encode_len(input.len())]))
   1146         }
   1147     }
   1148 }
   1149 /// Appends the base64url encoding of `input` to `s` returning the `str` that was appended.
   1150 ///
   1151 /// # Errors
   1152 ///
   1153 /// Errors iff an error occurs from allocating the capacity needed to append the encoded data.
   1154 ///
   1155 /// # Examples
   1156 ///
   1157 /// ```
   1158 /// # extern crate alloc;
   1159 /// # use alloc::{collections::TryReserveError, string::String};
   1160 /// let mut buffer = String::new();
   1161 /// assert_eq!(
   1162 ///     base64url_nopad::try_encode_append([0; 0].as_slice(), &mut buffer)?,
   1163 ///     ""
   1164 /// );
   1165 /// assert_eq!(
   1166 ///     base64url_nopad::try_encode_append([0; 1].as_slice(), &mut buffer)?,
   1167 ///     "AA"
   1168 /// );
   1169 /// assert_eq!(
   1170 ///     base64url_nopad::try_encode_append([128, 40, 3].as_slice(), &mut buffer)?,
   1171 ///     "gCgD"
   1172 /// );
   1173 /// assert_eq!(buffer, "AAgCgD");
   1174 /// assert_eq!(
   1175 ///     base64url_nopad::try_encode_append([0x7b; 22].as_slice(), &mut buffer)?,
   1176 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"
   1177 /// );
   1178 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew");
   1179 /// # Ok::<_, TryReserveError>(())
   1180 /// ```
   1181 #[cfg(feature = "alloc")]
   1182 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1183 #[expect(
   1184     clippy::arithmetic_side_effects,
   1185     clippy::indexing_slicing,
   1186     reason = "comments justify correctness"
   1187 )]
   1188 #[inline]
   1189 pub fn try_encode_append<'a>(
   1190     input: &[u8],
   1191     s: &'a mut String,
   1192 ) -> Result<&'a mut str, TryReserveError> {
   1193     // `encode_len` won't `panic` since Rust guarantees `input.len()` will return a value no larger
   1194     // than `isize::MAX`.
   1195     let additional_len = encode_len(input.len());
   1196     s.try_reserve_exact(additional_len).map(|()| {
   1197         // SAFETY:
   1198         // We only append base64url ASCII which is a subset of UTF-8, so this will remain valid UTF-8.
   1199         let utf8 = unsafe { s.as_mut_vec() };
   1200         let original_len = utf8.len();
   1201         // Overflow can't happen; otherwise `s.try_reserve_exact` would have erred.
   1202         utf8.resize(original_len + additional_len, 0);
   1203         // `utf8.len() >= original_len`, so indexing is fine.
   1204         // `encode_buffer` won't `panic` since `utf8[original_len..]` has length `additional_len`
   1205         // which is the exact number of bytes needed to encode `input`.
   1206         encode_buffer(input, &mut utf8[original_len..])
   1207     })
   1208 }
   1209 /// Same as [`try_encode_append`] except the encoded `str` is not returned.
   1210 ///
   1211 /// # Errors
   1212 ///
   1213 /// Errors iff [`try_encode_append`] does.
   1214 ///
   1215 /// # Examples
   1216 ///
   1217 /// ```
   1218 /// # extern crate alloc;
   1219 /// # use alloc::{collections::TryReserveError, string::String};
   1220 /// let mut buffer = String::new();
   1221 /// base64url_nopad::try_encode_append_only([0; 0].as_slice(), &mut buffer)?;
   1222 /// assert_eq!(buffer, "");
   1223 /// base64url_nopad::try_encode_append_only([0; 1].as_slice(), &mut buffer)?;
   1224 /// assert_eq!(buffer, "AA");
   1225 /// base64url_nopad::try_encode_append_only([128, 40, 3].as_slice(), &mut buffer)?;
   1226 /// assert_eq!(buffer, "AAgCgD");
   1227 /// base64url_nopad::try_encode_append_only([0x7b; 22].as_slice(), &mut buffer)?;
   1228 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew");
   1229 /// # Ok::<_, TryReserveError>(())
   1230 /// ```
   1231 #[cfg(feature = "alloc")]
   1232 #[inline]
   1233 pub fn try_encode_append_only(input: &[u8], s: &mut String) -> Result<(), TryReserveError> {
   1234     try_encode_append(input, s).map(|_| ())
   1235 }
   1236 /// Same as [`try_encode_append`] except a `panic` occurs on allocation failure.
   1237 ///
   1238 /// # Panics
   1239 ///
   1240 /// `panic`s iff [`try_encode_append`] errors.
   1241 ///
   1242 /// # Examples
   1243 ///
   1244 /// ```
   1245 /// # extern crate alloc;
   1246 /// # use alloc::{collections::TryReserveError, string::String};
   1247 /// let mut buffer = String::new();
   1248 /// assert_eq!(
   1249 ///     base64url_nopad::encode_append([0; 0].as_slice(), &mut buffer),
   1250 ///     ""
   1251 /// );
   1252 /// assert_eq!(
   1253 ///     base64url_nopad::encode_append([0; 1].as_slice(), &mut buffer),
   1254 ///     "AA"
   1255 /// );
   1256 /// assert_eq!(
   1257 ///     base64url_nopad::encode_append([128, 40, 3].as_slice(), &mut buffer),
   1258 ///     "gCgD"
   1259 /// );
   1260 /// assert_eq!(buffer, "AAgCgD");
   1261 /// assert_eq!(
   1262 ///     base64url_nopad::encode_append([0x7b; 22].as_slice(), &mut buffer),
   1263 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"
   1264 /// );
   1265 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew");
   1266 /// ```
   1267 #[cfg(feature = "alloc")]
   1268 #[expect(
   1269     clippy::unwrap_used,
   1270     reason = "purpose of this function is to panic on allocation failure"
   1271 )]
   1272 #[inline]
   1273 pub fn encode_append<'a>(input: &[u8], s: &'a mut String) -> &'a mut str {
   1274     try_encode_append(input, s).unwrap()
   1275 }
   1276 /// Same as [`encode_append`] except the encoded `str` is not returned.
   1277 ///
   1278 /// # Panics
   1279 ///
   1280 /// `panic`s iff [`encode_append`] does.
   1281 ///
   1282 /// # Examples
   1283 ///
   1284 /// ```
   1285 /// # extern crate alloc;
   1286 /// # use alloc::{collections::TryReserveError, string::String};
   1287 /// let mut buffer = String::new();
   1288 /// base64url_nopad::encode_append_only([0; 0].as_slice(), &mut buffer);
   1289 /// assert_eq!(buffer, "");
   1290 /// base64url_nopad::encode_append_only([0; 1].as_slice(), &mut buffer);
   1291 /// assert_eq!(buffer, "AA");
   1292 /// base64url_nopad::encode_append_only([128, 40, 3].as_slice(), &mut buffer);
   1293 /// assert_eq!(buffer, "AAgCgD");
   1294 /// base64url_nopad::encode_append_only([0x7b; 22].as_slice(), &mut buffer);
   1295 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew");
   1296 /// # Ok::<_, TryReserveError>(())
   1297 /// ```
   1298 #[cfg(feature = "alloc")]
   1299 #[inline]
   1300 pub fn encode_append_only(input: &[u8], s: &mut String) {
   1301     _ = encode_append(input, s);
   1302 }
   1303 /// Returns the exact number of bytes needed to decode a base64url without padding input of length `input_length`.
   1304 ///
   1305 /// `Some` is returned iff `input_length` represents a possible length of a base64url without padding input.
   1306 ///
   1307 /// # Examples
   1308 ///
   1309 /// ```
   1310 /// # use base64url_nopad::MAX_ENCODE_INPUT_LEN;
   1311 /// assert!(base64url_nopad::decode_len(1).is_none());
   1312 /// assert_eq!(base64url_nopad::decode_len(usize::MAX), Some(MAX_ENCODE_INPUT_LEN));
   1313 /// assert_eq!(base64url_nopad::decode_len(4), Some(3));
   1314 /// assert_eq!(base64url_nopad::decode_len(3), Some(2));
   1315 /// assert_eq!(base64url_nopad::decode_len(2), Some(1));
   1316 /// assert_eq!(base64url_nopad::decode_len(0), Some(0));
   1317 /// ```
   1318 #[expect(
   1319     clippy::arithmetic_side_effects,
   1320     reason = "proof and comment justifies their correctness"
   1321 )]
   1322 #[inline]
   1323 #[must_use]
   1324 pub const fn decode_len(input_length: usize) -> Option<usize> {
   1325     // 64^n is the number of distinct values of the input. Let the decoded output be O.
   1326     // There are 256 possible values each byte in O can be; thus we must find
   1327     // the maximum nonnegative integer m such that:
   1328     // 256^m = (2^8)^m = 2^(8m) <= 64^n = (2^6)^n = 2^(6n)
   1329     // <==>
   1330     // lg(2^(8m)) = 8m <= lg(2^(6n)) = 6n   lg is defined on all positive reals which 2^(8m) and 2^(6n) are
   1331     // <==>
   1332     // m <= 6n/8 = 3n/4
   1333     // Clearly that corresponds to m = ⌊3n/4⌋.
   1334     // From the proof in `encode_len_checked`, we know that n is a valid length
   1335     // iff n ≢ 1 (mod 4).
   1336     // We claim ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋.
   1337     // Proof:
   1338     // There are three partitions for n:
   1339     // (1) 4i = n ≡ 0 (mod 4) for some integer i
   1340     //   <==>
   1341     //   ⌊3n/4⌋ = ⌊3(4i)/4⌋ = ⌊3i⌋ = 3i = 3⌊i⌋ = 3⌊4i/4⌋ = 3⌊n/4⌋ + 0 = 3⌊n/4⌋ + ⌊3(0)/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋
   1342     // (2) 4i + 2 = n ≡ 2 (mod 4) for some integer i
   1343     //   <==>
   1344     //   ⌊3n/4⌋ = ⌊3(4i + 2)/4⌋ = ⌊3i + 6/4⌋ = 3i + ⌊6/4⌋ = 3i + 1 = 3⌊i⌋ + ⌊3(2)/4⌋
   1345     //                                                             = 3⌊(4i + 2)/4⌋ + ⌊3((4i + 2) mod 4)/4⌋
   1346     //                                                             = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋
   1347     // (3) 4i + 3 = n ≡ 3 (mod 4) for some integer i
   1348     //   <==>
   1349     //   ⌊3n/4⌋ = ⌊3(4i + 3)/4⌋ = ⌊3i + 9/4⌋ = 3i + ⌊9/4⌋ = 3i + 2 = 3⌊i⌋ + ⌊3(3)/4⌋
   1350     //                                                             = 3⌊(4i + 3)/4⌋ + ⌊3((4i + 3) mod 4)/4⌋
   1351     //                                                             = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋
   1352     // QED
   1353     // Naively implementing ⌊3n/4⌋ as (3 * n) / 4 can cause overflow due to `3 * n`; thus
   1354     // we implement the equivalent equation 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ instead:
   1355     // `(3 * (n / 4)) + ((3 * (n % 4)) / 4)` since none of the intermediate calculations suffer
   1356     // from overflow.
   1357     // `input_length % 4`.
   1358     let rem = input_length & 3;
   1359     if rem == 1 {
   1360         None
   1361     } else {
   1362         // 3 * (n >> 2) <= m < usize::MAX; thus the left operand of + is fine.
   1363         // rem <= 3
   1364         // <==>
   1365         // 3rem <= 9 < usize::MAX; thus 3 * rem is fine.
   1366         // <==>
   1367         // ⌊3rem/4⌋ <= 3rem, so the right operand of + is fine.
   1368         // The sum is fine since
   1369         // m = ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ = (3 * (n >> 2)) + ((3 * rem) >> 2), and m < usize::MAX.
   1370         Some((3 * (input_length >> 2)) + ((3 * rem) >> 2))
   1371     }
   1372 }
   1373 /// Error returned from [`decode_buffer`] and [`decode`].
   1374 ///
   1375 /// Note when [`alloc`](./index.html#alloc) is not enabled, [`Copy`] is also implemented.
   1376 #[derive(Clone, Debug, Eq, PartialEq)]
   1377 pub enum DecodeErr {
   1378     /// The encoded input had an invalid length.
   1379     EncodedLen,
   1380     /// The buffer supplied had a length that was too small to contain the decoded data.
   1381     BufferLen,
   1382     /// The encoded data contained trailing bits that were not zero.
   1383     TrailingBits,
   1384     /// The encoded data contained an invalid `u8`.
   1385     InvalidByte,
   1386     /// [`decode`] could not allocate enough memory to contain the decoded data.
   1387     #[cfg(feature = "alloc")]
   1388     TryReserve(TryReserveError),
   1389 }
   1390 #[cfg(not(feature = "alloc"))]
   1391 impl Copy for DecodeErr {}
   1392 impl Display for DecodeErr {
   1393     #[inline]
   1394     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1395         match *self {
   1396             Self::EncodedLen => f.write_str("length of encoded data was invalid"),
   1397             Self::BufferLen => {
   1398                 f.write_str("length of the output buffer is too small to contain the decoded data")
   1399             }
   1400             Self::TrailingBits => {
   1401                 f.write_str("encoded data contained trailing bits that were not zero")
   1402             }
   1403             Self::InvalidByte => f.write_str("encoded data contained an invalid byte"),
   1404             #[cfg(feature = "alloc")]
   1405             Self::TryReserve(ref err) => err.fmt(f),
   1406         }
   1407     }
   1408 }
   1409 impl Error for DecodeErr {}
   1410 /// Decodes `input` into `output` returning the subset of `output` containing the decoded data.
   1411 ///
   1412 /// # Errors
   1413 ///
   1414 /// Errors iff [`decode_len`] of `input.len()` does not return `Some` containing a
   1415 /// `usize` that does not exceed `ouput.len()` or `input` is an invalid base64url-encoded value without padding.
   1416 /// Note [`DecodeErr::TryReserve`] will never be returned.
   1417 ///
   1418 /// # Examples
   1419 ///
   1420 /// ```
   1421 /// # use base64url_nopad::DecodeErr;
   1422 /// assert_eq!(base64url_nopad::decode_buffer(b"", [0; 0].as_mut_slice())?, b"");
   1423 /// assert_eq!(
   1424 ///     base64url_nopad::decode_buffer(b"A", [0; 0].as_mut_slice()).unwrap_err(),
   1425 ///     DecodeErr::EncodedLen
   1426 /// );
   1427 /// assert_eq!(
   1428 ///     base64url_nopad::decode_buffer(b"A+".as_slice(), [0; 3].as_mut_slice()).unwrap_err(),
   1429 ///     DecodeErr::InvalidByte
   1430 /// );
   1431 /// assert_eq!(
   1432 ///     base64url_nopad::decode_buffer(b"AA".as_slice(), [0; 0].as_mut_slice()).unwrap_err(),
   1433 ///     DecodeErr::BufferLen
   1434 /// );
   1435 /// assert_eq!(
   1436 ///     base64url_nopad::decode_buffer(b"-8", [0; 3].as_mut_slice()).unwrap_err(),
   1437 ///     DecodeErr::TrailingBits
   1438 /// );
   1439 /// // A larger output buffer than necessary is OK.
   1440 /// assert_eq!(base64url_nopad::decode_buffer(b"C8Aa_A--91VZbx0", &mut [0; 128])?, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]);
   1441 /// # Ok::<_, DecodeErr>(())
   1442 /// ```
   1443 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1444 #[expect(
   1445     clippy::missing_panics_doc,
   1446     clippy::panic,
   1447     clippy::panic_in_result_fn,
   1448     reason = "want to crash when there is a bug"
   1449 )]
   1450 #[expect(
   1451     clippy::missing_asserts_for_indexing,
   1452     reason = "trust the compiler to already optimize since we match on the length"
   1453 )]
   1454 #[expect(
   1455     clippy::arithmetic_side_effects,
   1456     clippy::indexing_slicing,
   1457     reason = "comments justify correctness"
   1458 )]
   1459 #[expect(clippy::redundant_else, reason = "prefer the elses")]
   1460 #[inline]
   1461 pub const fn decode_buffer<'a>(
   1462     input: &[u8],
   1463     output: &'a mut [u8],
   1464 ) -> Result<&'a mut [u8], DecodeErr> {
   1465     let len = input.len();
   1466     if let Some(output_len) = decode_len(len) {
   1467         if output.len() >= output_len {
   1468             let mut output_idx = 0;
   1469             let (mut chunks, rem) = input.as_chunks::<4>();
   1470             let (mut snd, mut third);
   1471             // There is a _substantial_ boost in performance if we chunk decode.
   1472             while let [first, ref rest @ ..] = *chunks {
   1473                 if let Some(base64_fst) = Alphabet::from_ascii(first[0])
   1474                     && let Some(base64_snd) = Alphabet::from_ascii(first[1])
   1475                     && let Some(base64_third) = Alphabet::from_ascii(first[2])
   1476                     && let Some(base64_fourth) = Alphabet::from_ascii(first[3])
   1477                 {
   1478                     (snd, third) = (base64_snd.to_u8(), base64_third.to_u8());
   1479                     output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4);
   1480                     output_idx += 1;
   1481                     output[output_idx] = (snd << 4) | (third >> 2);
   1482                     output_idx += 1;
   1483                     output[output_idx] = (third << 6) | base64_fourth.to_u8();
   1484                     output_idx += 1;
   1485                     chunks = rest;
   1486                 } else {
   1487                     return Err(DecodeErr::InvalidByte);
   1488                 }
   1489             }
   1490             match rem.len() {
   1491                 0 => {}
   1492                 1 => {
   1493                     cold_path();
   1494                     panic!("there is a bug in base64url_nopad::decode_len");
   1495                 }
   1496                 2 => {
   1497                     if let Some(base64_fst) = Alphabet::from_ascii(rem[0])
   1498                         && let Some(base64_snd) = Alphabet::from_ascii(rem[1])
   1499                     {
   1500                         snd = base64_snd.to_u8();
   1501                         if snd.trailing_zeros() < 4 {
   1502                             cold_path();
   1503                             return Err(DecodeErr::TrailingBits);
   1504                         } else {
   1505                             output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4);
   1506                         }
   1507                     } else {
   1508                         cold_path();
   1509                         return Err(DecodeErr::InvalidByte);
   1510                     }
   1511                 }
   1512                 3 => {
   1513                     if let Some(base64_fst) = Alphabet::from_ascii(rem[0])
   1514                         && let Some(base64_snd) = Alphabet::from_ascii(rem[1])
   1515                         && let Some(base64_third) = Alphabet::from_ascii(rem[2])
   1516                     {
   1517                         (snd, third) = (base64_snd.to_u8(), base64_third.to_u8());
   1518                         if third.trailing_zeros() < 2 {
   1519                             cold_path();
   1520                             return Err(DecodeErr::TrailingBits);
   1521                         } else {
   1522                             output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4);
   1523                             output[output_idx + 1] = (snd << 4) | (third >> 2);
   1524                         }
   1525                     } else {
   1526                         cold_path();
   1527                         return Err(DecodeErr::InvalidByte);
   1528                     }
   1529                 }
   1530                 _ => {
   1531                     cold_path();
   1532                     panic!("there is a bug in core::slice::as_chunks");
   1533                 }
   1534             }
   1535             // SAFETY:
   1536             // `output.len() >= output_len`.
   1537             Ok(unsafe { output.split_at_mut_unchecked(output_len) }.0)
   1538         } else {
   1539             cold_path();
   1540             Err(DecodeErr::BufferLen)
   1541         }
   1542     } else {
   1543         Err(DecodeErr::EncodedLen)
   1544     }
   1545 }
   1546 /// Similar to [`decode_buffer`] except a `Vec` is returned instead using its buffer to write to.
   1547 ///
   1548 /// # Errors
   1549 ///
   1550 /// Errors iff [`decode_buffer`] errors or an error occurs from allocating the capacity needed to contain
   1551 /// the decoded data. Note [`DecodeErr::BufferLen`] is not possible to be returned.
   1552 ///
   1553 /// # Examples
   1554 ///
   1555 /// ```
   1556 /// # use base64url_nopad::DecodeErr;
   1557 /// assert_eq!(base64url_nopad::decode([0; 0].as_slice())?, b"");
   1558 /// assert_eq!(
   1559 ///     base64url_nopad::decode(b"A").unwrap_err(),
   1560 ///     DecodeErr::EncodedLen
   1561 /// );
   1562 /// assert_eq!(
   1563 ///     base64url_nopad::decode(b"AA==").unwrap_err(),
   1564 ///     DecodeErr::InvalidByte
   1565 /// );
   1566 /// assert_eq!(
   1567 ///     base64url_nopad::decode(b"-8").unwrap_err(),
   1568 ///     DecodeErr::TrailingBits
   1569 /// );
   1570 /// assert_eq!(base64url_nopad::decode(b"C8Aa_A--91VZbx0")?, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]);
   1571 /// # Ok::<_, DecodeErr>(())
   1572 /// ```
   1573 #[cfg(feature = "alloc")]
   1574 #[inline]
   1575 pub fn decode(input: &[u8]) -> Result<Vec<u8>, DecodeErr> {
   1576     decode_len(input.len())
   1577         .ok_or(DecodeErr::EncodedLen)
   1578         .and_then(|capacity| {
   1579             let mut buffer = Vec::new();
   1580             buffer
   1581                 .try_reserve_exact(capacity)
   1582                 .map_err(DecodeErr::TryReserve)
   1583                 .and_then(|()| {
   1584                     buffer.resize(capacity, 0);
   1585                     if let Err(e) = decode_buffer(input, buffer.as_mut_slice()) {
   1586                         Err(e)
   1587                     } else {
   1588                         Ok(buffer)
   1589                     }
   1590                 })
   1591         })
   1592 }
   1593 /// Similar to [`decode_buffer`] except the data is not decoded.
   1594 ///
   1595 /// In some situations, one does not want to actually decode data but merely validate that the encoded data
   1596 /// is valid base64url without padding. Since data is not actually decoded, one avoids the need to allocate
   1597 /// a large-enough buffer first.
   1598 ///
   1599 /// # Errors
   1600 ///
   1601 /// Errors iff `input` is an invalid base64url without padding.
   1602 ///
   1603 /// Note since no buffer is used to decode the data into, neither [`DecodeErr::BufferLen`] nor
   1604 /// [`DecodeErr::TryReserve`] will ever be returned.
   1605 ///
   1606 /// # Examples
   1607 ///
   1608 /// ```
   1609 /// # use base64url_nopad::DecodeErr;
   1610 /// base64url_nopad::validate_encoded_data(b"")?;
   1611 /// assert_eq!(
   1612 ///     base64url_nopad::validate_encoded_data(b"A").unwrap_err(),
   1613 ///     DecodeErr::EncodedLen
   1614 /// );
   1615 /// assert_eq!(
   1616 ///     base64url_nopad::validate_encoded_data(b"A/").unwrap_err(),
   1617 ///     DecodeErr::InvalidByte
   1618 /// );
   1619 /// assert_eq!(
   1620 ///     base64url_nopad::validate_encoded_data(b"-8").unwrap_err(),
   1621 ///     DecodeErr::TrailingBits
   1622 /// );
   1623 /// base64url_nopad::validate_encoded_data(b"C8Aa_A--91VZbx0")?;
   1624 /// # Ok::<_, DecodeErr>(())
   1625 /// ```
   1626 #[expect(
   1627     clippy::missing_panics_doc,
   1628     clippy::panic,
   1629     clippy::panic_in_result_fn,
   1630     reason = "want to crash when there is a bug"
   1631 )]
   1632 #[expect(
   1633     clippy::missing_asserts_for_indexing,
   1634     reason = "trust the compiler to already optimize since we match on the length"
   1635 )]
   1636 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   1637 #[inline]
   1638 pub const fn validate_encoded_data(input: &[u8]) -> Result<(), DecodeErr> {
   1639     let (mut chunks, rem) = input.as_chunks::<4>();
   1640     // There is a _substantial_ boost in performance if we chunk decode.
   1641     while let [first, ref rest @ ..] = *chunks {
   1642         if Alphabet::from_ascii(first[0]).is_some()
   1643             && Alphabet::from_ascii(first[1]).is_some()
   1644             && Alphabet::from_ascii(first[2]).is_some()
   1645             && Alphabet::from_ascii(first[3]).is_some()
   1646         {
   1647             chunks = rest;
   1648         } else {
   1649             return Err(DecodeErr::InvalidByte);
   1650         }
   1651     }
   1652     match rem.len() {
   1653         0 => Ok(()),
   1654         1 => {
   1655             cold_path();
   1656             Err(DecodeErr::EncodedLen)
   1657         }
   1658         2 => {
   1659             if Alphabet::from_ascii(rem[0]).is_some()
   1660                 && let Some(base64_snd) = Alphabet::from_ascii(rem[1])
   1661             {
   1662                 if base64_snd.to_u8().trailing_zeros() < 4 {
   1663                     cold_path();
   1664                     Err(DecodeErr::TrailingBits)
   1665                 } else {
   1666                     Ok(())
   1667                 }
   1668             } else {
   1669                 cold_path();
   1670                 Err(DecodeErr::InvalidByte)
   1671             }
   1672         }
   1673         3 => {
   1674             if Alphabet::from_ascii(rem[0]).is_some()
   1675                 && Alphabet::from_ascii(rem[1]).is_some()
   1676                 && let Some(base64_third) = Alphabet::from_ascii(rem[2])
   1677             {
   1678                 if base64_third.to_u8().trailing_zeros() < 2 {
   1679                     cold_path();
   1680                     Err(DecodeErr::TrailingBits)
   1681                 } else {
   1682                     Ok(())
   1683                 }
   1684             } else {
   1685                 cold_path();
   1686                 Err(DecodeErr::InvalidByte)
   1687             }
   1688         }
   1689         _ => {
   1690             cold_path();
   1691             panic!("there is a bug in core::slice::as_chunks");
   1692         }
   1693     }
   1694 }
   1695 /// Same as [`encode_buffer`] except `output` must have the _exact_ length needed to encode `input`, and the
   1696 /// encoded `str` is not returned.
   1697 ///
   1698 /// # Panics
   1699 ///
   1700 /// `panic`s iff `output` does not have the _exact_ length needed to encode `input`.
   1701 ///
   1702 /// # Examples
   1703 ///
   1704 /// ```
   1705 /// let mut buffer = [0; 256];
   1706 /// base64url_nopad::encode_buffer_exact([0; 0].as_slice(), &mut buffer[..0]);
   1707 /// base64url_nopad::encode_buffer_exact([0; 1].as_slice(), &mut buffer[..2]);
   1708 /// assert_eq!(*b"AA", buffer[..2]);
   1709 /// // Uncommenting below will cause a `panic` since the output buffer must be exact.
   1710 /// // base64url_nopad::encode_buffer_exact([255; 1].as_slice(), &mut buffer);
   1711 /// ```
   1712 #[inline]
   1713 pub const fn encode_buffer_exact(input: &[u8], output: &mut [u8]) {
   1714     assert!(
   1715         // `encode_len` won't `panic` since Rust guarantees `input.len()` is at most `isize::MAX`.
   1716         output.len() == encode_len(input.len()),
   1717         "encode_buffer_exact must be passed an output buffer whose length is exactly the length needed to encode the data"
   1718     );
   1719     _ = encode_buffer(input, output);
   1720 }
   1721 /// Same as [`decode_buffer`] except `output` must have the _exact_ length needed, and the decoded `slice`
   1722 /// is not returned.
   1723 ///
   1724 /// # Errors
   1725 ///
   1726 /// Errors iff [`decode_buffer`] errors. Note that since a `panic` occurs when `output.len()` is not the
   1727 /// exact length needed, [`DecodeErr::BufferLen`] is not possible in addition to [`DecodeErr::TryReserve`].
   1728 ///
   1729 /// # Panics
   1730 ///
   1731 /// `panic`s iff `output` does not have the _exact_ length needed to contain the decoded data. Note when `input`
   1732 /// contains an invalid length, [`DecodeErr::EncodedLen`] is returned _not_ a `panic`.
   1733 ///
   1734 /// # Examples
   1735 ///
   1736 /// ```
   1737 /// # use base64url_nopad::DecodeErr;
   1738 /// assert_eq!(
   1739 ///     base64url_nopad::decode_buffer_exact(b"A", [0; 0].as_mut_slice()).unwrap_err(),
   1740 ///     DecodeErr::EncodedLen
   1741 /// );
   1742 /// assert_eq!(
   1743 ///     base64url_nopad::decode_buffer_exact(b"+A", [0; 1].as_mut_slice()).unwrap_err(),
   1744 ///     DecodeErr::InvalidByte
   1745 /// );
   1746 /// assert_eq!(
   1747 ///     base64url_nopad::decode_buffer_exact(b"-8", [0; 1].as_mut_slice()).unwrap_err(),
   1748 ///     DecodeErr::TrailingBits
   1749 /// );
   1750 /// let mut buffer = [0; base64url_nopad::decode_len(b"C8Aa_A--91VZbx0".len()).unwrap()];
   1751 /// base64url_nopad::decode_buffer_exact(b"C8Aa_A--91VZbx0", &mut buffer)?;
   1752 /// assert_eq!(buffer, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]);
   1753 /// // Uncommenting below will cause a `panic` since a larger output buffer than necessary is _not_ OK.
   1754 /// // base64url_nopad::decode_buffer_exact(b"C8Aa_A--91VZbx0", &mut [0; 128])?;
   1755 /// # Ok::<_, DecodeErr>(())
   1756 /// ```
   1757 #[expect(
   1758     clippy::panic_in_result_fn,
   1759     reason = "purpose of this function is to panic when output does not have the exact length needed"
   1760 )]
   1761 #[inline]
   1762 pub const fn decode_buffer_exact(input: &[u8], output: &mut [u8]) -> Result<(), DecodeErr> {
   1763     if let Some(output_len) = decode_len(input.len()) {
   1764         assert!(
   1765             output.len() == output_len,
   1766             "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data"
   1767         );
   1768         if let Err(e) = decode_buffer(input, output) {
   1769             Err(e)
   1770         } else {
   1771             Ok(())
   1772         }
   1773     } else {
   1774         Err(DecodeErr::EncodedLen)
   1775     }
   1776 }
   1777 #[cfg(test)]
   1778 mod test {
   1779     #[cfg(any(
   1780         target_pointer_width = "16",
   1781         target_pointer_width = "32",
   1782         target_pointer_width = "64",
   1783     ))]
   1784     use super::MAX_ENCODE_INPUT_LEN;
   1785     #[cfg(feature = "alloc")]
   1786     use alloc::string::String;
   1787     #[cfg(any(
   1788         target_pointer_width = "16",
   1789         target_pointer_width = "32",
   1790         target_pointer_width = "64",
   1791     ))]
   1792     use rand::{RngExt as _, rngs::SmallRng};
   1793     #[expect(
   1794         clippy::as_conversions,
   1795         clippy::cast_possible_truncation,
   1796         reason = "comment justifies correctness"
   1797     )]
   1798     #[cfg(any(
   1799         target_pointer_width = "16",
   1800         target_pointer_width = "32",
   1801         target_pointer_width = "64",
   1802     ))]
   1803     #[ignore = "slow"]
   1804     #[test]
   1805     fn encode_decode_len() {
   1806         assert_eq!(MAX_ENCODE_INPUT_LEN, 3 * (usize::MAX.div_ceil(4)) - 1);
   1807         let mut rng = rand::make_rng::<SmallRng>();
   1808         for _ in 0u32..10_000_000 {
   1809             // `uN as usize` is fine since we `cfg` by pointer width.
   1810             #[cfg(target_pointer_width = "16")]
   1811             let len = rng.random::<u16>() as usize;
   1812             #[cfg(target_pointer_width = "32")]
   1813             let len = rng.random::<u32>() as usize;
   1814             #[cfg(target_pointer_width = "64")]
   1815             let len = rng.random::<u64>() as usize;
   1816             if len <= MAX_ENCODE_INPUT_LEN {
   1817                 assert_eq!(
   1818                     super::encode_len_checked(len).map(super::decode_len),
   1819                     Some(Some(len))
   1820                 );
   1821             } else {
   1822                 assert!(super::encode_len_checked(len).is_none());
   1823             }
   1824         }
   1825         for i in 0..1025 {
   1826             assert_eq!(
   1827                 super::encode_len_checked(i).map(super::decode_len),
   1828                 Some(Some(i))
   1829             );
   1830         }
   1831         #[cfg(target_pointer_width = "16")]
   1832         for i in MAX_ENCODE_INPUT_LEN + 1.. {
   1833             assert!(super::encode_len_checked(i).is_none());
   1834         }
   1835         #[cfg(not(target_pointer_width = "16"))]
   1836         for i in MAX_ENCODE_INPUT_LEN + 1..MAX_ENCODE_INPUT_LEN + 1_000_000 {
   1837             assert!(super::encode_len_checked(i).is_none());
   1838         }
   1839         assert!(super::encode_len_checked(usize::MAX).is_none());
   1840         assert_eq!(
   1841             super::encode_len_checked(MAX_ENCODE_INPUT_LEN),
   1842             Some(usize::MAX)
   1843         );
   1844         for _ in 0u32..10_000_000 {
   1845             #[cfg(target_pointer_width = "16")]
   1846             let len = rng.random::<u16>() as usize;
   1847             #[cfg(target_pointer_width = "32")]
   1848             let len = rng.random::<u32>() as usize;
   1849             #[cfg(target_pointer_width = "64")]
   1850             let len = rng.random::<u64>() as usize;
   1851             if len & 3 == 1 {
   1852                 assert!(super::decode_len(len).is_none());
   1853             } else {
   1854                 assert_eq!(
   1855                     super::decode_len(len).map(super::encode_len_checked),
   1856                     Some(Some(len))
   1857                 );
   1858             }
   1859         }
   1860         for i in 0..1025 {
   1861             if i & 3 == 1 {
   1862                 assert!(super::decode_len(i).is_none());
   1863             } else {
   1864                 assert_eq!(
   1865                     super::decode_len(i).map(super::encode_len_checked),
   1866                     Some(Some(i))
   1867                 );
   1868             }
   1869         }
   1870         #[cfg(target_pointer_width = "16")]
   1871         for i in 0..=usize::MAX {
   1872             if i & 3 == 1 {
   1873                 assert!(super::decode_len(i).is_none());
   1874             } else {
   1875                 assert_eq!(
   1876                     super::decode_len(i).map(super::encode_len_checked),
   1877                     Some(Some(i))
   1878                 );
   1879             }
   1880         }
   1881         #[cfg(not(target_pointer_width = "16"))]
   1882         for i in usize::MAX - 1_000_000..=usize::MAX {
   1883             if i & 3 == 1 {
   1884                 assert!(super::decode_len(i).is_none());
   1885             } else {
   1886                 assert_eq!(
   1887                     super::decode_len(i).map(super::encode_len_checked),
   1888                     Some(Some(i))
   1889                 );
   1890             }
   1891         }
   1892         assert_eq!(super::decode_len(usize::MAX), Some(MAX_ENCODE_INPUT_LEN));
   1893     }
   1894     #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   1895     #[cfg(feature = "alloc")]
   1896     #[test]
   1897     fn encode_write() {
   1898         let input = [9; 8192];
   1899         let mut buffer = String::with_capacity(super::encode_len(input.len()));
   1900         let cap = buffer.capacity();
   1901         let mut write_len;
   1902         for len in 0..input.len() {
   1903             write_len = super::encode_len(len);
   1904             match write_len.checked_add(buffer.len()) {
   1905                 None => {
   1906                     buffer.clear();
   1907                     // Indexing is fine since `len <= input.len()`.
   1908                     assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(()));
   1909                     assert_eq!(buffer.len(), write_len);
   1910                 }
   1911                 Some(l) => {
   1912                     if l > cap {
   1913                         buffer.clear();
   1914                         // Indexing is fine since `len <= input.len()`.
   1915                         assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(()));
   1916                         assert_eq!(buffer.len(), write_len);
   1917                     } else {
   1918                         // Indexing is fine since `len <= input.len()`.
   1919                         assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(()));
   1920                         assert_eq!(buffer.len(), l);
   1921                     }
   1922                 }
   1923             }
   1924             assert!(
   1925                 buffer
   1926                     .as_bytes()
   1927                     .iter()
   1928                     .all(|b| { matches!(*b, b'C' | b'J' | b'Q' | b'k') })
   1929             );
   1930         }
   1931     }
   1932 }