based64/
raw.rs

1//! Low level functions
2
3use core::mem;
4use core::ptr::NonNull;
5use super::{PAD, encode_len, decode_len, REVERSE_TABLE_SIZE, build_reverse_table, Codec};
6
7#[cold]
8#[inline(never)]
9fn unlikely_false() -> bool {
10    false
11}
12
13pub(crate) fn encode_inner(table: &[u8; 64], src: &[u8], dst: NonNull<u8>, len: &mut usize) {
14    let mut it = src.as_ptr();
15    let mut cursor = dst.as_ptr();
16    let mut remain_len = src.len();
17    macro_rules! encode_it {
18        () => {
19            //right now it is just memcpy, but it generates a bit less code
20            (cursor as *mut u32).write_unaligned(u32::from_ne_bytes([
21                *table.as_ptr().add(
22                    (*it).wrapping_shr(2) as usize
23                ),
24                *table.as_ptr().add(
25                    (((*it) & 0x03).wrapping_shl(4) | (*it.add(1)).wrapping_shr(4)) as usize
26                ),
27                *table.as_ptr().add(
28                    (((*it.add(1)) & 0x0f).wrapping_shl(2) | (*it.add(2)).wrapping_shr(6)) as usize
29                ),
30                *table.as_ptr().add(
31                    ((*it.add(2)) & 0x3f) as usize
32                )
33            ]));
34            cursor = cursor.add(mem::size_of::<u32>());
35            it = it.add(3);
36        }
37    }
38
39    const CHUNK_SIZE: usize = 3;
40    const EIGHT_CHUNKS: usize = CHUNK_SIZE * 8;
41    const FOUR_CHUNKS: usize = CHUNK_SIZE * 4;
42
43    while remain_len >= EIGHT_CHUNKS {
44        unsafe {
45            encode_it!();
46            encode_it!();
47            encode_it!();
48            encode_it!();
49            encode_it!();
50            encode_it!();
51            encode_it!();
52            encode_it!();
53        }
54
55        remain_len -= EIGHT_CHUNKS;
56    }
57
58    while remain_len >= FOUR_CHUNKS {
59        unsafe {
60            encode_it!();
61            encode_it!();
62            encode_it!();
63            encode_it!();
64        }
65
66        remain_len -= FOUR_CHUNKS;
67    }
68
69    while remain_len >= CHUNK_SIZE {
70        unsafe {
71            encode_it!();
72        }
73
74        remain_len -= CHUNK_SIZE;
75    }
76
77    match remain_len {
78        1 => unsafe {
79            (cursor as *mut u32).write_unaligned(u32::from_ne_bytes([
80                *table.as_ptr().add(
81                    (*it).wrapping_shr(2) as usize
82                ),
83                *table.as_ptr().add(
84                    ((*it) & 0x03).wrapping_shl(4) as usize
85                ),
86                PAD,
87                PAD
88            ]));
89            cursor = cursor.add(mem::size_of::<u32>());
90        },
91        2 => unsafe {
92            (cursor as *mut u32).write_unaligned(u32::from_ne_bytes([
93                *table.as_ptr().add(
94                    (*it).wrapping_shr(2) as usize
95                ),
96                *table.as_ptr().add(
97                    (((*it) & 0x03).wrapping_shl(4) | (*it.add(1)).wrapping_shr(4)) as usize
98                ),
99                *table.as_ptr().add(
100                    ((*it.add(1)) & 0x0f).wrapping_shl(2) as usize
101                ),
102                PAD
103            ]));
104            cursor = cursor.add(mem::size_of::<u32>());
105        },
106        _ => debug_assert_eq!(remain_len, 0),
107    }
108
109    *len = cursor as usize - dst.as_ptr() as usize;
110}
111
112#[inline]
113///Raw encoding function.
114///
115///# Arguments
116///
117///- `src` - Input to encode;
118///- `dst` - Output to write;
119///- `len` - Output length, modified with required size regardless of outcome, unless calculation wrapping happens.
120///
121///# Result
122///Returns `true` on success.
123///
124///Returns `false` if buffer overflow would to happen or required_len is too big.
125pub unsafe fn encode(table: &[u8; 64], src: &[u8], dst: NonNull<u8>, len: &mut usize) -> bool {
126    let required_len = encode_len(src.len());
127    if required_len < src.len() {
128        //bro, how likely is overflow?
129        return unlikely_false();
130    } else if required_len > *len {
131        *len = required_len;
132        return false;
133    }
134
135    encode_inner(table, src, dst, len);
136    true
137}
138
139pub(crate) fn decode_inner_with_rev(reverse_table: &[i8; REVERSE_TABLE_SIZE], mut src: &[u8], dst: NonNull<u8>, len: &mut usize) -> bool {
140    let mut prev_ch;
141    let mut cursor = dst.as_ptr();
142
143    'main: loop {
144        macro_rules! store_ch {
145            (last: $ch:expr) => {};
146            (gogo: $ch:expr) => {
147                prev_ch = $ch;
148            }
149        }
150
151        macro_rules! transform_valid_base64 {
152            ($kind:ident => $ch:expr => 0) => {{
153                prev_ch = $ch;
154            }};
155            ($kind:ident => $ch:expr => 1) => {{
156                *cursor = (prev_ch).wrapping_shl(2) | ($ch).wrapping_shr(4);
157                store_ch!($kind: $ch);
158            }};
159            ($kind:ident => $ch:expr => 2) => {{
160                *(cursor.add(1)) = (prev_ch).wrapping_shl(4) | ($ch).wrapping_shr(2);
161                store_ch!($kind: $ch);
162            }};
163            ($kind:ident => $ch:expr => 3) => {{
164                *(cursor.add(2)) = prev_ch.wrapping_shl(6) | ($ch);
165            }}
166        }
167
168        macro_rules! get_base64_byte {
169            ($kind:ident => $src:ident[$offset:literal + $idx:tt]) => {{
170                match *$src.as_ptr().add($offset + $idx) {
171                    PAD => {
172                        cursor = cursor.add(usize::saturating_sub($idx, 1));
173                        break 'main;
174                    },
175                    ch => match *reverse_table.as_ptr().add(ch as usize) {
176                        -1 => return unlikely_false(),
177                        pos => transform_valid_base64!($kind => pos as u8 => $idx)
178                    },
179                }
180            }}
181        }
182
183        macro_rules! decode_it {
184            ($offset:literal) => {{
185                get_base64_byte!(gogo => src[$offset + 0]);
186                get_base64_byte!(gogo => src[$offset + 1]);
187                get_base64_byte!(gogo => src[$offset + 2]);
188                get_base64_byte!(gogo => src[$offset + 3]);
189                cursor = cursor.add(3);
190            }}
191        }
192
193        match src.len() {
194            0 => {
195                break;
196            },
197            1 => {
198                //this is technically an error case
199                break;
200            },
201            2 => unsafe {
202                get_base64_byte!(gogo => src[0 + 0]);
203                get_base64_byte!(last => src[0 + 1]);
204                cursor = cursor.add(1);
205                break;
206            },
207            3 => unsafe {
208                get_base64_byte!(gogo => src[0 + 0]);
209                get_base64_byte!(gogo => src[0 + 1]);
210                get_base64_byte!(last => src[0 + 2]);
211                cursor = cursor.add(2);
212                break;
213            },
214            _ => {
215                const CHUNK_SIZE: usize = 4;
216                const EIGHT_CHUNKS: usize = CHUNK_SIZE * 8;
217                const FOUR_CHUNKS: usize = CHUNK_SIZE * 4;
218
219                while src.len() >= EIGHT_CHUNKS {
220                    unsafe {
221                        decode_it!(0);
222                        decode_it!(4);
223                        decode_it!(8);
224                        decode_it!(12);
225                        decode_it!(16);
226                        decode_it!(20);
227                        decode_it!(24);
228                        decode_it!(28);
229                    }
230                    src = &src[EIGHT_CHUNKS..];
231                }
232
233                while src.len() >= FOUR_CHUNKS {
234                    unsafe {
235                        decode_it!(0);
236                        decode_it!(4);
237                        decode_it!(8);
238                        decode_it!(12);
239                    }
240                    src = &src[FOUR_CHUNKS..];
241                }
242
243                while src.len() >= CHUNK_SIZE {
244                    unsafe {
245                        decode_it!(0);
246                    }
247                    src = &src[CHUNK_SIZE..];
248                }
249            }
250        }
251    }
252
253    *len = cursor as usize - dst.as_ptr() as usize;
254    true
255
256}
257
258#[inline(always)]
259pub(crate) fn decode_inner(table: &[u8; 64], src: &[u8], dst: NonNull<u8>, len: &mut usize) -> bool {
260    decode_inner_with_rev(&build_reverse_table(table), src, dst, len)
261}
262
263#[inline]
264///Raw decoding function.
265///
266///# Arguments
267///- `src` - Input to decode;
268///- `dst` - Output to write;
269///- `len` - Output length, modified with required size regardless of outcome.
270///
271///# Result
272///Returns `true` on success.
273///
274///Returns `false` if buffer overflow would to happen or `src` is empty or invalid base64.
275pub unsafe fn decode(table: &[u8; 64], src: &[u8], dst: NonNull<u8>, len: &mut usize) -> bool {
276    let required_len = decode_len(src);
277
278    if required_len == 0 {
279        *len = 0;
280        return true;
281    }
282
283    decode_inner(table, src, dst, len)
284}
285
286impl<'a> Codec<'a> {
287    #[inline(always)]
288    ///Raw encoding function.
289    ///
290    ///# Arguments
291    ///
292    ///- `src` - Input to encode;
293    ///- `dst` - Output to write;
294    ///- `len` - Output length, modified with required size regardless of outcome, unless calculation wrapping happens.
295    ///
296    ///# Result
297    ///Returns `true` on success.
298    ///
299    ///Returns `false` if buffer overflow would to happen or required_len is too big.
300    pub unsafe fn encode_to_raw(&self, src: &[u8], dst: NonNull<u8>, len: &mut usize) -> bool {
301        encode(self.table, src, dst, len)
302    }
303
304    #[inline]
305    ///Raw decoding function.
306    ///
307    ///# Arguments
308    ///- `src` - Input to decode;
309    ///- `dst` - Output to write;
310    ///- `len` - Output length, modified with required size regardless of outcome.
311    ///
312    ///# Result
313    ///Returns `true` on success.
314    ///
315    ///Returns `false` if buffer overflow would to happen or `src` is empty or invalid base64.
316    pub unsafe fn decode_to_raw(&self, src: &[u8], dst: NonNull<u8>, len: &mut usize) -> bool {
317        let required_len = decode_len(src);
318
319        if required_len == 0 {
320            *len = 0;
321            return true;
322        }
323
324        decode_inner_with_rev(&self.reverse, src, dst, len)
325    }
326}