Skip to main content

qubit_codec_misc/
base64_quantum_codec.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Base64 quantum codec.
9
10use core::num::NonZeroUsize;
11
12use qubit_codec::Codec;
13use qubit_codec::DecodeFailure;
14use qubit_utils::nonzero;
15
16use crate::MiscCodecError;
17use crate::MiscCodecResult;
18use crate::misc_codec_error::map_misc_decode_failure_with_consumed;
19
20/// Encodes and decodes one complete Base64 quantum.
21///
22/// A quantum maps exactly three raw bytes to four Base64 units. It does not
23/// handle final short input groups or `=` padding; callers that process streams
24/// must finalize those cases in a transcoder or facade layer.
25///
26/// # Examples
27///
28/// ```
29/// use qubit_codec_misc::Base64QuantumCodec;
30///
31/// let codec = Base64QuantumCodec::standard();
32/// assert_eq!(codec, Base64QuantumCodec::default());
33/// ```
34#[must_use]
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct Base64QuantumCodec {
37    /// Selects the URL-safe alphabet when set; otherwise the standard alphabet
38    /// is used.
39    url_safe: bool,
40}
41
42impl Base64QuantumCodec {
43    /// Creates a standard-alphabet Base64 quantum codec.
44    ///
45    /// # Returns
46    /// Standard Base64 quantum codec.
47    #[inline]
48    pub fn standard() -> Self {
49        Self { url_safe: false }
50    }
51
52    /// Creates a URL-safe-alphabet Base64 quantum codec.
53    ///
54    /// # Returns
55    /// URL-safe Base64 quantum codec.
56    #[inline]
57    pub fn url_safe() -> Self {
58        Self { url_safe: true }
59    }
60
61    /// Selects the alphabet for this quantum codec.
62    ///
63    /// # Returns
64    /// The 64-byte alphabet used for encoding.
65    #[inline]
66    fn alphabet(&self) -> &'static [u8; 64] {
67        if self.url_safe {
68            b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
69        } else {
70            b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
71        }
72    }
73
74    /// Converts one Base64 unit to a sextet.
75    ///
76    /// # Parameters
77    /// - `unit`: Encoded Base64 unit.
78    /// - `index`: Unit index in the original input.
79    ///
80    /// # Returns
81    /// Sextet value.
82    ///
83    /// # Errors
84    /// Returns [`MiscCodecError::InvalidInput`] when `unit` is not valid for
85    /// this quantum codec's alphabet.
86    fn decode_unit(&self, unit: u8, index: usize) -> MiscCodecResult<u8> {
87        match unit {
88            b'A'..=b'Z' => Ok(unit - b'A'),
89            b'a'..=b'z' => Ok(unit - b'a' + 26),
90            b'0'..=b'9' => Ok(unit - b'0' + 52),
91            b'+' if !self.url_safe => Ok(62),
92            b'/' if !self.url_safe => Ok(63),
93            b'-' if self.url_safe => Ok(62),
94            b'_' if self.url_safe => Ok(63),
95            _ => Err(MiscCodecError::InvalidInput {
96                codec: "base64-quantum",
97                reason: format!("invalid Base64 unit '{}' at index {}", char::from(unit), index),
98            }),
99        }
100    }
101}
102
103impl Default for Base64QuantumCodec {
104    /// Creates a standard-alphabet Base64 quantum codec.
105    #[inline]
106    fn default() -> Self {
107        Self::standard()
108    }
109}
110
111impl Codec for Base64QuantumCodec {
112    type Value = [u8; 3];
113    type Unit = u8;
114    type DecodeError = MiscCodecError;
115    type EncodeError = MiscCodecError;
116
117    const MIN_UNITS_PER_VALUE: usize = 4;
118    const MAX_ENCODE_UNITS_PER_VALUE: usize = 4;
119
120    const MAX_DECODE_UNITS_PER_VALUE: usize = 4;
121
122    /// Decodes one complete four-unit Base64 quantum.
123    ///
124    /// # Safety
125    /// The caller must provide four readable units at `input_index`.
126    unsafe fn decode(
127        &mut self,
128        input: &[u8],
129        input_index: usize,
130    ) -> Result<([u8; 3], NonZeroUsize), DecodeFailure<Self::DecodeError>> {
131        debug_assert!(input_index + 4 <= input.len());
132
133        let first = self
134            .decode_unit(input[input_index], input_index)
135            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(4)))?;
136        let second = self
137            .decode_unit(input[input_index + 1], input_index + 1)
138            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(4)))?;
139        let third = self
140            .decode_unit(input[input_index + 2], input_index + 2)
141            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(4)))?;
142        let fourth = self
143            .decode_unit(input[input_index + 3], input_index + 3)
144            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(4)))?;
145        Ok((
146            [
147                (first << 2) | (second >> 4),
148                (second << 4) | (third >> 2),
149                (third << 6) | fourth,
150            ],
151            nonzero(4),
152        ))
153    }
154
155    /// Encodes one complete three-byte Base64 quantum.
156    ///
157    /// # Safety
158    /// The caller must provide four writable output units at `output_index`.
159    unsafe fn encode(
160        &mut self,
161        value: &[u8; 3],
162        output: &mut [u8],
163        output_index: usize,
164    ) -> Result<usize, Self::EncodeError> {
165        debug_assert!(output_index + 4 <= output.len());
166
167        let alphabet = self.alphabet();
168        output[output_index] = alphabet[(value[0] >> 2) as usize];
169        output[output_index + 1] = alphabet[(((value[0] & 0x03) << 4) | (value[1] >> 4)) as usize];
170        output[output_index + 2] = alphabet[(((value[1] & 0x0f) << 2) | (value[2] >> 6)) as usize];
171        output[output_index + 3] = alphabet[(value[2] & 0x3f) as usize];
172        Ok(4)
173    }
174}