1#![deny(unsafe_code)]
2#![deny(missing_docs)]
3#![deny(clippy::all)]
4#![deny(clippy::pedantic)]
5
6mod decoder_writer;
31mod encoder_writer;
32mod queue;
33mod readers;
34
35pub use decoder_writer::DecoderWriter;
36pub use encoder_writer::EncoderWriter;
37pub use readers::{DecoderReader, EncoderReader};
38
39use base64_ng::{Alphabet, Engine};
40use tokio::io::{self, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
41
42const READ_ALL_EAGER_CAP: usize = 8192;
43
44pub async fn encode_reader_to_writer<A, const PAD: bool, R, W>(
51 engine: &Engine<A, PAD>,
52 reader: &mut R,
53 writer: &mut W,
54) -> io::Result<u64>
55where
56 A: Alphabet,
57 R: AsyncRead + Unpin + ?Sized,
58 W: AsyncWrite + Unpin + ?Sized,
59{
60 let input = read_to_end_guarded(reader).await?;
61 let output = WipingVec::from_vec(
62 engine
63 .encode_vec(input.as_slice())
64 .map_err(encode_io_error)?,
65 );
66 let written = output.len() as u64;
67 writer.write_all(output.as_slice()).await?;
68 Ok(written)
69}
70
71pub async fn encode_reader_to_writer_limited<A, const PAD: bool, R, W>(
83 engine: &Engine<A, PAD>,
84 reader: &mut R,
85 writer: &mut W,
86 max_input_len: usize,
87) -> io::Result<u64>
88where
89 A: Alphabet,
90 R: AsyncRead + Unpin + ?Sized,
91 W: AsyncWrite + Unpin + ?Sized,
92{
93 let input = read_to_end_limited(reader, max_input_len).await?;
94 let output = WipingVec::from_vec(
95 engine
96 .encode_vec(input.as_slice())
97 .map_err(encode_io_error)?,
98 );
99 let written = output.len() as u64;
100 writer.write_all(output.as_slice()).await?;
101 Ok(written)
102}
103
104pub async fn decode_reader_to_writer<A, const PAD: bool, R, W>(
114 engine: &Engine<A, PAD>,
115 reader: &mut R,
116 writer: &mut W,
117) -> io::Result<u64>
118where
119 A: Alphabet,
120 R: AsyncRead + Unpin + ?Sized,
121 W: AsyncWrite + Unpin + ?Sized,
122{
123 let input = read_to_end_guarded(reader).await?;
124 let output = WipingVec::from_vec(
125 engine
126 .decode_vec(input.as_slice())
127 .map_err(decode_io_error)?,
128 );
129 let written = output.len() as u64;
130 writer.write_all(output.as_slice()).await?;
131 Ok(written)
132}
133
134pub async fn decode_reader_to_writer_limited<A, const PAD: bool, R, W>(
146 engine: &Engine<A, PAD>,
147 reader: &mut R,
148 writer: &mut W,
149 max_input_len: usize,
150) -> io::Result<u64>
151where
152 A: Alphabet,
153 R: AsyncRead + Unpin + ?Sized,
154 W: AsyncWrite + Unpin + ?Sized,
155{
156 let input = read_to_end_limited(reader, max_input_len).await?;
157 let output = WipingVec::from_vec(
158 engine
159 .decode_vec(input.as_slice())
160 .map_err(decode_io_error)?,
161 );
162 let written = output.len() as u64;
163 writer.write_all(output.as_slice()).await?;
164 Ok(written)
165}
166
167pub fn encode_to_vec<A, const PAD: bool>(
173 engine: &Engine<A, PAD>,
174 input: impl AsRef<[u8]>,
175) -> io::Result<Vec<u8>>
176where
177 A: Alphabet,
178{
179 engine.encode_vec(input.as_ref()).map_err(encode_io_error)
180}
181
182pub fn decode_to_vec<A, const PAD: bool>(
188 engine: &Engine<A, PAD>,
189 input: impl AsRef<[u8]>,
190) -> io::Result<Vec<u8>>
191where
192 A: Alphabet,
193{
194 engine.decode_vec(input.as_ref()).map_err(decode_io_error)
195}
196
197fn encode_io_error(error: base64_ng::EncodeError) -> io::Error {
198 io::Error::new(io::ErrorKind::InvalidInput, error)
199}
200
201fn decode_io_error(error: base64_ng::DecodeError) -> io::Error {
202 io::Error::new(io::ErrorKind::InvalidData, error.kind().as_str())
203}
204
205fn wipe_bytes(bytes: &mut [u8]) {
206 base64_ng::secure_wipe(bytes);
207}
208
209struct WipingVec(Vec<u8>);
210
211impl WipingVec {
212 fn new() -> Self {
213 Self(Vec::new())
214 }
215
216 fn with_capacity(capacity: usize) -> Self {
217 Self(Vec::with_capacity(capacity))
218 }
219
220 fn from_vec(bytes: Vec<u8>) -> Self {
221 Self(bytes)
222 }
223
224 fn as_slice(&self) -> &[u8] {
225 &self.0
226 }
227
228 fn len(&self) -> usize {
229 self.0.len()
230 }
231
232 fn extend_from_slice_wiping_old(
233 &mut self,
234 bytes: &[u8],
235 capacity_limit: usize,
236 ) -> io::Result<()> {
237 let required = self.len().checked_add(bytes.len()).ok_or_else(|| {
238 io::Error::new(
239 io::ErrorKind::InvalidData,
240 "base64-ng-tokio input is too large",
241 )
242 })?;
243 if required > capacity_limit {
244 return Err(io::Error::new(
245 io::ErrorKind::InvalidData,
246 "base64-ng-tokio input exceeds configured limit",
247 ));
248 }
249
250 if required <= self.0.capacity() {
251 self.0.extend_from_slice(bytes);
252 return Ok(());
253 }
254
255 let grown_capacity = self.0.capacity().saturating_mul(2).max(required);
256 let replacement_capacity = grown_capacity.min(capacity_limit);
257 let mut replacement = Self::with_capacity(replacement_capacity);
258 replacement.0.extend_from_slice(&self.0);
259 replacement.0.extend_from_slice(bytes);
260 core::mem::swap(self, &mut replacement);
261 drop(replacement);
262 Ok(())
263 }
264}
265
266impl Drop for WipingVec {
267 fn drop(&mut self) {
268 self.0.resize(self.0.capacity(), 0);
271 wipe_bytes(&mut self.0);
272 self.0.clear();
273 }
274}
275
276struct WipingArray<const N: usize>([u8; N]);
277
278impl<const N: usize> WipingArray<N> {
279 const fn new() -> Self {
280 Self([0; N])
281 }
282}
283
284impl<const N: usize> Drop for WipingArray<N> {
285 fn drop(&mut self) {
286 wipe_bytes(&mut self.0);
287 }
288}
289
290async fn read_to_end_guarded<R>(reader: &mut R) -> io::Result<WipingVec>
291where
292 R: AsyncRead + Unpin + ?Sized,
293{
294 let mut input = WipingVec::new();
295 let mut chunk = WipingArray::<8192>::new();
296
297 loop {
298 let read = reader.read(&mut chunk.0).await?;
299 if read == 0 {
300 return Ok(input);
301 }
302
303 input.extend_from_slice_wiping_old(&chunk.0[..read], usize::MAX)?;
304 wipe_bytes(&mut chunk.0[..read]);
305 }
306}
307
308async fn read_to_end_limited<R>(reader: &mut R, max_input_len: usize) -> io::Result<WipingVec>
309where
310 R: AsyncRead + Unpin + ?Sized,
311{
312 let mut input = WipingVec::with_capacity(max_input_len.min(READ_ALL_EAGER_CAP));
313 let mut chunk = WipingArray::<8192>::new();
314
315 loop {
316 let remaining = max_input_len - input.len();
317 let read_cap = if remaining < chunk.0.len() {
318 remaining + 1
319 } else {
320 chunk.0.len()
321 };
322 let read = reader.read(&mut chunk.0[..read_cap]).await?;
323 if read == 0 {
324 return Ok(input);
325 }
326
327 if read > remaining {
328 return Err(io::Error::new(
329 io::ErrorKind::InvalidData,
330 "base64-ng-tokio input exceeds configured limit",
331 ));
332 }
333
334 input.extend_from_slice_wiping_old(&chunk.0[..read], max_input_len)?;
335 wipe_bytes(&mut chunk.0[..read]);
336 }
337}