1#![cfg_attr(feature = "nightly", feature(array_chunks))]
2#![cfg_attr(feature = "simd", feature(portable_simd))]
3#![cfg_attr(all(feature = "nightly", test), feature(test))]
4#![warn(clippy::perf)]
5#![warn(clippy::pedantic)]
6#![no_std]
7
8extern crate alloc;
15
16#[cfg(feature = "simd")]
17use core::{
18 mem::transmute_copy,
19 simd::{cmp::SimdPartialOrd, num::SimdUint, *},
20 slice,
21};
22
23use alloc::{string::String, vec};
24
25pub struct Encoder {
27 encode_table: [u8; 64],
28}
29
30impl Default for Encoder {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl Encoder {
37 #[must_use]
46 pub const fn new() -> Self {
47 Self {
48 encode_table: [
49 b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N',
50 b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b',
51 b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
52 b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'0', b'1', b'2', b'3',
53 b'4', b'5', b'6', b'7', b'8', b'9', b'+', b'/',
54 ],
55 }
56 }
57
58 #[cfg(not(feature = "simd"))]
59 #[must_use]
76 pub const fn with_encode_table(encode_table: [u8; 64]) -> Self {
77 Self { encode_table }
78 }
79
80 #[rustfmt::skip]
81 #[doc(hidden)]
82 pub fn internal_encode(&self, buf: &[u8], out: &mut [u8]) {
84 #[cfg(feature = "simd")]
85 let chunks = buf.array_chunks::<12>();
86 #[cfg(feature = "simd")]
87 let out_chunks = out.array_chunks_mut::<16>();
88
89 #[cfg(all(feature = "nightly", not(feature = "simd")))]
90 let chunks = buf.array_chunks::<24>();
91 #[cfg(all(feature = "nightly", not(feature = "simd")))]
92 let out_chunks = out.array_chunks_mut::<32>();
93
94 #[cfg(not(feature = "nightly"))]
95 let chunks = buf.chunks_exact(24);
96 #[cfg(not(feature = "nightly"))]
97 let out_chunks = out.chunks_exact_mut(32);
98
99 let mut output_index = 0;
100 let rem = chunks.remainder();
101
102 #[cfg(not(feature = "simd"))]
103 {
104 chunks.zip(out_chunks).for_each(|(chunk, out)| {
105 #[cfg(not(feature = "nightly"))]
106 let out: &mut [u8; 32] = out.try_into().unwrap();
107
108 let byte_array_1 = u64::from_be_bytes([
109 0, 0, chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5],
110 ]);
111
112 let byte_array_2 = u64::from_be_bytes([
113 0, 0, chunk[6], chunk[7], chunk[8], chunk[9], chunk[10], chunk[11],
114 ]);
115
116 let byte_array_3 = u64::from_be_bytes([
117 0, 0, chunk[12], chunk[13], chunk[14], chunk[15], chunk[16], chunk[17],
118 ]);
119
120 let byte_array_4 = u64::from_be_bytes([
121 0, 0, chunk[18], chunk[19], chunk[20], chunk[21], chunk[22], chunk[23],
122 ]);
123
124 *out = [
125 self.encode_table[(byte_array_1 >> 42 & 0b0011_1111) as usize],
126 self.encode_table[(byte_array_1 >> 36 & 0b0011_1111) as usize],
127 self.encode_table[(byte_array_1 >> 30 & 0b0011_1111) as usize],
128 self.encode_table[(byte_array_1 >> 24 & 0b0011_1111) as usize],
129 self.encode_table[(byte_array_1 >> 18 & 0b0011_1111) as usize],
130 self.encode_table[(byte_array_1 >> 12 & 0b0011_1111) as usize],
131 self.encode_table[(byte_array_1 >> 6 & 0b0011_1111) as usize],
132 self.encode_table[(byte_array_1 & 0b0011_1111) as usize],
133 self.encode_table[(byte_array_2 >> 42 & 0b0011_1111) as usize],
134 self.encode_table[(byte_array_2 >> 36 & 0b0011_1111) as usize],
135 self.encode_table[(byte_array_2 >> 30 & 0b0011_1111) as usize],
136 self.encode_table[(byte_array_2 >> 24 & 0b0011_1111) as usize],
137 self.encode_table[(byte_array_2 >> 18 & 0b0011_1111) as usize],
138 self.encode_table[(byte_array_2 >> 12 & 0b0011_1111) as usize],
139 self.encode_table[(byte_array_2 >> 6 & 0b0011_1111) as usize],
140 self.encode_table[(byte_array_2 & 0b0011_1111) as usize],
141 self.encode_table[(byte_array_3 >> 42 & 0b0011_1111) as usize],
142 self.encode_table[(byte_array_3 >> 36 & 0b0011_1111) as usize],
143 self.encode_table[(byte_array_3 >> 30 & 0b0011_1111) as usize],
144 self.encode_table[(byte_array_3 >> 24 & 0b0011_1111) as usize],
145 self.encode_table[(byte_array_3 >> 18 & 0b0011_1111) as usize],
146 self.encode_table[(byte_array_3 >> 12 & 0b0011_1111) as usize],
147 self.encode_table[(byte_array_3 >> 6 & 0b0011_1111) as usize],
148 self.encode_table[(byte_array_3 & 0b0011_1111) as usize],
149 self.encode_table[(byte_array_4 >> 42 & 0b0011_1111) as usize],
150 self.encode_table[(byte_array_4 >> 36 & 0b0011_1111) as usize],
151 self.encode_table[(byte_array_4 >> 30 & 0b0011_1111) as usize],
152 self.encode_table[(byte_array_4 >> 24 & 0b0011_1111) as usize],
153 self.encode_table[(byte_array_4 >> 18 & 0b0011_1111) as usize],
154 self.encode_table[(byte_array_4 >> 12 & 0b0011_1111) as usize],
155 self.encode_table[(byte_array_4 >> 6 & 0b0011_1111) as usize],
156 self.encode_table[(byte_array_4 & 0b0011_1111) as usize],
157 ];
158
159 output_index += 32;
160 });
161 }
162
163 #[cfg(feature = "simd")]
164 chunks.zip(out_chunks).for_each(|(chunk, out)| {
165 use crate::simd::*;
166
167 let buf = unsafe { slice::from_raw_parts(chunk.as_ptr(), 16) };
169 let vec: Simd<u8, 16> = Simd::from_slice(buf);
170
171 let indices = unpack_with_bswap(vec);
172
173 let chars = enc_translate(indices);
174
175 *out = chars.to_array();
176
177 output_index += 16;
178 });
179
180 let rem_out = &mut out[output_index..];
181
182 #[cfg(feature = "nightly")]
183 let chunks = rem.array_chunks::<3>();
184 #[cfg(feature = "nightly")]
185 let out_chunks = rem_out.array_chunks_mut::<4>();
186
187 #[cfg(not(feature = "nightly"))]
188 let chunks = rem.chunks_exact(3);
189 #[cfg(not(feature = "nightly"))]
190 let out_chunks = rem_out.chunks_exact_mut(4);
191
192 let rem = chunks.remainder();
193
194 chunks.zip(out_chunks).for_each(|(chunk, out)| {
195 #[cfg(not(feature = "nightly"))]
196 let out: &mut [u8; 4] = out.try_into().unwrap();
197
198 let byte_array = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], 0]);
199 let bit_1 = byte_array >> 26 & 0b0011_1111;
200 let bit_2 = byte_array >> 20 & 0b0011_1111;
201 let bit_3 = byte_array >> 14 & 0b0011_1111;
202 let bit_4 = byte_array >> 8 & 0b0011_1111;
203
204 *out = [
205 self.encode_table[bit_1 as usize],
206 self.encode_table[bit_2 as usize],
207 self.encode_table[bit_3 as usize],
208 self.encode_table[bit_4 as usize],
209 ];
210
211 output_index += 4;
212 });
213
214 let rem_out = &mut out[output_index..];
215
216 #[cfg(feature = "nightly")]
217 let chunks = rem.array_chunks::<2>();
218 #[cfg(feature = "nightly")]
219 let out_chunks = rem_out.array_chunks_mut::<3>();
220
221 #[cfg(not(feature = "nightly"))]
222 let chunks = rem.chunks_exact(2);
223 #[cfg(not(feature = "nightly"))]
224 let out_chunks = rem_out.chunks_exact_mut(3);
225
226 let rem = chunks.remainder();
227
228 chunks.zip(out_chunks).for_each(|(chunk, out)| {
229 #[cfg(not(feature = "nightly"))]
230 let out: &mut [u8; 3] = out.try_into().unwrap();
231
232 let byte_array = u16::from_be_bytes([chunk[0], chunk[1]]);
233 let bit_1 = byte_array >> 10 & 0b0011_1111;
234 let bit_2 = byte_array >> 4 & 0b0011_1111;
235 let bit_3 = byte_array << 2 & 0b0011_1111;
236
237 *out = [
238 self.encode_table[bit_1 as usize],
239 self.encode_table[bit_2 as usize],
240 self.encode_table[bit_3 as usize],
241 ];
242
243 output_index += 3;
244 });
245
246
247 let rem_out = &mut out[output_index..];
248
249 #[cfg(feature = "nightly")]
250 let chunks = rem.array_chunks::<1>();
251 #[cfg(feature = "nightly")]
252 let out_chunks = rem_out.array_chunks_mut::<2>();
253
254 #[cfg(not(feature = "nightly"))]
255 let chunks = rem.chunks_exact(1);
256 #[cfg(not(feature = "nightly"))]
257 let out_chunks = rem_out.chunks_exact_mut(2);
258
259 chunks.zip(out_chunks).for_each(|(chunk, out)| {
260 #[cfg(not(feature = "nightly"))]
261 let out: &mut [u8; 2] = out.try_into().unwrap();
262
263 let byte = chunk[0];
264 let bit_1 = byte >> 2;
265 let bit_2 = (byte & 0b0000_0011) << 4;
266
267 *out = [
268 self.encode_table[bit_1 as usize],
269 self.encode_table[bit_2 as usize],
270 ];
271
272 output_index += 2;
273 });
274 }
275
276 pub fn encode<T>(&self, bytes: T) -> String
290 where
291 T: AsRef<[u8]>,
292 {
293 let buf = bytes.as_ref();
294 let mut out = vec![b'='; div_ceil(buf.len(), 3) * 4];
295 self.internal_encode(buf, &mut out);
296
297 String::from_utf8(out).unwrap()
298 }
299
300 pub fn encode_with_ascii_check<T>(&self, bytes: T) -> String
315 where
316 T: AsRef<[u8]>,
317 {
318 let buf = bytes.as_ref();
319 let mut out = vec![b'='; div_ceil(buf.len(), 3) * 4];
320 self.internal_encode(buf, &mut out);
321
322 assert!(is_ascii(&out));
323
324 unsafe { String::from_utf8_unchecked(out) }
327 }
328
329 pub unsafe fn encode_unchecked<T>(&self, bytes: T) -> String
345 where
346 T: AsRef<[u8]>,
347 {
348 let buf = bytes.as_ref();
349 let mut out = vec![b'='; div_ceil(buf.len(), 3) * 4];
350 self.internal_encode(buf, &mut out);
351
352 String::from_utf8_unchecked(out)
353 }
354
355 pub fn encode_without_padding<T>(&self, bytes: T) -> String
369 where
370 T: AsRef<[u8]>,
371 {
372 let buf = bytes.as_ref();
373 let mut out = vec![0; div_ceil(buf.len(), 3) * 4];
374 self.internal_encode(buf, &mut out);
375
376 String::from_utf8(out).unwrap()
377 }
378
379 pub unsafe fn encode_unchecked_without_padding<T>(&self, bytes: T) -> String
395 where
396 T: AsRef<[u8]>,
397 {
398 let buf = bytes.as_ref();
399 let mut out = vec![0; div_ceil(buf.len(), 3) * 4];
400 self.internal_encode(buf, &mut out);
401
402 String::from_utf8_unchecked(out)
403 }
404}
405
406#[cfg(feature = "simd")]
438mod simd {
439 use crate::swizzle_dyn;
440 use core::mem::transmute;
441 use core::simd::cmp::*;
442 use core::simd::num::*;
443 use core::simd::*;
444
445 pub fn unpack_with_bswap(input: u8x16) -> u8x16 {
446 let in_u8 = simd_swizzle!(input, [1, 0, 2, 1, 4, 3, 5, 4, 7, 6, 8, 7, 10, 9, 11, 10,]);
447
448 unsafe {
450 let in_u32: u32x4 = transmute(in_u8);
451
452 let t0 = in_u32 & u32x4::splat(0x0fc0fc00);
453 let t0_u16 = transmute::<_, u16x8>(t0);
454
455 let t1 = simd_swizzle!(
456 t0_u16 >> Simd::splat(10),
457 t0_u16 >> Simd::splat(6),
458 [
459 0, 9, 2, 11, 4, 13, 6, 15, ]
468 );
469
470 let t2 = in_u32 & u32x4::splat(0x003f03f0);
471
472 let t3 = transmute::<_, u16x8>(t2)
473 * u16x8::from_array([
474 0x0010, 0x0100, 0x0010, 0x0100, 0x0010, 0x0100, 0x0010, 0x0100,
475 ]);
476
477 transmute(t1 | t3)
478 }
479 }
480
481 pub(crate) fn enc_translate(input: u8x16) -> u8x16 {
482 let lut: Simd<i8, 16> = Simd::from_array([
483 65, 71, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -19, -16, 0, 0,
484 ]);
485 let indices = input.saturating_sub(Simd::splat(51));
486 let mask = input.simd_gt(Simd::splat(25)).to_int().cast::<u8>();
487
488 let indices = indices - mask;
489
490 let out = input.cast::<i8>() + swizzle_dyn(lut.cast::<u8>(), indices).cast::<i8>();
491
492 out.cast::<u8>()
493 }
494}
495
496#[cfg(feature = "simd")]
497fn is_ascii(buffer: &[u8]) -> bool {
498 let mut mask: Simd<i8, 16> = Simd::splat(0);
499 let spans = buffer.array_chunks::<256>();
500 let rem = spans.remainder();
501
502 let mut has_error: Mask<i8, 16>;
503
504 for span in spans {
505 let chunks = span.array_chunks::<16>();
506
507 for chunk in chunks {
508 let current_bytes = Simd::from_slice(chunk);
509 mask |= current_bytes.cast();
510 }
511
512 has_error = mask.simd_lt(Simd::splat(0));
513
514 if has_error.any() {
515 return false;
516 }
517 }
518
519 let chunks = rem.array_chunks::<16>();
520 let rem = chunks.remainder();
521
522 for chunk in chunks {
523 let current_bytes = Simd::from_slice(chunk);
524 mask |= current_bytes.cast();
525 }
526
527 has_error = mask.simd_lt(Simd::splat(0));
528
529 if has_error.any() {
530 return false;
531 }
532
533 for x in rem {
534 if x > &127 {
535 return false;
536 }
537 }
538
539 true
540}
541
542#[cfg(not(feature = "simd"))]
543fn is_ascii(buffer: &[u8]) -> bool {
544 #[cfg(feature = "nightly")]
545 let spans = buffer.array_chunks::<256>();
546 #[cfg(not(feature = "nightly"))]
547 let spans = buffer.chunks_exact(256);
548 let rem = spans.remainder();
549 for span in spans {
550 if span > &[127; 256] {
551 return false;
552 }
553 }
554
555 for x in rem {
556 if x > &127 {
557 return false;
558 }
559 }
560
561 true
562}
563
564#[inline]
565const fn div_ceil(divisor: usize, dividend: usize) -> usize {
566 let quotient = divisor / dividend;
567 let remainder = divisor % dividend;
568
569 if remainder > 0 {
570 quotient + 1
571 } else {
572 quotient
573 }
574}
575
576#[cfg(feature = "simd")]
585#[inline]
586fn swizzle_dyn<const N: usize>(val: Simd<u8, N>, idxs: Simd<u8, N>) -> Simd<u8, N>
587where
588 LaneCount<N>: SupportedLaneCount,
589{
590 #![allow(unused_imports, unused_unsafe)]
591 #[cfg(all(target_arch = "aarch64", target_endian = "little"))]
592 use core::arch::aarch64::{uint8x8_t, vqtbl1q_u8, vtbl1_u8};
593 #[cfg(all(target_arch = "arm", target_feature = "v7", target_endian = "little"))]
594 use core::arch::arm::{uint8x8_t, vtbl1_u8};
595 #[cfg(target_arch = "wasm32")]
596 use core::arch::wasm32 as wasm;
597 #[cfg(target_arch = "x86")]
598 use core::arch::x86;
599 #[cfg(target_arch = "x86_64")]
600 use core::arch::x86_64 as x86;
601 unsafe {
603 #[cfg(target_feature = "ssse3")]
604 return transize(x86::_mm_shuffle_epi8, val, idxs);
605 #[cfg(target_feature = "simd128")]
606 return transize(wasm::i8x16_swizzle, val, idxs);
607 #[cfg(all(
608 target_arch = "aarch64",
609 target_feature = "neon",
610 target_endian = "little"
611 ))]
612 return transize(vqtbl1q_u8, val, idxs);
613 }
614}
615
616#[cfg(feature = "simd")]
624#[allow(dead_code)]
625#[inline(always)]
626unsafe fn transize<T, const N: usize>(
627 f: unsafe fn(T, T) -> T,
628 bytes: Simd<u8, N>,
629 idxs: Simd<u8, N>,
630) -> Simd<u8, N>
631where
632 LaneCount<N>: SupportedLaneCount,
633{
634 let idxs = zeroing_idxs(idxs);
635 unsafe { transmute_copy(&f(transmute_copy(&bytes), transmute_copy(&idxs))) }
637}
638
639#[cfg(feature = "simd")]
641#[inline(always)]
642fn zeroing_idxs<const N: usize>(idxs: Simd<u8, N>) -> Simd<u8, N>
643where
644 LaneCount<N>: SupportedLaneCount,
645{
646 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
648 let idxs = {
649 idxs.simd_lt(Simd::splat(N as u8))
650 .select(idxs, Simd::splat(u8::MAX))
651 };
652 idxs
654}
655
656#[cfg(all(test, not(feature = "nightly")))]
657#[test]
658fn b64_validity_check() {
659 use base64::{engine::general_purpose, Engine};
660 use rand::distributions::{Alphanumeric, DistString};
661 use rand::thread_rng;
662
663 for _ in 0..100_000 {
664 let mut rng = thread_rng();
665 let string = Alphanumeric.sample_string(&mut rng, 34);
666 let b64_encoded = general_purpose::STANDARD.encode(string.clone());
667 let encoder = Encoder::new();
668 let my_encoded = encoder.encode(string);
669
670 assert_eq!(my_encoded, b64_encoded);
671 }
672}
673
674#[cfg(all(test, feature = "nightly"))]
675mod test {
719 extern crate test;
720
721 use alloc::{string::String, vec};
722 use test::{black_box, Bencher};
723
724 use base64::{engine::general_purpose, Engine};
725 use rand::{
726 distributions::{Alphanumeric, DistString},
727 thread_rng,
728 };
729
730 use super::Encoder;
731
732 #[test]
733 fn b64_validity_check() {
734 for _ in 0..100000 {
735 let mut rng = thread_rng();
736 let string = Alphanumeric.sample_string(&mut rng, 34);
737 let b64_encoded = general_purpose::STANDARD.encode(string.clone());
738 let encoder = Encoder::new();
739 let my_encoded = encoder.encode(string);
740
741 if !my_encoded.eq(&b64_encoded) {
742 panic!("{my_encoded:?} != {b64_encoded:?}")
743 }
744 }
745 }
746
747 #[bench]
748 fn irelia_encoder(b: &mut Bencher) {
749 let mut strings = vec![String::new(); 10000];
750
751 let mut rng = black_box(thread_rng());
752
753 for x in 0..10000 {
754 strings[x] = black_box(Alphanumeric.sample_string(&mut rng, 1924));
755 }
756
757 let encoder = Encoder::new();
758
759 b.iter(|| {
760 black_box(for x in 0..10000 {
761 black_box(encoder.encode(&strings[x]));
762 });
763 })
764 }
765
766 #[bench]
767 fn irelia_encoder_ascii_check(b: &mut Bencher) {
768 let mut strings = vec![String::new(); 10000];
769
770 let mut rng = black_box(thread_rng());
771
772 for x in 0..10000 {
773 strings[x] = black_box(Alphanumeric.sample_string(&mut rng, 1924));
774 }
775
776 let encoder = Encoder::new();
777
778 b.iter(|| {
779 black_box({
780 for x in 0..10000 {
781 black_box(encoder.encode_with_ascii_check(&strings[x]));
782 }
783 });
784 })
785 }
786
787 #[bench]
788 fn irelia_encoder_unchecked(b: &mut Bencher) {
789 let mut strings = vec![String::new(); 10000];
790
791 let mut rng = black_box(thread_rng());
792
793 for x in 0..10000 {
794 strings[x] = black_box(Alphanumeric.sample_string(&mut rng, 1924));
795 }
796
797 let encoder = Encoder::new();
798
799 b.iter(|| {
800 black_box({
801 for x in 0..10000 {
802 black_box(unsafe { encoder.encode_unchecked(&strings[x]) });
803 }
804 });
805 })
806 }
807
808 #[bench]
809 fn base64_crate(b: &mut Bencher) {
810 let mut strings = vec![String::new(); 10000];
811
812 let mut rng = black_box(thread_rng());
813
814 for x in 0..10000 {
815 strings[x] = black_box(Alphanumeric.sample_string(&mut rng, 1924));
816 }
817
818 b.iter(|| {
819 black_box({
820 for x in 0..10000 {
821 general_purpose::STANDARD.encode(&strings[x]);
822 }
823 });
824 })
825 }
826}