sim_codec_binary_base64/
base64.rs1use sim_codec::{DecodeBudget, DecodeLimits};
7use sim_kernel::{CodecId, Error, Result};
8
9const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
10
11pub fn encode_base64(bytes: &[u8]) -> String {
16 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
17 for chunk in bytes.chunks(3) {
18 let b0 = chunk[0];
19 let b1 = *chunk.get(1).unwrap_or(&0);
20 let b2 = *chunk.get(2).unwrap_or(&0);
21 out.push(ALPHABET[(b0 >> 2) as usize] as char);
22 out.push(ALPHABET[(((b0 & 0x03) << 4) | (b1 >> 4)) as usize] as char);
23 if chunk.len() > 1 {
24 out.push(ALPHABET[(((b1 & 0x0f) << 2) | (b2 >> 6)) as usize] as char);
25 } else {
26 out.push('=');
27 }
28 if chunk.len() > 2 {
29 out.push(ALPHABET[(b2 & 0x3f) as usize] as char);
30 } else {
31 out.push('=');
32 }
33 }
34 out
35}
36
37pub fn decode_base64(codec: CodecId, text: &str) -> Result<Vec<u8>> {
46 decode_base64_with_limits(codec, text, DecodeLimits::default())
47}
48
49pub fn decode_base64_with_limits(
60 codec: CodecId,
61 text: &str,
62 limits: DecodeLimits,
63) -> Result<Vec<u8>> {
64 let budget = DecodeBudget::new(limits);
65 budget.check_input_bytes(codec, text.len())?;
66 let clean = strip_ascii_whitespace(codec, text)?;
67 budget.check_input_bytes(codec, clean.len())?;
68 if !clean.len().is_multiple_of(4) {
69 return codec_err(codec, "invalid base64 length");
70 }
71 budget.check_input_bytes(codec, clean.len() / 4 * 3)?;
72 decode_clean_base64(codec, &clean)
73}
74
75fn decode_clean_base64(codec: CodecId, clean: &[u8]) -> Result<Vec<u8>> {
76 let mut out = Vec::with_capacity(clean.len() / 4 * 3);
77 let chunk_count = clean.len() / 4;
78 for (index, chunk) in clean.chunks_exact(4).enumerate() {
79 if chunk[0] == b'=' || chunk[1] == b'=' {
80 return codec_err(codec, "invalid base64 padding");
81 }
82
83 let is_last = index + 1 == chunk_count;
84 let c0 = decode_b64(codec, chunk[0])?;
85 let c1 = decode_b64(codec, chunk[1])?;
86 let c2 = decode_optional_b64(codec, chunk[2])?;
87 let c3 = decode_optional_b64(codec, chunk[3])?;
88
89 match (c2, c3) {
90 (Some(c2), Some(c3)) => {
91 out.push((c0 << 2) | (c1 >> 4));
92 out.push(((c1 & 0x0f) << 4) | (c2 >> 2));
93 out.push(((c2 & 0x03) << 6) | c3);
94 }
95 (Some(c2), None) => {
96 if !is_last {
97 return codec_err(codec, "base64 padding before final quartet");
98 }
99 if c2 & 0x03 != 0 {
100 return codec_err(codec, "non-zero base64 padding bits");
101 }
102 out.push((c0 << 2) | (c1 >> 4));
103 out.push(((c1 & 0x0f) << 4) | (c2 >> 2));
104 }
105 (None, None) => {
106 if !is_last {
107 return codec_err(codec, "base64 padding before final quartet");
108 }
109 if c1 & 0x0f != 0 {
110 return codec_err(codec, "non-zero base64 padding bits");
111 }
112 out.push((c0 << 2) | (c1 >> 4));
113 }
114 (None, Some(_)) => return codec_err(codec, "invalid base64 padding"),
115 }
116 }
117 Ok(out)
118}
119
120fn strip_ascii_whitespace(codec: CodecId, text: &str) -> Result<Vec<u8>> {
121 let mut clean = Vec::with_capacity(text.len());
122 for byte in text.bytes() {
123 match byte {
124 b' ' | b'\n' | b'\r' | b'\t' => {}
125 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'+' | b'/' | b'=' => clean.push(byte),
126 _ => {
127 return Err(Error::CodecError {
128 codec,
129 message: format!("invalid base64 byte 0x{byte:02x}"),
130 });
131 }
132 }
133 }
134 Ok(clean)
135}
136
137fn decode_optional_b64(codec: CodecId, byte: u8) -> Result<Option<u8>> {
138 if byte == b'=' {
139 Ok(None)
140 } else {
141 decode_b64(codec, byte).map(Some)
142 }
143}
144
145fn decode_b64(codec: CodecId, byte: u8) -> Result<u8> {
146 match byte {
147 b'A'..=b'Z' => Ok(byte - b'A'),
148 b'a'..=b'z' => Ok(byte - b'a' + 26),
149 b'0'..=b'9' => Ok(byte - b'0' + 52),
150 b'+' => Ok(62),
151 b'/' => Ok(63),
152 _ => Err(Error::CodecError {
153 codec,
154 message: format!("invalid base64 byte 0x{byte:02x}"),
155 }),
156 }
157}
158
159fn codec_err<T>(codec: CodecId, message: impl Into<String>) -> Result<T> {
160 Err(Error::CodecError {
161 codec,
162 message: message.into(),
163 })
164}