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