lib.rs (58274B)
1 //! [![git]](https://git.philomathiclife.com/base64url_nopad/log.html) [![crates-io]](https://crates.io/crates/base64url_nopad) [![docs-rs]](crate) 2 //! 3 //! [git]: https://git.philomathiclife.com/git_badge.svg 4 //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 //! 7 //! `base64url_nopad` is a library for fast, efficient, correct, and `const` encoding and decoding of base64url 8 //! without padding data. All functions that can be `const` are `const`. Great care is made to ensure _all_ 9 //! arithmetic is free from "side effects" (e.g., overflow). `panic`s are avoided at all costs unless explicitly 10 //! documented _including_ `panic`s related to memory allocations. 11 //! 12 //! ## `base64url_nopad` in action 13 //! 14 //! ``` 15 //! # use base64url_nopad::DecodeErr; 16 //! /// Length of our input to encode. 17 //! const INPUT_LEN: usize = 259; 18 //! /// The base64url encoded value without padding of our input. 19 //! const ENCODED_VAL: &str = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_1MH0Q"; 20 //! let mut input = [0; INPUT_LEN]; 21 //! for i in 0..=255 { 22 //! input[usize::from(i)] = i; 23 //! } 24 //! input[256] = 83; 25 //! input[257] = 7; 26 //! input[258] = 209; 27 //! let mut output = [0; base64url_nopad::encode_len(INPUT_LEN)]; 28 //! assert_eq!(base64url_nopad::encode_buffer(&input, &mut output), ENCODED_VAL); 29 //! assert_eq!(base64url_nopad::decode_len(output.len()), Some(INPUT_LEN)); 30 //! base64url_nopad::decode_buffer(ENCODED_VAL.as_bytes(), &mut output[..INPUT_LEN])?; 31 //! assert_eq!(input, output[..INPUT_LEN]); 32 //! # Ok::<_, DecodeErr>(()) 33 //! ``` 34 //! 35 //! ## Cargo "features" 36 //! 37 //! ### `alloc` 38 //! 39 //! Enables support for memory allocations via [`alloc`]. 40 //! 41 //! ## Correctness of code 42 //! 43 //! This library is written in a way that is free from any overflow, underflow, or other kinds of 44 //! "arithmetic side effects". All functions that can `panic` are explicitly documented as such; and all 45 //! possible `panic`s are isolated to convenience functions that `panic` instead of error. Strict encoding and 46 //! decoding is performed; thus if an input contains _any_ invalid data, it is guaranteed to fail when decoding 47 //! it (e.g., trailing non-zero bits). 48 #![expect( 49 clippy::doc_paragraphs_missing_punctuation, 50 reason = "false positive for crate documentation having image links" 51 )] 52 #![cfg_attr( 53 all( 54 test, 55 target_os = "macos", 56 any( 57 target_pointer_width = "16", 58 target_pointer_width = "32", 59 target_pointer_width = "64" 60 ) 61 ), 62 allow(linker_info, reason = "getrandom causes linker-info to fire on macos") 63 )] 64 #![no_std] 65 #![cfg_attr(docsrs, feature(doc_cfg))] 66 #[cfg(any(doc, feature = "alloc"))] 67 extern crate alloc; 68 /// Unit tests. 69 #[cfg(test)] 70 mod tests; 71 #[cfg(any(doc, feature = "alloc"))] 72 use alloc::{collections::TryReserveError, string::String, vec::Vec}; 73 use core::{ 74 error::Error, 75 fmt::{self, Display, Formatter, Write}, 76 hint::cold_path, 77 mem, 78 }; 79 /// The base64url alphabet. 80 #[expect( 81 non_camel_case_types, 82 reason = "want to use a variant as close to what the value is" 83 )] 84 #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] 85 #[repr(u8)] 86 pub enum Alphabet { 87 /// A. 88 #[default] 89 A, 90 /// B. 91 B, 92 /// C. 93 C, 94 /// D. 95 D, 96 /// E. 97 E, 98 /// F. 99 F, 100 /// G. 101 G, 102 /// H. 103 H, 104 /// I. 105 I, 106 /// J. 107 J, 108 /// K. 109 K, 110 /// L. 111 L, 112 /// M. 113 M, 114 /// N. 115 N, 116 /// O. 117 O, 118 /// P. 119 P, 120 /// Q. 121 Q, 122 /// R. 123 R, 124 /// S. 125 S, 126 /// T. 127 T, 128 /// U. 129 U, 130 /// V. 131 V, 132 /// W. 133 W, 134 /// X. 135 X, 136 /// Y. 137 Y, 138 /// Z. 139 Z, 140 /// a. 141 a, 142 /// b. 143 b, 144 /// c. 145 c, 146 /// d. 147 d, 148 /// e. 149 e, 150 /// f. 151 f, 152 /// g. 153 g, 154 /// h. 155 h, 156 /// i. 157 i, 158 /// j. 159 j, 160 /// k. 161 k, 162 /// l. 163 l, 164 /// m. 165 m, 166 /// n. 167 n, 168 /// o. 169 o, 170 /// p. 171 p, 172 /// q. 173 q, 174 /// r. 175 r, 176 /// s. 177 s, 178 /// t. 179 t, 180 /// u. 181 u, 182 /// v. 183 v, 184 /// w. 185 w, 186 /// x. 187 x, 188 /// y. 189 y, 190 /// z. 191 z, 192 /// 0. 193 Zero, 194 /// 1. 195 One, 196 /// 2. 197 Two, 198 /// 3. 199 Three, 200 /// 4. 201 Four, 202 /// 5. 203 Five, 204 /// 6. 205 Six, 206 /// 7. 207 Seven, 208 /// 8. 209 Eight, 210 /// 9. 211 Nine, 212 /// -. 213 Hyphen, 214 /// _. 215 Underscore, 216 } 217 /// Sorted ASCII `u8`s for [`Alphabet`]. 218 const ASCII: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; 219 /// Sorted `char`s for [`Alphabet`]. 220 const CHARS: &[char; 64] = &[ 221 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 222 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 223 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', 224 '5', '6', '7', '8', '9', '-', '_', 225 ]; 226 /// [`Alphabet`] variants indexed based on the their ASCII representation. 227 const FROM_ASCII: &[Option<Alphabet>; 256] = &[ 228 None, 229 None, 230 None, 231 None, 232 None, 233 None, 234 None, 235 None, 236 None, 237 None, 238 None, 239 None, 240 None, 241 None, 242 None, 243 None, 244 None, 245 None, 246 None, 247 None, 248 None, 249 None, 250 None, 251 None, 252 None, 253 None, 254 None, 255 None, 256 None, 257 None, 258 None, 259 None, 260 None, 261 None, 262 None, 263 None, 264 None, 265 None, 266 None, 267 None, 268 None, 269 None, 270 None, 271 None, 272 None, 273 Some(Alphabet::Hyphen), 274 None, 275 None, 276 Some(Alphabet::Zero), 277 Some(Alphabet::One), 278 Some(Alphabet::Two), 279 Some(Alphabet::Three), 280 Some(Alphabet::Four), 281 Some(Alphabet::Five), 282 Some(Alphabet::Six), 283 Some(Alphabet::Seven), 284 Some(Alphabet::Eight), 285 Some(Alphabet::Nine), 286 None, 287 None, 288 None, 289 None, 290 None, 291 None, 292 None, 293 Some(Alphabet::A), 294 Some(Alphabet::B), 295 Some(Alphabet::C), 296 Some(Alphabet::D), 297 Some(Alphabet::E), 298 Some(Alphabet::F), 299 Some(Alphabet::G), 300 Some(Alphabet::H), 301 Some(Alphabet::I), 302 Some(Alphabet::J), 303 Some(Alphabet::K), 304 Some(Alphabet::L), 305 Some(Alphabet::M), 306 Some(Alphabet::N), 307 Some(Alphabet::O), 308 Some(Alphabet::P), 309 Some(Alphabet::Q), 310 Some(Alphabet::R), 311 Some(Alphabet::S), 312 Some(Alphabet::T), 313 Some(Alphabet::U), 314 Some(Alphabet::V), 315 Some(Alphabet::W), 316 Some(Alphabet::X), 317 Some(Alphabet::Y), 318 Some(Alphabet::Z), 319 None, 320 None, 321 None, 322 None, 323 Some(Alphabet::Underscore), 324 None, 325 Some(Alphabet::a), 326 Some(Alphabet::b), 327 Some(Alphabet::c), 328 Some(Alphabet::d), 329 Some(Alphabet::e), 330 Some(Alphabet::f), 331 Some(Alphabet::g), 332 Some(Alphabet::h), 333 Some(Alphabet::i), 334 Some(Alphabet::j), 335 Some(Alphabet::k), 336 Some(Alphabet::l), 337 Some(Alphabet::m), 338 Some(Alphabet::n), 339 Some(Alphabet::o), 340 Some(Alphabet::p), 341 Some(Alphabet::q), 342 Some(Alphabet::r), 343 Some(Alphabet::s), 344 Some(Alphabet::t), 345 Some(Alphabet::u), 346 Some(Alphabet::v), 347 Some(Alphabet::w), 348 Some(Alphabet::x), 349 Some(Alphabet::y), 350 Some(Alphabet::z), 351 None, 352 None, 353 None, 354 None, 355 None, 356 None, 357 None, 358 None, 359 None, 360 None, 361 None, 362 None, 363 None, 364 None, 365 None, 366 None, 367 None, 368 None, 369 None, 370 None, 371 None, 372 None, 373 None, 374 None, 375 None, 376 None, 377 None, 378 None, 379 None, 380 None, 381 None, 382 None, 383 None, 384 None, 385 None, 386 None, 387 None, 388 None, 389 None, 390 None, 391 None, 392 None, 393 None, 394 None, 395 None, 396 None, 397 None, 398 None, 399 None, 400 None, 401 None, 402 None, 403 None, 404 None, 405 None, 406 None, 407 None, 408 None, 409 None, 410 None, 411 None, 412 None, 413 None, 414 None, 415 None, 416 None, 417 None, 418 None, 419 None, 420 None, 421 None, 422 None, 423 None, 424 None, 425 None, 426 None, 427 None, 428 None, 429 None, 430 None, 431 None, 432 None, 433 None, 434 None, 435 None, 436 None, 437 None, 438 None, 439 None, 440 None, 441 None, 442 None, 443 None, 444 None, 445 None, 446 None, 447 None, 448 None, 449 None, 450 None, 451 None, 452 None, 453 None, 454 None, 455 None, 456 None, 457 None, 458 None, 459 None, 460 None, 461 None, 462 None, 463 None, 464 None, 465 None, 466 None, 467 None, 468 None, 469 None, 470 None, 471 None, 472 None, 473 None, 474 None, 475 None, 476 None, 477 None, 478 None, 479 None, 480 None, 481 None, 482 None, 483 None, 484 ]; 485 impl Alphabet { 486 /// Returns `Self` that corresponds to `b`. 487 /// 488 /// `Some` is returned iff `b` is in `0..=63`. 489 /// 490 /// # Examples 491 /// 492 /// ``` 493 /// # use base64url_nopad::Alphabet; 494 /// assert_eq!(Alphabet::from_u8(25), Some(Alphabet::Z)); 495 /// for i in 0..=63 { 496 /// assert!(Alphabet::from_u8(i).is_some()); 497 /// } 498 /// for i in 64..=255 { 499 /// assert!(Alphabet::from_u8(i).is_none()); 500 /// } 501 /// ``` 502 #[expect(unsafe_code, reason = "comment justifies correctness")] 503 #[expect(clippy::as_conversions, reason = "comment justifies correctness")] 504 #[inline] 505 #[must_use] 506 pub const fn from_u8(b: u8) -> Option<Self> { 507 // `Self` is `repr(u8)` and all `u8`s are valid from 0 until the maximum value 508 // represented by `Self::Underscore`. 509 if b <= Self::Underscore as u8 { 510 // SAFETY: 511 // Our safety precondition is that `b` is in-range. 512 Some(unsafe { mem::transmute::<u8, Self>(b) }) 513 } else { 514 None 515 } 516 } 517 /// Returns the `u8` `self` represents. 518 /// 519 /// # Examples 520 /// 521 /// ``` 522 /// # use base64url_nopad::Alphabet; 523 /// assert_eq!(Alphabet::Hyphen.to_u8(), 62); 524 /// assert_eq!(Alphabet::Eight.to_u8(), Alphabet::Eight as u8); 525 /// ``` 526 #[expect(clippy::as_conversions, reason = "comment justifies correctness")] 527 #[inline] 528 #[must_use] 529 pub const fn to_u8(self) -> u8 { 530 // `Self` is `repr(u8)`; thus this is correct. 531 self as u8 532 } 533 /// Returns the ASCII representation of `self`. 534 /// 535 /// # Examples 536 /// 537 /// ``` 538 /// # use base64url_nopad::Alphabet; 539 /// assert_eq!(Alphabet::c.to_ascii(), b'c'); 540 /// ``` 541 #[expect( 542 clippy::as_conversions, 543 clippy::indexing_slicing, 544 reason = "comments justify correctness" 545 )] 546 #[inline] 547 #[must_use] 548 pub const fn to_ascii(self) -> u8 { 549 // `u8 as usize` is always OK; and we want this to be `const` so can't rely on `usize::from`. 550 // `self.to_u8() < 64` and `ASCII.len() == 64`, so indexing can't `panic`. 551 ASCII[self.to_u8() as usize] 552 } 553 /// Returns `Some` iff `ascii` is the ASCII representation of `Self`. 554 /// 555 /// # Examples 556 /// 557 /// ``` 558 /// # use base64url_nopad::Alphabet; 559 /// for i in 0u8..=255 { 560 /// if i.is_ascii_alphanumeric() || i == b'-' || i == b'_' { 561 /// assert!(Alphabet::from_ascii(i).is_some()); 562 /// } else { 563 /// assert!(Alphabet::from_ascii(i).is_none()); 564 /// } 565 /// } 566 /// ``` 567 #[expect( 568 clippy::as_conversions, 569 clippy::indexing_slicing, 570 reason = "comments justify correctness" 571 )] 572 #[inline] 573 #[must_use] 574 pub const fn from_ascii(ascii: u8) -> Option<Self> { 575 // `u8 as usize` is always OK; and we want this to be `const` so can't rely on `usize::from`. 576 // `FROM_ASCII` has length 256, so indexing can't `panic`. 577 FROM_ASCII[ascii as usize] 578 } 579 /// Same as [`Self::to_ascii`] except a `char` is returned. 580 /// 581 /// # Examples 582 /// 583 /// ``` 584 /// # use base64url_nopad::Alphabet; 585 /// assert_eq!(Alphabet::J.to_char(), 'J'); 586 /// ``` 587 #[expect( 588 clippy::as_conversions, 589 clippy::indexing_slicing, 590 reason = "comments justify correctness" 591 )] 592 #[inline] 593 #[must_use] 594 pub const fn to_char(self) -> char { 595 // `u8 as usize` is always OK; and we want this to be `const` so can't rely on `usize::from`. 596 // `self.to_u8() < 64` and `CHARS.len() == 64`, so indexing can't `panic`. 597 CHARS[self.to_u8() as usize] 598 } 599 /// Same as [`Self::from_ascii`] except the input is a `char`. 600 /// 601 /// # Examples 602 /// 603 /// ``` 604 /// # use base64url_nopad::Alphabet; 605 /// for i in char::MIN..=char::MAX { 606 /// if i.is_ascii_alphanumeric() || i == '-' || i == '_' { 607 /// assert!(Alphabet::from_char(i).is_some()); 608 /// } else { 609 /// assert!(Alphabet::from_char(i).is_none()); 610 /// } 611 /// } 612 /// ``` 613 #[expect( 614 clippy::as_conversions, 615 clippy::cast_possible_truncation, 616 reason = "comments justify correctness" 617 )] 618 #[inline] 619 #[must_use] 620 pub const fn from_char(c: char) -> Option<Self> { 621 // `char as u32` is always OK. 622 let code_point = c as u32; 623 if code_point < 256 { 624 // We just verified `code_point` does not exceed `u8::MAX`, so `code_point as u8` is lossless. 625 Self::from_ascii(code_point as u8) 626 } else { 627 None 628 } 629 } 630 } 631 impl Display for Alphabet { 632 #[inline] 633 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 634 f.write_char(self.to_char()) 635 } 636 } 637 impl From<Alphabet> for u8 { 638 #[inline] 639 fn from(value: Alphabet) -> Self { 640 value.to_u8() 641 } 642 } 643 impl From<Alphabet> for u16 { 644 #[inline] 645 fn from(value: Alphabet) -> Self { 646 Self::from(value.to_u8()) 647 } 648 } 649 impl From<Alphabet> for u32 { 650 #[inline] 651 fn from(value: Alphabet) -> Self { 652 Self::from(value.to_u8()) 653 } 654 } 655 impl From<Alphabet> for u64 { 656 #[inline] 657 fn from(value: Alphabet) -> Self { 658 Self::from(value.to_u8()) 659 } 660 } 661 impl From<Alphabet> for u128 { 662 #[inline] 663 fn from(value: Alphabet) -> Self { 664 Self::from(value.to_u8()) 665 } 666 } 667 impl From<Alphabet> for char { 668 #[inline] 669 fn from(value: Alphabet) -> Self { 670 value.to_char() 671 } 672 } 673 /// The maximum value [`encode_len_checked`] will accept before returning `None`. 674 // This won't `panic` since `usize::MAX` ≢ 1 (mod 4). 675 pub const MAX_ENCODE_INPUT_LEN: usize = decode_len(usize::MAX).unwrap(); 676 /// Returns the exact number of bytes needed to encode an input of length `input_length`. 677 /// 678 /// `Some` is returned iff the length needed does not exceed [`usize::MAX`]. 679 /// 680 /// Note since Rust guarantees all memory allocations don't exceed [`isize::MAX`] bytes, then one can 681 /// instead call [`encode_len`] when the argument passed corresponds to the length of an allocation since 682 /// `isize::MAX <` [`MAX_ENCODE_INPUT_LEN`]. 683 /// 684 /// # Examples 685 /// 686 /// ``` 687 /// # use base64url_nopad::MAX_ENCODE_INPUT_LEN; 688 /// assert!(base64url_nopad::encode_len_checked(usize::MAX).is_none()); 689 /// assert!(base64url_nopad::encode_len_checked(MAX_ENCODE_INPUT_LEN + 1).is_none()); 690 /// assert_eq!(base64url_nopad::encode_len_checked(MAX_ENCODE_INPUT_LEN), Some(usize::MAX)); 691 /// assert_eq!(base64url_nopad::encode_len_checked(3), Some(4)); 692 /// assert_eq!(base64url_nopad::encode_len_checked(2), Some(3)); 693 /// assert_eq!(base64url_nopad::encode_len_checked(1), Some(2)); 694 /// assert_eq!(base64url_nopad::encode_len_checked(0), Some(0)); 695 /// ``` 696 #[expect( 697 clippy::arithmetic_side_effects, 698 clippy::integer_division, 699 clippy::integer_division_remainder_used, 700 reason = "proof and comment justifies their correctness" 701 )] 702 #[inline] 703 #[must_use] 704 pub const fn encode_len_checked(input_length: usize) -> Option<usize> { 705 // 256^n is the number of distinct values of the input. Let the base64 encoding in a URL safe 706 // way without padding of the input be O. There are 64 possible values each byte in O can be; thus we must find 707 // the minimum nonnegative integer m such that: 708 // 64^m = (2^6)^m = 2^(6m) >= 256^n = (2^8)^n = 2^(8n) 709 // <==> 710 // lg(2^(6m)) = 6m >= lg(2^(8n)) = 8n lg is defined on all positive reals which 2^(6m) and 2^(8n) are 711 // <==> 712 // m >= 8n/6 = 4n/3 713 // Clearly that corresponds to m = ⌈4n/3⌉. 714 // We claim ⌈4n/3⌉ = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉. 715 // Proof: 716 // There are three partitions for n: 717 // (1) 3i = n ≡ 0 (mod 3) for some integer i 718 // <==> 719 // ⌈4n/3⌉ = ⌈4(3i)/3⌉ = ⌈4i⌉ = 4i = 4⌊i⌋ = 4⌊3i/3⌋ = 4⌊n/3⌋ + 0 = 4⌊n/3⌋ + ⌈4(0)/3⌉ = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ 720 // (2) 3i + 1 = n ≡ 1 (mod 3) for some integer i 721 // <==> 722 // ⌈4n/3⌉ = ⌈4(3i + 1)/3⌉ = ⌈4i + 4/3⌉ = 4i + ⌈4/3⌉ = 4i + 2 = 4⌊i + 1/3⌋ + ⌈4(1)/3⌉ 723 // = 4⌊(3i + 1)/3⌋ + ⌈4((3i + 1) mod 3)/3⌉ 724 // = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ 725 // (3) 3i + 2 = n ≡ 2 (mod 3) for some integer i 726 // <==> 727 // ⌈4n/3⌉ = ⌈4(3i + 2)/3⌉ = ⌈4i + 8/3⌉ = 4i + ⌈8/3⌉ = 4i + 3 = 4⌊i + 2/3⌋ + ⌈4(2)/3⌉ 728 // = 4⌊(3i + 2)/3⌋ + ⌈4((3i + 2) mod 3)/3⌉ 729 // = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ 730 // QED 731 // Proof of no overflow: 732 // `MAX_ENCODE_INPUT_LEN` = decode_len(usize::MAX).unwrap(); thus all values less than or equal to 733 // `MAX_ENCODE_INPUT_LEN` won't overflow ignoring intermediate calcuations since ⌈4n/3⌉ is a 734 // monotonically increasing function. 735 // QED 736 // Naively implementing ⌈4n/3⌉ as (4 * n).div_ceil(3) can cause overflow due to `4 * n`; thus 737 // we implement the equivalent equation 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ instead: 738 // `(4 * (n / 3)) + (4 * (n % 3)).div_ceil(3)` since none of the intermediate calculations suffer 739 // from overflow. 740 if input_length <= MAX_ENCODE_INPUT_LEN { 741 // (n / 3) << 2 <= m <= usize::MAX; thus the left operand of + is fine. 742 // n % 3 <= 2 743 // <==> 744 // 4(n % 3) <= 8 < usize::MAX; thus (n % 3) << 2 is fine. 745 // <==> 746 // ⌈4(n % 3)/3⌉ <= 4(n % 3), so the right operand of + is fine. 747 // The sum is fine since 748 // m = ⌈4n/3⌉ = 4⌊n/3⌋ + ⌈4(n mod 3)/3⌉ = ((n / 3) << 2) + ((n % 3) << 2).div_ceil(3), and m <= usize::MAX. 749 Some(((input_length / 3) << 2) + ((input_length % 3) << 2).div_ceil(3)) 750 } else { 751 cold_path(); 752 None 753 } 754 } 755 /// Same as [`encode_len_checked`] except a `panic` occurs instead of `None` being returned. 756 /// 757 /// One should prefer this function over `encode_len_checked` when passing the length of a memory allocation 758 /// since such a length is guaranteed to succeed. 759 /// 760 /// # Panics 761 /// 762 /// `panic`s iff [`encode_len_checked`] returns `None`. 763 /// 764 /// # Examples 765 /// 766 /// ``` 767 /// # use base64url_nopad::MAX_ENCODE_INPUT_LEN; 768 /// // Uncommenting below will cause a `panic`. 769 /// // base64url_nopad::encode_len(usize::MAX - 4); 770 /// // Uncommenting below will cause a `panic`. 771 /// // base64url_nopad::encode_len(MAX_ENCODE_INPUT_LEN + 1); 772 /// assert_eq!(base64url_nopad::encode_len(MAX_ENCODE_INPUT_LEN), usize::MAX); 773 /// assert!(base64url_nopad::encode_len(isize::MAX as usize) > isize::MAX as usize); 774 /// assert_eq!(base64url_nopad::encode_len(3), 4); 775 /// assert_eq!(base64url_nopad::encode_len(2), 3); 776 /// assert_eq!(base64url_nopad::encode_len(1), 2); 777 /// assert_eq!(base64url_nopad::encode_len(0), 0); 778 /// ``` 779 #[expect(clippy::unwrap_used, reason = "comment justifies correctness")] 780 #[inline] 781 #[must_use] 782 pub const fn encode_len(input_length: usize) -> usize { 783 // A precondition for calling this function is to ensure `encode_len_checked` can't return `None`. 784 encode_len_checked(input_length).unwrap() 785 } 786 /// `const`-version of `unreachable`. 787 macro_rules! impossible { 788 ( $( $x:literal)? ) => { 789 { 790 cold_path(); 791 $( 792 panic!($x); 793 )? 794 } 795 }; 796 } 797 /// Encodes `input` into `output` re-interpreting the encoded subset of `output` as a `str` before returning it. 798 /// 799 /// `Some` is returned iff `output.len()` is large enough to write the encoded data into. 800 /// 801 /// Note since Rust guarantees all memory allocations don't exceed [`isize::MAX`] bytes, one can 802 /// instead call [`encode_buffer`] using a buffer whose length is at least as large as the value returned from 803 /// [`encode_len`] without fear of a `panic` and the benefit of getting a `str` instead of an `Option`. 804 /// 805 /// # Examples 806 /// 807 /// ``` 808 /// assert_eq!( 809 /// base64url_nopad::encode_buffer_checked([0; 0].as_slice(), [0; 0].as_mut_slice()).as_deref(), Some("") 810 /// ); 811 /// assert_eq!( 812 /// base64url_nopad::encode_buffer_checked([0; 1].as_slice(), [0; 2].as_mut_slice()).as_deref(), Some("AA") 813 /// ); 814 /// // A larger output buffer than necessary is OK. 815 /// assert_eq!( 816 /// base64url_nopad::encode_buffer_checked([1; 1].as_slice(), [0; 128].as_mut_slice()).as_deref(), Some("AQ") 817 /// ); 818 /// assert_eq!( 819 /// base64url_nopad::encode_buffer_checked( 820 /// [0xc9; 14].as_slice(), 821 /// [0; base64url_nopad::encode_len(14)].as_mut_slice() 822 /// ).as_deref(), 823 /// Some("ycnJycnJycnJycnJyck") 824 /// ); 825 /// assert!(base64url_nopad::encode_buffer_checked([0; 1].as_slice(), [0; 1].as_mut_slice()).is_none()); 826 /// ``` 827 #[expect(unsafe_code, reason = "comments justify correctness")] 828 #[expect( 829 clippy::missing_asserts_for_indexing, 830 reason = "trust the compiler to already optimize since we match on the length" 831 )] 832 #[expect( 833 clippy::arithmetic_side_effects, 834 clippy::as_conversions, 835 clippy::indexing_slicing, 836 reason = "comments justify correctness" 837 )] 838 #[inline] 839 pub const fn encode_buffer_checked<'a>(input: &[u8], output: &'a mut [u8]) -> Option<&'a mut str> { 840 // This won't `panic` since Rust guarantees that all memory allocations won't exceed `isize::MAX`. 841 let final_len = encode_len(input.len()); 842 if output.len() >= final_len { 843 let (mut chunks, rem) = input.as_chunks::<3>(); 844 let (mut fst, mut snd, mut third); 845 let mut output_idx = 0; 846 // There is a _substantial_ boost in performance if we chunk encode. 847 while let [first, ref rest @ ..] = *chunks { 848 (fst, snd, third) = (first[0], first[1], first[2]); 849 // We trim the last two bits and interpret `fst` as a 6-bit integer. 850 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work. 851 // `ASCII.len() == 64 > fst >> 2`, so indexing won't `panic`. 852 output[output_idx] = ASCII[(fst >> 2) as usize]; 853 // The two bits we trimmed are the first two bits of the next 6-bit integer. 854 output_idx += 1; 855 // We trim the last four bits and interpret `snd` as a 6-bit integer. 856 // The first two bits are the trailing 2 bits from the previous value. 857 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work. 858 // `ASCII.len() == 64 > ((fst & 3) << 4) | (snd >> 4)`, so indexing won't `panic`. 859 output[output_idx] = ASCII[(((fst & 3) << 4) | (snd >> 4)) as usize]; 860 output_idx += 1; 861 // We trim the last six bits and interpret `third` as a 6-bit integer. 862 // The first four bits are the trailing 4 bits from the previous value. 863 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work. 864 // `ASCII.len() == 64 > ((snd & 15) << 2) | (third >> 6)`, so indexing won't `panic`. 865 output[output_idx] = ASCII[(((snd & 15) << 2) | (third >> 6)) as usize]; 866 // Every third `u8` corresponds to a fourth base64url `u8`. 867 output_idx += 1; 868 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work. 869 // `ASCII.len() == 64 > (third & 63)`, so indexing won't `panic`. 870 output[output_idx] = ASCII[(third & 63) as usize]; 871 output_idx += 1; 872 chunks = rest; 873 } 874 match rem.len() { 875 0 => {} 876 1 => { 877 // `rem.len() == 1`, so indexing won't `panic`. 878 fst = rem[0]; 879 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work. 880 // `ASCII.len() == 64 > fst >> 2`, so indexing won't `panic`. 881 output[output_idx] = ASCII[(fst >> 2) as usize]; 882 // `ASCII.len() == 64 > (fst & 3) << 4`, so indexing won't `panic`. 883 output[output_idx + 1] = ASCII[((fst & 3) << 4) as usize]; 884 } 885 2 => { 886 // `rem.len() == 2`, so indexing won't `panic`. 887 (fst, snd) = (rem[0], rem[1]); 888 // `input.len()` is not a multiple of 3; thus we have to append a final `u8` containing the 889 // last bits. 890 // `u8 as usize` is always OK; and we want this to be `const` so `usize::from` won't work. 891 // `ASCII.len() == 64 > fst >> 2`, so indexing won't `panic`. 892 output[output_idx] = ASCII[(fst >> 2) as usize]; 893 // `ASCII.len() == 64 > ((fst & 3) << 4) | (snd >> 4)`, so indexing won't `panic`. 894 output[output_idx + 1] = ASCII[(((fst & 3) << 4) | (snd >> 4)) as usize]; 895 // `ASCII.len() == 64 > (snd & 15) << 2`, so indexing won't `panic`. 896 output[output_idx + 2] = ASCII[((snd & 15) << 2) as usize]; 897 } 898 _ => impossible!("there is a bug in core::slice::as_chunks"), 899 } 900 // SAFETY: 901 // We verified `output.len() >= final_len`. 902 let val = unsafe { output.split_at_mut_unchecked(final_len) }.0; 903 // SAFETY: 904 // `val` has the exact length needed to encode `input`, and all of the `u8`s in it 905 // are from `Alphabet::to_ascii` which is a subset of UTF-8; thus this is safe. 906 // Note the above is vacuously true when `val` is empty. 907 Some(unsafe { str::from_utf8_unchecked_mut(val) }) 908 } else { 909 cold_path(); 910 None 911 } 912 } 913 /// Same as [`encode_buffer_checked`] except a `panic` occurs instead of `None` being returned. 914 /// 915 /// # Panics 916 /// 917 /// `panic`s iff [`encode_buffer_checked`] returns `None` (i.e., the length of the output buffer is too small). 918 /// 919 /// # Examples 920 /// 921 /// ``` 922 /// assert_eq!( 923 /// base64url_nopad::encode_buffer([0; 0].as_slice(), [0; 0].as_mut_slice()), 924 /// "" 925 /// ); 926 /// assert_eq!( 927 /// base64url_nopad::encode_buffer([0; 1].as_slice(), [0; 2].as_mut_slice()), 928 /// "AA" 929 /// ); 930 /// // A larger output buffer than necessary is OK. 931 /// assert_eq!( 932 /// base64url_nopad::encode_buffer([255; 1].as_slice(), [0; 256].as_mut_slice()), 933 /// "_w" 934 /// ); 935 /// assert_eq!( 936 /// base64url_nopad::encode_buffer( 937 /// [0xc9; 14].as_slice(), 938 /// [0; base64url_nopad::encode_len(14)].as_mut_slice() 939 /// ), 940 /// "ycnJycnJycnJycnJyck" 941 /// ); 942 /// // The below will `panic` when uncommented since the supplied output buffer is too small. 943 /// // _ = base64url_nopad::encode_buffer([0; 1].as_slice(), [0; 1].as_mut_slice()); 944 /// ``` 945 #[expect(clippy::unwrap_used, reason = "comment justifies correctness")] 946 #[inline] 947 pub const fn encode_buffer<'a>(input: &[u8], output: &'a mut [u8]) -> &'a mut str { 948 // A precondition for calling this function is to ensure `encode_buffer_checked` can't return `None`. 949 encode_buffer_checked(input, output).unwrap() 950 } 951 /// Similar to [`encode_buffer`] except a `String` is returned instead using its buffer to write to. 952 /// 953 /// # Errors 954 /// 955 /// Errors iff an error occurs from allocating the capacity needed to contain the encoded data. 956 /// 957 /// # Examples 958 /// 959 /// ``` 960 /// # extern crate alloc; 961 /// # use alloc::collections::TryReserveError; 962 /// assert_eq!( 963 /// base64url_nopad::try_encode([0; 0].as_slice())?, 964 /// "" 965 /// ); 966 /// assert_eq!( 967 /// base64url_nopad::try_encode([0; 1].as_slice())?, 968 /// "AA" 969 /// ); 970 /// assert_eq!( 971 /// base64url_nopad::try_encode([128, 40, 3].as_slice())?, 972 /// "gCgD" 973 /// ); 974 /// assert_eq!( 975 /// base64url_nopad::try_encode([0x7b; 22].as_slice())?, 976 /// "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew" 977 /// ); 978 /// # Ok::<_, TryReserveError>(()) 979 /// ``` 980 #[cfg(feature = "alloc")] 981 #[expect(unsafe_code, reason = "comment justifies correctness")] 982 #[inline] 983 pub fn try_encode(input: &[u8]) -> Result<String, TryReserveError> { 984 let mut output = Vec::new(); 985 // `encode_len` won't `panic` since Rust guarantees `input.len()` will not return a value greater 986 // than `isize::MAX`. 987 let len = encode_len(input.len()); 988 output.try_reserve_exact(len).map(|()| { 989 output.resize(len, 0); 990 _ = encode_buffer(input, output.as_mut_slice()); 991 // SAFETY: 992 // `output` has the exact length needed to encode `input`, and all of the `u8`s in it 993 // are from `Alphabet` which is a subset of UTF-8; thus this is safe. 994 // Note the above is vacuously true when `output` is empty. 995 unsafe { String::from_utf8_unchecked(output) } 996 }) 997 } 998 /// Same as [`try_encode`] except a `panic` occurs on allocation failure. 999 /// 1000 /// # Panics 1001 /// 1002 /// `panic`s iff [`try_encode`] errors. 1003 /// 1004 /// # Examples 1005 /// 1006 /// ``` 1007 /// assert_eq!( 1008 /// base64url_nopad::encode([0; 0].as_slice()), 1009 /// "" 1010 /// ); 1011 /// assert_eq!( 1012 /// base64url_nopad::encode([0; 1].as_slice()), 1013 /// "AA" 1014 /// ); 1015 /// assert_eq!( 1016 /// base64url_nopad::encode([128, 40, 3].as_slice()), 1017 /// "gCgD" 1018 /// ); 1019 /// assert_eq!( 1020 /// base64url_nopad::encode([0x7b; 22].as_slice()), 1021 /// "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew" 1022 /// ); 1023 /// ``` 1024 #[cfg(feature = "alloc")] 1025 #[expect( 1026 clippy::unwrap_used, 1027 reason = "purpose of function is to panic on allocation failure" 1028 )] 1029 #[inline] 1030 #[must_use] 1031 pub fn encode(input: &[u8]) -> String { 1032 try_encode(input).unwrap() 1033 } 1034 /// Writes the base64url encoding of `input` using `writer`. 1035 /// 1036 /// Internally a buffer of at most 1024 bytes is used to write the encoded data. 1037 /// 1038 /// # Errors 1039 /// 1040 /// Errors iff [`Write::write_str`] does. 1041 /// 1042 /// # Panics 1043 /// 1044 /// `panic`s iff [`Write::write_str`] does. 1045 /// 1046 /// # Examples 1047 /// 1048 /// ``` 1049 /// # extern crate alloc; 1050 /// # use alloc::string::String; 1051 /// # use core::fmt::Error; 1052 /// let mut buffer = String::new(); 1053 /// base64url_nopad::encode_write([0; 0].as_slice(), &mut buffer)?; 1054 /// assert_eq!(buffer, ""); 1055 /// buffer.clear(); 1056 /// base64url_nopad::encode_write([0; 1].as_slice(), &mut buffer)?; 1057 /// assert_eq!(buffer, "AA"); 1058 /// buffer.clear(); 1059 /// base64url_nopad::encode_write( 1060 /// [0xc9; 14].as_slice(), 1061 /// &mut buffer, 1062 /// )?; 1063 /// assert_eq!(buffer, "ycnJycnJycnJycnJyck"); 1064 /// # Ok::<_, Error>(()) 1065 /// ``` 1066 #[expect(unsafe_code, reason = "comment justifies correctness")] 1067 #[expect( 1068 clippy::arithmetic_side_effects, 1069 clippy::as_conversions, 1070 clippy::indexing_slicing, 1071 reason = "comments justify correctness" 1072 )] 1073 #[inline] 1074 pub fn encode_write<W: Write>(mut input: &[u8], mut writer: W) -> fmt::Result { 1075 /// The max buffer size. 1076 /// 1077 /// This must be at least 4, no more than `i16::MAX`, and must be a power of 2. 1078 const MAX_BUFFER_LEN: usize = 1024; 1079 /// Want to ensure at compilation time that `MAX_BUFFER_LEN` upholds its invariants. Namely 1080 /// that it's at least as large as 4, doesn't exceed [`i16::MAX`], and is always a power of 2. 1081 const _: () = { 1082 // `i16::MAX <= usize::MAX`, so this is fine. 1083 /// `i16::MAX`. 1084 const MAX_LEN: usize = i16::MAX as usize; 1085 assert!( 1086 4 <= MAX_BUFFER_LEN && MAX_BUFFER_LEN < MAX_LEN && MAX_BUFFER_LEN.is_power_of_two(), 1087 "encode_write::MAX_BUFFER_LEN must be a power of two less than i16::MAX but at least as large as 4" 1088 ); 1089 }; 1090 /// The input size that corresponds to an encoded value of length `MAX_BUFFER_LEN`. 1091 // This will never `panic` since `MAX_BUFFER_LEN` is a power of two at least as large as 4 1092 // (i.e., `MAX_BUFFER_LEN` ≢ 1 (mod 4)). 1093 const INPUT_LEN: usize = decode_len(MAX_BUFFER_LEN).unwrap(); 1094 let mut buffer = [0; MAX_BUFFER_LEN]; 1095 // This won't `panic` since `input.len()` is guaranteed to be no more than `isize::MAX`. 1096 let len = encode_len(input.len()); 1097 if len <= MAX_BUFFER_LEN { 1098 // `buffer.len() == MAX_BUFFER_LEN >= len`, so indexing is fine. 1099 // `encode_buffer` won't `panic` since `len` is the exact number of bytes needed to encode 1100 // the data. 1101 writer.write_str(encode_buffer(input, &mut buffer[..len])) 1102 } else { 1103 let mut counter = 0; 1104 // `len / MAX_BUFFER_LEN` is equal to ⌊len / MAX_BUFFER_LEN⌋ since `MAX_BUFFER_LEN` is a power of two. 1105 // We can safely encode `term` chunks of `INPUT_LEN` length into `buffer`. 1106 let term = len >> MAX_BUFFER_LEN.trailing_zeros(); 1107 let mut input_buffer; 1108 while counter < term { 1109 // SAFETY: 1110 // `input.len() >= INPUT_LEN`. 1111 input_buffer = unsafe { input.split_at_unchecked(INPUT_LEN) }; 1112 // `encode_buffer` won't `panic` since `buffer` has length `MAX_BUFFER_LEN` which 1113 // is the exact length needed for `INPUT_LEN` length inputs which `input_buffer.0` is. 1114 writer.write_str(encode_buffer(input_buffer.0, buffer.as_mut_slice()))?; 1115 input = input_buffer.1; 1116 // `counter < term`, so overflow cannot happen. 1117 counter += 1; 1118 } 1119 // `encode_len` won't `panic` since `input.len() < MAX_ENCODE_INPUT_LEN`. 1120 // `input.len() < INPUT_LEN`; thus `encode_len(input.len()) < MAX_BUFFER_LEN = buffer.len()` so 1121 // indexing is fine. 1122 // `encode_buffer` won't `panic` since the buffer is the exact length needed to encode `input`. 1123 writer.write_str(encode_buffer(input, &mut buffer[..encode_len(input.len())])) 1124 } 1125 } 1126 /// Appends the base64url encoding of `input` to `s` returning the `str` that was appended. 1127 /// 1128 /// # Errors 1129 /// 1130 /// Errors iff an error occurs from allocating the capacity needed to append the encoded data. 1131 /// 1132 /// # Examples 1133 /// 1134 /// ``` 1135 /// # extern crate alloc; 1136 /// # use alloc::{collections::TryReserveError, string::String}; 1137 /// let mut buffer = String::new(); 1138 /// assert_eq!( 1139 /// base64url_nopad::try_encode_append([0; 0].as_slice(), &mut buffer)?, 1140 /// "" 1141 /// ); 1142 /// assert_eq!( 1143 /// base64url_nopad::try_encode_append([0; 1].as_slice(), &mut buffer)?, 1144 /// "AA" 1145 /// ); 1146 /// assert_eq!( 1147 /// base64url_nopad::try_encode_append([128, 40, 3].as_slice(), &mut buffer)?, 1148 /// "gCgD" 1149 /// ); 1150 /// assert_eq!(buffer, "AAgCgD"); 1151 /// assert_eq!( 1152 /// base64url_nopad::try_encode_append([0x7b; 22].as_slice(), &mut buffer)?, 1153 /// "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew" 1154 /// ); 1155 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"); 1156 /// # Ok::<_, TryReserveError>(()) 1157 /// ``` 1158 #[cfg(feature = "alloc")] 1159 #[expect(unsafe_code, reason = "comment justifies correctness")] 1160 #[expect( 1161 clippy::arithmetic_side_effects, 1162 clippy::indexing_slicing, 1163 reason = "comments justify correctness" 1164 )] 1165 #[inline] 1166 pub fn try_encode_append<'a>( 1167 input: &[u8], 1168 s: &'a mut String, 1169 ) -> Result<&'a mut str, TryReserveError> { 1170 // `encode_len` won't `panic` since Rust guarantees `input.len()` will return a value no larger 1171 // than `isize::MAX`. 1172 let additional_len = encode_len(input.len()); 1173 s.try_reserve_exact(additional_len).map(|()| { 1174 // SAFETY: 1175 // We only append base64url ASCII which is a subset of UTF-8, so this will remain valid UTF-8. 1176 let utf8 = unsafe { s.as_mut_vec() }; 1177 let original_len = utf8.len(); 1178 // Overflow can't happen; otherwise `s.try_reserve_exact` would have erred. 1179 utf8.resize(original_len + additional_len, 0); 1180 // `utf8.len() >= original_len`, so indexing is fine. 1181 // `encode_buffer` won't `panic` since `utf8[original_len..]` has length `additional_len` 1182 // which is the exact number of bytes needed to encode `input`. 1183 encode_buffer(input, &mut utf8[original_len..]) 1184 }) 1185 } 1186 /// Same as [`try_encode_append`] except the encoded `str` is not returned. 1187 /// 1188 /// # Errors 1189 /// 1190 /// Errors iff [`try_encode_append`] does. 1191 /// 1192 /// # Examples 1193 /// 1194 /// ``` 1195 /// # extern crate alloc; 1196 /// # use alloc::{collections::TryReserveError, string::String}; 1197 /// let mut buffer = String::new(); 1198 /// base64url_nopad::try_encode_append_only([0; 0].as_slice(), &mut buffer)?; 1199 /// assert_eq!(buffer, ""); 1200 /// base64url_nopad::try_encode_append_only([0; 1].as_slice(), &mut buffer)?; 1201 /// assert_eq!(buffer, "AA"); 1202 /// base64url_nopad::try_encode_append_only([128, 40, 3].as_slice(), &mut buffer)?; 1203 /// assert_eq!(buffer, "AAgCgD"); 1204 /// base64url_nopad::try_encode_append_only([0x7b; 22].as_slice(), &mut buffer)?; 1205 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"); 1206 /// # Ok::<_, TryReserveError>(()) 1207 /// ``` 1208 #[cfg(feature = "alloc")] 1209 #[inline] 1210 pub fn try_encode_append_only(input: &[u8], s: &mut String) -> Result<(), TryReserveError> { 1211 try_encode_append(input, s).map(|_| ()) 1212 } 1213 /// Same as [`try_encode_append`] except a `panic` occurs on allocation failure. 1214 /// 1215 /// # Panics 1216 /// 1217 /// `panic`s iff [`try_encode_append`] errors. 1218 /// 1219 /// # Examples 1220 /// 1221 /// ``` 1222 /// # extern crate alloc; 1223 /// # use alloc::{collections::TryReserveError, string::String}; 1224 /// let mut buffer = String::new(); 1225 /// assert_eq!( 1226 /// base64url_nopad::encode_append([0; 0].as_slice(), &mut buffer), 1227 /// "" 1228 /// ); 1229 /// assert_eq!( 1230 /// base64url_nopad::encode_append([0; 1].as_slice(), &mut buffer), 1231 /// "AA" 1232 /// ); 1233 /// assert_eq!( 1234 /// base64url_nopad::encode_append([128, 40, 3].as_slice(), &mut buffer), 1235 /// "gCgD" 1236 /// ); 1237 /// assert_eq!(buffer, "AAgCgD"); 1238 /// assert_eq!( 1239 /// base64url_nopad::encode_append([0x7b; 22].as_slice(), &mut buffer), 1240 /// "e3t7e3t7e3t7e3t7e3t7e3t7e3t7ew" 1241 /// ); 1242 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"); 1243 /// ``` 1244 #[cfg(feature = "alloc")] 1245 #[expect( 1246 clippy::unwrap_used, 1247 reason = "purpose of this function is to panic on allocation failure" 1248 )] 1249 #[inline] 1250 pub fn encode_append<'a>(input: &[u8], s: &'a mut String) -> &'a mut str { 1251 try_encode_append(input, s).unwrap() 1252 } 1253 /// Same as [`encode_append`] except the encoded `str` is not returned. 1254 /// 1255 /// # Panics 1256 /// 1257 /// `panic`s iff [`encode_append`] does. 1258 /// 1259 /// # Examples 1260 /// 1261 /// ``` 1262 /// # extern crate alloc; 1263 /// # use alloc::{collections::TryReserveError, string::String}; 1264 /// let mut buffer = String::new(); 1265 /// base64url_nopad::encode_append_only([0; 0].as_slice(), &mut buffer); 1266 /// assert_eq!(buffer, ""); 1267 /// base64url_nopad::encode_append_only([0; 1].as_slice(), &mut buffer); 1268 /// assert_eq!(buffer, "AA"); 1269 /// base64url_nopad::encode_append_only([128, 40, 3].as_slice(), &mut buffer); 1270 /// assert_eq!(buffer, "AAgCgD"); 1271 /// base64url_nopad::encode_append_only([0x7b; 22].as_slice(), &mut buffer); 1272 /// assert_eq!(buffer, "AAgCgDe3t7e3t7e3t7e3t7e3t7e3t7e3t7ew"); 1273 /// # Ok::<_, TryReserveError>(()) 1274 /// ``` 1275 #[cfg(feature = "alloc")] 1276 #[inline] 1277 pub fn encode_append_only(input: &[u8], s: &mut String) { 1278 _ = encode_append(input, s); 1279 } 1280 /// Returns the exact number of bytes needed to decode a base64url without padding input of length `input_length`. 1281 /// 1282 /// `Some` is returned iff `input_length` represents a possible length of a base64url without padding input. 1283 /// 1284 /// # Examples 1285 /// 1286 /// ``` 1287 /// # use base64url_nopad::MAX_ENCODE_INPUT_LEN; 1288 /// assert!(base64url_nopad::decode_len(1).is_none()); 1289 /// assert_eq!(base64url_nopad::decode_len(usize::MAX), Some(MAX_ENCODE_INPUT_LEN)); 1290 /// assert_eq!(base64url_nopad::decode_len(4), Some(3)); 1291 /// assert_eq!(base64url_nopad::decode_len(3), Some(2)); 1292 /// assert_eq!(base64url_nopad::decode_len(2), Some(1)); 1293 /// assert_eq!(base64url_nopad::decode_len(0), Some(0)); 1294 /// ``` 1295 #[expect( 1296 clippy::arithmetic_side_effects, 1297 reason = "proof and comment justifies their correctness" 1298 )] 1299 #[inline] 1300 #[must_use] 1301 pub const fn decode_len(input_length: usize) -> Option<usize> { 1302 // 64^n is the number of distinct values of the input. Let the decoded output be O. 1303 // There are 256 possible values each byte in O can be; thus we must find 1304 // the maximum nonnegative integer m such that: 1305 // 256^m = (2^8)^m = 2^(8m) <= 64^n = (2^6)^n = 2^(6n) 1306 // <==> 1307 // lg(2^(8m)) = 8m <= lg(2^(6n)) = 6n lg is defined on all positive reals which 2^(8m) and 2^(6n) are 1308 // <==> 1309 // m <= 6n/8 = 3n/4 1310 // Clearly that corresponds to m = ⌊3n/4⌋. 1311 // From the proof in `encode_len_checked`, we know that n is a valid length 1312 // iff n ≢ 1 (mod 4). 1313 // We claim ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋. 1314 // Proof: 1315 // There are three partitions for n: 1316 // (1) 4i = n ≡ 0 (mod 4) for some integer i 1317 // <==> 1318 // ⌊3n/4⌋ = ⌊3(4i)/4⌋ = ⌊3i⌋ = 3i = 3⌊i⌋ = 3⌊4i/4⌋ = 3⌊n/4⌋ + 0 = 3⌊n/4⌋ + ⌊3(0)/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ 1319 // (2) 4i + 2 = n ≡ 2 (mod 4) for some integer i 1320 // <==> 1321 // ⌊3n/4⌋ = ⌊3(4i + 2)/4⌋ = ⌊3i + 6/4⌋ = 3i + ⌊6/4⌋ = 3i + 1 = 3⌊i⌋ + ⌊3(2)/4⌋ 1322 // = 3⌊(4i + 2)/4⌋ + ⌊3((4i + 2) mod 4)/4⌋ 1323 // = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ 1324 // (3) 4i + 3 = n ≡ 3 (mod 4) for some integer i 1325 // <==> 1326 // ⌊3n/4⌋ = ⌊3(4i + 3)/4⌋ = ⌊3i + 9/4⌋ = 3i + ⌊9/4⌋ = 3i + 2 = 3⌊i⌋ + ⌊3(3)/4⌋ 1327 // = 3⌊(4i + 3)/4⌋ + ⌊3((4i + 3) mod 4)/4⌋ 1328 // = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ 1329 // QED 1330 // Naively implementing ⌊3n/4⌋ as (3 * n) / 4 can cause overflow due to `3 * n`; thus 1331 // we implement the equivalent equation 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ instead: 1332 // `(3 * (n / 4)) + ((3 * (n % 4)) / 4)` since none of the intermediate calculations suffer 1333 // from overflow. 1334 // `input_length % 4`. 1335 let rem = input_length & 3; 1336 if rem == 1 { 1337 None 1338 } else { 1339 // 3 * (n >> 2) <= m < usize::MAX; thus the left operand of + is fine. 1340 // rem <= 3 1341 // <==> 1342 // 3rem <= 9 < usize::MAX; thus 3 * rem is fine. 1343 // <==> 1344 // ⌊3rem/4⌋ <= 3rem, so the right operand of + is fine. 1345 // The sum is fine since 1346 // m = ⌊3n/4⌋ = 3⌊n/4⌋ + ⌊3(n mod 4)/4⌋ = (3 * (n >> 2)) + ((3 * rem) >> 2), and m < usize::MAX. 1347 Some((3 * (input_length >> 2)) + ((3 * rem) >> 2)) 1348 } 1349 } 1350 /// Error returned from [`decode_buffer`] and [`decode`]. 1351 /// 1352 /// Note when [`alloc`](./index.html#alloc) is not enabled, [`Copy`] is also implemented. 1353 #[derive(Clone, Debug, Eq, PartialEq)] 1354 pub enum DecodeErr { 1355 /// The encoded input had an invalid length. 1356 EncodedLen, 1357 /// The buffer supplied had a length that was too small to contain the decoded data. 1358 BufferLen, 1359 /// The encoded data contained trailing bits that were not zero. 1360 TrailingBits, 1361 /// The encoded data contained an invalid `u8`. 1362 InvalidByte, 1363 /// [`decode`] could not allocate enough memory to contain the decoded data. 1364 #[cfg(feature = "alloc")] 1365 TryReserve(TryReserveError), 1366 } 1367 #[cfg(not(feature = "alloc"))] 1368 impl Copy for DecodeErr {} 1369 impl Display for DecodeErr { 1370 #[inline] 1371 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1372 match *self { 1373 Self::EncodedLen => f.write_str("length of encoded data was invalid"), 1374 Self::BufferLen => { 1375 f.write_str("length of the output buffer is too small to contain the decoded data") 1376 } 1377 Self::TrailingBits => { 1378 f.write_str("encoded data contained trailing bits that were not zero") 1379 } 1380 Self::InvalidByte => f.write_str("encoded data contained an invalid byte"), 1381 #[cfg(feature = "alloc")] 1382 Self::TryReserve(ref err) => err.fmt(f), 1383 } 1384 } 1385 } 1386 impl Error for DecodeErr {} 1387 /// Decodes `input` into `output` returning the subset of `output` containing the decoded data. 1388 /// 1389 /// # Errors 1390 /// 1391 /// Errors iff [`decode_len`] of `input.len()` does not return `Some` containing a 1392 /// `usize` that does not exceed `ouput.len()` or `input` is an invalid base64url-encoded value without padding. 1393 /// Note [`DecodeErr::TryReserve`] will never be returned. 1394 /// 1395 /// # Examples 1396 /// 1397 /// ``` 1398 /// # use base64url_nopad::DecodeErr; 1399 /// assert_eq!(base64url_nopad::decode_buffer(b"", [0; 0].as_mut_slice())?, b""); 1400 /// assert_eq!( 1401 /// base64url_nopad::decode_buffer(b"A", [0; 0].as_mut_slice()).unwrap_err(), 1402 /// DecodeErr::EncodedLen 1403 /// ); 1404 /// assert_eq!( 1405 /// base64url_nopad::decode_buffer(b"A+".as_slice(), [0; 3].as_mut_slice()).unwrap_err(), 1406 /// DecodeErr::InvalidByte 1407 /// ); 1408 /// assert_eq!( 1409 /// base64url_nopad::decode_buffer(b"AA".as_slice(), [0; 0].as_mut_slice()).unwrap_err(), 1410 /// DecodeErr::BufferLen 1411 /// ); 1412 /// assert_eq!( 1413 /// base64url_nopad::decode_buffer(b"-8", [0; 3].as_mut_slice()).unwrap_err(), 1414 /// DecodeErr::TrailingBits 1415 /// ); 1416 /// // A larger output buffer than necessary is OK. 1417 /// assert_eq!(base64url_nopad::decode_buffer(b"C8Aa_A--91VZbx0", &mut [0; 128])?, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]); 1418 /// # Ok::<_, DecodeErr>(()) 1419 /// ``` 1420 #[expect(unsafe_code, reason = "comment justifies correctness")] 1421 #[expect( 1422 clippy::missing_asserts_for_indexing, 1423 reason = "trust the compiler to already optimize since we match on the length" 1424 )] 1425 #[expect( 1426 clippy::arithmetic_side_effects, 1427 clippy::indexing_slicing, 1428 reason = "comments justify correctness" 1429 )] 1430 #[expect(clippy::redundant_else, reason = "prefer the elses")] 1431 #[inline] 1432 pub const fn decode_buffer<'a>( 1433 input: &[u8], 1434 output: &'a mut [u8], 1435 ) -> Result<&'a mut [u8], DecodeErr> { 1436 let len = input.len(); 1437 if let Some(output_len) = decode_len(len) { 1438 if output.len() >= output_len { 1439 let mut output_idx = 0; 1440 let (mut chunks, rem) = input.as_chunks::<4>(); 1441 let (mut snd, mut third); 1442 // There is a _substantial_ boost in performance if we chunk decode. 1443 while let [first, ref rest @ ..] = *chunks { 1444 if let Some(base64_fst) = Alphabet::from_ascii(first[0]) 1445 && let Some(base64_snd) = Alphabet::from_ascii(first[1]) 1446 && let Some(base64_third) = Alphabet::from_ascii(first[2]) 1447 && let Some(base64_fourth) = Alphabet::from_ascii(first[3]) 1448 { 1449 (snd, third) = (base64_snd.to_u8(), base64_third.to_u8()); 1450 output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4); 1451 output_idx += 1; 1452 output[output_idx] = (snd << 4) | (third >> 2); 1453 output_idx += 1; 1454 output[output_idx] = (third << 6) | base64_fourth.to_u8(); 1455 output_idx += 1; 1456 chunks = rest; 1457 } else { 1458 return Err(DecodeErr::InvalidByte); 1459 } 1460 } 1461 match rem.len() { 1462 0 => {} 1463 1 => impossible!("there is a bug in base64url_nopad::decode_len"), 1464 2 => { 1465 if let Some(base64_fst) = Alphabet::from_ascii(rem[0]) 1466 && let Some(base64_snd) = Alphabet::from_ascii(rem[1]) 1467 { 1468 snd = base64_snd.to_u8(); 1469 if snd.trailing_zeros() < 4 { 1470 cold_path(); 1471 return Err(DecodeErr::TrailingBits); 1472 } else { 1473 output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4); 1474 } 1475 } else { 1476 cold_path(); 1477 return Err(DecodeErr::InvalidByte); 1478 } 1479 } 1480 3 => { 1481 if let Some(base64_fst) = Alphabet::from_ascii(rem[0]) 1482 && let Some(base64_snd) = Alphabet::from_ascii(rem[1]) 1483 && let Some(base64_third) = Alphabet::from_ascii(rem[2]) 1484 { 1485 (snd, third) = (base64_snd.to_u8(), base64_third.to_u8()); 1486 if third.trailing_zeros() < 2 { 1487 cold_path(); 1488 return Err(DecodeErr::TrailingBits); 1489 } else { 1490 output[output_idx] = (base64_fst.to_u8() << 2) | (snd >> 4); 1491 output[output_idx + 1] = (snd << 4) | (third >> 2); 1492 } 1493 } else { 1494 cold_path(); 1495 return Err(DecodeErr::InvalidByte); 1496 } 1497 } 1498 _ => impossible!("there is a bug in core::slice::as_chunks"), 1499 } 1500 // SAFETY: 1501 // `output.len() >= output_len`. 1502 Ok(unsafe { output.split_at_mut_unchecked(output_len) }.0) 1503 } else { 1504 cold_path(); 1505 Err(DecodeErr::BufferLen) 1506 } 1507 } else { 1508 Err(DecodeErr::EncodedLen) 1509 } 1510 } 1511 /// Similar to [`decode_buffer`] except a `Vec` is returned instead using its buffer to write to. 1512 /// 1513 /// # Errors 1514 /// 1515 /// Errors iff [`decode_buffer`] errors or an error occurs from allocating the capacity needed to contain 1516 /// the decoded data. Note [`DecodeErr::BufferLen`] is not possible to be returned. 1517 /// 1518 /// # Examples 1519 /// 1520 /// ``` 1521 /// # use base64url_nopad::DecodeErr; 1522 /// assert_eq!(base64url_nopad::decode([0; 0].as_slice())?, b""); 1523 /// assert_eq!( 1524 /// base64url_nopad::decode(b"A").unwrap_err(), 1525 /// DecodeErr::EncodedLen 1526 /// ); 1527 /// assert_eq!( 1528 /// base64url_nopad::decode(b"AA==").unwrap_err(), 1529 /// DecodeErr::InvalidByte 1530 /// ); 1531 /// assert_eq!( 1532 /// base64url_nopad::decode(b"-8").unwrap_err(), 1533 /// DecodeErr::TrailingBits 1534 /// ); 1535 /// assert_eq!(base64url_nopad::decode(b"C8Aa_A--91VZbx0")?, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]); 1536 /// # Ok::<_, DecodeErr>(()) 1537 /// ``` 1538 #[cfg(feature = "alloc")] 1539 #[inline] 1540 pub fn decode(input: &[u8]) -> Result<Vec<u8>, DecodeErr> { 1541 decode_len(input.len()) 1542 .ok_or(DecodeErr::EncodedLen) 1543 .and_then(|capacity| { 1544 let mut buffer = Vec::new(); 1545 buffer 1546 .try_reserve_exact(capacity) 1547 .map_err(DecodeErr::TryReserve) 1548 .and_then(|()| { 1549 buffer.resize(capacity, 0); 1550 if let Err(e) = decode_buffer(input, buffer.as_mut_slice()) { 1551 Err(e) 1552 } else { 1553 Ok(buffer) 1554 } 1555 }) 1556 }) 1557 } 1558 /// Similar to [`decode_buffer`] except the data is not decoded. 1559 /// 1560 /// In some situations, one does not want to actually decode data but merely validate that the encoded data 1561 /// is valid base64url without padding. Since data is not actually decoded, one avoids the need to allocate 1562 /// a large-enough buffer first. 1563 /// 1564 /// # Errors 1565 /// 1566 /// Errors iff `input` is an invalid base64url without padding. 1567 /// 1568 /// Note since no buffer is used to decode the data into, neither [`DecodeErr::BufferLen`] nor 1569 /// [`DecodeErr::TryReserve`] will ever be returned. 1570 /// 1571 /// # Examples 1572 /// 1573 /// ``` 1574 /// # use base64url_nopad::DecodeErr; 1575 /// base64url_nopad::validate_encoded_data(b"")?; 1576 /// assert_eq!( 1577 /// base64url_nopad::validate_encoded_data(b"A").unwrap_err(), 1578 /// DecodeErr::EncodedLen 1579 /// ); 1580 /// assert_eq!( 1581 /// base64url_nopad::validate_encoded_data(b"A/").unwrap_err(), 1582 /// DecodeErr::InvalidByte 1583 /// ); 1584 /// assert_eq!( 1585 /// base64url_nopad::validate_encoded_data(b"-8").unwrap_err(), 1586 /// DecodeErr::TrailingBits 1587 /// ); 1588 /// base64url_nopad::validate_encoded_data(b"C8Aa_A--91VZbx0")?; 1589 /// # Ok::<_, DecodeErr>(()) 1590 /// ``` 1591 #[expect( 1592 clippy::missing_asserts_for_indexing, 1593 reason = "trust the compiler to already optimize since we match on the length" 1594 )] 1595 #[expect(clippy::indexing_slicing, reason = "comments justify correctness")] 1596 #[inline] 1597 pub const fn validate_encoded_data(input: &[u8]) -> Result<(), DecodeErr> { 1598 let (mut chunks, rem) = input.as_chunks::<4>(); 1599 // There is a _substantial_ boost in performance if we chunk decode. 1600 while let [first, ref rest @ ..] = *chunks { 1601 if Alphabet::from_ascii(first[0]).is_some() 1602 && Alphabet::from_ascii(first[1]).is_some() 1603 && Alphabet::from_ascii(first[2]).is_some() 1604 && Alphabet::from_ascii(first[3]).is_some() 1605 { 1606 chunks = rest; 1607 } else { 1608 return Err(DecodeErr::InvalidByte); 1609 } 1610 } 1611 match rem.len() { 1612 0 => Ok(()), 1613 1 => { 1614 cold_path(); 1615 Err(DecodeErr::EncodedLen) 1616 } 1617 2 => { 1618 if Alphabet::from_ascii(rem[0]).is_some() 1619 && let Some(base64_snd) = Alphabet::from_ascii(rem[1]) 1620 { 1621 if base64_snd.to_u8().trailing_zeros() < 4 { 1622 cold_path(); 1623 Err(DecodeErr::TrailingBits) 1624 } else { 1625 Ok(()) 1626 } 1627 } else { 1628 cold_path(); 1629 Err(DecodeErr::InvalidByte) 1630 } 1631 } 1632 3 => { 1633 if Alphabet::from_ascii(rem[0]).is_some() 1634 && Alphabet::from_ascii(rem[1]).is_some() 1635 && let Some(base64_third) = Alphabet::from_ascii(rem[2]) 1636 { 1637 if base64_third.to_u8().trailing_zeros() < 2 { 1638 cold_path(); 1639 Err(DecodeErr::TrailingBits) 1640 } else { 1641 Ok(()) 1642 } 1643 } else { 1644 cold_path(); 1645 Err(DecodeErr::InvalidByte) 1646 } 1647 } 1648 _ => impossible!("there is a bug in core::slice::as_chunks"), 1649 } 1650 } 1651 /// Same as [`encode_buffer`] except `output` must have the _exact_ length needed to encode `input`, and the 1652 /// encoded `str` is not returned. 1653 /// 1654 /// # Panics 1655 /// 1656 /// `panic`s iff `output` does not have the _exact_ length needed to encode `input`. 1657 /// 1658 /// # Examples 1659 /// 1660 /// ``` 1661 /// let mut buffer = [0; 256]; 1662 /// base64url_nopad::encode_buffer_exact([0; 0].as_slice(), &mut buffer[..0]); 1663 /// base64url_nopad::encode_buffer_exact([0; 1].as_slice(), &mut buffer[..2]); 1664 /// assert_eq!(*b"AA", buffer[..2]); 1665 /// // Uncommenting below will cause a `panic` since the output buffer must be exact. 1666 /// // base64url_nopad::encode_buffer_exact([255; 1].as_slice(), &mut buffer); 1667 /// ``` 1668 #[inline] 1669 pub const fn encode_buffer_exact(input: &[u8], output: &mut [u8]) { 1670 assert!( 1671 // `encode_len` won't `panic` since Rust guarantees `input.len()` is at most `isize::MAX`. 1672 output.len() == encode_len(input.len()), 1673 "encode_buffer_exact must be passed an output buffer whose length is exactly the length needed to encode the data" 1674 ); 1675 _ = encode_buffer(input, output); 1676 } 1677 /// Same as [`decode_buffer`] except `output` must have the _exact_ length needed, and the decoded `slice` 1678 /// is not returned. 1679 /// 1680 /// # Errors 1681 /// 1682 /// Errors iff [`decode_buffer`] errors. Note that since a `panic` occurs when `output.len()` is not the 1683 /// exact length needed, [`DecodeErr::BufferLen`] is not possible in addition to [`DecodeErr::TryReserve`]. 1684 /// 1685 /// # Panics 1686 /// 1687 /// `panic`s iff `output` does not have the _exact_ length needed to contain the decoded data and `input` 1688 /// has a valid length (i.e., [`DecodeErr::EncodedLen`] is returned _not_ a `panic` when `input` has invalid 1689 /// length). 1690 /// 1691 /// # Examples 1692 /// 1693 /// ``` 1694 /// # use base64url_nopad::DecodeErr; 1695 /// assert_eq!( 1696 /// base64url_nopad::decode_buffer_exact(b"A", [0; 0].as_mut_slice()).unwrap_err(), 1697 /// DecodeErr::EncodedLen 1698 /// ); 1699 /// assert_eq!( 1700 /// base64url_nopad::decode_buffer_exact(b"+A", [0; 1].as_mut_slice()).unwrap_err(), 1701 /// DecodeErr::InvalidByte 1702 /// ); 1703 /// assert_eq!( 1704 /// base64url_nopad::decode_buffer_exact(b"-8", [0; 1].as_mut_slice()).unwrap_err(), 1705 /// DecodeErr::TrailingBits 1706 /// ); 1707 /// let mut buffer = [0; base64url_nopad::decode_len(b"C8Aa_A--91VZbx0".len()).unwrap()]; 1708 /// base64url_nopad::decode_buffer_exact(b"C8Aa_A--91VZbx0", &mut buffer)?; 1709 /// assert_eq!(buffer, [0x0b, 0xc0, 0x1a, 0xfc, 0x0f, 0xbe, 0xf7, b'U', b'Y', b'o', 0x1d]); 1710 /// // Uncommenting below will cause a `panic` since a larger output buffer than necessary is _not_ OK. 1711 /// // base64url_nopad::decode_buffer_exact(b"C8Aa_A--91VZbx0", &mut [0; 128])?; 1712 /// # Ok::<_, DecodeErr>(()) 1713 /// ``` 1714 #[expect( 1715 clippy::panic, 1716 clippy::panic_in_result_fn, 1717 reason = "purpose of this function is to panic when output does not have the exact length needed" 1718 )] 1719 #[inline] 1720 pub const fn decode_buffer_exact(input: &[u8], output: &mut [u8]) -> Result<(), DecodeErr> { 1721 let output_len = output.len(); 1722 match decode_buffer(input, output) { 1723 Ok(v) => { 1724 assert!( 1725 v.len() == output_len, 1726 "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data" 1727 ); 1728 Ok(()) 1729 } 1730 Err(e) => { 1731 if matches!(e, DecodeErr::BufferLen) { 1732 cold_path(); 1733 panic!( 1734 "decode_buffer_exact must be passed an output buffer whose length is exactly the length needed to decode the data" 1735 ); 1736 } else { 1737 Err(e) 1738 } 1739 } 1740 } 1741 }