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#[must_use]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct Base64QuantumCodec {
28    url_safe: bool,
29}
30
31impl Base64QuantumCodec {
32    /// Creates a standard-alphabet Base64 quantum codec.
33    ///
34    /// # Returns
35    /// Standard Base64 quantum codec.
36    #[inline]
37    pub fn standard() -> Self {
38        Self { url_safe: false }
39    }
40
41    /// Creates a URL-safe-alphabet Base64 quantum codec.
42    ///
43    /// # Returns
44    /// URL-safe Base64 quantum codec.
45    #[inline]
46    pub fn url_safe() -> Self {
47        Self { url_safe: true }
48    }
49
50    /// Selects the alphabet for this quantum codec.
51    ///
52    /// # Returns
53    /// The 64-byte alphabet used for encoding.
54    #[inline(always)]
55    fn alphabet(&self) -> &'static [u8; 64] {
56        if self.url_safe {
57            b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
58        } else {
59            b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
60        }
61    }
62
63    /// Converts one Base64 unit to a sextet.
64    ///
65    /// # Parameters
66    /// - `unit`: Encoded Base64 unit.
67    /// - `index`: Unit index in the original input.
68    ///
69    /// # Returns
70    /// Sextet value.
71    ///
72    /// # Errors
73    /// Returns [`MiscCodecError::InvalidInput`] when `unit` is not valid for
74    /// this quantum codec's alphabet.
75    fn decode_unit(&self, unit: u8, index: usize) -> MiscCodecResult<u8> {
76        match unit {
77            b'A'..=b'Z' => Ok(unit - b'A'),
78            b'a'..=b'z' => Ok(unit - b'a' + 26),
79            b'0'..=b'9' => Ok(unit - b'0' + 52),
80            b'+' if !self.url_safe => Ok(62),
81            b'/' if !self.url_safe => Ok(63),
82            b'-' if self.url_safe => Ok(62),
83            b'_' if self.url_safe => Ok(63),
84            _ => Err(MiscCodecError::InvalidInput {
85                codec: "base64-quantum",
86                reason: format!(
87                    "invalid Base64 unit '{}' at index {}",
88                    char::from(unit),
89                    index
90                ),
91            }),
92        }
93    }
94}
95
96impl Default for Base64QuantumCodec {
97    /// Creates a standard-alphabet Base64 quantum codec.
98    #[inline]
99    fn default() -> Self {
100        Self::standard()
101    }
102}
103
104impl Codec for Base64QuantumCodec {
105    type Value = [u8; 3];
106    type Unit = u8;
107    type DecodeError = MiscCodecError;
108    type EncodeError = MiscCodecError;
109
110    const MIN_UNITS_PER_VALUE: usize = 4;
111    const MAX_ENCODE_UNITS_PER_VALUE: usize = 4;
112
113    const MAX_DECODE_UNITS_PER_VALUE: usize = 4;
114
115    /// Decodes one complete four-unit Base64 quantum.
116    ///
117    /// # Safety
118    /// The caller must provide four readable units at `input_index`.
119    unsafe fn decode(
120        &mut self,
121        input: &[u8],
122        input_index: usize,
123    ) -> Result<([u8; 3], NonZeroUsize), DecodeFailure<Self::DecodeError>> {
124        debug_assert!(input_index + 4 <= input.len());
125
126        let first = self
127            .decode_unit(input[input_index], input_index)
128            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(4)))?;
129        let second = self
130            .decode_unit(input[input_index + 1], input_index + 1)
131            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(4)))?;
132        let third = self
133            .decode_unit(input[input_index + 2], input_index + 2)
134            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(4)))?;
135        let fourth = self
136            .decode_unit(input[input_index + 3], input_index + 3)
137            .map_err(|error| map_misc_decode_failure_with_consumed(error, nonzero(4)))?;
138        Ok((
139            [
140                (first << 2) | (second >> 4),
141                (second << 4) | (third >> 2),
142                (third << 6) | fourth,
143            ],
144            nonzero(4),
145        ))
146    }
147
148    /// Encodes one complete three-byte Base64 quantum.
149    ///
150    /// # Safety
151    /// The caller must provide four writable output units at `output_index`.
152    unsafe fn encode(
153        &mut self,
154        value: &[u8; 3],
155        output: &mut [u8],
156        output_index: usize,
157    ) -> Result<usize, Self::EncodeError> {
158        debug_assert!(output_index + 4 <= output.len());
159
160        let alphabet = self.alphabet();
161        output[output_index] = alphabet[(value[0] >> 2) as usize];
162        output[output_index + 1] = alphabet[(((value[0] & 0x03) << 4) | (value[1] >> 4)) as usize];
163        output[output_index + 2] = alphabet[(((value[1] & 0x0f) << 2) | (value[2] >> 6)) as usize];
164        output[output_index + 3] = alphabet[(value[2] & 0x3f) as usize];
165        Ok(4)
166    }
167}