base64url_nopad

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

lib.rs (58274B)


      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 fast, efficient, correct, and `const` encoding and decoding of base64url
      8 //! without padding data. All functions that can be `const` are `const`. Great care is made to ensure _all_
      9 //! arithmetic is free from "side effects" (e.g., overflow). `panic`s are avoided at all costs unless explicitly
     10 //! documented _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(linker_info, reason = "getrandom causes linker-info to fire on macos")
     63 )]
     64 #![no_std]
     65 #![cfg_attr(docsrs, feature(doc_cfg))]
     66 #[cfg(any(doc, feature = "alloc"))]
     67 extern crate alloc;
     68 /// Unit tests.
     69 #[cfg(test)]
     70 mod tests;
     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 /// `const`-version of `unreachable`.
    787 macro_rules! impossible {
    788     ( $( $x:literal)? ) => {
    789         {
    790             cold_path();
    791             $(
    792                 panic!($x);
    793             )?
    794         }
    795     };
    796 }
    797 /// Encodes `input` into `output` re-interpreting the encoded subset of `output` as a `str` before returning it.
    798 ///
    799 /// `Some` is returned iff `output.len()` is large enough to write the encoded data into.
    800 ///
    801 /// Note since Rust guarantees all memory allocations don't exceed [`isize::MAX`] bytes, one can
    802 /// instead call [`encode_buffer`] using a buffer whose length is at least as large as the value returned from
    803 /// [`encode_len`] without fear of a `panic` and the benefit of getting a `str` instead of an `Option`.
    804 ///
    805 /// # Examples
    806 ///
    807 /// ```
    808 /// assert_eq!(
    809 ///     base64url_nopad::encode_buffer_checked([0; 0].as_slice(), [0; 0].as_mut_slice()).as_deref(), Some("")
    810 /// );
    811 /// assert_eq!(
    812 ///     base64url_nopad::encode_buffer_checked([0; 1].as_slice(), [0; 2].as_mut_slice()).as_deref(), Some("AA")
    813 /// );
    814 /// // A larger output buffer than necessary is OK.
    815 /// assert_eq!(
    816 ///     base64url_nopad::encode_buffer_checked([1; 1].as_slice(), [0; 128].as_mut_slice()).as_deref(), Some("AQ")
    817 /// );
    818 /// assert_eq!(
    819 ///     base64url_nopad::encode_buffer_checked(
    820 ///         [0xc9; 14].as_slice(),
    821 ///         [0; base64url_nopad::encode_len(14)].as_mut_slice()
    822 ///     ).as_deref(),
    823 ///     Some("ycnJycnJycnJycnJyck")
    824 /// );
    825 /// assert!(base64url_nopad::encode_buffer_checked([0; 1].as_slice(), [0; 1].as_mut_slice()).is_none());
    826 /// ```
    827 #[expect(unsafe_code, reason = "comments justify correctness")]
    828 #[expect(
    829     clippy::missing_asserts_for_indexing,
    830     reason = "trust the compiler to already optimize since we match on the length"
    831 )]
    832 #[expect(
    833     clippy::arithmetic_side_effects,
    834     clippy::as_conversions,
    835     clippy::indexing_slicing,
    836     reason = "comments justify correctness"
    837 )]
    838 #[inline]
    839 pub const fn encode_buffer_checked<'a>(input: &[u8], output: &'a mut [u8]) -> Option<&'a mut str> {
    840     // This won't `panic` since Rust guarantees that all memory allocations won't exceed `isize::MAX`.
    841     let final_len = encode_len(input.len());
    842     if output.len() >= final_len {
    843         let (mut chunks, rem) = input.as_chunks::<3>();
    844         let (mut fst, mut snd, mut third);
    845         let mut output_idx = 0;
    846         // There is a _substantial_ boost in performance if we chunk encode.
    847         while let [first, ref rest @ ..] = *chunks {
    848             (fst, snd, third) = (first[0], first[1], first[2]);
    849             // We trim the last two bits and interpret `fst` as a 6-bit integer.
    850             // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    851             // `ASCII.len() == 64 > fst >> 2`, so indexing won't `panic`.
    852             output[output_idx] = ASCII[(fst >> 2) as usize];
    853             // The two bits we trimmed are the first two bits of the next 6-bit integer.
    854             output_idx += 1;
    855             // We trim the last four bits and interpret `snd` as a 6-bit integer.
    856             // The first two bits are the trailing 2 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 > ((fst & 3) << 4) | (snd >> 4)`, so indexing won't `panic`.
    859             output[output_idx] = ASCII[(((fst & 3) << 4) | (snd >> 4)) as usize];
    860             output_idx += 1;
    861             // We trim the last six bits and interpret `third` as a 6-bit integer.
    862             // The first four bits are the trailing 4 bits from the previous value.
    863             // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    864             // `ASCII.len() == 64 > ((snd & 15) << 2) | (third >> 6)`, so indexing won't `panic`.
    865             output[output_idx] = ASCII[(((snd & 15) << 2) | (third >> 6)) as usize];
    866             // Every third `u8` corresponds to a fourth base64url `u8`.
    867             output_idx += 1;
    868             // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    869             // `ASCII.len() == 64 > (third & 63)`, so indexing won't `panic`.
    870             output[output_idx] = ASCII[(third & 63) as usize];
    871             output_idx += 1;
    872             chunks = rest;
    873         }
    874         match rem.len() {
    875             0 => {}
    876             1 => {
    877                 // `rem.len() == 1`, so indexing won't `panic`.
    878                 fst = rem[0];
    879                 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    880                 // `ASCII.len() == 64 > fst >> 2`, so indexing won't `panic`.
    881                 output[output_idx] = ASCII[(fst >> 2) as usize];
    882                 // `ASCII.len() == 64 > (fst & 3) << 4`, so indexing won't `panic`.
    883                 output[output_idx + 1] = ASCII[((fst & 3) << 4) as usize];
    884             }
    885             2 => {
    886                 // `rem.len() == 2`, so indexing won't `panic`.
    887                 (fst, snd) = (rem[0], rem[1]);
    888                 // `input.len()` is not a multiple of 3; thus we have to append a final `u8` containing the
    889                 // last bits.
    890                 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    891                 // `ASCII.len() == 64 > fst >> 2`, so indexing won't `panic`.
    892                 output[output_idx] = ASCII[(fst >> 2) as usize];
    893                 // `ASCII.len() == 64 > ((fst & 3) << 4) | (snd >> 4)`, so indexing won't `panic`.
    894                 output[output_idx + 1] = ASCII[(((fst & 3) << 4) | (snd >> 4)) as usize];
    895                 // `ASCII.len() == 64 > (snd & 15) << 2`, so indexing won't `panic`.
    896                 output[output_idx + 2] = ASCII[((snd & 15) << 2) as usize];
    897             }
    898             _ => impossible!("there is a bug in core::slice::as_chunks"),
    899         }
    900         // SAFETY:
    901         // We verified `output.len() >= final_len`.
    902         let val = unsafe { output.split_at_mut_unchecked(final_len) }.0;
    903         // SAFETY:
    904         // `val` has the exact length needed to encode `input`, and all of the `u8`s in it
    905         // are from `Alphabet::to_ascii` which is a subset of UTF-8; thus this is safe.
    906         // Note the above is vacuously true when `val` is empty.
    907         Some(unsafe { str::from_utf8_unchecked_mut(val) })
    908     } else {
    909         cold_path();
    910         None
    911     }
    912 }
    913 /// Same as [`encode_buffer_checked`] except a `panic` occurs instead of `None` being returned.
    914 ///
    915 /// # Panics
    916 ///
    917 /// `panic`s iff [`encode_buffer_checked`] returns `None` (i.e., the length of the output buffer is too small).
    918 ///
    919 /// # Examples
    920 ///
    921 /// ```
    922 /// assert_eq!(
    923 ///     base64url_nopad::encode_buffer([0; 0].as_slice(), [0; 0].as_mut_slice()),
    924 ///     ""
    925 /// );
    926 /// assert_eq!(
    927 ///     base64url_nopad::encode_buffer([0; 1].as_slice(), [0; 2].as_mut_slice()),
    928 ///     "AA"
    929 /// );
    930 /// // A larger output buffer than necessary is OK.
    931 /// assert_eq!(
    932 ///     base64url_nopad::encode_buffer([255; 1].as_slice(), [0; 256].as_mut_slice()),
    933 ///     "_w"
    934 /// );
    935 /// assert_eq!(
    936 ///     base64url_nopad::encode_buffer(
    937 ///         [0xc9; 14].as_slice(),
    938 ///         [0; base64url_nopad::encode_len(14)].as_mut_slice()
    939 ///     ),
    940 ///     "ycnJycnJycnJycnJyck"
    941 /// );
    942 /// // The below will `panic` when uncommented since the supplied output buffer is too small.
    943 /// // _ = base64url_nopad::encode_buffer([0; 1].as_slice(), [0; 1].as_mut_slice());
    944 /// ```
    945 #[expect(clippy::unwrap_used, reason = "comment justifies correctness")]
    946 #[inline]
    947 pub const fn encode_buffer<'a>(input: &[u8], output: &'a mut [u8]) -> &'a mut str {
    948     // A precondition for calling this function is to ensure `encode_buffer_checked` can't return `None`.
    949     encode_buffer_checked(input, output).unwrap()
    950 }
    951 /// Similar to [`encode_buffer`] except a `String` is returned instead using its buffer to write to.
    952 ///
    953 /// # Errors
    954 ///
    955 /// Errors iff an error occurs from allocating the capacity needed to contain the encoded data.
    956 ///
    957 /// # Examples
    958 ///
    959 /// ```
    960 /// # extern crate alloc;
    961 /// # use alloc::collections::TryReserveError;
    962 /// assert_eq!(
    963 ///     base64url_nopad::try_encode([0; 0].as_slice())?,
    964 ///     ""
    965 /// );
    966 /// assert_eq!(
    967 ///     base64url_nopad::try_encode([0; 1].as_slice())?,
    968 ///     "AA"
    969 /// );
    970 /// assert_eq!(
    971 ///     base64url_nopad::try_encode([128, 40, 3].as_slice())?,
    972 ///     "gCgD"
    973 /// );
    974 /// assert_eq!(
    975 ///     base64url_nopad::try_encode([0x7b; 22].as_slice())?,
    976 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"
    977 /// );
    978 /// # Ok::<_, TryReserveError>(())
    979 /// ```
    980 #[cfg(feature = "alloc")]
    981 #[expect(unsafe_code, reason = "comment justifies correctness")]
    982 #[inline]
    983 pub fn try_encode(input: &[u8]) -> Result<String, TryReserveError> {
    984     let mut output = Vec::new();
    985     // `encode_len` won't `panic` since Rust guarantees `input.len()` will not return a value greater
    986     // than `isize::MAX`.
    987     let len = encode_len(input.len());
    988     output.try_reserve_exact(len).map(|()| {
    989         output.resize(len, 0);
    990         _ = encode_buffer(input, output.as_mut_slice());
    991         // SAFETY:
    992         // `output` has the exact length needed to encode `input`, and all of the `u8`s in it
    993         // are from `Alphabet` which is a subset of UTF-8; thus this is safe.
    994         // Note the above is vacuously true when `output` is empty.
    995         unsafe { String::from_utf8_unchecked(output) }
    996     })
    997 }
    998 /// Same as [`try_encode`] except a `panic` occurs on allocation failure.
    999 ///
   1000 /// # Panics
   1001 ///
   1002 /// `panic`s iff [`try_encode`] errors.
   1003 ///
   1004 /// # Examples
   1005 ///
   1006 /// ```
   1007 /// assert_eq!(
   1008 ///     base64url_nopad::encode([0; 0].as_slice()),
   1009 ///     ""
   1010 /// );
   1011 /// assert_eq!(
   1012 ///     base64url_nopad::encode([0; 1].as_slice()),
   1013 ///     "AA"
   1014 /// );
   1015 /// assert_eq!(
   1016 ///     base64url_nopad::encode([128, 40, 3].as_slice()),
   1017 ///     "gCgD"
   1018 /// );
   1019 /// assert_eq!(
   1020 ///     base64url_nopad::encode([0x7b; 22].as_slice()),
   1021 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"
   1022 /// );
   1023 /// ```
   1024 #[cfg(feature = "alloc")]
   1025 #[expect(
   1026     clippy::unwrap_used,
   1027     reason = "purpose of function is to panic on allocation failure"
   1028 )]
   1029 #[inline]
   1030 #[must_use]
   1031 pub fn encode(input: &[u8]) -> String {
   1032     try_encode(input).unwrap()
   1033 }
   1034 /// Writes the base64url encoding of `input` using `writer`.
   1035 ///
   1036 /// Internally a buffer of at most 1024 bytes is used to write the encoded data.
   1037 ///
   1038 /// # Errors
   1039 ///
   1040 /// Errors iff [`Write::write_str`] does.
   1041 ///
   1042 /// # Panics
   1043 ///
   1044 /// `panic`s iff [`Write::write_str`] does.
   1045 ///
   1046 /// # Examples
   1047 ///
   1048 /// ```
   1049 /// # extern crate alloc;
   1050 /// # use alloc::string::String;
   1051 /// # use core::fmt::Error;
   1052 /// let mut buffer = String::new();
   1053 /// base64url_nopad::encode_write([0; 0].as_slice(), &mut buffer)?;
   1054 /// assert_eq!(buffer, "");
   1055 /// buffer.clear();
   1056 /// base64url_nopad::encode_write([0; 1].as_slice(), &mut buffer)?;
   1057 /// assert_eq!(buffer, "AA");
   1058 /// buffer.clear();
   1059 /// base64url_nopad::encode_write(
   1060 ///     [0xc9; 14].as_slice(),
   1061 ///     &mut buffer,
   1062 /// )?;
   1063 /// assert_eq!(buffer, "ycnJycnJycnJycnJyck");
   1064 /// # Ok::<_, Error>(())
   1065 /// ```
   1066 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1067 #[expect(
   1068     clippy::arithmetic_side_effects,
   1069     clippy::as_conversions,
   1070     clippy::indexing_slicing,
   1071     reason = "comments justify correctness"
   1072 )]
   1073 #[inline]
   1074 pub fn encode_write<W: Write>(mut input: &[u8], mut writer: W) -> fmt::Result {
   1075     /// The max buffer size.
   1076     ///
   1077     /// This must be at least 4, no more than `i16::MAX`, and must be a power of 2.
   1078     const MAX_BUFFER_LEN: usize = 1024;
   1079     /// Want to ensure at compilation time that `MAX_BUFFER_LEN` upholds its invariants. Namely
   1080     /// that it's at least as large as 4, doesn't exceed [`i16::MAX`], and is always a power of 2.
   1081     const _: () = {
   1082         // `i16::MAX <= usize::MAX`, so this is fine.
   1083         /// `i16::MAX`.
   1084         const MAX_LEN: usize = i16::MAX as usize;
   1085         assert!(
   1086             4 <= MAX_BUFFER_LEN && MAX_BUFFER_LEN < MAX_LEN && MAX_BUFFER_LEN.is_power_of_two(),
   1087             "encode_write::MAX_BUFFER_LEN must be a power of two less than i16::MAX but at least as large as 4"
   1088         );
   1089     };
   1090     /// The input size that corresponds to an encoded value of length `MAX_BUFFER_LEN`.
   1091     // This will never `panic` since `MAX_BUFFER_LEN` is a power of two at least as large as 4
   1092     // (i.e., `MAX_BUFFER_LEN` ≢ 1 (mod 4)).
   1093     const INPUT_LEN: usize = decode_len(MAX_BUFFER_LEN).unwrap();
   1094     let mut buffer = [0; MAX_BUFFER_LEN];
   1095     // This won't `panic` since `input.len()` is guaranteed to be no more than `isize::MAX`.
   1096     let len = encode_len(input.len());
   1097     if len <= MAX_BUFFER_LEN {
   1098         // `buffer.len() == MAX_BUFFER_LEN >= len`, so indexing is fine.
   1099         // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode
   1100         // the data.
   1101         writer.write_str(encode_buffer(input, &mut buffer[..len]))
   1102     } else {
   1103         let mut counter = 0;
   1104         // `len / MAX_BUFFER_LEN` is equal to ⌊len / MAX_BUFFER_LEN⌋ since `MAX_BUFFER_LEN` is a power of two.
   1105         // We can safely encode `term` chunks of `INPUT_LEN` length into `buffer`.
   1106         let term = len >> MAX_BUFFER_LEN.trailing_zeros();
   1107         let mut input_buffer;
   1108         while counter < term {
   1109             // SAFETY:
   1110             // `input.len() >= INPUT_LEN`.
   1111             input_buffer = unsafe { input.split_at_unchecked(INPUT_LEN) };
   1112             // `encode_buffer` won't `panic` since `buffer` has length `MAX_BUFFER_LEN` which
   1113             // is the exact length needed for `INPUT_LEN` length inputs which `input_buffer.0` is.
   1114             writer.write_str(encode_buffer(input_buffer.0, buffer.as_mut_slice()))?;
   1115             input = input_buffer.1;
   1116             // `counter < term`, so overflow cannot happen.
   1117             counter += 1;
   1118         }
   1119         // `encode_len` won't `panic` since `input.len() < MAX_ENCODE_INPUT_LEN`.
   1120         // `input.len() < INPUT_LEN`; thus `encode_len(input.len()) < MAX_BUFFER_LEN = buffer.len()` so
   1121         // indexing is fine.
   1122         // `encode_buffer` won't `panic` since the buffer is the exact length needed to encode `input`.
   1123         writer.write_str(encode_buffer(input, &mut buffer[..encode_len(input.len())]))
   1124     }
   1125 }
   1126 /// Appends the base64url encoding of `input` to `s` returning the `str` that was appended.
   1127 ///
   1128 /// # Errors
   1129 ///
   1130 /// Errors iff an error occurs from allocating the capacity needed to append the encoded data.
   1131 ///
   1132 /// # Examples
   1133 ///
   1134 /// ```
   1135 /// # extern crate alloc;
   1136 /// # use alloc::{collections::TryReserveError, string::String};
   1137 /// let mut buffer = String::new();
   1138 /// assert_eq!(
   1139 ///     base64url_nopad::try_encode_append([0; 0].as_slice(), &mut buffer)?,
   1140 ///     ""
   1141 /// );
   1142 /// assert_eq!(
   1143 ///     base64url_nopad::try_encode_append([0; 1].as_slice(), &mut buffer)?,
   1144 ///     "AA"
   1145 /// );
   1146 /// assert_eq!(
   1147 ///     base64url_nopad::try_encode_append([128, 40, 3].as_slice(), &mut buffer)?,
   1148 ///     "gCgD"
   1149 /// );
   1150 /// assert_eq!(buffer, "AAgCgD");
   1151 /// assert_eq!(
   1152 ///     base64url_nopad::try_encode_append([0x7b; 22].as_slice(), &mut buffer)?,
   1153 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"
   1154 /// );
   1155 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew");
   1156 /// # Ok::<_, TryReserveError>(())
   1157 /// ```
   1158 #[cfg(feature = "alloc")]
   1159 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1160 #[expect(
   1161     clippy::arithmetic_side_effects,
   1162     clippy::indexing_slicing,
   1163     reason = "comments justify correctness"
   1164 )]
   1165 #[inline]
   1166 pub fn try_encode_append<'a>(
   1167     input: &[u8],
   1168     s: &'a mut String,
   1169 ) -> Result<&'a mut str, TryReserveError> {
   1170     // `encode_len` won't `panic` since Rust guarantees `input.len()` will return a value no larger
   1171     // than `isize::MAX`.
   1172     let additional_len = encode_len(input.len());
   1173     s.try_reserve_exact(additional_len).map(|()| {
   1174         // SAFETY:
   1175         // We only append base64url ASCII which is a subset of UTF-8, so this will remain valid UTF-8.
   1176         let utf8 = unsafe { s.as_mut_vec() };
   1177         let original_len = utf8.len();
   1178         // Overflow can't happen; otherwise `s.try_reserve_exact` would have erred.
   1179         utf8.resize(original_len + additional_len, 0);
   1180         // `utf8.len() >= original_len`, so indexing is fine.
   1181         // `encode_buffer` won't `panic` since `utf8[original_len..]` has length `additional_len`
   1182         // which is the exact number of bytes needed to encode `input`.
   1183         encode_buffer(input, &mut utf8[original_len..])
   1184     })
   1185 }
   1186 /// Same as [`try_encode_append`] except the encoded `str` is not returned.
   1187 ///
   1188 /// # Errors
   1189 ///
   1190 /// Errors iff [`try_encode_append`] does.
   1191 ///
   1192 /// # Examples
   1193 ///
   1194 /// ```
   1195 /// # extern crate alloc;
   1196 /// # use alloc::{collections::TryReserveError, string::String};
   1197 /// let mut buffer = String::new();
   1198 /// base64url_nopad::try_encode_append_only([0; 0].as_slice(), &mut buffer)?;
   1199 /// assert_eq!(buffer, "");
   1200 /// base64url_nopad::try_encode_append_only([0; 1].as_slice(), &mut buffer)?;
   1201 /// assert_eq!(buffer, "AA");
   1202 /// base64url_nopad::try_encode_append_only([128, 40, 3].as_slice(), &mut buffer)?;
   1203 /// assert_eq!(buffer, "AAgCgD");
   1204 /// base64url_nopad::try_encode_append_only([0x7b; 22].as_slice(), &mut buffer)?;
   1205 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew");
   1206 /// # Ok::<_, TryReserveError>(())
   1207 /// ```
   1208 #[cfg(feature = "alloc")]
   1209 #[inline]
   1210 pub fn try_encode_append_only(input: &[u8], s: &mut String) -> Result<(), TryReserveError> {
   1211     try_encode_append(input, s).map(|_| ())
   1212 }
   1213 /// Same as [`try_encode_append`] except a `panic` occurs on allocation failure.
   1214 ///
   1215 /// # Panics
   1216 ///
   1217 /// `panic`s iff [`try_encode_append`] errors.
   1218 ///
   1219 /// # Examples
   1220 ///
   1221 /// ```
   1222 /// # extern crate alloc;
   1223 /// # use alloc::{collections::TryReserveError, string::String};
   1224 /// let mut buffer = String::new();
   1225 /// assert_eq!(
   1226 ///     base64url_nopad::encode_append([0; 0].as_slice(), &mut buffer),
   1227 ///     ""
   1228 /// );
   1229 /// assert_eq!(
   1230 ///     base64url_nopad::encode_append([0; 1].as_slice(), &mut buffer),
   1231 ///     "AA"
   1232 /// );
   1233 /// assert_eq!(
   1234 ///     base64url_nopad::encode_append([128, 40, 3].as_slice(), &mut buffer),
   1235 ///     "gCgD"
   1236 /// );
   1237 /// assert_eq!(buffer, "AAgCgD");
   1238 /// assert_eq!(
   1239 ///     base64url_nopad::encode_append([0x7b; 22].as_slice(), &mut buffer),
   1240 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"
   1241 /// );
   1242 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew");
   1243 /// ```
   1244 #[cfg(feature = "alloc")]
   1245 #[expect(
   1246     clippy::unwrap_used,
   1247     reason = "purpose of this function is to panic on allocation failure"
   1248 )]
   1249 #[inline]
   1250 pub fn encode_append<'a>(input: &[u8], s: &'a mut String) -> &'a mut str {
   1251     try_encode_append(input, s).unwrap()
   1252 }
   1253 /// Same as [`encode_append`] except the encoded `str` is not returned.
   1254 ///
   1255 /// # Panics
   1256 ///
   1257 /// `panic`s iff [`encode_append`] does.
   1258 ///
   1259 /// # Examples
   1260 ///
   1261 /// ```
   1262 /// # extern crate alloc;
   1263 /// # use alloc::{collections::TryReserveError, string::String};
   1264 /// let mut buffer = String::new();
   1265 /// base64url_nopad::encode_append_only([0; 0].as_slice(), &mut buffer);
   1266 /// assert_eq!(buffer, "");
   1267 /// base64url_nopad::encode_append_only([0; 1].as_slice(), &mut buffer);
   1268 /// assert_eq!(buffer, "AA");
   1269 /// base64url_nopad::encode_append_only([128, 40, 3].as_slice(), &mut buffer);
   1270 /// assert_eq!(buffer, "AAgCgD");
   1271 /// base64url_nopad::encode_append_only([0x7b; 22].as_slice(), &mut buffer);
   1272 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew");
   1273 /// # Ok::<_, TryReserveError>(())
   1274 /// ```
   1275 #[cfg(feature = "alloc")]
   1276 #[inline]
   1277 pub fn encode_append_only(input: &[u8], s: &mut String) {
   1278     _ = encode_append(input, s);
   1279 }
   1280 /// Returns the exact number of bytes needed to decode a base64url without padding input of length `input_length`.
   1281 ///
   1282 /// `Some` is returned iff `input_length` represents a possible length of a base64url without padding input.
   1283 ///
   1284 /// # Examples
   1285 ///
   1286 /// ```
   1287 /// # use base64url_nopad::MAX_ENCODE_INPUT_LEN;
   1288 /// assert!(base64url_nopad::decode_len(1).is_none());
   1289 /// assert_eq!(base64url_nopad::decode_len(usize::MAX), Some(MAX_ENCODE_INPUT_LEN));
   1290 /// assert_eq!(base64url_nopad::decode_len(4), Some(3));
   1291 /// assert_eq!(base64url_nopad::decode_len(3), Some(2));
   1292 /// assert_eq!(base64url_nopad::decode_len(2), Some(1));
   1293 /// assert_eq!(base64url_nopad::decode_len(0), Some(0));
   1294 /// ```
   1295 #[expect(
   1296     clippy::arithmetic_side_effects,
   1297     reason = "proof and comment justifies their correctness"
   1298 )]
   1299 #[inline]
   1300 #[must_use]
   1301 pub const fn decode_len(input_length: usize) -> Option<usize> {
   1302     // 64^n is the number of distinct values of the input. Let the decoded output be O.
   1303     // There are 256 possible values each byte in O can be; thus we must find
   1304     // the maximum nonnegative integer m such that:
   1305     // 256^m = (2^8)^m = 2^(8m) <= 64^n = (2^6)^n = 2^(6n)
   1306     // <==>
   1307     // lg(2^(8m)) = 8m <= lg(2^(6n)) = 6n   lg is defined on all positive reals which 2^(8m) and 2^(6n) are
   1308     // <==>
   1309     // m <= 6n/8 = 3n/4
   1310     // Clearly that corresponds to m = ⌊3n/4⌋.
   1311     // From the proof in `encode_len_checked`, we know that n is a valid length
   1312     // iff n ≢ 1 (mod 4).
   1313     // We claim ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋.
   1314     // Proof:
   1315     // There are three partitions for n:
   1316     // (1) 4i = n ≡ 0 (mod 4) for some integer i
   1317     //   <==>
   1318     //   ⌊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⌋
   1319     // (2) 4i + 2 = n ≡ 2 (mod 4) for some integer i
   1320     //   <==>
   1321     //   ⌊3n/4⌋ = ⌊3(4i + 2)/4⌋ = ⌊3i + 6/4⌋ = 3i + ⌊6/4⌋ = 3i + 1 = 3⌊i⌋ + ⌊3(2)/4⌋
   1322     //                                                             = 3⌊(4i + 2)/4⌋ + ⌊3((4i + 2) mod 4)/4⌋
   1323     //                                                             = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋
   1324     // (3) 4i + 3 = n ≡ 3 (mod 4) for some integer i
   1325     //   <==>
   1326     //   ⌊3n/4⌋ = ⌊3(4i + 3)/4⌋ = ⌊3i + 9/4⌋ = 3i + ⌊9/4⌋ = 3i + 2 = 3⌊i⌋ + ⌊3(3)/4⌋
   1327     //                                                             = 3⌊(4i + 3)/4⌋ + ⌊3((4i + 3) mod 4)/4⌋
   1328     //                                                             = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋
   1329     // QED
   1330     // Naively implementing ⌊3n/4⌋ as (3 * n) / 4 can cause overflow due to `3 * n`; thus
   1331     // we implement the equivalent equation 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ instead:
   1332     // `(3 * (n / 4)) + ((3 * (n % 4)) / 4)` since none of the intermediate calculations suffer
   1333     // from overflow.
   1334     // `input_length % 4`.
   1335     let rem = input_length & 3;
   1336     if rem == 1 {
   1337         None
   1338     } else {
   1339         // 3 * (n >> 2) <= m < usize::MAX; thus the left operand of + is fine.
   1340         // rem <= 3
   1341         // <==>
   1342         // 3rem <= 9 < usize::MAX; thus 3 * rem is fine.
   1343         // <==>
   1344         // ⌊3rem/4⌋ <= 3rem, so the right operand of + is fine.
   1345         // The sum is fine since
   1346         // m = ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ = (3 * (n >> 2)) + ((3 * rem) >> 2), and m < usize::MAX.
   1347         Some((3 * (input_length >> 2)) + ((3 * rem) >> 2))
   1348     }
   1349 }
   1350 /// Error returned from [`decode_buffer`] and [`decode`].
   1351 ///
   1352 /// Note when [`alloc`](./index.html#alloc) is not enabled, [`Copy`] is also implemented.
   1353 #[derive(Clone, Debug, Eq, PartialEq)]
   1354 pub enum DecodeErr {
   1355     /// The encoded input had an invalid length.
   1356     EncodedLen,
   1357     /// The buffer supplied had a length that was too small to contain the decoded data.
   1358     BufferLen,
   1359     /// The encoded data contained trailing bits that were not zero.
   1360     TrailingBits,
   1361     /// The encoded data contained an invalid `u8`.
   1362     InvalidByte,
   1363     /// [`decode`] could not allocate enough memory to contain the decoded data.
   1364     #[cfg(feature = "alloc")]
   1365     TryReserve(TryReserveError),
   1366 }
   1367 #[cfg(not(feature = "alloc"))]
   1368 impl Copy for DecodeErr {}
   1369 impl Display for DecodeErr {
   1370     #[inline]
   1371     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1372         match *self {
   1373             Self::EncodedLen => f.write_str("length of encoded data was invalid"),
   1374             Self::BufferLen => {
   1375                 f.write_str("length of the output buffer is too small to contain the decoded data")
   1376             }
   1377             Self::TrailingBits => {
   1378                 f.write_str("encoded data contained trailing bits that were not zero")
   1379             }
   1380             Self::InvalidByte => f.write_str("encoded data contained an invalid byte"),
   1381             #[cfg(feature = "alloc")]
   1382             Self::TryReserve(ref err) => err.fmt(f),
   1383         }
   1384     }
   1385 }
   1386 impl Error for DecodeErr {}
   1387 /// Decodes `input` into `output` returning the subset of `output` containing the decoded data.
   1388 ///
   1389 /// # Errors
   1390 ///
   1391 /// Errors iff [`decode_len`] of `input.len()` does not return `Some` containing a
   1392 /// `usize` that does not exceed `ouput.len()` or `input` is an invalid base64url-encoded value without padding.
   1393 /// Note [`DecodeErr::TryReserve`] will never be returned.
   1394 ///
   1395 /// # Examples
   1396 ///
   1397 /// ```
   1398 /// # use base64url_nopad::DecodeErr;
   1399 /// assert_eq!(base64url_nopad::decode_buffer(b"", [0; 0].as_mut_slice())?, b"");
   1400 /// assert_eq!(
   1401 ///     base64url_nopad::decode_buffer(b"A", [0; 0].as_mut_slice()).unwrap_err(),
   1402 ///     DecodeErr::EncodedLen
   1403 /// );
   1404 /// assert_eq!(
   1405 ///     base64url_nopad::decode_buffer(b"A+".as_slice(), [0; 3].as_mut_slice()).unwrap_err(),
   1406 ///     DecodeErr::InvalidByte
   1407 /// );
   1408 /// assert_eq!(
   1409 ///     base64url_nopad::decode_buffer(b"AA".as_slice(), [0; 0].as_mut_slice()).unwrap_err(),
   1410 ///     DecodeErr::BufferLen
   1411 /// );
   1412 /// assert_eq!(
   1413 ///     base64url_nopad::decode_buffer(b"-8", [0; 3].as_mut_slice()).unwrap_err(),
   1414 ///     DecodeErr::TrailingBits
   1415 /// );
   1416 /// // A larger output buffer than necessary is OK.
   1417 /// 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]);
   1418 /// # Ok::<_, DecodeErr>(())
   1419 /// ```
   1420 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1421 #[expect(
   1422     clippy::missing_asserts_for_indexing,
   1423     reason = "trust the compiler to already optimize since we match on the length"
   1424 )]
   1425 #[expect(
   1426     clippy::arithmetic_side_effects,
   1427     clippy::indexing_slicing,
   1428     reason = "comments justify correctness"
   1429 )]
   1430 #[expect(clippy::redundant_else, reason = "prefer the elses")]
   1431 #[inline]
   1432 pub const fn decode_buffer<'a>(
   1433     input: &[u8],
   1434     output: &'a mut [u8],
   1435 ) -> Result<&'a mut [u8], DecodeErr> {
   1436     let len = input.len();
   1437     if let Some(output_len) = decode_len(len) {
   1438         if output.len() >= output_len {
   1439             let mut output_idx = 0;
   1440             let (mut chunks, rem) = input.as_chunks::<4>();
   1441             let (mut snd, mut third);
   1442             // There is a _substantial_ boost in performance if we chunk decode.
   1443             while let [first, ref rest @ ..] = *chunks {
   1444                 if let Some(base64_fst) = Alphabet::from_ascii(first[0])
   1445                     && let Some(base64_snd) = Alphabet::from_ascii(first[1])
   1446                     && let Some(base64_third) = Alphabet::from_ascii(first[2])
   1447                     && let Some(base64_fourth) = Alphabet::from_ascii(first[3])
   1448                 {
   1449                     (snd, third) = (base64_snd.to_u8(), base64_third.to_u8());
   1450                     output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4);
   1451                     output_idx += 1;
   1452                     output[output_idx] = (snd << 4) | (third >> 2);
   1453                     output_idx += 1;
   1454                     output[output_idx] = (third << 6) | base64_fourth.to_u8();
   1455                     output_idx += 1;
   1456                     chunks = rest;
   1457                 } else {
   1458                     return Err(DecodeErr::InvalidByte);
   1459                 }
   1460             }
   1461             match rem.len() {
   1462                 0 => {}
   1463                 1 => impossible!("there is a bug in base64url_nopad::decode_len"),
   1464                 2 => {
   1465                     if let Some(base64_fst) = Alphabet::from_ascii(rem[0])
   1466                         && let Some(base64_snd) = Alphabet::from_ascii(rem[1])
   1467                     {
   1468                         snd = base64_snd.to_u8();
   1469                         if snd.trailing_zeros() < 4 {
   1470                             cold_path();
   1471                             return Err(DecodeErr::TrailingBits);
   1472                         } else {
   1473                             output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4);
   1474                         }
   1475                     } else {
   1476                         cold_path();
   1477                         return Err(DecodeErr::InvalidByte);
   1478                     }
   1479                 }
   1480                 3 => {
   1481                     if let Some(base64_fst) = Alphabet::from_ascii(rem[0])
   1482                         && let Some(base64_snd) = Alphabet::from_ascii(rem[1])
   1483                         && let Some(base64_third) = Alphabet::from_ascii(rem[2])
   1484                     {
   1485                         (snd, third) = (base64_snd.to_u8(), base64_third.to_u8());
   1486                         if third.trailing_zeros() < 2 {
   1487                             cold_path();
   1488                             return Err(DecodeErr::TrailingBits);
   1489                         } else {
   1490                             output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4);
   1491                             output[output_idx + 1] = (snd << 4) | (third >> 2);
   1492                         }
   1493                     } else {
   1494                         cold_path();
   1495                         return Err(DecodeErr::InvalidByte);
   1496                     }
   1497                 }
   1498                 _ => impossible!("there is a bug in core::slice::as_chunks"),
   1499             }
   1500             // SAFETY:
   1501             // `output.len() >= output_len`.
   1502             Ok(unsafe { output.split_at_mut_unchecked(output_len) }.0)
   1503         } else {
   1504             cold_path();
   1505             Err(DecodeErr::BufferLen)
   1506         }
   1507     } else {
   1508         Err(DecodeErr::EncodedLen)
   1509     }
   1510 }
   1511 /// Similar to [`decode_buffer`] except a `Vec` is returned instead using its buffer to write to.
   1512 ///
   1513 /// # Errors
   1514 ///
   1515 /// Errors iff [`decode_buffer`] errors or an error occurs from allocating the capacity needed to contain
   1516 /// the decoded data. Note [`DecodeErr::BufferLen`] is not possible to be returned.
   1517 ///
   1518 /// # Examples
   1519 ///
   1520 /// ```
   1521 /// # use base64url_nopad::DecodeErr;
   1522 /// assert_eq!(base64url_nopad::decode([0; 0].as_slice())?, b"");
   1523 /// assert_eq!(
   1524 ///     base64url_nopad::decode(b"A").unwrap_err(),
   1525 ///     DecodeErr::EncodedLen
   1526 /// );
   1527 /// assert_eq!(
   1528 ///     base64url_nopad::decode(b"AA==").unwrap_err(),
   1529 ///     DecodeErr::InvalidByte
   1530 /// );
   1531 /// assert_eq!(
   1532 ///     base64url_nopad::decode(b"-8").unwrap_err(),
   1533 ///     DecodeErr::TrailingBits
   1534 /// );
   1535 /// assert_eq!(base64url_nopad::decode(b"C8Aa_A--91VZbx0")?, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]);
   1536 /// # Ok::<_, DecodeErr>(())
   1537 /// ```
   1538 #[cfg(feature = "alloc")]
   1539 #[inline]
   1540 pub fn decode(input: &[u8]) -> Result<Vec<u8>, DecodeErr> {
   1541     decode_len(input.len())
   1542         .ok_or(DecodeErr::EncodedLen)
   1543         .and_then(|capacity| {
   1544             let mut buffer = Vec::new();
   1545             buffer
   1546                 .try_reserve_exact(capacity)
   1547                 .map_err(DecodeErr::TryReserve)
   1548                 .and_then(|()| {
   1549                     buffer.resize(capacity, 0);
   1550                     if let Err(e) = decode_buffer(input, buffer.as_mut_slice()) {
   1551                         Err(e)
   1552                     } else {
   1553                         Ok(buffer)
   1554                     }
   1555                 })
   1556         })
   1557 }
   1558 /// Similar to [`decode_buffer`] except the data is not decoded.
   1559 ///
   1560 /// In some situations, one does not want to actually decode data but merely validate that the encoded data
   1561 /// is valid base64url without padding. Since data is not actually decoded, one avoids the need to allocate
   1562 /// a large-enough buffer first.
   1563 ///
   1564 /// # Errors
   1565 ///
   1566 /// Errors iff `input` is an invalid base64url without padding.
   1567 ///
   1568 /// Note since no buffer is used to decode the data into, neither [`DecodeErr::BufferLen`] nor
   1569 /// [`DecodeErr::TryReserve`] will ever be returned.
   1570 ///
   1571 /// # Examples
   1572 ///
   1573 /// ```
   1574 /// # use base64url_nopad::DecodeErr;
   1575 /// base64url_nopad::validate_encoded_data(b"")?;
   1576 /// assert_eq!(
   1577 ///     base64url_nopad::validate_encoded_data(b"A").unwrap_err(),
   1578 ///     DecodeErr::EncodedLen
   1579 /// );
   1580 /// assert_eq!(
   1581 ///     base64url_nopad::validate_encoded_data(b"A/").unwrap_err(),
   1582 ///     DecodeErr::InvalidByte
   1583 /// );
   1584 /// assert_eq!(
   1585 ///     base64url_nopad::validate_encoded_data(b"-8").unwrap_err(),
   1586 ///     DecodeErr::TrailingBits
   1587 /// );
   1588 /// base64url_nopad::validate_encoded_data(b"C8Aa_A--91VZbx0")?;
   1589 /// # Ok::<_, DecodeErr>(())
   1590 /// ```
   1591 #[expect(
   1592     clippy::missing_asserts_for_indexing,
   1593     reason = "trust the compiler to already optimize since we match on the length"
   1594 )]
   1595 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   1596 #[inline]
   1597 pub const fn validate_encoded_data(input: &[u8]) -> Result<(), DecodeErr> {
   1598     let (mut chunks, rem) = input.as_chunks::<4>();
   1599     // There is a _substantial_ boost in performance if we chunk decode.
   1600     while let [first, ref rest @ ..] = *chunks {
   1601         if Alphabet::from_ascii(first[0]).is_some()
   1602             && Alphabet::from_ascii(first[1]).is_some()
   1603             && Alphabet::from_ascii(first[2]).is_some()
   1604             && Alphabet::from_ascii(first[3]).is_some()
   1605         {
   1606             chunks = rest;
   1607         } else {
   1608             return Err(DecodeErr::InvalidByte);
   1609         }
   1610     }
   1611     match rem.len() {
   1612         0 => Ok(()),
   1613         1 => {
   1614             cold_path();
   1615             Err(DecodeErr::EncodedLen)
   1616         }
   1617         2 => {
   1618             if Alphabet::from_ascii(rem[0]).is_some()
   1619                 && let Some(base64_snd) = Alphabet::from_ascii(rem[1])
   1620             {
   1621                 if base64_snd.to_u8().trailing_zeros() < 4 {
   1622                     cold_path();
   1623                     Err(DecodeErr::TrailingBits)
   1624                 } else {
   1625                     Ok(())
   1626                 }
   1627             } else {
   1628                 cold_path();
   1629                 Err(DecodeErr::InvalidByte)
   1630             }
   1631         }
   1632         3 => {
   1633             if Alphabet::from_ascii(rem[0]).is_some()
   1634                 && Alphabet::from_ascii(rem[1]).is_some()
   1635                 && let Some(base64_third) = Alphabet::from_ascii(rem[2])
   1636             {
   1637                 if base64_third.to_u8().trailing_zeros() < 2 {
   1638                     cold_path();
   1639                     Err(DecodeErr::TrailingBits)
   1640                 } else {
   1641                     Ok(())
   1642                 }
   1643             } else {
   1644                 cold_path();
   1645                 Err(DecodeErr::InvalidByte)
   1646             }
   1647         }
   1648         _ => impossible!("there is a bug in core::slice::as_chunks"),
   1649     }
   1650 }
   1651 /// Same as [`encode_buffer`] except `output` must have the _exact_ length needed to encode `input`, and the
   1652 /// encoded `str` is not returned.
   1653 ///
   1654 /// # Panics
   1655 ///
   1656 /// `panic`s iff `output` does not have the _exact_ length needed to encode `input`.
   1657 ///
   1658 /// # Examples
   1659 ///
   1660 /// ```
   1661 /// let mut buffer = [0; 256];
   1662 /// base64url_nopad::encode_buffer_exact([0; 0].as_slice(), &mut buffer[..0]);
   1663 /// base64url_nopad::encode_buffer_exact([0; 1].as_slice(), &mut buffer[..2]);
   1664 /// assert_eq!(*b"AA", buffer[..2]);
   1665 /// // Uncommenting below will cause a `panic` since the output buffer must be exact.
   1666 /// // base64url_nopad::encode_buffer_exact([255; 1].as_slice(), &mut buffer);
   1667 /// ```
   1668 #[inline]
   1669 pub const fn encode_buffer_exact(input: &[u8], output: &mut [u8]) {
   1670     assert!(
   1671         // `encode_len` won't `panic` since Rust guarantees `input.len()` is at most `isize::MAX`.
   1672         output.len() == encode_len(input.len()),
   1673         "encode_buffer_exact must be passed an output buffer whose length is exactly the length needed to encode the data"
   1674     );
   1675     _ = encode_buffer(input, output);
   1676 }
   1677 /// Same as [`decode_buffer`] except `output` must have the _exact_ length needed, and the decoded `slice`
   1678 /// is not returned.
   1679 ///
   1680 /// # Errors
   1681 ///
   1682 /// Errors iff [`decode_buffer`] errors. Note that since a `panic` occurs when `output.len()` is not the
   1683 /// exact length needed, [`DecodeErr::BufferLen`] is not possible in addition to [`DecodeErr::TryReserve`].
   1684 ///
   1685 /// # Panics
   1686 ///
   1687 /// `panic`s iff `output` does not have the _exact_ length needed to contain the decoded data and `input`
   1688 /// has a valid length (i.e., [`DecodeErr::EncodedLen`] is returned _not_ a `panic` when `input` has invalid
   1689 /// length).
   1690 ///
   1691 /// # Examples
   1692 ///
   1693 /// ```
   1694 /// # use base64url_nopad::DecodeErr;
   1695 /// assert_eq!(
   1696 ///     base64url_nopad::decode_buffer_exact(b"A", [0; 0].as_mut_slice()).unwrap_err(),
   1697 ///     DecodeErr::EncodedLen
   1698 /// );
   1699 /// assert_eq!(
   1700 ///     base64url_nopad::decode_buffer_exact(b"+A", [0; 1].as_mut_slice()).unwrap_err(),
   1701 ///     DecodeErr::InvalidByte
   1702 /// );
   1703 /// assert_eq!(
   1704 ///     base64url_nopad::decode_buffer_exact(b"-8", [0; 1].as_mut_slice()).unwrap_err(),
   1705 ///     DecodeErr::TrailingBits
   1706 /// );
   1707 /// let mut buffer = [0; base64url_nopad::decode_len(b"C8Aa_A--91VZbx0".len()).unwrap()];
   1708 /// base64url_nopad::decode_buffer_exact(b"C8Aa_A--91VZbx0", &mut buffer)?;
   1709 /// assert_eq!(buffer, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]);
   1710 /// // Uncommenting below will cause a `panic` since a larger output buffer than necessary is _not_ OK.
   1711 /// // base64url_nopad::decode_buffer_exact(b"C8Aa_A--91VZbx0", &mut [0; 128])?;
   1712 /// # Ok::<_, DecodeErr>(())
   1713 /// ```
   1714 #[expect(
   1715     clippy::panic,
   1716     clippy::panic_in_result_fn,
   1717     reason = "purpose of this function is to panic when output does not have the exact length needed"
   1718 )]
   1719 #[inline]
   1720 pub const fn decode_buffer_exact(input: &[u8], output: &mut [u8]) -> Result<(), DecodeErr> {
   1721     let output_len = output.len();
   1722     match decode_buffer(input, output) {
   1723         Ok(v) => {
   1724             assert!(
   1725                 v.len() == output_len,
   1726                 "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data"
   1727             );
   1728             Ok(())
   1729         }
   1730         Err(e) => {
   1731             if matches!(e, DecodeErr::BufferLen) {
   1732                 cold_path();
   1733                 panic!(
   1734                     "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data"
   1735                 );
   1736             } else {
   1737                 Err(e)
   1738             }
   1739         }
   1740     }
   1741 }