1use super::{
4 contracts::{BackendFault, Failure, InputError, OperationError, Status},
5 specifications::{Base64, Codec, CodecSettings, EncodePadding},
6};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10#[non_exhaustive]
11pub enum OneShotError {
12 LengthOverflow,
14 Input(InputError),
16 PositionOverflow,
18 OutputTooSmall {
20 required: usize,
22 available: usize,
24 },
25 AllocationLimitExceeded {
27 required: usize,
29 limit: usize,
31 },
32 AllocationFailed {
34 requested: usize,
36 },
37 Backend(BackendFault),
39}
40
41impl core::fmt::Display for OneShotError {
42 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43 match self {
44 Self::LengthOverflow => formatter.write_str("base64 output length overflows usize"),
45 Self::Input(error) => error.fmt(formatter),
46 Self::PositionOverflow => formatter.write_str("base64 source position overflows usize"),
47 Self::OutputTooSmall {
48 required,
49 available,
50 } => write!(
51 formatter,
52 "base64 output buffer too small: required {required}, available {available}"
53 ),
54 Self::AllocationLimitExceeded { required, limit } => write!(
55 formatter,
56 "base64 output length {required} exceeds allocation limit {limit}"
57 ),
58 Self::AllocationFailed { requested } => {
59 write!(
60 formatter,
61 "failed to reserve {requested} base64 output bytes"
62 )
63 }
64 Self::Backend(fault) => write!(formatter, "base64 backend fault: {}", fault.as_str()),
65 }
66 }
67}
68
69#[cfg(feature = "std")]
70impl std::error::Error for OneShotError {}
71
72impl<S: Codec> Base64<S> {
73 pub fn encoded_len(&self, input_len: usize) -> Result<usize, OneShotError> {
75 encoded_len_for_settings(self.specification().settings(), input_len)
76 }
77
78 pub fn validate(&self, input: &[u8]) -> Result<(), OneShotError> {
80 self.decoded_len(input).map(|_| ())
81 }
82
83 pub fn decoded_len(&self, input: &[u8]) -> Result<usize, OneShotError> {
85 validate_and_measure(self, input)
86 }
87
88 pub fn encode_into(&self, input: &[u8], output: &mut [u8]) -> Result<usize, OneShotError> {
93 let required = self.encoded_len(input.len())?;
94 require_output(required, output.len())?;
95 encode_validated(self.settings(), input, &mut output[..required]);
96 Ok(required)
97 }
98
99 pub fn decode_into(&self, input: &[u8], output: &mut [u8]) -> Result<usize, OneShotError> {
104 let required = self.decoded_len(input)?;
105 require_output(required, output.len())?;
106 decode_validated(self.settings(), input, &mut output[..required]);
107 Ok(required)
108 }
109}
110
111const fn encoded_len_for_settings(
112 settings: CodecSettings,
113 input_len: usize,
114) -> Result<usize, OneShotError> {
115 let Some(complete) = (input_len / 3).checked_mul(4) else {
116 return Err(OneShotError::LengthOverflow);
117 };
118 let remainder = input_len % 3;
119 let tail = if remainder == 0 {
120 0
121 } else if matches!(settings.encode_padding(), EncodePadding::Padded) {
122 4
123 } else {
124 remainder + 1
125 };
126 match complete.checked_add(tail) {
127 Some(value) => Ok(value),
128 None => Err(OneShotError::LengthOverflow),
129 }
130}
131
132fn require_output(required: usize, available: usize) -> Result<(), OneShotError> {
133 if available < required {
134 Err(OneShotError::OutputTooSmall {
135 required,
136 available,
137 })
138 } else {
139 Ok(())
140 }
141}
142
143fn validate_and_measure<S: Codec>(codec: &Base64<S>, input: &[u8]) -> Result<usize, OneShotError> {
144 let mut decoder = codec.decoder();
145 let mut input_offset = 0;
146 let mut measured_len = 0usize;
147 let mut scratch = [0u8; 3];
148 while input_offset < input.len() {
149 let step = decoder
150 .update(&input[input_offset..], &mut scratch)
151 .map_err(map_operation_error)?;
152 let progress = step.progress();
153 if progress.input_consumed() == 0 && progress.output_produced() == 0 {
154 return Err(OneShotError::Backend(BackendFault::ImpossibleState));
155 }
156 input_offset += progress.input_consumed();
157 measured_len = measured_len
158 .checked_add(progress.output_produced())
159 .ok_or(OneShotError::LengthOverflow)?;
160 }
161
162 loop {
163 let step = decoder.finish(&mut scratch).map_err(map_operation_error)?;
164 measured_len = measured_len
165 .checked_add(step.progress().output_produced())
166 .ok_or(OneShotError::LengthOverflow)?;
167 match step.status() {
168 Status::Complete => return Ok(measured_len),
169 Status::OutputFull(_) => {}
170 Status::NeedInput => {
171 return Err(OneShotError::Backend(BackendFault::ImpossibleState));
172 }
173 }
174 }
175}
176
177pub(super) fn map_operation_error(error: OperationError) -> OneShotError {
178 match error {
179 OperationError::Failed(Failure::Input(error)) => OneShotError::Input(error),
180 OperationError::Failed(Failure::PositionOverflow) => OneShotError::PositionOverflow,
181 OperationError::Failed(Failure::Backend(fault)) => OneShotError::Backend(fault),
182 OperationError::Failed(Failure::ResourceLimit) | OperationError::Terminal(_) => {
183 OneShotError::Backend(BackendFault::ImpossibleState)
184 }
185 }
186}
187
188fn encode_validated(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
189 let alphabet = settings.alphabet().as_array();
190 let mut read = 0;
191 let mut write = 0;
192 while read + 3 <= input.len() {
193 let first = input[read];
194 let second = input[read + 1];
195 let third = input[read + 2];
196 output[write] = alphabet[usize::from(first >> 2)];
197 output[write + 1] = alphabet[usize::from(((first & 3) << 4) | (second >> 4))];
198 output[write + 2] = alphabet[usize::from(((second & 15) << 2) | (third >> 6))];
199 output[write + 3] = alphabet[usize::from(third & 63)];
200 read += 3;
201 write += 4;
202 }
203 encode_tail(settings, &input[read..], &mut output[write..]);
204}
205
206fn encode_tail(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
207 let alphabet = settings.alphabet().as_array();
208 if let [first, rest @ ..] = input {
209 output[0] = alphabet[usize::from(first >> 2)];
210 output[1] = alphabet[usize::from((first & 3) << 4)];
211 if let [second] = rest {
212 output[1] = alphabet[usize::from(((first & 3) << 4) | (second >> 4))];
213 output[2] = alphabet[usize::from((second & 15) << 2)];
214 if settings.encode_padding() == EncodePadding::Padded {
215 output[3] = b'=';
216 }
217 } else if settings.encode_padding() == EncodePadding::Padded {
218 output[2..4].copy_from_slice(b"==");
219 }
220 }
221}
222
223fn decode_validated(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
224 let mut read = 0;
225 let mut write = 0;
226 while read + 4 <= input.len() {
227 let input_quantum = &input[read..read + 4];
228 let first = decode_value(settings, input_quantum[0]);
229 let second = decode_value(settings, input_quantum[1]);
230 output[write] = (first << 2) | (second >> 4);
231 write += 1;
232 if input_quantum[2] != b'=' {
233 let third = decode_value(settings, input_quantum[2]);
234 output[write] = (second << 4) | (third >> 2);
235 write += 1;
236 if input_quantum[3] != b'=' {
237 output[write] = (third << 6) | decode_value(settings, input_quantum[3]);
238 write += 1;
239 }
240 }
241 read += 4;
242 }
243 decode_tail(settings, &input[read..], &mut output[write..]);
244}
245
246fn decode_tail(settings: CodecSettings, input: &[u8], output: &mut [u8]) {
247 if let [first, second, rest @ ..] = input {
248 let first = decode_value(settings, *first);
249 let second = decode_value(settings, *second);
250 output[0] = (first << 2) | (second >> 4);
251 if let [third] = rest
252 && *third != b'='
253 {
254 output[1] = (second << 4) | (decode_value(settings, *third) >> 2);
255 }
256 }
257}
258
259fn decode_value(settings: CodecSettings, byte: u8) -> u8 {
260 settings.alphabet().decode_byte(byte).unwrap_or(0)
261}
262
263#[cfg(test)]
264pub(super) fn encode(
265 profile: super::rfc4648_oracle::Profile,
266 input: &[u8],
267 output: &mut [u8],
268) -> Result<usize, OneShotError> {
269 match profile {
270 super::rfc4648_oracle::Profile::StandardPadded => {
271 super::specifications::STRICT_STANDARD_PADDED.encode_into(input, output)
272 }
273 super::rfc4648_oracle::Profile::StandardUnpadded => {
274 super::specifications::STRICT_STANDARD_UNPADDED.encode_into(input, output)
275 }
276 super::rfc4648_oracle::Profile::UrlSafePadded => {
277 super::specifications::STRICT_URL_SAFE_PADDED.encode_into(input, output)
278 }
279 super::rfc4648_oracle::Profile::UrlSafeUnpadded => {
280 super::specifications::STRICT_URL_SAFE_UNPADDED.encode_into(input, output)
281 }
282 }
283}
284
285#[cfg(test)]
286pub(super) fn decode(
287 profile: super::rfc4648_oracle::Profile,
288 input: &[u8],
289 output: &mut [u8],
290) -> Result<usize, OneShotError> {
291 match profile {
292 super::rfc4648_oracle::Profile::StandardPadded => {
293 super::specifications::STRICT_STANDARD_PADDED.decode_into(input, output)
294 }
295 super::rfc4648_oracle::Profile::StandardUnpadded => {
296 super::specifications::STRICT_STANDARD_UNPADDED.decode_into(input, output)
297 }
298 super::rfc4648_oracle::Profile::UrlSafePadded => {
299 super::specifications::STRICT_URL_SAFE_PADDED.decode_into(input, output)
300 }
301 super::rfc4648_oracle::Profile::UrlSafeUnpadded => {
302 super::specifications::STRICT_URL_SAFE_UNPADDED.decode_into(input, output)
303 }
304 }
305}