c32/lib.rs
1// © 2025 Max Karou. All Rights Reserved.
2// Licensed under Apache Version 2.0, or MIT License, at your discretion.
3//
4// Apache License: http://www.apache.org/licenses/LICENSE-2.0
5// MIT License: http://opensource.org/licenses/MIT
6//
7// Usage of this file is permitted solely under a sanctioned license.
8
9#![no_std]
10#![allow(clippy::doc_markdown)]
11#![allow(clippy::wildcard_imports)]
12#![allow(clippy::missing_errors_doc)]
13#![allow(clippy::items_after_statements)]
14#![cfg_attr(docsrs, feature(doc_auto_cfg))]
15#![cfg_attr(docsrs, feature(doc_alias))]
16
17//! [][Crates.io]
18//! [][Docs.rs]
19//! [][Workflow]
20//! [][License-Apache]
21//! [][License-MIT]
22//!
23//! Rust implementation of [Crockford's Base32][Crockford] encoding scheme.
24//!
25//! ## Implementation
26//!
27//! * **Lightweight** — The core functionality has zero external dependencies.
28//! * **Portable** — Fully compatible with `#![no_std]` environments.
29//! * **Safe** — Implemented entirely in safe Rust with no `unsafe` blocks.
30//!
31//! ```rust
32//! # #[cfg(feature = "alloc")] {
33//! let bytes = b"usque ad finem";
34//! let encoded = c32::encode(&bytes);
35//! assert_eq!(encoded, "1TQ6WBNCMG62S10CSMPWSBD");
36//! # }
37//! # Ok::<(), c32::Error>(())
38//! ```
39//!
40//! ```rust
41//! # #[cfg(feature = "alloc")] {
42//! let bytes = b"usque ad finem";
43//! let decoded = c32::decode("1TQ6WBNCMG62S10CSMPWSBD")?;
44//! assert_eq!(decoded, bytes);
45//! # }
46//! # Ok::<(), c32::Error>(())
47//! ```
48//!
49//! ## In `#![no_std]` Environments
50//!
51//! For environments without allocation support, the library provides
52//! buffer-based APIs:
53//!
54//! ```rust
55//! // encoding with a pre-allocated buffer
56//! let bytes = b"usque ad finem";
57//! let mut buffer = [0; 32];
58//!
59//! let written = c32::encode_into(bytes, &mut buffer)?;
60//! let encoded = &buffer[..written];
61//! assert_eq!(encoded, b"1TQ6WBNCMG62S10CSMPWSBD");
62//! # Ok::<(), c32::Error>(())
63//! ```
64//!
65//! ```rust
66//! // decoding with a pre-allocated buffer
67//! let encoded = b"1TQ6WBNCMG62S10CSMPWSBD";
68//! let mut buffer = [0; 32];
69//!
70//! let written = c32::decode_into(encoded, &mut buffer)?;
71//! let decoded = &buffer[..written];
72//! assert_eq!(decoded, b"usque ad finem");
73//! # Ok::<(), c32::Error>(())
74//! ```
75//!
76//! ## Checksums
77//!
78//! The `check` feature enables methods for encoding data with SHA256-based
79//! checksum verification.
80//!
81//! The encoded data follows this layout:
82//! ```text
83//! [version (1B)] + [payload (nB)] + [checksum (4B)]
84//! ```
85//!
86//! And is computed by...
87//! ```text
88//! 1. Concatenating the version byte with the payload bytes.
89//! 2. Taking the SHA256 hash of the concatenated bytes.
90//! 3. Taking the SHA256 hash of the result.
91//! 4. Using the first 4 bytes as the checksum.
92//! ```
93//!
94//! ### Examples
95//! ```rust
96//! # #[cfg(all(feature = "check", feature = "alloc"))] {
97//! let bytes = b"usque ad finem";
98//! let encoded = c32::encode_check(bytes, 22)?;
99//! assert_eq!(encoded, "P7AWVHENJJ0RB441K6JVK5DNJ7J3V5");
100//! # }
101//! # Ok::<(), c32::Error>(())
102//! ```
103//! ```rust
104//! # #[cfg(all(feature = "check", feature = "alloc"))] {
105//! let encoded = "P7AWVHENJJ0RB441K6JVK5DNJ7J3V5";
106//! let (decoded, version) = c32::decode_check(encoded)?;
107//! assert_eq!(decoded, b"usque ad finem");
108//! assert_eq!(version, 22);
109//! # }
110//! # Ok::<(), c32::Error>(())
111//! ```
112//!
113//! # Features
114//!
115//! Feature | Description
116//! ---------|-------------------------------------------------------------
117//! `alloc` | Allocation-based API via [`encode`] and [`decode`]
118//! `check` | Support for checksum validation
119//!
120//! For more details, please refer to the full [API Reference][Docs.rs].
121//!
122//! [Crates.io]: https://crates.io/crates/c32
123//! [Docs.rs]: https://docs.rs/c32
124//! [Workflow]: https://github.com/52/c32/actions
125//! [License-Apache]: https://opensource.org/licenses/Apache-2.0
126//! [License-MIT]: https://opensource.org/licenses/MIT
127//! [Crockford]: https://www.crockford.com/base32.html
128
129#[cfg(feature = "alloc")]
130extern crate alloc;
131
132use core::error;
133use core::fmt;
134use core::marker;
135use core::slice;
136use core::str;
137
138/// Re-exports for feature compatibility.
139///
140/// This module exports common allocation types.
141#[cfg(feature = "alloc")]
142pub(crate) mod __private {
143 pub use alloc::string::String;
144 pub use alloc::vec;
145 pub use alloc::vec::Vec;
146}
147
148#[cfg(feature = "alloc")]
149pub(crate) use __private::*;
150
151/// This module provides methods for computing [`SHA-256`] checksums.
152///
153/// [`SHA-256`]: https://helix.stormhub.org/papers/SHA-256.pdf
154#[cfg(feature = "check")]
155pub mod checksum {
156 use sha2::Sha256;
157
158 use super::*;
159
160 /// Length of the [`Checksum`] in bytes.
161 pub const BYTE_LENGTH: usize = 4;
162
163 /// A type alias for a [`Sha256`] checksum.
164 pub type Checksum = [u8; BYTE_LENGTH];
165
166 /// Computes a 4-byte [`Checksum`]from a byte array and version.
167 ///
168 /// The checksum is computed by:
169 ///
170 /// 1. Concatenating the version byte with the payload bytes.
171 /// 2. Taking the SHA256 hash of the concatenated bytes.
172 /// 3. Taking the SHA256 hash of the result.
173 /// 4. Using the first 4 bytes as the checksum.
174 ///
175 /// # Examples
176 ///
177 /// ```rust
178 /// use c32::checksum;
179 ///
180 /// let bytes = [42, 42, 42];
181 /// let sum = checksum::compute(&bytes, 0);
182 /// assert_eq!(sum.len(), 4);
183 /// ```
184 #[inline]
185 #[must_use]
186 pub const fn compute(bytes: &[u8], version: u8) -> Checksum {
187 let buffer = Sha256::new().update(&[version]).update(bytes).finalize();
188 let hash = Sha256::new().update(&buffer).finalize();
189 from_slice(&hash)
190 }
191
192 /// Creates a [`Checksum`] from a byte slice.
193 ///
194 /// # Examples
195 ///
196 /// ```rust
197 /// use c32::checksum;
198 ///
199 /// let hash = [1, 2, 3, 4, 5, 6, 7, 8];
200 /// let sum = checksum::from_slice(&hash);
201 /// assert_eq!(sum, [1, 2, 3, 4]);
202 /// ```
203 #[inline]
204 #[must_use]
205 pub const fn from_slice(bytes: &[u8]) -> Checksum {
206 let mut sum = [0u8; BYTE_LENGTH];
207 __internal::memcpy(&mut sum, 0, bytes, 0, BYTE_LENGTH);
208 sum
209 }
210}
211
212/// The Crockford Base32 alphabet used for encoding and decoding.
213pub(crate) const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
214
215/// A mapping from ASCII characters to their Crockford Base32 values.
216pub(crate) const BYTE_MAP: [i8; 128] = [
217 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
218 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
219 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1,
220 -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20,
221 21, 0, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1,
222 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25,
223 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1,
224];
225
226/// Error variants for fallible Crockford Base32 operations.
227#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
228pub enum Error {
229 /// The buffer size is insufficient for the operation.
230 ///
231 /// # Fields
232 ///
233 /// * `min` - The minimum required buffer size.
234 /// * `len` - The actual size of the provided buffer.
235 BufferTooSmall { min: usize, len: usize },
236 /// The input data size doesn't match the expected size.
237 ///
238 /// # Fields
239 ///
240 /// * `expected` - The expected data size in bytes.
241 /// * `got` - The actual size of the provided data.
242 InvalidDataSize { expected: usize, got: usize },
243 /// An invalid character was encountered during decoding.
244 ///
245 /// # Fields
246 ///
247 /// * `char` - The invalid character found in the input.
248 /// * `index` - The byte index of the character.
249 InvalidCharacter { char: char, index: usize },
250 /// The expected prefix character is missing.
251 ///
252 /// # Fields
253 ///
254 /// * `char` - The expected prefix character.
255 /// * `got` - The actual first character found.
256 MissingPrefix { char: char, got: Option<char> },
257 #[cfg(feature = "check")]
258 /// The provided version byte is invalid.
259 ///
260 /// # Fields
261 ///
262 /// * `expected` - The expected version constraints.
263 /// * `version` - The invalid version byte.
264 InvalidVersion { expected: &'static str, version: u8 },
265 /// The input has fewer bytes than are required.
266 ///
267 /// # Fields
268 ///
269 /// * `min` - The minimum required amount of bytes.
270 /// * `len` - The actual number of bytes provided.
271 #[cfg(feature = "check")]
272 InsufficientData { min: usize, len: usize },
273 /// The computed checksum does not match the expected sum.
274 ///
275 /// # Fields
276 ///
277 /// * `expected` - The expected checksum.
278 /// * `got` - The actual checksum.
279 #[cfg(feature = "check")]
280 ChecksumMismatch {
281 expected: checksum::Checksum,
282 got: checksum::Checksum,
283 },
284}
285
286impl fmt::Display for Error {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 match self {
289 Self::BufferTooSmall { min, len } => {
290 write!(f, "Buffer size '{len}' is less than required '{min}'")
291 }
292 Self::InvalidDataSize { expected, got } => {
293 write!(f, "Invalid data size '{got}', expected: '{expected}'")
294 }
295 Self::InvalidCharacter { char, index } => {
296 write!(f, "Invalid character '{char}' at position {index}")
297 }
298 Self::MissingPrefix { char, got } => {
299 write!(f, "Expected prefix '{char}', found '{got:?}'")
300 }
301 #[cfg(feature = "check")]
302 Self::InvalidVersion { expected, version } => {
303 write!(f, "Invalid version byte '{version}': {expected}")
304 }
305 #[cfg(feature = "check")]
306 Self::InsufficientData { min, len } => {
307 write!(f, "Input size '{len}' is less than required '{min}'")
308 }
309 #[cfg(feature = "check")]
310 Self::ChecksumMismatch { expected, got } => {
311 write!(f, "Expected checksum '{expected:?}', got '{got:?}'")
312 }
313 }
314 }
315}
316
317impl error::Error for Error {}
318
319/// Result type for fallible Crockford Base32 operations.
320pub type Result<T> = core::result::Result<T, Error>;
321
322/// A marker trait for Crockford Base32 variations.
323///
324/// # Generics
325///
326/// * `PREFIX` - Whether to include a prefix character.
327pub trait Encoding<const PREFIX: bool> {}
328
329/// [`Encoding`] implementations.
330///
331/// This module exports various [`Encoding`] types.
332pub mod en {
333 use super::*;
334
335 /// Default Crockford Base32 encoding.
336 ///
337 /// # Generics
338 ///
339 /// * `PREFIX` - Whether to include a prefix character.
340 ///
341 /// # Examples
342 ///
343 /// ```rust
344 /// use c32::en::Default;
345 /// use c32::Buffer;
346 ///
347 /// // Default encoding w/o prefix
348 /// let en = Buffer::<5, false, Default>::encode(&[42, 42, 42]);
349 /// assert_eq!(en.as_str(), "2MAHA");
350 ///
351 /// // Default encoding with prefix
352 /// let en = Buffer::<6, true, Default>::encode(&[42, 42, 42], 'S');
353 /// assert_eq!(en.as_str(), "S2MAHA");
354 /// ```
355 pub struct Default;
356 impl<const PREFIX: bool> Encoding<PREFIX> for Default {}
357
358 #[cfg(feature = "check")]
359 mod __check {
360 use super::*;
361
362 /// Crockford Base32 encoding with checksum validation.
363 ///
364 /// This encoding format includes a 4-byte `Checksum`.
365 ///
366 /// # Generics
367 ///
368 /// * `PREFIX` - Whether to include a prefix character.
369 ///
370 /// # Examples
371 ///
372 /// ```rust
373 /// use c32::en::Check;
374 /// use c32::Buffer;
375 ///
376 /// // Check encoding w/o prefix
377 /// let en = Buffer::<13, false, Check>::encode(&[42, 42, 42], 0);
378 /// assert_eq!(en.as_str(), "0AHA59B9201Z");
379 ///
380 /// // Check encoding with prefix
381 /// let en = Buffer::<14, true, Check>::encode(&[42, 42, 42], 'S', 0);
382 /// assert_eq!(en.as_str(), "S0AHA59B9201Z");
383 /// ```
384 pub struct Check;
385 impl<const PREFIX: bool> Encoding<PREFIX> for Check {}
386 }
387
388 #[cfg(feature = "check")]
389 pub use __check::*;
390}
391
392/// A fixed-size buffer for encoding or decoding Crockford's Base32.
393///
394/// [`Buffer`] manages a fixed-size array of bytes and tracks the number of
395/// bytes written during encoding and decoding operations, and is primarily
396/// designed for use in a constant context.
397///
398/// # Generics
399///
400/// * `LEN` - The size of the byte array in bytes.
401/// * `PREFIX` - Whether to include a prefix character, defaults to `false`.
402/// * `E` - The [`Encoding`] format to use, defaults to [`en::Default`].
403///
404/// # Examples
405///
406/// ```rust
407/// use c32::Buffer;
408///
409/// const BYTES: [u8; 3] = [42, 42, 42];
410/// const EN: Buffer<5> = Buffer::<5>::encode(&BYTES);
411/// assert_eq!(EN.as_str(), "2MAHA");
412///
413/// const DE: Buffer<5> = Buffer::<5>::decode(EN.as_bytes());
414/// assert_eq!(DE.as_bytes(), [42, 42, 42]);
415/// ```
416pub struct Buffer<
417 const LEN: usize,
418 const PREFIX: bool = false,
419 E: Encoding<PREFIX> = en::Default,
420> {
421 /// The underlying byte array.
422 __raw: [u8; LEN],
423 /// The number of written bytes to the buffer.
424 __pos: usize,
425 /// The associated [`Encoding`] format `E`.
426 __marker: marker::PhantomData<E>,
427}
428
429impl<const LEN: usize, const PREFIX: bool, E: Encoding<PREFIX>>
430 Buffer<LEN, PREFIX, E>
431{
432 /// An empty buffer with no bytes written.
433 ///
434 /// # Examples
435 ///
436 /// ```rust
437 /// use c32::Buffer;
438 ///
439 /// let buffer = Buffer::<10>::EMPTY;
440 /// assert_eq!(buffer.pos(), 0);
441 /// assert_eq!(buffer.as_bytes(), &[]);
442 /// ```
443 pub const EMPTY: Self = Self {
444 __raw: [0u8; LEN],
445 __pos: 0,
446 __marker: marker::PhantomData,
447 };
448
449 /// Creates a new [`Buffer`].
450 ///
451 /// This is an internal method.
452 const fn new(__raw: [u8; LEN], __pos: usize) -> Self {
453 Self {
454 __raw,
455 __pos,
456 __marker: marker::PhantomData,
457 }
458 }
459
460 /// Returns the number of bytes written to the buffer.
461 ///
462 /// # Examples
463 ///
464 /// ```rust
465 /// use c32::Buffer;
466 ///
467 /// const INPUT: [u8; 3] = [42, 42, 42];
468 /// const EN: Buffer<5> = Buffer::<5>::encode(&INPUT);
469 /// assert_eq!(EN.pos(), 5);
470 /// ```
471 #[inline]
472 #[must_use]
473 pub const fn pos(&self) -> usize {
474 self.__pos
475 }
476
477 /// Returns a string slice of the written bytes.
478 ///
479 /// # Examples
480 ///
481 /// ```rust
482 /// use c32::Buffer;
483 ///
484 /// const INPUT: [u8; 3] = [42, 42, 42];
485 /// const EN: Buffer<5> = Buffer::<5>::encode(&INPUT);
486 /// assert_eq!(EN.as_str(), "2MAHA");
487 /// ```
488 #[inline]
489 #[must_use]
490 pub const fn as_str(&self) -> &str {
491 // SAFETY: We only push valid UTF-8 to `self.__raw`.
492 unsafe { str::from_utf8_unchecked(self.as_bytes()) }
493 }
494
495 /// Returns a slice of the written bytes.
496 ///
497 /// # Examples
498 ///
499 /// ```rust
500 /// use c32::Buffer;
501 ///
502 /// const INPUT: [u8; 3] = [42, 42, 42];
503 /// const EN: Buffer<5> = Buffer::<5>::encode(&INPUT);
504 /// assert_eq!(EN.as_bytes(), b"2MAHA");
505 /// ```
506 #[inline]
507 #[must_use]
508 pub const fn as_bytes(&self) -> &[u8] {
509 // SAFETY: `self.__pos` is always within bounds.
510 unsafe { slice::from_raw_parts(self.as_ptr(), self.__pos) }
511 }
512
513 /// Returns a raw pointer to the buffer's data.
514 ///
515 /// # Examples
516 ///
517 /// ```rust
518 /// # use c32::Buffer;
519 /// let buffer = Buffer::<10, false>::EMPTY;
520 /// let ptr = buffer.as_ptr();
521 /// ```
522 #[inline]
523 #[must_use]
524 pub const fn as_ptr(&self) -> *const u8 {
525 self.__raw.as_ptr()
526 }
527}
528
529impl<const N: usize> Buffer<N, false, en::Default> {
530 /// Encodes a byte array into a [`Buffer`].
531 ///
532 /// # Examples
533 ///
534 /// ```rust
535 /// use c32::Buffer;
536 ///
537 /// const INPUT: [u8; 3] = [42, 42, 42];
538 /// const EN: Buffer<5> = Buffer::<5>::encode(&INPUT);
539 /// assert_eq!(EN.as_str(), "2MAHA");
540 /// ```
541 #[inline]
542 #[must_use]
543 pub const fn encode<const M: usize>(src: &[u8; M]) -> Self {
544 const { assert!(N >= encoded_len(M), "Size 'N' is too small") }
545
546 // Allocate the output buffer.
547 let mut __raw = [0u8; N];
548
549 // Encode the input into the buffer.
550 let __pos = __internal::en(src, 0, M, &mut __raw, 0, None);
551
552 Self::new(__raw, __pos)
553 }
554
555 /// Encodes a byte array into a [`Buffer`].
556 ///
557 /// # Examples
558 ///
559 /// ```rust
560 /// use c32::Buffer;
561 /// use c32::Error;
562 ///
563 /// let input = [42, 42, 42];
564 /// let en = Buffer::<5>::try_encode(&input)?;
565 /// assert_eq!(en.as_str(), "2MAHA");
566 /// # Ok::<(), Error>(())
567 /// ```
568 #[inline]
569 pub const fn try_encode<const M: usize>(src: &[u8; M]) -> Result<Self> {
570 // Assert that the buffer has enough capacity.
571 let capacity = encoded_len(M);
572 if N < capacity {
573 return Err(Error::BufferTooSmall {
574 min: capacity,
575 len: N,
576 });
577 }
578
579 Ok(Self::encode(src))
580 }
581
582 /// Decodes a slice of encoded bytes into a [`Buffer`].
583 ///
584 /// # Examples
585 ///
586 /// ```rust
587 /// use c32::Buffer;
588 ///
589 /// const INPUT: [u8; 5] = *b"2MAHA";
590 /// const DE: Buffer<5> = Buffer::<5>::decode(&INPUT);
591 /// assert_eq!(DE.as_bytes(), [42, 42, 42]);
592 /// ```
593 #[inline]
594 #[must_use]
595 pub const fn decode(src: &[u8]) -> Self {
596 assert!(N >= decoded_len(src.len()), "Size 'N' is too small");
597
598 // Allocate the output buffer.
599 let mut __raw = [0u8; N];
600
601 // Decode the input to the buffer.
602 let __pos = match __internal::de(src, 0, src.len(), &mut __raw, 0) {
603 Ok(pos) => pos,
604 Err(Error::InvalidCharacter { char: _, index: _ }) => {
605 panic!("Input contains invalid characters")
606 }
607 _ => unreachable!(),
608 };
609
610 Self::new(__raw, __pos)
611 }
612
613 /// Decodes a slice of encoded bytes into a [`Buffer`].
614 ///
615 /// # Examples
616 ///
617 /// ```rust
618 /// # use c32::Error;
619 /// use c32::Buffer;
620 ///
621 /// let input = b"2MAHA";
622 /// let de = Buffer::<5>::try_decode(input)?;
623 /// assert_eq!(de.as_bytes(), [42, 42, 42]);
624 /// # Ok::<(), Error>(())
625 /// ```
626 #[inline]
627 pub const fn try_decode(src: &[u8]) -> Result<Self> {
628 // Assert that the buffer has enough capacity.
629 let capacity = decoded_len(src.len());
630 if N < capacity {
631 return Err(Error::BufferTooSmall {
632 min: capacity,
633 len: N,
634 });
635 }
636
637 Ok(Self::decode(src))
638 }
639}
640
641impl<const N: usize> Buffer<N, true, en::Default> {
642 /// Encodes a byte array with a prefix into a [`Buffer`].
643 ///
644 /// # Examples
645 ///
646 /// ```rust
647 /// use c32::Buffer;
648 ///
649 /// const INPUT: [u8; 3] = [42, 42, 42];
650 /// const EN: Buffer<6, true> = Buffer::<6, true>::encode(&INPUT, 'S');
651 /// assert_eq!(EN.as_str(), "S2MAHA");
652 /// ```
653 #[inline]
654 #[must_use]
655 pub const fn encode<const M: usize>(src: &[u8; M], prefix: char) -> Self {
656 const { assert!(N > encoded_len(M), "Size 'N' is too small") }
657 assert!(prefix.is_ascii(), "Prefix must be an ASCII character");
658
659 // Allocate the output buffer.
660 let mut __raw = [0u8; N];
661
662 // Prepend the prefix character.
663 __raw[0] = prefix as u8;
664
665 // Encode the input to the buffer.
666 let __pos = __internal::en(src, 0, M, &mut __raw, 1, None) + 1;
667
668 Self::new(__raw, __pos)
669 }
670
671 /// Encodes a byte array with a prefix into a [`Buffer`].
672 ///
673 /// # Examples
674 ///
675 /// ```rust
676 /// # use c32::Error;
677 /// use c32::Buffer;
678 ///
679 /// let input = [42, 42, 42];
680 /// let en = Buffer::<6, true>::try_encode(&input, 'S')?;
681 /// assert_eq!(en.as_str(), "S2MAHA");
682 /// # Ok::<(), Error>(())
683 /// ```
684 #[inline]
685 pub const fn try_encode<const M: usize>(
686 src: &[u8; M],
687 prefix: char,
688 ) -> Result<Self> {
689 const { assert!(N > encoded_len(M), "Size 'N' is too small") }
690
691 // Assert that the prefix is ASCII.
692 if !prefix.is_ascii() {
693 return Err(Error::InvalidCharacter {
694 char: prefix,
695 index: 0,
696 });
697 }
698
699 Ok(Self::encode(src, prefix))
700 }
701
702 /// Decodes a slice of prefixed encoded bytes into a [`Buffer`].
703 ///
704 /// # Examples
705 ///
706 /// ```rust
707 /// use c32::Buffer;
708 ///
709 /// const INPUT: [u8; 6] = *b"S2MAHA";
710 /// const DE: Buffer<6, true> = Buffer::<6, true>::decode(&INPUT, 'S');
711 /// assert_eq!(DE.as_bytes(), [42, 42, 42]);
712 /// ```
713 #[inline]
714 #[must_use]
715 pub const fn decode(src: &[u8], prefix: char) -> Self {
716 assert!(N >= decoded_len(src.len() - 1), "Size 'N' is too small");
717 assert!(prefix.is_ascii(), "Prefix must be an ASCII character");
718 assert!(!src.is_empty(), "Input must contain min. 1 character");
719 assert!(src[0] == prefix as u8, "Input must start with prefix");
720
721 // Allocate the output buffer.
722 let mut __raw = [0u8; N];
723
724 // Decode the input (without prefix) to the buffer.
725 let __pos = match __internal::de(src, 1, src.len() - 1, &mut __raw, 0) {
726 Ok(pos) => pos,
727 Err(Error::InvalidCharacter { char: _, index: _ }) => {
728 panic!("Input contains invalid characters")
729 }
730 _ => unreachable!(),
731 };
732
733 Self::new(__raw, __pos)
734 }
735
736 /// Decodes a slice of prefixed encoded bytes into a [`Buffer`].
737 ///
738 /// # Examples
739 ///
740 /// ```rust
741 /// # use c32::Error;
742 /// use c32::Buffer;
743 ///
744 /// let input = b"S2MAHA";
745 /// let de = Buffer::<6, true>::try_decode(input, 'S')?;
746 /// assert_eq!(de.as_bytes(), [42, 42, 42]);
747 /// # Ok::<(), Error>(())
748 /// ```
749 #[inline]
750 pub const fn try_decode(src: &[u8], prefix: char) -> Result<Self> {
751 // Assert that the input is not empty.
752 if src.is_empty() {
753 return Err(Error::MissingPrefix {
754 char: prefix,
755 got: None,
756 });
757 }
758
759 // Assert that the prefix is ASCII.
760 if !prefix.is_ascii() {
761 return Err(Error::InvalidCharacter {
762 char: prefix,
763 index: 0,
764 });
765 }
766
767 // Assert that the string starts with the prefix.
768 if src[0] != prefix as u8 {
769 return Err(Error::MissingPrefix {
770 char: prefix,
771 got: Some(src[0] as char),
772 });
773 }
774
775 // Assert that the buffer has enough capacity.
776 let capacity = decoded_len(src.len() - 1); // Always 1 for ASCII prefixes
777 if N < capacity {
778 return Err(Error::BufferTooSmall {
779 min: capacity,
780 len: N,
781 });
782 }
783
784 // Allocate the output buffer
785 let mut __raw = [0u8; N];
786
787 // Decode the input into the buffer.
788 let __pos = match __internal::de(src, 1, src.len() - 1, &mut __raw, 0) {
789 Ok(pos) => pos,
790 Err(Error::InvalidCharacter { char, index }) => {
791 return Err(Error::InvalidCharacter {
792 char,
793 index: index + 1,
794 });
795 }
796 Err(e) => return Err(e),
797 };
798
799 Ok(Self::new(__raw, __pos))
800 }
801}
802
803#[cfg(feature = "check")]
804impl<const N: usize> Buffer<N, false, en::Check> {
805 /// Encodes a byte array with a checksum into a [`Buffer`].
806 ///
807 /// # Examples
808 ///
809 /// ```rust,no_fmt
810 /// use c32::en::Check;
811 /// use c32::Buffer;
812 ///
813 /// const INPUT: [u8; 3] = [42, 42, 42];
814 /// const EN: Buffer<13, false, Check> = Buffer::<13, false, Check>::encode(&INPUT, 0);
815 /// assert_eq!(EN.as_str(), "0AHA59B9201Z");
816 /// ```
817 #[inline]
818 #[must_use]
819 pub const fn encode<const M: usize>(src: &[u8; M], version: u8) -> Self {
820 const { assert!(N >= encoded_check_len(M), "Size 'N' is too small") }
821 assert!(version < 32, "Version must be < 32");
822
823 // Allocate the output buffer.
824 let mut __raw = [0u8; N];
825
826 // Prepend the version character.
827 __raw[0] = ALPHABET[version as usize];
828
829 // Compute the checksum.
830 let sum = checksum::compute(src, version);
831
832 // Encode the input and checksum to the buffer.
833 let __pos = __internal::en(src, 0, M, &mut __raw, 1, Some(sum)) + 1;
834
835 Self::new(__raw, __pos)
836 }
837
838 /// Encodes a byte array with a checksum into a [`Buffer`].
839 ///
840 /// # Examples
841 ///
842 /// ```rust
843 /// use c32::en::Check;
844 /// use c32::Buffer;
845 /// use c32::Error;
846 ///
847 /// let input = [42, 42, 42];
848 /// let en = Buffer::<13, false, Check>::try_encode(&input, 0)?;
849 /// assert_eq!(en.as_str(), "0AHA59B9201Z");
850 /// # Ok::<(), Error>(())
851 /// ```
852 #[inline]
853 pub const fn try_encode<const M: usize>(
854 src: &[u8; M],
855 version: u8,
856 ) -> Result<Self> {
857 const { assert!(N >= encoded_check_len(M), "Size 'N' is too small") }
858
859 // Assert that the version is valid (< 32).
860 if version >= 32 {
861 return Err(Error::InvalidVersion {
862 expected: "must be < 32",
863 version,
864 });
865 }
866
867 Ok(Self::encode(src, version))
868 }
869
870 /// Decodes a slice of check-encoded bytes into a [`Buffer`].
871 ///
872 /// # Examples
873 ///
874 /// ```rust,no_fmt
875 /// use c32::Buffer;
876 /// use c32::en::Check;
877 ///
878 /// const INPUT: [u8; 12] = *b"0AHA59B9201Z";
879 /// const RESULT: (Buffer<12, false, Check>, u8) = Buffer::<12, false, Check>::decode(&INPUT);
880 /// assert_eq!(RESULT.0.as_bytes(), [42, 42, 42]);
881 /// assert_eq!(RESULT.1, 0);
882 // ```
883 #[inline]
884 #[must_use]
885 pub const fn decode(src: &[u8]) -> (Self, u8) {
886 assert!(N >= decoded_check_len(src.len()), "Size 'N' is too small");
887 assert!(src.len() >= 2, "Input must contain min. 2 characters");
888
889 // Allocate the output buffer.
890 let mut __raw = [0u8; N];
891
892 // Extract the version byte
893 let mut buffer = [0u8; 1];
894 let _ = match __internal::de(&[src[0]], 0, 1, &mut buffer, 0) {
895 Ok(pos) => pos,
896 Err(Error::InvalidCharacter { char: _, index: _ }) => {
897 panic!("Input must not contain invalid characters")
898 }
899 _ => unreachable!(),
900 };
901
902 // Assert that the version is valid (< 32).
903 let version = buffer[0];
904 assert!(version < 32, "Version must be < 32");
905
906 // Decode the remaining bytes into the output buffer.
907 let __pos = match __internal::de(src, 1, src.len() - 1, &mut __raw, 0) {
908 Ok(pos) => pos,
909 Err(Error::InvalidCharacter { char: _, index: _ }) => {
910 panic!("Input must not contain invalid characters")
911 }
912 _ => unreachable!(),
913 };
914
915 let __pos = __pos - checksum::BYTE_LENGTH;
916
917 // Extract the checksum.
918 let mut sum = [0u8; checksum::BYTE_LENGTH];
919 __internal::memcpy(&mut sum, 0, &__raw, __pos, checksum::BYTE_LENGTH);
920
921 // Compute the expected checksum.
922 let mut expected = [0u8; checksum::BYTE_LENGTH];
923 __internal::memcpy(&mut expected, 0, &__raw, __pos, 4);
924
925 // Assert that the computed and actual checksums match.
926 assert!(__internal::memcmp(&expected, &sum, 4), "Checksum mismatch");
927
928 (Self::new(__raw, __pos), version)
929 }
930
931 /// Decodes a slice of check-encoded bytes into a [`Buffer`].
932 ///
933 /// # Examples
934 ///
935 /// ```rust
936 /// # use c32::Error;
937 /// use c32::en::Check;
938 /// use c32::Buffer;
939 ///
940 /// let input = b"0AHA59B9201Z";
941 /// let (de, version) = Buffer::<12, false, Check>::try_decode(input)?;
942 /// assert_eq!(de.as_bytes(), [42, 42, 42]);
943 /// assert_eq!(version, 0);
944 /// # Ok::<(), Error>(())
945 /// ```
946 #[inline]
947 pub const fn try_decode(src: &[u8]) -> Result<(Self, u8)> {
948 // Assert that the buffer has enough capacity.
949 let capacity = decoded_check_len(src.len());
950 if N < capacity {
951 return Err(Error::BufferTooSmall {
952 min: capacity,
953 len: N,
954 });
955 }
956
957 // Assert that the input bytes contain the minimum amount.
958 if src.len() < 2 {
959 return Err(Error::InsufficientData {
960 min: 2,
961 len: src.len(),
962 });
963 }
964
965 // Allocate the output buffer.
966 let mut __raw = [0u8; N];
967
968 // Extract the version byte
969 let mut buffer = [0u8; 1];
970 let _ = match __internal::de(&[src[0]], 0, 1, &mut buffer, 0) {
971 Ok(pos) => pos,
972 Err(err) => return Err(err),
973 };
974
975 // Assert that the version is valid (< 32).
976 let version = buffer[0];
977 if version >= 32 {
978 return Err(Error::InvalidVersion {
979 expected: "must be < 32",
980 version,
981 });
982 }
983
984 // Decode the remaining bytes into the output buffer.
985 let __pos = match __internal::de(src, 1, src.len() - 1, &mut __raw, 0) {
986 Ok(pos) => pos,
987 Err(Error::InvalidCharacter { char, index }) => {
988 return Err(Error::InvalidCharacter {
989 char,
990 index: index + 1,
991 });
992 }
993 Err(e) => return Err(e),
994 };
995
996 let __pos = __pos - checksum::BYTE_LENGTH;
997
998 // Extract the checksum.
999 let mut sum = [0u8; checksum::BYTE_LENGTH];
1000 __internal::memcpy(&mut sum, 0, &__raw, __pos, checksum::BYTE_LENGTH);
1001
1002 // Compute the expected checksum.
1003 let mut expected = [0u8; checksum::BYTE_LENGTH];
1004 __internal::memcpy(&mut expected, 0, &__raw, __pos, 4);
1005
1006 // Assert that the computed and actual checksums match.
1007 if !__internal::memcmp(&expected, &sum, checksum::BYTE_LENGTH) {
1008 return Err(Error::ChecksumMismatch { expected, got: sum });
1009 }
1010
1011 Ok((Self::new(__raw, __pos), version))
1012 }
1013}
1014
1015#[cfg(feature = "check")]
1016impl<const N: usize> Buffer<N, true, en::Check> {
1017 /// Encodes a byte array with a checksum and prefix into a [`Buffer`].
1018 ///
1019 ///
1020 /// # Examples
1021 ///
1022 /// ```rust,no_fmt
1023 /// use c32::en::Check;
1024 /// use c32::Buffer;
1025 ///
1026 /// const INPUT: [u8; 3] = [42, 42, 42];
1027 /// const EN: Buffer<14, true, Check> = Buffer::<14, true, Check>::encode(&INPUT, 'S', 0);
1028 /// assert_eq!(EN.as_str(), "S0AHA59B9201Z");
1029 /// ```
1030 #[inline]
1031 #[must_use]
1032 pub const fn encode<const M: usize>(
1033 src: &[u8; M],
1034 prefix: char,
1035 version: u8,
1036 ) -> Self {
1037 const { assert!(N > encoded_check_len(M), "Size 'N' is too small") }
1038 assert!(version < 32, "Version must be < 32");
1039
1040 // Allocate the output buffer.
1041 let mut __raw = [0u8; N];
1042
1043 // Prepend the prefix character.
1044 __raw[0] = prefix as u8;
1045
1046 // Prepend the version character.
1047 __raw[1] = ALPHABET[version as usize];
1048
1049 // Compute the checksum.
1050 let sum = checksum::compute(src, version);
1051
1052 // Encode the input and checksum to the buffer.
1053 let __pos = __internal::en(src, 0, M, &mut __raw, 2, Some(sum)) + 2;
1054
1055 Self::new(__raw, __pos)
1056 }
1057
1058 /// Encodes a byte array with a checksum and prefix into a [`Buffer`].
1059 ///
1060 /// # Examples
1061 ///
1062 /// ```rust
1063 /// # use c32::Error;
1064 /// use c32::en::Check;
1065 /// use c32::Buffer;
1066 ///
1067 /// let input = [42, 42, 42];
1068 /// let en = Buffer::<14, true, Check>::try_encode(&input, 'S', 0)?;
1069 /// assert_eq!(en.as_str(), "S0AHA59B9201Z");
1070 /// # Ok::<(), Error>(())
1071 /// ```
1072 #[inline]
1073 pub const fn try_encode<const M: usize>(
1074 src: &[u8; M],
1075 prefix: char,
1076 version: u8,
1077 ) -> Result<Self> {
1078 const { assert!(N > encoded_check_len(M), "Size 'N' is too small") }
1079
1080 // Assert that the version is valid (< 32).
1081 if version >= 32 {
1082 return Err(Error::InvalidVersion {
1083 expected: "must be < 32",
1084 version,
1085 });
1086 }
1087
1088 // Allocate the output buffer.
1089 let mut __raw = [0u8; N];
1090
1091 // Prepend the prefix character.
1092 __raw[0] = prefix as u8;
1093
1094 // Prepend the version character.
1095 __raw[1] = ALPHABET[version as usize];
1096
1097 // Compute the checksum.
1098 let sum = checksum::compute(src, version);
1099
1100 // Encode the input and checksum to the buffer.
1101 let __pos = __internal::en(src, 0, M, &mut __raw, 2, Some(sum)) + 2;
1102
1103 Ok(Self::new(__raw, __pos))
1104 }
1105
1106 /// Decodes a slice of prefixed check-encoded bytes into a [`Buffer`].
1107 ///
1108 /// # Examples
1109 ///
1110 /// ```rust,no_fmt
1111 /// use c32::en::Check;
1112 /// use c32::Buffer;
1113 ///
1114 /// const INPUT: [u8; 13] = *b"S0AHA59B9201Z";
1115 /// const RESULT: (Buffer<14, true, Check>, u8) = Buffer::<14, true, Check>::decode(&INPUT, 'S');
1116 /// assert_eq!(RESULT.0.as_bytes(), [42, 42, 42]);
1117 /// assert_eq!(RESULT.1, 0);
1118 /// ```
1119 #[inline]
1120 #[must_use]
1121 pub const fn decode(src: &[u8], prefix: char) -> (Self, u8) {
1122 assert!(N >= decoded_check_len(src.len() - 1), "'N' is too small");
1123 assert!(prefix.is_ascii(), "Prefix must be an ASCII character");
1124 assert!(src.len() >= 3, "Input must contain min. 3 characters");
1125 assert!(src[0] == prefix as u8, "Input must start with prefix");
1126
1127 // Extract the version byte.
1128 let mut buffer = [0u8; 1];
1129 let _ = match __internal::de(&[src[1]], 0, 1, &mut buffer, 0) {
1130 Ok(pos) => pos,
1131 Err(Error::InvalidCharacter { char: _, index: _ }) => {
1132 panic!("Input must not contain invalid characters")
1133 }
1134 _ => unreachable!(),
1135 };
1136
1137 // Assert that the version is < 32.
1138 let version = buffer[0];
1139 assert!(version < 32, "Version must be < 32");
1140
1141 // Allocate the output buffer.
1142 let mut __raw = [0u8; N];
1143
1144 // Decode the payload.
1145 let pos = match __internal::de(src, 2, src.len() - 2, &mut __raw, 0) {
1146 Ok(pos) => pos,
1147 Err(Error::InvalidCharacter { char: _, index: _ }) => {
1148 panic!("Input must not contain invalid characters")
1149 }
1150 _ => unreachable!(),
1151 };
1152
1153 let __pos = pos - checksum::BYTE_LENGTH;
1154
1155 // Extract the checksum.
1156 let mut sum = [0u8; checksum::BYTE_LENGTH];
1157 __internal::memcpy(&mut sum, 0, &__raw, __pos, checksum::BYTE_LENGTH);
1158
1159 // Compute the expected checksum.
1160 let mut expected = [0u8; checksum::BYTE_LENGTH];
1161 __internal::memcpy(&mut expected, 0, &__raw, __pos, 4);
1162
1163 // Assert that the computed and actual checksums match.
1164 assert!(__internal::memcmp(&expected, &sum, 4), "Checksum mismatch");
1165
1166 (Self::new(__raw, __pos), version)
1167 }
1168
1169 /// Decodes a slice of prefixed check-encoded bytes into a [`Buffer`].
1170 ///
1171 /// # Examples
1172 ///
1173 /// ```rust
1174 /// # use c32::Error;
1175 /// use c32::en::Check;
1176 /// use c32::Buffer;
1177 ///
1178 /// let input = b"S0AHA59B9201Z";
1179 /// let (de, version) = Buffer::<14, true, Check>::try_decode(input, 'S')?;
1180 /// assert_eq!(de.as_bytes(), [42, 42, 42]);
1181 /// assert_eq!(version, 0);
1182 /// # Ok::<(), Error>(())
1183 /// ```
1184 #[inline]
1185 pub const fn try_decode(src: &[u8], prefix: char) -> Result<(Self, u8)> {
1186 // Assert that the buffer has enough capacity.
1187 let capacity = decoded_check_len(src.len() - prefix.len_utf8());
1188 if N < capacity {
1189 return Err(Error::BufferTooSmall {
1190 min: capacity,
1191 len: N,
1192 });
1193 }
1194
1195 // Assert that the prefix is ASCII.
1196 if !prefix.is_ascii() {
1197 return Err(Error::InvalidCharacter {
1198 char: prefix,
1199 index: 0,
1200 });
1201 }
1202
1203 // Assert that the input is not empty.
1204 if src.is_empty() {
1205 return Err(Error::MissingPrefix {
1206 char: prefix,
1207 got: None,
1208 });
1209 }
1210
1211 // Assert that the input has the minimum required length.
1212 if src.len() < 3 {
1213 return Err(Error::InsufficientData {
1214 min: 3,
1215 len: src.len(),
1216 });
1217 }
1218
1219 // Assert that the string starts with the prefix.
1220 if src[0] != prefix as u8 {
1221 return Err(Error::MissingPrefix {
1222 char: prefix,
1223 got: Some(src[0] as char),
1224 });
1225 }
1226
1227 // Extract the version byte
1228 let mut buffer = [0u8; 1];
1229 let _ = match __internal::de(&[src[1]], 0, 1, &mut buffer, 0) {
1230 Ok(pos) => pos,
1231 Err(err) => return Err(err),
1232 };
1233
1234 // Assert that the version is valid (< 32).
1235 let version = buffer[0];
1236 if version >= 32 {
1237 return Err(Error::InvalidVersion {
1238 expected: "must be < 32",
1239 version,
1240 });
1241 }
1242
1243 // Allocate the output buffer.
1244 let mut __raw = [0u8; N];
1245
1246 // Decode the payload into the buffer.
1247 let mut __pos =
1248 match __internal::de(src, 2, src.len() - 2, &mut __raw, 0) {
1249 Ok(pos) => pos,
1250 Err(Error::InvalidCharacter { char, index }) => {
1251 return Err(Error::InvalidCharacter {
1252 char,
1253 index: index + 2,
1254 });
1255 }
1256 Err(e) => return Err(e),
1257 };
1258
1259 // Extract the checksum.
1260 __pos -= checksum::BYTE_LENGTH;
1261 let mut sum = [0u8; checksum::BYTE_LENGTH];
1262 __internal::memcpy(&mut sum, 0, &__raw, __pos, checksum::BYTE_LENGTH);
1263
1264 // Compute the expected checksum.
1265 let mut expected = [0u8; checksum::BYTE_LENGTH];
1266 __internal::memcpy(&mut expected, 0, &__raw, __pos, 4);
1267
1268 // Assert that the checksums match.
1269 if !__internal::memcmp(&expected, &sum, checksum::BYTE_LENGTH) {
1270 return Err(Error::ChecksumMismatch { expected, got: sum });
1271 }
1272
1273 Ok((Self::new(__raw, __pos), version))
1274 }
1275}
1276
1277/// Computes the required capacity for encoding into Crockford Base32.
1278///
1279/// # Notes
1280///
1281/// The calculation breaks down into:
1282///
1283/// - For every 5 bytes (40 bits), exactly 8 Base32 characters are needed.
1284/// - For remaining bytes, more characters are needed based on the total bits.
1285///
1286/// # Examples
1287///
1288/// ```rust
1289/// assert_eq!(c32::encoded_len(0), 0);
1290/// assert_eq!(c32::encoded_len(1), 2);
1291/// assert_eq!(c32::encoded_len(3), 5);
1292/// ```
1293#[inline]
1294#[must_use]
1295pub const fn encoded_len(n: usize) -> usize {
1296 (n * 8 + 4) / 5
1297}
1298
1299/// Computes the required capacity for encoding into Crockford Base32Check.
1300///
1301/// # Notes
1302///
1303/// The calculation breaks down into:
1304///
1305/// - One byte for the version character.
1306/// - The encoded length of the payload plus a 4-byte checksum.
1307///
1308/// # Examples
1309///
1310/// ```rust
1311/// assert_eq!(c32::encoded_check_len(0), 8);
1312/// assert_eq!(c32::encoded_check_len(1), 9);
1313/// assert_eq!(c32::encoded_check_len(3), 13);
1314/// ```
1315#[inline]
1316#[must_use]
1317#[cfg(feature = "check")]
1318pub const fn encoded_check_len(n: usize) -> usize {
1319 1 + encoded_len(n + 4)
1320}
1321
1322/// Computes the required capacity for decoding from Crockford Base32.
1323///
1324/// # Examples
1325///
1326/// ```rust
1327/// assert_eq!(c32::decoded_len(0), 0);
1328/// assert_eq!(c32::decoded_len(1), 1);
1329/// assert_eq!(c32::decoded_len(2), 2);
1330/// ```
1331#[inline]
1332#[must_use]
1333pub const fn decoded_len(n: usize) -> usize {
1334 n
1335}
1336
1337/// Computes the required capacity for decoding from Crockford Base32Check.
1338///
1339/// # Examples
1340///
1341/// ```rust
1342/// assert_eq!(c32::decoded_check_len(8), 8);
1343/// assert_eq!(c32::decoded_check_len(9), 9);
1344/// assert_eq!(c32::decoded_check_len(13), 13);
1345/// ```
1346#[inline]
1347#[must_use]
1348#[cfg(feature = "check")]
1349pub const fn decoded_check_len(n: usize) -> usize {
1350 n
1351}
1352
1353/// Encodes bytes into a Crockford Base32-encoded string.
1354///
1355/// # Panics
1356///
1357/// This method can panic in two cases:
1358///
1359/// - If encoding fails despitce sufficient buffer capacity.
1360/// - If the encoded output contains non-UTF8 bytes.
1361///
1362/// Both panics should never occur under normal circumstances.
1363///
1364/// # Examples
1365///
1366/// ```rust
1367/// let en = c32::encode([42, 42, 42]);
1368/// assert_eq!(en, "2MAHA");
1369/// ```
1370#[inline]
1371#[must_use]
1372#[cfg(feature = "alloc")]
1373pub fn encode<B>(src: B) -> String
1374where
1375 B: AsRef<[u8]>,
1376{
1377 let src = src.as_ref();
1378
1379 // Allocate the output buffer.
1380 let capacity = encoded_len(src.len());
1381 let mut dst = vec![0u8; capacity];
1382
1383 // This should not panic, as we allocate enough space.
1384 let offset = encode_into(src, &mut dst).unwrap();
1385 dst.truncate(offset);
1386
1387 // This should not panic, as we only push valid ASCII.
1388 String::from_utf8(dst).unwrap()
1389}
1390
1391/// Decodes a Crockford Base32-encoded string.
1392///
1393/// # Errors
1394///
1395/// This method will return an [`Error`] if:
1396///
1397/// - [`Error::InvalidCharacter`], the input contains invalid characters.
1398///
1399/// # Examples
1400///
1401/// ```rust
1402/// # use c32::Error;
1403/// let de = c32::decode("2MAHA")?;
1404/// assert_eq!(de, [42, 42, 42]);
1405/// # Ok::<(), Error>(())
1406/// ```
1407#[inline]
1408#[cfg(feature = "alloc")]
1409pub fn decode(str: &str) -> Result<Vec<u8>> {
1410 let bytes = str.as_bytes();
1411
1412 // Allocate the output buffer.
1413 let capacity = decoded_len(bytes.len());
1414 let mut dst = vec![0u8; capacity];
1415
1416 // Decode the input bytes into the buffer.
1417 let offset = decode_into(bytes, &mut dst)?;
1418 dst.truncate(offset);
1419
1420 Ok(dst)
1421}
1422
1423/// Encodes bytes into a prefixed Crockford Base32-encoded string.
1424///
1425/// # Examples
1426///
1427/// ```rust
1428/// let en = c32::encode_prefixed([42, 42, 42], 'S');
1429/// assert_eq!(en, "S2MAHA");
1430/// ```
1431#[inline]
1432#[must_use]
1433#[cfg(feature = "alloc")]
1434pub fn encode_prefixed<B>(src: B, prefix: char) -> String
1435where
1436 B: AsRef<[u8]>,
1437{
1438 let src = src.as_ref();
1439
1440 // Encode the input bytes.
1441 let encoded = encode(src);
1442
1443 // Allocate the output string.
1444 let capacity = prefix.len_utf8() + encoded.len();
1445 let mut dst = String::with_capacity(capacity);
1446
1447 // Append the prefix and encoded string.
1448 dst.push(prefix);
1449 dst.push_str(&encoded);
1450 dst
1451}
1452
1453/// Decodes a prefixed Crockford Base32-encoded string.
1454///
1455/// # Errors
1456///
1457/// This method will return an [`Error`] if:
1458///
1459/// - [`Error::MissingPrefix`], the input does not start with the prefix.
1460/// - [`Error::InvalidCharacter`], the input contains invalid characters.
1461///
1462/// # Examples
1463///
1464/// ```rust
1465/// # use c32::Error;
1466/// let de = c32::decode_prefixed("P2MAHA", 'P')?;
1467/// assert_eq!(de, [42, 42, 42]);
1468/// # Ok::<(), Error>(())
1469/// ```
1470#[inline]
1471#[cfg(feature = "alloc")]
1472pub fn decode_prefixed(str: &str, prefix: char) -> Result<Vec<u8>> {
1473 // Assert that the string starts with the prefix.
1474 if !str.starts_with(prefix) {
1475 return Err(Error::MissingPrefix {
1476 char: prefix,
1477 got: str.chars().next(),
1478 });
1479 }
1480
1481 // Skip the prefix character and decode the rest.
1482 match decode(&str[prefix.len_utf8()..]) {
1483 Ok(bytes) => Ok(bytes),
1484 Err(Error::InvalidCharacter { char, index }) => {
1485 // This adjusts the index in an 'InvalidCharacter' to account for
1486 // the prefix in the original string that we don't decode.
1487 Err(Error::InvalidCharacter {
1488 char,
1489 index: index + prefix.len_utf8(),
1490 })
1491 }
1492 Err(e) => Err(e),
1493 }
1494}
1495
1496/// Encodes bytes into a Crockford Base32Check-encoded string.
1497///
1498/// # Panics
1499///
1500/// This method can panic in two cases:
1501///
1502/// - If encoding fails despite sufficient buffer capacity.
1503/// - If the encoded output contains non-UTF8 bytes.
1504///
1505/// Both panics should never occur under normal circumstances.
1506///
1507/// # Errors
1508///
1509/// This method will return an [`Error`] if:
1510///
1511/// - [`Error::InvalidVersion`], the version is 32 or greater.
1512///
1513/// # Examples
1514///
1515/// ```rust
1516/// # use c32::Error;
1517/// let en = c32::encode_check([42, 42, 42], 0)?;
1518/// assert_eq!(en, "0AHA59B9201Z");
1519/// # Ok::<(), Error>(())
1520/// ```
1521#[inline]
1522#[cfg(all(feature = "alloc", feature = "check"))]
1523pub fn encode_check<B>(src: B, version: u8) -> Result<String>
1524where
1525 B: AsRef<[u8]>,
1526{
1527 let src = src.as_ref();
1528
1529 // Allocate the output string.
1530 let capacity = encoded_check_len(src.len());
1531 let mut dst = vec![0u8; capacity];
1532
1533 // This should not panic, as we allocate enough space.
1534 let offset = encode_check_into(src, &mut dst, version)?;
1535 dst.truncate(offset);
1536
1537 // This should not panic, as we only push valid ASCII.
1538 Ok(String::from_utf8(dst).unwrap())
1539}
1540
1541/// Decodes a Crockford Base32Check-encoded string.
1542///
1543/// # Errors
1544///
1545/// This method will return an [`Error`] if:
1546///
1547/// - [`Error::InvalidCharacter`], the input contains invalid characters.
1548/// - [`Error::InsufficientData`], the input has fewer bytes than required.
1549/// - [`Error::ChecksumMismatch`], the checksum's do not match.
1550/// - [`Error::InvalidVersion`], the version is 32 or greater.
1551///
1552/// # Examples
1553///
1554/// ```rust
1555/// # use c32::Error;
1556/// let (bytes, version) = c32::decode_check("0AHA59B9201Z")?;
1557/// assert_eq!(bytes, [42, 42, 42]);
1558/// assert_eq!(version, 0);
1559/// # Ok::<(), Error>(())
1560/// ```
1561#[inline]
1562#[cfg(all(feature = "alloc", feature = "check"))]
1563pub fn decode_check(str: &str) -> Result<(Vec<u8>, u8)> {
1564 let bytes = str.as_bytes();
1565
1566 // Allocate the output buffer.
1567 let capacity = decoded_check_len(bytes.len());
1568 let mut dst = vec![0u8; capacity];
1569
1570 // Decode the input bytes into the buffer.
1571 let (offset, version) = decode_check_into(bytes, &mut dst)?;
1572 dst.truncate(offset);
1573
1574 Ok((dst, version))
1575}
1576
1577/// Encodes bytes into a prefixed Crockford Base32Check-encoded string.
1578///
1579/// # Errors
1580///
1581/// This method will return an [`Error`] if:
1582///
1583/// - [`Error::InvalidVersion`], the version is 32 or greater.
1584///
1585/// # Examples
1586///
1587/// ```rust
1588/// # use c32::Error;
1589/// let en = c32::encode_check_prefixed([42, 42, 42], 'S', 0)?;
1590/// assert_eq!(en, "S0AHA59B9201Z");
1591/// # Ok::<(), Error>(())
1592/// ```
1593#[inline]
1594#[cfg(all(feature = "alloc", feature = "check"))]
1595pub fn encode_check_prefixed<B>(
1596 src: B,
1597 prefix: char,
1598 version: u8,
1599) -> Result<String>
1600where
1601 B: AsRef<[u8]>,
1602{
1603 let src = src.as_ref();
1604
1605 // Encode the input bytes.
1606 let encoded = encode_check(src, version)?;
1607
1608 // Allocate the output string.
1609 let capacity = prefix.len_utf8() + encoded.len();
1610 let mut dst = String::with_capacity(capacity);
1611
1612 // Append the prefix and encoded string.
1613 dst.push(prefix);
1614 dst.push_str(&encoded);
1615 Ok(dst)
1616}
1617
1618/// Decodes a prefixed Crockford Base32Check-encoded string.
1619///
1620/// # Errors
1621///
1622/// This method will return an [`Error`] if:
1623///
1624/// - [`Error::MissingPrefix`], the input does not start with the prefix.
1625/// - [`Error::InvalidCharacter`], the input contains invalid characters.
1626/// - [`Error::InsufficientData`], the input has fewer bytes than required.
1627/// - [`Error::InvalidVersion`], the version is 32 or greater.
1628/// - [`Error::ChecksumMismatch`], the checksum's do not match.
1629///
1630/// # Examples
1631///
1632/// ```rust
1633/// # use c32::Error;
1634/// let (bytes, version) = c32::decode_check_prefixed("S0AHA59B9201Z", 'S')?;
1635/// assert_eq!(bytes, [42, 42, 42]);
1636/// assert_eq!(version, 0);
1637/// # Ok::<(), Error>(())
1638/// ```
1639#[inline]
1640#[cfg(all(feature = "alloc", feature = "check"))]
1641pub fn decode_check_prefixed(str: &str, prefix: char) -> Result<(Vec<u8>, u8)> {
1642 // Assert that the string starts with the prefix.
1643 if !str.starts_with(prefix) {
1644 return Err(Error::MissingPrefix {
1645 char: prefix,
1646 got: str.chars().next(),
1647 });
1648 }
1649
1650 // Skip the prefix character and decode the rest.
1651 match decode_check(&str[prefix.len_utf8()..]) {
1652 Ok(bytes) => Ok(bytes),
1653 Err(Error::InvalidCharacter { char, index }) => {
1654 // This adjusts the index in an 'InvalidCharacter' to account for
1655 // the prefix in the original string that we don't decode.
1656 Err(Error::InvalidCharacter {
1657 char,
1658 index: index + prefix.len_utf8(),
1659 })
1660 }
1661 Err(e) => Err(e),
1662 }
1663}
1664
1665/// Encodes bytes as Crockford Base32 into a provided buffer.
1666///
1667/// # Returns
1668///
1669/// The number of bytes written to the output buffer.
1670///
1671/// # Errors
1672///
1673/// This method will return an [`Error`] if:
1674///
1675/// - [`Error::BufferTooSmall`], the output buffer lacks capacity.
1676///
1677/// # Examples
1678///
1679/// ```rust
1680/// # use c32::Error;
1681/// # let mut dst = [0u8; 5];
1682/// let offset = c32::encode_into(&[42, 42, 42], &mut dst)?;
1683/// assert_eq!(&dst[..offset], b"2MAHA");
1684/// assert_eq!(offset, 5);
1685/// # Ok::<(), Error>(())
1686/// ```
1687#[inline]
1688pub fn encode_into(src: &[u8], dst: &mut [u8]) -> Result<usize> {
1689 // Assert that the buffer has enough capacity.
1690 let capacity = encoded_len(src.len());
1691 if dst.len() < capacity {
1692 return Err(Error::BufferTooSmall {
1693 min: capacity,
1694 len: dst.len(),
1695 });
1696 }
1697
1698 // Encode the input bytes, and return the amount of bytes written.
1699 let offset = __internal::en(src, 0, src.len(), &mut dst[..], 0, None);
1700 Ok(offset)
1701}
1702
1703/// Decodes Crockford Base32-encoded bytes into a provided buffer.
1704///
1705/// # Returns
1706///
1707/// The number of bytes written to the output buffer.
1708///
1709/// # Errors
1710///
1711/// This method will return an [`Error`] if:
1712///
1713/// - [`Error::BufferTooSmall`], the output buffer lacks capacity.
1714/// - [`Error::InvalidCharacter`], the input contains invalid characters.
1715///
1716/// # Examples
1717///
1718/// ```rust
1719/// # use c32::Error;
1720/// # let mut dst = [0u8; 5];
1721/// let offset = c32::decode_into(b"2MAHA", &mut dst)?;
1722/// assert_eq!(&dst[..offset], [42, 42, 42]);
1723/// assert_eq!(offset, 3);
1724/// # Ok::<(), Error>(())
1725/// ```
1726#[inline]
1727pub fn decode_into(src: &[u8], dst: &mut [u8]) -> Result<usize> {
1728 // Assert that the buffer has enough capacity.
1729 let capacity = decoded_len(src.len());
1730 if dst.len() < capacity {
1731 return Err(Error::BufferTooSmall {
1732 min: capacity,
1733 len: dst.len(),
1734 });
1735 }
1736
1737 // Encode the input bytes, and return the amount of bytes written.
1738 __internal::de(src, 0, src.len(), dst, 0)
1739}
1740
1741/// Encodes bytes as Crockford Base32Check into a provided buffer.
1742///
1743/// # Returns
1744///
1745/// The number of bytes written to the output buffer.
1746///
1747/// # Errors
1748///
1749/// This method will return an [`Error`] if:
1750///
1751/// - [`Error::BufferTooSmall`], the output buffer lacks capacity.
1752/// - [`Error::InvalidVersion`], the version is 32 or greater.
1753///
1754/// # Examples
1755///
1756/// ```rust
1757/// # use c32::Error;
1758/// # let mut dst = [0u8; 13];
1759/// let offset = c32::encode_check_into(&[42, 42, 42], &mut dst, 0)?;
1760/// assert_eq!(&dst[..offset], b"0AHA59B9201Z");
1761/// assert_eq!(offset, 12);
1762/// # Ok::<(), Error>(())
1763/// ```
1764#[inline]
1765#[cfg(feature = "check")]
1766pub fn encode_check_into(
1767 src: &[u8],
1768 dst: &mut [u8],
1769 version: u8,
1770) -> Result<usize> {
1771 // Assert that the buffer has enough capacity.
1772 let capacity = encoded_check_len(src.len());
1773 if dst.len() < capacity {
1774 return Err(Error::BufferTooSmall {
1775 min: capacity,
1776 len: dst.len(),
1777 });
1778 }
1779
1780 // Assert that the version is valid (< 32).
1781 if version >= 32 {
1782 return Err(Error::InvalidVersion {
1783 expected: "must be < 32",
1784 version,
1785 });
1786 }
1787
1788 // Insert the version character into the output buffer.
1789 let mut offset = 0;
1790 dst[offset] = ALPHABET[version as usize];
1791 offset += 1;
1792
1793 // Compute the checksum for the input bytes and version.
1794 let sum = checksum::compute(src, version);
1795
1796 // Encode the bytes and checksum.
1797 offset +=
1798 __internal::en(src, 0, src.len(), &mut dst[offset..], 0, Some(sum));
1799
1800 Ok(offset)
1801}
1802
1803/// Decodes Crockford Base32Check-encoded bytes into a provided buffer.
1804///
1805/// # Returns
1806///
1807/// The number of bytes written to the output buffer and the version.
1808///
1809/// # Errors
1810///
1811/// This method will return an [`Error`] if:
1812///
1813/// - [`Error::InvalidCharacter`], the input contains invalid characters.
1814/// - [`Error::InsufficientData`], the input has fewer bytes than required.
1815/// - [`Error::InvalidVersion`], the version is 32 or greater.
1816/// - [`Error::ChecksumMismatch`], the checksum's do not match.
1817/// - [`Error::BufferTooSmall`], the output buffer lacks capacity.
1818///
1819/// # Examples
1820///
1821/// ```rust
1822/// # use c32::Error;
1823/// # let mut dst = [0u8; 12];
1824/// let (offset, version) = c32::decode_check_into(b"0AHA59B9201Z", &mut dst)?;
1825/// assert_eq!(&dst[..offset], [42, 42, 42]);
1826/// assert_eq!(version, 0);
1827/// # Ok::<(), Error>(())
1828/// ```
1829#[inline]
1830#[cfg(feature = "check")]
1831#[allow(clippy::missing_panics_doc)]
1832pub fn decode_check_into(src: &[u8], dst: &mut [u8]) -> Result<(usize, u8)> {
1833 // Assert that the buffer has enough capacity.
1834 let capacity = decoded_check_len(src.len());
1835 if dst.len() < capacity {
1836 return Err(Error::BufferTooSmall {
1837 min: capacity,
1838 len: dst.len(),
1839 });
1840 }
1841
1842 // Assert that the input bytes contain the minimum amount.
1843 if src.len() < 2 {
1844 return Err(Error::InsufficientData {
1845 min: 2,
1846 len: src.len(),
1847 });
1848 }
1849
1850 // This should not panic, as the check above ensures enough bytes.
1851 let (tag, payload) = src.split_first().unwrap();
1852
1853 // Decode the version byte.
1854 let mut buffer = [0u8; 1];
1855 let _ = __internal::de(&[*tag], 0, 1, &mut buffer, 0)?;
1856 let version = buffer[0];
1857
1858 // Assert that the recovered version is valid. (< 32).
1859 if version >= 32 {
1860 return Err(Error::InvalidVersion {
1861 expected: "must be < 32",
1862 version,
1863 });
1864 }
1865
1866 // Decode the remaining bytes into the output buffer.
1867 let mut offset = match __internal::de(payload, 0, payload.len(), dst, 0) {
1868 Ok(pos) => pos,
1869 Err(Error::InvalidCharacter { char, index }) => {
1870 return Err(Error::InvalidCharacter {
1871 char,
1872 index: index + 1,
1873 });
1874 }
1875 Err(e) => return Err(e),
1876 };
1877
1878 // Extract the checksum.
1879 offset -= checksum::BYTE_LENGTH;
1880 let sum =
1881 checksum::from_slice(&dst[offset..offset + checksum::BYTE_LENGTH]);
1882
1883 // Compute the expected checksum.
1884 let expected = checksum::compute(&dst[..offset], version);
1885
1886 // Assert that the computed and actual checksums match.
1887 if !__internal::memcmp(&expected, &sum, checksum::BYTE_LENGTH) {
1888 return Err(Error::ChecksumMismatch { expected, got: sum });
1889 }
1890
1891 Ok((offset, version))
1892}
1893
1894/// Private module containing internal methods.
1895#[allow(dead_code)]
1896mod __internal {
1897 use super::*;
1898
1899 /// Encodes a byte slice into Crockford Base32.
1900 ///
1901 /// # Notes
1902 ///
1903 /// - The output buffer must be properly sized.
1904 #[inline]
1905 #[must_use]
1906 pub(crate) const fn en(
1907 src: &[u8],
1908 src_offset: usize,
1909 src_len: usize,
1910 dst: &mut [u8],
1911 dst_offset: usize,
1912 checksum: Option<[u8; 4]>,
1913 ) -> usize {
1914 const MASK_5: u16 = 0x1F;
1915 const SHIFT_5: u16 = 5;
1916
1917 let mut carry = 0;
1918 let mut carry_bits = 0;
1919 let mut dst_pos = dst_offset;
1920
1921 // count leading zeros
1922 let mut leading_zeros = 0;
1923 while leading_zeros < src_len && src[src_offset + leading_zeros] == 0 {
1924 leading_zeros += 1;
1925 }
1926
1927 // process checksum if provided
1928 if let Some(sum) = checksum {
1929 let mut checksum_pos = sum.len();
1930 while checksum_pos > 0 {
1931 checksum_pos -= 1;
1932
1933 // accumulate bits into carry
1934 carry |= (sum[checksum_pos] as u16) << carry_bits;
1935 carry_bits += 8;
1936
1937 // extract 5-bit chunks
1938 while carry_bits >= SHIFT_5 {
1939 // write character from chunk
1940 dst[dst_pos] = ALPHABET[(carry & MASK_5) as usize];
1941 dst_pos += 1;
1942
1943 // shift out processed bytes
1944 carry >>= SHIFT_5;
1945 carry_bits -= SHIFT_5;
1946 }
1947 }
1948 }
1949
1950 // process bytes in reverse
1951 let mut input_pos = src_offset + src_len;
1952 while input_pos > src_offset {
1953 input_pos -= 1;
1954
1955 // accumulate bits into carry
1956 carry |= (src[input_pos] as u16) << carry_bits;
1957 carry_bits += 8;
1958
1959 // extract 5-bit chunks
1960 while carry_bits >= SHIFT_5 {
1961 // write character from chunk
1962 dst[dst_pos] = ALPHABET[(carry & MASK_5) as usize];
1963 dst_pos += 1;
1964
1965 // shift out processed bits
1966 carry >>= SHIFT_5;
1967 carry_bits -= SHIFT_5;
1968 }
1969 }
1970
1971 // process remaining bits
1972 if carry_bits > 0 && carry > 0 {
1973 dst[dst_pos] = ALPHABET[(carry & MASK_5) as usize];
1974 dst_pos += 1;
1975 }
1976
1977 // truncate trailing zeros
1978 while dst_pos > dst_offset && dst[dst_pos - 1] == ALPHABET[0] {
1979 dst_pos -= 1;
1980 }
1981
1982 // restore leading zeros from input
1983 let mut j = 0;
1984 while j < leading_zeros {
1985 dst[dst_pos] = ALPHABET[0];
1986 dst_pos += 1;
1987 j += 1;
1988 }
1989
1990 // reverse the buffer
1991 if dst_pos > dst_offset {
1992 let mut lhs = dst_offset;
1993 let mut rhs = dst_pos - 1;
1994 while lhs < rhs {
1995 let temp = dst[lhs];
1996 dst[lhs] = dst[rhs];
1997 dst[rhs] = temp;
1998 lhs += 1;
1999 rhs -= 1;
2000 }
2001 }
2002
2003 dst_pos - dst_offset
2004 }
2005
2006 /// Decodes Crockford Base32-encoded bytes.
2007 ///
2008 /// # Notes
2009 ///
2010 /// - The output buffer must be properly sized.
2011 #[inline]
2012 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
2013 pub(crate) const fn de(
2014 src: &[u8],
2015 src_offset: usize,
2016 src_len: usize,
2017 dst: &mut [u8],
2018 dst_offset: usize,
2019 ) -> Result<usize> {
2020 const MASK_8: u16 = 0xFF;
2021 const SHIFT_8: u16 = 8;
2022
2023 let mut carry = 0;
2024 let mut carry_bits = 0;
2025 let mut dst_pos = dst_offset;
2026
2027 // count leading zeros
2028 let mut leading_zeros = 0;
2029 while leading_zeros < src_len
2030 && src[src_offset + leading_zeros] == ALPHABET[0]
2031 {
2032 leading_zeros += 1;
2033 }
2034
2035 // process characters in reverse
2036 let mut input_pos = src_offset + src_len;
2037 while input_pos > src_offset {
2038 input_pos -= 1;
2039
2040 // fetch the byte
2041 let byte = src[input_pos];
2042 if byte >= 128 {
2043 return Err(Error::InvalidCharacter {
2044 char: byte as char,
2045 index: input_pos - src_offset,
2046 });
2047 }
2048
2049 // convert the byte to a map index
2050 let index = BYTE_MAP[byte as usize];
2051 if index < 0 {
2052 return Err(Error::InvalidCharacter {
2053 char: byte as char,
2054 index: input_pos - src_offset,
2055 });
2056 }
2057
2058 // accumulate bits into carry
2059 carry |= (index as u16) << carry_bits;
2060 carry_bits += 5;
2061
2062 // extract 8-bit chunks
2063 while carry_bits >= SHIFT_8 {
2064 // write byte from chunk
2065 dst[dst_pos] = (carry & MASK_8) as u8;
2066 dst_pos += 1;
2067
2068 // shift out processed bits
2069 carry >>= SHIFT_8;
2070 carry_bits -= SHIFT_8;
2071 }
2072 }
2073
2074 // process remaining bits
2075 if carry_bits > 0 {
2076 dst[dst_pos] = carry as u8;
2077 dst_pos += 1;
2078 }
2079
2080 // truncate trailing zeros
2081 while dst_pos > dst_offset && dst[dst_pos - 1] == 0 {
2082 dst_pos -= 1;
2083 }
2084
2085 // restore leading zeros from input
2086 let mut j = 0;
2087 while j < leading_zeros {
2088 dst[dst_pos] = 0;
2089 dst_pos += 1;
2090 j += 1;
2091 }
2092
2093 // reverse the buffer
2094 if dst_pos > dst_offset {
2095 let mut lhs = dst_offset;
2096 let mut rhs = dst_pos - 1;
2097 while lhs < rhs {
2098 let temp = dst[lhs];
2099 dst[lhs] = dst[rhs];
2100 dst[rhs] = temp;
2101 lhs += 1;
2102 rhs -= 1;
2103 }
2104 }
2105
2106 Ok(dst_pos - dst_offset)
2107 }
2108
2109 /// Copies `n` bytes from `src` to `dst`.
2110 ///
2111 /// # Notes
2112 ///
2113 /// - Both slices must be properly sized.
2114 #[inline]
2115 pub(crate) const fn memcpy(
2116 dst: &mut [u8],
2117 dst_offset: usize,
2118 src: &[u8],
2119 src_offset: usize,
2120 n: usize,
2121 ) {
2122 let mut i = 0;
2123 while i < n {
2124 dst[dst_offset + i] = src[src_offset + i];
2125 i += 1;
2126 }
2127 }
2128
2129 /// Compares `n` bytes between `lhs` and `rhs`.
2130 ///
2131 /// # Notes
2132 ///
2133 /// - Both slices must be properly sized.
2134 #[inline]
2135 #[must_use]
2136 pub(crate) const fn memcmp(lhs: &[u8], rhs: &[u8], n: usize) -> bool {
2137 let mut i = 0;
2138 while i < n {
2139 if lhs[i] != rhs[i] {
2140 return false;
2141 }
2142 i += 1;
2143 }
2144 true
2145 }
2146}