base64_ng/v2/
incremental.rs1use core::num::NonZeroUsize;
4
5use super::{
6 contracts::{Lifecycle, OperationError, Progress, Step},
7 specifications::{Base64, Codec, CodecSettings, EncodePadding},
8};
9
10const INPUT_QUANTUM: usize = 3;
11const OUTPUT_QUANTUM: usize = 4;
12
13#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct EncoderState {
20 settings: CodecSettings,
21 tail: [u8; INPUT_QUANTUM],
22 tail_len: usize,
23 pending: [u8; OUTPUT_QUANTUM],
24 pending_start: usize,
25 pending_len: usize,
26 lifecycle: Lifecycle,
27}
28
29impl EncoderState {
30 pub(crate) const fn new(settings: CodecSettings) -> Self {
31 Self {
32 settings,
33 tail: [0; INPUT_QUANTUM],
34 tail_len: 0,
35 pending: [0; OUTPUT_QUANTUM],
36 pending_start: 0,
37 pending_len: 0,
38 lifecycle: Lifecycle::new(),
39 }
40 }
41
42 pub fn update(&mut self, input: &[u8], output: &mut [u8]) -> Result<Step, OperationError> {
44 let span = self.lifecycle.reserve_input(input.len())?;
45 let consumed =
46 planned_input_consumption(self.tail_len, self.pending_len, input.len(), output.len());
47 self.lifecycle.commit_input(span, consumed)?;
48
49 let mut produced = self.drain_pending(output);
50 let mut input_offset = 0;
51 while input_offset < consumed {
52 self.tail[self.tail_len] = input[input_offset];
53 self.tail_len += 1;
54 input_offset += 1;
55
56 if self.tail_len == INPUT_QUANTUM {
57 self.pending = encode_quantum(self.settings, self.tail);
58 self.pending_start = 0;
59 self.pending_len = OUTPUT_QUANTUM;
60 self.tail_len = 0;
61 produced += self.drain_pending(&mut output[produced..]);
62 }
63 }
64
65 let progress = Progress::new(consumed, produced);
66 if self.pending_len != 0 || consumed != input.len() {
67 self.lifecycle.output_full(progress, NonZeroUsize::MIN)
68 } else {
69 self.lifecycle.need_input(progress)
70 }
71 }
72
73 pub fn finish(&mut self, output: &mut [u8]) -> Result<Step, OperationError> {
75 if self.lifecycle.begin_finish()? {
76 return self.lifecycle.finish(Progress::ZERO);
77 }
78
79 let mut produced = self.drain_pending(output);
80 if self.pending_len == 0 && self.tail_len != 0 {
81 self.pending_len = encode_tail(
82 self.settings,
83 &self.tail[..self.tail_len],
84 &mut self.pending,
85 );
86 self.pending_start = 0;
87 self.tail_len = 0;
88 produced += self.drain_pending(&mut output[produced..]);
89 }
90
91 let progress = Progress::new(0, produced);
92 if self.pending_len == 0 {
93 self.lifecycle.finish(progress)
94 } else {
95 self.lifecycle.output_full(progress, NonZeroUsize::MIN)
96 }
97 }
98
99 pub fn reset(&mut self) {
101 self.tail = [0; INPUT_QUANTUM];
102 self.tail_len = 0;
103 self.pending = [0; OUTPUT_QUANTUM];
104 self.pending_start = 0;
105 self.pending_len = 0;
106 self.lifecycle.reset();
107 }
108
109 pub fn clear(&mut self) {
116 self.wipe();
117 }
118
119 #[must_use]
121 pub const fn source_position(&self) -> usize {
122 self.lifecycle.source_position()
123 }
124
125 #[must_use]
127 pub const fn buffered_input_len(&self) -> usize {
128 self.tail_len
129 }
130
131 pub(crate) fn wipe(&mut self) {
133 crate::wipe_bytes(&mut self.tail);
134 crate::wipe_bytes(&mut self.pending);
135 self.tail_len = 0;
136 self.pending_start = 0;
137 self.pending_len = 0;
138 self.lifecycle.reset();
139 }
140
141 fn drain_pending(&mut self, output: &mut [u8]) -> usize {
142 let written = self.pending_len.min(output.len());
143 let pending_end = self.pending_start + written;
144 output[..written].copy_from_slice(&self.pending[self.pending_start..pending_end]);
145 self.pending_start = pending_end;
146 self.pending_len -= written;
147 if self.pending_len == 0 {
148 self.pending_start = 0;
149 }
150 written
151 }
152
153 #[cfg(kani)]
154 pub(crate) const fn proof_invariants(&self) -> bool {
155 self.tail_len < INPUT_QUANTUM
156 && self.pending_start <= OUTPUT_QUANTUM
157 && self.pending_len <= OUTPUT_QUANTUM
158 && self.pending_start + self.pending_len <= OUTPUT_QUANTUM
159 }
160}
161
162impl<S: Codec> Base64<S> {
163 pub fn encoder(&self) -> EncoderState {
165 EncoderState::new(self.settings())
166 }
167}
168
169fn planned_input_consumption(
170 initial_tail: usize,
171 pending: usize,
172 input: usize,
173 output: usize,
174) -> usize {
175 let pending_written = pending.min(output);
176 if pending_written != pending {
177 return 0;
178 }
179
180 let mut available_output = output - pending_written;
181 let mut tail = initial_tail;
182 let mut consumed = 0;
183 while consumed < input {
184 let copied = (INPUT_QUANTUM - tail).min(input - consumed);
185 consumed += copied;
186 tail += copied;
187 if tail != INPUT_QUANTUM {
188 break;
189 }
190
191 tail = 0;
192 let written = OUTPUT_QUANTUM.min(available_output);
193 available_output -= written;
194 if written != OUTPUT_QUANTUM {
195 break;
196 }
197 }
198 consumed
199}
200
201fn encode_quantum(settings: CodecSettings, input: [u8; INPUT_QUANTUM]) -> [u8; OUTPUT_QUANTUM] {
202 let table = settings.alphabet().as_array();
203 [
204 table[usize::from(input[0] >> 2)],
205 table[usize::from(((input[0] & 0x03) << 4) | (input[1] >> 4))],
206 table[usize::from(((input[1] & 0x0f) << 2) | (input[2] >> 6))],
207 table[usize::from(input[2] & 0x3f)],
208 ]
209}
210
211fn encode_tail(settings: CodecSettings, input: &[u8], output: &mut [u8; 4]) -> usize {
212 let table = settings.alphabet().as_array();
213 output[0] = table[usize::from(input[0] >> 2)];
214 output[1] = table[usize::from((input[0] & 0x03) << 4)];
215
216 if input.len() == 1 {
217 if settings.encode_padding() == EncodePadding::Padded {
218 output[2] = b'=';
219 output[3] = b'=';
220 4
221 } else {
222 2
223 }
224 } else {
225 output[1] = table[usize::from(((input[0] & 0x03) << 4) | (input[1] >> 4))];
226 output[2] = table[usize::from((input[1] & 0x0f) << 2)];
227 if settings.encode_padding() == EncodePadding::Padded {
228 output[3] = b'=';
229 4
230 } else {
231 3
232 }
233 }
234}