use crate::bit_reader::{bit_width, BitRead, BitReader};
use crate::document::{DepEntry, Document, Entry, Record, Table, Tag, Value};
use crate::parse::remap::FixedWidthIntArray;
use std::collections::HashMap;
enum NodeKind {
Null,
Leaf,
Array,
Map,
}
impl NodeKind {
fn from_bits(bits: u32) -> Self {
match bits & 3 {
0 => Self::Null,
1 => Self::Leaf,
2 => Self::Array,
3 => Self::Map,
_ => unreachable!(),
}
}
}
struct NodeType {
kind: NodeKind,
carries_key: bool,
}
impl NodeType {
fn from_flags(flags: u32) -> Self {
let kind = NodeKind::from_bits(flags);
let carries_key = matches!(kind, NodeKind::Map) || (flags & 0x80) != 0;
Self { kind, carries_key }
}
}
struct DecodeContext<'a> {
value_strings: &'a [String],
value_kinds: &'a [String],
key_strings: &'a [String],
header_index_bits: u8,
value_index_bits: u8,
value_kind_bits: u8,
key_index_bits: u8,
type_index_bits: u8,
node_types: Vec<NodeType>,
}
struct TableContext<'a> {
pair_remap: Option<&'a FixedWidthIntArray>,
value_remap: Option<&'a FixedWidthIntArray>,
dep_names: Vec<String>,
dep_index_bits: u8,
}
fn remap_index(remap: Option<&FixedWidthIntArray>, raw: u32, default_bits: u8) -> (u8, u32) {
match remap {
Some(r) if r.is_active() => (r.index_bit_width, r.remap(raw).unwrap_or(raw)),
_ => (default_bits, raw),
}
}
fn read_pair_vec_string(
reader: &mut impl BitRead,
ctx: &DecodeContext,
remap: Option<&FixedWidthIntArray>,
) -> Option<String> {
let (bits, _) = remap_index(remap, 0, ctx.key_index_bits);
let raw_index = reader.read_bits(bits)?;
let (_, mapped) = remap_index(remap, raw_index, ctx.key_index_bits);
if (mapped as usize) < ctx.key_strings.len() {
Some(ctx.key_strings[mapped as usize].clone())
} else {
Some(format!("<key:{}>", mapped))
}
}
fn read_value(
reader: &mut impl BitRead,
ctx: &DecodeContext,
value_remap: Option<&FixedWidthIntArray>,
) -> Option<String> {
let (bits, _) = remap_index(value_remap, 0, ctx.value_index_bits);
let raw_index = reader.read_bits(bits)?;
let kind_index = reader.read_bits(ctx.value_kind_bits)? as usize;
let (_, mapped) = remap_index(value_remap, raw_index, ctx.value_index_bits);
let value_index = mapped as usize;
let value = ctx
.value_strings
.get(value_index)
.cloned()
.unwrap_or_default();
let type_name = ctx
.value_kinds
.get(kind_index)
.map(|s| s.as_str())
.unwrap_or("");
if type_name.is_empty() {
Some(value)
} else {
Some(format!("{}'{}'", type_name, value))
}
}
fn decode_node(
reader: &mut impl BitRead,
ctx: &DecodeContext,
tctx: &TableContext,
record_end_bit: usize,
) -> Option<Value> {
let type_index = reader.read_bits(ctx.type_index_bits)? as usize;
let node_type = ctx.node_types.get(type_index).unwrap_or(&NodeType {
kind: NodeKind::Null,
carries_key: false,
});
let self_key = if node_type.carries_key {
read_pair_vec_string(reader, ctx, tctx.pair_remap)?
} else {
String::new()
};
let value = decode_node_value(reader, ctx, tctx, record_end_bit, &node_type.kind)?;
wrap_with_self_key(self_key, value)
}
fn decode_node_value(
reader: &mut impl BitRead,
ctx: &DecodeContext,
tctx: &TableContext,
record_end_bit: usize,
kind: &NodeKind,
) -> Option<Value> {
match kind {
NodeKind::Null => Some(Value::Null),
NodeKind::Leaf => Some(Value::Leaf(read_value(reader, ctx, tctx.value_remap)?)),
NodeKind::Array => {
let mut arr = Vec::new();
while reader.position() < record_end_bit {
if !reader.read_bit()? {
break;
}
arr.push(decode_node(reader, ctx, tctx, record_end_bit)?);
}
Some(Value::Array(arr))
}
NodeKind::Map => {
let mut map = HashMap::new();
while reader.position() < record_end_bit {
if !reader.read_bit()? {
break;
}
let k = read_pair_vec_string(reader, ctx, tctx.pair_remap)?;
let v = decode_node(reader, ctx, tctx, record_end_bit)?;
map.insert(k, v);
}
Some(Value::Map(map))
}
}
}
fn wrap_with_self_key(self_key: String, value: Value) -> Option<Value> {
if !self_key.is_empty() && !self_key.eq_ignore_ascii_case("none") {
let mut wrapper = HashMap::new();
wrapper.insert(self_key, value);
Some(Value::Map(wrapper))
} else {
Some(value)
}
}
fn read_packed_name_list(
reader: &mut impl BitRead,
ctx: &DecodeContext,
pair_remap: Option<&FixedWidthIntArray>,
) -> Option<Vec<String>> {
let mut list = Vec::new();
for _ in 0..4096 {
let s = read_pair_vec_string(reader, ctx, pair_remap)?;
if s.is_empty() || s.eq_ignore_ascii_case("none") {
break;
}
list.push(s);
}
Some(list)
}
fn decode_op_value(
reader: &mut impl BitRead,
ctx: &DecodeContext,
tctx: &TableContext,
record_end_bit: usize,
op: u32,
) -> Option<Value> {
match op {
1 => Some(Value::Null),
2 => decode_node(reader, ctx, tctx, record_end_bit),
3 => {
let ref_str = read_pair_vec_string(reader, ctx, tctx.pair_remap)?;
Some(Value::Ref { r#ref: ref_str })
}
_ => Some(Value::Null),
}
}
fn parse_tags(
reader: &mut impl BitRead,
ctx: &DecodeContext,
tctx: &TableContext,
record_end_bit: usize,
) -> Vec<Tag> {
let mut tags = Vec::new();
while reader.position() + 8 <= record_end_bit {
let Some(tag_byte) = reader.read_bits(8) else {
break;
};
let tag_byte = tag_byte as u8;
if tag_byte == b'z' {
break;
}
let tag = match tag_byte {
b'a' => {
read_pair_vec_string(reader, ctx, tctx.pair_remap).map(|pair| Tag::KeyName { pair })
}
b'b' => reader.read_bits(32).map(|value| Tag::U32 { value }),
b'c' => reader.read_bits(32).map(|u32_value| {
let f32_value = f32::from_bits(u32_value);
Tag::F32 {
u32_value,
f32_value,
}
}),
b'd' => read_packed_name_list(reader, ctx, tctx.pair_remap)
.map(|list| Tag::NameListD { list }),
b'e' => read_packed_name_list(reader, ctx, tctx.pair_remap)
.map(|list| Tag::NameListE { list }),
b'f' => read_packed_name_list(reader, ctx, tctx.pair_remap)
.map(|list| Tag::NameListF { list }),
b'p' => decode_node(reader, ctx, tctx, record_end_bit)
.map(|variant| Tag::Variant { variant }),
_ => break,
};
match tag {
Some(t) => tags.push(t),
None => break,
}
}
tags
}
fn parse_entries(
reader: &mut impl BitRead,
ctx: &DecodeContext,
tctx: &TableContext,
record_end_bit: usize,
) -> Vec<Entry> {
let mut entries = Vec::new();
while reader.position() + 2 <= record_end_bit {
let Some(op) = reader.read_bits(2) else {
break;
};
if op == 0 {
break;
}
let Some(key) = read_pair_vec_string(reader, ctx, tctx.pair_remap) else {
break;
};
let Some(value) = decode_op_value(reader, ctx, tctx, record_end_bit, op) else {
break;
};
let dep_entries = parse_dep_entries(reader, ctx, tctx, record_end_bit);
entries.push(Entry {
key,
value,
dep_entries,
});
}
entries
}
fn parse_dep_entries(
reader: &mut impl BitRead,
ctx: &DecodeContext,
tctx: &TableContext,
record_end_bit: usize,
) -> Vec<DepEntry> {
if tctx.dep_names.is_empty() {
return Vec::new();
}
let mut dep_entries = Vec::new();
while reader.position() + 2 <= record_end_bit {
let Some(dep_op) = reader.read_bits(2) else {
break;
};
if dep_op == 0 {
break;
}
let Some(dep_key) = read_pair_vec_string(reader, ctx, tctx.pair_remap) else {
break;
};
let dep_index = if tctx.dep_index_bits > 0 {
reader.read_bits(tctx.dep_index_bits).unwrap_or(0)
} else {
0
};
let dep_table_name = tctx
.dep_names
.get(dep_index as usize)
.cloned()
.unwrap_or_default();
let Some(dep_value) = decode_op_value(reader, ctx, tctx, record_end_bit, dep_op) else {
break;
};
dep_entries.push(DepEntry {
dep_table_name,
dep_index,
key: dep_key,
value: dep_value,
});
}
dep_entries
}
fn parse_records(
reader: &mut impl BitRead,
ctx: &DecodeContext,
tctx: &TableContext,
) -> Vec<Record> {
let mut records = Vec::new();
loop {
reader.align_byte();
if !reader.has_bits(32) {
break;
}
let record_start = reader.position();
let Some(record_len_bytes) = reader.read_bits(32) else {
break;
};
if record_len_bytes == 0 {
if reader.has_bits(8) {
reader.read_bits(8);
}
break;
}
let record_end_bit = (record_start + record_len_bytes as usize * 8) & !7;
if record_end_bit > reader.total_bits() {
break;
}
let tags = parse_tags(reader, ctx, tctx, record_end_bit);
let entries = parse_entries(reader, ctx, tctx, record_end_bit);
if reader.position() < record_end_bit {
reader.seek(record_end_bit);
}
records.push(Record { tags, entries });
}
records
}
pub struct DecodeInput<'a> {
pub data: &'a [u8],
pub header_strings: &'a [String],
pub value_strings: &'a [String],
pub value_strings_declared: u32,
pub value_kinds: &'a [String],
pub value_kinds_declared: u32,
pub key_strings: &'a [String],
pub key_strings_declared: u32,
pub row_flags: &'a [u32],
pub binary_offset: usize,
}
pub fn decode_table_data(input: &DecodeInput) -> Option<Document> {
if input.binary_offset >= input.data.len() {
return None;
}
let binary_data = &input.data[input.binary_offset..];
let mut reader = BitReader::new(binary_data);
decode_tables(&mut reader, input)
}
fn build_context<'a>(input: &'a DecodeInput<'a>) -> DecodeContext<'a> {
let node_types = input
.row_flags
.iter()
.map(|&f| NodeType::from_flags(f))
.collect();
DecodeContext {
value_strings: input.value_strings,
value_kinds: input.value_kinds,
key_strings: input.key_strings,
header_index_bits: bit_width(input.header_strings.len() as u32),
value_index_bits: bit_width(input.value_strings_declared.max(1)),
value_kind_bits: bit_width(input.value_kinds_declared.max(1)),
key_index_bits: bit_width(input.key_strings_declared.max(1)),
type_index_bits: bit_width(input.row_flags.len() as u32),
node_types,
}
}
pub fn decode_tables(reader: &mut impl BitRead, input: &DecodeInput) -> Option<Document> {
let ctx = build_context(input);
let table_id_bits = ctx.header_index_bits;
let mut tables = HashMap::new();
while reader.has_bits(table_id_bits as usize) {
let table_id = reader.read_bits(table_id_bits)?;
if table_id == 0 {
break;
}
let table_name = input.header_strings.get(table_id as usize)?.clone();
let (dep_names, dep_count) = read_table_deps(reader, table_id_bits, input.header_strings);
let remap_a = FixedWidthIntArray::read(reader)?;
let remap_b = FixedWidthIntArray::read(reader)?;
let tctx = TableContext {
pair_remap: if remap_a.is_active() {
Some(&remap_a)
} else {
None
},
value_remap: if remap_b.is_active() {
Some(&remap_b)
} else {
None
},
dep_index_bits: if dep_count > 0 {
bit_width(dep_count as u32)
} else {
0
},
dep_names,
};
reader.align_byte();
let records = parse_records(reader, &ctx, &tctx);
tables.insert(
table_name.clone(),
Table {
name: table_name,
deps: tctx.dep_names,
records,
},
);
}
Some(Document { tables })
}
fn read_table_deps(
reader: &mut impl BitRead,
table_id_bits: u8,
header_strings: &[String],
) -> (Vec<String>, usize) {
let mut dep_names = Vec::new();
let mut count = 0;
loop {
if !reader.has_bits(table_id_bits as usize) {
break;
}
let dep_id = match reader.read_bits(table_id_bits) {
Some(0) | None => break,
Some(id) => id,
};
count += 1;
if let Some(name) = header_strings.get(dep_id as usize) {
dep_names.push(name.clone());
}
}
(dep_names, count)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_decode_context<'a>(
key_strings: &'a [String],
value_strings: &'a [String],
value_kinds: &'a [String],
row_flags: &'a [u32],
) -> DecodeContext<'a> {
let node_types = row_flags.iter().map(|&f| NodeType::from_flags(f)).collect();
DecodeContext {
value_strings,
value_kinds,
key_strings,
header_index_bits: 1,
value_index_bits: bit_width(value_strings.len().max(1) as u32),
value_kind_bits: bit_width(value_kinds.len().max(1) as u32),
key_index_bits: bit_width(key_strings.len().max(1) as u32),
type_index_bits: bit_width(row_flags.len() as u32),
node_types,
}
}
fn make_table_context() -> TableContext<'static> {
TableContext {
pair_remap: None,
value_remap: None,
dep_names: Vec::new(),
dep_index_bits: 0,
}
}
#[test]
fn test_decode_empty_binary() {
let data = vec![0u8; 4];
let header_strings = vec!["test".to_string()];
let row_flags = vec![0u32];
let result = decode_table_data(&DecodeInput {
data: &data,
header_strings: &header_strings,
value_strings: &[],
value_strings_declared: 0,
value_kinds: &[],
value_kinds_declared: 0,
key_strings: &[],
key_strings_declared: 0,
row_flags: &row_flags,
binary_offset: 0,
});
let doc = result.unwrap();
assert!(doc.tables.is_empty());
}
#[test]
fn test_parse_tags_empty_z_terminator() {
let data = [b'z'];
let mut reader = BitReader::new(&data);
let key_strings = vec!["none".to_string()];
let row_flags = vec![0u32];
let ctx = make_decode_context(&key_strings, &[], &[], &row_flags);
let tctx = make_table_context();
let tags = parse_tags(&mut reader, &ctx, &tctx, data.len() * 8);
assert!(tags.is_empty());
}
#[test]
fn test_parse_tags_tag_a_key_name() {
let key_strings: Vec<String> = vec!["none".to_string(), "test_key".to_string()];
let row_flags = vec![0u32];
let ctx = make_decode_context(&key_strings, &[], &[], &row_flags);
let tctx = make_table_context();
let data = [0x61, 0xBD, 0x00];
let mut reader = BitReader::new(&data);
let tags = parse_tags(&mut reader, &ctx, &tctx, data.len() * 8);
assert_eq!(tags.len(), 1);
match &tags[0] {
Tag::KeyName { pair } => assert_eq!(pair, "test_key"),
other => panic!("Expected Tag::KeyName, got {:?}", other),
}
}
#[test]
fn test_parse_tags_tag_b_u32() {
let key_strings = vec!["none".to_string()];
let row_flags = vec![0u32];
let ctx = make_decode_context(&key_strings, &[], &[], &row_flags);
let tctx = make_table_context();
let data = [0x62, 0x2A, 0x00, 0x00, 0x00, 0x7A];
let mut reader = BitReader::new(&data);
let tags = parse_tags(&mut reader, &ctx, &tctx, data.len() * 8);
assert_eq!(tags.len(), 1);
match &tags[0] {
Tag::U32 { value } => assert_eq!(*value, 42),
other => panic!("Expected Tag::U32, got {:?}", other),
}
}
#[test]
fn test_parse_tags_tag_c_f32() {
let key_strings = vec!["none".to_string()];
let row_flags = vec![0u32];
let ctx = make_decode_context(&key_strings, &[], &[], &row_flags);
let tctx = make_table_context();
let data = [0x63, 0x00, 0x00, 0x80, 0x3F, 0x7A];
let mut reader = BitReader::new(&data);
let tags = parse_tags(&mut reader, &ctx, &tctx, data.len() * 8);
assert_eq!(tags.len(), 1);
match &tags[0] {
Tag::F32 {
u32_value,
f32_value,
} => {
assert_eq!(*u32_value, 0x3F800000);
assert!((f32_value - 1.0).abs() < f32::EPSILON);
}
other => panic!("Expected Tag::F32, got {:?}", other),
}
}
#[test]
fn test_parse_tags_multiple() {
let key_strings = vec!["none".to_string()];
let row_flags = vec![0u32];
let ctx = make_decode_context(&key_strings, &[], &[], &row_flags);
let tctx = make_table_context();
let data = [
0x62, 0x01, 0x00, 0x00, 0x00, 0x62, 0x02, 0x00, 0x00, 0x00, 0x7A,
];
let mut reader = BitReader::new(&data);
let tags = parse_tags(&mut reader, &ctx, &tctx, data.len() * 8);
assert_eq!(tags.len(), 2);
match (&tags[0], &tags[1]) {
(Tag::U32 { value: v1 }, Tag::U32 { value: v2 }) => {
assert_eq!(*v1, 1);
assert_eq!(*v2, 2);
}
other => panic!("Expected two Tag::U32, got {:?}", other),
}
}
#[test]
fn test_parse_tags_unknown_tag_breaks() {
let key_strings = vec!["none".to_string()];
let row_flags = vec![0u32];
let ctx = make_decode_context(&key_strings, &[], &[], &row_flags);
let tctx = make_table_context();
let data = [0xFF];
let mut reader = BitReader::new(&data);
let tags = parse_tags(&mut reader, &ctx, &tctx, data.len() * 8);
assert!(tags.is_empty());
}
#[test]
fn test_read_packed_name_list_terminated_by_none() {
let key_strings: Vec<String> =
vec!["none".to_string(), "foo".to_string(), "bar".to_string()];
let row_flags = vec![0u32];
let ctx = make_decode_context(&key_strings, &[], &[], &row_flags);
let data = [0x09];
let mut reader = BitReader::new(&data);
let list = read_packed_name_list(&mut reader, &ctx, None).unwrap();
assert_eq!(list, vec!["foo", "bar"]);
}
#[test]
fn test_read_packed_name_list_empty() {
let key_strings: Vec<String> = vec!["none".to_string(), "test".to_string()];
let row_flags = vec![0u32];
let ctx = make_decode_context(&key_strings, &[], &[], &row_flags);
let data = [0x00];
let mut reader = BitReader::new(&data);
let list = read_packed_name_list(&mut reader, &ctx, None).unwrap();
assert!(list.is_empty());
}
#[test]
fn test_read_table_deps() {
let header_strings = vec![
"unused".to_string(),
"inv".to_string(),
"inv_comp".to_string(),
"firmware".to_string(),
];
let data = [0x0E];
let mut reader = BitReader::new(&data);
let (dep_names, count) = read_table_deps(&mut reader, 2, &header_strings);
assert_eq!(count, 2);
assert_eq!(dep_names, vec!["inv_comp", "firmware"]);
}
#[test]
fn test_read_table_deps_empty() {
let header_strings = vec!["unused".to_string(), "inv".to_string()];
let data = [0x00];
let mut reader = BitReader::new(&data);
let (dep_names, count) = read_table_deps(&mut reader, 1, &header_strings);
assert_eq!(count, 0);
assert!(dep_names.is_empty());
}
#[test]
fn test_wrap_with_self_key_nonempty() {
let value = Value::Leaf("hello".to_string());
let result = wrap_with_self_key("my_key".to_string(), value).unwrap();
match result {
Value::Map(map) => {
assert_eq!(map.len(), 1);
assert!(map.contains_key("my_key"));
}
other => panic!("Expected Map, got {:?}", other),
}
}
#[test]
fn test_wrap_with_self_key_empty() {
let value = Value::Leaf("hello".to_string());
let result = wrap_with_self_key(String::new(), value.clone()).unwrap();
match result {
Value::Leaf(s) => assert_eq!(s, "hello"),
other => panic!("Expected Leaf, got {:?}", other),
}
}
#[test]
fn test_wrap_with_self_key_none() {
let value = Value::Leaf("hello".to_string());
let result = wrap_with_self_key("None".to_string(), value).unwrap();
match result {
Value::Leaf(s) => assert_eq!(s, "hello"),
other => panic!("Expected Leaf (none key skipped), got {:?}", other),
}
}
#[test]
fn test_remap_index_no_remap() {
let (bits, mapped) = remap_index(None, 5, 8);
assert_eq!(bits, 8);
assert_eq!(mapped, 5);
}
}