Skip to main content

bluetape_rs_codec/
base64.rs

1//! Strict Base64 encoding and decoding.
2
3use std::error::Error;
4use std::fmt;
5
6use base64::Engine as _;
7use base64::engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD};
8
9/// Error returned when Base64 decoding rejects caller-owned input.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum Base64DecodeError {
13    /// The encoded text contains a byte outside the selected Base64 alphabet.
14    InvalidByte {
15        /// Zero-based byte position of the invalid byte.
16        index: usize,
17        /// Invalid byte value.
18        byte: u8,
19    },
20    /// The encoded text length is invalid for Base64 decoding.
21    InvalidLength {
22        /// Number of bytes in the encoded text.
23        len: usize,
24    },
25    /// The final non-padding symbol has impossible trailing bits.
26    InvalidLastSymbol {
27        /// Zero-based byte position of the invalid symbol.
28        index: usize,
29        /// Invalid byte value.
30        byte: u8,
31    },
32    /// The encoded text violates the selected padding policy.
33    InvalidPadding,
34}
35
36impl fmt::Display for Base64DecodeError {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        match self {
39            Self::InvalidByte { index, byte } => {
40                write!(
41                    f,
42                    "base64 input contains invalid byte 0x{byte:02x} at position {index}"
43                )
44            }
45            Self::InvalidLength { len } => {
46                write!(f, "base64 input has invalid byte length {len}")
47            }
48            Self::InvalidLastSymbol { index, byte } => {
49                write!(
50                    f,
51                    "base64 input contains invalid last symbol 0x{byte:02x} at position {index}"
52                )
53            }
54            Self::InvalidPadding => write!(f, "base64 input violates padding policy"),
55        }
56    }
57}
58
59impl Error for Base64DecodeError {}
60
61impl From<base64::DecodeError> for Base64DecodeError {
62    fn from(error: base64::DecodeError) -> Self {
63        match error {
64            base64::DecodeError::InvalidByte(index, byte) => Self::InvalidByte { index, byte },
65            base64::DecodeError::InvalidLength(len) => Self::InvalidLength { len },
66            base64::DecodeError::InvalidLastSymbol(index, byte) => {
67                Self::InvalidLastSymbol { index, byte }
68            }
69            base64::DecodeError::InvalidPadding => Self::InvalidPadding,
70        }
71    }
72}
73
74/// Encodes bytes with the standard Base64 alphabet and `=` padding.
75///
76/// # Examples
77///
78/// ```
79/// use bluetape_rs_codec::encode_base64;
80///
81/// assert_eq!(encode_base64(b"fo"), "Zm8=");
82/// ```
83#[must_use]
84pub fn encode_base64(bytes: impl AsRef<[u8]>) -> String {
85    STANDARD.encode(bytes)
86}
87
88/// Decodes standard Base64 text that uses the standard alphabet and padding.
89///
90/// # Examples
91///
92/// ```
93/// use bluetape_rs_codec::decode_base64;
94///
95/// assert_eq!(decode_base64("Zm8=")?, b"fo");
96/// # Ok::<(), bluetape_rs_codec::Base64DecodeError>(())
97/// ```
98///
99/// # Errors
100///
101/// Returns [`Base64DecodeError`] when input contains invalid bytes, invalid
102/// length, invalid trailing bits, or padding that does not match the standard
103/// padded policy.
104pub fn decode_base64(encoded: impl AsRef<str>) -> Result<Vec<u8>, Base64DecodeError> {
105    STANDARD.decode(encoded.as_ref()).map_err(Into::into)
106}
107
108/// Encodes bytes with the standard Base64 alphabet and no padding.
109///
110/// # Examples
111///
112/// ```
113/// use bluetape_rs_codec::encode_base64_unpadded;
114///
115/// assert_eq!(encode_base64_unpadded(b"fo"), "Zm8");
116/// ```
117#[must_use]
118pub fn encode_base64_unpadded(bytes: impl AsRef<[u8]>) -> String {
119    STANDARD_NO_PAD.encode(bytes)
120}
121
122/// Decodes standard Base64 text that must not contain padding.
123///
124/// # Examples
125///
126/// ```
127/// use bluetape_rs_codec::decode_base64_unpadded;
128///
129/// assert_eq!(decode_base64_unpadded("Zm8")?, b"fo");
130/// # Ok::<(), bluetape_rs_codec::Base64DecodeError>(())
131/// ```
132///
133/// # Errors
134///
135/// Returns [`Base64DecodeError`] when input contains invalid bytes, invalid
136/// length, invalid trailing bits, or padding in a no-padding variant.
137pub fn decode_base64_unpadded(encoded: impl AsRef<str>) -> Result<Vec<u8>, Base64DecodeError> {
138    STANDARD_NO_PAD.decode(encoded.as_ref()).map_err(Into::into)
139}
140
141/// Encodes bytes with the URL-safe Base64 alphabet and `=` padding.
142///
143/// # Examples
144///
145/// ```
146/// use bluetape_rs_codec::encode_base64_url;
147///
148/// assert_eq!(encode_base64_url([0xfb, 0xff]), "-_8=");
149/// ```
150#[must_use]
151pub fn encode_base64_url(bytes: impl AsRef<[u8]>) -> String {
152    URL_SAFE.encode(bytes)
153}
154
155/// Decodes URL-safe Base64 text that uses `=` padding.
156///
157/// # Examples
158///
159/// ```
160/// use bluetape_rs_codec::decode_base64_url;
161///
162/// assert_eq!(decode_base64_url("-_8=")?, vec![0xfb, 0xff]);
163/// # Ok::<(), bluetape_rs_codec::Base64DecodeError>(())
164/// ```
165///
166/// # Errors
167///
168/// Returns [`Base64DecodeError`] when input contains invalid bytes, invalid
169/// length, invalid trailing bits, or padding that does not match the URL-safe
170/// padded policy.
171pub fn decode_base64_url(encoded: impl AsRef<str>) -> Result<Vec<u8>, Base64DecodeError> {
172    URL_SAFE.decode(encoded.as_ref()).map_err(Into::into)
173}
174
175/// Encodes bytes with the URL-safe Base64 alphabet and no padding.
176///
177/// # Examples
178///
179/// ```
180/// use bluetape_rs_codec::encode_base64_url_unpadded;
181///
182/// assert_eq!(encode_base64_url_unpadded([0xfb, 0xff]), "-_8");
183/// ```
184#[must_use]
185pub fn encode_base64_url_unpadded(bytes: impl AsRef<[u8]>) -> String {
186    URL_SAFE_NO_PAD.encode(bytes)
187}
188
189/// Decodes URL-safe Base64 text that must not contain padding.
190///
191/// # Examples
192///
193/// ```
194/// use bluetape_rs_codec::decode_base64_url_unpadded;
195///
196/// assert_eq!(decode_base64_url_unpadded("-_8")?, vec![0xfb, 0xff]);
197/// # Ok::<(), bluetape_rs_codec::Base64DecodeError>(())
198/// ```
199///
200/// # Errors
201///
202/// Returns [`Base64DecodeError`] when input contains invalid bytes, invalid
203/// length, invalid trailing bits, or padding in a no-padding variant.
204pub fn decode_base64_url_unpadded(encoded: impl AsRef<str>) -> Result<Vec<u8>, Base64DecodeError> {
205    URL_SAFE_NO_PAD.decode(encoded.as_ref()).map_err(Into::into)
206}