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