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