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