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