1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #![cfg_attr(not(feature = "std"), no_std)]
15 #![deny(missing_docs)]
16
17 //! `pw_base64` provides simple encoding of data into base64.
18 //!
19 //! ```
20 //! const INPUT: &'static [u8] = "I Pigweed".as_bytes();
21 //!
22 //! // [`encoded_size`] can be used to calculate the size of the output buffer.
23 //! let mut output = [0u8; pw_base64::encoded_size(INPUT.len())];
24 //!
25 //! // Data can be encoded to a `&mut [u8]`.
26 //! let output_size = pw_base64::encode(INPUT, &mut output).unwrap();
27 //! assert_eq!(&output[0..output_size], b"SSDwn5KWIFBpZ3dlZWQ=");
28 //!
29 //! // The output buffer can also be automatically converted to a `&str`.
30 //! let output_str = pw_base64::encode_str(INPUT, &mut output).unwrap();
31 //! assert_eq!(output_str, "SSDwn5KWIFBpZ3dlZWQ=");
32 //! ```
33
34 use pw_status::{Error, Result};
35 use pw_stream::{Cursor, ReadInteger, Seek, Write};
36
37 // Helper macro to make declaring the base 64 encode table more concise.
38 macro_rules! b {
39 ($char:tt) => {
40 stringify!($char).as_bytes()[0]
41 };
42 }
43
44 // We use `u8`s in our encoding table instead of `char`s in order to avoid the
45 // overhead of 1) storing each entry as 4 bytes and 2) overhead of converting
46 // from `char` to `u8` while building the output.
47 //
48 // When constructing this table, the `b!` macro makes the assumption that
49 // all the characters are a single byte in utf8. This is true as base64
50 // only outputs ASCII characters.
51 #[rustfmt::skip]
52 const BASE64_ENCODE_TABLE: [u8; 64] = [
53 b!(A), b!(B), b!(C), b!(D), b!(E), b!(F), b!(G), b!(H),
54 b!(I), b!(J), b!(K), b!(L), b!(M), b!(N), b!(O), b!(P),
55 b!(Q), b!(R), b!(S), b!(T), b!(U), b!(V), b!(W), b!(X),
56 b!(Y), b!(Z), b!(a), b!(b), b!(c), b!(d), b!(e), b!(f),
57 b!(g), b!(h), b!(i), b!(j), b!(k), b!(l), b!(m), b!(n),
58 b!(o), b!(p), b!(q), b!(r), b!(s), b!(t), b!(u), b!(v),
59 b!(w), b!(x), b!(y), b!(z), b!(0), b!(1), b!(2), b!(3),
60 b!(4), b!(5), b!(6), b!(7), b!(8), b!(9), b!(+), b!(/),
61 ];
62 const BASE64_PADDING: u8 = b!(=);
63
64 /// Returns the size of the output buffer needed to encode an input buffer of
65 /// size `input_size`.
encoded_size(input_size: usize) -> usize66 pub const fn encoded_size(input_size: usize) -> usize {
67 (input_size + 2) / 3 * 4 // +2 to round up to a 3-byte group
68 }
69
70 // Base 64 encoding represents every 3 bytes with 4 ascii characters. Each
71 // of these 4 ascii characters represents 6 bits of data from the 3 bytes of
72 // input. The below helpers calculate each of the 4 characters form the 3 bytes
73 // of input.
char_0(b: &[u8; 3]) -> u874 const fn char_0(b: &[u8; 3]) -> u8 {
75 BASE64_ENCODE_TABLE[((b[0] & 0b11111100) >> 2) as usize]
76 }
77
char_1(b: &[u8; 3]) -> u878 const fn char_1(b: &[u8; 3]) -> u8 {
79 BASE64_ENCODE_TABLE[(((b[0] & 0b00000011) << 4) | ((b[1] & 0b11110000) >> 4)) as usize]
80 }
81
char_2(b: &[u8; 3]) -> u882 const fn char_2(b: &[u8; 3]) -> u8 {
83 BASE64_ENCODE_TABLE[(((b[1] & 0b00001111) << 2) | ((b[2] & 0b11000000) >> 6)) as usize]
84 }
85
char_3(b: &[u8; 3]) -> u886 const fn char_3(b: &[u8; 3]) -> u8 {
87 BASE64_ENCODE_TABLE[(b[2] & 0b00111111) as usize]
88 }
89
90 /// Encode `input` as base64 into the `output_buffer`.
91 ///
92 /// Returns the number of bytes written to `output_buffer` on success or
93 /// `Error::OutOfRange` if `output_buffer` is not large enough.
encode(input: &[u8], output: &mut [u8]) -> Result<usize>94 pub fn encode(input: &[u8], output: &mut [u8]) -> Result<usize> {
95 if output.len() < encoded_size(input.len()) {
96 return Err(Error::OutOfRange);
97 }
98 let mut input = Cursor::new(input);
99 let mut output = Cursor::new(output);
100
101 let mut remaining_bytes = input.len();
102 while remaining_bytes > 0 {
103 let bytes = [
104 input.read_u8_le().unwrap_or(0),
105 input.read_u8_le().unwrap_or(0),
106 input.read_u8_le().unwrap_or(0),
107 ];
108
109 output.write(&[
110 char_0(&bytes),
111 char_1(&bytes),
112 if remaining_bytes > 1 {
113 char_2(&bytes)
114 } else {
115 BASE64_PADDING
116 },
117 if remaining_bytes > 2 {
118 char_3(&bytes)
119 } else {
120 BASE64_PADDING
121 },
122 ])?;
123 remaining_bytes = remaining_bytes.saturating_add_signed(-3);
124 }
125
126 output.stream_position().map(|len| len as usize)
127 }
128
129 /// Encode `input` as base64 into `output_buffer` and interprets it as a
130 /// string.
131 ///
132 /// Returns a `&str` referencing the `output_buffer` buffer on success or
133 /// `Error::OutOfRange` if `output_buffer` is not large enough.
134 ///
135 /// Using this method avoids having to do unicode checking as it can guarantee
136 /// that the data written to `output_buffer` is only valid ASCII bytes.
encode_str<'a>(input: &[u8], output_buffer: &'a mut [u8]) -> Result<&'a str>137 pub fn encode_str<'a>(input: &[u8], output_buffer: &'a mut [u8]) -> Result<&'a str> {
138 let encode_len = encode(input, output_buffer)?;
139 // Safety: Since we are building the output buffer strictly from ASCII
140 // characters, it is guaranteed to be a valid string.
141 unsafe {
142 Ok(core::str::from_utf8_unchecked(
143 &output_buffer[0..encode_len],
144 ))
145 }
146 }
147
148 #[cfg(test)]
149 mod tests;
150