2 #[cfg(any(feature = "alloc", feature = "std", test))]
3 use crate::{chunked_encoder, STANDARD}
;
4 #[cfg(any(feature = "alloc", feature = "std", test))]
5 use alloc
::{string::String, vec}
;
6 use core
::convert
::TryInto
;
8 ///Encode arbitrary octets as base64.
10 ///Convenience for `encode_config(input, base64::STANDARD);`.
15 ///extern crate base64;
18 /// let b64 = base64::encode(b"hello world");
19 /// println!("{}", b64);
22 #[cfg(any(feature = "alloc", feature = "std", test))]
23 pub fn encode
<T
: ?Sized
+ AsRef
<[u8]>>(input
: &T
) -> String
{
24 encode_config(input
, STANDARD
)
27 ///Encode arbitrary octets as base64.
33 ///extern crate base64;
36 /// let b64 = base64::encode_config(b"hello world~", base64::STANDARD);
37 /// println!("{}", b64);
39 /// let b64_url = base64::encode_config(b"hello internet~", base64::URL_SAFE);
40 /// println!("{}", b64_url);
43 #[cfg(any(feature = "alloc", feature = "std", test))]
44 pub fn encode_config
<T
: ?Sized
+ AsRef
<[u8]>>(input
: &T
, config
: Config
) -> String
{
45 let mut buf
= match encoded_size(input
.as_ref().len(), config
) {
46 Some(n
) => vec
![0; n
],
47 None
=> panic
!("integer overflow when calculating buffer size"),
50 let encoded_len
= encode_config_slice(input
.as_ref(), config
, &mut buf
[..]);
51 debug_assert_eq
!(encoded_len
, buf
.len());
53 String
::from_utf8(buf
).expect("Invalid UTF8")
56 ///Encode arbitrary octets as base64.
57 ///Writes into the supplied output buffer, which will grow the buffer if needed.
62 ///extern crate base64;
65 /// let mut buf = String::new();
66 /// base64::encode_config_buf(b"hello world~", base64::STANDARD, &mut buf);
67 /// println!("{}", buf);
70 /// base64::encode_config_buf(b"hello internet~", base64::URL_SAFE, &mut buf);
71 /// println!("{}", buf);
74 #[cfg(any(feature = "alloc", feature = "std", test))]
75 pub fn encode_config_buf
<T
: ?Sized
+ AsRef
<[u8]>>(input
: &T
, config
: Config
, buf
: &mut String
) {
76 let input_bytes
= input
.as_ref();
79 let mut sink
= chunked_encoder
::StringSink
::new(buf
);
80 let encoder
= chunked_encoder
::ChunkedEncoder
::new(config
);
83 .encode(input_bytes
, &mut sink
)
84 .expect("Writing to a String shouldn't fail")
88 /// Encode arbitrary octets as base64.
89 /// Writes into the supplied output buffer.
91 /// This is useful if you wish to avoid allocation entirely (e.g. encoding into a stack-resident
92 /// or statically-allocated buffer).
96 /// If `output` is too small to hold the encoded version of `input`, a panic will result.
101 /// extern crate base64;
104 /// let s = b"hello internet!";
105 /// let mut buf = Vec::new();
106 /// // make sure we'll have a slice big enough for base64 + padding
107 /// buf.resize(s.len() * 4 / 3 + 4, 0);
109 /// let bytes_written = base64::encode_config_slice(s,
110 /// base64::STANDARD, &mut buf);
112 /// // shorten our vec down to just what was written
113 /// buf.resize(bytes_written, 0);
115 /// assert_eq!(s, base64::decode(&buf).unwrap().as_slice());
118 pub fn encode_config_slice
<T
: ?Sized
+ AsRef
<[u8]>>(
123 let input_bytes
= input
.as_ref();
125 let encoded_size
= encoded_size(input_bytes
.len(), config
)
126 .expect("usize overflow when calculating buffer size");
128 let mut b64_output
= &mut output
[0..encoded_size
];
130 encode_with_padding(&input_bytes
, config
, encoded_size
, &mut b64_output
);
135 /// B64-encode and pad (if configured).
137 /// This helper exists to avoid recalculating encoded_size, which is relatively expensive on short
140 /// `encoded_size` is the encoded size calculated for `input`.
142 /// `output` must be of size `encoded_size`.
144 /// All bytes in `output` will be written to since it is exactly the size of the output.
145 fn encode_with_padding(input
: &[u8], config
: Config
, encoded_size
: usize, output
: &mut [u8]) {
146 debug_assert_eq
!(encoded_size
, output
.len());
148 let b64_bytes_written
= encode_to_slice(input
, output
, config
.char_set
.encode_table());
150 let padding_bytes
= if config
.pad
{
151 add_padding(input
.len(), &mut output
[b64_bytes_written
..])
156 let encoded_bytes
= b64_bytes_written
157 .checked_add(padding_bytes
)
158 .expect("usize overflow when calculating b64 length");
160 debug_assert_eq
!(encoded_size
, encoded_bytes
);
164 fn read_u64(s
: &[u8]) -> u64 {
165 u64::from_be_bytes(s
[..8].try_into().unwrap())
168 /// Encode input bytes to utf8 base64 bytes. Does not pad.
169 /// `output` must be long enough to hold the encoded `input` without padding.
170 /// Returns the number of bytes written.
172 pub fn encode_to_slice(input
: &[u8], output
: &mut [u8], encode_table
: &[u8; 64]) -> usize {
173 let mut input_index
: usize = 0;
175 const BLOCKS_PER_FAST_LOOP
: usize = 4;
176 const LOW_SIX_BITS
: u64 = 0x3F;
178 // we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need
179 // 2 trailing bytes to be available to read..
180 let last_fast_index
= input
.len().saturating_sub(BLOCKS_PER_FAST_LOOP
* 6 + 2);
181 let mut output_index
= 0;
183 if last_fast_index
> 0 {
184 while input_index
<= last_fast_index
{
185 // Major performance wins from letting the optimizer do the bounds check once, mostly
186 // on the output side
187 let input_chunk
= &input
[input_index
..(input_index
+ (BLOCKS_PER_FAST_LOOP
* 6 + 2))];
188 let output_chunk
= &mut output
[output_index
..(output_index
+ BLOCKS_PER_FAST_LOOP
* 8)];
190 // Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent
191 // to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for
192 // large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect
193 // that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte
194 // SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once.
195 // Plus, single-digit percentage performance differences might well be quite different
196 // on different hardware.
198 let input_u64
= read_u64(&input_chunk
[0..]);
200 output_chunk
[0] = encode_table
[((input_u64
>> 58) & LOW_SIX_BITS
) as usize];
201 output_chunk
[1] = encode_table
[((input_u64
>> 52) & LOW_SIX_BITS
) as usize];
202 output_chunk
[2] = encode_table
[((input_u64
>> 46) & LOW_SIX_BITS
) as usize];
203 output_chunk
[3] = encode_table
[((input_u64
>> 40) & LOW_SIX_BITS
) as usize];
204 output_chunk
[4] = encode_table
[((input_u64
>> 34) & LOW_SIX_BITS
) as usize];
205 output_chunk
[5] = encode_table
[((input_u64
>> 28) & LOW_SIX_BITS
) as usize];
206 output_chunk
[6] = encode_table
[((input_u64
>> 22) & LOW_SIX_BITS
) as usize];
207 output_chunk
[7] = encode_table
[((input_u64
>> 16) & LOW_SIX_BITS
) as usize];
209 let input_u64
= read_u64(&input_chunk
[6..]);
211 output_chunk
[8] = encode_table
[((input_u64
>> 58) & LOW_SIX_BITS
) as usize];
212 output_chunk
[9] = encode_table
[((input_u64
>> 52) & LOW_SIX_BITS
) as usize];
213 output_chunk
[10] = encode_table
[((input_u64
>> 46) & LOW_SIX_BITS
) as usize];
214 output_chunk
[11] = encode_table
[((input_u64
>> 40) & LOW_SIX_BITS
) as usize];
215 output_chunk
[12] = encode_table
[((input_u64
>> 34) & LOW_SIX_BITS
) as usize];
216 output_chunk
[13] = encode_table
[((input_u64
>> 28) & LOW_SIX_BITS
) as usize];
217 output_chunk
[14] = encode_table
[((input_u64
>> 22) & LOW_SIX_BITS
) as usize];
218 output_chunk
[15] = encode_table
[((input_u64
>> 16) & LOW_SIX_BITS
) as usize];
220 let input_u64
= read_u64(&input_chunk
[12..]);
222 output_chunk
[16] = encode_table
[((input_u64
>> 58) & LOW_SIX_BITS
) as usize];
223 output_chunk
[17] = encode_table
[((input_u64
>> 52) & LOW_SIX_BITS
) as usize];
224 output_chunk
[18] = encode_table
[((input_u64
>> 46) & LOW_SIX_BITS
) as usize];
225 output_chunk
[19] = encode_table
[((input_u64
>> 40) & LOW_SIX_BITS
) as usize];
226 output_chunk
[20] = encode_table
[((input_u64
>> 34) & LOW_SIX_BITS
) as usize];
227 output_chunk
[21] = encode_table
[((input_u64
>> 28) & LOW_SIX_BITS
) as usize];
228 output_chunk
[22] = encode_table
[((input_u64
>> 22) & LOW_SIX_BITS
) as usize];
229 output_chunk
[23] = encode_table
[((input_u64
>> 16) & LOW_SIX_BITS
) as usize];
231 let input_u64
= read_u64(&input_chunk
[18..]);
233 output_chunk
[24] = encode_table
[((input_u64
>> 58) & LOW_SIX_BITS
) as usize];
234 output_chunk
[25] = encode_table
[((input_u64
>> 52) & LOW_SIX_BITS
) as usize];
235 output_chunk
[26] = encode_table
[((input_u64
>> 46) & LOW_SIX_BITS
) as usize];
236 output_chunk
[27] = encode_table
[((input_u64
>> 40) & LOW_SIX_BITS
) as usize];
237 output_chunk
[28] = encode_table
[((input_u64
>> 34) & LOW_SIX_BITS
) as usize];
238 output_chunk
[29] = encode_table
[((input_u64
>> 28) & LOW_SIX_BITS
) as usize];
239 output_chunk
[30] = encode_table
[((input_u64
>> 22) & LOW_SIX_BITS
) as usize];
240 output_chunk
[31] = encode_table
[((input_u64
>> 16) & LOW_SIX_BITS
) as usize];
242 output_index
+= BLOCKS_PER_FAST_LOOP
* 8;
243 input_index
+= BLOCKS_PER_FAST_LOOP
* 6;
247 // Encode what's left after the fast loop.
249 const LOW_SIX_BITS_U8
: u8 = 0x3F;
251 let rem
= input
.len() % 3;
252 let start_of_rem
= input
.len() - rem
;
254 // start at the first index not handled by fast loop, which may be 0.
256 while input_index
< start_of_rem
{
257 let input_chunk
= &input
[input_index
..(input_index
+ 3)];
258 let output_chunk
= &mut output
[output_index
..(output_index
+ 4)];
260 output_chunk
[0] = encode_table
[(input_chunk
[0] >> 2) as usize];
262 encode_table
[((input_chunk
[0] << 4 | input_chunk
[1] >> 4) & LOW_SIX_BITS_U8
) as usize];
264 encode_table
[((input_chunk
[1] << 2 | input_chunk
[2] >> 6) & LOW_SIX_BITS_U8
) as usize];
265 output_chunk
[3] = encode_table
[(input_chunk
[2] & LOW_SIX_BITS_U8
) as usize];
272 output
[output_index
] = encode_table
[(input
[start_of_rem
] >> 2) as usize];
273 output
[output_index
+ 1] = encode_table
[((input
[start_of_rem
] << 4
274 | input
[start_of_rem
+ 1] >> 4)
275 & LOW_SIX_BITS_U8
) as usize];
276 output
[output_index
+ 2] =
277 encode_table
[((input
[start_of_rem
+ 1] << 2) & LOW_SIX_BITS_U8
) as usize];
280 output
[output_index
] = encode_table
[(input
[start_of_rem
] >> 2) as usize];
281 output
[output_index
+ 1] =
282 encode_table
[((input
[start_of_rem
] << 4) & LOW_SIX_BITS_U8
) as usize];
289 /// calculate the base64 encoded string size, including padding if appropriate
290 pub fn encoded_size(bytes_len
: usize, config
: Config
) -> Option
<usize> {
291 let rem
= bytes_len
% 3;
293 let complete_input_chunks
= bytes_len
/ 3;
294 let complete_chunk_output
= complete_input_chunks
.checked_mul(4);
298 complete_chunk_output
.and_then(|c
| c
.checked_add(4))
300 let encoded_rem
= match rem
{
303 _
=> unreachable
!("Impossible remainder"),
305 complete_chunk_output
.and_then(|c
| c
.checked_add(encoded_rem
))
308 complete_chunk_output
312 /// Write padding characters.
313 /// `output` is the slice where padding should be written, of length at least 2.
315 /// Returns the number of padding bytes written.
316 pub fn add_padding(input_len
: usize, output
: &mut [u8]) -> usize {
317 let rem
= input_len
% 3;
318 let mut bytes_written
= 0;
319 for _
in 0..((3 - rem
) % 3) {
320 output
[bytes_written
] = b'
='
;
331 decode
::decode_config_buf
,
332 tests
::{assert_encode_sanity, random_config}
,
333 Config
, STANDARD
, URL_SAFE_NO_PAD
,
337 distributions
::{Distribution, Uniform}
,
344 fn encoded_size_correct_standard() {
345 assert_encoded_length(0, 0, STANDARD
);
347 assert_encoded_length(1, 4, STANDARD
);
348 assert_encoded_length(2, 4, STANDARD
);
349 assert_encoded_length(3, 4, STANDARD
);
351 assert_encoded_length(4, 8, STANDARD
);
352 assert_encoded_length(5, 8, STANDARD
);
353 assert_encoded_length(6, 8, STANDARD
);
355 assert_encoded_length(7, 12, STANDARD
);
356 assert_encoded_length(8, 12, STANDARD
);
357 assert_encoded_length(9, 12, STANDARD
);
359 assert_encoded_length(54, 72, STANDARD
);
361 assert_encoded_length(55, 76, STANDARD
);
362 assert_encoded_length(56, 76, STANDARD
);
363 assert_encoded_length(57, 76, STANDARD
);
365 assert_encoded_length(58, 80, STANDARD
);
369 fn encoded_size_correct_no_pad() {
370 assert_encoded_length(0, 0, URL_SAFE_NO_PAD
);
372 assert_encoded_length(1, 2, URL_SAFE_NO_PAD
);
373 assert_encoded_length(2, 3, URL_SAFE_NO_PAD
);
374 assert_encoded_length(3, 4, URL_SAFE_NO_PAD
);
376 assert_encoded_length(4, 6, URL_SAFE_NO_PAD
);
377 assert_encoded_length(5, 7, URL_SAFE_NO_PAD
);
378 assert_encoded_length(6, 8, URL_SAFE_NO_PAD
);
380 assert_encoded_length(7, 10, URL_SAFE_NO_PAD
);
381 assert_encoded_length(8, 11, URL_SAFE_NO_PAD
);
382 assert_encoded_length(9, 12, URL_SAFE_NO_PAD
);
384 assert_encoded_length(54, 72, URL_SAFE_NO_PAD
);
386 assert_encoded_length(55, 74, URL_SAFE_NO_PAD
);
387 assert_encoded_length(56, 75, URL_SAFE_NO_PAD
);
388 assert_encoded_length(57, 76, URL_SAFE_NO_PAD
);
390 assert_encoded_length(58, 78, URL_SAFE_NO_PAD
);
394 fn encoded_size_overflow() {
395 assert_eq
!(None
, encoded_size(std
::usize::MAX
, STANDARD
));
399 fn encode_config_buf_into_nonempty_buffer_doesnt_clobber_prefix() {
400 let mut orig_data
= Vec
::new();
401 let mut prefix
= String
::new();
402 let mut encoded_data_no_prefix
= String
::new();
403 let mut encoded_data_with_prefix
= String
::new();
404 let mut decoded
= Vec
::new();
406 let prefix_len_range
= Uniform
::new(0, 1000);
407 let input_len_range
= Uniform
::new(0, 1000);
409 let mut rng
= rand
::rngs
::SmallRng
::from_entropy();
414 encoded_data_no_prefix
.clear();
415 encoded_data_with_prefix
.clear();
418 let input_len
= input_len_range
.sample(&mut rng
);
420 for _
in 0..input_len
{
421 orig_data
.push(rng
.gen());
424 let prefix_len
= prefix_len_range
.sample(&mut rng
);
425 for _
in 0..prefix_len
{
426 // getting convenient random single-byte printable chars that aren't base64 is
430 encoded_data_with_prefix
.push_str(&prefix
);
432 let config
= random_config(&mut rng
);
433 encode_config_buf(&orig_data
, config
, &mut encoded_data_no_prefix
);
434 encode_config_buf(&orig_data
, config
, &mut encoded_data_with_prefix
);
437 encoded_data_no_prefix
.len() + prefix_len
,
438 encoded_data_with_prefix
.len()
440 assert_encode_sanity(&encoded_data_no_prefix
, config
, input_len
);
441 assert_encode_sanity(&encoded_data_with_prefix
[prefix_len
..], config
, input_len
);
443 // append plain encode onto prefix
444 prefix
.push_str(&mut encoded_data_no_prefix
);
446 assert_eq
!(prefix
, encoded_data_with_prefix
);
448 decode_config_buf(&encoded_data_no_prefix
, config
, &mut decoded
).unwrap();
449 assert_eq
!(orig_data
, decoded
);
454 fn encode_config_slice_into_nonempty_buffer_doesnt_clobber_suffix() {
455 let mut orig_data
= Vec
::new();
456 let mut encoded_data
= Vec
::new();
457 let mut encoded_data_original_state
= Vec
::new();
458 let mut decoded
= Vec
::new();
460 let input_len_range
= Uniform
::new(0, 1000);
462 let mut rng
= rand
::rngs
::SmallRng
::from_entropy();
466 encoded_data
.clear();
467 encoded_data_original_state
.clear();
470 let input_len
= input_len_range
.sample(&mut rng
);
472 for _
in 0..input_len
{
473 orig_data
.push(rng
.gen());
476 // plenty of existing garbage in the encoded buffer
477 for _
in 0..10 * input_len
{
478 encoded_data
.push(rng
.gen());
481 encoded_data_original_state
.extend_from_slice(&encoded_data
);
483 let config
= random_config(&mut rng
);
485 let encoded_size
= encoded_size(input_len
, config
).unwrap();
489 encode_config_slice(&orig_data
, config
, &mut encoded_data
)
492 assert_encode_sanity(
493 std
::str::from_utf8(&encoded_data
[0..encoded_size
]).unwrap(),
499 &encoded_data
[encoded_size
..],
500 &encoded_data_original_state
[encoded_size
..]
503 decode_config_buf(&encoded_data
[0..encoded_size
], config
, &mut decoded
).unwrap();
504 assert_eq
!(orig_data
, decoded
);
509 fn encode_config_slice_fits_into_precisely_sized_slice() {
510 let mut orig_data
= Vec
::new();
511 let mut encoded_data
= Vec
::new();
512 let mut decoded
= Vec
::new();
514 let input_len_range
= Uniform
::new(0, 1000);
516 let mut rng
= rand
::rngs
::SmallRng
::from_entropy();
520 encoded_data
.clear();
523 let input_len
= input_len_range
.sample(&mut rng
);
525 for _
in 0..input_len
{
526 orig_data
.push(rng
.gen());
529 let config
= random_config(&mut rng
);
531 let encoded_size
= encoded_size(input_len
, config
).unwrap();
533 encoded_data
.resize(encoded_size
, 0);
537 encode_config_slice(&orig_data
, config
, &mut encoded_data
)
540 assert_encode_sanity(
541 std
::str::from_utf8(&encoded_data
[0..encoded_size
]).unwrap(),
546 decode_config_buf(&encoded_data
[0..encoded_size
], config
, &mut decoded
).unwrap();
547 assert_eq
!(orig_data
, decoded
);
552 fn encode_to_slice_random_valid_utf8() {
553 let mut input
= Vec
::new();
554 let mut output
= Vec
::new();
556 let input_len_range
= Uniform
::new(0, 1000);
558 let mut rng
= rand
::rngs
::SmallRng
::from_entropy();
564 let input_len
= input_len_range
.sample(&mut rng
);
566 for _
in 0..input_len
{
567 input
.push(rng
.gen());
570 let config
= random_config(&mut rng
);
572 // fill up the output buffer with garbage
573 let encoded_size
= encoded_size(input_len
, config
).unwrap();
574 for _
in 0..encoded_size
{
575 output
.push(rng
.gen());
578 let orig_output_buf
= output
.to_vec();
581 encode_to_slice(&input
, &mut output
, config
.char_set
.encode_table());
583 // make sure the part beyond bytes_written is the same garbage it was before
584 assert_eq
!(orig_output_buf
[bytes_written
..], output
[bytes_written
..]);
586 // make sure the encoded bytes are UTF-8
587 let _
= str::from_utf8(&output
[0..bytes_written
]).unwrap();
592 fn encode_with_padding_random_valid_utf8() {
593 let mut input
= Vec
::new();
594 let mut output
= Vec
::new();
596 let input_len_range
= Uniform
::new(0, 1000);
598 let mut rng
= rand
::rngs
::SmallRng
::from_entropy();
604 let input_len
= input_len_range
.sample(&mut rng
);
606 for _
in 0..input_len
{
607 input
.push(rng
.gen());
610 let config
= random_config(&mut rng
);
612 // fill up the output buffer with garbage
613 let encoded_size
= encoded_size(input_len
, config
).unwrap();
614 for _
in 0..encoded_size
+ 1000 {
615 output
.push(rng
.gen());
618 let orig_output_buf
= output
.to_vec();
620 encode_with_padding(&input
, config
, encoded_size
, &mut output
[0..encoded_size
]);
622 // make sure the part beyond b64 is the same garbage it was before
623 assert_eq
!(orig_output_buf
[encoded_size
..], output
[encoded_size
..]);
625 // make sure the encoded bytes are UTF-8
626 let _
= str::from_utf8(&output
[0..encoded_size
]).unwrap();
631 fn add_padding_random_valid_utf8() {
632 let mut output
= Vec
::new();
634 let mut rng
= rand
::rngs
::SmallRng
::from_entropy();
636 // cover our bases for length % 3
637 for input_len
in 0..10 {
640 // fill output with random
642 output
.push(rng
.gen());
645 let orig_output_buf
= output
.to_vec();
647 let bytes_written
= add_padding(input_len
, &mut output
);
649 // make sure the part beyond bytes_written is the same garbage it was before
650 assert_eq
!(orig_output_buf
[bytes_written
..], output
[bytes_written
..]);
652 // make sure the encoded bytes are UTF-8
653 let _
= str::from_utf8(&output
[0..bytes_written
]).unwrap();
657 fn assert_encoded_length(input_len
: usize, encoded_len
: usize, config
: Config
) {
658 assert_eq
!(encoded_len
, encoded_size(input_len
, config
).unwrap());
660 let mut bytes
: Vec
<u8> = Vec
::new();
661 let mut rng
= rand
::rngs
::SmallRng
::from_entropy();
663 for _
in 0..input_len
{
664 bytes
.push(rng
.gen());
667 let encoded
= encode_config(&bytes
, config
);
668 assert_encode_sanity(&encoded
, config
, input_len
);
670 assert_eq
!(encoded_len
, encoded
.len());