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](https://img.shields.io/crates/v/c32.svg)][Crates.io]
16//! [![Documentation](https://docs.rs/c32/badge.svg)][Docs.rs]
17//! [![Build Status](https://img.shields.io/github/actions/workflow/status/52/c32/rust.yml?branch=master)][Workflow]
18//! [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)][License-Apache]
19//! [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)][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}