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#![deny(unsafe_code)]
10#![cfg_attr(not(feature = "std"), no_std)]
11#![cfg_attr(docsrs, feature(doc_auto_cfg))]
12#![cfg_attr(docsrs, feature(doc_alias))]
13#![allow(clippy::doc_markdown)]
14
15//! [][Crates.io]
16//! [][Docs.rs]
17//! [][Workflow]
18//! [][License-Apache]
19//! [][License-MIT]
20//!
21//! Rust implementation of [Crockford's Base32][Crockford] encoding scheme.
22//!
23//! ## Implementation
24//!
25//! * **Lightweight** — The core functionality has zero external dependencies.
26//! * **Portable** — Fully compatible with `#![no_std]` environments.
27//! * **Safe** — Implemented entirely in safe Rust with no `unsafe` blocks.
28//!
29//! # Examples
30//!
31//! #### `std` or `alloc`
32//!
33//! ```rust
34//! # #[cfg(feature = "alloc")] {
35//! // encoding...
36//! let bytes = b"usque ad finem";
37//! let encoded = c32::encode(&bytes);
38//! assert_eq!(encoded, "1TQ6WBNCMG62S10CSMPWSBD");
39//! # }
40//! # Ok::<(), c32::Error>(())
41//! ```
42//!
43//! ```rust
44//! # #[cfg(feature = "alloc")] {
45//! // decoding...
46//! let bytes = b"usque ad finem";
47//! let decoded = c32::decode("1TQ6WBNCMG62S10CSMPWSBD")?;
48//! assert_eq!(decoded, bytes);
49//! # }
50//! # Ok::<(), c32::Error>(())
51//! ```
52//!
53//! #### `#![no_std]`
54//!
55//! ```rust
56//! // encoding...
57//! let bytes = b"usque ad finem";
58//! let mut buffer = [0; 32];
59//!
60//! let written = c32::encode_into(bytes, &mut buffer)?;
61//! let encoded = &buffer[..written];
62//! assert_eq!(encoded, b"1TQ6WBNCMG62S10CSMPWSBD");
63//! # Ok::<(), c32::Error>(())
64//! ```
65//!
66//! ```rust
67//! // decoding...
68//! let encoded = b"1TQ6WBNCMG62S10CSMPWSBD";
69//! let mut buffer = [0; 32];
70//!
71//! let written = c32::decode_into(encoded, &mut buffer)?;
72//! let decoded = &buffer[..written];
73//! assert_eq!(decoded, b"usque ad finem");
74//! # Ok::<(), c32::Error>(())
75//! ```
76//!
77//! # Checksums (`check`)
78//!
79//! The `check` feature provides methods for encoding data with SHA256-based
80//! checksum verification.
81//!
82//! The encoded data follows this layout:
83//!
84//! ```text
85//! [version (1B)] + [payload (nB)] + [checksum (4B)]
86//! ```
87//!
88//! And is computed by...
89//!
90//! ```text
91//! 1. Concatenating the version byte with the payload bytes.
92//! 2. Taking the SHA256 hash of the concatenated bytes.
93//! 3. Taking the SHA256 hash of the result.
94//! 4. Using the first 4 bytes as the checksum.
95//! ```
96//!
97//! ## Examples
98//!
99//! #### `std` or `alloc`
100//!
101//! ```rust
102//! # #[cfg(all(feature = "check", feature = "alloc"))] {
103//! // encoding...
104//! let bytes = b"usque ad finem";
105//! let encoded = c32::encode_check(bytes, 22)?;
106//! assert_eq!(encoded, "P7AWVHENJJ0RB441K6JVK5DNJ7J3V5");
107//! # }
108//! # Ok::<(), c32::Error>(())
109//! ```
110//!
111//! ```rust
112//! # #[cfg(all(feature = "check", feature = "alloc"))] {
113//! // decoding...
114//! let encoded = "P7AWVHENJJ0RB441K6JVK5DNJ7J3V5";
115//! let (version, decoded) = c32::decode_check(encoded)?;
116//! assert_eq!(decoded, b"usque ad finem");
117//! assert_eq!(version, 22);
118//! # }
119//! # Ok::<(), c32::Error>(())
120//! ```
121//!
122//! #### `#![no_std]`
123//!
124//! ```rust
125//! # #[cfg(feature = "check")] {
126//! // encoding...
127//! let bytes = b"usque ad finem";
128//! let mut buffer = [0; 32];
129//!
130//! let written = c32::encode_check_into(bytes, 22, &mut buffer)?;
131//! let encoded = &buffer[..written];
132//! assert_eq!(encoded, b"P7AWVHENJJ0RB441K6JVK5DNJ7J3V5");
133//! # }
134//! # Ok::<(), c32::Error>(())
135//! ```
136//!
137//! ```rust
138//! # #[cfg(feature = "check")] {
139//! // decoding...
140//! let encoded = b"P7AWVHENJJ0RB441K6JVK5DNJ7J3V5";
141//! let mut buffer = [0; 32];
142//!
143//! let (version, written) = c32::decode_check_into(encoded, &mut buffer)?;
144//! let decoded = &buffer[..written];
145//! assert_eq!(decoded, b"usque ad finem");
146//! assert_eq!(version, 22);
147//! # }
148//! # Ok::<(), c32::Error>(())
149//! ```
150//!
151//! # Features
152//!
153//! Feature | Description
154//! ---------|-------------------------------------------------------------
155//! `std` | Implement `std::error::Error` for [`Error`]
156//! `alloc` | Allocation-based API via [`encode`] and [`decode`]
157//! `check` | Support for checksum validation
158//!
159//! For more details, please refer to the full [API Reference][Docs.rs].
160//!
161//! [Crates.io]: https://crates.io/crates/c32
162//! [Docs.rs]: https://docs.rs/c32
163//! [Workflow]: https://github.com/52/c32/actions
164//! [License-Apache]: https://opensource.org/licenses/Apache-2.0
165//! [License-MIT]: https://opensource.org/licenses/MIT
166//! [Crockford]: https://www.crockford.com/base32.html
167
168#[cfg(feature = "alloc")]
169extern crate alloc;
170
171#[cfg(feature = "std")]
172extern crate std;
173
174/// Re-exports for `std` & `alloc` compatibility.
175///
176/// This module provides a unified interface for common allocation types.
177pub(crate) mod lib {
178 #[cfg(feature = "std")]
179 mod core {
180 pub use std::borrow::Cow;
181 pub use std::string::String;
182 pub use std::vec;
183 pub use std::vec::Vec;
184 }
185
186 #[cfg(all(not(feature = "std"), feature = "alloc"))]
187 mod core {
188 pub use alloc::borrow::Cow;
189 pub use alloc::string::String;
190 pub use alloc::vec;
191 pub use alloc::vec::Vec;
192 }
193
194 #[cfg(any(feature = "std", feature = "alloc"))]
195 pub use core::*;
196}
197
198/// Checksum computation and validation for Crockford Base32Check encoding.
199///
200/// This module provides functionality for generating 4-byte checksums.
201#[cfg(feature = "check")]
202pub mod checksum {
203 use sha2::Digest;
204 use sha2::Sha256;
205
206 /// Length of the checksum in bytes.
207 pub const BYTE_LENGTH: usize = 4;
208
209 /// Type alias for a checksum.
210 pub type Checksum = [u8; BYTE_LENGTH];
211
212 /// Computes a 4-byte checksum from input bytes and a version number.
213 ///
214 /// The checksum is computed by:
215 /// 1. Concatenating the version byte with the payload bytes.
216 /// 2. Taking the SHA256 hash of the concatenated bytes.
217 /// 3. Taking the SHA256 hash of the result.
218 /// 4. Using the first 4 bytes as the checksum.
219 ///
220 /// # Arguments
221 /// * `bytes` - The input bytes to compute the checksum for.
222 /// * `version` - A version byte to prepend to the input bytes.
223 ///
224 /// # Returns
225 /// A 4-byte array containing the computed checksum.
226 pub fn compute<B>(bytes: B, version: u8) -> Checksum
227 where
228 B: AsRef<[u8]>,
229 {
230 let bytes = bytes.as_ref();
231 let buffer = Sha256::new()
232 .chain_update([version])
233 .chain_update(bytes)
234 .finalize();
235
236 let mut checksum = [0u8; BYTE_LENGTH];
237 checksum.copy_from_slice(&Sha256::digest(buffer)[..BYTE_LENGTH]);
238 checksum
239 }
240
241 /// Creates a checksum from a slice of bytes.
242 ///
243 /// # Returns
244 /// A 4-byte array containing the first 4 bytes from the input.
245 ///
246 /// # Panics
247 /// Panics if the input slice contains fewer than 4 bytes.
248 pub(crate) fn from_slice<B>(bytes: B) -> Checksum
249 where
250 B: AsRef<[u8]>,
251 {
252 let bytes = bytes.as_ref();
253 let mut checksum = [0u8; BYTE_LENGTH];
254 checksum.copy_from_slice(&bytes[..BYTE_LENGTH]);
255 checksum
256 }
257}
258
259/// Crockford Base32 alphabet, used for encoding/decoding.
260pub(crate) const C32_ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
261
262/// Crockford Base32 byte map, used for lookup of values.
263pub(crate) const C32_BYTE_MAP: [i8; 128] = [
264 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
265 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
266 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1,
267 -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20,
268 21, 0, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1,
269 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25,
270 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1,
271];
272
273/// Error variants for Crockford Base32 encoding and decoding.
274#[derive(Debug, Clone, PartialEq, Eq)]
275pub enum Error {
276 /// Attempted to decode an invalid Crockford-encoded string.
277 InvalidString,
278 /// Encountered a character that is not present in the `C32_ALPHABET`.
279 InvalidChar(char),
280 /// Computed checksum does not match expected checksum.
281 #[cfg(feature = "check")]
282 InvalidChecksum(checksum::Checksum, checksum::Checksum),
283 /// Version must be less than or equal to 32.
284 #[cfg(feature = "check")]
285 InvalidVersion(u8),
286 /// Data needs for an operation is not met.
287 #[cfg(feature = "check")]
288 InvalidDataSize(usize, usize),
289 /// Buffer does not have enough capacity.
290 InvalidBufferSize(usize, usize),
291 /// Conversion from a integer failed.
292 TryFromInt(core::num::TryFromIntError),
293}
294
295impl core::fmt::Display for Error {
296 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
297 match self {
298 Self::InvalidString => {
299 write!(f, "String must contain only valid ASCII characters")
300 }
301 Self::InvalidChar(char) => {
302 write!(f, "Character '{char}' is not a valid C32 character")
303 }
304 #[cfg(feature = "check")]
305 Self::InvalidChecksum(comp, exp) => {
306 write!(f, "Checksum validation failed: computed {comp:?}, expected {exp:?}")
307 }
308 #[cfg(feature = "check")]
309 Self::InvalidVersion(version) => {
310 write!(f, "Version must be <= 32, got {version}")
311 }
312 #[cfg(feature = "check")]
313 Self::InvalidDataSize(recv, min) => {
314 write!(f, "Not enough data: received '{recv}' bytes, minimum required is '{min}'")
315 }
316 Self::InvalidBufferSize(recv, min) => {
317 write!(f, "Not enough buffer capacity: received '{recv}', minimum required is '{min}'")
318 }
319 Self::TryFromInt(err) => {
320 write!(f, "{err}")
321 }
322 }
323 }
324}
325
326#[cfg(feature = "std")]
327impl std::error::Error for Error {}
328
329impl From<core::num::TryFromIntError> for Error {
330 fn from(err: core::num::TryFromIntError) -> Self {
331 Self::TryFromInt(err)
332 }
333}
334
335/// Result type for fallible Crockford Base32 operations.
336pub(crate) type Result<T> = core::result::Result<T, Error>;
337
338/// Computes the required buffer capacity for encoding into Crockford Base32.
339///
340/// # Examples
341///
342/// ```rust
343/// assert_eq!(c32::encoded_len(0), 0);
344/// assert_eq!(c32::encoded_len(1), 2);
345/// assert_eq!(c32::encoded_len(3), 5);
346/// ```
347#[must_use]
348pub const fn encoded_len(len: usize) -> usize {
349 (len * 8 + 4) / 5
350}
351
352/// Encodes bytes as Crockford Base32 into a provided output buffer.
353///
354/// # Returns
355/// - `Ok(usize)`: The number of bytes written to the output buffer.
356/// - `Err(Error)`: If any errors occur during the encoding process.
357///
358/// # Errors
359/// - [`Error::InvalidBufferSize`] if the output buffer is too small.
360///
361/// # Examples
362/// ```rust
363/// let bytes = b"usque ad finem";
364///
365/// // allocate a buffer with enough capacity
366/// let mut buffer = [0; 32];
367///
368/// // encode bytes into the buffer
369/// let written = c32::encode_into(bytes, &mut buffer)?;
370///
371/// let expected = b"1TQ6WBNCMG62S10CSMPWSBD";
372/// assert_eq!(&buffer[..written], expected);
373/// # Ok::<(), c32::Error>(())
374/// ```
375pub fn encode_into<'a, B>(bytes: B, output: &mut [u8]) -> Result<usize>
376where
377 B: Clone + IntoIterator<Item = &'a u8>,
378 B::IntoIter: DoubleEndedIterator,
379{
380 // for 5-bit chunks
381 const MASK_5: u32 = 0x1F;
382 const SHIFT_5: u32 = 5;
383
384 let mut carry = 0;
385 let mut carry_bits = 0;
386 let mut output_pos = 0;
387
388 // process bytes in reverse
389 for byte in bytes.clone().into_iter().rev() {
390 // accumulate bits into carry
391 carry |= u32::from(*byte) << carry_bits;
392 carry_bits += 8;
393
394 // extract 5-bit chunks
395 while carry_bits >= SHIFT_5 {
396 // assert that we have enough capacity
397 if output_pos >= output.len() {
398 return Err(Error::InvalidBufferSize(
399 output.len(),
400 output_pos + 1,
401 ));
402 }
403
404 // write character from chunk
405 output[output_pos] = C32_ALPHABET[(carry & MASK_5) as usize];
406 output_pos += 1;
407
408 // shift out processed bits
409 carry >>= SHIFT_5;
410 carry_bits -= SHIFT_5;
411 }
412 }
413
414 // process the remaining bits
415 if carry_bits > 0 {
416 output[output_pos] = C32_ALPHABET[(carry & MASK_5) as usize];
417 output_pos += 1;
418 }
419
420 // truncate the trailing zeros
421 while output_pos > 0 && output[output_pos - 1] == C32_ALPHABET[0] {
422 output_pos -= 1;
423 }
424
425 // restore the leading zeros from the original input
426 for _ in bytes.into_iter().take_while(|&&b| b == 0) {
427 // assert that we have enough capacity
428 if output_pos >= output.len() {
429 return Err(Error::InvalidBufferSize(output.len(), output_pos + 1));
430 }
431
432 // write zero character to output
433 output[output_pos] = C32_ALPHABET[0];
434 output_pos += 1;
435 }
436
437 // reverse buffer to get correct byte order
438 output[..output_pos].reverse();
439
440 Ok(output_pos)
441}
442
443/// Computes the required capacity for encoding into Crockford Base32Check.
444///
445/// # Examples
446///
447/// ```rust
448/// assert_eq!(c32::encoded_check_len(0), 8);
449/// assert_eq!(c32::encoded_check_len(1), 9);
450/// assert_eq!(c32::encoded_check_len(3), 13);
451/// ```
452#[must_use]
453#[cfg(feature = "check")]
454pub const fn encoded_check_len(len: usize) -> usize {
455 1 + encoded_len(len + 4)
456}
457
458/// Encodes bytes as Crockford Base32Check into a provided output buffer.
459///
460/// # Returns
461/// - `Ok(usize)`: The number of bytes written to the output buffer.
462/// - `Err(Error)`: If any errors occur during the encoding process.
463///
464/// # Errors
465/// * [`Error::InvalidVersion`] if the version >= 32.
466/// - [`Error::InvalidBufferSize`] if the output buffer is too small.
467///
468/// # Examples
469/// ```rust
470/// let bytes = b"usque ad finem";
471/// let version = 22;
472///
473/// // allocate a buffer with enough capacity
474/// let mut buffer = [0; 32];
475///
476/// // encode bytes into the buffer
477/// let written = c32::encode_check_into(bytes, version, &mut buffer)?;
478///
479/// let expected = b"P7AWVHENJJ0RB441K6JVK5DNJ7J3V5";
480/// assert_eq!(&buffer[..written], expected);
481/// # Ok::<(), c32::Error>(())
482/// ```
483#[cfg(feature = "check")]
484pub fn encode_check_into<B>(
485 bytes: B,
486 version: u8,
487 output: &mut [u8],
488) -> Result<usize>
489where
490 B: AsRef<[u8]>,
491{
492 let bytes = bytes.as_ref();
493
494 // assert that the version is valid
495 if version >= 32 {
496 return Err(Error::InvalidVersion(version));
497 }
498
499 // assert that we have enough capacity
500 let capacity = encoded_check_len(bytes.len());
501 if output.len() < capacity {
502 return Err(Error::InvalidBufferSize(output.len(), capacity));
503 }
504
505 // insert the version character
506 let mut output_pos = 0;
507 output[output_pos] = C32_ALPHABET[version as usize];
508 output_pos += 1;
509
510 // compute the input checksum
511 let checksum = checksum::compute(bytes, version);
512
513 // encode [bytes + checksum] into the buffer
514 output_pos += encode_into(
515 bytes.iter().chain(checksum.iter()),
516 &mut output[output_pos..],
517 )?;
518
519 Ok(output_pos)
520}
521
522/// Encodes bytes into a Crockford Base32-encoded string.
523///
524/// # Panics
525/// This function can panic in two cases:
526/// - If encoding fails despite sufficient buffer capacity.
527/// - If the encoded output contains non-UTF8 bytes.
528///
529/// All panics indicate implementation issues and should never occur.
530///
531/// # Examples
532/// ```rust
533/// let bytes = b"usque ad finem";
534///
535/// // encode bytes into a string
536/// let encoded = c32::encode(bytes);
537///
538/// let expected = "1TQ6WBNCMG62S10CSMPWSBD";
539/// assert_eq!(encoded, expected);
540/// ```
541#[cfg(feature = "alloc")]
542pub fn encode<B>(bytes: B) -> lib::String
543where
544 B: AsRef<[u8]>,
545{
546 let bytes = bytes.as_ref();
547
548 // allocate the output buffer
549 let capacity = encoded_len(bytes.len());
550 let mut output = lib::vec![0; capacity];
551
552 // SAFETY:
553 // this should not panic as the buffer is allocated with enough capacity
554 let written = encode_into(bytes, &mut output).unwrap();
555 output.truncate(written);
556
557 // SAFETY:
558 // this should not panic as the output only contains ASCII characters
559 lib::String::from_utf8(output).unwrap()
560}
561
562/// Encodes bytes into a Crockford Base32Check-encoded string.
563///
564/// # Errors
565/// * [`Error::InvalidVersion`] if the version >= 32.
566/// - [`Error::InvalidBufferSize`] if the output buffer is too small.
567///
568/// # Panics
569/// This function can panic in one cases:
570/// - If the encoded output contains non-UTF8 bytes.
571///
572/// All panics indicate implementation issues and should never occur.
573///
574/// # Examples
575/// ```rust
576/// let bytes = b"usque ad finem";
577/// let version = 22;
578///
579/// // encode bytes with into a string
580/// let encoded = c32::encode_check(bytes, version)?;
581///
582/// let expected = "P7AWVHENJJ0RB441K6JVK5DNJ7J3V5";
583/// assert_eq!(encoded, expected);
584/// # Ok::<(), c32::Error>(())
585/// ```
586#[cfg(all(feature = "check", feature = "alloc"))]
587pub fn encode_check<B>(bytes: B, version: u8) -> Result<lib::String>
588where
589 B: AsRef<[u8]>,
590{
591 let bytes = bytes.as_ref();
592
593 // allocate the output buffer
594 let capacity = encoded_check_len(bytes.len());
595 let mut output = lib::vec![0; capacity];
596
597 // encode into the output buffer
598 let written = encode_check_into(bytes, version, &mut output)?;
599 output.truncate(written);
600
601 // SAFETY:
602 // this should not panic as the output only contains ASCII characters
603 Ok(lib::String::from_utf8(output).unwrap())
604}
605
606/// Computes the required capacity for decoding from Crockford Base32.
607///
608/// # Examples
609///
610/// ```rust
611/// assert_eq!(c32::decoded_len(0), 0);
612/// assert_eq!(c32::decoded_len(2), 2);
613/// assert_eq!(c32::decoded_len(5), 5);
614/// ```
615#[must_use]
616pub const fn decoded_len(len: usize) -> usize {
617 len
618}
619
620/// Decodes Crockford Base32-encoded bytes into a provided output buffer.
621///
622/// # Returns
623/// - `Ok(usize)`: The number of bytes written to the output buffer.
624/// - `Err(Error)`: If any errors occur during the decoding process.
625///
626/// # Errors
627/// - [`Error::InvalidString`] if the input contains non-ASCII characters.
628/// - [`Error::InvalidChar`] if the character is not found in `C32_ALPHABET`.
629/// - [`Error::InvalidBufferSize`] if the output buffer is too small.
630/// - [`Error::TryFromInt`] when bit arithmetic operations exceeds bounds.
631///
632/// # Examples
633/// ```rust
634/// let bytes = b"1TQ6WBNCMG62S10CSMPWSBD";
635///
636/// // allocate a buffer with enough capacity
637/// let mut buffer = [0; 32];
638///
639/// // decode bytes into the buffer
640/// let written = c32::decode_into(bytes, &mut buffer)?;
641///
642/// let expected = b"usque ad finem";
643/// assert_eq!(&buffer[..written], expected);
644/// # Ok::<(), c32::Error>(())
645/// ```
646pub fn decode_into<B>(bytes: B, output: &mut [u8]) -> Result<usize>
647where
648 B: AsRef<[u8]>,
649{
650 // for 8-bit chunks
651 const MASK_8: u32 = 0xFF;
652 const SHIFT_8: u32 = 8;
653
654 let bytes = bytes.as_ref();
655
656 // return early if the bytes are empty
657 if bytes.is_empty() {
658 return Ok(0);
659 }
660
661 // assert that the bytes are ascii
662 if !bytes.is_ascii() {
663 return Err(Error::InvalidString);
664 }
665
666 // assert that we have enough capacity
667 let capacity = decoded_len(bytes.len());
668 if output.len() < capacity {
669 return Err(Error::InvalidBufferSize(output.len(), capacity));
670 }
671
672 let mut carry = 0;
673 let mut carry_bits = 0;
674 let mut output_pos = 0;
675
676 // process characters in reverse
677 for char in bytes.iter().rev() {
678 let index = C32_BYTE_MAP.get(*char as usize).copied().unwrap_or(-1);
679
680 // assert that our character is present in `C32_BYTE_MAP`
681 if index.is_negative() {
682 return Err(Error::InvalidChar(*char as char));
683 }
684
685 // accumulate bits into carry
686 carry |= u32::from(u8::try_from(index)?) << carry_bits;
687 carry_bits += 5;
688
689 // extract 8-bit chunks
690 while carry_bits >= SHIFT_8 {
691 // write the byte from chunk
692 output[output_pos] = (carry & MASK_8) as u8;
693 output_pos += 1;
694
695 // shift out processed bits
696 carry >>= SHIFT_8;
697 carry_bits -= SHIFT_8;
698 }
699 }
700
701 // process the remaining bits
702 if carry_bits > 0 {
703 output[output_pos] = u8::try_from(carry)?;
704 output_pos += 1;
705 }
706
707 // truncate the trailing zeros
708 while output_pos > 0 && output[output_pos - 1] == 0 {
709 output_pos -= 1;
710 }
711
712 // restore the leading zeros from the original input
713 let zeros = bytes.iter().take_while(|&&b| b == C32_ALPHABET[0]).count();
714 if zeros > 0 {
715 output[output_pos..output_pos + zeros].fill(0);
716 output_pos += zeros;
717 }
718
719 // reverse buffer to get correct byte order
720 output[..output_pos].reverse();
721
722 Ok(output_pos)
723}
724
725/// Computes the required capacity for decoding from Crockford Base32Check.
726///
727/// # Examples
728///
729/// ```rust
730/// assert_eq!(c32::decoded_check_len(0), 0);
731/// assert_eq!(c32::decoded_check_len(2), 2);
732/// assert_eq!(c32::decoded_check_len(5), 5);
733/// ```
734#[must_use]
735#[cfg(feature = "check")]
736pub const fn decoded_check_len(len: usize) -> usize {
737 len
738}
739
740/// Decodes Crockford Base32Check bytes into a provided output buffer.
741///
742/// # Returns
743/// - `Ok((u8, usize))`: The version byte and number of bytes written.
744/// - `Err(Error)`: If any errors occur during the decoding process.
745///
746/// # Errors
747/// - [`Error::InvalidString`] if the input contains non-ASCII characters.
748/// - [`Error::InvalidChar`] if the character is not found in `C32_ALPHABET`.
749/// - [`Error::InvalidBufferSize`] if the output buffer is too small.
750/// - [`Error::InvalidDataSize`] when data requirements are not met.
751/// - [`Error::TryFromInt`] when bit arithmetic operations exceeds bounds.
752///
753/// # Panics
754/// This function can panic in two cases:
755/// - If slice bounds are exceeded while computing the checksum.
756/// - If the input bytes are empty when calling `split_first()`.
757///
758/// This panic indicates an implementation issue and should never occur.
759///
760/// # Examples
761/// ```rust
762/// let bytes = b"P7AWVHENJJ0RB441K6JVK5DNJ7J3V5";
763///
764/// // allocate a buffer with enough capacity
765/// let mut buffer = [0; 32];
766///
767/// // decode bytes with checksum into the buffer
768/// let (version, written) = c32::decode_check_into(bytes, &mut buffer)?;
769///
770/// let expected = b"usque ad finem";
771/// assert_eq!(&buffer[..written], expected);
772/// assert_eq!(version, 22);
773/// # Ok::<(), c32::Error>(())
774/// ```
775#[cfg(feature = "check")]
776pub fn decode_check_into<B>(bytes: B, output: &mut [u8]) -> Result<(u8, usize)>
777where
778 B: AsRef<[u8]>,
779{
780 let bytes = bytes.as_ref();
781
782 // assert the minimal byte length (version + bytes)
783 if bytes.len() < 2 {
784 return Err(Error::InvalidDataSize(bytes.len(), 2));
785 }
786
787 // SAFETY:
788 // the length check above ensures we have enough bytes to split
789 let (tag, bytes) = bytes.split_first().unwrap();
790
791 // decode bytes + checksum into the output buffer
792 let mut output_pos = decode_into(bytes, output)?;
793
794 // assert that we wrote at least 4 bytes (checksum)
795 if output_pos < checksum::BYTE_LENGTH {
796 return Err(Error::InvalidDataSize(bytes.len(), 2));
797 }
798
799 // decode the version tag
800 let mut buffer = [0; 1];
801 decode_into([*tag], &mut buffer)?;
802 let version = buffer[0];
803
804 // compute the checksums (computed and expected)
805 output_pos -= checksum::BYTE_LENGTH;
806 let comp_checksum = checksum::compute(&output[..output_pos], version);
807 let exp_checksum = checksum::from_slice(
808 &output[output_pos..output_pos + checksum::BYTE_LENGTH],
809 );
810
811 // assert that both checksums match
812 if comp_checksum != exp_checksum {
813 return Err(Error::InvalidChecksum(comp_checksum, exp_checksum));
814 }
815
816 Ok((version, output_pos))
817}
818
819/// Decodes a Crockford Base32-encoded string.
820///
821/// # Returns
822/// - `Ok(Vec<u8>)`: A vector containing the decoded bytes.
823/// - `Err(Error)`: If any errors occur during the decoding process.
824///
825/// # Errors
826/// - [`Error::InvalidString`] if the input contains non-ASCII characters.
827/// - [`Error::InvalidChar`] if the character is not found in `C32_ALPHABET`.
828/// - [`Error::InvalidBufferSize`] if the output buffer is too small.
829/// - [`Error::TryFromInt`] when bit arithmetic operations exceeds bounds.
830///
831/// # Examples
832/// ```rust
833/// let encoded = "1TQ6WBNCMG62S10CSMPWSBD";
834///
835/// // decode string into a vector
836/// let decoded = c32::decode(encoded)?;
837///
838/// let expected = b"usque ad finem";
839/// assert_eq!(&decoded, expected);
840/// # Ok::<(), c32::Error>(())
841/// ```
842#[cfg(feature = "alloc")]
843pub fn decode<'a, S>(str: S) -> Result<lib::Vec<u8>>
844where
845 S: Into<lib::Cow<'a, str>>,
846{
847 let str = str.into();
848
849 // allocate the output buffer
850 let capacity = decoded_len(str.len());
851 let mut output = lib::vec![0; capacity];
852
853 // decode into the output buffer
854 let written = decode_into(str.as_ref(), &mut output)?;
855 output.truncate(written);
856
857 Ok(output)
858}
859
860/// Decodes a Crockford Base32Check-encoded string.
861///
862/// # Returns
863/// - `Ok((u8, Vec<u8>))`: The version byte and decoded bytes.
864/// - `Err(Error)`: If any errors occur during the decoding process.
865///
866/// # Errors
867/// - [`Error::InvalidString`] if the input contains non-ASCII characters.
868/// - [`Error::InvalidChar`] if the character is not found in `C32_ALPHABET`.
869/// - [`Error::InvalidBufferSize`] if the output buffer is too small.
870/// - [`Error::InvalidDataSize`] when data requirements are not met.
871/// - [`Error::TryFromInt`] when bit arithmetic operations exceeds bounds.
872///
873/// # Examples
874/// ```rust
875/// let encoded = "P7AWVHENJJ0RB441K6JVK5DNJ7J3V5";
876///
877/// // decode string with into a vector
878/// let (version, decoded) = c32::decode_check(encoded)?;
879///
880/// let expected = b"usque ad finem";
881/// assert_eq!(decoded, expected);
882/// assert_eq!(version, 22);
883/// # Ok::<(), c32::Error>(())
884/// ```
885#[cfg(all(feature = "check", feature = "alloc"))]
886pub fn decode_check<'a, S>(str: S) -> Result<(u8, lib::Vec<u8>)>
887where
888 S: Into<lib::Cow<'a, str>>,
889{
890 let str = str.into();
891
892 // allocate the output buffer
893 let capacity = decoded_check_len(str.len());
894 let mut output = lib::vec![0; capacity];
895
896 // decode into the output buffer
897 let (version, written) = decode_check_into(str.as_ref(), &mut output)?;
898 output.truncate(written);
899
900 Ok((version, output))
901}