base64_pad

base64 with padding library.
git clone https://git.philomathiclife.com/repos/base64_pad
Log | Files | Refs | README

lib.rs (62939B)


      1 //! [![git]](https://git.philomathiclife.com/base64_pad/log.html) [![crates-io]](https://crates.io/crates/base64_pad) [![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 //! `base64_pad` is a library for fast, efficient, correct, and `const` encoding and decoding of base64
      8 //! with 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 //! ## `base64_pad` in action
     13 //!
     14 //! ```
     15 //! # use base64_pad::DecodeErr;
     16 //! /// Length of our input to encode.
     17 //! const INPUT_LEN: usize = 259;
     18 //! /// The base64-encoded value with 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; base64_pad::encode_len(INPUT_LEN)];
     28 //! assert_eq!(base64_pad::encode_buffer(&input, &mut output), ENCODED_VAL);
     29 //! assert_eq!(base64_pad::decode_len(output.len()), Some(INPUT_LEN + 2));
     30 //! base64_pad::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 base64 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     Plus,
    214     /// /.
    215     Slash,
    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     Some(Alphabet::Plus),
    272     None,
    273     None,
    274     None,
    275     Some(Alphabet::Slash),
    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     None,
    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 base64_pad::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::Slash`.
    509         if b <= Self::Slash 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 base64_pad::Alphabet;
    523     /// assert_eq!(Alphabet::Plus.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 base64_pad::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 base64_pad::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 base64_pad::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 base64_pad::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 /// Returns the exact number of bytes needed to encode an input of length `input_length` _without_ padding.
    674 ///
    675 /// Calling code must ensure `input_length <= MAX_ENCODE_INPUT_LEN`.
    676 #[expect(
    677     clippy::arithmetic_side_effects,
    678     clippy::integer_division,
    679     clippy::integer_division_remainder_used,
    680     reason = "proof and comment justifies their correctness"
    681 )]
    682 const fn encode_len_internal(input_length: usize) -> usize {
    683     // 256^n is the number of distinct values of the input. Let the base64 encoding with padding of the input be O.
    684     // There are 64 possible values each byte in O can be; thus we must find
    685     // the minimum nonnegative integer m such that:
    686     // 64^m = (2^6)^m = 2^(6m) >= 256^n = (2^8)^n = 2^(8n)
    687     // <==>
    688     // lg(2^(6m)) = 6m >= lg(2^(8n)) = 8n   lg is defined on all positive reals which 2^(6m) and 2^(8n) are
    689     // <==>
    690     // m >= 8n/6 = 4n/3
    691     // Clearly that corresponds to m = ⌈4n/3⌉.
    692     // We claim ⌈4n/3⌉ = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉.
    693     // Proof:
    694     // There are three partitions for n:
    695     // (1) 3i = n ≡ 0 (mod 3) for some integer i
    696     //   <==>
    697     //   ⌈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⌉
    698     // (2) 3i + 1 = n ≡ 1 (mod 3) for some integer i
    699     //   <==>
    700     //   ⌈4n/3⌉ = ⌈4(3i + 1)/3⌉ = ⌈4i + 4/3⌉ = 4i + ⌈4/3⌉ = 4i + 2 = 4⌊i + 1/3⌋ + ⌈4(1)/3⌉
    701     //                                                             = 4⌊(3i + 1)/3⌋ + ⌈4((3i + 1) mod 3)/3⌉
    702     //                                                             = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉
    703     // (3) 3i + 2 = n ≡ 2 (mod 3) for some integer i
    704     //   <==>
    705     //   ⌈4n/3⌉ = ⌈4(3i + 2)/3⌉ = ⌈4i + 8/3⌉ = 4i + ⌈8/3⌉ = 4i + 3 = 4⌊i + 2/3⌋ + ⌈4(2)/3⌉
    706     //                                                             = 4⌊(3i + 2)/3⌋ + ⌈4((3i + 2) mod 3)/3⌉
    707     //                                                             = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉
    708     // QED
    709     // Proof of no overflow:
    710     // `MAX_ENCODE_INPUT_LEN` = decode_len(usize::MAX).unwrap(); thus all values less than or equal to
    711     // `MAX_ENCODE_INPUT_LEN` won't overflow ignoring intermediate calcuations since ⌈4n/3⌉ is a
    712     // monotonically increasing function.
    713     // QED
    714     // Naively implementing ⌈4n/3⌉ as (4 * n).div_ceil(3) can cause overflow due to `4 * n`; thus
    715     // we implement the equivalent equation 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ instead:
    716     // `(4 * (n / 3)) + (4 * (n % 3)).div_ceil(3)` since none of the intermediate calculations suffer
    717     // from overflow.
    718     // (n / 3) << 2 <= m <= usize::MAX; thus the left operand of + is fine.
    719     // n % 3 <= 2
    720     // <==>
    721     // 4(n % 3) <= 8 < usize::MAX; thus (n % 3) << 2 is fine.
    722     // <==>
    723     // ⌈4(n % 3)/3⌉ <= 4(n % 3), so the right operand of + is fine.
    724     // The sum is fine since
    725     // m = ⌈4n/3⌉ = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ = ((n / 3) << 2) + ((n % 3) << 2).div_ceil(3)
    726     // m.next_multiple_of(4) <= usize::MAX since calling code must ensure
    727     // `input_length <= MAX_ENCODE_INPUT_LEN`.
    728     ((input_length / 3) << 2) + ((input_length % 3) << 2).div_ceil(3)
    729 }
    730 /// The maximum value [`encode_len_checked`] will accept before returning `None`.
    731 // This won't `panic` since `usize::MAX - 3` ≡ 0 (mod 4).
    732 pub const MAX_ENCODE_INPUT_LEN: usize = decode_len(usize::MAX - 3).unwrap();
    733 /// Returns the exact number of bytes needed to encode an input of length `input_length`.
    734 ///
    735 /// `Some` is returned iff the length needed does not exceed [`usize::MAX`].
    736 ///
    737 /// Note since Rust guarantees all memory allocations don't exceed [`isize::MAX`] bytes, then one can
    738 /// instead call [`encode_len`] when the argument passed corresponds to the length of an allocation since
    739 /// `isize::MAX <` [`MAX_ENCODE_INPUT_LEN`].
    740 ///
    741 /// # Examples
    742 ///
    743 /// ```
    744 /// # use base64_pad::MAX_ENCODE_INPUT_LEN;
    745 /// assert!(base64_pad::encode_len_checked(usize::MAX).is_none());
    746 /// assert!(base64_pad::encode_len_checked(MAX_ENCODE_INPUT_LEN + 1).is_none());
    747 /// assert_eq!(base64_pad::encode_len_checked(MAX_ENCODE_INPUT_LEN), Some(usize::MAX - 3));
    748 /// assert_eq!(base64_pad::encode_len_checked(3), Some(4));
    749 /// assert_eq!(base64_pad::encode_len_checked(2), Some(4));
    750 /// assert_eq!(base64_pad::encode_len_checked(1), Some(4));
    751 /// assert_eq!(base64_pad::encode_len_checked(0), Some(0));
    752 /// ```
    753 #[inline]
    754 #[must_use]
    755 pub const fn encode_len_checked(input_length: usize) -> Option<usize> {
    756     if input_length <= MAX_ENCODE_INPUT_LEN {
    757         // This won't overflow since `input_length <= MAX_ENCODE_INPUT_LEN`.
    758         Some(encode_len_internal(input_length).next_multiple_of(4))
    759     } else {
    760         cold_path();
    761         None
    762     }
    763 }
    764 /// Same as [`encode_len_checked`] except a `panic` occurs instead of `None` being returned.
    765 ///
    766 /// One should prefer this function over `encode_len_checked` when passing the length of a memory allocation
    767 /// since such a length is guaranteed to succeed.
    768 ///
    769 /// # Panics
    770 ///
    771 /// `panic`s iff [`encode_len_checked`] returns `None`.
    772 ///
    773 /// # Examples
    774 ///
    775 /// ```
    776 /// # use base64_pad::MAX_ENCODE_INPUT_LEN;
    777 /// // Uncommenting below will cause a `panic`.
    778 /// // base64_pad::encode_len(usize::MAX - 4);
    779 /// // Uncommenting below will cause a `panic`.
    780 /// // base64_pad::encode_len(MAX_ENCODE_INPUT_LEN + 1);
    781 /// assert_eq!(base64_pad::encode_len(MAX_ENCODE_INPUT_LEN), usize::MAX - 3);
    782 /// assert!(base64_pad::encode_len(isize::MAX as usize) > isize::MAX as usize);
    783 /// assert_eq!(base64_pad::encode_len(3), 4);
    784 /// assert_eq!(base64_pad::encode_len(2), 4);
    785 /// assert_eq!(base64_pad::encode_len(1), 4);
    786 /// assert_eq!(base64_pad::encode_len(0), 0);
    787 /// ```
    788 #[expect(clippy::unwrap_used, reason = "comment justifies correctness")]
    789 #[inline]
    790 #[must_use]
    791 pub const fn encode_len(input_length: usize) -> usize {
    792     // A precondition for calling this function is to ensure `encode_len_checked` can't return `None`.
    793     encode_len_checked(input_length).unwrap()
    794 }
    795 /// `const`-version of `unreachable`.
    796 macro_rules! impossible {
    797     ( $( $x:literal)? ) => {
    798         {
    799             cold_path();
    800             $(
    801                 panic!($x);
    802             )?
    803         }
    804     };
    805 }
    806 /// Encodes `input` _without padding_ into `output` re-interpreting the encoded subset of `output` as a `str`
    807 /// before returning it.
    808 ///
    809 /// # Panics
    810 ///
    811 /// `panic`s iff `output` is not large enough to contained the encoded data.
    812 #[expect(unsafe_code, reason = "comments justify correctness")]
    813 #[expect(
    814     clippy::missing_asserts_for_indexing,
    815     reason = "trust the compiler to already optimize since we match on the length"
    816 )]
    817 #[expect(
    818     clippy::arithmetic_side_effects,
    819     clippy::as_conversions,
    820     clippy::indexing_slicing,
    821     reason = "comments justify correctness"
    822 )]
    823 const fn encode_buffer_internal<'a>(input: &[u8], output: &'a mut [u8]) -> &'a mut str {
    824     let (mut chunks, rem) = input.as_chunks::<3>();
    825     let (mut fst, mut snd, mut third);
    826     let mut output_idx = 0;
    827     // There is a _substantial_ boost in performance if we chunk encode.
    828     while let [first, ref rest @ ..] = *chunks {
    829         (fst, snd, third) = (first[0], first[1], first[2]);
    830         // We trim the last two bits and interpret `fst` as a 6-bit integer.
    831         // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    832         // `ASCII.len() == 64 > fst >> 2u8`, so indexing won't `panic`.
    833         output[output_idx] = ASCII[(fst >> 2u8) as usize];
    834         // The two bits we trimmed are the first two bits of the next 6-bit integer.
    835         output_idx += 1;
    836         // We trim the last four bits and interpret `snd` as a 6-bit integer.
    837         // The first two bits are the trailing 2 bits from the previous value.
    838         // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    839         // `ASCII.len() == 64 > ((fst & 3) << 4u8) | (snd >> 4u8)`, so indexing won't `panic`.
    840         output[output_idx] = ASCII[(((fst & 3) << 4u8) | (snd >> 4u8)) as usize];
    841         output_idx += 1;
    842         // We trim the last six bits and interpret `third` as a 6-bit integer.
    843         // The first four bits are the trailing 4 bits from the previous value.
    844         // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    845         // `ASCII.len() == 64 > ((snd & 15) << 2u8) | (third >> 6u8)`, so indexing won't `panic`.
    846         output[output_idx] = ASCII[(((snd & 15) << 2u8) | (third >> 6u8)) as usize];
    847         // Every third `u8` corresponds to a fourth base64 `u8`.
    848         output_idx += 1;
    849         // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    850         // `ASCII.len() == 64 > (third & 63)`, so indexing won't `panic`.
    851         output[output_idx] = ASCII[(third & 63) as usize];
    852         output_idx += 1;
    853         chunks = rest;
    854     }
    855     match rem.len() {
    856         0 => {}
    857         1 => {
    858             // `rem.len() == 1`, so indexing won't `panic`.
    859             fst = rem[0];
    860             // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work.
    861             // `ASCII.len() == 64 > fst >> 2u8`, so indexing won't `panic`.
    862             output[output_idx] = ASCII[(fst >> 2u8) as usize];
    863             output_idx += 1;
    864             // `ASCII.len() == 64 > (fst & 3) << 4u8`, so indexing won't `panic`.
    865             output[output_idx] = ASCII[((fst & 3) << 4u8) as usize];
    866             output_idx += 1;
    867         }
    868         2 => {
    869             // `rem.len() == 2`, so indexing won't `panic`.
    870             (fst, snd) = (rem[0], rem[1]);
    871             // `input.len()` is not a multiple of 3; thus we have to append a final `u8` containing the
    872             // last bits.
    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 >> 2u8`, so indexing won't `panic`.
    875             output[output_idx] = ASCII[(fst >> 2u8) as usize];
    876             output_idx += 1;
    877             // `ASCII.len() == 64 > ((fst & 3) << 4u8) | (snd >> 4u8)`, so indexing won't `panic`.
    878             output[output_idx] = ASCII[(((fst & 3) << 4u8) | (snd >> 4u8)) as usize];
    879             output_idx += 1;
    880             // `ASCII.len() == 64 > (snd & 15) << 2u8`, so indexing won't `panic`.
    881             output[output_idx] = ASCII[((snd & 15) << 2u8) as usize];
    882             output_idx += 1;
    883         }
    884         _ => impossible!("there is a bug in core::slice::as_chunks"),
    885     }
    886     // SAFETY:
    887     // `output_idx <= output.len()`.
    888     let val = unsafe { output.split_at_mut_unchecked(output_idx) }.0;
    889     // SAFETY:
    890     // `val` has the exact length needed to encode `input`, and all of the `u8`s in it
    891     // are from `Alphabet::to_ascii` which is a subset of UTF-8; thus this is safe.
    892     // Note the above is vacuously true when `val` is empty.
    893     unsafe { str::from_utf8_unchecked_mut(val) }
    894 }
    895 /// Encodes `input` into `output` re-interpreting the encoded subset of `output` as a `str` before returning it.
    896 ///
    897 /// `Some` is returned iff `output.len()` is large enough to write the encoded data into.
    898 ///
    899 /// Note since Rust guarantees all memory allocations don't exceed [`isize::MAX`] bytes, one can
    900 /// instead call [`encode_buffer`] using a buffer whose length is at least as large as the value returned from
    901 /// [`encode_len`] without fear of a `panic` and the benefit of getting a `str` instead of an `Option`.
    902 ///
    903 /// # Examples
    904 ///
    905 /// ```
    906 /// assert_eq!(
    907 ///     base64_pad::encode_buffer_checked([0; 0].as_slice(), [0; 0].as_mut_slice()).as_deref(), Some("")
    908 /// );
    909 /// assert_eq!(
    910 ///     base64_pad::encode_buffer_checked([0; 1].as_slice(), [0; 4].as_mut_slice()).as_deref(), Some("AA==")
    911 /// );
    912 /// // A larger output buffer than necessary is OK.
    913 /// assert_eq!(
    914 ///     base64_pad::encode_buffer_checked([1; 1].as_slice(), [0; 128].as_mut_slice()).as_deref(), Some("AQ==")
    915 /// );
    916 /// assert_eq!(
    917 ///     base64_pad::encode_buffer_checked(
    918 ///         [0xc9; 14].as_slice(),
    919 ///         [0; base64_pad::encode_len(14)].as_mut_slice()
    920 ///     ).as_deref(),
    921 ///     Some("ycnJycnJycnJycnJyck=")
    922 /// );
    923 /// assert!(base64_pad::encode_buffer_checked([0; 1].as_slice(), [0; 3].as_mut_slice()).is_none());
    924 /// ```
    925 #[expect(unsafe_code, reason = "comments justify correctness")]
    926 #[expect(
    927     clippy::arithmetic_side_effects,
    928     clippy::indexing_slicing,
    929     reason = "comments justify correctness"
    930 )]
    931 #[inline]
    932 pub const fn encode_buffer_checked<'a>(input: &[u8], output: &'a mut [u8]) -> Option<&'a mut str> {
    933     // This won't `panic` since Rust guarantees that all memory allocations won't exceed `isize::MAX`.
    934     let final_len = encode_len(input.len());
    935     if output.len() >= final_len {
    936         match encode_buffer_internal(input, output).len() & 3 {
    937             0 => {}
    938             1 => impossible!("there is a bug in base64_pad::encode_buffer_internal"),
    939             2 => {
    940                 // The encoded data must be padded with two `b'='`; so underflow can't happen nor will
    941                 // indexing `panic`.
    942                 output[final_len - 1] = b'=';
    943                 output[final_len - 2] = b'=';
    944             }
    945             3 => {
    946                 // The encoded data must be padded with one `b'='`; so underflow can't happen nor will
    947                 // indexing `panic`.
    948                 output[final_len - 1] = b'=';
    949             }
    950             _ => impossible!("usize & 3 is greater than 3"),
    951         }
    952         // SAFETY:
    953         // We verified `output.len() >= final_len`.
    954         let val = unsafe { output.split_at_mut_unchecked(final_len) }.0;
    955         // SAFETY:
    956         // `val` has the exact length needed to encode `input`, and all of the `u8`s in it
    957         // are from `Alphabet::to_ascii` which is a subset of UTF-8; thus this is safe.
    958         // Note the above is vacuously true when `val` is empty.
    959         Some(unsafe { str::from_utf8_unchecked_mut(val) })
    960     } else {
    961         cold_path();
    962         None
    963     }
    964 }
    965 /// Same as [`encode_buffer_checked`] except a `panic` occurs instead of `None` being returned.
    966 ///
    967 /// # Panics
    968 ///
    969 /// `panic`s iff [`encode_buffer_checked`] returns `None` (i.e., the length of the output buffer is too small).
    970 ///
    971 /// # Examples
    972 ///
    973 /// ```
    974 /// assert_eq!(
    975 ///     base64_pad::encode_buffer([0; 0].as_slice(), [0; 0].as_mut_slice()),
    976 ///     ""
    977 /// );
    978 /// assert_eq!(
    979 ///     base64_pad::encode_buffer([0; 1].as_slice(), [0; 4].as_mut_slice()),
    980 ///     "AA=="
    981 /// );
    982 /// // A larger output buffer than necessary is OK.
    983 /// assert_eq!(
    984 ///     base64_pad::encode_buffer([255; 1].as_slice(), [0; 256].as_mut_slice()),
    985 ///     "/w=="
    986 /// );
    987 /// assert_eq!(
    988 ///     base64_pad::encode_buffer(
    989 ///         [0xc9; 14].as_slice(),
    990 ///         [0; base64_pad::encode_len(14)].as_mut_slice()
    991 ///     ),
    992 ///     "ycnJycnJycnJycnJyck="
    993 /// );
    994 /// // The below will `panic` when uncommented since the supplied output buffer is too small.
    995 /// // _ = base64_pad::encode_buffer([0; 1].as_slice(), [0; 3].as_mut_slice());
    996 /// ```
    997 #[expect(clippy::unwrap_used, reason = "comment justifies correctness")]
    998 #[inline]
    999 pub const fn encode_buffer<'a>(input: &[u8], output: &'a mut [u8]) -> &'a mut str {
   1000     // A precondition for calling this function is to ensure `encode_buffer_checked` can't return `None`.
   1001     encode_buffer_checked(input, output).unwrap()
   1002 }
   1003 /// Similar to [`encode_buffer`] except a `String` is returned instead using its buffer to write to.
   1004 ///
   1005 /// # Errors
   1006 ///
   1007 /// Errors iff an error occurs from allocating the capacity needed to contain the encoded data.
   1008 ///
   1009 /// # Examples
   1010 ///
   1011 /// ```
   1012 /// # extern crate alloc;
   1013 /// # use alloc::collections::TryReserveError;
   1014 /// assert_eq!(
   1015 ///     base64_pad::try_encode([0; 0].as_slice())?,
   1016 ///     ""
   1017 /// );
   1018 /// assert_eq!(
   1019 ///     base64_pad::try_encode([0; 1].as_slice())?,
   1020 ///     "AA=="
   1021 /// );
   1022 /// assert_eq!(
   1023 ///     base64_pad::try_encode([128, 40, 3].as_slice())?,
   1024 ///     "gCgD"
   1025 /// );
   1026 /// assert_eq!(
   1027 ///     base64_pad::try_encode([0x7b; 22].as_slice())?,
   1028 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew=="
   1029 /// );
   1030 /// # Ok::<_, TryReserveError>(())
   1031 /// ```
   1032 #[cfg(feature = "alloc")]
   1033 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1034 #[inline]
   1035 pub fn try_encode(input: &[u8]) -> Result<String, TryReserveError> {
   1036     let mut output = Vec::new();
   1037     // `encode_len` won't `panic` since Rust guarantees `input.len()` will not return a value greater
   1038     // than `isize::MAX`.
   1039     let len = encode_len(input.len());
   1040     output.try_reserve_exact(len).map(|()| {
   1041         output.resize(len, 0);
   1042         _ = encode_buffer(input, output.as_mut_slice());
   1043         // SAFETY:
   1044         // `output` has the exact length needed to encode `input`, and all of the `u8`s in it
   1045         // are from `Alphabet` which is a subset of UTF-8; thus this is safe.
   1046         // Note the above is vacuously true when `output` is empty.
   1047         unsafe { String::from_utf8_unchecked(output) }
   1048     })
   1049 }
   1050 /// Same as [`try_encode`] except a `panic` occurs on allocation failure.
   1051 ///
   1052 /// # Panics
   1053 ///
   1054 /// `panic`s iff [`try_encode`] errors.
   1055 ///
   1056 /// # Examples
   1057 ///
   1058 /// ```
   1059 /// assert_eq!(
   1060 ///     base64_pad::encode([0; 0].as_slice()),
   1061 ///     ""
   1062 /// );
   1063 /// assert_eq!(
   1064 ///     base64_pad::encode([0; 1].as_slice()),
   1065 ///     "AA=="
   1066 /// );
   1067 /// assert_eq!(
   1068 ///     base64_pad::encode([128, 40, 3].as_slice()),
   1069 ///     "gCgD"
   1070 /// );
   1071 /// assert_eq!(
   1072 ///     base64_pad::encode([0x7b; 22].as_slice()),
   1073 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew=="
   1074 /// );
   1075 /// ```
   1076 #[cfg(feature = "alloc")]
   1077 #[expect(
   1078     clippy::unwrap_used,
   1079     reason = "purpose of function is to panic on allocation failure"
   1080 )]
   1081 #[inline]
   1082 #[must_use]
   1083 pub fn encode(input: &[u8]) -> String {
   1084     try_encode(input).unwrap()
   1085 }
   1086 /// Writes the base64 encoding of `input` using `writer`.
   1087 ///
   1088 /// Internally a buffer of at most 1024 bytes is used to write the encoded data. Smaller buffers may be used
   1089 /// for small inputs.
   1090 ///
   1091 /// # Errors
   1092 ///
   1093 /// Errors iff [`Write::write_str`] does.
   1094 ///
   1095 /// # Panics
   1096 ///
   1097 /// `panic`s iff [`Write::write_str`] does.
   1098 ///
   1099 /// # Examples
   1100 ///
   1101 /// ```
   1102 /// # extern crate alloc;
   1103 /// # use alloc::string::String;
   1104 /// # use core::fmt::Error;
   1105 /// let mut buffer = String::new();
   1106 /// base64_pad::encode_write([0; 0].as_slice(), &mut buffer)?;
   1107 /// assert_eq!(buffer, "");
   1108 /// buffer.clear();
   1109 /// base64_pad::encode_write([0; 1].as_slice(), &mut buffer)?;
   1110 /// assert_eq!(buffer, "AA==");
   1111 /// buffer.clear();
   1112 /// base64_pad::encode_write(
   1113 ///     [0xc9; 14].as_slice(),
   1114 ///     &mut buffer,
   1115 /// )?;
   1116 /// assert_eq!(buffer, "ycnJycnJycnJycnJyck=");
   1117 /// # Ok::<_, Error>(())
   1118 /// ```
   1119 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1120 #[expect(
   1121     clippy::arithmetic_side_effects,
   1122     clippy::as_conversions,
   1123     clippy::indexing_slicing,
   1124     reason = "comments justify correctness"
   1125 )]
   1126 #[inline]
   1127 pub fn encode_write<W: Write>(mut input: &[u8], mut writer: W) -> fmt::Result {
   1128     /// The max buffer size.
   1129     ///
   1130     /// This must be at least 4, no more than `i16::MAX`, and must be a power of 2.
   1131     const MAX_BUFFER_LEN: usize = 1024;
   1132     /// Want to ensure at compilation time that `MAX_BUFFER_LEN` upholds its invariants. Namely
   1133     /// that it's at least as large as 4, doesn't exceed [`i16::MAX`], and is always a power of 2.
   1134     const _: () = {
   1135         // `i16::MAX <= usize::MAX`, so this is fine.
   1136         /// `i16::MAX`.
   1137         const MAX_LEN: usize = i16::MAX as usize;
   1138         assert!(
   1139             4 <= MAX_BUFFER_LEN && MAX_BUFFER_LEN < MAX_LEN && MAX_BUFFER_LEN.is_power_of_two(),
   1140             "encode_write::MAX_BUFFER_LEN must be a power of two less than i16::MAX but at least as large as 4"
   1141         );
   1142     };
   1143     /// The input size that corresponds to an encoded value of length `MAX_BUFFER_LEN`.
   1144     // This will never `panic` since `MAX_BUFFER_LEN` is a power of two at least as large as 4
   1145     // (i.e., `MAX_BUFFER_LEN` ≡ 0 (mod 4)).
   1146     const INPUT_LEN: usize = decode_len(MAX_BUFFER_LEN).unwrap();
   1147     let mut buffer = [0; MAX_BUFFER_LEN];
   1148     // This won't `panic` since `input.len()` is guaranteed to be no more than `isize::MAX` which is less than
   1149     // `MAX_ENCODE_INPUT_LEN`.
   1150     let no_pad_len = encode_len_internal(input.len());
   1151     // This won't overflow since `input.len()` is guaranteed to be no more than `isize::MAX` which is less than
   1152     // `MAX_ENCODE_INPUT_LEN`.
   1153     let len = no_pad_len.next_multiple_of(4);
   1154     if len <= MAX_BUFFER_LEN {
   1155         // `buffer.len() == MAX_BUFFER_LEN >= len`, so indexing is fine.
   1156         // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode
   1157         // the data.
   1158         writer.write_str(encode_buffer(input, &mut buffer[..len]))
   1159     } else {
   1160         let mut counter = 0;
   1161         // `no_pad_len / MAX_BUFFER_LEN` is equal to ⌊no_pad_len / MAX_BUFFER_LEN⌋ since `MAX_BUFFER_LEN` is a
   1162         // power of two. We can safely encode `term` chunks of `INPUT_LEN` length into `buffer`.
   1163         let term = no_pad_len >> MAX_BUFFER_LEN.trailing_zeros();
   1164         let mut input_buffer;
   1165         while counter < term {
   1166             // SAFETY:
   1167             // `input.len() >= INPUT_LEN`.
   1168             input_buffer = unsafe { input.split_at_unchecked(INPUT_LEN) };
   1169             // `encode_buffer_internal` won't `panic` since `buffer` has length `MAX_BUFFER_LEN` which
   1170             // is the exact length needed for `INPUT_LEN` length inputs which `input_buffer.0` is.
   1171             writer.write_str(encode_buffer_internal(
   1172                 input_buffer.0,
   1173                 buffer.as_mut_slice(),
   1174             ))?;
   1175             input = input_buffer.1;
   1176             // `counter < term`, so overflow cannot happen.
   1177             counter += 1;
   1178         }
   1179         // `encode_len` won't `panic` since `input.len() < MAX_ENCODE_INPUT_LEN`.
   1180         // `input.len() < INPUT_LEN`; thus `encode_len(input.len()) < MAX_BUFFER_LEN = buffer.len()` so
   1181         // indexing is fine.
   1182         // `encode_buffer` won't `panic` since the buffer is the exact length needed to encode `input`.
   1183         writer.write_str(encode_buffer(input, &mut buffer[..encode_len(input.len())]))
   1184     }
   1185 }
   1186 /// Appends the base64 encoding of `input` to `s` returning the `str` that was appended.
   1187 ///
   1188 /// # Errors
   1189 ///
   1190 /// Errors iff an error occurs from allocating the capacity needed to append the encoded data.
   1191 ///
   1192 /// # Examples
   1193 ///
   1194 /// ```
   1195 /// # extern crate alloc;
   1196 /// # use alloc::{collections::TryReserveError, string::String};
   1197 /// let mut buffer = String::new();
   1198 /// assert_eq!(
   1199 ///     base64_pad::try_encode_append([0; 0].as_slice(), &mut buffer)?,
   1200 ///     ""
   1201 /// );
   1202 /// assert_eq!(
   1203 ///     base64_pad::try_encode_append([0; 1].as_slice(), &mut buffer)?,
   1204 ///     "AA=="
   1205 /// );
   1206 /// assert_eq!(
   1207 ///     base64_pad::try_encode_append([128, 40, 3].as_slice(), &mut buffer)?,
   1208 ///     "gCgD"
   1209 /// );
   1210 /// assert_eq!(buffer, "AA==gCgD");
   1211 /// assert_eq!(
   1212 ///     base64_pad::try_encode_append([0x7b; 22].as_slice(), &mut buffer)?,
   1213 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew=="
   1214 /// );
   1215 /// assert_eq!(buffer, "AA==gCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew==");
   1216 /// # Ok::<_, TryReserveError>(())
   1217 /// ```
   1218 #[cfg(feature = "alloc")]
   1219 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1220 #[expect(
   1221     clippy::arithmetic_side_effects,
   1222     clippy::indexing_slicing,
   1223     reason = "comments justify correctness"
   1224 )]
   1225 #[inline]
   1226 pub fn try_encode_append<'a>(
   1227     input: &[u8],
   1228     s: &'a mut String,
   1229 ) -> Result<&'a mut str, TryReserveError> {
   1230     // `encode_len` won't `panic` since Rust guarantees `input.len()` will return a value no larger
   1231     // than `isize::MAX`.
   1232     let additional_len = encode_len(input.len());
   1233     s.try_reserve_exact(additional_len).map(|()| {
   1234         // SAFETY:
   1235         // We only append base64 ASCII which is a subset of UTF-8, so this will remain valid UTF-8.
   1236         let utf8 = unsafe { s.as_mut_vec() };
   1237         let original_len = utf8.len();
   1238         // Overflow can't happen; otherwise `s.try_reserve_exact` would have erred.
   1239         utf8.resize(original_len + additional_len, 0);
   1240         // `utf8.len() >= original_len`, so indexing is fine.
   1241         // `encode_buffer` won't `panic` since `utf8[original_len..]` has length `additional_len`
   1242         // which is the exact number of bytes needed to encode `input`.
   1243         encode_buffer(input, &mut utf8[original_len..])
   1244     })
   1245 }
   1246 /// Same as [`try_encode_append`] except the encoded `str` is not returned.
   1247 ///
   1248 /// # Errors
   1249 ///
   1250 /// Errors iff [`try_encode_append`] does.
   1251 ///
   1252 /// # Examples
   1253 ///
   1254 /// ```
   1255 /// # extern crate alloc;
   1256 /// # use alloc::{collections::TryReserveError, string::String};
   1257 /// let mut buffer = String::new();
   1258 /// base64_pad::try_encode_append_only([0; 0].as_slice(), &mut buffer)?;
   1259 /// assert_eq!(buffer, "");
   1260 /// base64_pad::try_encode_append_only([0; 1].as_slice(), &mut buffer)?;
   1261 /// assert_eq!(buffer, "AA==");
   1262 /// base64_pad::try_encode_append_only([128, 40, 3].as_slice(), &mut buffer)?;
   1263 /// assert_eq!(buffer, "AA==gCgD");
   1264 /// base64_pad::try_encode_append_only([0x7b; 22].as_slice(), &mut buffer)?;
   1265 /// assert_eq!(buffer, "AA==gCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew==");
   1266 /// # Ok::<_, TryReserveError>(())
   1267 /// ```
   1268 #[cfg(feature = "alloc")]
   1269 #[inline]
   1270 pub fn try_encode_append_only(input: &[u8], s: &mut String) -> Result<(), TryReserveError> {
   1271     try_encode_append(input, s).map(|_| ())
   1272 }
   1273 /// Same as [`try_encode_append`] except a `panic` occurs on allocation failure.
   1274 ///
   1275 /// # Panics
   1276 ///
   1277 /// `panic`s iff [`try_encode_append`] errors.
   1278 ///
   1279 /// # Examples
   1280 ///
   1281 /// ```
   1282 /// # extern crate alloc;
   1283 /// # use alloc::{collections::TryReserveError, string::String};
   1284 /// let mut buffer = String::new();
   1285 /// assert_eq!(
   1286 ///     base64_pad::encode_append([0; 0].as_slice(), &mut buffer),
   1287 ///     ""
   1288 /// );
   1289 /// assert_eq!(
   1290 ///     base64_pad::encode_append([0; 1].as_slice(), &mut buffer),
   1291 ///     "AA=="
   1292 /// );
   1293 /// assert_eq!(
   1294 ///     base64_pad::encode_append([128, 40, 3].as_slice(), &mut buffer),
   1295 ///     "gCgD"
   1296 /// );
   1297 /// assert_eq!(buffer, "AA==gCgD");
   1298 /// assert_eq!(
   1299 ///     base64_pad::encode_append([0x7b; 22].as_slice(), &mut buffer),
   1300 ///     "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew=="
   1301 /// );
   1302 /// assert_eq!(buffer, "AA==gCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew==");
   1303 /// ```
   1304 #[cfg(feature = "alloc")]
   1305 #[expect(
   1306     clippy::unwrap_used,
   1307     reason = "purpose of this function is to panic on allocation failure"
   1308 )]
   1309 #[inline]
   1310 pub fn encode_append<'a>(input: &[u8], s: &'a mut String) -> &'a mut str {
   1311     try_encode_append(input, s).unwrap()
   1312 }
   1313 /// Same as [`encode_append`] except the encoded `str` is not returned.
   1314 ///
   1315 /// # Panics
   1316 ///
   1317 /// `panic`s iff [`encode_append`] does.
   1318 ///
   1319 /// # Examples
   1320 ///
   1321 /// ```
   1322 /// # extern crate alloc;
   1323 /// # use alloc::{collections::TryReserveError, string::String};
   1324 /// let mut buffer = String::new();
   1325 /// base64_pad::encode_append_only([0; 0].as_slice(), &mut buffer);
   1326 /// assert_eq!(buffer, "");
   1327 /// base64_pad::encode_append_only([0; 1].as_slice(), &mut buffer);
   1328 /// assert_eq!(buffer, "AA==");
   1329 /// base64_pad::encode_append_only([128, 40, 3].as_slice(), &mut buffer);
   1330 /// assert_eq!(buffer, "AA==gCgD");
   1331 /// base64_pad::encode_append_only([0x7b; 22].as_slice(), &mut buffer);
   1332 /// assert_eq!(buffer, "AA==gCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew==");
   1333 /// # Ok::<_, TryReserveError>(())
   1334 /// ```
   1335 #[cfg(feature = "alloc")]
   1336 #[inline]
   1337 pub fn encode_append_only(input: &[u8], s: &mut String) {
   1338     _ = encode_append(input, s);
   1339 }
   1340 /// Returns the maximum number of bytes an encoded value of length `input_length` corresponds to when decoded.
   1341 ///
   1342 /// `Some` is returned iff `input_length` represents a possible length of a base64 with padding input.
   1343 ///
   1344 /// Note due to padding, this may return a value that is too large. When used to calculate the length needed
   1345 /// for `output` in [`decode_buffer_exact`], one must subtract the number of trailing `b'='` from the returned
   1346 /// value keeping in mind that an encoded value can have at most two trailing `b'='`.
   1347 ///
   1348 /// # Examples
   1349 ///
   1350 /// ```
   1351 /// # use base64_pad::MAX_ENCODE_INPUT_LEN;
   1352 /// assert!(base64_pad::decode_len(1).is_none());
   1353 /// assert!(base64_pad::decode_len(2).is_none());
   1354 /// assert!(base64_pad::decode_len(3).is_none());
   1355 /// assert!(base64_pad::decode_len(usize::MAX).is_none());
   1356 /// assert!(base64_pad::decode_len(usize::MAX - 1).is_none());
   1357 /// assert!(base64_pad::decode_len(usize::MAX - 2).is_none());
   1358 /// assert_eq!(base64_pad::decode_len(usize::MAX - 3), Some(MAX_ENCODE_INPUT_LEN));
   1359 /// assert_eq!(base64_pad::decode_len(4), Some(3));
   1360 /// assert_eq!(base64_pad::decode_len(0), Some(0));
   1361 /// ```
   1362 #[expect(
   1363     clippy::arithmetic_side_effects,
   1364     reason = "proof and comment justifies their correctness"
   1365 )]
   1366 #[inline]
   1367 #[must_use]
   1368 pub const fn decode_len(input_length: usize) -> Option<usize> {
   1369     // Encoded-values always have a length that is a multiple of 4; thus per `encoded_len_checked`, the decoded
   1370     // length is 3/4 of it.
   1371     if input_length.trailing_zeros() > 1 {
   1372         // We divide by 4 before multiplying by 3; thus this can't overflow.
   1373         Some(3 * (input_length >> 2))
   1374     } else {
   1375         None
   1376     }
   1377 }
   1378 /// Returns the exact number of bytes needed to decode a base64 input _without_ padding of length `input_length`.
   1379 #[expect(
   1380     clippy::arithmetic_side_effects,
   1381     reason = "proof and comment justifies their correctness"
   1382 )]
   1383 const fn decode_no_pad_len(input_length: usize) -> usize {
   1384     // 64^n is the number of distinct values of the input. Let the decoded output be O.
   1385     // There are 256 possible values each byte in O can be; thus we must find
   1386     // the maximum nonnegative integer m such that:
   1387     // 256^m = (2^8)^m = 2^(8m) <= 64^n = (2^6)^n = 2^(6n)
   1388     // <==>
   1389     // lg(2^(8m)) = 8m <= lg(2^(6n)) = 6n   lg is defined on all positive reals which 2^(8m) and 2^(6n) are
   1390     // <==>
   1391     // m <= 6n/8 = 3n/4
   1392     // Clearly that corresponds to m = ⌊3n/4⌋.
   1393     // From the proof in `encode_len_checked`, we know that n is a valid length
   1394     // iff n ≢ 1 (mod 4).
   1395     // We claim ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋.
   1396     // Proof:
   1397     // There are three partitions for n:
   1398     // (1) 4i = n ≡ 0 (mod 4) for some integer i
   1399     //   <==>
   1400     //   ⌊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⌋
   1401     // (2) 4i + 2 = n ≡ 2 (mod 4) for some integer i
   1402     //   <==>
   1403     //   ⌊3n/4⌋ = ⌊3(4i + 2)/4⌋ = ⌊3i + 6/4⌋ = 3i + ⌊6/4⌋ = 3i + 1 = 3⌊i⌋ + ⌊3(2)/4⌋
   1404     //                                                             = 3⌊(4i + 2)/4⌋ + ⌊3((4i + 2) mod 4)/4⌋
   1405     //                                                             = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋
   1406     // (3) 4i + 3 = n ≡ 3 (mod 4) for some integer i
   1407     //   <==>
   1408     //   ⌊3n/4⌋ = ⌊3(4i + 3)/4⌋ = ⌊3i + 9/4⌋ = 3i + ⌊9/4⌋ = 3i + 2 = 3⌊i⌋ + ⌊3(3)/4⌋
   1409     //                                                             = 3⌊(4i + 3)/4⌋ + ⌊3((4i + 3) mod 4)/4⌋
   1410     //                                                             = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋
   1411     // QED
   1412     // Naively implementing ⌊3n/4⌋ as (3 * n) / 4 can cause overflow due to `3 * n`; thus
   1413     // we implement the equivalent equation 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ instead:
   1414     // `(3 * (n / 4)) + ((3 * (n % 4)) / 4)` since none of the intermediate calculations suffer
   1415     // from overflow.
   1416     // `input_length % 4`.
   1417     // 3 * (n >> 2) <= m < usize::MAX; thus the left operand of + is fine.
   1418     // rem <= 3
   1419     // <==>
   1420     // 3rem <= 9 < usize::MAX; thus 3 * rem is fine.
   1421     // <==>
   1422     // ⌊3rem/4⌋ <= 3rem, so the right operand of + is fine.
   1423     // The sum is fine since
   1424     // m = ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ = (3 * (n >> 2)) + ((3 * rem) >> 2), and m < usize::MAX.
   1425     (3 * (input_length >> 2)) + ((3 * (input_length & 3)) >> 2)
   1426 }
   1427 /// Error returned from [`decode_buffer`] and [`decode`].
   1428 ///
   1429 /// Note when [`alloc`](./index.html#alloc) is not enabled, [`Copy`] is also implemented.
   1430 #[derive(Clone, Debug, Eq, PartialEq)]
   1431 pub enum DecodeErr {
   1432     /// The encoded input had an invalid length.
   1433     EncodedLen,
   1434     /// The buffer supplied had a length that was too small to contain the decoded data.
   1435     BufferLen,
   1436     /// The encoded data contained trailing bits that were not zero.
   1437     TrailingBits,
   1438     /// The encoded data contained an invalid `u8`.
   1439     InvalidByte,
   1440     /// [`decode`] could not allocate enough memory to contain the decoded data.
   1441     #[cfg(feature = "alloc")]
   1442     TryReserve(TryReserveError),
   1443 }
   1444 #[cfg(not(feature = "alloc"))]
   1445 impl Copy for DecodeErr {}
   1446 impl Display for DecodeErr {
   1447     #[inline]
   1448     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1449         match *self {
   1450             Self::EncodedLen => f.write_str("length of encoded data was invalid"),
   1451             Self::BufferLen => {
   1452                 f.write_str("length of the output buffer is too small to contain the decoded data")
   1453             }
   1454             Self::TrailingBits => {
   1455                 f.write_str("encoded data contained trailing bits that were not zero")
   1456             }
   1457             Self::InvalidByte => f.write_str("encoded data contained an invalid byte"),
   1458             #[cfg(feature = "alloc")]
   1459             Self::TryReserve(ref err) => err.fmt(f),
   1460         }
   1461     }
   1462 }
   1463 impl Error for DecodeErr {}
   1464 /// Trims padding from `input`.
   1465 ///
   1466 /// Returns `None` iff `input` does not have a valid length.
   1467 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1468 #[expect(
   1469     clippy::arithmetic_side_effects,
   1470     clippy::indexing_slicing,
   1471     reason = "comments justify correctness"
   1472 )]
   1473 const fn trim_padding(input: &[u8]) -> Option<&[u8]> {
   1474     let len = input.len();
   1475     if len.trailing_zeros() > 1 {
   1476         // `len - 1` won't underflow since `len > 0`, and `input[len - 1]` won't `panic` since that is the
   1477         // last index of `input`.
   1478         if len > 0 && input[len - 1] == b'=' {
   1479             // `len - 2` won't underflow since `len > 0` and is a multiple of 4, and `input[len - 2]` won't `panic`
   1480             // since `len` is the length of `input`.
   1481             let l = if input[len - 2] == b'=' {
   1482                 len - 2
   1483             } else {
   1484                 len - 1
   1485             };
   1486             // SAFETY:
   1487             // `input.len() >= l`.
   1488             Some(unsafe { input.split_at_unchecked(l) }.0)
   1489         } else {
   1490             Some(input)
   1491         }
   1492     } else {
   1493         None
   1494     }
   1495 }
   1496 /// Decodes `input` into `output` returning the subset of `output` containing the decoded data.
   1497 ///
   1498 /// # Errors
   1499 ///
   1500 /// Errors iff `output` is not large enough to contain the decoded data or `input` is an invalid base64-encoded
   1501 /// value with padding.
   1502 ///
   1503 /// Note [`DecodeErr::TryReserve`] will never be returned.
   1504 ///
   1505 /// # Examples
   1506 ///
   1507 /// ```
   1508 /// # use base64_pad::DecodeErr;
   1509 /// assert_eq!(base64_pad::decode_buffer(b"", [0; 0].as_mut_slice())?, b"");
   1510 /// assert_eq!(
   1511 ///     base64_pad::decode_buffer(b"A", [0; 1].as_mut_slice()).unwrap_err(),
   1512 ///     DecodeErr::EncodedLen
   1513 /// );
   1514 /// assert_eq!(
   1515 ///     base64_pad::decode_buffer(b"A-==".as_slice(), [0; 1].as_mut_slice()).unwrap_err(),
   1516 ///     DecodeErr::InvalidByte
   1517 /// );
   1518 /// assert_eq!(
   1519 ///     base64_pad::decode_buffer(b"AA==".as_slice(), [0; 0].as_mut_slice()).unwrap_err(),
   1520 ///     DecodeErr::BufferLen
   1521 /// );
   1522 /// assert_eq!(
   1523 ///     base64_pad::decode_buffer(b"+8==", [0; 1].as_mut_slice()).unwrap_err(),
   1524 ///     DecodeErr::TrailingBits
   1525 /// );
   1526 /// // A larger output buffer than necessary is OK.
   1527 /// assert_eq!(base64_pad::decode_buffer(b"C8Aa/A++91VZbx0=", &mut [0; 128])?, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]);
   1528 /// # Ok::<_, DecodeErr>(())
   1529 /// ```
   1530 #[expect(unsafe_code, reason = "comment justifies correctness")]
   1531 #[expect(
   1532     clippy::missing_asserts_for_indexing,
   1533     reason = "trust the compiler to already optimize since we match on the length"
   1534 )]
   1535 #[expect(
   1536     clippy::arithmetic_side_effects,
   1537     clippy::indexing_slicing,
   1538     reason = "comments justify correctness"
   1539 )]
   1540 #[expect(clippy::redundant_else, reason = "prefer the elses")]
   1541 #[inline]
   1542 pub const fn decode_buffer<'a>(
   1543     input: &[u8],
   1544     output: &'a mut [u8],
   1545 ) -> Result<&'a mut [u8], DecodeErr> {
   1546     if let Some(i) = trim_padding(input) {
   1547         let output_len = decode_no_pad_len(i.len());
   1548         if output.len() >= output_len {
   1549             let mut output_idx = 0;
   1550             let (mut chunks, rem) = i.as_chunks::<4>();
   1551             let (mut snd, mut third);
   1552             // There is a _substantial_ boost in performance if we chunk decode.
   1553             while let [first, ref rest @ ..] = *chunks {
   1554                 if let Some(base64_fst) = Alphabet::from_ascii(first[0])
   1555                     && let Some(base64_snd) = Alphabet::from_ascii(first[1])
   1556                     && let Some(base64_third) = Alphabet::from_ascii(first[2])
   1557                     && let Some(base64_fourth) = Alphabet::from_ascii(first[3])
   1558                 {
   1559                     (snd, third) = (base64_snd.to_u8(), base64_third.to_u8());
   1560                     output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4);
   1561                     output_idx += 1;
   1562                     output[output_idx] = (snd << 4) | (third >> 2);
   1563                     output_idx += 1;
   1564                     output[output_idx] = (third << 6) | base64_fourth.to_u8();
   1565                     output_idx += 1;
   1566                     chunks = rest;
   1567                 } else {
   1568                     return Err(DecodeErr::InvalidByte);
   1569                 }
   1570             }
   1571             match rem.len() {
   1572                 0 => {}
   1573                 1 => impossible!("there is a bug in base64_pad::trim_padding"),
   1574                 2 => {
   1575                     if let Some(base64_fst) = Alphabet::from_ascii(rem[0])
   1576                         && let Some(base64_snd) = Alphabet::from_ascii(rem[1])
   1577                     {
   1578                         snd = base64_snd.to_u8();
   1579                         if snd.trailing_zeros() < 4 {
   1580                             cold_path();
   1581                             return Err(DecodeErr::TrailingBits);
   1582                         } else {
   1583                             output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4);
   1584                         }
   1585                     } else {
   1586                         cold_path();
   1587                         return Err(DecodeErr::InvalidByte);
   1588                     }
   1589                 }
   1590                 3 => {
   1591                     if let Some(base64_fst) = Alphabet::from_ascii(rem[0])
   1592                         && let Some(base64_snd) = Alphabet::from_ascii(rem[1])
   1593                         && let Some(base64_third) = Alphabet::from_ascii(rem[2])
   1594                     {
   1595                         (snd, third) = (base64_snd.to_u8(), base64_third.to_u8());
   1596                         if third.trailing_zeros() < 2 {
   1597                             cold_path();
   1598                             return Err(DecodeErr::TrailingBits);
   1599                         } else {
   1600                             output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4);
   1601                             output[output_idx + 1] = (snd << 4) | (third >> 2);
   1602                         }
   1603                     } else {
   1604                         cold_path();
   1605                         return Err(DecodeErr::InvalidByte);
   1606                     }
   1607                 }
   1608                 _ => impossible!("there is a bug in core::slice::as_chunks"),
   1609             }
   1610             // SAFETY:
   1611             // `output.len() >= output_len`.
   1612             Ok(unsafe { output.split_at_mut_unchecked(output_len) }.0)
   1613         } else {
   1614             cold_path();
   1615             Err(DecodeErr::BufferLen)
   1616         }
   1617     } else {
   1618         Err(DecodeErr::EncodedLen)
   1619     }
   1620 }
   1621 /// Similar to [`decode_buffer`] except a `Vec` is returned instead using its buffer to write to.
   1622 ///
   1623 /// # Errors
   1624 ///
   1625 /// Errors iff [`decode_buffer`] errors or an error occurs from allocating the capacity needed to contain
   1626 /// the decoded data. Note [`DecodeErr::BufferLen`] is not possible to be returned.
   1627 ///
   1628 /// # Examples
   1629 ///
   1630 /// ```
   1631 /// # use base64_pad::DecodeErr;
   1632 /// assert_eq!(base64_pad::decode([0; 0].as_slice())?, b"");
   1633 /// assert_eq!(
   1634 ///     base64_pad::decode(b"A").unwrap_err(),
   1635 ///     DecodeErr::EncodedLen
   1636 /// );
   1637 /// assert_eq!(
   1638 ///     base64_pad::decode(b"A-==").unwrap_err(),
   1639 ///     DecodeErr::InvalidByte
   1640 /// );
   1641 /// assert_eq!(
   1642 ///     base64_pad::decode(b"+8==").unwrap_err(),
   1643 ///     DecodeErr::TrailingBits
   1644 /// );
   1645 /// assert_eq!(base64_pad::decode(b"C8Aa/A++91VZbx0=")?, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]);
   1646 /// # Ok::<_, DecodeErr>(())
   1647 /// ```
   1648 #[cfg(feature = "alloc")]
   1649 #[inline]
   1650 pub fn decode(input: &[u8]) -> Result<Vec<u8>, DecodeErr> {
   1651     trim_padding(input)
   1652         .ok_or(DecodeErr::EncodedLen)
   1653         .and_then(|i| {
   1654             let capacity = decode_no_pad_len(i.len());
   1655             let mut buffer = Vec::new();
   1656             buffer
   1657                 .try_reserve_exact(capacity)
   1658                 .map_err(DecodeErr::TryReserve)
   1659                 .and_then(|()| {
   1660                     buffer.resize(capacity, 0);
   1661                     if let Err(e) = decode_buffer(input, buffer.as_mut_slice()) {
   1662                         Err(e)
   1663                     } else {
   1664                         Ok(buffer)
   1665                     }
   1666                 })
   1667         })
   1668 }
   1669 /// Similar to [`decode_buffer`] except the data is not decoded.
   1670 ///
   1671 /// In some situations, one does not want to actually decode data but merely validate that the encoded data
   1672 /// is valid base64 with padding. Since data is not actually decoded, one avoids the need to allocate
   1673 /// a large-enough buffer first.
   1674 ///
   1675 /// # Errors
   1676 ///
   1677 /// Errors iff `input` is an invalid base64 with padding.
   1678 ///
   1679 /// Note since no buffer is used to decode the data into, neither [`DecodeErr::BufferLen`] nor
   1680 /// [`DecodeErr::TryReserve`] will ever be returned.
   1681 ///
   1682 /// # Examples
   1683 ///
   1684 /// ```
   1685 /// # use base64_pad::DecodeErr;
   1686 /// base64_pad::validate_encoded_data(b"")?;
   1687 /// assert_eq!(
   1688 ///     base64_pad::validate_encoded_data(b"A").unwrap_err(),
   1689 ///     DecodeErr::EncodedLen
   1690 /// );
   1691 /// assert_eq!(
   1692 ///     base64_pad::validate_encoded_data(b"A_==").unwrap_err(),
   1693 ///     DecodeErr::InvalidByte
   1694 /// );
   1695 /// assert_eq!(
   1696 ///     base64_pad::validate_encoded_data(b"+8==").unwrap_err(),
   1697 ///     DecodeErr::TrailingBits
   1698 /// );
   1699 /// base64_pad::validate_encoded_data(b"C8Aa/A++91VZbx0=")?;
   1700 /// # Ok::<_, DecodeErr>(())
   1701 /// ```
   1702 #[expect(
   1703     clippy::missing_asserts_for_indexing,
   1704     reason = "trust the compiler to already optimize since we match on the length"
   1705 )]
   1706 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")]
   1707 #[inline]
   1708 pub const fn validate_encoded_data(input: &[u8]) -> Result<(), DecodeErr> {
   1709     if let Some(i) = trim_padding(input) {
   1710         let (mut chunks, rem) = i.as_chunks::<4>();
   1711         // There is a _substantial_ boost in performance if we chunk decode.
   1712         while let [first, ref rest @ ..] = *chunks {
   1713             if Alphabet::from_ascii(first[0]).is_some()
   1714                 && Alphabet::from_ascii(first[1]).is_some()
   1715                 && Alphabet::from_ascii(first[2]).is_some()
   1716                 && Alphabet::from_ascii(first[3]).is_some()
   1717             {
   1718                 chunks = rest;
   1719             } else {
   1720                 return Err(DecodeErr::InvalidByte);
   1721             }
   1722         }
   1723         match rem.len() {
   1724             0 => Ok(()),
   1725             1 => impossible!("there is a bug in base64_pad::trim_padding"),
   1726             2 => {
   1727                 if Alphabet::from_ascii(rem[0]).is_some()
   1728                     && let Some(base64_snd) = Alphabet::from_ascii(rem[1])
   1729                 {
   1730                     if base64_snd.to_u8().trailing_zeros() < 4 {
   1731                         cold_path();
   1732                         Err(DecodeErr::TrailingBits)
   1733                     } else {
   1734                         Ok(())
   1735                     }
   1736                 } else {
   1737                     cold_path();
   1738                     Err(DecodeErr::InvalidByte)
   1739                 }
   1740             }
   1741             3 => {
   1742                 if Alphabet::from_ascii(rem[0]).is_some()
   1743                     && Alphabet::from_ascii(rem[1]).is_some()
   1744                     && let Some(base64_third) = Alphabet::from_ascii(rem[2])
   1745                 {
   1746                     if base64_third.to_u8().trailing_zeros() < 2 {
   1747                         cold_path();
   1748                         Err(DecodeErr::TrailingBits)
   1749                     } else {
   1750                         Ok(())
   1751                     }
   1752                 } else {
   1753                     cold_path();
   1754                     Err(DecodeErr::InvalidByte)
   1755                 }
   1756             }
   1757             _ => impossible!("there is a bug in core::slice::as_chunks"),
   1758         }
   1759     } else {
   1760         Err(DecodeErr::EncodedLen)
   1761     }
   1762 }
   1763 /// Same as [`encode_buffer`] except `output` must have the _exact_ length needed to encode `input`, and the
   1764 /// encoded `str` is not returned.
   1765 ///
   1766 /// # Panics
   1767 ///
   1768 /// `panic`s iff `output` does not have the _exact_ length needed to encode `input`.
   1769 ///
   1770 /// # Examples
   1771 ///
   1772 /// ```
   1773 /// let mut buffer = [0; 256];
   1774 /// base64_pad::encode_buffer_exact([0; 0].as_slice(), &mut buffer[..0]);
   1775 /// base64_pad::encode_buffer_exact([0; 1].as_slice(), &mut buffer[..4]);
   1776 /// assert_eq!(*b"AA==", buffer[..4]);
   1777 /// // Uncommenting below will cause a `panic` since the output buffer must be exact.
   1778 /// // base64_pad::encode_buffer_exact([255; 1].as_slice(), &mut buffer);
   1779 /// ```
   1780 #[inline]
   1781 pub const fn encode_buffer_exact(input: &[u8], output: &mut [u8]) {
   1782     assert!(
   1783         // `encode_len` won't `panic` since Rust guarantees `input.len()` is at most `isize::MAX`.
   1784         output.len() == encode_len(input.len()),
   1785         "encode_buffer_exact must be passed an output buffer whose length is exactly the length needed to encode the data"
   1786     );
   1787     _ = encode_buffer(input, output);
   1788 }
   1789 /// Same as [`decode_buffer`] except `output` must have the _exact_ length needed, and the decoded `slice`
   1790 /// is not returned.
   1791 ///
   1792 /// Note one must be careful in determining the length of `output` before calling; in particular, one can't simply
   1793 /// call [`decode_len`] on the length of `input` since `input` may contain padding which will cause `decode_len`
   1794 /// to return a value that is larger than necessary.
   1795 ///
   1796 /// # Errors
   1797 ///
   1798 /// Errors iff [`decode_buffer`] errors. Note that since a `panic` occurs when `output.len()` is not the
   1799 /// exact length needed, [`DecodeErr::BufferLen`] is not possible in addition to [`DecodeErr::TryReserve`].
   1800 ///
   1801 /// # Panics
   1802 ///
   1803 /// `panic`s iff `output` does not have the _exact_ length needed to contain the decoded data and `input`
   1804 /// has a valid encoded length (i.e., [`DecodeErr::EncodedLen`] is returned _not_ a `panic` when `input`
   1805 /// has invalid length).
   1806 ///
   1807 /// # Examples
   1808 ///
   1809 /// ```
   1810 /// # use base64_pad::DecodeErr;
   1811 /// assert_eq!(
   1812 ///     base64_pad::decode_buffer_exact(b"A", [0; 0].as_mut_slice()).unwrap_err(),
   1813 ///     DecodeErr::EncodedLen
   1814 /// );
   1815 /// assert_eq!(
   1816 ///     base64_pad::decode_buffer_exact(b"-A==", [0; 1].as_mut_slice()).unwrap_err(),
   1817 ///     DecodeErr::InvalidByte
   1818 /// );
   1819 /// assert_eq!(
   1820 ///     base64_pad::decode_buffer_exact(b"+8==", [0; 1].as_mut_slice()).unwrap_err(),
   1821 ///     DecodeErr::TrailingBits
   1822 /// );
   1823 /// let mut buffer = [0; base64_pad::decode_len(b"C8Aa/A++91VZbx0=".len()).unwrap() - 1];
   1824 /// base64_pad::decode_buffer_exact(b"C8Aa/A++91VZbx0=", &mut buffer)?;
   1825 /// assert_eq!(buffer, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]);
   1826 /// // Uncommenting below will cause a `panic` since a larger output buffer than necessary is _not_ OK.
   1827 /// // base64_pad::decode_buffer_exact(
   1828 /// //     b"C8Aa/A++91VZbx0=",
   1829 /// //     &mut [0; base64_pad::decode_len(b"C8Aa/A++91VZbx0=".len()).unwrap()],
   1830 /// // )?;
   1831 /// # Ok::<_, DecodeErr>(())
   1832 /// ```
   1833 #[expect(
   1834     clippy::panic,
   1835     clippy::panic_in_result_fn,
   1836     reason = "purpose of this function is to panic when output does not have the exact length needed"
   1837 )]
   1838 #[inline]
   1839 pub const fn decode_buffer_exact(input: &[u8], output: &mut [u8]) -> Result<(), DecodeErr> {
   1840     let output_len = output.len();
   1841     match decode_buffer(input, output) {
   1842         Ok(v) => {
   1843             assert!(
   1844                 v.len() == output_len,
   1845                 "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data"
   1846             );
   1847             Ok(())
   1848         }
   1849         Err(e) => {
   1850             if matches!(e, DecodeErr::BufferLen) {
   1851                 cold_path();
   1852                 panic!(
   1853                     "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data"
   1854                 );
   1855             } else {
   1856                 Err(e)
   1857             }
   1858         }
   1859     }
   1860 }