use crate::control::{PROGRESS_BYTE_INTERVAL, Stage, WalkerControl};
use crate::{Error, Result, RevitFile, compression, formats, streams};
#[derive(Debug, Clone)]
pub enum InstanceField {
Pointer { raw: [u32; 2] },
ElementId { tag: u32, id: u32 },
RefContainer { col_a: Vec<u16>, col_b: Vec<u16> },
Integer { value: i64, signed: bool, size: u8 },
Float { value: f64, size: u8 },
Bool(bool),
Guid([u8; 16]),
String(std::string::String),
Vector(Vec<InstanceField>),
Bytes(Vec<u8>),
}
#[derive(Debug, Clone, Default)]
pub struct HandleIndex {
map: std::collections::BTreeMap<u32, usize>,
}
impl HandleIndex {
pub fn new() -> Self {
Self {
map: std::collections::BTreeMap::new(),
}
}
pub fn insert(&mut self, element_id: u32, offset: usize) {
self.map.insert(element_id, offset);
}
pub fn get(&self, element_id: u32) -> Option<usize> {
self.map.get(&element_id).copied()
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (u32, usize)> + '_ {
self.map.iter().map(|(k, v)| (*k, *v))
}
}
pub trait ElementDecoder: Sync + Send {
fn class_name(&self) -> &'static str;
fn decode(
&self,
bytes: &[u8],
schema: &formats::ClassEntry,
index: &HandleIndex,
) -> Result<DecodedElement>;
}
pub trait ElementEncoder: Sync + Send {
fn class_name(&self) -> &'static str;
fn encode(&self, decoded: &DecodedElement, schema: &formats::ClassEntry) -> Vec<u8> {
encode_instance(decoded, schema)
}
}
pub fn write_field_by_type(value: &InstanceField, ty: &formats::FieldType, out: &mut Vec<u8>) {
use formats::FieldType;
if let InstanceField::Bytes(raw) = value {
out.extend_from_slice(raw);
return;
}
match (ty, value) {
(FieldType::Primitive { kind, size }, v) => {
let n = *size as usize;
match (*kind, *size, v) {
(0x01, _, InstanceField::Bool(b)) => {
out.push(if *b { 1 } else { 0 });
for _ in 1..n {
out.push(0);
}
}
(0x02, 2, InstanceField::Integer { value, .. }) => {
out.extend_from_slice(&(*value as u16).to_le_bytes());
}
(0x04, 4, InstanceField::Integer { value, .. })
| (0x05, 4, InstanceField::Integer { value, .. }) => {
out.extend_from_slice(&(*value as u32).to_le_bytes());
}
(0x06, 4, InstanceField::Float { value, .. }) => {
out.extend_from_slice(&(*value as f32).to_le_bytes());
}
(0x07, 8, InstanceField::Float { value, .. }) => {
out.extend_from_slice(&value.to_le_bytes());
}
(0x0b, 8, InstanceField::Integer { value, .. }) => {
out.extend_from_slice(&value.to_le_bytes());
}
_ => {} }
}
(FieldType::String, InstanceField::String(s)) => {
let utf16: Vec<u16> = s.encode_utf16().collect();
out.extend_from_slice(&(utf16.len() as u32).to_le_bytes());
for code in utf16 {
out.extend_from_slice(&code.to_le_bytes());
}
}
(FieldType::Guid, InstanceField::Guid(g)) => {
out.extend_from_slice(g);
}
(
FieldType::ElementId | FieldType::ElementIdRef { .. },
InstanceField::ElementId { tag, id },
) => {
out.extend_from_slice(&tag.to_le_bytes());
out.extend_from_slice(&id.to_le_bytes());
}
(FieldType::Pointer { .. }, InstanceField::Pointer { raw }) => {
out.extend_from_slice(&raw[0].to_le_bytes());
out.extend_from_slice(&raw[1].to_le_bytes());
}
(FieldType::Vector { kind, .. }, InstanceField::Vector(items)) => {
out.extend_from_slice(&(items.len() as u32).to_le_bytes());
for item in items {
match (*kind, item) {
(0x01, InstanceField::Bool(b)) => out.push(if *b { 1 } else { 0 }),
(0x04, InstanceField::Integer { value, .. })
| (0x05, InstanceField::Integer { value, .. }) => {
out.extend_from_slice(&(*value as u32).to_le_bytes());
}
(0x07, InstanceField::Float { value, .. }) => {
out.extend_from_slice(&value.to_le_bytes());
}
(0x0b, InstanceField::Integer { value, .. }) => {
out.extend_from_slice(&value.to_le_bytes());
}
(0x0d, InstanceField::Vector(point)) => {
for p in point.iter().take(3) {
if let InstanceField::Float { value, .. } = p {
out.extend_from_slice(&value.to_le_bytes());
} else {
out.extend_from_slice(&0.0_f64.to_le_bytes());
}
}
}
_ => {}
}
}
}
(FieldType::Container { kind, .. }, InstanceField::Vector(_))
if matches!(*kind, 0x01 | 0x02 | 0x04 | 0x05 | 0x07 | 0x0b | 0x0d) =>
{
let fake_vec = FieldType::Vector {
kind: *kind,
body: Vec::new(),
};
write_field_by_type(value, &fake_vec, out);
}
(FieldType::Container { .. } | FieldType::Unknown { .. }, _) => {
}
_ => {}
}
}
pub fn write_adocument_field(value: &InstanceField, ft: &formats::FieldType, out: &mut Vec<u8>) {
match (ft, value) {
(formats::FieldType::Pointer { .. }, InstanceField::Pointer { raw }) => {
out.extend_from_slice(&raw[0].to_le_bytes());
out.extend_from_slice(&raw[1].to_le_bytes());
}
(
formats::FieldType::ElementId | formats::FieldType::ElementIdRef { .. },
InstanceField::ElementId { tag, id },
) => {
out.extend_from_slice(&tag.to_le_bytes());
out.extend_from_slice(&id.to_le_bytes());
}
(
formats::FieldType::Container { kind: 0x0e, .. },
InstanceField::RefContainer { col_a, col_b },
) => {
out.extend_from_slice(&encode_ref_container(col_a, col_b));
}
(_, InstanceField::Bytes(raw)) => {
out.extend_from_slice(raw);
}
_ => write_field_by_type(value, ft, out),
}
}
pub fn encode_adocument_fields(
schema: &formats::ClassEntry,
fields: &[(String, InstanceField)],
) -> Vec<u8> {
let mut out = Vec::new();
for (idx, schema_field) in schema.fields.iter().enumerate() {
let Some((_, value)) = fields.get(idx) else {
break;
};
if let Some(ft) = schema_field.field_type.as_ref() {
write_adocument_field(value, ft, &mut out);
} else if let InstanceField::Bytes(raw) = value {
out.extend_from_slice(raw);
}
}
out
}
pub fn encode_ref_container(col_a: &[u16], col_b: &[u16]) -> Vec<u8> {
const ELEM_SIZE: usize = 6;
let count_a = col_a.len();
let count_b = col_b.len();
let mut out = Vec::with_capacity(2 * (4 + ELEM_SIZE * count_a.max(count_b)));
out.extend_from_slice(&(count_a as u32).to_le_bytes());
for id in col_a {
out.extend_from_slice(&id.to_le_bytes());
out.extend_from_slice(&[0u8; 4]); }
out.extend_from_slice(&(count_b as u32).to_le_bytes());
for id in col_b {
out.extend_from_slice(&id.to_le_bytes());
out.extend_from_slice(&[0u8; 4]); }
out
}
pub fn encode_instance(decoded: &DecodedElement, schema: &formats::ClassEntry) -> Vec<u8> {
let mut out = Vec::with_capacity(decoded.byte_range.len());
for (idx, schema_field) in schema.fields.iter().enumerate() {
let Some((_, value)) = decoded.fields.get(idx) else {
continue;
};
if let Some(ft) = schema_field.field_type.as_ref() {
write_field_by_type(value, ft, &mut out);
} else if let InstanceField::Bytes(raw) = value {
out.extend_from_slice(raw);
}
}
out
}
#[derive(Debug, Clone, PartialEq)]
pub struct ElementProvenance {
pub source_stream: Option<std::string::String>,
pub source_offset: Option<usize>,
pub record_kind: Option<std::string::String>,
pub decoder: Option<std::string::String>,
pub confidence: f32,
pub missing_required_fields: Vec<std::string::String>,
pub warnings: Vec<std::string::String>,
}
impl Default for ElementProvenance {
fn default() -> Self {
Self {
source_stream: None,
source_offset: None,
record_kind: None,
decoder: None,
confidence: 1.0,
missing_required_fields: Vec::new(),
warnings: Vec::new(),
}
}
}
impl ElementProvenance {
pub fn schema_generic(stream: &str, offset: usize, class: &str) -> Self {
Self {
source_stream: Some(stream.into()),
source_offset: Some(offset),
record_kind: Some("schema_instance".into()),
decoder: Some(format!("generic_decode_instance:{class}")),
confidence: 0.55,
missing_required_fields: Vec::new(),
warnings: Vec::new(),
}
}
pub fn schema_typed(stream: &str, offset: usize, decoder_name: &str) -> Self {
Self {
source_stream: Some(stream.into()),
source_offset: Some(offset),
record_kind: Some("schema_instance".into()),
decoder: Some(decoder_name.into()),
confidence: 0.9,
missing_required_fields: Vec::new(),
warnings: Vec::new(),
}
}
pub fn partition(
stream: &str,
offset: usize,
kind: &str,
decoder: &str,
confidence: f32,
warnings: impl IntoIterator<Item = impl Into<std::string::String>>,
) -> Self {
Self {
source_stream: Some(stream.into()),
source_offset: Some(offset),
record_kind: Some(kind.into()),
decoder: Some(decoder.into()),
confidence: confidence.clamp(0.0, 1.0),
missing_required_fields: Vec::new(),
warnings: warnings.into_iter().map(Into::into).collect(),
}
}
pub fn to_json_value(&self) -> serde_json::Value {
serde_json::json!({
"source_stream": self.source_stream,
"source_offset": self.source_offset,
"record_kind": self.record_kind,
"decoder": self.decoder,
"confidence": self.confidence,
"missing_required_fields": self.missing_required_fields,
"warnings": self.warnings,
})
}
}
pub const DEFAULT_MIN_ELEMENT_CONFIDENCE: f32 = 0.55;
#[derive(Debug, Clone)]
pub struct DecodedElement {
pub id: Option<u32>,
pub class: std::string::String,
pub fields: Vec<(std::string::String, InstanceField)>,
pub byte_range: std::ops::Range<usize>,
pub provenance: ElementProvenance,
}
impl DecodedElement {
pub fn with_provenance(mut self, provenance: ElementProvenance) -> Self {
self.provenance = provenance;
self
}
pub fn meets_confidence(&self, min_confidence: f32) -> bool {
self.provenance.confidence + f32::EPSILON >= min_confidence
}
}
pub fn read_field_by_type(
bytes: &[u8],
cursor: &mut usize,
ty: &formats::FieldType,
) -> InstanceField {
use formats::FieldType;
let rem = || bytes.get(*cursor..).unwrap_or(&[]);
match ty {
FieldType::Primitive { kind, size } => {
let n = *size as usize;
let slice = rem();
if slice.len() < n {
return InstanceField::Bytes(slice.to_vec());
}
match (*kind, *size) {
(0x01, _) => {
let b = slice[0] != 0;
*cursor += n.max(1);
InstanceField::Bool(b)
}
(0x02, 2) => {
let v = u16::from_le_bytes([slice[0], slice[1]]) as i64;
*cursor += 2;
InstanceField::Integer {
value: v,
signed: false,
size: 2,
}
}
(0x04, 4) | (0x05, 4) => {
let v = u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as i64;
*cursor += 4;
InstanceField::Integer {
value: v,
signed: *kind == 0x04,
size: 4,
}
}
(0x06, 4) => {
let v = f32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as f64;
*cursor += 4;
InstanceField::Float { value: v, size: 4 }
}
(0x07, 8) => {
let v = f64::from_le_bytes([
slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6],
slice[7],
]);
*cursor += 8;
InstanceField::Float { value: v, size: 8 }
}
(0x0b, 8) => {
let v = i64::from_le_bytes([
slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6],
slice[7],
]);
*cursor += 8;
InstanceField::Integer {
value: v,
signed: true,
size: 8,
}
}
_ => {
let bytes = slice[..n.min(slice.len())].to_vec();
*cursor += n.min(slice.len());
InstanceField::Bytes(bytes)
}
}
}
FieldType::String => {
let slice = rem();
if slice.len() < 4 {
return InstanceField::Bytes(slice.to_vec());
}
let char_count = u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as usize;
let byte_count = char_count.saturating_mul(2);
if slice.len() < 4 + byte_count {
return InstanceField::Bytes(slice.to_vec());
}
let utf16_bytes = &slice[4..4 + byte_count];
let (text, _, had_errors) = encoding_rs::UTF_16LE.decode(utf16_bytes);
*cursor += 4 + byte_count;
if had_errors {
InstanceField::Bytes(utf16_bytes.to_vec())
} else {
InstanceField::String(text.into_owned())
}
}
FieldType::Guid => {
let slice = rem();
if slice.len() < 16 {
return InstanceField::Bytes(slice.to_vec());
}
let mut g = [0u8; 16];
g.copy_from_slice(&slice[..16]);
*cursor += 16;
InstanceField::Guid(g)
}
FieldType::ElementId | FieldType::ElementIdRef { .. } => {
let slice = rem();
if slice.len() < 8 {
return InstanceField::Bytes(slice.to_vec());
}
let tag = u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]);
let id = u32::from_le_bytes([slice[4], slice[5], slice[6], slice[7]]);
*cursor += 8;
InstanceField::ElementId { tag, id }
}
FieldType::Pointer { .. } => {
let slice = rem();
if slice.len() < 8 {
return InstanceField::Bytes(slice.to_vec());
}
let a = u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]);
let b = u32::from_le_bytes([slice[4], slice[5], slice[6], slice[7]]);
*cursor += 8;
InstanceField::Pointer { raw: [a, b] }
}
FieldType::Vector { kind, .. } => {
let slice = rem();
if slice.len() < 4 {
return InstanceField::Bytes(slice.to_vec());
}
let count = u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as usize;
let elem_size: Option<usize> = match *kind {
0x01 => Some(1),
0x05 | 0x04 => Some(4),
0x07 | 0x0b => Some(8),
0x0d => Some(24),
_ => None,
};
let Some(esz) = elem_size else {
let consumed = slice.len().min(4);
let out = slice[..consumed].to_vec();
*cursor += consumed;
return InstanceField::Bytes(out);
};
let needed = count
.checked_mul(esz)
.and_then(|n| n.checked_add(4))
.unwrap_or(usize::MAX);
if slice.len() < needed {
return InstanceField::Bytes(slice.to_vec());
}
let mut items = Vec::with_capacity(count);
let mut local = 4;
for _ in 0..count {
let elem_bytes = &slice[local..local + esz];
let item = match (*kind, esz) {
(0x01, 1) => InstanceField::Bool(elem_bytes[0] != 0),
(0x04, 4) | (0x05, 4) => {
let v = u32::from_le_bytes([
elem_bytes[0],
elem_bytes[1],
elem_bytes[2],
elem_bytes[3],
]) as i64;
InstanceField::Integer {
value: v,
signed: *kind == 0x04,
size: 4,
}
}
(0x07, 8) => {
let v = f64::from_le_bytes([
elem_bytes[0],
elem_bytes[1],
elem_bytes[2],
elem_bytes[3],
elem_bytes[4],
elem_bytes[5],
elem_bytes[6],
elem_bytes[7],
]);
InstanceField::Float { value: v, size: 8 }
}
(0x0b, 8) => {
let v = i64::from_le_bytes([
elem_bytes[0],
elem_bytes[1],
elem_bytes[2],
elem_bytes[3],
elem_bytes[4],
elem_bytes[5],
elem_bytes[6],
elem_bytes[7],
]);
InstanceField::Integer {
value: v,
signed: true,
size: 8,
}
}
(0x0d, 24) => {
let mut point = Vec::with_capacity(3);
for k in 0..3 {
let off = k * 8;
let v = f64::from_le_bytes([
elem_bytes[off],
elem_bytes[off + 1],
elem_bytes[off + 2],
elem_bytes[off + 3],
elem_bytes[off + 4],
elem_bytes[off + 5],
elem_bytes[off + 6],
elem_bytes[off + 7],
]);
point.push(InstanceField::Float { value: v, size: 8 });
}
InstanceField::Vector(point)
}
_ => InstanceField::Bytes(elem_bytes.to_vec()),
};
items.push(item);
local += esz;
}
*cursor += 4 + count * esz;
InstanceField::Vector(items)
}
FieldType::Container { kind, .. } => {
if matches!(*kind, 0x01 | 0x02 | 0x04 | 0x05 | 0x07 | 0x0b | 0x0d) {
let fake_vec = FieldType::Vector {
kind: *kind,
body: Vec::new(),
};
return read_field_by_type(bytes, cursor, &fake_vec);
}
let slice = rem();
let consumed = slice.len().min(4);
let out = slice[..consumed].to_vec();
*cursor += consumed;
InstanceField::Bytes(out)
}
FieldType::Unknown { .. } => {
let slice = rem();
let out = slice.to_vec();
*cursor = bytes.len();
InstanceField::Bytes(out)
}
}
}
pub fn decode_instance(bytes: &[u8], start: usize, class: &formats::ClassEntry) -> DecodedElement {
decode_instance_with_limits(bytes, start, class, WalkerLimits::default())
}
pub fn decode_instance_with_limits(
bytes: &[u8],
start: usize,
class: &formats::ClassEntry,
limits: WalkerLimits,
) -> DecodedElement {
let end = start
.checked_add(limits.max_per_record_decode_bytes)
.map(|n| n.min(bytes.len()))
.unwrap_or(bytes.len());
let bytes = bytes.get(..end).unwrap_or(bytes);
let mut cursor = start;
let mut fields = Vec::with_capacity(class.fields.len());
for field in &class.fields {
let value = match field.field_type.as_ref() {
Some(ft) => read_field_by_type(bytes, &mut cursor, ft),
None => {
InstanceField::Bytes(Vec::new())
}
};
fields.push((field.name.clone(), value));
}
DecodedElement {
id: None,
class: class.name.clone(),
fields,
byte_range: start..cursor,
provenance: ElementProvenance::schema_generic("schema", start, &class.name),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WalkerLimitHit {
MaxScanBytes,
MaxCandidates,
MaxTrialOffsets,
MaxPerRecordDecodeBytes,
}
impl WalkerLimitHit {
pub fn code(self) -> &'static str {
match self {
Self::MaxScanBytes => "walker-max-scan-bytes",
Self::MaxCandidates => "walker-max-candidates",
Self::MaxTrialOffsets => "walker-max-trial-offsets",
Self::MaxPerRecordDecodeBytes => "walker-max-record-decode-bytes",
}
}
pub fn message(self) -> &'static str {
match self {
Self::MaxScanBytes => "walker scan stopped at max_scan_bytes",
Self::MaxCandidates => "walker candidate list stopped at max_candidates",
Self::MaxTrialOffsets => "walker trial scan stopped at max_trial_offsets",
Self::MaxPerRecordDecodeBytes => {
"walker candidate decode stopped at max_per_record_decode_bytes"
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct WalkerLimits {
pub max_scan_bytes: usize,
pub max_candidates: usize,
pub max_trial_offsets: usize,
pub max_per_record_decode_bytes: usize,
pub max_container_records: usize,
}
impl Default for WalkerLimits {
fn default() -> Self {
Self {
max_scan_bytes: 128 * 1024 * 1024,
max_candidates: 100_000,
max_trial_offsets: 16_000_000,
max_per_record_decode_bytes: 1024 * 1024,
max_container_records: 1000,
}
}
}
#[derive(Debug, Clone)]
pub struct ADocumentInstance {
pub entry_offset: usize,
pub version: u32,
pub fields: Vec<(String, InstanceField)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Completeness {
pub total: usize,
pub typed: usize,
pub raw_bytes_fallback: usize,
pub typed_and_non_empty: usize,
}
impl Completeness {
pub fn typed_ratio(&self) -> Option<f64> {
if self.total == 0 {
None
} else {
Some(self.typed as f64 / self.total as f64)
}
}
pub fn is_fully_typed(&self) -> bool {
self.total > 0 && self.raw_bytes_fallback == 0
}
}
impl ADocumentInstance {
pub fn elem_table_pointer(&self) -> Option<[u32; 2]> {
const NAMES: &[&str] = &[
"m_elem_table",
"m_elemtable",
"elem_table",
"elemtable",
"elementtable",
"m_element_table",
];
for (field_name, value) in &self.fields {
let norm = field_name
.trim_start_matches('m')
.trim_start_matches('_')
.to_lowercase();
let compacted = norm.replace('_', "");
let matched = NAMES
.iter()
.any(|n| n.eq_ignore_ascii_case(field_name) || *n == compacted);
if !matched {
continue;
}
if let InstanceField::Pointer { raw } = value {
if *raw == [0, 0] || *raw == [u32::MAX, u32::MAX] {
return None;
}
return Some(*raw);
}
}
None
}
pub fn completeness(&self) -> Completeness {
let mut out = Completeness {
total: self.fields.len(),
..Completeness::default()
};
for (_, value) in &self.fields {
match value {
InstanceField::Bytes(b) => {
out.raw_bytes_fallback += 1;
if !b.is_empty() {
}
}
_ => {
out.typed += 1;
let non_empty = match value {
InstanceField::Vector(items) => !items.is_empty(),
InstanceField::String(s) => !s.is_empty(),
InstanceField::RefContainer { col_a, .. } => !col_a.is_empty(),
_ => true,
};
if non_empty {
out.typed_and_non_empty += 1;
}
}
}
}
out
}
}
pub fn read_adocument(rf: &mut RevitFile) -> Result<Option<ADocumentInstance>> {
read_adocument_with_limits(rf, WalkerLimits::default())
}
pub fn read_adocument_with_limits(
rf: &mut RevitFile,
limits: WalkerLimits,
) -> Result<Option<ADocumentInstance>> {
Ok(read_adocument_internal(rf, limits)?.0)
}
fn read_adocument_internal(
rf: &mut RevitFile,
limits: WalkerLimits,
) -> Result<(Option<ADocumentInstance>, Option<DetectionResult>)> {
let formats_raw = rf.read_stream(streams::FORMATS_LATEST)?;
let formats_d = compression::inflate_stream_at(streams::FORMATS_LATEST, &formats_raw, 0)?;
let schema = formats::parse_schema(&formats_d)?;
let adoc = schema
.classes
.iter()
.find(|c| c.name == "ADocument")
.ok_or_else(|| Error::BasicFileInfo("ADocument not in schema".into()))?;
let raw = rf.read_stream(streams::GLOBAL_LATEST)?;
let (_, d) = compression::inflate_stream_auto(streams::GLOBAL_LATEST, &raw)?;
let detection = detect_adocument_start_with_limits(&d, Some(adoc), limits);
let Some(entry) = detection.offset else {
return Ok((None, Some(detection)));
};
let mut cursor = entry;
let mut fields = Vec::with_capacity(adoc.fields.len());
for field in &adoc.fields {
let Some(ft) = &field.field_type else {
break;
};
let Some((consumed, value)) = read_field(ft, &d[cursor..], limits) else {
break;
};
fields.push((field.name.clone(), value));
cursor = cursor.saturating_add(consumed);
if cursor > d.len() {
break;
}
}
let version = rf.basic_file_info().ok().map(|b| b.version).unwrap_or(0);
Ok((
Some(ADocumentInstance {
entry_offset: entry,
version,
fields,
}),
Some(detection),
))
}
pub fn read_adocument_strict(rf: &mut RevitFile) -> Result<ADocumentInstance> {
let Some(inst) = read_adocument(rf)? else {
return Err(Error::BasicFileInfo(
"ADocument entry-point detector returned None".into(),
));
};
let c = inst.completeness();
if !c.is_fully_typed() {
return Err(Error::BasicFileInfo(format!(
"ADocument decode incomplete: {} of {} fields fell back to raw bytes",
c.raw_bytes_fallback, c.total
)));
}
Ok(inst)
}
pub fn read_adocument_lossy(
rf: &mut RevitFile,
) -> Result<crate::parse_mode::Decoded<ADocumentInstance>> {
read_adocument_lossy_with_limits(rf, WalkerLimits::default())
}
pub fn read_adocument_lossy_with_limits(
rf: &mut RevitFile,
limits: WalkerLimits,
) -> Result<crate::parse_mode::Decoded<ADocumentInstance>> {
use crate::parse_mode::{Decoded, Diagnostics, Warning};
let (maybe_inst, detection) = read_adocument_internal(rf, limits)?;
let mut diagnostics = Diagnostics::default();
if let Some(hit) = detection.and_then(|d| d.limit_hit) {
diagnostics.warn(Warning::new(hit.code(), hit.message()));
}
let Some(inst) = maybe_inst else {
let mut d = Diagnostics::default();
d.extend(diagnostics);
d.fail_stream("ADocument");
let placeholder = ADocumentInstance {
entry_offset: 0,
version: 0,
fields: Vec::new(),
};
return Ok(Decoded::partial(placeholder, d));
};
let c = inst.completeness();
if c.is_fully_typed() && diagnostics.is_empty() {
return Ok(Decoded::complete(inst));
}
for (name, value) in &inst.fields {
if matches!(value, InstanceField::Bytes(_)) {
diagnostics.partial_field(name.clone());
}
}
diagnostics.confidence = c.typed_ratio().map(|r| r as f32);
Ok(Decoded::partial(inst, diagnostics))
}
pub fn iter_elements(rf: &mut RevitFile) -> Result<impl Iterator<Item = DecodedElement>> {
iter_elements_with_limits(rf, PRODUCTION_ELEMENT_MIN_SCORE, WalkerLimits::default())
}
pub fn iter_elements_with_options(
rf: &mut RevitFile,
min_score: i64,
) -> Result<impl Iterator<Item = DecodedElement>> {
iter_elements_with_limits(rf, min_score, WalkerLimits::default())
}
pub fn iter_elements_with_limits(
rf: &mut RevitFile,
min_score: i64,
limits: WalkerLimits,
) -> Result<impl Iterator<Item = DecodedElement>> {
iter_elements_with_control(rf, min_score, limits, &WalkerControl::default())
}
pub fn iter_elements_with_control(
rf: &mut RevitFile,
min_score: i64,
limits: WalkerLimits,
control: &WalkerControl,
) -> Result<impl Iterator<Item = DecodedElement> + use<>> {
control.check()?;
control.report(Stage::SchemaParse, 0, None);
let formats_raw = rf.read_stream(streams::FORMATS_LATEST)?;
let formats_d = compression::inflate_stream_at(streams::FORMATS_LATEST, &formats_raw, 0)?;
let schema = formats::parse_schema(&formats_d)?;
control.report(Stage::SchemaParse, 1, Some(1));
let raw = rf.read_stream(streams::GLOBAL_LATEST)?;
let (_, d) = compression::inflate_stream_auto(streams::GLOBAL_LATEST, &raw)?;
let class_by_name: std::collections::HashMap<&str, &formats::ClassEntry> = schema
.classes
.iter()
.map(|c| (c.name.as_str(), c))
.collect();
let candidates =
scan_candidates_with_control(&schema, &d, min_score, limits, control)?.candidates;
let candidate_total = candidates.len() as u64;
let mut out = Vec::with_capacity(candidates.len());
let mut seen_ids = std::collections::HashSet::<u32>::new();
for (index, cand) in candidates.into_iter().enumerate() {
if index % 256 == 0 {
control.check()?;
control.report(Stage::ElementDecode, index as u64, Some(candidate_total));
}
let Some(cls) = class_by_name.get(cand.class_name.as_str()).copied() else {
continue;
};
let Some(mut decoded) =
crate::elements::decode_instance_prefer_typed_with_limits(&d, cand.offset, cls, limits)
else {
continue;
};
let self_id = find_self_id_field(cls)
.and_then(|idx| decoded.fields.get(idx))
.and_then(|(_, field)| match field {
InstanceField::ElementId { id, .. } if *id != 0 => Some(*id),
_ => None,
})
.or(decoded.id.filter(|id| *id != 0));
if let Some(id) = self_id {
if !seen_ids.insert(id) {
continue;
}
decoded.id = Some(id);
}
out.push(decoded);
}
control.check()?;
control.report(Stage::ElementDecode, candidate_total, Some(candidate_total));
control.report(Stage::PartitionScan, 0, None);
if let Ok(bfi) = rf.basic_file_info() {
if let Ok(scan) = crate::partition_arc_walls::scan_partition_arc_walls_with_limits(
rf,
bfi.version,
limits,
) {
for wall in scan.walls {
let typed = crate::elements::arc_wall::from_partition_arc_wall(&wall);
let decoded = typed.to_decoded_element(&wall.partition, wall.offset);
if let Some(id) = decoded.id {
if !seen_ids.insert(id) {
continue;
}
}
out.push(decoded);
}
}
if let Ok(mvp) =
crate::partition_schema_mvp::recover_partition_schema_mvp(rf, bfi.version, limits)
{
for decoded in mvp.into_elements() {
if let Some(id) = decoded.id {
if !seen_ids.insert(id) {
continue;
}
}
out.push(decoded);
}
}
}
control.check()?;
control.report(Stage::PartitionScan, 1, Some(1));
Ok(out.into_iter())
}
#[cfg(test)]
fn find_adocument_start(d: &[u8]) -> Option<usize> {
find_adocument_start_with_schema(d, None)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DetectionStrategy {
Heuristic,
Scored,
NotFound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DetectionResult {
pub offset: Option<usize>,
pub score: Option<i64>,
pub candidates_evaluated: usize,
pub limit_hit: Option<WalkerLimitHit>,
pub strategy: DetectionStrategy,
}
pub fn detect_adocument_start(
d: &[u8],
adoc_schema: Option<&formats::ClassEntry>,
) -> DetectionResult {
detect_adocument_start_with_limits(d, adoc_schema, WalkerLimits::default())
}
pub fn detect_adocument_start_with_limits(
d: &[u8],
adoc_schema: Option<&formats::ClassEntry>,
limits: WalkerLimits,
) -> DetectionResult {
let (scan_bytes, scan_limit_hit) = bounded_scan_bytes(d, limits);
if let Some(h) = heuristic_find(scan_bytes) {
if let Some(cls) = adoc_schema {
if let TrialWalkOutcome::Match(w) = trial_walk_checked(cls, &scan_bytes[h..], limits) {
let sc = walk_score(&w);
if sc >= 90 {
return DetectionResult {
offset: Some(h),
score: Some(sc),
candidates_evaluated: 1,
limit_hit: scan_limit_hit,
strategy: DetectionStrategy::Heuristic,
};
}
}
} else {
return DetectionResult {
offset: Some(h),
score: None,
candidates_evaluated: 0,
limit_hit: scan_limit_hit,
strategy: DetectionStrategy::Heuristic,
};
}
}
if let Some(cls) = adoc_schema {
let mut best: Option<(i64, usize)> = None;
let mut evaluated = 0usize;
let mut limit_hit = scan_limit_hit;
let end = scan_bytes.len().saturating_sub(256);
for offset in 0x100..end {
if evaluated >= limits.max_trial_offsets {
limit_hit.get_or_insert(WalkerLimitHit::MaxTrialOffsets);
break;
}
evaluated += 1;
match trial_walk_checked(cls, &scan_bytes[offset..], limits) {
TrialWalkOutcome::Match(walk) => {
let sc = walk_score(&walk);
if best.as_ref().is_none_or(|(bs, _)| sc > *bs) {
best = Some((sc, offset));
}
}
TrialWalkOutcome::NoMatch => {}
TrialWalkOutcome::Limit(hit) => {
limit_hit.get_or_insert(hit);
}
}
}
if let Some((sc, off)) = best {
if sc >= 80 {
return DetectionResult {
offset: Some(off),
score: Some(sc),
candidates_evaluated: evaluated,
limit_hit,
strategy: DetectionStrategy::Scored,
};
}
return DetectionResult {
offset: None,
score: Some(sc),
candidates_evaluated: evaluated,
limit_hit,
strategy: DetectionStrategy::NotFound,
};
}
return DetectionResult {
offset: None,
score: None,
candidates_evaluated: evaluated,
limit_hit,
strategy: DetectionStrategy::NotFound,
};
}
DetectionResult {
offset: None,
score: None,
candidates_evaluated: 0,
limit_hit: scan_limit_hit,
strategy: DetectionStrategy::NotFound,
}
}
#[derive(Debug, Clone)]
pub struct ScanCandidate {
pub offset: usize,
pub class_name: String,
pub class_tag: u16,
pub score: i64,
}
#[derive(Debug, Clone, Default)]
pub struct ScanCandidatesResult {
pub candidates: Vec<ScanCandidate>,
pub scanned_bytes: usize,
pub trial_offsets_evaluated: usize,
pub limit_hit: Option<WalkerLimitHit>,
}
pub fn scan_candidates(
schema: &formats::SchemaTable,
bytes: &[u8],
min_score: i64,
) -> Vec<ScanCandidate> {
scan_candidates_with_limits(schema, bytes, min_score, WalkerLimits::default()).candidates
}
pub fn scan_candidates_with_limits(
schema: &formats::SchemaTable,
bytes: &[u8],
min_score: i64,
limits: WalkerLimits,
) -> ScanCandidatesResult {
scan_candidates_with_control(schema, bytes, min_score, limits, &WalkerControl::default())
.expect("a scan without a cancellation token cannot be cancelled")
}
pub fn scan_candidates_with_control(
schema: &formats::SchemaTable,
bytes: &[u8],
min_score: i64,
limits: WalkerLimits,
control: &WalkerControl,
) -> Result<ScanCandidatesResult> {
let mut tag_to_class: std::collections::HashMap<u16, Vec<&formats::ClassEntry>> =
std::collections::HashMap::with_capacity(schema.classes.len());
for cls in &schema.classes {
if let Some(tag) = cls.tag {
tag_to_class.entry(tag).or_default().push(cls);
}
}
let (bytes, scan_limit_hit) = bounded_scan_bytes(bytes, limits);
let mut out: Vec<ScanCandidate> = Vec::new();
let mut trial_offsets_evaluated = 0usize;
let mut limit_hit = scan_limit_hit;
if bytes.len() < 4 {
return Ok(ScanCandidatesResult {
candidates: out,
scanned_bytes: bytes.len(),
trial_offsets_evaluated,
limit_hit,
});
}
let end = bytes.len().saturating_sub(2);
let mut i = 0usize;
while i + 2 <= end {
if i & (PROGRESS_BYTE_INTERVAL - 1) == 0 {
control.check()?;
control.report(Stage::CandidateScan, i as u64, Some(bytes.len() as u64));
}
let tag = u16::from_le_bytes([bytes[i], bytes[i + 1]]);
if let Some(classes) = tag_to_class.get(&tag) {
let instance_start = i + 2;
if instance_start < bytes.len() {
for cls in classes {
if trial_offsets_evaluated >= limits.max_trial_offsets {
limit_hit.get_or_insert(WalkerLimitHit::MaxTrialOffsets);
break;
}
trial_offsets_evaluated += 1;
match trial_walk_checked(cls, &bytes[instance_start..], limits) {
TrialWalkOutcome::Match(walk) => {
let score = walk_score(&walk);
if score >= min_score {
out.push(ScanCandidate {
offset: instance_start,
class_name: cls.name.clone(),
class_tag: tag,
score,
});
if out.len() >= limits.max_candidates {
limit_hit.get_or_insert(WalkerLimitHit::MaxCandidates);
break;
}
}
}
TrialWalkOutcome::NoMatch => {}
TrialWalkOutcome::Limit(hit) => {
limit_hit.get_or_insert(hit);
}
}
}
}
}
if matches!(
limit_hit,
Some(WalkerLimitHit::MaxCandidates | WalkerLimitHit::MaxTrialOffsets)
) {
break;
}
i += 2;
}
control.check()?;
control.report(
Stage::CandidateScan,
bytes.len() as u64,
Some(bytes.len() as u64),
);
out.sort_by_key(|c| std::cmp::Reverse(c.score));
Ok(ScanCandidatesResult {
candidates: out,
scanned_bytes: bytes.len(),
trial_offsets_evaluated,
limit_hit,
})
}
pub const PRODUCTION_ELEMENT_MIN_SCORE: i64 = 80;
pub const DIAGNOSTIC_ELEMENT_MIN_SCORE: i64 = 0;
pub fn find_self_id_field(cls: &formats::ClassEntry) -> Option<usize> {
use formats::FieldType;
const CANONICAL_NAMES: &[&str] = &["m_id", "m_id64", "m_handle", "m_elementId"];
for canonical in CANONICAL_NAMES {
if let Some((idx, _)) = cls.fields.iter().enumerate().find(|(_, f)| {
f.name == *canonical
&& matches!(
f.field_type,
Some(FieldType::ElementId) | Some(FieldType::ElementIdRef { .. })
)
}) {
return Some(idx);
}
}
for (idx, f) in cls.fields.iter().enumerate() {
if matches!(f.field_type, Some(FieldType::ElementId)) {
return Some(idx);
}
}
None
}
pub fn build_handle_index(
schema: &formats::SchemaTable,
bytes: &[u8],
min_score: i64,
) -> HandleIndex {
build_handle_index_with_limits(schema, bytes, min_score, WalkerLimits::default())
}
pub fn build_handle_index_with_limits(
schema: &formats::SchemaTable,
bytes: &[u8],
min_score: i64,
limits: WalkerLimits,
) -> HandleIndex {
let mut index = HandleIndex::new();
let candidates = scan_candidates_with_limits(schema, bytes, min_score, limits).candidates;
let class_by_name: std::collections::HashMap<&str, &formats::ClassEntry> = schema
.classes
.iter()
.map(|c| (c.name.as_str(), c))
.collect();
for cand in &candidates {
let Some(cls) = class_by_name.get(cand.class_name.as_str()).copied() else {
continue;
};
let Some(id_idx) = find_self_id_field(cls) else {
continue;
};
let decoded = decode_instance_with_limits(bytes, cand.offset, cls, limits);
if let Some((_, InstanceField::ElementId { id, .. })) = decoded.fields.get(id_idx) {
if *id != 0 {
index.map.entry(*id).or_insert(cand.offset);
}
}
}
index
}
#[doc(hidden)]
pub fn __fuzz_find_adocument_start(
d: &[u8],
schema: Option<&formats::ClassEntry>,
) -> Option<usize> {
find_adocument_start_with_schema(d, schema)
}
fn find_adocument_start_with_schema(
d: &[u8],
adoc_schema: Option<&formats::ClassEntry>,
) -> Option<usize> {
detect_adocument_start(d, adoc_schema).offset
}
fn heuristic_find(d: &[u8]) -> Option<usize> {
let mut last_table_end = 0usize;
let mut i = 0;
while i + 4 < d.len() {
if d[i..i + 4] == [1, 0, 0, 0] {
let mut cursor = i + 4;
let mut expect: u32 = 2;
let mut end = i + 4;
while cursor + 4 <= d.len() {
let marker = expect.to_le_bytes();
let window_end = (cursor + 64).min(d.len());
if let Some(p) = d[cursor..window_end].windows(4).position(|w| w == marker) {
end = cursor + p + 4;
cursor = end;
expect += 1;
} else {
break;
}
}
if expect >= 6 {
last_table_end = end + 32;
i = end;
continue;
}
}
i += 1;
}
let min_start = last_table_end.max(0x200);
let mut k = min_start;
while k + 16 <= d.len() {
if d[k..k + 8].iter().all(|&b| b == 0) {
let next_u32 = u32::from_le_bytes([d[k + 8], d[k + 9], d[k + 10], d[k + 11]]);
let next_next = u32::from_le_bytes([d[k + 12], d[k + 13], d[k + 14], d[k + 15]]);
if (1..=100).contains(&next_u32) && (next_next == 0xffffffff || next_next <= 0x10000) {
return Some(k);
}
}
k += 1;
}
None
}
pub fn trial_walk(cls: &formats::ClassEntry, bytes: &[u8]) -> Option<Vec<(u32, u32)>> {
trial_walk_with_limits(cls, bytes, WalkerLimits::default())
}
pub fn trial_walk_with_limits(
cls: &formats::ClassEntry,
bytes: &[u8],
limits: WalkerLimits,
) -> Option<Vec<(u32, u32)>> {
match trial_walk_checked(cls, bytes, limits) {
TrialWalkOutcome::Match(walk) => Some(walk),
TrialWalkOutcome::NoMatch | TrialWalkOutcome::Limit(_) => None,
}
}
enum TrialWalkOutcome {
Match(Vec<(u32, u32)>),
NoMatch,
Limit(WalkerLimitHit),
}
fn trial_walk_checked(
cls: &formats::ClassEntry,
bytes: &[u8],
limits: WalkerLimits,
) -> TrialWalkOutcome {
let source_len = bytes.len();
let capped = source_len > limits.max_per_record_decode_bytes;
let bytes = &bytes[..source_len.min(limits.max_per_record_decode_bytes)];
let mut cursor = 0;
let mut out = Vec::new();
for field in &cls.fields {
let Some(ft) = field.field_type.as_ref() else {
return TrialWalkOutcome::NoMatch;
};
let tag_id = match ft {
formats::FieldType::Pointer { .. } => {
if cursor + 8 > bytes.len() {
return if capped {
TrialWalkOutcome::Limit(WalkerLimitHit::MaxPerRecordDecodeBytes)
} else {
TrialWalkOutcome::NoMatch
};
}
cursor += 8;
(u32::MAX, u32::MAX)
}
formats::FieldType::ElementId | formats::FieldType::ElementIdRef { .. } => {
if cursor + 8 > bytes.len() {
return if capped {
TrialWalkOutcome::Limit(WalkerLimitHit::MaxPerRecordDecodeBytes)
} else {
TrialWalkOutcome::NoMatch
};
}
let tag = u32::from_le_bytes([
bytes[cursor],
bytes[cursor + 1],
bytes[cursor + 2],
bytes[cursor + 3],
]);
let id = u32::from_le_bytes([
bytes[cursor + 4],
bytes[cursor + 5],
bytes[cursor + 6],
bytes[cursor + 7],
]);
cursor += 8;
(tag, id)
}
formats::FieldType::Container { kind: 0x0e, .. } => {
if cursor + 4 > bytes.len() {
return if capped {
TrialWalkOutcome::Limit(WalkerLimitHit::MaxPerRecordDecodeBytes)
} else {
TrialWalkOutcome::NoMatch
};
}
let count = u32::from_le_bytes([
bytes[cursor],
bytes[cursor + 1],
bytes[cursor + 2],
bytes[cursor + 3],
]) as usize;
if count > limits.max_container_records {
return TrialWalkOutcome::NoMatch;
}
let Some(col_payload_bytes) = count.checked_mul(6) else {
return TrialWalkOutcome::NoMatch;
};
let Some(col_bytes) = 4usize.checked_add(col_payload_bytes) else {
return TrialWalkOutcome::NoMatch;
};
let Some(col2_count_end) =
cursor.checked_add(col_bytes).and_then(|n| n.checked_add(4))
else {
return TrialWalkOutcome::NoMatch;
};
if col2_count_end > bytes.len() {
return if capped {
TrialWalkOutcome::Limit(WalkerLimitHit::MaxPerRecordDecodeBytes)
} else {
TrialWalkOutcome::NoMatch
};
}
let col2_count = u32::from_le_bytes([
bytes[cursor + col_bytes],
bytes[cursor + col_bytes + 1],
bytes[cursor + col_bytes + 2],
bytes[cursor + col_bytes + 3],
]) as usize;
if col2_count != count {
return TrialWalkOutcome::NoMatch;
}
let Some(total) = col_bytes.checked_mul(2) else {
return TrialWalkOutcome::NoMatch;
};
let Some(total_end) = cursor.checked_add(total) else {
return TrialWalkOutcome::NoMatch;
};
if total_end > bytes.len() {
return if capped {
TrialWalkOutcome::Limit(WalkerLimitHit::MaxPerRecordDecodeBytes)
} else {
TrialWalkOutcome::NoMatch
};
}
cursor = total_end;
(u32::MAX, u32::MAX)
}
other => {
let before = cursor;
let _value = read_field_by_type(bytes, &mut cursor, other);
if cursor > bytes.len() {
return TrialWalkOutcome::NoMatch;
}
if cursor == before {
match other {
formats::FieldType::Primitive { size: 0, .. } => {
}
_ => {
return if capped {
TrialWalkOutcome::Limit(WalkerLimitHit::MaxPerRecordDecodeBytes)
} else {
TrialWalkOutcome::NoMatch
};
}
}
}
(u32::MAX, u32::MAX)
}
};
out.push(tag_id);
}
TrialWalkOutcome::Match(out)
}
fn bounded_scan_bytes(bytes: &[u8], limits: WalkerLimits) -> (&[u8], Option<WalkerLimitHit>) {
if bytes.len() > limits.max_scan_bytes {
(
&bytes[..limits.max_scan_bytes],
Some(WalkerLimitHit::MaxScanBytes),
)
} else {
(bytes, None)
}
}
pub fn walk_score(walk: &[(u32, u32)]) -> i64 {
if walk.len() < 3 {
return i64::MIN;
}
let last3 = &walk[walk.len() - 3..];
let real_ids: Vec<u32> = last3
.iter()
.filter(|(t, _)| *t != u32::MAX)
.map(|(_, i)| *i)
.collect();
if real_ids.is_empty() || real_ids.iter().all(|i| *i == 0) {
return i64::MIN;
}
let mut s: i64 = 0;
for (t, i) in last3 {
if *t == u32::MAX {
continue;
}
if *t == 0 {
s += 10;
}
if (1..=10000).contains(i) {
s += 20;
} else if (1..=0xffff).contains(i) {
s += 5;
} else {
s -= 10;
}
}
if real_ids.len() >= 2 {
let max = *real_ids.iter().max().unwrap();
let min = *real_ids.iter().min().unwrap();
if max > 0 && max - min <= 50 {
s += 25;
}
}
s
}
fn read_field(
ft: &formats::FieldType,
bytes: &[u8],
limits: WalkerLimits,
) -> Option<(usize, InstanceField)> {
match ft {
formats::FieldType::Pointer { .. } => {
if bytes.len() < 8 {
return None;
}
let a = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let b = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
Some((8, InstanceField::Pointer { raw: [a, b] }))
}
formats::FieldType::ElementId | formats::FieldType::ElementIdRef { .. } => {
if bytes.len() < 8 {
return None;
}
let tag = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let id = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
Some((8, InstanceField::ElementId { tag, id }))
}
formats::FieldType::Container { kind: 0x0e, .. } => {
if bytes.len() < 4 {
return None;
}
let count = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
if count > limits.max_container_records {
return None;
}
let elem_size = 6;
let col_bytes = 4 + count * elem_size;
let total = 2 * col_bytes;
if bytes.len() < total {
return None;
}
let col2_count = u32::from_le_bytes([
bytes[col_bytes],
bytes[col_bytes + 1],
bytes[col_bytes + 2],
bytes[col_bytes + 3],
]) as usize;
if col2_count != count {
let mut col_a = Vec::with_capacity(count);
for k in 0..count {
let base = 4 + k * elem_size;
col_a.push(u16::from_le_bytes([bytes[base], bytes[base + 1]]));
}
return Some((
col_bytes,
InstanceField::RefContainer {
col_a,
col_b: Vec::new(),
},
));
}
let mut col_a = Vec::with_capacity(count);
let mut col_b = Vec::with_capacity(count);
for k in 0..count {
let base_a = 4 + k * elem_size;
let base_b = col_bytes + 4 + k * elem_size;
col_a.push(u16::from_le_bytes([bytes[base_a], bytes[base_a + 1]]));
col_b.push(u16::from_le_bytes([bytes[base_b], bytes[base_b + 1]]));
}
Some((total, InstanceField::RefContainer { col_a, col_b }))
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn find_adocument_start_returns_none_on_empty_stream() {
assert_eq!(find_adocument_start(&[]), None);
assert_eq!(find_adocument_start(&[0u8; 0x200]), None);
}
#[test]
fn scan_candidates_returns_empty_on_empty_schema() {
let schema = formats::SchemaTable {
classes: Vec::new(),
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let out = scan_candidates(&schema, &[0u8; 100], 0);
assert!(out.is_empty());
}
#[test]
fn scan_candidates_handles_buffer_shorter_than_4_bytes() {
let schema = formats::SchemaTable {
classes: Vec::new(),
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let out = scan_candidates(&schema, &[], i64::MIN);
assert!(out.is_empty());
let out = scan_candidates(&schema, &[0, 1], i64::MIN);
assert!(out.is_empty());
}
#[test]
fn scan_candidates_reports_max_scan_bytes_limit() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "Limited".into(),
tag: Some(0xCAFE),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(3),
was_parent_only: false,
fields: vec![
FieldEntry {
name: "m_a".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_b".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_c".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
],
};
let schema = formats::SchemaTable {
classes: vec![cls],
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let mut buf = vec![0u8; 80];
buf[64] = 0xfe;
buf[65] = 0xca;
let result = scan_candidates_with_limits(
&schema,
&buf,
i64::MIN,
WalkerLimits {
max_scan_bytes: 32,
..WalkerLimits::default()
},
);
assert!(result.candidates.is_empty());
assert_eq!(result.scanned_bytes, 32);
assert_eq!(result.limit_hit, Some(WalkerLimitHit::MaxScanBytes));
}
#[test]
fn scan_candidates_reports_max_candidates_limit() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "Limited".into(),
tag: Some(0xBEEF),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(3),
was_parent_only: false,
fields: vec![
FieldEntry {
name: "m_a".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_b".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_c".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
],
};
let schema = formats::SchemaTable {
classes: vec![cls],
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let mut buf = vec![0u8; 96];
for offset in [0usize, 34] {
buf[offset] = 0xef;
buf[offset + 1] = 0xbe;
for (idx, id) in [1u32, 2, 3].into_iter().enumerate() {
let base = offset + 2 + idx * 8;
buf[base..base + 4].copy_from_slice(&0u32.to_le_bytes());
buf[base + 4..base + 8].copy_from_slice(&id.to_le_bytes());
}
}
let result = scan_candidates_with_limits(
&schema,
&buf,
i64::MIN,
WalkerLimits {
max_candidates: 1,
..WalkerLimits::default()
},
);
assert_eq!(result.candidates.len(), 1);
assert_eq!(result.limit_hit, Some(WalkerLimitHit::MaxCandidates));
}
#[test]
fn scan_candidates_reports_per_record_decode_limit() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "Limited".into(),
tag: Some(0xABCD),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(3),
was_parent_only: false,
fields: vec![
FieldEntry {
name: "m_a".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_b".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_c".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
],
};
let schema = formats::SchemaTable {
classes: vec![cls],
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let mut buf = vec![0u8; 64];
buf[0] = 0xcd;
buf[1] = 0xab;
let result = scan_candidates_with_limits(
&schema,
&buf,
i64::MIN,
WalkerLimits {
max_per_record_decode_bytes: 8,
..WalkerLimits::default()
},
);
assert!(result.candidates.is_empty());
assert_eq!(
result.limit_hit,
Some(WalkerLimitHit::MaxPerRecordDecodeBytes)
);
}
#[test]
fn scan_candidates_rejects_truncated_second_container_payload() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "ContainerOnly".into(),
tag: Some(0xB00B),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(1),
was_parent_only: false,
fields: vec![FieldEntry {
name: "m_refs".into(),
cpp_type: None,
field_type: Some(FieldType::Container {
kind: 0x0e,
cpp_signature: None,
body: Vec::new(),
}),
}],
};
let schema = formats::SchemaTable {
classes: vec![cls],
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let mut buf = Vec::new();
buf.extend_from_slice(&0xB00Bu16.to_le_bytes());
buf.extend_from_slice(&1u32.to_le_bytes());
buf.extend_from_slice(&[0u8; 6]);
buf.extend_from_slice(&1u32.to_le_bytes());
let result = scan_candidates_with_limits(&schema, &buf, i64::MIN, WalkerLimits::default());
assert!(result.candidates.is_empty());
assert_eq!(result.limit_hit, None);
}
#[test]
fn detect_adocument_start_reports_trial_offset_limit() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "ADocument".into(),
tag: Some(0x0004),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(3),
was_parent_only: false,
fields: vec![
FieldEntry {
name: "m_a".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_b".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_c".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
],
};
let bytes = vec![0u8; 1024];
let result = detect_adocument_start_with_limits(
&bytes,
Some(&cls),
WalkerLimits {
max_trial_offsets: 2,
..WalkerLimits::default()
},
);
assert_eq!(result.strategy, DetectionStrategy::NotFound);
assert_eq!(result.candidates_evaluated, 2);
assert_eq!(result.limit_hit, Some(WalkerLimitHit::MaxTrialOffsets));
}
#[test]
fn scan_candidates_sorts_by_score_descending() {
use formats::{ClassEntry, FieldEntry, FieldType};
let simple_cls = ClassEntry {
name: "Simple".into(),
tag: Some(0xABCD),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(3),
was_parent_only: false,
fields: vec![
FieldEntry {
name: "m_a".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_b".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_c".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
],
};
let schema = formats::SchemaTable {
classes: vec![simple_cls],
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let mut buf = vec![0u8; 64];
buf[0] = 0xCD;
buf[1] = 0xAB; buf[2..6].copy_from_slice(&0u32.to_le_bytes());
buf[6..10].copy_from_slice(&1u32.to_le_bytes());
buf[10..14].copy_from_slice(&0u32.to_le_bytes());
buf[14..18].copy_from_slice(&2u32.to_le_bytes());
buf[18..22].copy_from_slice(&0u32.to_le_bytes());
buf[22..26].copy_from_slice(&3u32.to_le_bytes());
let out = scan_candidates(&schema, &buf, i64::MIN);
assert!(
!out.is_empty(),
"expected at least one candidate for tagged simple class"
);
for pair in out.windows(2) {
assert!(pair[0].score >= pair[1].score);
}
}
#[test]
fn build_handle_index_empty_schema_returns_empty_index() {
let schema = formats::SchemaTable {
classes: Vec::new(),
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let idx = build_handle_index(&schema, &[0u8; 100], 0);
assert!(idx.is_empty());
}
#[test]
fn build_handle_index_extracts_self_id_and_skips_zero() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "Indexable".into(),
tag: Some(0xBEEF),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(3),
was_parent_only: false,
fields: vec![
FieldEntry {
name: "m_id".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_owner".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_other".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
],
};
let schema = formats::SchemaTable {
classes: vec![cls],
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let mut buf = vec![0u8; 128];
buf[0] = 0xEF;
buf[1] = 0xBE;
buf[2..6].copy_from_slice(&0u32.to_le_bytes()); buf[6..10].copy_from_slice(&42u32.to_le_bytes()); buf[10..14].copy_from_slice(&0u32.to_le_bytes()); buf[14..18].copy_from_slice(&1u32.to_le_bytes()); buf[18..22].copy_from_slice(&0u32.to_le_bytes()); buf[22..26].copy_from_slice(&2u32.to_le_bytes()); buf[34] = 0xEF;
buf[35] = 0xBE;
buf[36..40].copy_from_slice(&0u32.to_le_bytes()); buf[40..44].copy_from_slice(&0u32.to_le_bytes()); buf[44..48].copy_from_slice(&0u32.to_le_bytes());
buf[48..52].copy_from_slice(&11u32.to_le_bytes());
buf[52..56].copy_from_slice(&0u32.to_le_bytes());
buf[56..60].copy_from_slice(&12u32.to_le_bytes());
let idx = build_handle_index(&schema, &buf, i64::MIN + 1);
assert_eq!(
idx.get(42),
Some(2),
"instance 1 should map id 42 → offset 2 (after class tag)"
);
assert_eq!(
idx.get(0),
None,
"id=0 is the no-id sentinel, must not be indexed"
);
}
#[test]
fn build_handle_index_skips_classes_without_self_id_field() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "Parent".into(),
tag: Some(0xABAB),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(1),
was_parent_only: true,
fields: vec![FieldEntry {
name: "m_name".into(),
cpp_type: None,
field_type: Some(FieldType::String),
}],
};
let schema = formats::SchemaTable {
classes: vec![cls],
cpp_types: Vec::new(),
skipped_records: 0,
..Default::default()
};
let mut buf = vec![0u8; 64];
buf[0] = 0xAB;
buf[1] = 0xAB;
let idx = build_handle_index(&schema, &buf, i64::MIN + 1);
assert!(idx.is_empty());
}
#[test]
fn find_self_id_field_prefers_m_id() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "Wall".into(),
tag: Some(0x0080),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(3),
was_parent_only: false,
fields: vec![
FieldEntry {
name: "m_ownerId".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_id".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
FieldEntry {
name: "m_extraId".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
],
};
assert_eq!(find_self_id_field(&cls), Some(1));
}
#[test]
fn find_self_id_field_falls_back_to_first_bare_elementid() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "Weird".into(),
tag: Some(0x0100),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(3),
was_parent_only: false,
fields: vec![
FieldEntry {
name: "m_ownerFamilyId".into(),
cpp_type: None,
field_type: Some(FieldType::ElementIdRef {
referenced_tag: 0x0080,
sub: 0,
}),
},
FieldEntry {
name: "m_anonId".into(),
cpp_type: None,
field_type: Some(FieldType::ElementId),
},
],
};
assert_eq!(find_self_id_field(&cls), Some(1));
}
#[test]
fn find_self_id_field_returns_none_when_no_elementid() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "Empty".into(),
tag: None,
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(1),
was_parent_only: true,
fields: vec![FieldEntry {
name: "m_name".into(),
cpp_type: None,
field_type: Some(FieldType::String),
}],
};
assert_eq!(find_self_id_field(&cls), None);
}
#[test]
fn find_self_id_field_rejects_elementidref_only_matches() {
use formats::{ClassEntry, FieldEntry, FieldType};
let cls = ClassEntry {
name: "AllRef".into(),
tag: Some(0x0200),
parent: None,
ancestor_tag: None,
offset: 0,
declared_field_count: Some(2),
was_parent_only: false,
fields: vec![
FieldEntry {
name: "m_a".into(),
cpp_type: None,
field_type: Some(FieldType::ElementIdRef {
referenced_tag: 0x0080,
sub: 0,
}),
},
FieldEntry {
name: "m_b".into(),
cpp_type: None,
field_type: Some(FieldType::ElementIdRef {
referenced_tag: 0x0081,
sub: 0,
}),
},
],
};
assert_eq!(find_self_id_field(&cls), None);
}
#[test]
fn read_field_pointer_reads_8_bytes() {
let ft = formats::FieldType::Pointer { kind: 2 };
let bytes = [0x01u8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00];
let (n, v) = read_field(&ft, &bytes, WalkerLimits::default()).unwrap();
assert_eq!(n, 8);
match v {
InstanceField::Pointer { raw } => assert_eq!(raw, [1, 2]),
_ => panic!("expected Pointer"),
}
}
#[test]
fn read_field_container_u32_delegates_to_vector_layout() {
let ft = formats::FieldType::Container {
kind: 0x05,
cpp_signature: None,
body: Vec::new(),
};
let mut bytes = vec![0x03, 0x00, 0x00, 0x00]; for v in [10u32, 20, 30] {
bytes.extend_from_slice(&v.to_le_bytes());
}
let mut cursor = 0;
let field = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, 4 + 3 * 4);
match field {
InstanceField::Vector(items) => {
assert_eq!(items.len(), 3);
for (i, expected) in [10i64, 20, 30].iter().enumerate() {
match &items[i] {
InstanceField::Integer { value, size, .. } => {
assert_eq!(*value, *expected);
assert_eq!(*size, 4);
}
other => panic!("items[{i}]: expected Integer, got {other:?}"),
}
}
}
other => panic!("expected Vector, got {other:?}"),
}
}
#[test]
fn read_field_container_f64_delegates_to_vector_layout() {
let ft = formats::FieldType::Container {
kind: 0x07,
cpp_signature: None,
body: Vec::new(),
};
let mut bytes = vec![0x02, 0x00, 0x00, 0x00]; for v in [1.5_f64, 9.75] {
bytes.extend_from_slice(&v.to_le_bytes());
}
let mut cursor = 0;
let field = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, 4 + 2 * 8);
match field {
InstanceField::Vector(items) => {
assert_eq!(items.len(), 2);
match &items[0] {
InstanceField::Float { value, size } => {
assert!((*value - 1.5).abs() < 1e-12);
assert_eq!(*size, 8);
}
other => panic!("items[0]: expected Float, got {other:?}"),
}
}
other => panic!("expected Vector, got {other:?}"),
}
}
#[test]
fn container_scalar_round_trips_byte_identical() {
let cases: Vec<(u8, Vec<u8>)> = vec![
(0x01, vec![3, 0, 0, 0, 1, 0, 1]),
(
0x05,
vec![2, 0, 0, 0, 0x00, 0x01, 0x00, 0x00, 0x0A, 0, 0, 0],
),
(0x07, {
let mut v = vec![1, 0, 0, 0];
v.extend_from_slice(&1.5_f64.to_le_bytes());
v
}),
(0x0d, {
let mut v = vec![1, 0, 0, 0];
for f in [3.0_f64, 4.0, 5.0] {
v.extend_from_slice(&f.to_le_bytes());
}
v
}),
];
for (kind, wire) in cases {
let ft = formats::FieldType::Container {
kind,
cpp_signature: None,
body: Vec::new(),
};
let mut cursor = 0;
let decoded = read_field_by_type(&wire, &mut cursor, &ft);
assert_eq!(
cursor,
wire.len(),
"kind 0x{kind:02x}: reader consumed wrong number of bytes"
);
let mut rewritten = Vec::new();
write_field_by_type(&decoded, &ft, &mut rewritten);
assert_eq!(
rewritten, wire,
"kind 0x{kind:02x}: round-trip produced different bytes"
);
}
}
#[test]
fn read_field_container_0x0e_still_falls_back() {
let ft = formats::FieldType::Container {
kind: 0x0e,
cpp_signature: None,
body: Vec::new(),
};
let bytes = vec![0xDE, 0xAD, 0xBE, 0xEF, 0x11, 0x22];
let mut cursor = 0;
let field = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, 4);
match field {
InstanceField::Bytes(b) => assert_eq!(b, vec![0xDE, 0xAD, 0xBE, 0xEF]),
other => panic!("expected Bytes fallback, got {other:?}"),
}
}
#[test]
fn read_field_element_id_reads_8_bytes() {
let ft = formats::FieldType::ElementId;
let bytes = [0x00u8, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00];
let (n, v) = read_field(&ft, &bytes, WalkerLimits::default()).unwrap();
assert_eq!(n, 8);
match v {
InstanceField::ElementId { tag, id } => {
assert_eq!(tag, 0);
assert_eq!(id, 27);
}
_ => panic!("expected ElementId"),
}
}
#[test]
fn read_field_by_type_primitive_u32() {
let ft = formats::FieldType::Primitive {
kind: 0x05,
size: 4,
};
let bytes = [0x2a, 0x00, 0x00, 0x00];
let mut cursor = 0;
let v = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, 4);
match v {
InstanceField::Integer {
value,
signed,
size,
} => {
assert_eq!(value, 42);
assert!(!signed);
assert_eq!(size, 4);
}
_ => panic!("expected Integer, got {v:?}"),
}
}
#[test]
fn read_field_by_type_primitive_f64() {
let ft = formats::FieldType::Primitive {
kind: 0x07,
size: 8,
};
let bytes = 42.5_f64.to_le_bytes();
let mut cursor = 0;
let v = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, 8);
match v {
InstanceField::Float { value, size } => {
assert!((value - 42.5).abs() < 1e-9);
assert_eq!(size, 8);
}
_ => panic!("expected Float, got {v:?}"),
}
}
#[test]
fn read_field_by_type_primitive_bool() {
let ft = formats::FieldType::Primitive {
kind: 0x01,
size: 1,
};
let bytes = [1u8, 99];
let mut cursor = 0;
let v = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, 1);
match v {
InstanceField::Bool(b) => assert!(b),
_ => panic!("expected Bool, got {v:?}"),
}
}
#[test]
fn read_field_by_type_guid_16_bytes() {
let ft = formats::FieldType::Guid;
let bytes: Vec<u8> = (0..16).collect();
let mut cursor = 0;
let v = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, 16);
match v {
InstanceField::Guid(g) => assert_eq!(g.to_vec(), (0..16u8).collect::<Vec<_>>()),
_ => panic!("expected Guid, got {v:?}"),
}
}
#[test]
fn read_field_by_type_string_utf16le() {
let ft = formats::FieldType::String;
let mut bytes = vec![];
bytes.extend_from_slice(&4u32.to_le_bytes());
for ch in "Test".encode_utf16() {
bytes.extend_from_slice(&ch.to_le_bytes());
}
let mut cursor = 0;
let v = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, bytes.len());
match v {
InstanceField::String(s) => assert_eq!(s, "Test"),
_ => panic!("expected String, got {v:?}"),
}
}
#[test]
fn read_field_by_type_graceful_on_short_input() {
let ft = formats::FieldType::Pointer { kind: 2 };
let bytes = [0xff, 0xff, 0xff];
let mut cursor = 0;
let v = read_field_by_type(&bytes, &mut cursor, &ft);
match v {
InstanceField::Bytes(_) => {}
_ => panic!("expected Bytes on short input, got {v:?}"),
}
}
#[test]
fn handle_index_basic_operations() {
let mut idx = HandleIndex::new();
assert!(idx.is_empty());
idx.insert(42, 0x100);
idx.insert(7, 0x050);
assert_eq!(idx.len(), 2);
assert_eq!(idx.get(42), Some(0x100));
assert_eq!(idx.get(7), Some(0x050));
assert_eq!(idx.get(99), None);
let pairs: Vec<_> = idx.iter().collect();
assert_eq!(pairs, vec![(7, 0x050), (42, 0x100)]);
}
#[test]
fn decode_instance_walks_fields_in_schema_order() {
let class = formats::ClassEntry {
name: "SynthClass".to_string(),
offset: 0,
fields: vec![
formats::FieldEntry {
name: "a_bool".to_string(),
cpp_type: Some("bool".into()),
field_type: Some(formats::FieldType::Primitive {
kind: 0x01,
size: 1,
}),
},
formats::FieldEntry {
name: "a_u32".to_string(),
cpp_type: Some("unsigned int".into()),
field_type: Some(formats::FieldType::Primitive {
kind: 0x05,
size: 4,
}),
},
formats::FieldEntry {
name: "a_guid".to_string(),
cpp_type: Some("Guid".into()),
field_type: Some(formats::FieldType::Guid),
},
],
tag: Some(123),
parent: None,
declared_field_count: Some(3),
was_parent_only: false,
ancestor_tag: None,
};
let mut bytes = Vec::new();
bytes.push(1); bytes.extend_from_slice(&42u32.to_le_bytes()); bytes.extend_from_slice(&[7u8; 16]);
let decoded = decode_instance(&bytes, 0, &class);
assert_eq!(decoded.class, "SynthClass");
assert_eq!(decoded.fields.len(), 3);
assert!(matches!(decoded.fields[0].1, InstanceField::Bool(true)));
match &decoded.fields[1].1 {
InstanceField::Integer { value, .. } => assert_eq!(*value, 42),
other => panic!("expected Integer, got {other:?}"),
}
match &decoded.fields[2].1 {
InstanceField::Guid(g) => assert_eq!(g[0], 7),
other => panic!("expected Guid, got {other:?}"),
}
assert_eq!(decoded.byte_range.end, bytes.len());
}
#[test]
fn read_field_ref_container_2column() {
let ft = formats::FieldType::Container {
kind: 0x0e,
cpp_signature: None,
body: Vec::new(),
};
let mut bytes = Vec::new();
bytes.extend_from_slice(&[2, 0, 0, 0]); bytes.extend_from_slice(&[0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff]); bytes.extend_from_slice(&[0xbb, 0xbb, 0xff, 0xff, 0xff, 0xff]); bytes.extend_from_slice(&[2, 0, 0, 0]); bytes.extend_from_slice(&[0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff]); bytes.extend_from_slice(&[0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff]); let (n, v) = read_field(&ft, &bytes, WalkerLimits::default()).unwrap();
assert_eq!(n, 32); match v {
InstanceField::RefContainer { col_a, col_b } => {
assert_eq!(col_a, vec![0xaaaa, 0xbbbb]);
assert_eq!(col_b, vec![0xcccc, 0xdddd]);
}
_ => panic!("expected RefContainer"),
}
}
#[test]
fn read_field_honors_walker_limits_container_cap() {
let ft = formats::FieldType::Container {
kind: 0x0e,
cpp_signature: None,
body: Vec::new(),
};
let mut bytes = Vec::new();
bytes.extend_from_slice(&[2, 0, 0, 0]);
bytes.extend_from_slice(&[0xaa, 0xaa, 0xff, 0xff, 0xff, 0xff]);
bytes.extend_from_slice(&[0xbb, 0xbb, 0xff, 0xff, 0xff, 0xff]);
bytes.extend_from_slice(&[2, 0, 0, 0]);
bytes.extend_from_slice(&[0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff]);
bytes.extend_from_slice(&[0xdd, 0xdd, 0xff, 0xff, 0xff, 0xff]);
assert!(read_field(&ft, &bytes, WalkerLimits::default()).is_some());
let tight = WalkerLimits {
max_container_records: 1,
..WalkerLimits::default()
};
assert!(read_field(&ft, &bytes, tight).is_none());
let at_cap = WalkerLimits {
max_container_records: 2,
..WalkerLimits::default()
};
assert!(read_field(&ft, &bytes, at_cap).is_some());
}
#[test]
fn element_provenance_defaults_and_filters() {
let p = ElementProvenance::default();
assert!((p.confidence - 1.0).abs() < f32::EPSILON);
let typed = ElementProvenance::schema_typed("Global/Latest", 42, "LevelDecoder");
assert!((typed.confidence - 0.9).abs() < f32::EPSILON);
assert_eq!(typed.source_offset, Some(42));
let low = ElementProvenance::partition(
"partition",
0,
"partition_schema_mvp",
"level",
0.4,
Some("elevation_unknown"),
);
let el = DecodedElement {
id: None,
class: "Level".into(),
fields: vec![],
byte_range: 0..0,
provenance: low,
};
assert!(!el.meets_confidence(DEFAULT_MIN_ELEMENT_CONFIDENCE));
assert!(el.meets_confidence(0.4));
let json = el.provenance.to_json_value();
assert_eq!(json["decoder"], "level");
assert_eq!(json["warnings"][0], "elevation_unknown");
}
#[test]
fn walker_limits_default_matches_legacy_hardcoded() {
assert_eq!(WalkerLimits::default().max_scan_bytes, 128 * 1024 * 1024);
assert_eq!(WalkerLimits::default().max_candidates, 100_000);
assert_eq!(WalkerLimits::default().max_trial_offsets, 16_000_000);
assert_eq!(
WalkerLimits::default().max_per_record_decode_bytes,
1024 * 1024
);
assert_eq!(WalkerLimits::default().max_container_records, 1000);
}
#[test]
fn detect_adocument_start_on_empty_returns_not_found() {
let r = detect_adocument_start(&[], None);
assert_eq!(r.strategy, DetectionStrategy::NotFound);
assert_eq!(r.offset, None);
assert_eq!(r.score, None);
assert_eq!(r.candidates_evaluated, 0);
assert_eq!(r.limit_hit, None);
}
#[test]
fn detect_adocument_start_on_small_buffer_no_schema() {
let d = vec![0u8; 64];
let r = detect_adocument_start(&d, None);
assert_eq!(r.strategy, DetectionStrategy::NotFound);
assert_eq!(r.offset, None);
}
#[test]
fn read_field_by_type_vector_of_f64() {
let ft = formats::FieldType::Vector {
kind: 0x07,
body: Vec::new(),
};
let mut bytes = Vec::new();
bytes.extend_from_slice(&[2, 0, 0, 0]); bytes.extend_from_slice(&1.25f64.to_le_bytes());
bytes.extend_from_slice(&(-3.5f64).to_le_bytes());
let mut cursor = 0;
let field = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, 4 + 2 * 8);
match field {
InstanceField::Vector(items) => {
assert_eq!(items.len(), 2);
assert!(matches!(&items[0], InstanceField::Float { value, .. } if *value == 1.25));
assert!(matches!(&items[1], InstanceField::Float { value, .. } if *value == -3.5));
}
other => panic!("expected Vector, got {other:?}"),
}
}
#[test]
fn read_field_by_type_vector_of_points() {
let ft = formats::FieldType::Vector {
kind: 0x0d,
body: Vec::new(),
};
let mut bytes = Vec::new();
bytes.extend_from_slice(&[1, 0, 0, 0]); for v in [10.0f64, 20.0, 30.0] {
bytes.extend_from_slice(&v.to_le_bytes());
}
let mut cursor = 0;
let field = read_field_by_type(&bytes, &mut cursor, &ft);
assert_eq!(cursor, 4 + 24);
match field {
InstanceField::Vector(points) => {
assert_eq!(points.len(), 1);
match &points[0] {
InstanceField::Vector(xyz) => {
assert_eq!(xyz.len(), 3);
let extract = |f: &InstanceField| match f {
InstanceField::Float { value, .. } => *value,
_ => f64::NAN,
};
assert_eq!(extract(&xyz[0]), 10.0);
assert_eq!(extract(&xyz[1]), 20.0);
assert_eq!(extract(&xyz[2]), 30.0);
}
other => panic!("expected inner Vector(point), got {other:?}"),
}
}
other => panic!("expected outer Vector, got {other:?}"),
}
}
#[test]
fn adocument_instance_completeness_basic() {
let inst = ADocumentInstance {
entry_offset: 0,
version: 2024,
fields: vec![
(
"a".into(),
InstanceField::Integer {
value: 1,
signed: false,
size: 4,
},
),
("b".into(), InstanceField::Bytes(vec![0xff])),
("c".into(), InstanceField::String("hello".into())),
("d".into(), InstanceField::Vector(vec![])),
("e".into(), InstanceField::Bytes(Vec::new())),
],
};
let c = inst.completeness();
assert_eq!(c.total, 5);
assert_eq!(c.typed, 3); assert_eq!(c.raw_bytes_fallback, 2); assert_eq!(c.typed_and_non_empty, 2); assert!(!c.is_fully_typed());
let ratio = c.typed_ratio().unwrap();
assert!((ratio - 0.6).abs() < 1e-9);
}
#[test]
fn completeness_empty_instance_has_no_ratio() {
let inst = ADocumentInstance {
entry_offset: 0,
version: 0,
fields: vec![],
};
let c = inst.completeness();
assert_eq!(c.total, 0);
assert!(c.typed_ratio().is_none());
assert!(!c.is_fully_typed());
}
#[test]
fn completeness_fully_typed_instance() {
let inst = ADocumentInstance {
entry_offset: 0,
version: 2026,
fields: vec![
("a".into(), InstanceField::Bool(true)),
("b".into(), InstanceField::ElementId { tag: 0, id: 42 }),
],
};
let c = inst.completeness();
assert!(c.is_fully_typed());
assert_eq!(c.typed_ratio(), Some(1.0));
}
#[test]
fn read_field_by_type_vector_unknown_kind_falls_back() {
let ft = formats::FieldType::Vector {
kind: 0xff,
body: Vec::new(),
};
let bytes = [0, 0, 0, 0, 0xaa, 0xbb];
let mut cursor = 0;
let field = read_field_by_type(&bytes, &mut cursor, &ft);
assert!(matches!(field, InstanceField::Bytes(_)));
}
fn mk_adoc(fields: Vec<(String, InstanceField)>) -> ADocumentInstance {
ADocumentInstance {
entry_offset: 0,
version: 2026,
fields,
}
}
#[test]
fn elem_table_pointer_extracts_canonical_field() {
let adoc = mk_adoc(vec![(
"m_elem_table".into(),
InstanceField::Pointer { raw: [42, 1337] },
)]);
assert_eq!(adoc.elem_table_pointer(), Some([42, 1337]));
}
#[test]
fn elem_table_pointer_accepts_snake_case_variant() {
let adoc = mk_adoc(vec![(
"m_elemTable".into(),
InstanceField::Pointer { raw: [7, 8] },
)]);
assert_eq!(adoc.elem_table_pointer(), Some([7, 8]));
}
#[test]
fn elem_table_pointer_returns_none_on_null_sentinel() {
let adoc = mk_adoc(vec![(
"m_elem_table".into(),
InstanceField::Pointer { raw: [0, 0] },
)]);
assert_eq!(adoc.elem_table_pointer(), None);
}
#[test]
fn elem_table_pointer_returns_none_on_max_sentinel() {
let adoc = mk_adoc(vec![(
"m_elem_table".into(),
InstanceField::Pointer {
raw: [u32::MAX, u32::MAX],
},
)]);
assert_eq!(adoc.elem_table_pointer(), None);
}
#[test]
fn elem_table_pointer_returns_none_when_field_absent() {
let adoc = mk_adoc(vec![(
"m_something_else".into(),
InstanceField::Pointer { raw: [1, 2] },
)]);
assert_eq!(adoc.elem_table_pointer(), None);
}
#[test]
fn elem_table_pointer_returns_none_when_field_is_bytes_fallback() {
let adoc = mk_adoc(vec![(
"m_elem_table".into(),
InstanceField::Bytes(vec![0x2a, 0, 0, 0, 0x39, 0x05, 0, 0]),
)]);
assert_eq!(adoc.elem_table_pointer(), None);
}
fn mk_schema(class_name: &str, fields: Vec<(&str, formats::FieldType)>) -> formats::ClassEntry {
formats::ClassEntry {
name: class_name.to_string(),
offset: 0,
fields: fields
.into_iter()
.map(|(name, ft)| formats::FieldEntry {
name: name.to_string(),
cpp_type: None,
field_type: Some(ft),
})
.collect(),
tag: None,
parent: None,
declared_field_count: None,
was_parent_only: false,
ancestor_tag: None,
}
}
#[test]
fn write_primitive_round_trips_u32() {
let ft = formats::FieldType::Primitive {
kind: 0x05,
size: 4,
};
let value = InstanceField::Integer {
value: 0xDEADBEEF,
signed: false,
size: 4,
};
let mut out = Vec::new();
write_field_by_type(&value, &ft, &mut out);
assert_eq!(out, vec![0xEF, 0xBE, 0xAD, 0xDE]);
let mut cursor = 0;
let round = read_field_by_type(&out, &mut cursor, &ft);
match round {
InstanceField::Integer { value: v, .. } => assert_eq!(v as u32, 0xDEADBEEF),
other => panic!("expected Integer, got {:?}", other),
}
}
#[test]
fn write_primitive_round_trips_f64() {
let ft = formats::FieldType::Primitive {
kind: 0x07,
size: 8,
};
let value = InstanceField::Float {
value: std::f64::consts::PI,
size: 8,
};
let mut out = Vec::new();
write_field_by_type(&value, &ft, &mut out);
let mut cursor = 0;
match read_field_by_type(&out, &mut cursor, &ft) {
InstanceField::Float { value, .. } => {
assert!((value - std::f64::consts::PI).abs() < 1e-15);
}
other => panic!("expected Float, got {:?}", other),
}
}
#[test]
fn write_primitive_round_trips_bool() {
let ft = formats::FieldType::Primitive {
kind: 0x01,
size: 1,
};
for expected in [true, false] {
let mut out = Vec::new();
write_field_by_type(&InstanceField::Bool(expected), &ft, &mut out);
let mut cursor = 0;
match read_field_by_type(&out, &mut cursor, &ft) {
InstanceField::Bool(b) => assert_eq!(b, expected),
other => panic!("expected Bool, got {:?}", other),
}
}
}
#[test]
fn write_string_round_trips_unicode() {
let ft = formats::FieldType::String;
for s in ["hello", "café", "日本語", ""] {
let mut out = Vec::new();
write_field_by_type(&InstanceField::String(s.to_string()), &ft, &mut out);
let mut cursor = 0;
match read_field_by_type(&out, &mut cursor, &ft) {
InstanceField::String(got) => assert_eq!(got, s),
other => panic!("expected String, got {:?}", other),
}
}
}
#[test]
fn write_guid_round_trips() {
let ft = formats::FieldType::Guid;
let g = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54,
0x32, 0x10,
];
let mut out = Vec::new();
write_field_by_type(&InstanceField::Guid(g), &ft, &mut out);
assert_eq!(out.len(), 16);
let mut cursor = 0;
match read_field_by_type(&out, &mut cursor, &ft) {
InstanceField::Guid(got) => assert_eq!(got, g),
other => panic!("expected Guid, got {:?}", other),
}
}
#[test]
fn write_element_id_round_trips() {
let ft = formats::FieldType::ElementId;
let value = InstanceField::ElementId {
tag: 0x14,
id: 0x1337,
};
let mut out = Vec::new();
write_field_by_type(&value, &ft, &mut out);
let mut cursor = 0;
match read_field_by_type(&out, &mut cursor, &ft) {
InstanceField::ElementId { tag, id } => {
assert_eq!(tag, 0x14);
assert_eq!(id, 0x1337);
}
other => panic!("expected ElementId, got {:?}", other),
}
}
#[test]
fn write_pointer_round_trips() {
let ft = formats::FieldType::Pointer { kind: 0x01 };
let value = InstanceField::Pointer {
raw: [0xAABBCCDD, 0x11223344],
};
let mut out = Vec::new();
write_field_by_type(&value, &ft, &mut out);
let mut cursor = 0;
match read_field_by_type(&out, &mut cursor, &ft) {
InstanceField::Pointer { raw } => assert_eq!(raw, [0xAABBCCDD, 0x11223344]),
other => panic!("expected Pointer, got {:?}", other),
}
}
#[test]
fn write_vector_of_doubles_round_trips() {
let ft = formats::FieldType::Vector {
kind: 0x07,
body: Vec::new(),
};
let items: Vec<InstanceField> = [1.0, 2.5, -7.25]
.iter()
.map(|v| InstanceField::Float { value: *v, size: 8 })
.collect();
let mut out = Vec::new();
write_field_by_type(&InstanceField::Vector(items), &ft, &mut out);
let mut cursor = 0;
match read_field_by_type(&out, &mut cursor, &ft) {
InstanceField::Vector(values) => {
assert_eq!(values.len(), 3);
if let InstanceField::Float { value, .. } = values[2] {
assert!((value + 7.25).abs() < 1e-9);
}
}
other => panic!("expected Vector, got {:?}", other),
}
}
#[test]
fn write_bytes_fallback_passes_through_verbatim() {
let ft = formats::FieldType::Unknown { bytes: Vec::new() };
let payload = vec![0xDE, 0xAD, 0xBE, 0xEF];
let mut out = Vec::new();
write_field_by_type(&InstanceField::Bytes(payload.clone()), &ft, &mut out);
assert_eq!(out, payload);
}
#[test]
fn encode_instance_round_trips_typed_fields() {
let schema = mk_schema(
"TestClass",
vec![
(
"m_id",
formats::FieldType::Primitive {
kind: 0x05,
size: 4,
},
),
(
"m_value",
formats::FieldType::Primitive {
kind: 0x07,
size: 8,
},
),
],
);
let decoded = DecodedElement {
id: None,
class: "TestClass".into(),
fields: vec![
(
"m_id".into(),
InstanceField::Integer {
value: 0x2a,
signed: false,
size: 4,
},
),
(
"m_value".into(),
InstanceField::Float {
value: 9.5,
size: 8,
},
),
],
byte_range: 0..12,
provenance: Default::default(),
};
let bytes = encode_instance(&decoded, &schema);
assert_eq!(bytes.len(), 12);
let re = decode_instance(&bytes, 0, &schema);
assert_eq!(re.fields.len(), 2);
}
#[test]
fn encode_instance_tolerates_field_count_mismatch() {
let schema = mk_schema(
"TestClass",
vec![
(
"a",
formats::FieldType::Primitive {
kind: 0x05,
size: 4,
},
),
(
"b",
formats::FieldType::Primitive {
kind: 0x05,
size: 4,
},
),
],
);
let decoded = DecodedElement {
id: None,
class: "TestClass".into(),
fields: vec![(
"a".into(),
InstanceField::Integer {
value: 0x2a,
signed: false,
size: 4,
},
)],
byte_range: 0..4,
provenance: Default::default(),
};
let bytes = encode_instance(&decoded, &schema);
assert_eq!(bytes.len(), 4);
}
#[test]
fn encode_ref_container_round_trips_balanced_columns() {
let col_a = vec![1u16, 2, 3, 4, 5];
let col_b = vec![10u16, 20, 30, 40, 50];
let bytes = encode_ref_container(&col_a, &col_b);
assert_eq!(bytes.len(), 68);
let ft = formats::FieldType::Container {
kind: 0x0e,
cpp_signature: None,
body: Vec::new(),
};
let (consumed, field) = read_field(&ft, &bytes, WalkerLimits::default()).expect("read ok");
assert_eq!(consumed, 68);
match field {
InstanceField::RefContainer { col_a: a, col_b: b } => {
assert_eq!(a, col_a);
assert_eq!(b, col_b);
}
other => panic!("expected RefContainer, got {:?}", other),
}
}
#[test]
fn encode_ref_container_empty_is_two_zero_length_prefixes() {
let bytes = encode_ref_container(&[], &[]);
assert_eq!(bytes.len(), 8);
assert_eq!(&bytes[..4], &[0u8; 4]);
assert_eq!(&bytes[4..], &[0u8; 4]);
}
#[test]
fn encode_ref_container_single_row_round_trips() {
let bytes = encode_ref_container(&[42], &[99]);
assert_eq!(bytes.len(), 20);
let ft = formats::FieldType::Container {
kind: 0x0e,
cpp_signature: None,
body: Vec::new(),
};
let (_, field) = read_field(&ft, &bytes, WalkerLimits::default()).unwrap();
match field {
InstanceField::RefContainer { col_a, col_b } => {
assert_eq!(col_a, vec![42]);
assert_eq!(col_b, vec![99]);
}
other => panic!("expected RefContainer, got {:?}", other),
}
}
#[test]
fn write_adocument_field_pointer_round_trips() {
let ft = formats::FieldType::Pointer { kind: 0x01 };
let value = InstanceField::Pointer {
raw: [0xDEADBEEF, 0x1337],
};
let mut out = Vec::new();
write_adocument_field(&value, &ft, &mut out);
assert_eq!(out.len(), 8);
let (consumed, decoded) = read_field(&ft, &out, WalkerLimits::default()).expect("read ok");
assert_eq!(consumed, 8);
match decoded {
InstanceField::Pointer { raw } => {
assert_eq!(raw, [0xDEADBEEF, 0x1337]);
}
other => panic!("expected Pointer, got {:?}", other),
}
}
#[test]
fn write_adocument_field_element_id_round_trips() {
let ft = formats::FieldType::ElementId;
let value = InstanceField::ElementId {
tag: 0x14,
id: 0x42,
};
let mut out = Vec::new();
write_adocument_field(&value, &ft, &mut out);
let (_, decoded) = read_field(&ft, &out, WalkerLimits::default()).unwrap();
match decoded {
InstanceField::ElementId { tag, id } => {
assert_eq!(tag, 0x14);
assert_eq!(id, 0x42);
}
other => panic!("expected ElementId, got {:?}", other),
}
}
#[test]
fn write_adocument_field_container_round_trips_two_columns() {
let ft = formats::FieldType::Container {
kind: 0x0e,
cpp_signature: None,
body: Vec::new(),
};
let value = InstanceField::RefContainer {
col_a: vec![0x10, 0x20, 0x30],
col_b: vec![0x01, 0x02, 0x03],
};
let mut out = Vec::new();
write_adocument_field(&value, &ft, &mut out);
let (_, decoded) = read_field(&ft, &out, WalkerLimits::default()).unwrap();
match decoded {
InstanceField::RefContainer { col_a, col_b } => {
assert_eq!(col_a, vec![0x10, 0x20, 0x30]);
assert_eq!(col_b, vec![0x01, 0x02, 0x03]);
}
other => panic!("expected RefContainer, got {:?}", other),
}
}
#[test]
fn write_adocument_field_bytes_passes_through_verbatim() {
let ft = formats::FieldType::Unknown { bytes: Vec::new() };
let value = InstanceField::Bytes(vec![0xAA, 0xBB, 0xCC, 0xDD]);
let mut out = Vec::new();
write_adocument_field(&value, &ft, &mut out);
assert_eq!(out, vec![0xAA, 0xBB, 0xCC, 0xDD]);
}
#[test]
fn encode_adocument_fields_walks_schema_order() {
let schema = formats::ClassEntry {
name: "ADocument".into(),
offset: 0,
fields: vec![
formats::FieldEntry {
name: "m_ptr".into(),
cpp_type: None,
field_type: Some(formats::FieldType::Pointer { kind: 1 }),
},
formats::FieldEntry {
name: "m_elem_table".into(),
cpp_type: None,
field_type: Some(formats::FieldType::Container {
kind: 0x0e,
cpp_signature: None,
body: Vec::new(),
}),
},
formats::FieldEntry {
name: "m_version".into(),
cpp_type: None,
field_type: Some(formats::FieldType::Primitive {
kind: 0x05,
size: 4,
}),
},
],
tag: None,
parent: None,
declared_field_count: None,
was_parent_only: false,
ancestor_tag: None,
};
let fields = vec![
("m_ptr".into(), InstanceField::Pointer { raw: [0x11, 0x22] }),
(
"m_elem_table".into(),
InstanceField::RefContainer {
col_a: vec![0x1, 0x2],
col_b: vec![0x3, 0x4],
},
),
(
"m_version".into(),
InstanceField::Integer {
value: 2024,
signed: false,
size: 4,
},
),
];
let bytes = encode_adocument_fields(&schema, &fields);
assert_eq!(bytes.len(), 44);
let mut cursor = 0;
let (n1, v1) = read_field(
schema.fields[0].field_type.as_ref().unwrap(),
&bytes[cursor..],
WalkerLimits::default(),
)
.unwrap();
cursor += n1;
let (n2, v2) = read_field(
schema.fields[1].field_type.as_ref().unwrap(),
&bytes[cursor..],
WalkerLimits::default(),
)
.unwrap();
cursor += n2;
let mut pcur = 0usize;
let v3 = read_field_by_type(
&bytes[cursor..],
&mut pcur,
schema.fields[2].field_type.as_ref().unwrap(),
);
assert!(matches!(v1, InstanceField::Pointer { raw } if raw == [0x11, 0x22]));
assert!(matches!(v2, InstanceField::RefContainer { col_a, col_b }
if col_a == vec![0x1, 0x2] && col_b == vec![0x3, 0x4]));
assert!(matches!(v3, InstanceField::Integer { value, .. } if value == 2024));
}
#[test]
fn encode_adocument_fields_tolerates_missing_decoded_field() {
let schema = formats::ClassEntry {
name: "ADocument".into(),
offset: 0,
fields: vec![
formats::FieldEntry {
name: "a".into(),
cpp_type: None,
field_type: Some(formats::FieldType::Pointer { kind: 1 }),
},
formats::FieldEntry {
name: "b".into(),
cpp_type: None,
field_type: Some(formats::FieldType::ElementId),
},
],
tag: None,
parent: None,
declared_field_count: None,
was_parent_only: false,
ancestor_tag: None,
};
let fields = vec![("a".into(), InstanceField::Pointer { raw: [1, 2] })];
let bytes = encode_adocument_fields(&schema, &fields);
assert_eq!(bytes.len(), 8); }
#[test]
fn encode_ref_container_pads_each_record_to_six_bytes() {
let bytes = encode_ref_container(&[0x1234], &[0xabcd]);
assert_eq!(bytes.len(), 20);
assert_eq!(&bytes[0..4], &[0x01, 0x00, 0x00, 0x00]);
assert_eq!(&bytes[4..10], &[0x34, 0x12, 0x00, 0x00, 0x00, 0x00]);
assert_eq!(&bytes[10..14], &[0x01, 0x00, 0x00, 0x00]);
assert_eq!(&bytes[14..20], &[0xcd, 0xab, 0x00, 0x00, 0x00, 0x00]);
}
#[test]
fn default_element_encoder_uses_encode_instance() {
struct MyEncoder;
impl ElementEncoder for MyEncoder {
fn class_name(&self) -> &'static str {
"TestClass"
}
}
let schema = mk_schema(
"TestClass",
vec![(
"a",
formats::FieldType::Primitive {
kind: 0x05,
size: 4,
},
)],
);
let decoded = DecodedElement {
id: None,
class: "TestClass".into(),
fields: vec![(
"a".into(),
InstanceField::Integer {
value: 0x2a,
signed: false,
size: 4,
},
)],
byte_range: 0..4,
provenance: Default::default(),
};
let enc = MyEncoder;
let bytes = enc.encode(&decoded, &schema);
assert_eq!(bytes, vec![0x2a, 0x00, 0x00, 0x00]);
}
}