use super::types::EscherRecordType;
use crate::ole::ppt::package::{PptError, Result};
use zerocopy::{byteorder::{U16, U32, LittleEndian}, FromBytes};
#[derive(Debug, Clone)]
pub struct EscherRecord<'data> {
pub record_type: EscherRecordType,
pub record_type_raw: u16,
pub version: u8,
pub instance: u16,
pub length: u32,
pub data: &'data [u8],
}
impl<'data> EscherRecord<'data> {
pub fn parse(data: &'data [u8], offset: usize) -> Result<(Self, usize)> {
if offset + 8 > data.len() {
return Err(PptError::Corrupted(
"Not enough data for Escher record header".to_string()
));
}
let ver_inst = U16::<LittleEndian>::read_from_bytes(&data[offset..offset + 2])
.map(|v| v.get())
.unwrap_or(0);
let record_type_raw = U16::<LittleEndian>::read_from_bytes(&data[offset + 2..offset + 4])
.map(|v| v.get())
.unwrap_or(0);
let length = U32::<LittleEndian>::read_from_bytes(&data[offset + 4..offset + 8])
.map(|v| v.get())
.unwrap_or(0);
let version = (ver_inst & 0x000F) as u8;
let instance = (ver_inst >> 4) & 0x0FFF;
let record_type = EscherRecordType::from(record_type_raw);
let data_end = offset + 8 + length as usize;
if data_end > data.len() {
if record_type.is_container() && offset + 8 <= data.len() {
let available_length = (data.len() - offset - 8) as u32;
let record_data = &data[offset + 8..];
return Ok((
Self {
record_type,
record_type_raw,
version,
instance,
length: available_length,
data: record_data,
},
8 + record_data.len(),
));
}
return Err(PptError::Corrupted(
format!("Escher record data extends beyond bounds: offset={}, length={}, data_len={}",
offset, length, data.len())
));
}
let record_data = &data[offset + 8..data_end];
Ok((
Self {
record_type,
record_type_raw,
version,
instance,
length,
data: record_data,
},
8 + length as usize,
))
}
#[inline]
pub fn is_container(&self) -> bool {
self.version == 0x0F || self.record_type.is_container()
}
#[inline]
pub fn can_contain_text(&self) -> bool {
self.record_type.can_contain_text()
}
#[inline]
pub fn data_offset(&self, parent_data: &[u8]) -> Option<usize> {
let parent_ptr = parent_data.as_ptr() as usize;
let data_ptr = self.data.as_ptr() as usize;
if data_ptr >= parent_ptr && data_ptr < parent_ptr + parent_data.len() {
Some(data_ptr - parent_ptr)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_container_record() {
let data = vec![
0x0F, 0x00, 0x04, 0xF0, 0x08, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
];
let (record, consumed) = EscherRecord::parse(&data, 0).unwrap();
assert_eq!(record.version, 0x0F);
assert_eq!(record.instance, 0);
assert_eq!(record.record_type, EscherRecordType::SpContainer);
assert_eq!(record.length, 8);
assert_eq!(record.data.len(), 8);
assert_eq!(consumed, 16);
assert!(record.is_container());
}
#[test]
fn test_parse_atom_record() {
let data = vec![
0x02, 0x00, 0x0A, 0xF0, 0x04, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, ];
let (record, consumed) = EscherRecord::parse(&data, 0).unwrap();
assert_eq!(record.version, 0x02);
assert_eq!(record.instance, 0);
assert_eq!(record.record_type, EscherRecordType::Sp);
assert_eq!(record.length, 4);
assert_eq!(record.data, &[0xAA, 0xBB, 0xCC, 0xDD]);
assert_eq!(consumed, 12);
assert!(!record.is_container());
}
}