//! Uint8Array base64/hex 编解码纯函数层(无 VM 依赖)。 //! //! 承载 FromBase64/FromHex 状态机与编码输出,供 typed_array.rs 的 //! setFromBase64/setFromHex/toBase64/toHex/fromBase64/fromHex 六个内置共用。 //! 解码逐块产出 `(read, written)`:`read` 计全部已消费字符(含跳过的 ASCII //! 空白),`written` 为已写入字节数;`Err(DecodeError)` 统一表示 SyntaxError。已解码 //! 块在出错前经 `write` 回调逐个写出(setFrom 族"前块已写入后抛错"语义)。 //! 解码失败统一以 `Err(DecodeError)` 表示,绑定层映射为 SyntaxError。 /// 解码失败标记(绑定层映射为 SyntaxError)。 #[derive(Debug, Clone, Copy)] pub struct DecodeError; /// base64 字母表:标准表(`+/`)与 URL 安全表(`-_`)。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Base64Alphabet { Standard, Url, } /// 尾块处理模式(lastChunkHandling 三取值)。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LastChunkHandling { Loose, Strict, StopBeforePartial, } const B64_ALPHABET_STANDARD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const B64_ALPHABET_URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; fn b64_table(alphabet: Base64Alphabet) -> &'static [u8; 64] { match alphabet { Base64Alphabet::Standard => B64_ALPHABET_STANDARD, Base64Alphabet::Url => B64_ALPHABET_URL, } } /// 判 ASCII 空白(0x09/0x0A/0x0C/0x0D/0x20 五码;VT 与所有非 ASCII 空格均不算)。 fn is_base64_whitespace(unit: u16) -> bool { matches!(unit, 0x09 | 0x0A | 0x0C | 0x0D | 0x20) } /// 取 base64 数据字符的值(0-63);`=` 与任何非字母表字符返回 `None`。 fn base64_value(unit: u16, alphabet: Base64Alphabet) -> Option { match unit { 0x41..=0x5a => Some(unit as u32 - 0x41), 0x61..=0x7a => Some(unit as u32 - 0x61 + 26), 0x30..=0x39 => Some(unit as u32 - 0x30 + 52), 0x2b if alphabet == Base64Alphabet::Standard => Some(62), 0x2f if alphabet == Base64Alphabet::Standard => Some(63), 0x2d if alphabet == Base64Alphabet::Url => Some(62), 0x5f if alphabet == Base64Alphabet::Url => Some(63), _ => None, } } /// 解码一个满 4 字符块:校验 padding 位置(`=` 只可居 3/4 位)与数据字符, /// strict 模式另查 padding 位必须为 0。 /// /// # 边界与前提 /// - `chunk` 恰 4 字符;调用方保证只在此处解码满块。 /// - 返回 `(24 位值, 解码字节数 1-3)`:24 位值按 4 槽位左对齐(padding 槽补 0), /// 字节数 = 3 - padding 数。 fn decode_full_chunk( chunk: [u16; 4], alphabet: Base64Alphabet, handling: LastChunkHandling, ) -> Result<(u32, usize), DecodeError> { let pad = match (chunk[2] == 0x3d, chunk[3] == 0x3d) { (false, false) => 0, (false, true) => 1, (true, true) => 2, // `XX=X`:padding 居第 3 位,四种模式全非法。 (true, false) => return Err(DecodeError), }; let data = &chunk[..4 - pad]; let mut bits: u32 = 0; for &c in data { let v = base64_value(c, alphabet).ok_or(DecodeError)?; bits = (bits << 6) | v; } bits <<= 6 * pad as u32; if handling == LastChunkHandling::Strict && pad > 0 { // padding 位即末数据字符的低 2/4 位(pad 1 查 2 位、pad 2 查 4 位)。 let last = base64_value(*data.last().unwrap(), alphabet).unwrap(); let mask = if pad == 1 { 0b11 } else { 0b1111 }; if last & mask != 0 { return Err(DecodeError); } } Ok((bits, 3 - pad)) } /// 把满块解码出的 24 位值按字节数经回调写出,返回新的已写字节数。 fn write_chunk_bytes(write: &mut dyn FnMut(usize, u8), written: usize, bits: u32, count: usize) -> usize { for i in 0..count { write(written + i, (bits >> (16 - 8 * i)) as u8); } written + count } /// 尾块(1-3 个非空白字符,已越过全部满块)的终态裁决。 /// /// # 边界与前提 /// - 1 字符尾块:`=` 三种模式全抛;数据字符仅 stop-before-partial 停(成功); /// - 含 `=` 尾块:唯一放行形态是 3 字符 `XX=` 且 stop-before-partial(停在不写 /// 出的块前);`X==`/`X=`/`=` 居首等其余 padding 形态三种模式全抛; /// - 全数据 2-3 字符尾块:strict 抛、stop-before-partial 停在块前、loose 解 /// `clen - 1` 字节(2 字符 1 字节、3 字符 2 字节)并写出。 /// - 成功 `read`:解出字节的 loose 路径取块后消费位置;各"停在块前"路径 /// (sbp、余量不足)取块前位置 `chunk_start`。 #[allow(clippy::too_many_arguments)] fn finish_partial( chunk: [u16; 4], clen: usize, alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option, write: &mut dyn FnMut(usize, u8), mut written: usize, chunk_start: usize, read: usize, ) -> Result<(usize, usize), DecodeError> { if (0..clen).any(|i| chunk[i] == 0x3d) { let trailing = (0..clen).rev().take_while(|&i| chunk[i] == 0x3d).count(); if clen == 3 && trailing == 1 && handling == LastChunkHandling::StopBeforePartial { for &c in &chunk[..2] { base64_value(c, alphabet).ok_or(DecodeError)?; } return Ok((chunk_start, written)); } return Err(DecodeError); } for &c in &chunk[..clen] { base64_value(c, alphabet).ok_or(DecodeError)?; } match handling { LastChunkHandling::StopBeforePartial => Ok((chunk_start, written)), LastChunkHandling::Strict => Err(DecodeError), LastChunkHandling::Loose => { if clen == 1 { return Err(DecodeError); } // maxLength 余量不足时不消费该块直接停(不写出)。 if let Some(m) = max_len { if written + clen - 1 > m { return Ok((chunk_start, written)); } } let v = |i: usize| base64_value(chunk[i], alphabet).unwrap(); write(written, (v(0) << 2 | v(1) >> 4) as u8); written += 1; if clen == 3 { write(written, ((v(1) << 4 | v(2) >> 2) & 0xFF) as u8); written += 1; } Ok((read, written)) } } } /// FromBase64 状态机:按块消费输入串,已解码字节经 `write` 回调逐块写出。 /// /// # 步骤 /// 1. `max_len == 0` 短路:输入整体忽略,直接成功空结果。 /// 2. 循环收集至多 4 个非空白字符(跳过五码 ASCII 空白)组成块。 /// 3. 满块先过 maxLength 余量判定(不足则块前停、不消费、不写出),再解码写出; /// 写出后恰好补齐 maxLength 即成功返回。 /// 4. 块不足 4 字符时走终态裁决([`finish_partial`])。 /// /// # 边界与前提 /// - `units` 为输入串的 UTF-16 单元序列;非 ASCII 码元一律是非法字符。 /// - 出错时 `write` 已产生的写入保留(调用方承载"前块已写入"语义)。 /// - `Ok((read, written))` 中 `read` 为成功语义下的消费位置(含跳过空白, /// 停止路径为块前位置)。 pub fn decode_base64( units: &[u16], alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option, write: &mut dyn FnMut(usize, u8), ) -> Result<(usize, usize), DecodeError> { if max_len == Some(0) { return Ok((0, 0)); } let n = units.len(); let mut read = 0usize; let mut written = 0usize; loop { let chunk_start = read; let mut chunk = [0u16; 4]; let mut clen = 0usize; while clen < 4 && read < n { let c = units[read]; read += 1; if is_base64_whitespace(c) { continue; } chunk[clen] = c; clen += 1; } match clen { 4 => { if chunk[2] == 0x3d || chunk[3] == 0x3d { // 带 padding 的满块只可作末块:其后还有非空白字符时 // ("extra padding")在写出该块字节前抛。 if units[read..].iter().any(|&c| !is_base64_whitespace(c)) { return Err(DecodeError); } } let (bits, count) = decode_full_chunk(chunk, alphabet, handling)?; if let Some(m) = max_len { if written + count > m { return Ok((chunk_start, written)); } } written = write_chunk_bytes(write, written, bits, count); if let Some(m) = max_len { if written == m { return Ok((read, written)); } } } 0 => return Ok((read, written)), _ => return finish_partial(chunk, clen, alphabet, handling, max_len, write, written, chunk_start, read), } } } /// 取单个 hex 字符值(大小写宽容);非 hex 字符返回 `None`。 fn hex_value(unit: u16) -> Option { match unit { 0x30..=0x39 => Some(unit as u8 - 0x30), 0x61..=0x66 => Some(unit as u8 - 0x61 + 10), 0x41..=0x46 => Some(unit as u8 - 0x41 + 10), _ => None, } } /// FromHex 状态机:无空白跳过、无 padding;长度奇数在最前判定 /// (`read` 0、零写入);坏字符抛错时保留已写出的前字节。 /// /// # 边界与前提 /// - `units` 为输入串的 UTF-16 单元序列;任何非 hex 字符(含所有空白)非法。 /// - 每对解码前判 `written + 1 > max_len` 则对该对前停(不消费、不写出)。 pub fn decode_hex( units: &[u16], max_len: Option, write: &mut dyn FnMut(usize, u8), ) -> Result<(usize, usize), DecodeError> { if units.len() % 2 == 1 { return Err(DecodeError); } let mut written = 0usize; let mut read = 0usize; for (i, pair) in units.chunks_exact(2).enumerate() { let hi = hex_value(pair[0]).ok_or(DecodeError)?; let lo = hex_value(pair[1]).ok_or(DecodeError)?; if let Some(m) = max_len { if written + 1 > m { return Ok((i * 2, written)); } } write(written, hi * 16 + lo); written += 1; read = i * 2 + 2; } Ok((read, written)) } /// 编码 base64:标准表或 URL 表;`omit_padding` 时剥尾部 `=`。 pub fn encode_base64(bytes: &[u8], alphabet: Base64Alphabet, omit_padding: bool) -> String { let table = b64_table(alphabet); let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4); for chunk in bytes.chunks(3) { let bits = (chunk[0] as u32) << 16 | (chunk.get(1).copied().unwrap_or(0) as u32) << 8 | chunk.get(2).copied().unwrap_or(0) as u32; out.push(table[(bits >> 18) as usize] as char); out.push(table[((bits >> 12) & 0x3f) as usize] as char); if chunk.len() > 1 { out.push(table[((bits >> 6) & 0x3f) as usize] as char); } else if !omit_padding { out.push('='); } if chunk.len() > 2 { out.push(table[(bits & 0x3f) as usize] as char); } else if !omit_padding { out.push('='); } } out } /// 编码小写两位 hex。 pub fn encode_hex(bytes: &[u8]) -> String { const HEX: &[u8; 16] = b"0123456789abcdef"; let mut out = String::with_capacity(bytes.len() * 2); for &b in bytes { out.push(HEX[(b >> 4) as usize] as char); out.push(HEX[(b & 0x0f) as usize] as char); } out } #[cfg(test)] mod tests { use super::*; fn units(s: &str) -> Vec { s.chars().map(|c| c as u16).collect() } fn decode( units: &[u16], alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option, ) -> Result<(usize, Vec), DecodeError> { let mut bytes = Vec::new(); decode_base64(units, alphabet, handling, max_len, &mut |_, b| bytes.push(b)) .map(|(r, w)| (r, bytes.into_iter().take(w).collect())) } fn decode_hex_full(units: &[u16], max_len: Option) -> Result<(usize, Vec), DecodeError> { let mut bytes = Vec::new(); decode_hex(units, max_len, &mut |_, b| bytes.push(b)).map(|(r, w)| (r, bytes.into_iter().take(w).collect())) } /// RFC 4648 标准向量:编码/解码双向。 #[test] fn rfc4648_vectors_roundtrip() { let vectors: [(&str, &[u8]); 7] = [ ("", &[]), ("Zg==", &[102]), ("Zm8=", &[102, 111]), ("Zm9v", &[102, 111, 111]), ("Zm9vYg==", &[102, 111, 111, 98]), ("Zm9vYmE=", &[102, 111, 111, 98, 97]), ("Zm9vYmFy", &[102, 111, 111, 98, 97, 114]), ]; for (s, bytes) in vectors { assert_eq!(encode_base64(bytes, Base64Alphabet::Standard, false), s); let (read, decoded) = decode(&units(s), Base64Alphabet::Standard, LastChunkHandling::Loose, None).unwrap(); assert_eq!(decoded, *bytes); assert_eq!(read, s.chars().count()); } } #[test] fn encode_base64_url_alphabet_and_omit_padding() { assert_eq!(encode_base64(&[199, 239, 242], Base64Alphabet::Standard, false), "x+/y"); assert_eq!(encode_base64(&[199, 239, 242], Base64Alphabet::Url, false), "x-_y"); assert_eq!(encode_base64(&[199, 239], Base64Alphabet::Standard, false), "x+8="); assert_eq!(encode_base64(&[199, 239], Base64Alphabet::Standard, true), "x+8"); assert_eq!(encode_base64(&[255], Base64Alphabet::Standard, true), "/w"); assert_eq!(encode_base64(&[255], Base64Alphabet::Url, true), "_w"); } #[test] fn decode_base64_whitespace_and_read_count() { for ws in [' ', '\t', '\n', '\u{0C}', '\r'] { let s = format!("Z{ws}g=="); let (read, bytes) = decode(&units(&s), Base64Alphabet::Standard, LastChunkHandling::Loose, None).unwrap(); assert_eq!(bytes, vec![102]); assert_eq!(read, 5); } // VT 不是 ASCII 空白:非法字符。 assert!(decode(&units("Z\u{0B}g=="), Base64Alphabet::Standard, LastChunkHandling::Loose, None).is_err()); } #[test] fn decode_base64_strict_padding_bits() { // 末数据字符低 2 位非 0(`h` = 33 = 0b100001):strict 抛,其余过。 for (h, ok) in [ (LastChunkHandling::Loose, true), (LastChunkHandling::Strict, false), (LastChunkHandling::StopBeforePartial, true), ] { let r = decode(&units("ZXhhZh=="), Base64Alphabet::Standard, h, None); assert_eq!(r.is_ok(), ok, "ZXhhZh== {h:?}"); if ok { assert_eq!(r.unwrap().1, vec![101, 120, 97, 102]); } } // 低 4 位非 0(`h` 居 pad 2 末位):strict 抛;`g` 低 4 位全 0 则过。 assert!(decode(&units("ZXhhZg=="), Base64Alphabet::Standard, LastChunkHandling::Strict, None).is_ok()); assert!(decode(&units("Zg1="), Base64Alphabet::Standard, LastChunkHandling::Strict, None).is_err()); // pad 2 块字节值:`Zg==` → 102。 assert_eq!( decode(&units("Zg=="), Base64Alphabet::Standard, LastChunkHandling::Loose, None) .unwrap() .1, vec![102] ); // pad 1 块字节值:`Zm8=` → [102, 111]。 assert_eq!( decode(&units("Zm8="), Base64Alphabet::Standard, LastChunkHandling::Loose, None) .unwrap() .1, vec![102, 111] ); } #[test] fn decode_base64_last_chunk_matrix() { let cases: [(&str, [bool; 3]); 13] = [ // (输入, [loose, strict, sbp] 是否成功) ("A", [false, false, true]), ("ABCDA", [false, false, true]), ("AA=", [false, false, true]), ("aQ=", [false, false, true]), ("ABCDAA=", [false, false, true]), ("AAA", [true, false, true]), ("ZXhhZg", [true, false, true]), ("ZXhhZg=", [false, false, true]), ("ZXhhZg===", [false, false, false]), ("=", [false, false, false]), ("A==", [false, false, false]), ("AAAA=", [false, false, false]), ("ZXhhZgg", [true, false, true]), ]; for (s, expect) in cases { for (h, ok) in [ (LastChunkHandling::Loose, expect[0]), (LastChunkHandling::Strict, expect[1]), (LastChunkHandling::StopBeforePartial, expect[2]), ] { let r = decode(&units(s), Base64Alphabet::Standard, h, None); assert_eq!(r.is_ok(), ok, "{s:?} {h:?}"); } } // sbp 停止路径:前块保留、尾块不消费。 let (read, bytes) = decode(&units("ABCDAA="), Base64Alphabet::Standard, LastChunkHandling::StopBeforePartial, None).unwrap(); assert_eq!((read, bytes), (4, vec![0, 16, 131])); let (read, bytes) = decode(&units("ZXhhZg"), Base64Alphabet::Standard, LastChunkHandling::StopBeforePartial, None).unwrap(); assert_eq!((read, bytes), (4, vec![101, 120, 97])); // loose 对 2 字符尾块解 1 字节。 let (read, bytes) = decode(&units("ZXhhZg"), Base64Alphabet::Standard, LastChunkHandling::Loose, None).unwrap(); assert_eq!((read, bytes), (6, vec![101, 120, 97, 102])); // loose 对 3 字符尾块解 2 字节。 let (read, bytes) = decode(&units("ZXhhZgg"), Base64Alphabet::Standard, LastChunkHandling::Loose, None).unwrap(); assert_eq!((read, bytes), (7, vec![101, 120, 97, 102, 8])); } #[test] fn decode_base64_max_length_stop_timing() { // 块前停:第 2 块 3 字节会超限,不消费(read 停在前值 4)。 let (read, bytes) = decode(&units("Zm9vYmFy"), Base64Alphabet::Standard, LastChunkHandling::Loose, Some(5)).unwrap(); assert_eq!((read, bytes), (4, vec![102, 111, 111])); // 恰好补齐:`YmE=` 出 2 字节补到 5。 let (read, bytes) = decode(&units("Zm9vYmE="), Base64Alphabet::Standard, LastChunkHandling::Loose, Some(5)).unwrap(); assert_eq!((read, bytes), (8, vec![102, 111, 111, 98, 97])); // 零长短路:垃圾输入整体忽略。 let (read, bytes) = decode(&units("aaaa#"), Base64Alphabet::Standard, LastChunkHandling::Strict, Some(0)).unwrap(); assert_eq!((read, bytes), (0, Vec::new())); // 尾 3 字符块余量不足:不消费。 let (read, bytes) = decode(&units("Zm9vYmE"), Base64Alphabet::Standard, LastChunkHandling::Loose, Some(3)).unwrap(); assert_eq!((read, bytes), (4, vec![102, 111, 111])); } #[test] fn decode_base64_illegal_characters() { for s in ["Zm.9v", "Zm9v^", "Zg==&", "Z\u{2212}==", "Zg\u{00A0}==", "Zg\u{2009}==", "Zg\u{2028}=="] { assert!( decode(&units(s), Base64Alphabet::Standard, LastChunkHandling::Loose, None).is_err(), "{s:?}" ); } } #[test] fn decode_base64_url_alphabet_decode() { let (read, bytes) = decode(&units("x-_y"), Base64Alphabet::Url, LastChunkHandling::Loose, None).unwrap(); assert_eq!((read, bytes), (4, vec![199, 239, 242])); // URL 表中标准表符号非法。 assert!(decode(&units("x+/y"), Base64Alphabet::Url, LastChunkHandling::Loose, None).is_err()); assert!(decode(&units("x-_y"), Base64Alphabet::Standard, LastChunkHandling::Loose, None).is_err()); } #[test] fn decode_base64_writes_up_to_error() { // 坏字符前已写出的字节保留(setFrom 语义同)。 let mut out = Vec::new(); let r = decode_base64( &units("MjYyZm.9v"), Base64Alphabet::Standard, LastChunkHandling::Loose, None, &mut |i, b| out.insert(i, b), ); assert!(r.is_err()); assert_eq!(out, vec![50, 54, 50]); // 带 padding 满块后随非空白字符:该块字节不写出(extra padding)。 let mut out = Vec::new(); let r = decode_base64( &units("MjYyZg==="), Base64Alphabet::Standard, LastChunkHandling::Loose, None, &mut |i, b| out.insert(i, b), ); assert!(r.is_err()); assert_eq!(out, vec![50, 54, 50]); } #[test] fn decode_hex_vectors_and_case_insensitive() { let cases: [(&str, &[u8]); 5] = [ ("", &[]), ("66", &[102]), ("666f", &[102, 111]), ("666F", &[102, 111]), ("666f6f626172", &[102, 111, 111, 98, 97, 114]), ]; for (s, bytes) in cases { let (read, decoded) = decode_hex_full(&units(s), None).unwrap(); assert_eq!(decoded, *bytes); assert_eq!(read, s.len()); } } #[test] fn decode_hex_odd_and_max_length() { assert!(decode_hex_full(&units("a"), None).is_err()); assert!(decode_hex_full(&units("aaa"), None).is_err()); // 奇数在最前判定:零长目标也抛(零写入)。 assert!(decode_hex_full(&units("1"), Some(0)).is_err()); // 对前停:2 字节目标下 `aabbcc` 停在第 3 对前(read 4)。 let (read, bytes) = decode_hex_full(&units("aabbcc"), Some(2)).unwrap(); assert_eq!((read, bytes), (4, vec![0xaa, 0xbb])); // 恰好补齐。 let (read, bytes) = decode_hex_full(&units("aabbcc"), Some(3)).unwrap(); assert_eq!((read, bytes), (6, vec![0xaa, 0xbb, 0xcc])); } #[test] fn decode_hex_illegal_characters() { for s in ["a.a", "aa^", "a a", "a\ta", "a\u{00A0}a", "a\u{2009}a", "a\u{2028}a"] { assert!(decode_hex_full(&units(s), None).is_err(), "{s:?}"); } } #[test] fn decode_hex_writes_up_to_error() { // 前对写出、坏对抛错。 let mut out = Vec::new(); let r = decode_hex(&units("aaag"), None, &mut |i, b| out.insert(i, b)); assert!(r.is_err()); assert_eq!(out, vec![0xaa]); } #[test] fn encode_hex_lowercase() { assert_eq!(encode_hex(&[]), ""); assert_eq!(encode_hex(&[102]), "66"); assert_eq!(encode_hex(&[102, 111, 111, 98, 97, 114]), "666f6f626172"); assert_eq!(encode_hex(&[170, 187, 204]), "aabbcc"); } }