feat: Uint8Array base64/hex 六方法与共享原型 toString

This commit is contained in:
2026-09-22 14:28:38 +00:00
parent d6acfdf04f
commit 6fcd1af059
7 changed files with 1323 additions and 10 deletions
+2
View File
@@ -60,6 +60,8 @@ pub mod string;
pub mod stubs; pub mod stubs;
/// Symbol 内置对象实现(constructor/for/keyFor/toString)。 /// Symbol 内置对象实现(constructor/for/keyFor/toString)。
pub mod symbol; pub mod symbol;
/// TypedArray base64/hex 编解码纯函数(FromBase64/FromHex 状态机与编码输出)。
pub mod ta_codec;
/// Temporal 内置对象实现(目录模块树:Now / Instant / PlainDate / PlainTime / PlainDateTime / PlainMonthDay / PlainYearMonth / ZonedDateTime / Duration)。 /// Temporal 内置对象实现(目录模块树:Now / Instant / PlainDate / PlainTime / PlainDateTime / PlainMonthDay / PlainYearMonth / ZonedDateTime / Duration)。
pub mod temporal; pub mod temporal;
/// TypedArray 内置对象实现(元素访问、fill/slice/subarray/set 等)。 /// TypedArray 内置对象实现(元素访问、fill/slice/subarray/set 等)。
+551
View File
@@ -0,0 +1,551 @@
//! 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<u32> {
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<usize>,
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<usize>,
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<u8> {
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<usize>, 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<u16> {
s.chars().map(|c| c as u16).collect()
}
fn decode(
units: &[u16], alphabet: Base64Alphabet, handling: LastChunkHandling, max_len: Option<usize>,
) -> Result<(usize, Vec<u8>), 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<usize>) -> Result<(usize, Vec<u8>), 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");
}
}
+230
View File
@@ -1671,3 +1671,233 @@ pub fn typed_array_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} }
NativeResult::Ok(new_obj) NativeResult::Ok(new_obj)
} }
// ── Uint8Array base64/hex 六方法族 ─────────────────────────────────────────────
/// ValidateUint8Array:接收者须为 kind 恰为 Uint8 的 TypedArray 对象;
/// Uint8Clamped 与其他种类一律抛 TypeError。
fn validate_uint8_array<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<TypedArrayData, JsValue> {
let view = get_typed_array_data(vm, this_val)?;
if view.kind != TypedArrayKind::Uint8 {
return Err(type_error(vm, "Uint8Array method called on incompatible receiver"));
}
Ok(view)
}
/// GetOptionsObject:undefined 直通;其余值过 ToObject,失败抛 TypeError。
fn ta_options_object<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<JsValue>, JsValue> {
if value.is_undefined() {
return Ok(None);
}
match oxide_runtime_api::to_object(value, vm) {
Ok(obj) => Ok(Some(obj)),
Err(e) => Err(type_error(vm, &e)),
}
}
/// 读只收字符串原始值的选项字段(alphabet/lastChunkHandling):undefined 取
/// 默认值;装箱串/抛错 toString 对象等一律抛 TypeError,不做 ToPrimitive。
fn ta_string_option<H: VmHost>(
vm: &mut H, opts: &JsObject, opts_val: JsValue, key: &str, default: &'static str,
) -> Result<String, JsValue> {
let key_si = vm.string_key_si(key);
let v = vm
.ordinary_get(opts, key_si, opts_val)
.map_err(|e| crate::iterator::engine_error(vm, &e))?;
if v.is_undefined() {
return Ok(default.to_string());
}
if !v.is_string() {
return Err(type_error(vm, &format!("{key} must be a string")));
}
Ok(vm.lookup_str(v).unwrap_or_default())
}
/// ToBoolean:仅 null/undefined/false/NaN/±0 为假。
fn ta_to_boolean(v: JsValue) -> bool {
if v.is_null() || v.is_undefined() {
return false;
}
if v.is_bool() {
return v.as_bool();
}
if v.is_int() {
return v.as_int() != 0;
}
if v.is_double() {
let d = v.as_double();
return !d.is_nan() && d != 0.0;
}
true
}
/// 取选项中的 alphabet 值并归一到字母表枚举;非 "base64"/"base64url" 抛 TypeError。
fn ta_alphabet_option<H: VmHost>(
vm: &mut H, opts: &JsObject, opts_val: JsValue,
) -> Result<crate::ta_codec::Base64Alphabet, JsValue> {
let s = ta_string_option(vm, opts, opts_val, "alphabet", "base64")?;
match s.as_str() {
"base64url" => Ok(crate::ta_codec::Base64Alphabet::Url),
"base64" => Ok(crate::ta_codec::Base64Alphabet::Standard),
_ => Err(type_error(vm, "alphabet must be 'base64' or 'base64url'")),
}
}
/// 取选项中的 lastChunkHandling 值并归一到模式枚举;非三取值抛 TypeError。
fn ta_handling_option<H: VmHost>(
vm: &mut H, opts: &JsObject, opts_val: JsValue,
) -> Result<crate::ta_codec::LastChunkHandling, JsValue> {
let s = ta_string_option(vm, opts, opts_val, "lastChunkHandling", "loose")?;
match s.as_str() {
"strict" => Ok(crate::ta_codec::LastChunkHandling::Strict),
"stop-before-partial" => Ok(crate::ta_codec::LastChunkHandling::StopBeforePartial),
"loose" => Ok(crate::ta_codec::LastChunkHandling::Loose),
_ => Err(type_error(vm, "lastChunkHandling must be 'loose', 'strict', or 'stop-before-partial'")),
}
}
/// setFromBase64/fromBase64 共用入口:string 类型检查先于选项读取,
/// 选项读完跑 FromBase64 状态机,已解码字节经 `write` 逐块写出;
/// `max_len` 为 None 时静态方法无界。
fn ta_decode_base64<H: VmHost>(
vm: &mut H, string: JsValue, options: JsValue, max_len: Option<usize>, write: &mut dyn FnMut(usize, u8),
) -> Result<(usize, usize), JsValue> {
if !string.is_string() {
return Err(type_error(vm, "string argument required"));
}
let opts = ta_options_object(vm, options)?;
let mut alphabet = crate::ta_codec::Base64Alphabet::Standard;
let mut handling = crate::ta_codec::LastChunkHandling::Loose;
if let Some(opts_val) = opts {
let obj = unsafe { &*opts_val.as_js_object_ptr() };
// SAFETY: opts_val 来自 ToObject 成功路径,必为活动对象指针。
alphabet = ta_alphabet_option(vm, obj, opts_val)?;
handling = ta_handling_option(vm, obj, opts_val)?;
}
let units = vm.string_units(string).into_owned();
match crate::ta_codec::decode_base64(&units, alphabet, handling, max_len, write) {
Ok(r) => Ok(r),
Err(_) => Err(crate::error::create_syntax_error(vm, "invalid base64 input")),
}
}
/// setFrom* 结果对象 `{ read, written }`:普通对象 + 两数据属性。
fn ta_read_written_result<H: VmHost>(vm: &mut H, read: usize, written: usize) -> JsValue {
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
let read_si = vm.kernel_core().perm_interner().intern("read").0;
let written_si = vm.kernel_core().perm_interner().intern("written").0;
let obj_ref = unsafe { &mut *obj };
vm.set_or_create_prop_value(obj_ref, read_si, JsValue::int(read as i32));
vm.set_or_create_prop_value(obj_ref, written_si, JsValue::int(written as i32));
JsValue::from_js_object(obj)
}
/// `%Uint8Array%.prototype.toBase64(options)`:ValidateUint8Array 先于选项
/// 副作用,按字母表与 omitPadding 编码视图字节。
pub fn uint8array_to_base64<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(validate_uint8_array(vm, this_val));
let opts = native_try!(ta_options_object(vm, if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }));
let mut alphabet = crate::ta_codec::Base64Alphabet::Standard;
let mut omit_padding = false;
if let Some(opts_val) = opts {
let obj = unsafe { &*opts_val.as_js_object_ptr() };
// SAFETY: opts_val 来自 ToObject 成功路径,必为活动对象指针。
alphabet = native_try!(ta_alphabet_option(vm, obj, opts_val));
let key_si = vm.string_key_si("omitPadding");
let v = native_try!(vm
.ordinary_get(obj, key_si, opts_val)
.map_err(|e| crate::iterator::engine_error(vm, &e)));
omit_padding = ta_to_boolean(v);
}
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
let buffer = unsafe { &*buffer_ptr };
// SAFETY: buffer_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。
NativeResult::Ok(vm.new_string_owned(crate::ta_codec::encode_base64(
&buffer[view.byte_offset..view.byte_offset + view.length],
alphabet,
omit_padding,
)))
}
/// `%Uint8Array%.prototype.toHex()`:ValidateUint8Array 后编码小写两位 hex。
pub fn uint8array_to_hex<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(validate_uint8_array(vm, this_val));
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
let buffer = unsafe { &*buffer_ptr };
// SAFETY: buffer_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。
NativeResult::Ok(
vm.new_string_owned(crate::ta_codec::encode_hex(&buffer[view.byte_offset..view.byte_offset + view.length])),
)
}
/// `%Uint8Array%.prototype.setFromBase64(string, options)`:解码写入视图,
/// 返回 `{ read, written }`;前块已写入后遇错抛 SyntaxError(前字节保留)。
pub fn uint8array_set_from_base64<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(validate_uint8_array(vm, this_val));
let string = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let options = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
let buffer = unsafe { &mut *buffer_ptr };
// SAFETY: buffer_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。
let start = view.byte_offset;
let (read, written) = native_try!(ta_decode_base64(vm, string, options, Some(view.length), &mut |idx, b| {
buffer[start + idx] = b;
}));
NativeResult::Ok(ta_read_written_result(vm, read, written))
}
/// `%Uint8Array%.prototype.setFromHex(string)`:无选项;奇数长度最前判定
/// (零写入)抛 SyntaxError;坏字符保留前字节写入后抛。
pub fn uint8array_set_from_hex<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let view = native_try!(validate_uint8_array(vm, this_val));
let string = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
if !string.is_string() {
return NativeResult::Err(type_error(vm, "string argument required"));
}
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
let buffer = unsafe { &mut *buffer_ptr };
// SAFETY: buffer_ptr 来自活动 ArrayBuffer 对象,视图范围由构造保证界内。
let start = view.byte_offset;
let units = vm.string_units(string).into_owned();
let (read, written) = match crate::ta_codec::decode_hex(&units, Some(view.length), &mut |idx, b| {
buffer[start + idx] = b;
}) {
Ok(r) => r,
Err(_) => return NativeResult::Err(crate::error::create_syntax_error(vm, "invalid hex input")),
};
NativeResult::Ok(ta_read_written_result(vm, read, written))
}
/// `Uint8Array.fromBase64(string, options)`:不读 this,结果 proto 恒为
/// %Uint8Array%.prototype(不经 species/构造器)。
pub fn uint8array_from_base64<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let string = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let options = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let mut bytes = Vec::new();
native_try!(ta_decode_base64(vm, string, options, None, &mut |_, b| bytes.push(b)));
let len = bytes.len();
let buffer = JsValue::from_js_object(new_array_buffer(vm, bytes));
NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, TypedArrayKind::Uint8, buffer, 0, len)))
}
/// `Uint8Array.fromHex(string)`:不读 this;奇数长度最前判定抛 SyntaxError。
pub fn uint8array_from_hex<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let string = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
if !string.is_string() {
return NativeResult::Err(type_error(vm, "string argument required"));
}
let units = vm.string_units(string).into_owned();
let mut bytes = Vec::new();
match crate::ta_codec::decode_hex(&units, None, &mut |_, b| bytes.push(b)) {
Ok(_) => {}
Err(_) => return NativeResult::Err(crate::error::create_syntax_error(vm, "invalid hex input")),
}
let len = bytes.len();
let buffer = JsValue::from_js_object(new_array_buffer(vm, bytes));
NativeResult::Ok(JsValue::from_js_object(create_typed_array(vm, TypedArrayKind::Uint8, buffer, 0, len)))
}
+12 -9
View File
@@ -159,11 +159,12 @@ fn make_typed_array_abstract_ctor(
} }
pub(crate) fn make_typed_array_family( pub(crate) fn make_typed_array_family(
string_forge: &PermInterner, shape_forge: &ShapeForge, labels: BuiltinLabels, object_proto: &P<JsObject>, string_forge: &PermInterner, shape_forge: &ShapeForge, labels: BuiltinLabels, array_proto: &P<JsObject>,
) -> TypedArrayFamily { ) -> TypedArrayFamily {
let obj_proto_val = JsValue::from_js_object(object_proto.as_ptr() as *mut JsObject); let array_proto_val = JsValue::from_js_object(array_proto.as_ptr() as *mut JsObject);
// 给共享原型开 "constructor" 槽位(占位值在 wire 时填抽象构造器)。 // 给共享原型开 "constructor" 槽位(占位值在 wire 时填抽象构造器);
let mut typed_array_proto_obj = JsObject::new_empty(EMPTY_SHAPE_ID, obj_proto_val); // [[Prototype]] 按规范接 Array.prototype(toString 经绑定层共享同一函数对象)。
let mut typed_array_proto_obj = JsObject::new_empty(EMPTY_SHAPE_ID, array_proto_val);
let ctor_si = labels.constructor; let ctor_si = labels.constructor;
let proto_shape = shape_forge.make_shape(typed_array_proto_obj.shape_id(), ctor_si); let proto_shape = shape_forge.make_shape(typed_array_proto_obj.shape_id(), ctor_si);
typed_array_proto_obj.set_shape_id(proto_shape); typed_array_proto_obj.set_shape_id(proto_shape);
@@ -252,9 +253,9 @@ fn set_proto_if_changed(obj: &P<JsObject>, proto: JsValue) {
/// TypedArray 家族共享对),经 `wire_ctor_proto` 覆盖占位槽; /// TypedArray 家族共享对),经 `wire_ctor_proto` 覆盖占位槽;
/// 2. 23 个非 TypedArray 构造器的 `[[Prototype]]` → Function.prototype /// 2. 23 个非 TypedArray 构造器的 `[[Prototype]]` → Function.prototype
/// (标准内置函数对象均继承 Function.prototype); /// (标准内置函数对象均继承 Function.prototype);
/// 3. 25 个非 Object 原型的 `[[Prototype]]` → Object.prototype,另含 /// 3. 24 个非 Object 原型的 `[[Prototype]]` → Object.prototype,另含
/// Temporal 命名空间对象、Temporal.now、Console 单例与 Math/JSON /// Temporal 命名空间对象、Temporal.now、Console 单例与 Math/JSON
/// 命名空间对象; /// 命名空间对象;%TypedArray% 共享原型例外接 Array.prototype;
/// 4. %IteratorPrototype% → Object.prototype,6 个集合迭代器原型 /// 4. %IteratorPrototype% → Object.prototype,6 个集合迭代器原型
/// → %IteratorPrototype%; /// → %IteratorPrototype%;
/// 5. TypedArray 家族:11 个具体原型 → %TypedArray% 共享原型,11 个具体 /// 5. TypedArray 家族:11 个具体原型 → %TypedArray% 共享原型,11 个具体
@@ -337,7 +338,7 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) {
} }
let obj_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject); let obj_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject);
let non_object_protos: [&P<JsObject>; 25] = [ let non_object_protos: [&P<JsObject>; 24] = [
&world.array_proto, &world.array_proto,
&world.function_proto, &world.function_proto,
&world.string_proto, &world.string_proto,
@@ -351,7 +352,6 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) {
&world.regexp_proto, &world.regexp_proto,
&world.array_buffer_proto, &world.array_buffer_proto,
&world.data_view_proto, &world.data_view_proto,
&world.typed_array_proto,
&world.instant_proto, &world.instant_proto,
&world.plain_date_proto, &world.plain_date_proto,
&world.plain_time_proto, &world.plain_time_proto,
@@ -367,6 +367,9 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) {
for proto in &non_object_protos { for proto in &non_object_protos {
set_proto_if_changed(proto, obj_proto_val); set_proto_if_changed(proto, obj_proto_val);
} }
// %TypedArray%.prototype → Array.prototype(toString 语义经此继承)。
let array_proto_val = JsValue::from_js_object(world.array_proto.as_ptr() as *mut JsObject);
set_proto_if_changed(&world.typed_array_proto, array_proto_val);
// Temporal 命名空间对象(非构造器)继承 Object.prototype。 // Temporal 命名空间对象(非构造器)继承 Object.prototype。
set_proto_if_changed(&world.temporal_object, obj_proto_val); set_proto_if_changed(&world.temporal_object, obj_proto_val);
@@ -468,7 +471,7 @@ impl BuiltinWorld {
let (array_buffer_proto, array_buffer_constructor) = let (array_buffer_proto, array_buffer_constructor) =
make_named_pair(string_forge, shape_forge, labels, "ArrayBuffer"); make_named_pair(string_forge, shape_forge, labels, "ArrayBuffer");
let (data_view_proto, data_view_constructor) = make_named_pair(string_forge, shape_forge, labels, "DataView"); let (data_view_proto, data_view_constructor) = make_named_pair(string_forge, shape_forge, labels, "DataView");
let typed_arrays = make_typed_array_family(string_forge, shape_forge, labels, &object_proto); let typed_arrays = make_typed_array_family(string_forge, shape_forge, labels, &array_proto);
let sym_match = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let sym_match = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let sym_replace = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let sym_replace = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
+1 -1
View File
@@ -311,7 +311,7 @@ impl BuiltinWorld {
(current.data_view_proto.clone(), current.data_view_constructor.clone()) (current.data_view_proto.clone(), current.data_view_constructor.clone())
}; };
let typed_arrays = if dirty.typed_array_family { let typed_arrays = if dirty.typed_array_family {
make_typed_array_family(string_forge, shape_forge, labels, &object_proto) make_typed_array_family(string_forge, shape_forge, labels, &array_proto)
} else { } else {
TypedArrayFamily { TypedArrayFamily {
typed_array_proto: current.typed_array_proto.clone(), typed_array_proto: current.typed_array_proto.clone(),
@@ -238,6 +238,103 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
oxide_builtins::typed_array::typed_array_to_string_tag_getter::<crate::vm::Vm> as *const (), oxide_builtins::typed_array::typed_array_to_string_tag_getter::<crate::vm::Vm> as *const (),
); );
// 共享原型的 own toString 与 Array.prototype.toString 共享同一函数对象
// (规范:同一内置函数对象;身份与描述符均按 own 属性验收)。
let to_string_si = core.perm_interner().intern("toString").0;
let array_proto_ref = unsafe { &*session.builtin_world().array_proto.as_ptr() };
// SAFETY: array_proto 由 BuiltinWorld 构造期建立,session 存活期内恒有效。
if let Some(pos) = core.shape_forge().lookup_position(array_proto_ref.shape_id(), to_string_si) {
let value = array_proto_ref.get_prop_at(pos);
let new_shape = core.shape_forge().make_shape(shared_proto.shape_id(), to_string_si);
shared_proto.set_shape_id(new_shape);
let new_pos = shared_proto.push_prop(value);
shared_proto.set_data_meta(new_pos, PropAttributes::new(true, false, true));
shared_proto.bump_generation();
}
// base64/hex 四原型法挂 Uint8Array 专属原型(Uint8ClampedArray 经链继承、
// kind 校验拒绝);两静态法挂 Uint8Array 构造器。
let world = session.builtin_world();
let sh = core.shape_forge().as_ref();
let sf = core.perm_interner().as_ref();
let u8_proto_ptr = session.builtin_world().uint8array_proto.as_ptr() as *mut JsObject;
let u8_proto = unsafe { &mut *u8_proto_ptr };
// SAFETY: 指针来自 session 存活期内的 BuiltinWorld 对象。
let _ = world.bind_method(
u8_proto,
sh,
sf,
"toBase64",
unsafe {
oxide_types::object::NativeFnPtr::from_raw(
oxide_builtins::typed_array::uint8array_to_base64::<crate::vm::Vm> as *const (),
)
},
0,
);
let _ = world.bind_method(
u8_proto,
sh,
sf,
"toHex",
unsafe {
oxide_types::object::NativeFnPtr::from_raw(
oxide_builtins::typed_array::uint8array_to_hex::<crate::vm::Vm> as *const (),
)
},
0,
);
let _ = world.bind_method(
u8_proto,
sh,
sf,
"setFromBase64",
unsafe {
oxide_types::object::NativeFnPtr::from_raw(
oxide_builtins::typed_array::uint8array_set_from_base64::<crate::vm::Vm> as *const (),
)
},
1,
);
let _ = world.bind_method(
u8_proto,
sh,
sf,
"setFromHex",
unsafe {
oxide_types::object::NativeFnPtr::from_raw(
oxide_builtins::typed_array::uint8array_set_from_hex::<crate::vm::Vm> as *const (),
)
},
1,
);
let u8_ctor_ptr = session.builtin_world().uint8array_constructor.as_ptr() as *mut JsObject;
let u8_ctor = unsafe { &mut *u8_ctor_ptr };
let _ = world.bind_method(
u8_ctor,
sh,
sf,
"fromBase64",
unsafe {
oxide_types::object::NativeFnPtr::from_raw(
oxide_builtins::typed_array::uint8array_from_base64::<crate::vm::Vm> as *const (),
)
},
1,
);
let _ = world.bind_method(
u8_ctor,
sh,
sf,
"fromHex",
unsafe {
oxide_types::object::NativeFnPtr::from_raw(
oxide_builtins::typed_array::uint8array_from_hex::<crate::vm::Vm> as *const (),
)
},
1,
);
bind_typed_array_abstract_ctor(core, session); bind_typed_array_abstract_ctor(core, session);
// 把 `%TypedArray%` 抽象构造器暴露为全局 `TypedArray`(具体构造器的 [[Prototype]])。 // 把 `%TypedArray%` 抽象构造器暴露为全局 `TypedArray`(具体构造器的 [[Prototype]])。
@@ -0,0 +1,430 @@
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
vm.run(&Arc::new(module))
}
fn to_str(vm: &Vm, val: JsValue) -> String {
vm.lookup_str(val).unwrap_or_default()
}
#[test]
fn to_base64_rfc4648_vectors() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
var vectors = [
[new Uint8Array([]), ""],
[new Uint8Array([102]), "Zg=="],
[new Uint8Array([102, 111]), "Zm8="],
[new Uint8Array([102, 111, 111]), "Zm9v"],
[new Uint8Array([102, 111, 111, 98]), "Zm9vYg=="],
[new Uint8Array([102, 111, 111, 98, 97]), "Zm9vYmE="],
[new Uint8Array([102, 111, 111, 98, 97, 114]), "Zm9vYmFy"]
];
for (var i = 0; i < vectors.length; i++) {
if (vectors[i][0].toBase64() !== vectors[i][1]) return "vector " + i;
}
return true;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn to_base64_alphabet_and_omit_padding() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
var a = new Uint8Array([199, 239, 242]);
if (a.toBase64() !== "x+/y") return "std";
if (a.toBase64({ alphabet: "base64url" }) !== "x-_y") return "url";
var b = new Uint8Array([199, 239]);
if (b.toBase64() !== "x+8=") return "pad";
if (b.toBase64({ omitPadding: true }) !== "x+8") return "omit";
if (b.toBase64({ omitPadding: 1 }) !== "x+8") return "tobool";
if (b.toBase64({ omitPadding: 0 }) !== "x+8=") return "tobool0";
if (new Uint8Array([255]).toBase64({ alphabet: "base64url", omitPadding: true }) !== "_w") return "urlomit";
return true;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn to_base64_option_coercion_and_receiver() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
// 装箱串与抛错 toString 对象均 TypeError(不做 ToPrimitive)。
var t1 = (function() { try { new Uint8Array(2).toBase64({ alphabet: Object("base64") }); return false; } catch (e) { return e instanceof TypeError; } })();
var t2 = (function() { try { new Uint8Array(2).toBase64({ alphabet: { toString: function() { throw 1; } } }); return false; } catch (e) { return e instanceof TypeError; } })();
var t3 = (function() { try { new Uint8Array(2).toBase64({ alphabet: "other" }); return false; } catch (e) { return e instanceof TypeError; } })();
// 选项 getter 触发恰一次,且其副作用在数据读取前生效。
var accesses = 0;
var array = new Uint8Array([0]);
var options = {};
Object.defineProperty(options, "alphabet", {
get: function() { accesses += 1; array[0] = 255; return "base64"; }
});
var result = array.toBase64(options);
return t1 && t2 && t3 && accesses === 1 && result === "/w==" && array[0] === 255;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn to_hex_and_receiver_validation() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
if (new Uint8Array([]).toHex() !== "") return "empty";
if (new Uint8Array([102, 111, 111, 98, 97, 114]).toHex() !== "666f6f626172") return "vector";
// 非 Uint8 接收者抛 TypeError(kind 校验先于任何副作用)。
var t1 = (function() { try { Uint8Array.prototype.toHex.call(new Uint8ClampedArray(2), {}); return false; } catch (e) { return e instanceof TypeError; } })();
var t2 = (function() { try { Uint8Array.prototype.toBase64.call(new Int8Array(2)); return false; } catch (e) { return e instanceof TypeError; } })();
// 非对象接收者同样抛。
var t3 = (function() { try { Uint8Array.prototype.toBase64.call([]); return false; } catch (e) { return e instanceof TypeError; } })();
return t1 && t2 && t3;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn set_from_base64_results_and_target_size() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
// 标准向量:read/written 与内容。
var pairs = [
["", 0, []],
["Zg==", 4, [102]],
["Zm8=", 4, [102, 111]],
["Zm9v", 4, [102, 111, 111]],
["Zm9vYg==", 8, [102, 111, 111, 98]]
];
for (var i = 0; i < pairs.length; i++) {
var target = new Uint8Array(4);
var r = target.setFromBase64(pairs[i][0]);
if (r.read !== pairs[i][1] || r.written !== pairs[i][2].length) return "rw " + i;
for (var k = 0; k < 4; k++) {
var expect = k < pairs[i][2].length ? pairs[i][2][k] : 0;
if (target[k] !== expect) return "body " + i;
}
}
// 小目标:块前停(第 2 块不消费)、恰好补齐。
var t1 = new Uint8Array([255, 255, 255, 255, 255]);
var r1 = t1.setFromBase64("Zm9vYmFy");
if (r1.read !== 4 || r1.written !== 3 || t1[3] !== 255) return "small";
var t2 = new Uint8Array([255, 255, 255, 255, 255]);
var r2 = t2.setFromBase64("Zm9vYmE=");
if (r2.read !== 8 || r2.written !== 5) return "exact";
// 空白跳过且计入 read。
var t3 = new Uint8Array(3);
var r3 = t3.setFromBase64("Z g==");
if (r3.read !== 5 || r3.written !== 1 || t3[0] !== 102) return "ws";
return true;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn set_from_base64_last_chunk_handling() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
function ok(input, handling, read, written) {
var t = new Uint8Array(6);
var r = t.setFromBase64(input, handling === undefined ? undefined : { lastChunkHandling: handling });
return r.read === read && r.written === written;
}
function throws(input, handling) {
try {
new Uint8Array(6).setFromBase64(input, handling === undefined ? undefined : { lastChunkHandling: handling });
return false;
} catch (e) { return e instanceof SyntaxError; }
}
if (!ok("ZXhhZg==", undefined, 8, 4)) return "pad default";
if (!ok("ZXhhZg==", "strict", 8, 4)) return "pad strict";
if (!ok("ZXhhZg", "loose", 6, 4)) return "nopad loose";
if (!ok("ZXhhZg", "stop-before-partial", 4, 3)) return "nopad sbp";
if (!throws("ZXhhZg", "strict")) return "nopad strict";
if (!ok("ZXhhZh==", undefined, 8, 4)) return "bits default";
if (!throws("ZXhhZh==", "strict")) return "bits strict";
if (!throws("ZXhhZg=")) return "partialpad default";
if (!ok("ZXhhZg=", "stop-before-partial", 4, 3)) return "partialpad sbp";
if (!throws("ZXhhZg=", "strict")) return "partialpad strict";
if (!throws("ZXhhZg===")) return "excesspad default";
if (!throws("ZXhhZg===", "stop-before-partial")) return "excesspad sbp";
// 非法字符(含非 ASCII 空格)。
if (!throws("Zm.9v")) return "illegal";
if (!throws("Zg\u{00A0}==")) return "nbsp";
return true;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn set_from_base64_subarray_and_option_order() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
// subarray 偏移生效。
var base = new Uint8Array([255, 255, 255, 255, 255, 255, 255]);
var sub = base.subarray(2, 5);
var r = sub.setFromBase64("Zm9vYmFy");
if (r.read !== 4 || r.written !== 3) return "sub rw";
if (base[2] !== 102 || base[3] !== 111 || base[4] !== 111 || base[5] !== 255) return "sub body";
// 接收者校验先于选项副作用:抛错 getter 零触发。
var touched = 0;
var options = {};
Object.defineProperty(options, "alphabet", {
get: function() { touched += 1; throw new TypeError("no"); }
});
try {
Uint8Array.prototype.setFromBase64.call(new Int8Array(2), "Zg==", options);
return false;
} catch (e) {
if (!(e instanceof TypeError)) return "type";
}
if (touched !== 0) return "touched";
// string 非原始串抛 TypeError,选项零触发。
var optTouched = 0;
var touchy = {};
Object.defineProperty(touchy, "alphabet", {
get: function() { optTouched += 1; throw 1; }
});
try {
new Uint8Array(2).setFromBase64({ toString: function() { throw 1; } }, touchy);
return false;
} catch (e) {
if (!(e instanceof TypeError)) return "str type";
}
return optTouched === 0;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn set_from_base64_writes_up_to_error() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
var t = new Uint8Array([255, 255, 255, 255, 255]);
var threw = false;
try { t.setFromBase64("MjYyZm.9v"); } catch (e) { threw = e instanceof SyntaxError; }
if (!threw) return "throw";
return t[0] === 50 && t[1] === 54 && t[2] === 50 && t[3] === 255 && t[4] === 255;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn set_from_hex_results_and_odd_length() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
var t = new Uint8Array(3);
var r = t.setFromHex("aabbcc");
if (r.read !== 6 || r.written !== 3) return "rw";
if (t[0] !== 170 || t[1] !== 187 || t[2] !== 204) return "body";
// 大小写宽容。
t.fill(0);
var r2 = t.setFromHex("AABB");
if (r2.read !== 4 || t[0] !== 170 || t[1] !== 187) return "case";
// 奇数长度最前判定:零写入抛 SyntaxError(含零长目标)。
var threw1 = false;
try { new Uint8Array(0).setFromHex("1"); } catch (e) { threw1 = e instanceof SyntaxError; }
if (!threw1) return "odd zero";
var t3 = new Uint8Array(3);
var threw2 = false;
try { t3.setFromHex("aaa"); } catch (e) { threw2 = e instanceof SyntaxError; }
if (!threw2 || t3[0] !== 0) return "odd write";
// 坏字符保留前字节。
var t4 = new Uint8Array(3);
var threw3 = false;
try { t4.setFromHex("aaag"); } catch (e) { threw3 = e instanceof SyntaxError; }
if (!threw3 || t4[0] !== 170 || t4[1] !== 0) return "uptoerr";
return true;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn from_base64_static() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
var vectors = [
["", 0], ["Zg==", 1], ["Zm8=", 2], ["Zm9v", 3],
["Zm9vYg==", 4], ["Zm9vYmE=", 5], ["Zm9vYmFy", 6]
];
for (var i = 0; i < vectors.length; i++) {
var arr = Uint8Array.fromBase64(vectors[i][0]);
if (arr.length !== vectors[i][1]) return "len " + i;
if (arr.buffer.byteLength !== vectors[i][1]) return "buf " + i;
if (Object.getPrototypeOf(arr) !== Uint8Array.prototype) return "proto " + i;
}
// 不读 this:子类构造器不触发,结果 proto 恒 %Uint8Array%.prototype。
var Subclass = new Function("Uint8Array", "class S extends Uint8Array { constructor() { throw new Error('ctor'); } } return S;")(Uint8Array);
var fromSubclass = Subclass.fromBase64("Zg==");
if (Object.getPrototypeOf(fromSubclass) !== Uint8Array.prototype) return "sub proto";
// 字符串检查先于选项。
var threw1 = false;
try { Uint8Array.fromBase64(42, { alphabet: Object("base64") }); } catch (e) { threw1 = e instanceof TypeError; }
if (!threw1) return "strcheck";
// 尾块矩阵(fromBase64 无 maxLength 面)。
if (!throws("A")) return "A";
if (Uint8Array.fromBase64("A", { lastChunkHandling: "stop-before-partial" }).length !== 0) return "A sbp";
if (Uint8Array.fromBase64("ABCDA", { lastChunkHandling: "stop-before-partial" }).length !== 3) return "ABCDA sbp";
var t5 = Uint8Array.fromBase64("AA=", { lastChunkHandling: "stop-before-partial" });
if (t5.length !== 0) return "AA= sbp";
var t6 = Uint8Array.fromBase64("ZXhhZg", { lastChunkHandling: "stop-before-partial" });
if (t6.length !== 3) return "ZXhhZg sbp";
var threw2 = false;
try { Uint8Array.fromBase64("ZXhhZg", { lastChunkHandling: "strict" }); } catch (e) { threw2 = e instanceof SyntaxError; }
if (!threw2) return "strict";
return true;
function throws(s) {
try { Uint8Array.fromBase64(s); return false; } catch (e) { return e instanceof SyntaxError; }
}
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn from_hex_static() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
var arr = Uint8Array.fromHex("666f6f626172");
if (arr.length !== 6 || arr.buffer.byteLength !== 6) return "len";
if (Object.getPrototypeOf(arr) !== Uint8Array.prototype) return "proto";
if (arr[0] !== 102 || arr[5] !== 114) return "body";
var threw = false;
try { Uint8Array.fromHex("a"); } catch (e) { threw = e instanceof SyntaxError; }
if (!threw) return "odd";
var threw2 = false;
try { Uint8Array.fromHex({ toString: function() { throw 1; } }); } catch (e) { threw2 = e instanceof TypeError; }
if (!threw2) return "type";
return true;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn typed_array_prototype_to_string_chain() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
// own toString 与 Array.prototype.toString 同一函数对象。
if (TypedArray.prototype.toString !== Array.prototype.toString) return "identity";
var d = Object.getOwnPropertyDescriptor(TypedArray.prototype, "toString");
if (d === undefined) return "not own";
if (d.writable !== true || d.enumerable !== false || d.configurable !== true) return "attrs";
// proto 链接 Array.prototype。
if (Object.getPrototypeOf(TypedArray.prototype) !== Array.prototype) return "chain";
// 实例 toString 走 Array 语义(经 TA 自身 join)。
if (new Uint8Array([1, 2, 3]).toString() !== "1,2,3") return "instance";
if (new Int16Array([7]).toString() !== "7") return "instance16";
return true;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn clamped_inherits_base64_methods_but_rejects() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
// clamped 接收者被 kind 校验拒绝(原型链当前不继承方法,
// 调用不可见成员同样 TypeError)。
var threw1 = false;
try { new Uint8ClampedArray(2).toBase64(); } catch (e) { threw1 = e instanceof TypeError; }
var threw2 = false;
try { new Uint8ClampedArray(2).setFromHex("aa"); } catch (e) { threw2 = e instanceof TypeError; }
var threw3 = false;
try { new Uint8ClampedArray(2).setFromBase64("Zg=="); } catch (e) { threw3 = e instanceof TypeError; }
var threw4 = false;
try { new Uint8ClampedArray(2).toHex(); } catch (e) { threw4 = e instanceof TypeError; }
return threw1 && threw2 && threw3 && threw4;
})()"#,
)
.unwrap();
assert!(result.as_bool());
}
#[test]
fn base64_method_metadata() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
r#"(function() {
function meta(obj, name, length) {
var m = obj[name];
if (typeof m !== "function") return name + " missing";
if (m.name !== name) return name + " name";
if (m.length !== length) return name + " length";
var d = Object.getOwnPropertyDescriptor(obj, name);
if (d.enumerable !== false || d.writable !== true || d.configurable !== true) return name + " attrs";
return null;
}
var bad = meta(Uint8Array.prototype, "toBase64", 0);
if (bad) return bad;
bad = meta(Uint8Array.prototype, "toHex", 0);
if (bad) return bad;
bad = meta(Uint8Array.prototype, "setFromBase64", 1);
if (bad) return bad;
bad = meta(Uint8Array.prototype, "setFromHex", 1);
if (bad) return bad;
bad = meta(Uint8Array, "fromBase64", 1);
if (bad) return bad;
bad = meta(Uint8Array, "fromHex", 1);
return bad === null;
})()"#,
)
.unwrap();
assert!(result.as_bool(), "metadata: {}", to_str(&vm, result));
}