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 = 16 * 1024 * 1024;
17
18const PARALLEL_DECODE_THRESHOLD: usize = 4 * 1024 * 1024;
23
24pub fn encode_to_writer(data: &[u8], wrap_col: usize, out: &mut impl Write) -> io::Result<()> {
27 if data.is_empty() {
28 return Ok(());
29 }
30
31 if wrap_col == 0 {
32 return encode_no_wrap(data, out);
33 }
34
35 encode_wrapped(data, wrap_col, out)
36}
37
38fn encode_no_wrap(data: &[u8], out: &mut impl Write) -> io::Result<()> {
40 if data.len() >= PARALLEL_ENCODE_THRESHOLD {
41 return encode_no_wrap_parallel(data, out);
42 }
43
44 let actual_chunk = NOWRAP_CHUNK.min(data.len());
45 let enc_max = BASE64_ENGINE.encoded_length(actual_chunk);
46 let mut buf: Vec<u8> = Vec::with_capacity(enc_max);
48 #[allow(clippy::uninit_vec)]
49 unsafe {
50 buf.set_len(enc_max);
51 }
52
53 for chunk in data.chunks(NOWRAP_CHUNK) {
54 let enc_len = BASE64_ENGINE.encoded_length(chunk.len());
55 let encoded = BASE64_ENGINE.encode(chunk, buf[..enc_len].as_out());
56 out.write_all(encoded)?;
57 }
58 Ok(())
59}
60
61fn encode_no_wrap_parallel(data: &[u8], out: &mut impl Write) -> io::Result<()> {
65 let num_threads = rayon::current_num_threads().max(1);
66 let raw_chunk = data.len() / num_threads;
67 let chunk_size = ((raw_chunk + 2) / 3) * 3;
69
70 let chunks: Vec<&[u8]> = data.chunks(chunk_size.max(3)).collect();
71 let encoded_chunks: Vec<Vec<u8>> = chunks
72 .par_iter()
73 .map(|chunk| {
74 let enc_len = BASE64_ENGINE.encoded_length(chunk.len());
75 let mut buf: Vec<u8> = Vec::with_capacity(enc_len);
76 #[allow(clippy::uninit_vec)]
77 unsafe {
78 buf.set_len(enc_len);
79 }
80 let _ = BASE64_ENGINE.encode(chunk, buf[..enc_len].as_out());
81 buf
82 })
83 .collect();
84
85 let iov: Vec<io::IoSlice> = encoded_chunks.iter().map(|c| io::IoSlice::new(c)).collect();
87 write_all_vectored(out, &iov)
88}
89
90fn encode_wrapped(data: &[u8], wrap_col: usize, out: &mut impl Write) -> io::Result<()> {
95 let bytes_per_line = wrap_col * 3 / 4;
98 if bytes_per_line == 0 {
99 return encode_wrapped_small(data, wrap_col, out);
101 }
102
103 if data.len() >= PARALLEL_ENCODE_THRESHOLD && bytes_per_line.is_multiple_of(3) {
106 return encode_wrapped_parallel(data, wrap_col, bytes_per_line, out);
107 }
108
109 if bytes_per_line.is_multiple_of(3) {
114 let enc_len = BASE64_ENGINE.encoded_length(data.len());
116 let num_lines = (enc_len + wrap_col - 1) / wrap_col;
117 let total_output = enc_len + num_lines; let mut out_buf: Vec<u8> = Vec::with_capacity(total_output);
120 #[allow(clippy::uninit_vec)]
121 unsafe {
122 out_buf.set_len(total_output);
123 }
124
125 let encoded = BASE64_ENGINE.encode(data, out_buf[..enc_len].as_out());
127 let encoded_len = encoded.len();
128
129 let num_full_lines = encoded_len / wrap_col;
134 let remainder = encoded_len % wrap_col;
135
136 let ptr = out_buf.as_mut_ptr();
137 let mut wp = total_output; if remainder > 0 {
141 wp -= 1;
142 unsafe { *ptr.add(wp) = b'\n' };
143 wp -= remainder;
144 let rp = encoded_len - remainder;
145 if wp != rp {
146 unsafe {
147 std::ptr::copy(ptr.add(rp), ptr.add(wp), remainder);
148 }
149 }
150 }
151
152 let mut line = num_full_lines;
154 while line >= 4 {
155 line -= 4;
156 let rp = line * wrap_col;
157 let owp = wp;
158 unsafe {
159 wp = owp - 1;
161 *ptr.add(wp) = b'\n';
162 wp -= wrap_col;
163 std::ptr::copy(ptr.add(rp + 3 * wrap_col), ptr.add(wp), wrap_col);
164
165 wp -= 1;
167 *ptr.add(wp) = b'\n';
168 wp -= wrap_col;
169 std::ptr::copy(ptr.add(rp + 2 * wrap_col), ptr.add(wp), wrap_col);
170
171 wp -= 1;
173 *ptr.add(wp) = b'\n';
174 wp -= wrap_col;
175 std::ptr::copy(ptr.add(rp + wrap_col), ptr.add(wp), wrap_col);
176
177 wp -= 1;
179 *ptr.add(wp) = b'\n';
180 wp -= wrap_col;
181 std::ptr::copy(ptr.add(rp), ptr.add(wp), wrap_col);
182 }
183 }
184 while line > 0 {
185 line -= 1;
186 let rp = line * wrap_col;
187 wp -= 1;
188 unsafe {
189 *ptr.add(wp) = b'\n';
190 }
191 wp -= wrap_col;
192 if wp != rp {
193 unsafe {
194 std::ptr::copy(ptr.add(rp), ptr.add(wp), wrap_col);
195 }
196 }
197 }
198
199 return out.write_all(&out_buf[..total_output]);
200 }
201
202 let lines_per_chunk = (32 * 1024 * 1024) / bytes_per_line;
204 let max_input_chunk = (lines_per_chunk * bytes_per_line).max(bytes_per_line);
205 let input_chunk = max_input_chunk.min(data.len());
206
207 let enc_max = BASE64_ENGINE.encoded_length(input_chunk);
208 let mut encode_buf: Vec<u8> = Vec::with_capacity(enc_max);
209 #[allow(clippy::uninit_vec)]
210 unsafe {
211 encode_buf.set_len(enc_max);
212 }
213
214 for chunk in data.chunks(max_input_chunk.max(1)) {
215 let enc_len = BASE64_ENGINE.encoded_length(chunk.len());
216 let encoded = BASE64_ENGINE.encode(chunk, encode_buf[..enc_len].as_out());
217 write_wrapped_iov(encoded, wrap_col, out)?;
218 }
219
220 Ok(())
221}
222
223static NEWLINE: [u8; 1] = [b'\n'];
225
226#[inline]
231fn write_wrapped_iov(encoded: &[u8], wrap_col: usize, out: &mut impl Write) -> io::Result<()> {
232 const MAX_IOV: usize = 1024;
235
236 let num_full_lines = encoded.len() / wrap_col;
237 let remainder = encoded.len() % wrap_col;
238 let total_iov = num_full_lines * 2 + if remainder > 0 { 2 } else { 0 };
239
240 if total_iov <= MAX_IOV {
242 let mut iov: Vec<io::IoSlice> = Vec::with_capacity(total_iov);
243 let mut pos = 0;
244 for _ in 0..num_full_lines {
245 iov.push(io::IoSlice::new(&encoded[pos..pos + wrap_col]));
246 iov.push(io::IoSlice::new(&NEWLINE));
247 pos += wrap_col;
248 }
249 if remainder > 0 {
250 iov.push(io::IoSlice::new(&encoded[pos..pos + remainder]));
251 iov.push(io::IoSlice::new(&NEWLINE));
252 }
253 return write_all_vectored(out, &iov);
254 }
255
256 let mut iov: Vec<io::IoSlice> = Vec::with_capacity(MAX_IOV);
258 let mut pos = 0;
259 for _ in 0..num_full_lines {
260 iov.push(io::IoSlice::new(&encoded[pos..pos + wrap_col]));
261 iov.push(io::IoSlice::new(&NEWLINE));
262 pos += wrap_col;
263 if iov.len() >= MAX_IOV {
264 write_all_vectored(out, &iov)?;
265 iov.clear();
266 }
267 }
268 if remainder > 0 {
269 iov.push(io::IoSlice::new(&encoded[pos..pos + remainder]));
270 iov.push(io::IoSlice::new(&NEWLINE));
271 }
272 if !iov.is_empty() {
273 write_all_vectored(out, &iov)?;
274 }
275 Ok(())
276}
277
278#[inline]
282fn write_wrapped_iov_streaming(
283 encoded: &[u8],
284 wrap_col: usize,
285 col: &mut usize,
286 out: &mut impl Write,
287) -> io::Result<()> {
288 const MAX_IOV: usize = 1024;
289 let mut iov: Vec<io::IoSlice> = Vec::with_capacity(MAX_IOV);
290 let mut rp = 0;
291
292 while rp < encoded.len() {
293 let space = wrap_col - *col;
294 let avail = encoded.len() - rp;
295
296 if avail <= space {
297 iov.push(io::IoSlice::new(&encoded[rp..rp + avail]));
299 *col += avail;
300 if *col == wrap_col {
301 iov.push(io::IoSlice::new(&NEWLINE));
302 *col = 0;
303 }
304 break;
305 } else {
306 iov.push(io::IoSlice::new(&encoded[rp..rp + space]));
308 iov.push(io::IoSlice::new(&NEWLINE));
309 rp += space;
310 *col = 0;
311 }
312
313 if iov.len() >= MAX_IOV - 1 {
314 write_all_vectored(out, &iov)?;
315 iov.clear();
316 }
317 }
318
319 if !iov.is_empty() {
320 write_all_vectored(out, &iov)?;
321 }
322 Ok(())
323}
324
325fn encode_wrapped_parallel(
334 data: &[u8],
335 wrap_col: usize,
336 bytes_per_line: usize,
337 out: &mut impl Write,
338) -> io::Result<()> {
339 let line_out = wrap_col + 1; let total_full_lines = data.len() / bytes_per_line;
341 let remainder_input = data.len() % bytes_per_line;
342
343 let remainder_encoded = if remainder_input > 0 {
345 BASE64_ENGINE.encoded_length(remainder_input) + 1 } else {
347 0
348 };
349 let total_output = total_full_lines * line_out + remainder_encoded;
350
351 let mut outbuf: Vec<u8> = Vec::with_capacity(total_output);
353 #[allow(clippy::uninit_vec)]
354 unsafe {
355 outbuf.set_len(total_output);
356 }
357
358 let num_threads = rayon::current_num_threads().max(1);
360 let lines_per_chunk = (total_full_lines / num_threads).max(1);
361 let input_chunk = lines_per_chunk * bytes_per_line;
362
363 let mut tasks: Vec<(usize, usize, usize)> = Vec::new();
365 let mut in_off = 0usize;
366 let mut out_off = 0usize;
367 while in_off < data.len() {
368 let chunk_input = input_chunk.min(data.len() - in_off);
369 let aligned_input = if in_off + chunk_input < data.len() {
371 (chunk_input / bytes_per_line) * bytes_per_line
372 } else {
373 chunk_input
374 };
375 if aligned_input == 0 {
376 break;
377 }
378 let full_lines = aligned_input / bytes_per_line;
379 let rem = aligned_input % bytes_per_line;
380 let chunk_output = full_lines * line_out
381 + if rem > 0 {
382 BASE64_ENGINE.encoded_length(rem) + 1
383 } else {
384 0
385 };
386 tasks.push((in_off, out_off, aligned_input));
387 in_off += aligned_input;
388 out_off += chunk_output;
389 }
390
391 let out_addr = outbuf.as_mut_ptr() as usize;
398
399 tasks.par_iter().for_each(|&(in_off, out_off, chunk_len)| {
400 let input = &data[in_off..in_off + chunk_len];
401 let full_lines = chunk_len / bytes_per_line;
402 let rem = chunk_len % bytes_per_line;
403
404 let out_ptr = out_addr as *mut u8;
405
406 if full_lines > 0 {
410 let dst = unsafe { out_ptr.add(out_off) };
411 let mut line_idx = 0;
412
413 while line_idx + 4 <= full_lines {
415 let in_base = line_idx * bytes_per_line;
416 let out_base = line_idx * line_out;
417 unsafe {
418 let s0 = std::slice::from_raw_parts_mut(dst.add(out_base), wrap_col);
419 let _ = BASE64_ENGINE
420 .encode(&input[in_base..in_base + bytes_per_line], s0.as_out());
421 *dst.add(out_base + wrap_col) = b'\n';
422
423 let s1 = std::slice::from_raw_parts_mut(dst.add(out_base + line_out), wrap_col);
424 let _ = BASE64_ENGINE.encode(
425 &input[in_base + bytes_per_line..in_base + 2 * bytes_per_line],
426 s1.as_out(),
427 );
428 *dst.add(out_base + line_out + wrap_col) = b'\n';
429
430 let s2 =
431 std::slice::from_raw_parts_mut(dst.add(out_base + 2 * line_out), wrap_col);
432 let _ = BASE64_ENGINE.encode(
433 &input[in_base + 2 * bytes_per_line..in_base + 3 * bytes_per_line],
434 s2.as_out(),
435 );
436 *dst.add(out_base + 2 * line_out + wrap_col) = b'\n';
437
438 let s3 =
439 std::slice::from_raw_parts_mut(dst.add(out_base + 3 * line_out), wrap_col);
440 let _ = BASE64_ENGINE.encode(
441 &input[in_base + 3 * bytes_per_line..in_base + 4 * bytes_per_line],
442 s3.as_out(),
443 );
444 *dst.add(out_base + 3 * line_out + wrap_col) = b'\n';
445 }
446 line_idx += 4;
447 }
448
449 while line_idx < full_lines {
451 let in_base = line_idx * bytes_per_line;
452 let out_base = line_idx * line_out;
453 unsafe {
454 let s = std::slice::from_raw_parts_mut(dst.add(out_base), wrap_col);
455 let _ =
456 BASE64_ENGINE.encode(&input[in_base..in_base + bytes_per_line], s.as_out());
457 *dst.add(out_base + wrap_col) = b'\n';
458 }
459 line_idx += 1;
460 }
461 }
462
463 if rem > 0 {
465 let line_input = &input[full_lines * bytes_per_line..];
466 let enc_len = BASE64_ENGINE.encoded_length(rem);
467 let woff = out_off + full_lines * line_out;
468 let out_slice =
470 unsafe { std::slice::from_raw_parts_mut(out_ptr.add(woff), enc_len + 1) };
471 let _ = BASE64_ENGINE.encode(line_input, out_slice[..enc_len].as_out());
472 out_slice[enc_len] = b'\n';
473 }
474 });
475
476 out.write_all(&outbuf[..total_output])
477}
478
479#[allow(dead_code)]
483#[inline]
484fn fuse_wrap(encoded: &[u8], wrap_col: usize, out_buf: &mut [u8]) -> usize {
485 let line_out = wrap_col + 1; let mut rp = 0;
487 let mut wp = 0;
488
489 while rp + 8 * wrap_col <= encoded.len() {
491 unsafe {
492 let src = encoded.as_ptr().add(rp);
493 let dst = out_buf.as_mut_ptr().add(wp);
494
495 std::ptr::copy_nonoverlapping(src, dst, wrap_col);
496 *dst.add(wrap_col) = b'\n';
497
498 std::ptr::copy_nonoverlapping(src.add(wrap_col), dst.add(line_out), wrap_col);
499 *dst.add(line_out + wrap_col) = b'\n';
500
501 std::ptr::copy_nonoverlapping(src.add(2 * wrap_col), dst.add(2 * line_out), wrap_col);
502 *dst.add(2 * line_out + wrap_col) = b'\n';
503
504 std::ptr::copy_nonoverlapping(src.add(3 * wrap_col), dst.add(3 * line_out), wrap_col);
505 *dst.add(3 * line_out + wrap_col) = b'\n';
506
507 std::ptr::copy_nonoverlapping(src.add(4 * wrap_col), dst.add(4 * line_out), wrap_col);
508 *dst.add(4 * line_out + wrap_col) = b'\n';
509
510 std::ptr::copy_nonoverlapping(src.add(5 * wrap_col), dst.add(5 * line_out), wrap_col);
511 *dst.add(5 * line_out + wrap_col) = b'\n';
512
513 std::ptr::copy_nonoverlapping(src.add(6 * wrap_col), dst.add(6 * line_out), wrap_col);
514 *dst.add(6 * line_out + wrap_col) = b'\n';
515
516 std::ptr::copy_nonoverlapping(src.add(7 * wrap_col), dst.add(7 * line_out), wrap_col);
517 *dst.add(7 * line_out + wrap_col) = b'\n';
518 }
519 rp += 8 * wrap_col;
520 wp += 8 * line_out;
521 }
522
523 while rp + 4 * wrap_col <= encoded.len() {
525 unsafe {
526 let src = encoded.as_ptr().add(rp);
527 let dst = out_buf.as_mut_ptr().add(wp);
528
529 std::ptr::copy_nonoverlapping(src, dst, wrap_col);
530 *dst.add(wrap_col) = b'\n';
531
532 std::ptr::copy_nonoverlapping(src.add(wrap_col), dst.add(line_out), wrap_col);
533 *dst.add(line_out + wrap_col) = b'\n';
534
535 std::ptr::copy_nonoverlapping(src.add(2 * wrap_col), dst.add(2 * line_out), wrap_col);
536 *dst.add(2 * line_out + wrap_col) = b'\n';
537
538 std::ptr::copy_nonoverlapping(src.add(3 * wrap_col), dst.add(3 * line_out), wrap_col);
539 *dst.add(3 * line_out + wrap_col) = b'\n';
540 }
541 rp += 4 * wrap_col;
542 wp += 4 * line_out;
543 }
544
545 while rp + wrap_col <= encoded.len() {
547 unsafe {
548 std::ptr::copy_nonoverlapping(
549 encoded.as_ptr().add(rp),
550 out_buf.as_mut_ptr().add(wp),
551 wrap_col,
552 );
553 *out_buf.as_mut_ptr().add(wp + wrap_col) = b'\n';
554 }
555 rp += wrap_col;
556 wp += line_out;
557 }
558
559 if rp < encoded.len() {
561 let remaining = encoded.len() - rp;
562 unsafe {
563 std::ptr::copy_nonoverlapping(
564 encoded.as_ptr().add(rp),
565 out_buf.as_mut_ptr().add(wp),
566 remaining,
567 );
568 }
569 wp += remaining;
570 out_buf[wp] = b'\n';
571 wp += 1;
572 }
573
574 wp
575}
576
577fn encode_wrapped_small(data: &[u8], wrap_col: usize, out: &mut impl Write) -> io::Result<()> {
579 let enc_max = BASE64_ENGINE.encoded_length(data.len());
580 let mut buf: Vec<u8> = Vec::with_capacity(enc_max);
581 #[allow(clippy::uninit_vec)]
582 unsafe {
583 buf.set_len(enc_max);
584 }
585 let encoded = BASE64_ENGINE.encode(data, buf[..enc_max].as_out());
586
587 let wc = wrap_col.max(1);
588 for line in encoded.chunks(wc) {
589 out.write_all(line)?;
590 out.write_all(b"\n")?;
591 }
592 Ok(())
593}
594
595pub fn decode_to_writer(data: &[u8], ignore_garbage: bool, out: &mut impl Write) -> io::Result<()> {
599 if data.is_empty() {
600 return Ok(());
601 }
602
603 if ignore_garbage {
604 let mut cleaned = strip_non_base64(data);
605 return decode_clean_slice(&mut cleaned, out);
606 }
607
608 decode_stripping_whitespace(data, out)
610}
611
612pub fn decode_owned(
614 data: &mut Vec<u8>,
615 ignore_garbage: bool,
616 out: &mut impl Write,
617) -> io::Result<()> {
618 if data.is_empty() {
619 return Ok(());
620 }
621
622 if ignore_garbage {
623 data.retain(|&b| is_base64_char(b));
624 } else {
625 strip_whitespace_inplace(data);
626 }
627
628 decode_clean_slice(data, out)
629}
630
631fn strip_whitespace_inplace(data: &mut Vec<u8>) {
636 if memchr::memchr2(b'\n', b'\r', data).is_none() {
640 if data
642 .iter()
643 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c)
644 {
645 data.retain(|&b| NOT_WHITESPACE[b as usize]);
646 }
647 return;
648 }
649
650 let ptr = data.as_mut_ptr();
654 let len = data.len();
655 let mut wp = 0usize;
656 let mut gap_start = 0usize;
657 let mut has_rare_ws = false;
658
659 for pos in memchr::memchr2_iter(b'\n', b'\r', data.as_slice()) {
660 let gap_len = pos - gap_start;
661 if gap_len > 0 {
662 if !has_rare_ws {
663 has_rare_ws = data[gap_start..pos]
665 .iter()
666 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
667 }
668 if wp != gap_start {
669 unsafe {
670 std::ptr::copy(ptr.add(gap_start), ptr.add(wp), gap_len);
671 }
672 }
673 wp += gap_len;
674 }
675 gap_start = pos + 1;
676 }
677 let tail_len = len - gap_start;
679 if tail_len > 0 {
680 if !has_rare_ws {
681 has_rare_ws = data[gap_start..]
682 .iter()
683 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
684 }
685 if wp != gap_start {
686 unsafe {
687 std::ptr::copy(ptr.add(gap_start), ptr.add(wp), tail_len);
688 }
689 }
690 wp += tail_len;
691 }
692
693 data.truncate(wp);
694
695 if has_rare_ws {
698 let ptr = data.as_mut_ptr();
699 let len = data.len();
700 let mut rp = 0;
701 let mut cwp = 0;
702 while rp < len {
703 let b = unsafe { *ptr.add(rp) };
704 if NOT_WHITESPACE[b as usize] {
705 unsafe { *ptr.add(cwp) = b };
706 cwp += 1;
707 }
708 rp += 1;
709 }
710 data.truncate(cwp);
711 }
712}
713
714static NOT_WHITESPACE: [bool; 256] = {
717 let mut table = [true; 256];
718 table[b' ' as usize] = false;
719 table[b'\t' as usize] = false;
720 table[b'\n' as usize] = false;
721 table[b'\r' as usize] = false;
722 table[0x0b] = false; table[0x0c] = false; table
725};
726
727fn decode_stripping_whitespace(data: &[u8], out: &mut impl Write) -> io::Result<()> {
734 if memchr::memchr2(b'\n', b'\r', data).is_none() {
737 if !data
739 .iter()
740 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c)
741 {
742 return decode_borrowed_clean(out, data);
743 }
744 let mut cleaned: Vec<u8> = Vec::with_capacity(data.len());
746 for &b in data {
747 if NOT_WHITESPACE[b as usize] {
748 cleaned.push(b);
749 }
750 }
751 return decode_clean_slice(&mut cleaned, out);
752 }
753
754 let mut clean: Vec<u8> = Vec::with_capacity(data.len());
758 let dst = clean.as_mut_ptr();
759 let mut wp = 0usize;
760 let mut gap_start = 0usize;
761 let mut has_rare_ws = false;
764
765 for pos in memchr::memchr2_iter(b'\n', b'\r', data) {
766 let gap_len = pos - gap_start;
767 if gap_len > 0 {
768 if !has_rare_ws {
771 has_rare_ws = data[gap_start..pos]
772 .iter()
773 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
774 }
775 unsafe {
776 std::ptr::copy_nonoverlapping(data.as_ptr().add(gap_start), dst.add(wp), gap_len);
777 }
778 wp += gap_len;
779 }
780 gap_start = pos + 1;
781 }
782 let tail_len = data.len() - gap_start;
784 if tail_len > 0 {
785 if !has_rare_ws {
786 has_rare_ws = data[gap_start..]
787 .iter()
788 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
789 }
790 unsafe {
791 std::ptr::copy_nonoverlapping(data.as_ptr().add(gap_start), dst.add(wp), tail_len);
792 }
793 wp += tail_len;
794 }
795 unsafe {
796 clean.set_len(wp);
797 }
798
799 if has_rare_ws {
802 let ptr = clean.as_mut_ptr();
803 let len = clean.len();
804 let mut rp = 0;
805 let mut cwp = 0;
806 while rp < len {
807 let b = unsafe { *ptr.add(rp) };
808 if NOT_WHITESPACE[b as usize] {
809 unsafe { *ptr.add(cwp) = b };
810 cwp += 1;
811 }
812 rp += 1;
813 }
814 clean.truncate(cwp);
815 }
816
817 if clean.len() >= PARALLEL_DECODE_THRESHOLD {
820 decode_borrowed_clean_parallel(out, &clean)
821 } else {
822 decode_clean_slice(&mut clean, out)
823 }
824}
825
826fn decode_clean_slice(data: &mut [u8], out: &mut impl Write) -> io::Result<()> {
828 if data.is_empty() {
829 return Ok(());
830 }
831 match BASE64_ENGINE.decode_inplace(data) {
832 Ok(decoded) => out.write_all(decoded),
833 Err(_) => decode_error(),
834 }
835}
836
837#[cold]
839#[inline(never)]
840fn decode_error() -> io::Result<()> {
841 Err(io::Error::new(io::ErrorKind::InvalidData, "invalid input"))
842}
843
844fn decode_borrowed_clean(out: &mut impl Write, data: &[u8]) -> io::Result<()> {
846 if data.is_empty() {
847 return Ok(());
848 }
849 if data.len() >= PARALLEL_DECODE_THRESHOLD {
852 return decode_borrowed_clean_parallel(out, data);
853 }
854 match BASE64_ENGINE.decode_to_vec(data) {
855 Ok(decoded) => {
856 out.write_all(&decoded)?;
857 Ok(())
858 }
859 Err(_) => decode_error(),
860 }
861}
862
863fn decode_borrowed_clean_parallel(out: &mut impl Write, data: &[u8]) -> io::Result<()> {
867 let num_threads = rayon::current_num_threads().max(1);
868 let raw_chunk = data.len() / num_threads;
869 let chunk_size = ((raw_chunk + 3) / 4) * 4;
871
872 let chunks: Vec<&[u8]> = data.chunks(chunk_size.max(4)).collect();
873
874 let mut offsets: Vec<usize> = Vec::with_capacity(chunks.len() + 1);
878 offsets.push(0);
879 let mut total_decoded = 0usize;
880 for (i, chunk) in chunks.iter().enumerate() {
881 let decoded_size = if i == chunks.len() - 1 {
882 let pad = chunk.iter().rev().take(2).filter(|&&b| b == b'=').count();
884 chunk.len() * 3 / 4 - pad
885 } else {
886 chunk.len() * 3 / 4
888 };
889 total_decoded += decoded_size;
890 offsets.push(total_decoded);
891 }
892
893 let mut output_buf: Vec<u8> = Vec::with_capacity(total_decoded);
895 #[allow(clippy::uninit_vec)]
896 unsafe {
897 output_buf.set_len(total_decoded);
898 }
899
900 let out_addr = output_buf.as_mut_ptr() as usize;
905 let decode_result: Result<Vec<()>, io::Error> = chunks
906 .par_iter()
907 .enumerate()
908 .map(|(i, chunk)| {
909 let offset = offsets[i];
910 let expected_size = offsets[i + 1] - offset;
911 let out_slice = unsafe {
913 std::slice::from_raw_parts_mut((out_addr as *mut u8).add(offset), expected_size)
914 };
915 let decoded = BASE64_ENGINE
916 .decode(chunk, out_slice.as_out())
917 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid input"))?;
918 debug_assert_eq!(decoded.len(), expected_size);
919 Ok(())
920 })
921 .collect();
922
923 decode_result?;
924
925 out.write_all(&output_buf[..total_decoded])
926}
927
928fn strip_non_base64(data: &[u8]) -> Vec<u8> {
930 data.iter()
931 .copied()
932 .filter(|&b| is_base64_char(b))
933 .collect()
934}
935
936#[inline]
938fn is_base64_char(b: u8) -> bool {
939 b.is_ascii_alphanumeric() || b == b'+' || b == b'/' || b == b'='
940}
941
942pub fn encode_stream(
945 reader: &mut impl Read,
946 wrap_col: usize,
947 writer: &mut impl Write,
948) -> io::Result<()> {
949 if wrap_col == 0 {
950 return encode_stream_nowrap(reader, writer);
951 }
952 encode_stream_wrapped(reader, wrap_col, writer)
953}
954
955fn encode_stream_nowrap(reader: &mut impl Read, writer: &mut impl Write) -> io::Result<()> {
960 const NOWRAP_READ: usize = 12 * 1024 * 1024; let mut buf: Vec<u8> = Vec::with_capacity(NOWRAP_READ);
967 #[allow(clippy::uninit_vec)]
968 unsafe {
969 buf.set_len(NOWRAP_READ);
970 }
971 let encode_buf_size = BASE64_ENGINE.encoded_length(NOWRAP_READ);
972 let mut encode_buf: Vec<u8> = Vec::with_capacity(encode_buf_size);
973 #[allow(clippy::uninit_vec)]
974 unsafe {
975 encode_buf.set_len(encode_buf_size);
976 }
977
978 loop {
979 let n = read_full(reader, &mut buf)?;
980 if n == 0 {
981 break;
982 }
983 let enc_len = BASE64_ENGINE.encoded_length(n);
984 let encoded = BASE64_ENGINE.encode(&buf[..n], encode_buf[..enc_len].as_out());
985 writer.write_all(encoded)?;
986 }
987 Ok(())
988}
989
990fn encode_stream_wrapped(
998 reader: &mut impl Read,
999 wrap_col: usize,
1000 writer: &mut impl Write,
1001) -> io::Result<()> {
1002 let bytes_per_line = wrap_col * 3 / 4;
1003 if bytes_per_line > 0 && bytes_per_line.is_multiple_of(3) {
1007 return encode_stream_wrapped_fused(reader, wrap_col, bytes_per_line, writer);
1008 }
1009
1010 const STREAM_READ: usize = 12 * 1024 * 1024;
1012 let mut buf: Vec<u8> = Vec::with_capacity(STREAM_READ);
1013 #[allow(clippy::uninit_vec)]
1014 unsafe {
1015 buf.set_len(STREAM_READ);
1016 }
1017 let encode_buf_size = BASE64_ENGINE.encoded_length(STREAM_READ);
1018 let mut encode_buf: Vec<u8> = Vec::with_capacity(encode_buf_size);
1019 #[allow(clippy::uninit_vec)]
1020 unsafe {
1021 encode_buf.set_len(encode_buf_size);
1022 }
1023
1024 let mut col = 0usize;
1025
1026 loop {
1027 let n = read_full(reader, &mut buf)?;
1028 if n == 0 {
1029 break;
1030 }
1031 let enc_len = BASE64_ENGINE.encoded_length(n);
1032 let encoded = BASE64_ENGINE.encode(&buf[..n], encode_buf[..enc_len].as_out());
1033
1034 write_wrapped_iov_streaming(encoded, wrap_col, &mut col, writer)?;
1035 }
1036
1037 if col > 0 {
1038 writer.write_all(b"\n")?;
1039 }
1040
1041 Ok(())
1042}
1043
1044fn encode_stream_wrapped_fused(
1050 reader: &mut impl Read,
1051 wrap_col: usize,
1052 bytes_per_line: usize,
1053 writer: &mut impl Write,
1054) -> io::Result<()> {
1055 let lines_per_chunk = (12 * 1024 * 1024) / bytes_per_line;
1058 let read_size = lines_per_chunk * bytes_per_line;
1059 let line_out = wrap_col + 1; let mut buf: Vec<u8> = Vec::with_capacity(read_size);
1064 #[allow(clippy::uninit_vec)]
1065 unsafe {
1066 buf.set_len(read_size);
1067 }
1068 let max_output = lines_per_chunk * line_out + BASE64_ENGINE.encoded_length(bytes_per_line) + 2;
1070 let mut out_buf: Vec<u8> = Vec::with_capacity(max_output);
1071 #[allow(clippy::uninit_vec)]
1072 unsafe {
1073 out_buf.set_len(max_output);
1074 }
1075
1076 loop {
1077 let n = read_full(reader, &mut buf)?;
1078 if n == 0 {
1079 break;
1080 }
1081
1082 let full_lines = n / bytes_per_line;
1083 let remainder = n % bytes_per_line;
1084
1085 let dst = out_buf.as_mut_ptr();
1089 let mut line_idx = 0;
1090
1091 while line_idx + 4 <= full_lines {
1093 let in_base = line_idx * bytes_per_line;
1094 let out_base = line_idx * line_out;
1095 unsafe {
1096 let s0 = std::slice::from_raw_parts_mut(dst.add(out_base), wrap_col);
1097 let _ = BASE64_ENGINE.encode(&buf[in_base..in_base + bytes_per_line], s0.as_out());
1098 *dst.add(out_base + wrap_col) = b'\n';
1099
1100 let s1 = std::slice::from_raw_parts_mut(dst.add(out_base + line_out), wrap_col);
1101 let _ = BASE64_ENGINE.encode(
1102 &buf[in_base + bytes_per_line..in_base + 2 * bytes_per_line],
1103 s1.as_out(),
1104 );
1105 *dst.add(out_base + line_out + wrap_col) = b'\n';
1106
1107 let s2 = std::slice::from_raw_parts_mut(dst.add(out_base + 2 * line_out), wrap_col);
1108 let _ = BASE64_ENGINE.encode(
1109 &buf[in_base + 2 * bytes_per_line..in_base + 3 * bytes_per_line],
1110 s2.as_out(),
1111 );
1112 *dst.add(out_base + 2 * line_out + wrap_col) = b'\n';
1113
1114 let s3 = std::slice::from_raw_parts_mut(dst.add(out_base + 3 * line_out), wrap_col);
1115 let _ = BASE64_ENGINE.encode(
1116 &buf[in_base + 3 * bytes_per_line..in_base + 4 * bytes_per_line],
1117 s3.as_out(),
1118 );
1119 *dst.add(out_base + 3 * line_out + wrap_col) = b'\n';
1120 }
1121 line_idx += 4;
1122 }
1123
1124 while line_idx < full_lines {
1126 let in_base = line_idx * bytes_per_line;
1127 let out_base = line_idx * line_out;
1128 unsafe {
1129 let s = std::slice::from_raw_parts_mut(dst.add(out_base), wrap_col);
1130 let _ = BASE64_ENGINE.encode(&buf[in_base..in_base + bytes_per_line], s.as_out());
1131 *dst.add(out_base + wrap_col) = b'\n';
1132 }
1133 line_idx += 1;
1134 }
1135
1136 let mut wp = full_lines * line_out;
1137
1138 if remainder > 0 {
1140 let enc_len = BASE64_ENGINE.encoded_length(remainder);
1141 let line_input = &buf[full_lines * bytes_per_line..n];
1142 unsafe {
1143 let s = std::slice::from_raw_parts_mut(dst.add(wp), enc_len);
1144 let _ = BASE64_ENGINE.encode(line_input, s.as_out());
1145 *dst.add(wp + enc_len) = b'\n';
1146 }
1147 wp += enc_len + 1;
1148 }
1149
1150 writer.write_all(&out_buf[..wp])?;
1151 }
1152
1153 Ok(())
1154}
1155
1156pub fn decode_stream(
1163 reader: &mut impl Read,
1164 ignore_garbage: bool,
1165 writer: &mut impl Write,
1166) -> io::Result<()> {
1167 const READ_CHUNK: usize = 16 * 1024 * 1024;
1168 let mut buf: Vec<u8> = Vec::with_capacity(READ_CHUNK + 4);
1171 #[allow(clippy::uninit_vec)]
1172 unsafe {
1173 buf.set_len(READ_CHUNK + 4);
1174 }
1175 let mut carry = [0u8; 4];
1176 let mut carry_len = 0usize;
1177
1178 loop {
1179 if carry_len > 0 {
1181 unsafe {
1182 std::ptr::copy_nonoverlapping(carry.as_ptr(), buf.as_mut_ptr(), carry_len);
1183 }
1184 }
1185 let n = read_full(reader, &mut buf[carry_len..carry_len + READ_CHUNK])?;
1186 if n == 0 {
1187 break;
1188 }
1189 let total_raw = carry_len + n;
1190
1191 let clean_len = if ignore_garbage {
1194 let ptr = buf.as_mut_ptr();
1196 let mut wp = 0usize;
1197 for i in 0..total_raw {
1198 let b = unsafe { *ptr.add(i) };
1199 if is_base64_char(b) {
1200 unsafe { *ptr.add(wp) = b };
1201 wp += 1;
1202 }
1203 }
1204 wp
1205 } else {
1206 let ptr = buf.as_mut_ptr();
1210 let data = &buf[..total_raw];
1211 let mut wp = 0usize;
1212 let mut gap_start = 0usize;
1213 let mut has_rare_ws = false;
1214
1215 for pos in memchr::memchr2_iter(b'\n', b'\r', data) {
1216 let gap_len = pos - gap_start;
1217 if gap_len > 0 {
1218 if !has_rare_ws {
1219 has_rare_ws = data[gap_start..pos]
1220 .iter()
1221 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
1222 }
1223 if wp != gap_start {
1224 unsafe {
1225 std::ptr::copy(ptr.add(gap_start), ptr.add(wp), gap_len);
1226 }
1227 }
1228 wp += gap_len;
1229 }
1230 gap_start = pos + 1;
1231 }
1232 let tail_len = total_raw - gap_start;
1233 if tail_len > 0 {
1234 if !has_rare_ws {
1235 has_rare_ws = data[gap_start..total_raw]
1236 .iter()
1237 .any(|&b| b == b' ' || b == b'\t' || b == 0x0b || b == 0x0c);
1238 }
1239 if wp != gap_start {
1240 unsafe {
1241 std::ptr::copy(ptr.add(gap_start), ptr.add(wp), tail_len);
1242 }
1243 }
1244 wp += tail_len;
1245 }
1246
1247 if has_rare_ws {
1249 let mut rp = 0;
1250 let mut cwp = 0;
1251 while rp < wp {
1252 let b = unsafe { *ptr.add(rp) };
1253 if NOT_WHITESPACE[b as usize] {
1254 unsafe { *ptr.add(cwp) = b };
1255 cwp += 1;
1256 }
1257 rp += 1;
1258 }
1259 cwp
1260 } else {
1261 wp
1262 }
1263 };
1264
1265 carry_len = 0;
1266 let is_last = n < READ_CHUNK;
1267
1268 if is_last {
1269 decode_clean_slice(&mut buf[..clean_len], writer)?;
1271 } else {
1272 let decode_len = (clean_len / 4) * 4;
1274 let leftover = clean_len - decode_len;
1275 if leftover > 0 {
1276 unsafe {
1277 std::ptr::copy_nonoverlapping(
1278 buf.as_ptr().add(decode_len),
1279 carry.as_mut_ptr(),
1280 leftover,
1281 );
1282 }
1283 carry_len = leftover;
1284 }
1285 if decode_len > 0 {
1286 decode_clean_slice(&mut buf[..decode_len], writer)?;
1287 }
1288 }
1289 }
1290
1291 if carry_len > 0 {
1293 let mut carry_buf = carry[..carry_len].to_vec();
1294 decode_clean_slice(&mut carry_buf, writer)?;
1295 }
1296
1297 Ok(())
1298}
1299
1300fn write_all_vectored(out: &mut impl Write, slices: &[io::IoSlice]) -> io::Result<()> {
1303 if slices.is_empty() {
1304 return Ok(());
1305 }
1306 let total: usize = slices.iter().map(|s| s.len()).sum();
1307
1308 let written = match out.write_vectored(slices) {
1310 Ok(n) if n >= total => return Ok(()),
1311 Ok(n) => n,
1312 Err(e) => return Err(e),
1313 };
1314
1315 let mut skip = written;
1317 for slice in slices {
1318 let slen = slice.len();
1319 if skip >= slen {
1320 skip -= slen;
1321 continue;
1322 }
1323 if skip > 0 {
1324 out.write_all(&slice[skip..])?;
1325 skip = 0;
1326 } else {
1327 out.write_all(slice)?;
1328 }
1329 }
1330 Ok(())
1331}
1332
1333#[inline]
1337fn read_full(reader: &mut impl Read, buf: &mut [u8]) -> io::Result<usize> {
1338 let n = reader.read(buf)?;
1340 if n == buf.len() || n == 0 {
1341 return Ok(n);
1342 }
1343 let mut total = n;
1345 while total < buf.len() {
1346 match reader.read(&mut buf[total..]) {
1347 Ok(0) => break,
1348 Ok(n) => total += n,
1349 Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
1350 Err(e) => return Err(e),
1351 }
1352 }
1353 Ok(total)
1354}