1use std::io::{self, Read, Write};
2
3use base64_simd::AsOut;
4use rayon::prelude::*;
5
6const BASE64_ENGINE: &base64_simd::Base64 = &base64_simd::STANDARD;
7
8const NOWRAP_CHUNK: usize = 32 * 1024 * 1024 - (32 * 1024 * 1024 % 3);
11
12const PARALLEL_ENCODE_THRESHOLD: usize = 1024 * 1024;
15
16const PARALLEL_DECODE_THRESHOLD: usize = 1024 * 1024;
19
20pub fn encode_to_writer(data: &[u8], wrap_col: usize, out: &mut impl Write) -> io::Result<()> {
23 if data.is_empty() {
24 return Ok(());
25 }
26
27 if wrap_col == 0 {
28 return encode_no_wrap(data, out);
29 }
30
31 encode_wrapped(data, wrap_col, out)
32}
33
34fn encode_no_wrap(data: &[u8], out: &mut impl Write) -> io::Result<()> {
36 if data.len() >= PARALLEL_ENCODE_THRESHOLD {
37 return encode_no_wrap_parallel(data, out);
38 }
39
40 let actual_chunk = NOWRAP_CHUNK.min(data.len());
41 let enc_max = BASE64_ENGINE.encoded_length(actual_chunk);
42 let mut buf: Vec<u8> = Vec::with_capacity(enc_max);
44 #[allow(clippy::uninit_vec)]
45 unsafe {
46 buf.set_len(enc_max);
47 }
48
49 for chunk in data.chunks(NOWRAP_CHUNK) {
50 let enc_len = BASE64_ENGINE.encoded_length(chunk.len());
51 let encoded = BASE64_ENGINE.encode(chunk, buf[..enc_len].as_out());
52 out.write_all(encoded)?;
53 }
54 Ok(())
55}
56
57fn encode_no_wrap_parallel(data: &[u8], out: &mut impl Write) -> io::Result<()> {
61 let num_threads = rayon::current_num_threads().max(1);
62 let raw_chunk = data.len() / num_threads;
63 let chunk_size = ((raw_chunk + 2) / 3) * 3;
65
66 let chunks: Vec<&[u8]> = data.chunks(chunk_size.max(3)).collect();
67 let encoded_chunks: Vec<Vec<u8>> = chunks
68 .par_iter()
69 .map(|chunk| {
70 let enc_len = BASE64_ENGINE.encoded_length(chunk.len());
71 let mut buf: Vec<u8> = Vec::with_capacity(enc_len);
72 #[allow(clippy::uninit_vec)]
73 unsafe {
74 buf.set_len(enc_len);
75 }
76 let _ = BASE64_ENGINE.encode(chunk, buf[..enc_len].as_out());
77 buf
78 })
79 .collect();
80
81 let iov: Vec<io::IoSlice> = encoded_chunks.iter().map(|c| io::IoSlice::new(c)).collect();
83 write_all_vectored(out, &iov)
84}
85
86fn encode_wrapped(data: &[u8], wrap_col: usize, out: &mut impl Write) -> io::Result<()> {
91 let bytes_per_line = wrap_col * 3 / 4;
94 if bytes_per_line == 0 {
95 return encode_wrapped_small(data, wrap_col, out);
97 }
98
99 if data.len() >= PARALLEL_ENCODE_THRESHOLD && bytes_per_line.is_multiple_of(3) {
102 return encode_wrapped_parallel(data, wrap_col, bytes_per_line, out);
103 }
104
105 let lines_per_chunk = (32 * 1024 * 1024) / bytes_per_line;
107 let max_input_chunk = (lines_per_chunk * bytes_per_line).max(bytes_per_line);
108 let input_chunk = max_input_chunk.min(data.len());
109
110 let enc_max = BASE64_ENGINE.encoded_length(input_chunk);
111 let mut encode_buf: Vec<u8> = Vec::with_capacity(enc_max);
112 #[allow(clippy::uninit_vec)]
113 unsafe {
114 encode_buf.set_len(enc_max);
115 }
116
117 for chunk in data.chunks(max_input_chunk.max(1)) {
118 let enc_len = BASE64_ENGINE.encoded_length(chunk.len());
119 let encoded = BASE64_ENGINE.encode(chunk, encode_buf[..enc_len].as_out());
120
121 write_wrapped_iov(encoded, wrap_col, out)?;
125 }
126
127 Ok(())
128}
129
130static NEWLINE: [u8; 1] = [b'\n'];
132
133#[inline]
138fn write_wrapped_iov(encoded: &[u8], wrap_col: usize, out: &mut impl Write) -> io::Result<()> {
139 const MAX_IOV: usize = 1024;
142
143 let num_full_lines = encoded.len() / wrap_col;
144 let remainder = encoded.len() % wrap_col;
145 let total_iov = num_full_lines * 2 + if remainder > 0 { 2 } else { 0 };
146
147 if total_iov <= MAX_IOV {
149 let mut iov: Vec<io::IoSlice> = Vec::with_capacity(total_iov);
150 let mut pos = 0;
151 for _ in 0..num_full_lines {
152 iov.push(io::IoSlice::new(&encoded[pos..pos + wrap_col]));
153 iov.push(io::IoSlice::new(&NEWLINE));
154 pos += wrap_col;
155 }
156 if remainder > 0 {
157 iov.push(io::IoSlice::new(&encoded[pos..pos + remainder]));
158 iov.push(io::IoSlice::new(&NEWLINE));
159 }
160 return write_all_vectored(out, &iov);
161 }
162
163 let mut iov: Vec<io::IoSlice> = Vec::with_capacity(MAX_IOV);
165 let mut pos = 0;
166 for _ in 0..num_full_lines {
167 iov.push(io::IoSlice::new(&encoded[pos..pos + wrap_col]));
168 iov.push(io::IoSlice::new(&NEWLINE));
169 pos += wrap_col;
170 if iov.len() >= MAX_IOV {
171 write_all_vectored(out, &iov)?;
172 iov.clear();
173 }
174 }
175 if remainder > 0 {
176 iov.push(io::IoSlice::new(&encoded[pos..pos + remainder]));
177 iov.push(io::IoSlice::new(&NEWLINE));
178 }
179 if !iov.is_empty() {
180 write_all_vectored(out, &iov)?;
181 }
182 Ok(())
183}
184
185#[inline]
189fn write_wrapped_iov_streaming(
190 encoded: &[u8],
191 wrap_col: usize,
192 col: &mut usize,
193 out: &mut impl Write,
194) -> io::Result<()> {
195 const MAX_IOV: usize = 1024;
196 let mut iov: Vec<io::IoSlice> = Vec::with_capacity(MAX_IOV);
197 let mut rp = 0;
198
199 while rp < encoded.len() {
200 let space = wrap_col - *col;
201 let avail = encoded.len() - rp;
202
203 if avail <= space {
204 iov.push(io::IoSlice::new(&encoded[rp..rp + avail]));
206 *col += avail;
207 if *col == wrap_col {
208 iov.push(io::IoSlice::new(&NEWLINE));
209 *col = 0;
210 }
211 break;
212 } else {
213 iov.push(io::IoSlice::new(&encoded[rp..rp + space]));
215 iov.push(io::IoSlice::new(&NEWLINE));
216 rp += space;
217 *col = 0;
218 }
219
220 if iov.len() >= MAX_IOV - 1 {
221 write_all_vectored(out, &iov)?;
222 iov.clear();
223 }
224 }
225
226 if !iov.is_empty() {
227 write_all_vectored(out, &iov)?;
228 }
229 Ok(())
230}
231
232fn encode_wrapped_parallel(
241 data: &[u8],
242 wrap_col: usize,
243 bytes_per_line: usize,
244 out: &mut impl Write,
245) -> io::Result<()> {
246 let line_out = wrap_col + 1; let total_full_lines = data.len() / bytes_per_line;
248 let remainder_input = data.len() % bytes_per_line;
249
250 let remainder_encoded = if remainder_input > 0 {
252 BASE64_ENGINE.encoded_length(remainder_input) + 1 } else {
254 0
255 };
256 let total_output = total_full_lines * line_out + remainder_encoded;
257
258 let mut outbuf: Vec<u8> = Vec::with_capacity(total_output);
260 #[allow(clippy::uninit_vec)]
261 unsafe {
262 outbuf.set_len(total_output);
263 }
264
265 let num_threads = rayon::current_num_threads().max(1);
267 let lines_per_chunk = (total_full_lines / num_threads).max(1);
268 let input_chunk = lines_per_chunk * bytes_per_line;
269
270 let mut tasks: Vec<(usize, usize, usize)> = Vec::new();
272 let mut in_off = 0usize;
273 let mut out_off = 0usize;
274 while in_off < data.len() {
275 let chunk_input = input_chunk.min(data.len() - in_off);
276 let aligned_input = if in_off + chunk_input < data.len() {
278 (chunk_input / bytes_per_line) * bytes_per_line
279 } else {
280 chunk_input
281 };
282 if aligned_input == 0 {
283 break;
284 }
285 let full_lines = aligned_input / bytes_per_line;
286 let rem = aligned_input % bytes_per_line;
287 let chunk_output = full_lines * line_out
288 + if rem > 0 {
289 BASE64_ENGINE.encoded_length(rem) + 1
290 } else {
291 0
292 };
293 tasks.push((in_off, out_off, aligned_input));
294 in_off += aligned_input;
295 out_off += chunk_output;
296 }
297
298 let out_addr = outbuf.as_mut_ptr() as usize;
304
305 tasks.par_iter().for_each(|&(in_off, out_off, chunk_len)| {
306 let input = &data[in_off..in_off + chunk_len];
307 let full_lines = chunk_len / bytes_per_line;
308 let rem = chunk_len % bytes_per_line;
309 let full_input = full_lines * bytes_per_line;
310
311 let out_ptr = out_addr as *mut u8;
312
313 if full_lines > 0 {
315 let enc_total = BASE64_ENGINE.encoded_length(full_input);
316 let mut enc_buf: Vec<u8> = Vec::with_capacity(enc_total);
317 #[allow(clippy::uninit_vec)]
318 unsafe {
319 enc_buf.set_len(enc_total);
320 }
321 let _ = BASE64_ENGINE.encode(&input[..full_input], enc_buf[..enc_total].as_out());
322
323 let src = enc_buf.as_ptr();
326 let dst = unsafe { out_ptr.add(out_off) };
327 let mut rp = 0;
328 let mut wp = 0;
329
330 while rp + 8 * wrap_col <= enc_total {
332 unsafe {
333 std::ptr::copy_nonoverlapping(src.add(rp), dst.add(wp), wrap_col);
334 *dst.add(wp + wrap_col) = b'\n';
335 std::ptr::copy_nonoverlapping(
336 src.add(rp + wrap_col),
337 dst.add(wp + line_out),
338 wrap_col,
339 );
340 *dst.add(wp + line_out + wrap_col) = b'\n';
341 std::ptr::copy_nonoverlapping(
342 src.add(rp + 2 * wrap_col),
343 dst.add(wp + 2 * line_out),
344 wrap_col,
345 );
346 *dst.add(wp + 2 * line_out + wrap_col) = b'\n';
347 std::ptr::copy_nonoverlapping(
348 src.add(rp + 3 * wrap_col),
349 dst.add(wp + 3 * line_out),
350 wrap_col,
351 );
352 *dst.add(wp + 3 * line_out + wrap_col) = b'\n';
353 std::ptr::copy_nonoverlapping(
354 src.add(rp + 4 * wrap_col),
355 dst.add(wp + 4 * line_out),
356 wrap_col,
357 );
358 *dst.add(wp + 4 * line_out + wrap_col) = b'\n';
359 std::ptr::copy_nonoverlapping(
360 src.add(rp + 5 * wrap_col),
361 dst.add(wp + 5 * line_out),
362 wrap_col,
363 );
364 *dst.add(wp + 5 * line_out + wrap_col) = b'\n';
365 std::ptr::copy_nonoverlapping(
366 src.add(rp + 6 * wrap_col),
367 dst.add(wp + 6 * line_out),
368 wrap_col,
369 );
370 *dst.add(wp + 6 * line_out + wrap_col) = b'\n';
371 std::ptr::copy_nonoverlapping(
372 src.add(rp + 7 * wrap_col),
373 dst.add(wp + 7 * line_out),
374 wrap_col,
375 );
376 *dst.add(wp + 7 * line_out + wrap_col) = b'\n';
377 }
378 rp += 8 * wrap_col;
379 wp += 8 * line_out;
380 }
381
382 while rp + wrap_col <= enc_total {
384 unsafe {
385 std::ptr::copy_nonoverlapping(src.add(rp), dst.add(wp), wrap_col);
386 *dst.add(wp + wrap_col) = b'\n';
387 }
388 rp += wrap_col;
389 wp += line_out;
390 }
391 }
392
393 if rem > 0 {
395 let line_input = &input[full_input..];
396 let enc_len = BASE64_ENGINE.encoded_length(rem);
397 let woff = out_off + full_lines * line_out;
398 let out_slice =
400 unsafe { std::slice::from_raw_parts_mut(out_ptr.add(woff), enc_len + 1) };
401 let _ = BASE64_ENGINE.encode(line_input, out_slice[..enc_len].as_out());
402 out_slice[enc_len] = b'\n';
403 }
404 });
405
406 out.write_all(&outbuf[..total_output])
407}
408
409#[inline]
413fn fuse_wrap(encoded: &[u8], wrap_col: usize, out_buf: &mut [u8]) -> usize {
414 let line_out = wrap_col + 1; let mut rp = 0;
416 let mut wp = 0;
417
418 while rp + 8 * wrap_col <= encoded.len() {
420 unsafe {
421 let src = encoded.as_ptr().add(rp);
422 let dst = out_buf.as_mut_ptr().add(wp);
423
424 std::ptr::copy_nonoverlapping(src, dst, wrap_col);
425 *dst.add(wrap_col) = b'\n';
426
427 std::ptr::copy_nonoverlapping(src.add(wrap_col), dst.add(line_out), wrap_col);
428 *dst.add(line_out + wrap_col) = b'\n';
429
430 std::ptr::copy_nonoverlapping(src.add(2 * wrap_col), dst.add(2 * line_out), wrap_col);
431 *dst.add(2 * line_out + wrap_col) = b'\n';
432
433 std::ptr::copy_nonoverlapping(src.add(3 * wrap_col), dst.add(3 * line_out), wrap_col);
434 *dst.add(3 * line_out + wrap_col) = b'\n';
435
436 std::ptr::copy_nonoverlapping(src.add(4 * wrap_col), dst.add(4 * line_out), wrap_col);
437 *dst.add(4 * line_out + wrap_col) = b'\n';
438
439 std::ptr::copy_nonoverlapping(src.add(5 * wrap_col), dst.add(5 * line_out), wrap_col);
440 *dst.add(5 * line_out + wrap_col) = b'\n';
441
442 std::ptr::copy_nonoverlapping(src.add(6 * wrap_col), dst.add(6 * line_out), wrap_col);
443 *dst.add(6 * line_out + wrap_col) = b'\n';
444
445 std::ptr::copy_nonoverlapping(src.add(7 * wrap_col), dst.add(7 * line_out), wrap_col);
446 *dst.add(7 * line_out + wrap_col) = b'\n';
447 }
448 rp += 8 * wrap_col;
449 wp += 8 * line_out;
450 }
451
452 while rp + 4 * wrap_col <= encoded.len() {
454 unsafe {
455 let src = encoded.as_ptr().add(rp);
456 let dst = out_buf.as_mut_ptr().add(wp);
457
458 std::ptr::copy_nonoverlapping(src, dst, wrap_col);
459 *dst.add(wrap_col) = b'\n';
460
461 std::ptr::copy_nonoverlapping(src.add(wrap_col), dst.add(line_out), wrap_col);
462 *dst.add(line_out + wrap_col) = b'\n';
463
464 std::ptr::copy_nonoverlapping(src.add(2 * wrap_col), dst.add(2 * line_out), wrap_col);
465 *dst.add(2 * line_out + wrap_col) = b'\n';
466
467 std::ptr::copy_nonoverlapping(src.add(3 * wrap_col), dst.add(3 * line_out), wrap_col);
468 *dst.add(3 * line_out + wrap_col) = b'\n';
469 }
470 rp += 4 * wrap_col;
471 wp += 4 * line_out;
472 }
473
474 while rp + wrap_col <= encoded.len() {
476 unsafe {
477 std::ptr::copy_nonoverlapping(
478 encoded.as_ptr().add(rp),
479 out_buf.as_mut_ptr().add(wp),
480 wrap_col,
481 );
482 *out_buf.as_mut_ptr().add(wp + wrap_col) = b'\n';
483 }
484 rp += wrap_col;
485 wp += line_out;
486 }
487
488 if rp < encoded.len() {
490 let remaining = encoded.len() - rp;
491 unsafe {
492 std::ptr::copy_nonoverlapping(
493 encoded.as_ptr().add(rp),
494 out_buf.as_mut_ptr().add(wp),
495 remaining,
496 );
497 }
498 wp += remaining;
499 out_buf[wp] = b'\n';
500 wp += 1;
501 }
502
503 wp
504}
505
506fn encode_wrapped_small(data: &[u8], wrap_col: usize, out: &mut impl Write) -> io::Result<()> {
508 let enc_max = BASE64_ENGINE.encoded_length(data.len());
509 let mut buf: Vec<u8> = Vec::with_capacity(enc_max);
510 #[allow(clippy::uninit_vec)]
511 unsafe {
512 buf.set_len(enc_max);
513 }
514 let encoded = BASE64_ENGINE.encode(data, buf[..enc_max].as_out());
515
516 let wc = wrap_col.max(1);
517 for line in encoded.chunks(wc) {
518 out.write_all(line)?;
519 out.write_all(b"\n")?;
520 }
521 Ok(())
522}
523
524pub fn decode_to_writer(data: &[u8], ignore_garbage: bool, out: &mut impl Write) -> io::Result<()> {
528 if data.is_empty() {
529 return Ok(());
530 }
531
532 if ignore_garbage {
533 let mut cleaned = strip_non_base64(data);
534 return decode_clean_slice(&mut cleaned, out);
535 }
536
537 decode_stripping_whitespace(data, out)
539}
540
541pub fn decode_owned(
543 data: &mut Vec<u8>,
544 ignore_garbage: bool,
545 out: &mut impl Write,
546) -> io::Result<()> {
547 if data.is_empty() {
548 return Ok(());
549 }
550
551 if ignore_garbage {
552 data.retain(|&b| is_base64_char(b));
553 } else {
554 strip_whitespace_inplace(data);
555 }
556
557 decode_clean_slice(data, out)
558}
559
560fn strip_whitespace_inplace(data: &mut Vec<u8>) {
565 let has_ws = data.iter().any(|&b| !NOT_WHITESPACE[b as usize]);
567 if !has_ws {
568 return;
569 }
570
571 let ptr = data.as_mut_ptr();
575 let len = data.len();
576 let mut wp = 0usize;
577 let mut gap_start = 0usize;
578 let mut has_rare_ws = false;
579
580 for pos in memchr::memchr2_iter(b'\n', b'\r', data.as_slice()) {
581 let gap_len = pos - gap_start;
582 if gap_len > 0 {
583 if !has_rare_ws {
584 has_rare_ws = data[gap_start..pos]
586 .iter()
587 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
588 }
589 if wp != gap_start {
590 unsafe {
591 std::ptr::copy(ptr.add(gap_start), ptr.add(wp), gap_len);
592 }
593 }
594 wp += gap_len;
595 }
596 gap_start = pos + 1;
597 }
598 let tail_len = len - gap_start;
600 if tail_len > 0 {
601 if !has_rare_ws {
602 has_rare_ws = data[gap_start..]
603 .iter()
604 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
605 }
606 if wp != gap_start {
607 unsafe {
608 std::ptr::copy(ptr.add(gap_start), ptr.add(wp), tail_len);
609 }
610 }
611 wp += tail_len;
612 }
613
614 data.truncate(wp);
615
616 if has_rare_ws {
619 let ptr = data.as_mut_ptr();
620 let len = data.len();
621 let mut rp = 0;
622 let mut cwp = 0;
623 while rp < len {
624 let b = unsafe { *ptr.add(rp) };
625 if NOT_WHITESPACE[b as usize] {
626 unsafe { *ptr.add(cwp) = b };
627 cwp += 1;
628 }
629 rp += 1;
630 }
631 data.truncate(cwp);
632 }
633}
634
635static NOT_WHITESPACE: [bool; 256] = {
638 let mut table = [true; 256];
639 table[b' ' as usize] = false;
640 table[b'\t' as usize] = false;
641 table[b'\n' as usize] = false;
642 table[b'\r' as usize] = false;
643 table[0x0b] = false; table[0x0c] = false; table
646};
647
648fn decode_stripping_whitespace(data: &[u8], out: &mut impl Write) -> io::Result<()> {
655 let has_ws = data.iter().any(|&b| !NOT_WHITESPACE[b as usize]);
657 if !has_ws {
658 return decode_borrowed_clean(out, data);
660 }
661
662 let mut clean: Vec<u8> = Vec::with_capacity(data.len());
666 let dst = clean.as_mut_ptr();
667 let mut wp = 0usize;
668 let mut gap_start = 0usize;
669 let mut has_rare_ws = false;
672
673 for pos in memchr::memchr2_iter(b'\n', b'\r', data) {
674 let gap_len = pos - gap_start;
675 if gap_len > 0 {
676 if !has_rare_ws {
679 has_rare_ws = data[gap_start..pos]
680 .iter()
681 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
682 }
683 unsafe {
684 std::ptr::copy_nonoverlapping(data.as_ptr().add(gap_start), dst.add(wp), gap_len);
685 }
686 wp += gap_len;
687 }
688 gap_start = pos + 1;
689 }
690 let tail_len = data.len() - gap_start;
692 if tail_len > 0 {
693 if !has_rare_ws {
694 has_rare_ws = data[gap_start..]
695 .iter()
696 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
697 }
698 unsafe {
699 std::ptr::copy_nonoverlapping(data.as_ptr().add(gap_start), dst.add(wp), tail_len);
700 }
701 wp += tail_len;
702 }
703 unsafe {
704 clean.set_len(wp);
705 }
706
707 if has_rare_ws {
710 let ptr = clean.as_mut_ptr();
711 let len = clean.len();
712 let mut rp = 0;
713 let mut cwp = 0;
714 while rp < len {
715 let b = unsafe { *ptr.add(rp) };
716 if NOT_WHITESPACE[b as usize] {
717 unsafe { *ptr.add(cwp) = b };
718 cwp += 1;
719 }
720 rp += 1;
721 }
722 clean.truncate(cwp);
723 }
724
725 decode_clean_slice(&mut clean, out)
726}
727
728fn decode_clean_slice(data: &mut [u8], out: &mut impl Write) -> io::Result<()> {
730 if data.is_empty() {
731 return Ok(());
732 }
733 match BASE64_ENGINE.decode_inplace(data) {
734 Ok(decoded) => out.write_all(decoded),
735 Err(_) => decode_error(),
736 }
737}
738
739#[cold]
741#[inline(never)]
742fn decode_error() -> io::Result<()> {
743 Err(io::Error::new(io::ErrorKind::InvalidData, "invalid input"))
744}
745
746fn decode_borrowed_clean(out: &mut impl Write, data: &[u8]) -> io::Result<()> {
748 if data.is_empty() {
749 return Ok(());
750 }
751 if data.len() >= PARALLEL_DECODE_THRESHOLD {
754 return decode_borrowed_clean_parallel(out, data);
755 }
756 match BASE64_ENGINE.decode_to_vec(data) {
757 Ok(decoded) => {
758 out.write_all(&decoded)?;
759 Ok(())
760 }
761 Err(_) => decode_error(),
762 }
763}
764
765fn decode_borrowed_clean_parallel(out: &mut impl Write, data: &[u8]) -> io::Result<()> {
769 let num_threads = rayon::current_num_threads().max(1);
770 let raw_chunk = data.len() / num_threads;
771 let chunk_size = ((raw_chunk + 3) / 4) * 4;
773
774 let chunks: Vec<&[u8]> = data.chunks(chunk_size.max(4)).collect();
775
776 let mut offsets: Vec<usize> = Vec::with_capacity(chunks.len() + 1);
780 offsets.push(0);
781 let mut total_decoded = 0usize;
782 for (i, chunk) in chunks.iter().enumerate() {
783 let decoded_size = if i == chunks.len() - 1 {
784 let pad = chunk.iter().rev().take(2).filter(|&&b| b == b'=').count();
786 chunk.len() * 3 / 4 - pad
787 } else {
788 chunk.len() * 3 / 4
790 };
791 total_decoded += decoded_size;
792 offsets.push(total_decoded);
793 }
794
795 let mut output_buf: Vec<u8> = Vec::with_capacity(total_decoded);
797 #[allow(clippy::uninit_vec)]
798 unsafe {
799 output_buf.set_len(total_decoded);
800 }
801
802 let out_addr = output_buf.as_mut_ptr() as usize;
807 let decode_result: Result<Vec<()>, io::Error> = chunks
808 .par_iter()
809 .enumerate()
810 .map(|(i, chunk)| {
811 let offset = offsets[i];
812 let expected_size = offsets[i + 1] - offset;
813 let out_slice = unsafe {
815 std::slice::from_raw_parts_mut((out_addr as *mut u8).add(offset), expected_size)
816 };
817 let decoded = BASE64_ENGINE
818 .decode(chunk, out_slice.as_out())
819 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid input"))?;
820 debug_assert_eq!(decoded.len(), expected_size);
821 Ok(())
822 })
823 .collect();
824
825 decode_result?;
826
827 out.write_all(&output_buf[..total_decoded])
828}
829
830fn strip_non_base64(data: &[u8]) -> Vec<u8> {
832 data.iter()
833 .copied()
834 .filter(|&b| is_base64_char(b))
835 .collect()
836}
837
838#[inline]
840fn is_base64_char(b: u8) -> bool {
841 b.is_ascii_alphanumeric() || b == b'+' || b == b'/' || b == b'='
842}
843
844pub fn encode_stream(
847 reader: &mut impl Read,
848 wrap_col: usize,
849 writer: &mut impl Write,
850) -> io::Result<()> {
851 if wrap_col == 0 {
852 return encode_stream_nowrap(reader, writer);
853 }
854 encode_stream_wrapped(reader, wrap_col, writer)
855}
856
857fn encode_stream_nowrap(reader: &mut impl Read, writer: &mut impl Write) -> io::Result<()> {
862 const NOWRAP_READ: usize = 12 * 1024 * 1024; let mut buf = vec![0u8; NOWRAP_READ];
867 let encode_buf_size = BASE64_ENGINE.encoded_length(NOWRAP_READ);
868 let mut encode_buf = vec![0u8; encode_buf_size];
869
870 loop {
871 let n = read_full(reader, &mut buf)?;
872 if n == 0 {
873 break;
874 }
875 let enc_len = BASE64_ENGINE.encoded_length(n);
876 let encoded = BASE64_ENGINE.encode(&buf[..n], encode_buf[..enc_len].as_out());
877 writer.write_all(encoded)?;
878 }
879 Ok(())
880}
881
882fn encode_stream_wrapped(
890 reader: &mut impl Read,
891 wrap_col: usize,
892 writer: &mut impl Write,
893) -> io::Result<()> {
894 let bytes_per_line = wrap_col * 3 / 4;
895 if bytes_per_line > 0 && bytes_per_line.is_multiple_of(3) {
899 return encode_stream_wrapped_fused(reader, wrap_col, bytes_per_line, writer);
900 }
901
902 const STREAM_READ: usize = 12 * 1024 * 1024;
904 let mut buf = vec![0u8; STREAM_READ];
905 let encode_buf_size = BASE64_ENGINE.encoded_length(STREAM_READ);
906 let mut encode_buf = vec![0u8; encode_buf_size];
907
908 let mut col = 0usize;
909
910 loop {
911 let n = read_full(reader, &mut buf)?;
912 if n == 0 {
913 break;
914 }
915 let enc_len = BASE64_ENGINE.encoded_length(n);
916 let encoded = BASE64_ENGINE.encode(&buf[..n], encode_buf[..enc_len].as_out());
917
918 write_wrapped_iov_streaming(encoded, wrap_col, &mut col, writer)?;
919 }
920
921 if col > 0 {
922 writer.write_all(b"\n")?;
923 }
924
925 Ok(())
926}
927
928fn encode_stream_wrapped_fused(
933 reader: &mut impl Read,
934 wrap_col: usize,
935 bytes_per_line: usize,
936 writer: &mut impl Write,
937) -> io::Result<()> {
938 let lines_per_chunk = (12 * 1024 * 1024) / bytes_per_line;
941 let read_size = lines_per_chunk * bytes_per_line;
942
943 let mut buf = vec![0u8; read_size];
944 let enc_max = BASE64_ENGINE.encoded_length(read_size);
945 let fused_max = enc_max + (enc_max / wrap_col + 2); let total_buf_size = fused_max + enc_max;
947 let mut work_buf: Vec<u8> = vec![0u8; total_buf_size];
949
950 let mut trailing_partial = false;
951
952 loop {
953 let n = read_full(reader, &mut buf)?;
954 if n == 0 {
955 break;
956 }
957
958 let enc_len = BASE64_ENGINE.encoded_length(n);
959 let encode_start = fused_max;
961 let _ = BASE64_ENGINE.encode(
962 &buf[..n],
963 work_buf[encode_start..encode_start + enc_len].as_out(),
964 );
965
966 let (fused_region, encode_region) = work_buf.split_at_mut(fused_max);
968 let encoded = &encode_region[..enc_len];
969 let wp = fuse_wrap(encoded, wrap_col, fused_region);
970
971 writer.write_all(&fused_region[..wp])?;
972 trailing_partial = !enc_len.is_multiple_of(wrap_col);
973 }
974
975 let _ = trailing_partial;
979
980 Ok(())
981}
982
983pub fn decode_stream(
990 reader: &mut impl Read,
991 ignore_garbage: bool,
992 writer: &mut impl Write,
993) -> io::Result<()> {
994 const READ_CHUNK: usize = 16 * 1024 * 1024;
995 let mut buf = vec![0u8; READ_CHUNK];
996 let mut clean: Vec<u8> = Vec::with_capacity(READ_CHUNK + 4);
999 let mut carry = [0u8; 4];
1000 let mut carry_len = 0usize;
1001
1002 loop {
1003 let n = read_full(reader, &mut buf)?;
1004 if n == 0 {
1005 break;
1006 }
1007
1008 unsafe {
1010 std::ptr::copy_nonoverlapping(carry.as_ptr(), clean.as_mut_ptr(), carry_len);
1011 }
1012
1013 let chunk = &buf[..n];
1014 if ignore_garbage {
1015 let dst = unsafe { clean.as_mut_ptr().add(carry_len) };
1017 let mut wp = 0usize;
1018 for &b in chunk {
1019 if is_base64_char(b) {
1020 unsafe { *dst.add(wp) = b };
1021 wp += 1;
1022 }
1023 }
1024 unsafe { clean.set_len(carry_len + wp) };
1025 } else {
1026 let dst = unsafe { clean.as_mut_ptr().add(carry_len) };
1032 let mut wp = 0usize;
1033 let mut gap_start = 0usize;
1034 let mut has_rare_ws = false;
1035
1036 for pos in memchr::memchr2_iter(b'\n', b'\r', chunk) {
1037 let gap_len = pos - gap_start;
1038 if gap_len > 0 {
1039 if !has_rare_ws {
1040 has_rare_ws = chunk[gap_start..pos]
1041 .iter()
1042 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
1043 }
1044 unsafe {
1045 std::ptr::copy_nonoverlapping(
1046 chunk.as_ptr().add(gap_start),
1047 dst.add(wp),
1048 gap_len,
1049 );
1050 }
1051 wp += gap_len;
1052 }
1053 gap_start = pos + 1;
1054 }
1055 let tail_len = n - gap_start;
1056 if tail_len > 0 {
1057 if !has_rare_ws {
1058 has_rare_ws = chunk[gap_start..n]
1059 .iter()
1060 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
1061 }
1062 unsafe {
1063 std::ptr::copy_nonoverlapping(
1064 chunk.as_ptr().add(gap_start),
1065 dst.add(wp),
1066 tail_len,
1067 );
1068 }
1069 wp += tail_len;
1070 }
1071 let total_clean = carry_len + wp;
1072 unsafe { clean.set_len(total_clean) };
1073
1074 if has_rare_ws {
1077 let ptr = clean.as_mut_ptr();
1078 let mut rp = carry_len;
1079 let mut cwp = carry_len;
1080 while rp < total_clean {
1081 let b = unsafe { *ptr.add(rp) };
1082 if NOT_WHITESPACE[b as usize] {
1083 unsafe { *ptr.add(cwp) = b };
1084 cwp += 1;
1085 }
1086 rp += 1;
1087 }
1088 clean.truncate(cwp);
1089 }
1090 }
1091
1092 carry_len = 0;
1093 let is_last = n < READ_CHUNK;
1094
1095 if is_last {
1096 decode_clean_slice(&mut clean, writer)?;
1098 } else {
1099 let clean_len = clean.len();
1101 let decode_len = (clean_len / 4) * 4;
1102 let leftover = clean_len - decode_len;
1103 if leftover > 0 {
1104 unsafe {
1105 std::ptr::copy_nonoverlapping(
1106 clean.as_ptr().add(decode_len),
1107 carry.as_mut_ptr(),
1108 leftover,
1109 );
1110 }
1111 carry_len = leftover;
1112 }
1113 if decode_len > 0 {
1114 clean.truncate(decode_len);
1115 decode_clean_slice(&mut clean, writer)?;
1116 }
1117 }
1118 }
1119
1120 if carry_len > 0 {
1122 let mut carry_buf = carry[..carry_len].to_vec();
1123 decode_clean_slice(&mut carry_buf, writer)?;
1124 }
1125
1126 Ok(())
1127}
1128
1129fn write_all_vectored(out: &mut impl Write, slices: &[io::IoSlice]) -> io::Result<()> {
1132 if slices.is_empty() {
1133 return Ok(());
1134 }
1135 let total: usize = slices.iter().map(|s| s.len()).sum();
1136
1137 let written = match out.write_vectored(slices) {
1139 Ok(n) if n >= total => return Ok(()),
1140 Ok(n) => n,
1141 Err(e) => return Err(e),
1142 };
1143
1144 let mut skip = written;
1146 for slice in slices {
1147 let slen = slice.len();
1148 if skip >= slen {
1149 skip -= slen;
1150 continue;
1151 }
1152 if skip > 0 {
1153 out.write_all(&slice[skip..])?;
1154 skip = 0;
1155 } else {
1156 out.write_all(slice)?;
1157 }
1158 }
1159 Ok(())
1160}
1161
1162#[inline]
1166fn read_full(reader: &mut impl Read, buf: &mut [u8]) -> io::Result<usize> {
1167 let n = reader.read(buf)?;
1169 if n == buf.len() || n == 0 {
1170 return Ok(n);
1171 }
1172 let mut total = n;
1174 while total < buf.len() {
1175 match reader.read(&mut buf[total..]) {
1176 Ok(0) => break,
1177 Ok(n) => total += n,
1178 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
1179 Err(e) => return Err(e),
1180 }
1181 }
1182 Ok(total)
1183}