// ===== decode:ETF 字节流 → EtfTerm =====
// 纯核零依赖:Bytes/String 即 core builtin;真帧金罐守护(见 golden_test)。
///|
pub(all) suberror EtfError {
Truncated(String)
Malformed(String)
DepthExceeded(String) // 嵌套超限(恶意深嵌套帧——递归栈爆防护)
}
///|
/// 读入口:带 VERSION_MAGIC 的整帧(0x83 起);无 magic 诚实拒
pub fn decode(
data : Bytes,
attab~ : Array[String] = [],
) -> EtfTerm raise EtfError {
if data.length() < 1 {
raise EtfError::Truncated("empty")
}
if data[0].to_int() != VERSION_MAGIC {
raise EtfError::Malformed("missing 0x83 magic")
}
let pair = dec(data, 1, DEPTH_LIMIT, attab)
pair.0
}
///|
/// 读入口(无 magic,段内):返回 (term, 下一 pos)——组合用
pub fn decode_term(
data : Bytes,
pos : Int32,
attab~ : Array[String] = [],
) -> (EtfTerm, Int32) raise EtfError {
dec(data, pos, DEPTH_LIMIT, attab)
}
///|
/// 嵌套深度上限(恶意深嵌套帧=递归栈爆防护——dist 直连暴露面;
/// dist 面 tuple/list 实测深 3-5,64 远超业务余量)
const DEPTH_LIMIT : Int32 = 64
///|
/// 流内严格解码(131 流内=错帧——版本字节只属流头/独立流入口 decode;
/// A3 同流多项契约:dist header 后续项省略 131,现 131=线形违)
fn dec(
data : Bytes,
pos : Int32,
depth : Int32,
attab : Array[String],
) -> (EtfTerm, Int32) raise EtfError {
if depth <= 0 {
raise EtfError::DepthExceeded("nesting exceeds \{DEPTH_LIMIT} at \{pos}")
}
if pos >= data.length() {
raise EtfError::Truncated("tag at \{pos}")
}
let tag = data[pos].to_int()
if tag == VERSION_MAGIC {
raise EtfError::Malformed(
"spurious VERSION_MAGIC at \{pos}(流内只属流头)",
)
}
match tag {
TAG_SMALL_INTEGER => (EtfTerm::EInt(data[pos + 1].to_int()), pos + 2)
TAG_INTEGER => {
let v = rd_u32(data, pos + 1)
if v < 0 {
// u32 高位=Int32 符号位(静默负溢 bug 面——EInt 值域 Int32 诚实钉死)
raise EtfError::Malformed("INTEGER exceeds Int32 domain")
}
(EtfTerm::EInt(v), pos + 5)
}
TAG_NEW_FLOAT => {
// 8B IEEE754 BE——按位组装 Double(骨架:读 64bit 整数再 reinterpret)
let bits = rd_u64(data, pos + 1)
(EtfTerm::EFloat(bits.reinterpret_as_double()), pos + 9)
}
TAG_ATOM_EXT => {
let n = rd_u16(data, pos + 1)
(EtfTerm::EAtom(latin(data, pos + 3, n)), pos + 3 + n)
}
TAG_ATOM_UTF8 => {
let n = rd_u16(data, pos + 1)
(EtfTerm::EAtom(latin(data, pos + 3, n)), pos + 3 + n)
}
TAG_SMALL_ATOM_UTF8 => {
let n = data[pos + 1].to_int()
(EtfTerm::EAtom(latin(data, pos + 2, n)), pos + 2 + n)
}
TAG_SMALL_TUPLE => {
let arity = data[pos + 1].to_int()
let (items, np) = decode_n(data, pos + 2, arity, depth, attab)
(EtfTerm::ETuple(items), np)
}
TAG_LARGE_TUPLE => {
let arity = rd_u32(data, pos + 1)
let (items, np) = decode_n(data, pos + 5, arity, depth, attab)
(EtfTerm::ETuple(items), np)
}
TAG_NIL => (EtfTerm::ENil, pos + 1)
TAG_STRING => {
// latin1 字节列→String(整数字节快捷形)
let n = rd_u16(data, pos + 1)
(EtfTerm::EString(latin(data, pos + 3, n)), pos + 3 + n)
}
TAG_LIST => {
let n = rd_u32(data, pos + 1)
let (items, np) = decode_n(data, pos + 5, n, depth, attab)
// 尾项必须 ENil(proper list; improper 挂账)
let (tail, np2) = dec(data, np, depth - 1, attab)
if tail is EtfTerm::ENil {
(EtfTerm::EList(items), np2)
} else {
raise EtfError::Malformed("improper list tail")
}
}
TAG_BINARY => {
let n = rd_u32(data, pos + 1)
if pos + 5 + n > data.length() {
raise EtfError::Truncated("binary at \{pos}")
}
(EtfTerm::EBinary(data[pos + 5:pos + 5 + n].to_owned()), pos + 5 + n)
}
TAG_MAP => {
let n = rd_u32(data, pos + 1)
let pairs : Array[(EtfTerm, EtfTerm)] = []
let mut cur = pos + 5
for _ in 0.. {
let (node, np) = decode_atom_item(data, pos + 1, depth - 1, attab)
let id = rd_u32(data, np)
let serial = rd_u32(data, np + 4)
let creation = rd_u32(data, np + 8)
(EtfTerm::EPid(node, id, serial, creation), np + 12)
}
TAG_NEW_PORT => {
let (node, np) = decode_atom_item(data, pos + 1, depth - 1, attab)
let id = rd_u32(data, np)
let creation = rd_u32(data, np + 4)
(EtfTerm::EPort(node, id, creation), np + 8)
}
TAG_NEW_PORT_V2 => {
// OTP26 port 扩展形:node+id u32+serial u32+creation u32(真帧 dump 铁证
// probea@dev 6 端口项 port_idx 键值;serial 位 port 对象占位跳过)
let (node, np) = decode_atom_item(data, pos + 1, depth - 1, attab)
let id = rd_u32(data, np)
let creation = rd_u32(data, np + 8)
(EtfTerm::EPort(node, id, creation), np + 12)
}
TAG_NEW_REFERENCE => {
let idlen = rd_u16(data, pos + 1)
let (node, np) = decode_atom_item(data, pos + 3, depth - 1, attab)
let creation = rd_u32(data, np) // 'Z' 形 creation u32(OTP21+)
let id0 = rd_u32(data, np + 4)
(EtfTerm::ERef(node, id0, 0, creation), np + 4 + 4 * idlen)
}
114 => { // 'r' NEW_REFERENCE_EXT creation u8 形(与 'Z' 只差位宽——规格书 §7)
let idlen = rd_u16(data, pos + 1)
let (node, np) = decode_atom_item(data, pos + 3, depth - 1, attab)
let creation = data[np].to_int()
let id0 = rd_u32(data, np + 1)
(EtfTerm::ERef(node, id0, 0, creation), np + 1 + 4 * idlen)
}
115 => { // 's' SMALL_ATOM(len u8 latin1)
let n = data[pos + 1].to_int()
(EtfTerm::EAtom(latin(data, pos + 2, n)), pos + 2 + n)
}
TAG_SMALL_BIG => {
// 110 SMALL_BIG_EXT:nbytes u8 | sign u8 | digits 小端(big.c bytes_to_big 铁证)
let n = data[pos + 1].to_int()
let sign = data[pos + 2].to_int()
if n > 4 {
raise EtfError::Malformed("bignum exceeds i32 domain (\{n} bytes)")
}
if pos + 3 + n > data.length() {
raise EtfError::Truncated("small_big at \{pos}")
}
if sign > 1 {
raise EtfError::Malformed("bignum sign " + sign.to_string())
}
// 小端组装(digits[0]=最低字节)
let mut v = 0
for i in 0.. 2147483647) {
raise EtfError::Malformed("bignum exceeds i32 domain")
}
let val = if sign == 1 { -v } else { v }
(EtfTerm::EInt(val), pos + 3 + n)
}
TAG_ATOM_CACHE_REF => {
// 'R' ATOM_CACHE_REF:u8 槽号→查分发头 attab(真帧 52 00/52 01 大量;
// attab 空或越界=诚实拒——单帧无 dist header 语境)
if pos + 2 > data.length() {
raise EtfError::Truncated("cache ref at \{pos}")
}
let ix = data[pos + 1].to_int()
if ix >= attab.length() {
raise EtfError::Malformed(
"atom cache ref \{ix} 越界(attab \{attab.length()})",
)
}
(EtfTerm::EAtom(attab[ix]), pos + 2)
}
other => raise EtfError::Malformed("unsupported ETF tag \{other} at \{pos}")
}
}
///|
fn decode_n(
data : Bytes,
pos : Int32,
count : Int32,
depth : Int32,
attab : Array[String],
) -> (Array[EtfTerm], Int32) raise EtfError {
let items : Array[EtfTerm] = []
let mut cur = pos
for _ in 0.. (String, Int32) raise EtfError {
let (t, np) = dec(data, pos, depth - 1, attab)
match t {
EtfTerm::EAtom(s) => (s, np)
_ => raise EtfError::Malformed("node term not atom")
}
}
///|
fn rd_u16(data : Bytes, pos : Int32) -> Int32 raise EtfError {
if pos + 2 > data.length() {
raise EtfError::Truncated("u16 at \{pos}")
}
(data[pos].to_int() << 8) | data[pos + 1].to_int()
}
///|
fn rd_u32(data : Bytes, pos : Int32) -> Int32 raise EtfError {
if pos + 4 > data.length() {
raise EtfError::Truncated("u32 at \{pos}")
}
(data[pos].to_int() << 24) |
(data[pos + 1].to_int() << 16) |
(data[pos + 2].to_int() << 8) |
data[pos + 3].to_int()
}
///|
fn rd_u64(data : Bytes, pos : Int32) -> UInt64 raise EtfError {
if pos + 8 > data.length() {
raise EtfError::Truncated("u64 at \{pos}")
}
let mut v : UInt64 = 0UL
for k in 0..<8 {
v = (v << 8) | data[pos + k].to_int().to_uint64()
}
v
}
///|
/// latin-1/ASCII 域字节串→String(骨架 v0:原子/节点名全 ASCII 域;零 @utf8 依赖——core 零依赖守恒)
fn latin(data : Bytes, pos : Int32, len : Int32) -> String raise EtfError {
if pos + len > data.length() {
raise EtfError::Truncated("latin[\{len}] at \{pos} over \{data.length()}")
}
let sb = StringBuilder()
let mut i = pos
while i < pos + len {
match data[i].to_int().to_char() {
Some(c) => sb.write_char(c)
None => sb.write_char('?')
}
i = i + 1
}
sb.to_string()
}