use std::fmt;
use crate::{error::Error, util::read_u16_le};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ControlPropertyType {
Empty,
Short,
Integer,
Long,
String,
SafeArray,
Variant,
FixedData,
Object,
StringVariant,
Boolean,
VariantRef,
Unknown(u8),
}
impl ControlPropertyType {
pub fn from_raw(raw: u8) -> Self {
match raw & 0x0F {
0 => Self::Empty,
1 => Self::Short,
2 => Self::Integer,
3 => Self::Long,
4 => Self::String,
5 => Self::SafeArray,
6 => Self::Variant,
8 => Self::FixedData,
9 => Self::Object,
0xA => Self::StringVariant,
0xB => Self::Boolean,
0xC => Self::VariantRef,
n => Self::Unknown(n),
}
}
pub fn base_data_size(self) -> usize {
match self {
Self::Empty | Self::VariantRef => 0,
Self::Short | Self::Integer | Self::Long | Self::Variant | Self::Boolean => 4,
Self::String | Self::StringVariant => 6,
Self::FixedData => 6,
Self::Object => 0x1C,
Self::SafeArray => 0, Self::Unknown(_) => 0,
}
}
}
impl fmt::Display for ControlPropertyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "Empty"),
Self::Short => write!(f, "Short"),
Self::Integer => write!(f, "Integer"),
Self::Long => write!(f, "Long"),
Self::String | Self::StringVariant => write!(f, "String"),
Self::SafeArray => write!(f, "SafeArray"),
Self::Variant => write!(f, "Variant"),
Self::FixedData => write!(f, "FixedData"),
Self::Object => write!(f, "Object"),
Self::Boolean => write!(f, "Boolean"),
Self::VariantRef => write!(f, "VariantRef"),
Self::Unknown(n) => write!(f, "Type{n}"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ControlPropertyEntry<'a> {
bytes: &'a [u8],
}
impl<'a> ControlPropertyEntry<'a> {
pub const HEADER_SIZE: usize = 4;
#[inline]
pub fn frame_offset(&self) -> Result<u16, Error> {
read_u16_le(self.bytes, 0x00)
}
#[inline]
pub fn raw_type(&self) -> u8 {
self.bytes.get(0x02).copied().unwrap_or(0)
}
pub fn property_type(&self) -> ControlPropertyType {
ControlPropertyType::from_raw(self.raw_type())
}
#[inline]
pub fn flags(&self) -> u8 {
self.bytes.get(0x03).copied().unwrap_or(0)
}
pub fn data(&self) -> &'a [u8] {
self.bytes.get(Self::HEADER_SIZE..).unwrap_or(&[])
}
pub fn total_size(&self) -> Result<usize, Error> {
let ptype = self.property_type();
if ptype == ControlPropertyType::SafeArray {
return self.calc_safearray_total_size();
}
let base = ptype.base_data_size();
if base == 0 && self.flags() & 0x04 != 0 {
return Self::HEADER_SIZE
.checked_add(6)
.ok_or(Error::ArithmeticOverflow {
context: "ControlPropertyEntry::total_size header+6",
});
}
if matches!(
ptype,
ControlPropertyType::Short
| ControlPropertyType::Integer
| ControlPropertyType::Long
| ControlPropertyType::Boolean
) && self.flags() & 0x20 != 0
{
return Self::HEADER_SIZE
.checked_add(8)
.ok_or(Error::ArithmeticOverflow {
context: "ControlPropertyEntry::total_size header+8",
});
}
Self::HEADER_SIZE
.checked_add(base)
.ok_or(Error::ArithmeticOverflow {
context: "ControlPropertyEntry::total_size header+base",
})
}
fn calc_safearray_total_size(&self) -> Result<usize, Error> {
let elem_info = self.bytes.get(0x08).copied().unwrap_or(0);
let desc_offset: usize = if elem_info & 0x60 != 0 { 0x20 } else { 0x10 };
let needed = desc_offset
.checked_add(3)
.ok_or(Error::ArithmeticOverflow {
context: "calc_safearray_total_size desc_offset+3",
})?;
if self.bytes.len() < needed {
return Ok(0x28);
}
let dim_count = read_u16_le(self.bytes, desc_offset)? as usize;
let elem_flags_offset = desc_offset
.checked_add(2)
.ok_or(Error::ArithmeticOverflow {
context: "calc_safearray_total_size desc_offset+2",
})?;
let elem_flags = self
.bytes
.get(elem_flags_offset)
.copied()
.ok_or(Error::Truncated {
needed: elem_flags_offset.saturating_add(1),
available: self.bytes.len(),
})?;
let base: usize = if elem_info & 0x60 != 0 { 0x38 } else { 0x28 };
let dim_data = if dim_count > 0 {
dim_count.saturating_sub(1).saturating_mul(8)
} else {
0
};
let elem_extra: usize = if elem_flags & 0xE0 != 0 { 4 } else { 0 };
base.checked_add(dim_data)
.and_then(|v| v.checked_add(elem_extra))
.ok_or(Error::ArithmeticOverflow {
context: "calc_safearray_total_size base+dim_data+elem_extra",
})
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct ControlPropertyIter<'a> {
data: &'a [u8],
pos: usize,
remaining: u16,
}
impl<'a> ControlPropertyIter<'a> {
pub fn new(data: &'a [u8], count: u16) -> Self {
Self {
data,
pos: 0,
remaining: count,
}
}
}
impl<'a> Iterator for ControlPropertyIter<'a> {
type Item = ControlPropertyEntry<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining == 0 {
return None;
}
let header_end = self.pos.checked_add(ControlPropertyEntry::HEADER_SIZE)?;
if header_end > self.data.len() {
return None;
}
let entry_bytes = self.data.get(self.pos..)?;
if entry_bytes.len() < ControlPropertyEntry::HEADER_SIZE {
return None;
}
let entry = ControlPropertyEntry { bytes: entry_bytes };
let size = entry
.total_size()
.ok()?
.max(ControlPropertyEntry::HEADER_SIZE);
self.pos = self.pos.checked_add(size)?;
self.remaining = self.remaining.checked_sub(1)?;
Some(entry)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_control_property_types() {
assert_eq!(ControlPropertyType::from_raw(1), ControlPropertyType::Short);
assert_eq!(ControlPropertyType::from_raw(3), ControlPropertyType::Long);
assert_eq!(
ControlPropertyType::from_raw(4),
ControlPropertyType::String
);
assert_eq!(
ControlPropertyType::from_raw(5),
ControlPropertyType::SafeArray
);
assert_eq!(
ControlPropertyType::from_raw(9),
ControlPropertyType::Object
);
assert_eq!(
ControlPropertyType::from_raw(0xB),
ControlPropertyType::Boolean
);
assert_eq!(format!("{}", ControlPropertyType::String), "String");
assert_eq!(format!("{}", ControlPropertyType::Object), "Object");
}
#[test]
fn test_entry_sizes() {
assert_eq!(ControlPropertyType::Long.base_data_size(), 4);
assert_eq!(ControlPropertyType::String.base_data_size(), 6);
assert_eq!(ControlPropertyType::Object.base_data_size(), 0x1C);
assert_eq!(ControlPropertyType::SafeArray.base_data_size(), 0);
}
const CLS_CRC32_SA_ENTRY: [u8; 0x30] = [
0x38, 0x00, 0x05, 0x00, 0x5C, 0x00, 0x55, 0x00, 0x00, 0x00, 0x65, 0x00, 0x72, 0x00, 0x5C, 0x00, 0x01, 0x00, 0x92, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x5C, 0x00, 0xDE, 0x44, 0xAD, 0xB4, ];
#[test]
fn test_safearray_entry_size() {
let entry = ControlPropertyEntry {
bytes: &CLS_CRC32_SA_ENTRY,
};
assert_eq!(entry.frame_offset().unwrap(), 0x38);
assert_eq!(entry.property_type(), ControlPropertyType::SafeArray);
assert_eq!(entry.flags(), 0x00);
assert_eq!(entry.total_size().unwrap(), 0x2C);
}
#[test]
fn test_long_entry_size() {
let data: [u8; 8] = [0x34, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00];
let entry = ControlPropertyEntry { bytes: &data };
assert_eq!(entry.property_type(), ControlPropertyType::Long);
assert_eq!(entry.total_size().unwrap(), 4 + 4); }
#[test]
fn test_long_entry_with_flags() {
let data: [u8; 12] = [
0x10, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let entry = ControlPropertyEntry { bytes: &data };
assert_eq!(entry.property_type(), ControlPropertyType::Long);
assert_eq!(entry.total_size().unwrap(), 4 + 8); }
#[test]
fn test_iter_empty() {
let iter = ControlPropertyIter::new(&[], 0);
assert_eq!(iter.count(), 0);
}
}