1use super::alphabet::{
4 STANDARD_ALPHABET, URL_SAFE_ALPHABET, ValidatedAlphabet, ValidatedAlphabetError,
5};
6
7#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
9pub enum EncodePadding {
10 Padded,
12 Unpadded,
14}
15
16#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
18pub enum DecodePadding {
19 RequireCanonical,
21 Forbid,
23 Indifferent,
25}
26
27#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
29pub enum TrailingBits {
30 RequireCanonical,
32 AllowNonCanonical,
34}
35
36#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
38pub struct CodecSettings {
39 alphabet: ValidatedAlphabet,
40 encode_padding: EncodePadding,
41 decode_padding: DecodePadding,
42 trailing_bits: TrailingBits,
43}
44
45impl CodecSettings {
46 const fn new(
47 alphabet: ValidatedAlphabet,
48 encode_padding: EncodePadding,
49 decode_padding: DecodePadding,
50 trailing_bits: TrailingBits,
51 ) -> Self {
52 Self {
53 alphabet,
54 encode_padding,
55 decode_padding,
56 trailing_bits,
57 }
58 }
59
60 #[must_use]
62 pub const fn alphabet(&self) -> &ValidatedAlphabet {
63 &self.alphabet
64 }
65
66 #[must_use]
68 pub const fn encode_padding(&self) -> EncodePadding {
69 self.encode_padding
70 }
71
72 #[must_use]
74 pub const fn decode_padding(&self) -> DecodePadding {
75 self.decode_padding
76 }
77
78 #[must_use]
80 pub const fn trailing_bits(&self) -> TrailingBits {
81 self.trailing_bits
82 }
83
84 #[must_use]
89 pub const fn permits_secret_processing(&self) -> bool {
90 !matches!(self.decode_padding, DecodePadding::Indifferent)
91 && matches!(self.trailing_bits, TrailingBits::RequireCanonical)
92 }
93}
94
95mod sealed {
96 pub trait Sealed {}
97}
98
99pub trait Codec: sealed::Sealed + Send + Sync {
106 fn settings(&self) -> CodecSettings;
108}
109
110#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
112pub struct Base64<S> {
113 specification: S,
114}
115
116impl<S: Codec> Base64<S> {
117 pub const fn new(specification: S) -> Self {
119 Self { specification }
120 }
121
122 pub const fn specification(&self) -> &S {
124 &self.specification
125 }
126 pub fn settings(&self) -> CodecSettings {
128 self.specification.settings()
129 }
130}
131
132macro_rules! strict_specification {
133 ($name:ident, $alphabet:expr, $encode:expr, $decode:expr) => {
134 #[doc = "A sealed strict RFC 4648 built-in specification."]
135 #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
136 pub struct $name;
137
138 impl $name {
139 const SETTINGS: CodecSettings =
140 CodecSettings::new($alphabet, $encode, $decode, TrailingBits::RequireCanonical);
141
142 pub(crate) const fn const_settings() -> CodecSettings {
143 Self::SETTINGS
144 }
145 }
146
147 impl sealed::Sealed for $name {}
148
149 impl Codec for $name {
150 fn settings(&self) -> CodecSettings {
151 Self::SETTINGS
152 }
153 }
154 };
155}
156
157strict_specification!(
158 StrictStandardPadded,
159 STANDARD_ALPHABET,
160 EncodePadding::Padded,
161 DecodePadding::RequireCanonical
162);
163strict_specification!(
164 StrictStandardUnpadded,
165 STANDARD_ALPHABET,
166 EncodePadding::Unpadded,
167 DecodePadding::Forbid
168);
169strict_specification!(
170 StrictUrlSafePadded,
171 URL_SAFE_ALPHABET,
172 EncodePadding::Padded,
173 DecodePadding::RequireCanonical
174);
175strict_specification!(
176 StrictUrlSafeUnpadded,
177 URL_SAFE_ALPHABET,
178 EncodePadding::Unpadded,
179 DecodePadding::Forbid
180);
181
182pub const STRICT_STANDARD_PADDED: Base64<StrictStandardPadded> = Base64::new(StrictStandardPadded);
184pub const STRICT_STANDARD_UNPADDED: Base64<StrictStandardUnpadded> =
186 Base64::new(StrictStandardUnpadded);
187pub const STRICT_URL_SAFE_PADDED: Base64<StrictUrlSafePadded> = Base64::new(StrictUrlSafePadded);
189pub const STRICT_URL_SAFE_UNPADDED: Base64<StrictUrlSafeUnpadded> =
191 Base64::new(StrictUrlSafeUnpadded);
192
193#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
195pub struct RuntimeSpec {
196 settings: CodecSettings,
197}
198
199impl RuntimeSpec {
200 pub(crate) const fn const_settings(&self) -> CodecSettings {
201 self.settings
202 }
203}
204
205pub(crate) const fn runtime_codec(
206 alphabet: ValidatedAlphabet,
207 encode_padding: EncodePadding,
208 decode_padding: DecodePadding,
209 trailing_bits: TrailingBits,
210) -> Base64<RuntimeSpec> {
211 Base64::new(RuntimeSpec {
212 settings: CodecSettings::new(alphabet, encode_padding, decode_padding, trailing_bits),
213 })
214}
215
216pub(crate) const fn compatibility_codec(
217 alphabet: ValidatedAlphabet,
218 encode_padding: EncodePadding,
219 decode_padding: DecodePadding,
220 trailing_bits: TrailingBits,
221) -> Base64<RuntimeSpec> {
222 runtime_codec(alphabet, encode_padding, decode_padding, trailing_bits)
223}
224
225impl sealed::Sealed for RuntimeSpec {}
226
227impl Codec for RuntimeSpec {
228 fn settings(&self) -> CodecSettings {
229 self.settings
230 }
231}
232
233#[derive(Clone, Copy, Debug, Eq, PartialEq)]
235pub enum CodecBuilderError {
236 EncodedPaddingRejected,
238 EncodedPaddingRequired,
240}
241
242impl core::fmt::Display for CodecBuilderError {
243 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
244 match self {
245 Self::EncodedPaddingRejected => {
246 formatter.write_str("encoding emits padding rejected by decode policy")
247 }
248 Self::EncodedPaddingRequired => {
249 formatter.write_str("encoding omits padding required by decode policy")
250 }
251 }
252 }
253}
254
255#[derive(Clone, Copy, Debug, Eq, PartialEq)]
257pub struct CodecBuilder {
258 settings: CodecSettings,
259}
260
261impl CodecBuilder {
262 #[must_use]
264 pub const fn new(alphabet: ValidatedAlphabet) -> Self {
265 Self {
266 settings: CodecSettings::new(
267 alphabet,
268 EncodePadding::Padded,
269 DecodePadding::RequireCanonical,
270 TrailingBits::RequireCanonical,
271 ),
272 }
273 }
274
275 pub const fn from_table(table: [u8; 64]) -> Result<Self, ValidatedAlphabetError> {
277 match ValidatedAlphabet::new(table) {
278 Ok(alphabet) => Ok(Self::new(alphabet)),
279 Err(error) => Err(error),
280 }
281 }
282
283 pub const fn from_slice(bytes: &[u8]) -> Result<Self, ValidatedAlphabetError> {
285 match ValidatedAlphabet::try_from_slice(bytes) {
286 Ok(alphabet) => Ok(Self::new(alphabet)),
287 Err(error) => Err(error),
288 }
289 }
290
291 #[must_use]
293 pub const fn encode_padding(mut self, policy: EncodePadding) -> Self {
294 self.settings.encode_padding = policy;
295 self
296 }
297
298 #[must_use]
300 pub const fn decode_padding(mut self, policy: DecodePadding) -> Self {
301 self.settings.decode_padding = policy;
302 self
303 }
304
305 #[must_use]
307 pub const fn trailing_bits(mut self, policy: TrailingBits) -> Self {
308 self.settings.trailing_bits = policy;
309 self
310 }
311
312 pub const fn build(self) -> Result<Base64<RuntimeSpec>, CodecBuilderError> {
314 match (self.settings.encode_padding, self.settings.decode_padding) {
315 (EncodePadding::Padded, DecodePadding::Forbid) => {
316 Err(CodecBuilderError::EncodedPaddingRejected)
317 }
318 (EncodePadding::Unpadded, DecodePadding::RequireCanonical) => {
319 Err(CodecBuilderError::EncodedPaddingRequired)
320 }
321 _ => Ok(Base64::new(RuntimeSpec {
322 settings: self.settings,
323 })),
324 }
325 }
326}