lib.rs (73722B)
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 // `MAX_ENCODE_INPUT_LEN` = decode_len(usize::MAX).unwrap(); thus all values less than or equal to 948 // `MAX_ENCODE_INPUT_LEN` won't overflow ignoring intermediate calcuations since ⌈4n/3⌉ is a 949 // monotonically increasing function. 950 // QED 951 // Naively implementing ⌈4n/3⌉ as (4 * n).div_ceil(3) can cause overflow due to `4 * n`; thus 952 // we implement the equivalent equation 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ instead: 953 // `(4 * (n / 3)) + (4 * (n % 3)).div_ceil(3)` since none of the intermediate calculations suffer 954 // from overflow. 955 if input_length <= MAX_ENCODE_INPUT_LEN { 956 // (n / 3) << 2u8 <= m <= usize::MAX; thus the left operand of + is fine. 957 // n % 3 <= 2 958 // <==> 959 // 4(n % 3) <= 8 < usize::MAX; thus (n % 3) << 2u8 is fine. 960 // <==> 961 // ⌈4(n % 3)/3⌉ <= 4(n % 3), so the right operand of + is fine. 962 // The sum is fine since 963 // m = ⌈4n/3⌉ = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ = ((n / 3) << 2u8) + ((n % 3) << 2u8).div_ceil(3), and m <= usize::MAX. 964 Some(((input_length / 3) << 2u8) + ((input_length % 3) << 2u8).div_ceil(3)) 965 } else { 966 None 967 } 968 } 969 /// Same as [`encode_len_checked`] except a `panic` occurs instead of `None` being returned. 970 /// 971 /// One should prefer this function over `encode_len_checked` when passing the length of a memory allocation 972 /// since such a length is guaranteed to succeed. 973 /// 974 /// # Panics 975 /// 976 /// `panic`s iff [`encode_len_checked`] returns `None`. 977 /// 978 /// # Examples 979 /// 980 /// ``` 981 /// # use base64url_nopad::MAX_ENCODE_INPUT_LEN; 982 /// // Uncommenting below will cause a `panic`. 983 /// // base64url_nopad::encode_len(usize::MAX - 4); 984 /// // Uncommenting below will cause a `panic`. 985 /// // base64url_nopad::encode_len(MAX_ENCODE_INPUT_LEN + 1); 986 /// assert_eq!(base64url_nopad::encode_len(MAX_ENCODE_INPUT_LEN), usize::MAX); 987 /// assert!(base64url_nopad::encode_len(isize::MAX as usize) > isize::MAX as usize); 988 /// assert_eq!(base64url_nopad::encode_len(3), 4); 989 /// assert_eq!(base64url_nopad::encode_len(2), 3); 990 /// assert_eq!(base64url_nopad::encode_len(1), 2); 991 /// assert_eq!(base64url_nopad::encode_len(0), 0); 992 /// ``` 993 #[expect(clippy::unwrap_used, reason = "comment justifies correctness")] 994 #[inline] 995 #[must_use] 996 pub const fn encode_len(input_length: usize) -> usize { 997 // A precondition for calling this function is to ensure `encode_len_checked` can't return `None`. 998 encode_len_checked(input_length).unwrap() 999 } 1000 /// Encodes `input` into `output` re-interpreting the encoded subset of `output` as a `str` before returning it. 1001 /// 1002 /// `Some` is returned iff `output.len()` is large enough to write the encoded data into. 1003 /// 1004 /// Note since Rust guarantees all memory allocations don't exceed [`isize::MAX`] bytes, one can 1005 /// instead call [`encode_buffer`] using a buffer whose length is at least as large as the value returned from 1006 /// [`encode_len`] without fear of a `panic` and the benefit of getting a `str` instead of an `Option`. 1007 /// 1008 /// # Examples 1009 /// 1010 /// ``` 1011 /// assert!( 1012 /// base64url_nopad::encode_buffer_checked([0; 0].as_slice(), [0; 0].as_mut_slice()).is_some_and(|val| val.is_empty()) 1013 /// ); 1014 /// assert!( 1015 /// base64url_nopad::encode_buffer_checked([0; 1].as_slice(), [0; 2].as_mut_slice()).is_some_and(|val| val == "AA") 1016 /// ); 1017 /// // A larger output buffer than necessary is OK. 1018 /// assert!( 1019 /// base64url_nopad::encode_buffer_checked([1; 1].as_slice(), [0; 128].as_mut_slice()).is_some_and(|val| val == "AQ") 1020 /// ); 1021 /// assert!( 1022 /// base64url_nopad::encode_buffer_checked( 1023 /// [0xc9; 14].as_slice(), 1024 /// [0; base64url_nopad::encode_len(14)].as_mut_slice() 1025 /// ).is_some_and(|val| val == "ycnJycnJycnJycnJyck") 1026 /// ); 1027 /// assert!(base64url_nopad::encode_buffer_checked([0; 1].as_slice(), [0; 1].as_mut_slice()).is_none()); 1028 /// ``` 1029 #[expect(unsafe_code, reason = "comments justify correctness")] 1030 #[expect( 1031 clippy::arithmetic_side_effects, 1032 clippy::indexing_slicing, 1033 reason = "comments justify correctness" 1034 )] 1035 #[inline] 1036 pub const fn encode_buffer_checked<'a>( 1037 mut input: &[u8], 1038 output: &'a mut [u8], 1039 ) -> Option<&'a mut str> { 1040 // This won't `panic` since Rust guarantees that all memory allocations won't exceed `isize::MAX`. 1041 let final_len = encode_len(input.len()); 1042 if output.len() >= final_len { 1043 // We increment this by `1` for each `u8` in `input`. On every third `u8`, we increment it an extra 1044 // time since we use 4 base64url `u8`s for each `u8`. 1045 // We also verified that `output.len()` large enough; thus all indexing operations 1046 // using it are correct, and incrementing it never results in overflow. 1047 let mut output_idx = 0; 1048 let mut counter = ThreeOrdinal::First; 1049 let mut trailing = 0; 1050 let mut shift; 1051 while let [first, ref rest @ ..] = *input { 1052 match counter { 1053 ThreeOrdinal::First => { 1054 // We trim the last two bits and interpret `first` as a 6-bit integer. 1055 shift = first >> 2; 1056 // SAFETY: 1057 // `shift <= 63` since we shifted at least two bits to the right. 1058 output[output_idx] = unsafe { Alphabet::from_u8_unchecked(shift) }.to_ascii(); 1059 // The two bits we trimmed are the first two bits of the next 6-bit integer. 1060 trailing = (first & 3) << 4; 1061 counter = ThreeOrdinal::Second; 1062 } 1063 ThreeOrdinal::Second => { 1064 // We trim the last four bits and interpret `first` as a 6-bit integer. 1065 // The first two bits are the trailing 2 bits from the previous value. 1066 shift = trailing | (first >> 4); 1067 // SAFETY: 1068 // `shift <= 63` since `first` was shifted at least two bits to the right, and 1069 // `trailing = (first & 3) << 4` which means its high two bits are 0 as well. 1070 output[output_idx] = unsafe { Alphabet::from_u8_unchecked(shift) }.to_ascii(); 1071 // The four bits we trimmed are the first four bits of the next 6-bit integer. 1072 trailing = (first & 15) << 2; 1073 counter = ThreeOrdinal::Third; 1074 } 1075 ThreeOrdinal::Third => { 1076 // We trim the last six bits and interpret `first` as a 6-bit integer. 1077 // The first four bits are the trailing 4 bits from the previous value. 1078 shift = trailing | (first >> 6); 1079 // SAFETY: 1080 // `shift <= 63` since `first` was shifted at least two bits to the right, and 1081 // `trailing = (first & 15) << 2` which means its high two bits are 0 as well. 1082 output[output_idx] = unsafe { Alphabet::from_u8_unchecked(shift) }.to_ascii(); 1083 // Every third `u8` corresponds to a fourth base64url `u8`. 1084 output_idx += 1; 1085 // We use the 6 bits we just trimmed. 1086 shift = first & 63; 1087 // SAFETY: 1088 // `shift <= 63` since `first & 63` is. 1089 output[output_idx] = unsafe { Alphabet::from_u8_unchecked(shift) }.to_ascii(); 1090 counter = ThreeOrdinal::First; 1091 } 1092 } 1093 input = rest; 1094 output_idx += 1; 1095 } 1096 if !matches!(counter, ThreeOrdinal::First) { 1097 // `input.len()` is not a multiple of 3; thus we have to append a trailing base64url `u8` that 1098 // is simply the current value of `trailing`. 1099 // SAFETY: 1100 // `trailing <= 63` since `trailing` is either `(first & 3) << 4` or `(first & 15) << 2` where 1101 // `first` is any `u8`. This means the high two bits are guaranteed to be 0. 1102 output[output_idx] = unsafe { Alphabet::from_u8_unchecked(trailing) }.to_ascii(); 1103 } 1104 // SAFETY: 1105 // We verified `output.len() >= final_len`. 1106 let val = unsafe { output.split_at_mut_unchecked(final_len) }.0; 1107 // SAFETY: 1108 // `val` has the exact length needed to encode `input`, and all of the `u8`s in it 1109 // are from `Alphabet::to_ascii` which is a subset of UTF-8; thus this is safe. 1110 // Note the above is vacuously true when `val` is empty. 1111 Some(unsafe { str::from_utf8_unchecked_mut(val) }) 1112 } else { 1113 None 1114 } 1115 } 1116 /// Same as [`encode_buffer_checked`] except a `panic` occurs instead of `None` being returned. 1117 /// 1118 /// # Panics 1119 /// 1120 /// `panic`s iff [`encode_buffer_checked`] returns `None` (i.e., the length of the output buffer is too small). 1121 /// 1122 /// # Examples 1123 /// 1124 /// ``` 1125 /// assert_eq!( 1126 /// base64url_nopad::encode_buffer([0; 0].as_slice(), [0; 0].as_mut_slice()), 1127 /// "" 1128 /// ); 1129 /// assert_eq!( 1130 /// base64url_nopad::encode_buffer([0; 1].as_slice(), [0; 2].as_mut_slice()), 1131 /// "AA" 1132 /// ); 1133 /// // A larger output buffer than necessary is OK. 1134 /// assert_eq!( 1135 /// base64url_nopad::encode_buffer([255; 1].as_slice(), [0; 256].as_mut_slice()), 1136 /// "_w" 1137 /// ); 1138 /// assert_eq!( 1139 /// base64url_nopad::encode_buffer( 1140 /// [0xc9; 14].as_slice(), 1141 /// [0; base64url_nopad::encode_len(14)].as_mut_slice() 1142 /// ), 1143 /// "ycnJycnJycnJycnJyck" 1144 /// ); 1145 /// // The below will `panic` when uncommented since the supplied output buffer is too small. 1146 /// // _ = base64url_nopad::encode_buffer([0; 1].as_slice(), [0; 1].as_mut_slice()); 1147 /// ``` 1148 #[expect(clippy::unwrap_used, reason = "comment justifies correctness")] 1149 #[inline] 1150 pub const fn encode_buffer<'a>(input: &[u8], output: &'a mut [u8]) -> &'a mut str { 1151 // A precondition for calling this function is to ensure `encode_buffer_checked` can't return `None`. 1152 encode_buffer_checked(input, output).unwrap() 1153 } 1154 /// Similar to [`encode_buffer`] except a `String` is returned instead using its buffer to write to. 1155 /// 1156 /// # Errors 1157 /// 1158 /// Errors iff an error occurs from allocating the capacity needed to contain the encoded data. 1159 /// 1160 /// # Examples 1161 /// 1162 /// ``` 1163 /// # extern crate alloc; 1164 /// # use alloc::collections::TryReserveError; 1165 /// assert_eq!( 1166 /// base64url_nopad::try_encode([0; 0].as_slice())?, 1167 /// "" 1168 /// ); 1169 /// assert_eq!( 1170 /// base64url_nopad::try_encode([0; 1].as_slice())?, 1171 /// "AA" 1172 /// ); 1173 /// assert_eq!( 1174 /// base64url_nopad::try_encode([128, 40, 3].as_slice())?, 1175 /// "gCgD" 1176 /// ); 1177 /// assert_eq!( 1178 /// base64url_nopad::try_encode([0x7b; 22].as_slice())?, 1179 /// "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew" 1180 /// ); 1181 /// # Ok::<_, TryReserveError>(()) 1182 /// ``` 1183 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 1184 #[cfg(feature = "alloc")] 1185 #[expect(unsafe_code, reason = "comment justifies correctness")] 1186 #[inline] 1187 pub fn try_encode(input: &[u8]) -> Result<String, TryReserveError> { 1188 let mut output = Vec::new(); 1189 // `encode_len` won't `panic` since Rust guarantees `input.len()` will not return a value greater 1190 // than `isize::MAX`. 1191 let len = encode_len(input.len()); 1192 output.try_reserve_exact(len).map(|()| { 1193 output.resize(len, 0); 1194 _ = encode_buffer(input, output.as_mut_slice()); 1195 // SAFETY: 1196 // `output` has the exact length needed to encode `input`, and all of the `u8`s in it 1197 // are from `Alphabet` which is a subset of UTF-8; thus this is safe. 1198 // Note the above is vacuously true when `output` is empty. 1199 unsafe { String::from_utf8_unchecked(output) } 1200 }) 1201 } 1202 /// Same as [`try_encode`] except a `panic` occurs on allocation failure. 1203 /// 1204 /// # Panics 1205 /// 1206 /// `panic`s iff [`try_encode`] errors. 1207 /// 1208 /// # Examples 1209 /// 1210 /// ``` 1211 /// assert_eq!( 1212 /// base64url_nopad::encode([0; 0].as_slice()), 1213 /// "" 1214 /// ); 1215 /// assert_eq!( 1216 /// base64url_nopad::encode([0; 1].as_slice()), 1217 /// "AA" 1218 /// ); 1219 /// assert_eq!( 1220 /// base64url_nopad::encode([128, 40, 3].as_slice()), 1221 /// "gCgD" 1222 /// ); 1223 /// assert_eq!( 1224 /// base64url_nopad::encode([0x7b; 22].as_slice()), 1225 /// "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew" 1226 /// ); 1227 /// ``` 1228 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 1229 #[cfg(feature = "alloc")] 1230 #[expect( 1231 clippy::unwrap_used, 1232 reason = "purpose of function is to panic on allocation failure" 1233 )] 1234 #[inline] 1235 #[must_use] 1236 pub fn encode(input: &[u8]) -> String { 1237 try_encode(input).unwrap() 1238 } 1239 /// Writes the base64url encoding of `input` using `writer`. 1240 /// 1241 /// Internally a buffer of at most 1024 bytes is used to write the encoded data. Smaller buffers may be used 1242 /// for small inputs. 1243 /// 1244 /// # Errors 1245 /// 1246 /// Errors iff [`Write::write_str`] does. 1247 /// 1248 /// # Panics 1249 /// 1250 /// `panic`s iff [`Write::write_str`] does. 1251 /// 1252 /// # Examples 1253 /// 1254 /// ``` 1255 /// # extern crate alloc; 1256 /// # use alloc::string::String; 1257 /// # use core::fmt::Error; 1258 /// let mut buffer = String::new(); 1259 /// base64url_nopad::encode_write([0; 0].as_slice(), &mut buffer)?; 1260 /// assert_eq!(buffer, ""); 1261 /// buffer.clear(); 1262 /// base64url_nopad::encode_write([0; 1].as_slice(), &mut buffer)?; 1263 /// assert_eq!(buffer, "AA"); 1264 /// buffer.clear(); 1265 /// base64url_nopad::encode_write( 1266 /// [0xc9; 14].as_slice(), 1267 /// &mut buffer, 1268 /// )?; 1269 /// assert_eq!(buffer, "ycnJycnJycnJycnJyck"); 1270 /// # Ok::<_, Error>(()) 1271 /// ``` 1272 #[expect(unsafe_code, reason = "comment justifies correctness")] 1273 #[expect( 1274 clippy::arithmetic_side_effects, 1275 clippy::as_conversions, 1276 clippy::indexing_slicing, 1277 reason = "comments justify correctness" 1278 )] 1279 #[inline] 1280 pub fn encode_write<W: Write>(mut input: &[u8], writer: &mut W) -> fmt::Result { 1281 /// The minimum buffer size we use. 1282 const MIN_BUFFER_LEN: usize = 256; 1283 /// The medium buffer size we use. 1284 const MID_BUFFER_LEN: usize = MIN_BUFFER_LEN << 1; 1285 /// The max buffer size. 1286 /// 1287 /// This must be at least 4, no more than `i16::MAX`, and must be a power of 2. 1288 const MAX_BUFFER_LEN: usize = MID_BUFFER_LEN << 1; 1289 /// The minimum length of the input until we must chunk encode the data. 1290 const LOOP_LEN: usize = MAX_BUFFER_LEN + 1; 1291 /// Want to ensure at compilation time that `MAX_BUFFER_LEN` upholds its invariants. Namely 1292 /// that it's at least as large as 4, doesn't exceed [`i16::MAX`], and is always a power of 2. 1293 const _: () = { 1294 // `i16::MAX <= usize::MAX`, so this is fine. 1295 /// `i16::MAX`. 1296 const MAX_LEN: usize = i16::MAX as usize; 1297 assert!( 1298 4 <= MAX_BUFFER_LEN && MAX_BUFFER_LEN < MAX_LEN && MAX_BUFFER_LEN.is_power_of_two(), 1299 "encode_write::MAX_BUFFER_LEN must be a power of two less than i16::MAX but at least as large as 4" 1300 ); 1301 }; 1302 /// The input size that corresponds to an encoded value of length `MAX_BUFFER_LEN`. 1303 // This will never `panic` since `MAX_BUFFER_LEN` is a power of two at least as large as 4 1304 // (i.e., `MAX_BUFFER_LEN` ≢ 1 (mod 4)). 1305 const INPUT_LEN: usize = decode_len(MAX_BUFFER_LEN).unwrap(); 1306 // This won't `panic` since `input.len()` is guaranteed to be no more than `isize::MAX`. 1307 let len = encode_len(input.len()); 1308 match len { 1309 0 => Ok(()), 1310 1..=MIN_BUFFER_LEN => { 1311 let mut buffer = [0; MIN_BUFFER_LEN]; 1312 // `buffer.len() == MIN_BUFFER_LEN >= len`, so indexing is fine. 1313 // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode 1314 // the data. 1315 writer.write_str(encode_buffer(input, &mut buffer[..len])) 1316 } 1317 257..=MID_BUFFER_LEN => { 1318 let mut buffer = [0; MID_BUFFER_LEN]; 1319 // `buffer.len() == MID_BUFFER_LEN >= len`, so indexing is fine. 1320 // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode 1321 // the data. 1322 writer.write_str(encode_buffer(input, &mut buffer[..len])) 1323 } 1324 513..=MAX_BUFFER_LEN => { 1325 let mut buffer = [0; MAX_BUFFER_LEN]; 1326 // `buffer.len() == MAX_BUFFER_LEN >= len`, so indexing is fine. 1327 // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode 1328 // the data. 1329 writer.write_str(encode_buffer(input, &mut buffer[..len])) 1330 } 1331 LOOP_LEN.. => { 1332 let mut buffer = [0; MAX_BUFFER_LEN]; 1333 let mut counter = 0; 1334 // `len / MAX_BUFFER_LEN` is equal to ⌊len / MAX_BUFFER_LEN⌋ since `MAX_BUFFER_LEN` is a power of two. 1335 // We can safely encode `term` chunks of `INPUT_LEN` length into `buffer`. 1336 let term = len >> MAX_BUFFER_LEN.trailing_zeros(); 1337 let mut input_buffer; 1338 while counter < term { 1339 // SAFETY: 1340 // `input.len() >= INPUT_LEN`. 1341 input_buffer = unsafe { input.split_at_unchecked(INPUT_LEN) }; 1342 // `encode_buffer` won't `panic` since `buffer` has length `MAX_BUFFER_LEN` which 1343 // is the exact length needed for `INPUT_LEN` length inputs which `input_buffer.0` is. 1344 writer.write_str(encode_buffer(input_buffer.0, buffer.as_mut_slice()))?; 1345 input = input_buffer.1; 1346 // `counter < term`, so overflow cannot happen. 1347 counter += 1; 1348 } 1349 // `encode_len` won't `panic` since `input.len() < MAX_ENCODE_INPUT_LEN`. 1350 // `input.len() < INPUT_LEN`; thus `encode_len(input.len()) < MAX_BUFFER_LEN = buffer.len()` so 1351 // indexing is fine. 1352 // `encode_buffer` won't `panic` since the buffer is the exact length needed to encode `input`. 1353 writer.write_str(encode_buffer(input, &mut buffer[..encode_len(input.len())])) 1354 } 1355 } 1356 } 1357 /// Appends the base64url encoding of `input` to `s` returning the `str` that was appended. 1358 /// 1359 /// # Errors 1360 /// 1361 /// Errors iff an error occurs from allocating the capacity needed to append the encoded data. 1362 /// 1363 /// # Examples 1364 /// 1365 /// ``` 1366 /// # extern crate alloc; 1367 /// # use alloc::{collections::TryReserveError, string::String}; 1368 /// let mut buffer = String::new(); 1369 /// assert_eq!( 1370 /// base64url_nopad::try_encode_append([0; 0].as_slice(), &mut buffer)?, 1371 /// "" 1372 /// ); 1373 /// assert_eq!( 1374 /// base64url_nopad::try_encode_append([0; 1].as_slice(), &mut buffer)?, 1375 /// "AA" 1376 /// ); 1377 /// assert_eq!( 1378 /// base64url_nopad::try_encode_append([128, 40, 3].as_slice(), &mut buffer)?, 1379 /// "gCgD" 1380 /// ); 1381 /// assert_eq!(buffer, "AAgCgD"); 1382 /// assert_eq!( 1383 /// base64url_nopad::try_encode_append([0x7b; 22].as_slice(), &mut buffer)?, 1384 /// "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew" 1385 /// ); 1386 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"); 1387 /// # Ok::<_, TryReserveError>(()) 1388 /// ``` 1389 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 1390 #[cfg(feature = "alloc")] 1391 #[expect(unsafe_code, reason = "comment justifies correctness")] 1392 #[expect( 1393 clippy::arithmetic_side_effects, 1394 clippy::indexing_slicing, 1395 reason = "comments justify correctness" 1396 )] 1397 #[inline] 1398 pub fn try_encode_append<'a>( 1399 input: &[u8], 1400 s: &'a mut String, 1401 ) -> Result<&'a mut str, TryReserveError> { 1402 // `encode_len` won't `panic` since Rust guarantees `input.len()` will return a value no larger 1403 // than `isize::MAX`. 1404 let additional_len = encode_len(input.len()); 1405 s.try_reserve_exact(additional_len).map(|()| { 1406 // SAFETY: 1407 // We only append base64url ASCII which is a subset of UTF-8, so this will remain valid UTF-8. 1408 let utf8 = unsafe { s.as_mut_vec() }; 1409 let original_len = utf8.len(); 1410 // Overflow can't happen; otherwise `s.try_reserve_exact` would have erred. 1411 utf8.resize(original_len + additional_len, 0); 1412 // `utf8.len() >= original_len`, so indexing is fine. 1413 // `encode_buffer` won't `panic` since `utf8[original_len..]` has length `additional_len` 1414 // which is the exact number of bytes needed to encode `input`. 1415 encode_buffer(input, &mut utf8[original_len..]) 1416 }) 1417 } 1418 /// Same as [`try_encode_append`] except the encoded `str` is not returned. 1419 /// 1420 /// # Errors 1421 /// 1422 /// Errors iff [`try_encode_append`] does. 1423 /// 1424 /// # Examples 1425 /// 1426 /// ``` 1427 /// # extern crate alloc; 1428 /// # use alloc::{collections::TryReserveError, string::String}; 1429 /// let mut buffer = String::new(); 1430 /// base64url_nopad::try_encode_append_only([0; 0].as_slice(), &mut buffer)?; 1431 /// assert_eq!(buffer, ""); 1432 /// base64url_nopad::try_encode_append_only([0; 1].as_slice(), &mut buffer)?; 1433 /// assert_eq!(buffer, "AA"); 1434 /// base64url_nopad::try_encode_append_only([128, 40, 3].as_slice(), &mut buffer)?; 1435 /// assert_eq!(buffer, "AAgCgD"); 1436 /// base64url_nopad::try_encode_append_only([0x7b; 22].as_slice(), &mut buffer)?; 1437 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"); 1438 /// # Ok::<_, TryReserveError>(()) 1439 /// ``` 1440 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 1441 #[cfg(feature = "alloc")] 1442 #[inline] 1443 pub fn try_encode_append_only(input: &[u8], s: &mut String) -> Result<(), TryReserveError> { 1444 try_encode_append(input, s).map(|_| ()) 1445 } 1446 /// Same as [`try_encode_append`] except a `panic` occurs on allocation failure. 1447 /// 1448 /// # Panics 1449 /// 1450 /// `panic`s iff [`try_encode_append`] errors. 1451 /// 1452 /// # Examples 1453 /// 1454 /// ``` 1455 /// # extern crate alloc; 1456 /// # use alloc::{collections::TryReserveError, string::String}; 1457 /// let mut buffer = String::new(); 1458 /// assert_eq!( 1459 /// base64url_nopad::encode_append([0; 0].as_slice(), &mut buffer), 1460 /// "" 1461 /// ); 1462 /// assert_eq!( 1463 /// base64url_nopad::encode_append([0; 1].as_slice(), &mut buffer), 1464 /// "AA" 1465 /// ); 1466 /// assert_eq!( 1467 /// base64url_nopad::encode_append([128, 40, 3].as_slice(), &mut buffer), 1468 /// "gCgD" 1469 /// ); 1470 /// assert_eq!(buffer, "AAgCgD"); 1471 /// assert_eq!( 1472 /// base64url_nopad::encode_append([0x7b; 22].as_slice(), &mut buffer), 1473 /// "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew" 1474 /// ); 1475 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"); 1476 /// ``` 1477 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 1478 #[cfg(feature = "alloc")] 1479 #[expect( 1480 clippy::unwrap_used, 1481 reason = "purpose of this function is to panic on allocation failure" 1482 )] 1483 #[inline] 1484 pub fn encode_append<'a>(input: &[u8], s: &'a mut String) -> &'a mut str { 1485 try_encode_append(input, s).unwrap() 1486 } 1487 /// Same as [`encode_append`] except the encoded `str` is not returned. 1488 /// 1489 /// # Panics 1490 /// 1491 /// `panic`s iff [`encode_append`] does. 1492 /// 1493 /// # Examples 1494 /// 1495 /// ``` 1496 /// # extern crate alloc; 1497 /// # use alloc::{collections::TryReserveError, string::String}; 1498 /// let mut buffer = String::new(); 1499 /// base64url_nopad::encode_append_only([0; 0].as_slice(), &mut buffer); 1500 /// assert_eq!(buffer, ""); 1501 /// base64url_nopad::encode_append_only([0; 1].as_slice(), &mut buffer); 1502 /// assert_eq!(buffer, "AA"); 1503 /// base64url_nopad::encode_append_only([128, 40, 3].as_slice(), &mut buffer); 1504 /// assert_eq!(buffer, "AAgCgD"); 1505 /// base64url_nopad::encode_append_only([0x7b; 22].as_slice(), &mut buffer); 1506 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"); 1507 /// # Ok::<_, TryReserveError>(()) 1508 /// ``` 1509 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 1510 #[cfg(feature = "alloc")] 1511 #[inline] 1512 pub fn encode_append_only(input: &[u8], s: &mut String) { 1513 _ = encode_append(input, s); 1514 } 1515 /// Returns the exact number of bytes needed to decode a base64url without padding input of length `input_length`. 1516 /// 1517 /// `Some` is returned iff `input_length` represents a possible length of a base64url without padding input. 1518 /// 1519 /// # Examples 1520 /// 1521 /// ``` 1522 /// # use base64url_nopad::MAX_ENCODE_INPUT_LEN; 1523 /// assert!(base64url_nopad::decode_len(1).is_none()); 1524 /// assert!(base64url_nopad::decode_len(usize::MAX).is_some_and(|len| len == MAX_ENCODE_INPUT_LEN)); 1525 /// assert!(base64url_nopad::decode_len(4).is_some_and(|len| len == 3)); 1526 /// assert!(base64url_nopad::decode_len(3).is_some_and(|len| len == 2)); 1527 /// assert!(base64url_nopad::decode_len(2).is_some_and(|len| len == 1)); 1528 /// assert!(base64url_nopad::decode_len(0).is_some_and(|len| len == 0)); 1529 /// ``` 1530 #[expect( 1531 clippy::arithmetic_side_effects, 1532 reason = "proof and comment justifies their correctness" 1533 )] 1534 #[inline] 1535 #[must_use] 1536 pub const fn decode_len(input_length: usize) -> Option<usize> { 1537 // 64^n is the number of distinct values of the input. Let the decoded output be O. 1538 // There are 256 possible values each byte in O can be; thus we must find 1539 // the maximum nonnegative integer m such that: 1540 // 256^m = (2^8)^m = 2^(8m) <= 64^n = (2^6)^n = 2^(6n) 1541 // <==> 1542 // lg(2^(8m)) = 8m <= lg(2^(6n)) = 6n lg is defined on all positive reals which 2^(8m) and 2^(6n) are 1543 // <==> 1544 // m <= 6n/8 = 3n/4 1545 // Clearly that corresponds to m = ⌊3n/4⌋. 1546 // From the proof in `encode_len_checked`, we know that n is a valid length 1547 // iff n ≢ 1 (mod 4). 1548 // We claim ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋. 1549 // Proof: 1550 // There are three partitions for n: 1551 // (1) 4i = n ≡ 0 (mod 4) for some integer i 1552 // <==> 1553 // ⌊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⌋ 1554 // (2) 4i + 2 = n ≡ 2 (mod 4) for some integer i 1555 // <==> 1556 // ⌊3n/4⌋ = ⌊3(4i + 2)/4⌋ = ⌊3i + 6/4⌋ = 3i + ⌊6/4⌋ = 3i + 1 = 3⌊i⌋ + ⌊3(2)/4⌋ 1557 // = 3⌊(4i + 2)/4⌋ + ⌊3((4i + 2) mod 4)/4⌋ 1558 // = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ 1559 // (3) 4i + 3 = n ≡ 3 (mod 4) for some integer i 1560 // <==> 1561 // ⌊3n/4⌋ = ⌊3(4i + 3)/4⌋ = ⌊3i + 9/4⌋ = 3i + ⌊9/4⌋ = 3i + 2 = 3⌊i⌋ + ⌊3(3)/4⌋ 1562 // = 3⌊(4i + 3)/4⌋ + ⌊3((4i + 3) mod 4)/4⌋ 1563 // = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ 1564 // QED 1565 // Naively implementing ⌊3n/4⌋ as (3 * n) / 4 can cause overflow due to `3 * n`; thus 1566 // we implement the equivalent equation 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ instead: 1567 // `(3 * (n / 4)) + ((3 * (n % 4)) / 4)` since none of the intermediate calculations suffer 1568 // from overflow. 1569 // `input_length % 4`. 1570 let rem = input_length & 3; 1571 if rem == 1 { 1572 None 1573 } else { 1574 // 3 * (n >> 2u8) <= m < usize::MAX; thus the left operand of + is fine. 1575 // rem <= 3 1576 // <==> 1577 // 3rem <= 9 < usize::MAX; thus 3 * rem is fine. 1578 // <==> 1579 // ⌊3rem/4⌋ <= 3rem, so the right operand of + is fine. 1580 // The sum is fine since 1581 // m = ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ = (3 * (n >> 2u8)) + ((3 * rem) >> 2u8), and m < usize::MAX. 1582 Some((3 * (input_length >> 2u8)) + ((3 * rem) >> 2u8)) 1583 } 1584 } 1585 /// Ordinal numbers from first to fourth inclusively. 1586 enum FourOrdinal { 1587 /// First. 1588 First, 1589 /// Second. 1590 Second, 1591 /// Third. 1592 Third, 1593 /// Fourth. 1594 Fourth, 1595 } 1596 /// Error returned from [`decode_buffer`] and [`decode`]. 1597 #[derive(Clone, Debug, Eq, PartialEq)] 1598 pub enum DecodeErr { 1599 /// The encoded input had an invalid length. 1600 EncodedLen, 1601 /// The buffer supplied had a length that was too small to contain the decoded data. 1602 BufferLen, 1603 /// The encoded data contained trailing bits that were not zero. 1604 TrailingBits, 1605 /// The encoded data contained an invalid `u8`. 1606 InvalidByte, 1607 /// [`decode`] could not allocate enough memory to contain the decoded data. 1608 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 1609 #[cfg(feature = "alloc")] 1610 TryReserve(TryReserveError), 1611 } 1612 #[cfg_attr(docsrs, doc(cfg(not(feature = "alloc"))))] 1613 #[cfg(not(feature = "alloc"))] 1614 impl Copy for DecodeErr {} 1615 impl Display for DecodeErr { 1616 #[inline] 1617 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1618 match *self { 1619 Self::EncodedLen => f.write_str("length of encoded data was invalid"), 1620 Self::BufferLen => { 1621 f.write_str("length of the output buffer is too small to contain the decoded data") 1622 } 1623 Self::TrailingBits => { 1624 f.write_str("encoded data contained trailing bits that were not zero") 1625 } 1626 Self::InvalidByte => f.write_str("encoded data contained an invalid byte"), 1627 #[cfg(feature = "alloc")] 1628 Self::TryReserve(ref err) => err.fmt(f), 1629 } 1630 } 1631 } 1632 impl Error for DecodeErr {} 1633 /// Decodes `input` into `output` returning the subset of `output` containing the decoded data. 1634 /// 1635 /// # Errors 1636 /// 1637 /// Errors iff [`decode_len`] of `input.len()` does not return `Some` containing a 1638 /// `usize` that does not exceed `ouput.len()` or `input` is an invalid base64url-encoded value without padding. 1639 /// Note [`DecodeErr::TryReserve`] will never be returned. 1640 /// 1641 /// # Examples 1642 /// 1643 /// ``` 1644 /// # use base64url_nopad::DecodeErr; 1645 /// assert_eq!(base64url_nopad::decode_buffer([0; 0].as_slice(), [0; 0].as_mut_slice())?, b""); 1646 /// assert_eq!( 1647 /// base64url_nopad::decode_buffer([0; 1].as_slice(), [0; 0].as_mut_slice()).unwrap_err(), 1648 /// DecodeErr::EncodedLen 1649 /// ); 1650 /// assert_eq!( 1651 /// base64url_nopad::decode_buffer([0; 2].as_slice(), [0; 3].as_mut_slice()).unwrap_err(), 1652 /// DecodeErr::InvalidByte 1653 /// ); 1654 /// assert_eq!( 1655 /// base64url_nopad::decode_buffer([0; 2].as_slice(), [0; 0].as_mut_slice()).unwrap_err(), 1656 /// DecodeErr::BufferLen 1657 /// ); 1658 /// assert_eq!( 1659 /// base64url_nopad::decode_buffer(b"-8", [0; 3].as_mut_slice()).unwrap_err(), 1660 /// DecodeErr::TrailingBits 1661 /// ); 1662 /// // A larger output buffer than necessary is OK. 1663 /// 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]); 1664 /// # Ok::<_, DecodeErr>(()) 1665 /// ``` 1666 #[expect(unsafe_code, reason = "comment justifies correctness")] 1667 #[expect( 1668 clippy::arithmetic_side_effects, 1669 clippy::indexing_slicing, 1670 reason = "comments justify correctness" 1671 )] 1672 #[inline] 1673 pub const fn decode_buffer<'a>( 1674 mut input: &[u8], 1675 output: &'a mut [u8], 1676 ) -> Result<&'a mut [u8], DecodeErr> { 1677 if let Some(output_len) = decode_len(input.len()) { 1678 if output.len() >= output_len { 1679 // `input.len() % 4`. 1680 let len = input.len() & 3; 1681 // A trailing `Alphabet` is added iff the encode value is not a multiple of 4 (i.e., len % 4 != 0). 1682 match len { 1683 2 => { 1684 // We know `input` is not empty; otherwise `len % 3 == 0`. 1685 if let Some(val) = Alphabet::from_ascii(input[input.len() - 1]) { 1686 if val.to_u8().trailing_zeros() < 4 { 1687 return Err(DecodeErr::TrailingBits); 1688 } 1689 } else { 1690 return Err(DecodeErr::InvalidByte); 1691 } 1692 } 1693 3 => { 1694 // We know `input` is not empty; otherwise `len % 3 == 0`. 1695 if let Some(val) = Alphabet::from_ascii(input[input.len() - 1]) { 1696 if val.to_u8().trailing_zeros() < 2 { 1697 return Err(DecodeErr::TrailingBits); 1698 } 1699 } else { 1700 return Err(DecodeErr::InvalidByte); 1701 } 1702 } 1703 // The only possible value is `0` since if `len` were `1`, `decode_len` would have failed. 1704 _ => {} 1705 } 1706 let mut val = 0; 1707 let mut output_idx = 0; 1708 let mut counter = FourOrdinal::First; 1709 while let [mut first, ref rest @ ..] = *input { 1710 if let Some(base64) = Alphabet::from_ascii(first) { 1711 first = base64.to_u8(); 1712 match counter { 1713 FourOrdinal::First => { 1714 val = first << 2; 1715 counter = FourOrdinal::Second; 1716 } 1717 FourOrdinal::Second => { 1718 output[output_idx] = val | (first >> 4); 1719 val = first << 4; 1720 counter = FourOrdinal::Third; 1721 output_idx += 1; 1722 } 1723 FourOrdinal::Third => { 1724 output[output_idx] = val | (first >> 2); 1725 val = first << 6; 1726 counter = FourOrdinal::Fourth; 1727 output_idx += 1; 1728 } 1729 FourOrdinal::Fourth => { 1730 output[output_idx] = val | first; 1731 counter = FourOrdinal::First; 1732 output_idx += 1; 1733 } 1734 } 1735 input = rest; 1736 } else { 1737 return Err(DecodeErr::InvalidByte); 1738 } 1739 } 1740 // SAFETY: 1741 // `output.len() >= output_len`. 1742 Ok(unsafe { output.split_at_mut_unchecked(output_len) }.0) 1743 } else { 1744 Err(DecodeErr::BufferLen) 1745 } 1746 } else { 1747 Err(DecodeErr::EncodedLen) 1748 } 1749 } 1750 /// Similar to [`decode_buffer`] except a `Vec` is returned instead using its buffer to write to. 1751 /// 1752 /// # Errors 1753 /// 1754 /// Errors iff [`decode_buffer`] errors or an error occurs from allocating the capacity needed to contain 1755 /// the decoded data. Note [`DecodeErr::BufferLen`] is not possible to be returned. 1756 /// 1757 /// # Examples 1758 /// 1759 /// ``` 1760 /// # use base64url_nopad::DecodeErr; 1761 /// assert_eq!(base64url_nopad::decode([0; 0].as_slice())?, b""); 1762 /// assert_eq!( 1763 /// base64url_nopad::decode([0; 1].as_slice()).unwrap_err(), 1764 /// DecodeErr::EncodedLen 1765 /// ); 1766 /// assert_eq!( 1767 /// base64url_nopad::decode([0; 2].as_slice()).unwrap_err(), 1768 /// DecodeErr::InvalidByte 1769 /// ); 1770 /// assert_eq!( 1771 /// base64url_nopad::decode(b"-8").unwrap_err(), 1772 /// DecodeErr::TrailingBits 1773 /// ); 1774 /// assert_eq!(base64url_nopad::decode(b"C8Aa_A--91VZbx0")?, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]); 1775 /// # Ok::<_, DecodeErr>(()) 1776 /// ``` 1777 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] 1778 #[cfg(feature = "alloc")] 1779 #[inline] 1780 pub fn decode(input: &[u8]) -> Result<Vec<u8>, DecodeErr> { 1781 decode_len(input.len()) 1782 .ok_or(DecodeErr::EncodedLen) 1783 .and_then(|capacity| { 1784 let mut buffer = Vec::new(); 1785 buffer 1786 .try_reserve_exact(capacity) 1787 .map_err(DecodeErr::TryReserve) 1788 .and_then(|()| { 1789 buffer.resize(capacity, 0); 1790 if let Err(e) = decode_buffer(input, buffer.as_mut_slice()) { 1791 Err(e) 1792 } else { 1793 Ok(buffer) 1794 } 1795 }) 1796 }) 1797 } 1798 /// Similar to [`decode_buffer`] except the data is not decoded. 1799 /// 1800 /// In some situations, one does not want to actually decode data but merely validate that the encoded data 1801 /// is valid base64url without padding. Since data is not actually decoded, one avoids the need to allocate 1802 /// a large-enough buffer first. 1803 /// 1804 /// # Errors 1805 /// 1806 /// Errors iff `input` is an invalid base64url without padding. 1807 /// 1808 /// Note since no buffer is used to decode the data into, neither [`DecodeErr::BufferLen`] nor 1809 /// [`DecodeErr::TryReserve`] will ever be returned. 1810 /// 1811 /// # Examples 1812 /// 1813 /// ``` 1814 /// # use base64url_nopad::DecodeErr; 1815 /// base64url_nopad::validate_encoded_data([0; 0].as_slice())?; 1816 /// assert_eq!( 1817 /// base64url_nopad::validate_encoded_data([0; 1].as_slice()).unwrap_err(), 1818 /// DecodeErr::EncodedLen 1819 /// ); 1820 /// assert_eq!( 1821 /// base64url_nopad::validate_encoded_data([0; 2].as_slice()).unwrap_err(), 1822 /// DecodeErr::InvalidByte 1823 /// ); 1824 /// assert_eq!( 1825 /// base64url_nopad::validate_encoded_data(b"-8").unwrap_err(), 1826 /// DecodeErr::TrailingBits 1827 /// ); 1828 /// base64url_nopad::validate_encoded_data(b"C8Aa_A--91VZbx0")?; 1829 /// # Ok::<_, DecodeErr>(()) 1830 /// ``` 1831 #[expect( 1832 clippy::arithmetic_side_effects, 1833 clippy::indexing_slicing, 1834 reason = "comments justify correctness" 1835 )] 1836 #[inline] 1837 pub const fn validate_encoded_data(mut input: &[u8]) -> Result<(), DecodeErr> { 1838 let len = input.len(); 1839 // `len % 4`. 1840 match len & 3 { 1841 // `input.len()` is invalid iff it is equivalent to 1 modulo 4 per the proof in 1842 // `decode_len`. 1843 1 => return Err(DecodeErr::EncodedLen), 1844 2 => { 1845 // We know `input` is not empty; otherwise `len % 4 == 0`. 1846 if let Some(val) = Alphabet::from_ascii(input[len - 1]) { 1847 if val.to_u8().trailing_zeros() < 4 { 1848 return Err(DecodeErr::TrailingBits); 1849 } 1850 } else { 1851 return Err(DecodeErr::InvalidByte); 1852 } 1853 } 1854 3 => { 1855 // We know `input` is not empty; otherwise `len % 4 == 0`. 1856 if let Some(val) = Alphabet::from_ascii(input[len - 1]) { 1857 if val.to_u8().trailing_zeros() < 2 { 1858 return Err(DecodeErr::TrailingBits); 1859 } 1860 } else { 1861 return Err(DecodeErr::InvalidByte); 1862 } 1863 } 1864 // When the input has length that is a multple of 4, then no trailing bits were added and thus 1865 // all values are possible. 1866 _ => {} 1867 } 1868 while let [first, ref rest @ ..] = *input { 1869 if Alphabet::from_ascii(first).is_some() { 1870 input = rest; 1871 } else { 1872 return Err(DecodeErr::InvalidByte); 1873 } 1874 } 1875 Ok(()) 1876 } 1877 /// Same as [`encode_buffer`] except `output` must have the _exact_ length needed to encode `input`, and the 1878 /// encoded `str` is not returned. 1879 /// 1880 /// # Panics 1881 /// 1882 /// `panic`s iff `output` does not have the _exact_ length needed to encode `input`. 1883 /// 1884 /// # Examples 1885 /// 1886 /// ``` 1887 /// let mut buffer = [0; 256]; 1888 /// base64url_nopad::encode_buffer_exact([0; 0].as_slice(), &mut buffer[..0]); 1889 /// base64url_nopad::encode_buffer_exact([0; 1].as_slice(), &mut buffer[..2]); 1890 /// assert_eq!(*b"AA", buffer[..2]); 1891 /// // Uncommenting below will cause a `panic` since the output buffer must be exact. 1892 /// // base64url_nopad::encode_buffer_exact([255; 1].as_slice(), &mut buffer); 1893 /// ``` 1894 #[inline] 1895 pub const fn encode_buffer_exact(input: &[u8], output: &mut [u8]) { 1896 assert!( 1897 // `encode_len` won't `panic` since Rust guarantees `input.len()` is at most `isize::MAX`. 1898 output.len() == encode_len(input.len()), 1899 "encode_buffer_exact must be passed an output buffer whose length is exactly the length needed to encode the data" 1900 ); 1901 _ = encode_buffer(input, output); 1902 } 1903 /// Same as [`decode_buffer`] except `output` must have the _exact_ length needed, and the decoded `slice` 1904 /// is not returned. 1905 /// 1906 /// # Errors 1907 /// 1908 /// Errors iff [`decode_buffer`] errors. Note that since a `panic` occurs when `output.len()` is not the 1909 /// exact length needed, [`DecodeErr::BufferLen`] is not possible in addition to [`DecodeErr::TryReserve`]. 1910 /// 1911 /// # Panics 1912 /// 1913 /// `panic`s iff `output` does not have the _exact_ length needed to contain the decoded data. Note when `input` 1914 /// contains an invalid length, [`DecodeErr::EncodedLen`] is returned _not_ a `panic`. 1915 /// 1916 /// # Examples 1917 /// 1918 /// ``` 1919 /// # use base64url_nopad::DecodeErr; 1920 /// assert_eq!( 1921 /// base64url_nopad::decode_buffer_exact([0; 1].as_slice(), [0; 0].as_mut_slice()).unwrap_err(), 1922 /// DecodeErr::EncodedLen 1923 /// ); 1924 /// assert_eq!( 1925 /// base64url_nopad::decode_buffer_exact([0; 2].as_slice(), [0; 1].as_mut_slice()).unwrap_err(), 1926 /// DecodeErr::InvalidByte 1927 /// ); 1928 /// assert_eq!( 1929 /// base64url_nopad::decode_buffer_exact(b"-8", [0; 1].as_mut_slice()).unwrap_err(), 1930 /// DecodeErr::TrailingBits 1931 /// ); 1932 /// let mut buffer = [0; base64url_nopad::decode_len(b"C8Aa_A--91VZbx0".len()).unwrap()]; 1933 /// base64url_nopad::decode_buffer(b"C8Aa_A--91VZbx0", &mut buffer)?; 1934 /// assert_eq!(buffer, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]); 1935 /// // Uncommenting below will cause a `panic` since a larger output buffer than necessary is _not_ OK. 1936 /// // base64url_nopad::decode_buffer_exact(b"C8Aa_A--91VZbx0", &mut [0; 128])?; 1937 /// # Ok::<_, DecodeErr>(()) 1938 /// ``` 1939 #[expect( 1940 clippy::panic_in_result_fn, 1941 reason = "purpose of this function is to panic when output does not have the exact length needed" 1942 )] 1943 #[inline] 1944 pub const fn decode_buffer_exact(input: &[u8], output: &mut [u8]) -> Result<(), DecodeErr> { 1945 if let Some(output_len) = decode_len(input.len()) { 1946 assert!( 1947 output.len() == output_len, 1948 "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data" 1949 ); 1950 if let Err(e) = decode_buffer(input, output) { 1951 Err(e) 1952 } else { 1953 Ok(()) 1954 } 1955 } else { 1956 Err(DecodeErr::EncodedLen) 1957 } 1958 } 1959 #[cfg(test)] 1960 mod test { 1961 use super::MAX_ENCODE_INPUT_LEN; 1962 #[cfg(feature = "alloc")] 1963 use alloc::string::String; 1964 use rand::{Rng as _, SeedableRng as _, rngs::SmallRng}; 1965 #[expect( 1966 clippy::as_conversions, 1967 clippy::cast_possible_truncation, 1968 reason = "comment justifies correctness" 1969 )] 1970 #[cfg(any( 1971 target_pointer_width = "16", 1972 target_pointer_width = "32", 1973 target_pointer_width = "64", 1974 ))] 1975 #[ignore = "slow"] 1976 #[test] 1977 fn encode_decode_len() { 1978 assert_eq!(MAX_ENCODE_INPUT_LEN, 3 * (usize::MAX.div_ceil(4)) - 1); 1979 let mut rng = SmallRng::from_os_rng(); 1980 for _ in 0u32..10_000_000 { 1981 // `uN as usize` is fine since we `cfg` by pointer width. 1982 #[cfg(target_pointer_width = "16")] 1983 let len = rng.random::<u16>() as usize; 1984 #[cfg(target_pointer_width = "32")] 1985 let len = rng.random::<u32>() as usize; 1986 #[cfg(target_pointer_width = "64")] 1987 let len = rng.random::<u64>() as usize; 1988 if len <= MAX_ENCODE_INPUT_LEN { 1989 assert!( 1990 super::encode_len_checked(len) 1991 .is_some_and(|l| super::decode_len(l).is_some_and(|orig| orig == len)) 1992 ); 1993 } else { 1994 assert!(super::encode_len_checked(len).is_none()); 1995 } 1996 } 1997 for i in 0..1025 { 1998 assert!( 1999 super::encode_len_checked(i) 2000 .is_some_and(|l| super::decode_len(l).is_some_and(|orig| orig == i)) 2001 ); 2002 } 2003 #[cfg(target_pointer_width = "16")] 2004 for i in MAX_ENCODE_INPUT_LEN + 1.. { 2005 assert!(super::encode_len_checked(i).is_none()); 2006 } 2007 #[cfg(not(target_pointer_width = "16"))] 2008 for i in MAX_ENCODE_INPUT_LEN + 1..MAX_ENCODE_INPUT_LEN + 1_000_000 { 2009 assert!(super::encode_len_checked(i).is_none()); 2010 } 2011 assert!(super::encode_len_checked(usize::MAX).is_none()); 2012 assert!(super::encode_len_checked(MAX_ENCODE_INPUT_LEN).is_some_and(|l| l == usize::MAX)); 2013 for _ in 0u32..10_000_000 { 2014 #[cfg(target_pointer_width = "16")] 2015 let len = rng.random::<u16>() as usize; 2016 #[cfg(target_pointer_width = "32")] 2017 let len = rng.random::<u32>() as usize; 2018 #[cfg(target_pointer_width = "64")] 2019 let len = rng.random::<u64>() as usize; 2020 if len & 3 == 1 { 2021 assert!(super::decode_len(len).is_none()); 2022 } else { 2023 assert!( 2024 super::decode_len(len).is_some_and( 2025 |l| super::encode_len_checked(l).is_some_and(|orig| orig == len) 2026 ) 2027 ); 2028 } 2029 } 2030 for i in 0..1025 { 2031 if i & 3 == 1 { 2032 assert!(super::decode_len(i).is_none()); 2033 } else { 2034 assert!( 2035 super::decode_len(i).is_some_and( 2036 |l| super::encode_len_checked(l).is_some_and(|orig| orig == i) 2037 ) 2038 ); 2039 } 2040 } 2041 #[cfg(target_pointer_width = "16")] 2042 for i in 0..=usize::MAX { 2043 if i & 3 == 1 { 2044 assert!(super::decode_len(i).is_none()); 2045 } else { 2046 assert!(super::decode_len(i).is_some_and(|l| super::encode_len_checked(l).is_some_and(|orig| orig == i))); 2047 } 2048 } 2049 #[cfg(not(target_pointer_width = "16"))] 2050 for i in usize::MAX - 1_000_000..=usize::MAX { 2051 if i & 3 == 1 { 2052 assert!(super::decode_len(i).is_none()); 2053 } else { 2054 assert!(super::decode_len(i).is_some_and(|l| super::encode_len_checked(l).is_some_and(|orig| orig == i))); 2055 } 2056 } 2057 assert!(super::decode_len(usize::MAX).is_some_and(|l| l == MAX_ENCODE_INPUT_LEN)); 2058 } 2059 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 2060 #[cfg(feature = "alloc")] 2061 #[test] 2062 fn encode_write() { 2063 let input = [9; 8192]; 2064 let mut buffer = String::with_capacity(super::encode_len(input.len())); 2065 let cap = buffer.capacity(); 2066 let mut write_len; 2067 for len in 0..input.len() { 2068 write_len = super::encode_len(len); 2069 match write_len.checked_add(buffer.len()) { 2070 None => { 2071 buffer.clear(); 2072 // Indexing is fine since `len <= input.len()`. 2073 assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); 2074 assert_eq!(buffer.len(), write_len); 2075 } 2076 Some(l) => { 2077 if l > cap { 2078 buffer.clear(); 2079 // Indexing is fine since `len <= input.len()`. 2080 assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); 2081 assert_eq!(buffer.len(), write_len); 2082 } else { 2083 // Indexing is fine since `len <= input.len()`. 2084 assert_eq!(super::encode_write(&input[..len], &mut buffer), Ok(())); 2085 assert_eq!(buffer.len(), l); 2086 } 2087 } 2088 } 2089 assert!( 2090 buffer 2091 .as_bytes() 2092 .iter() 2093 .all(|b| { matches!(*b, b'C' | b'J' | b'Q' | b'k') }) 2094 ); 2095 } 2096 } 2097 }