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