• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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