use crate::{Result, error::Error};
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[repr(u8)]
pub enum ElementType {
End = 0x00,
Void = 0x01,
Boolean = 0x02,
Char = 0x03,
I1 = 0x04,
U1 = 0x05,
I2 = 0x06,
U2 = 0x07,
I4 = 0x08,
U4 = 0x09,
I8 = 0x0a,
U8 = 0x0b,
R4 = 0x0c,
R8 = 0x0d,
String = 0x0e,
SzArray = 0x1d,
Type = 0x50,
Boxed = 0x51,
Field = 0x53,
Property = 0x54,
Enum = 0x55,
}
impl ElementType {
#[must_use]
pub fn from_u8(b: u8) -> Option<Self> {
Some(match b {
0x00 => Self::End,
0x01 => Self::Void,
0x02 => Self::Boolean,
0x03 => Self::Char,
0x04 => Self::I1,
0x05 => Self::U1,
0x06 => Self::I2,
0x07 => Self::U2,
0x08 => Self::I4,
0x09 => Self::U4,
0x0a => Self::I8,
0x0b => Self::U8,
0x0c => Self::R4,
0x0d => Self::R8,
0x0e => Self::String,
0x1d => Self::SzArray,
0x50 => Self::Type,
0x51 => Self::Boxed,
0x53 => Self::Field,
0x54 => Self::Property,
0x55 => Self::Enum,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub enum Value {
Boolean(bool),
Char(u16),
I1(i8),
U1(u8),
I2(i16),
U2(u16),
I4(i32),
U4(u32),
I8(i64),
U8(u64),
R4(f32),
R8(f64),
String(Option<String>),
Type(Option<String>),
Array(Option<Vec<Value>>),
Raw(u8, Vec<u8>),
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub struct NamedArg {
pub kind: ElementType,
pub elem_type: u8,
pub inner_elem_type: Option<u8>,
pub name: String,
pub value: Value,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub struct CustomAttribute {
pub fixed: Vec<Value>,
pub named: Vec<NamedArg>,
}
impl CustomAttribute {
pub fn decode_with_types(blob: &[u8], ctor_types: &[u8]) -> Result<Self> {
let mut r = Reader::new(blob);
let prolog = r.read_u16_le()?;
if prolog != 0x0001 {
return Err(Error::FormatError(format!(
"CustomAttribute: bad prolog 0x{prolog:04x}"
)));
}
let mut fixed = Vec::with_capacity(ctor_types.len());
for &ty in ctor_types {
fixed.push(r.read_value(ty)?);
}
let num_named = r.read_u16_le()?;
let mut named = Vec::with_capacity(num_named as usize);
for _ in 0..num_named {
named.push(r.read_named_arg()?);
}
Ok(Self { fixed, named })
}
pub fn decode_raw_named(blob: &[u8], fixed_blob_len: usize) -> Result<Vec<NamedArg>> {
let mut r = Reader::new(blob);
let prolog = r.read_u16_le()?;
if prolog != 0x0001 {
return Err(Error::FormatError(format!(
"CustomAttribute: bad prolog 0x{prolog:04x}"
)));
}
r.skip(fixed_blob_len)?;
let num_named = r.read_u16_le()?;
let mut named = Vec::with_capacity(num_named as usize);
for _ in 0..num_named {
named.push(r.read_named_arg()?);
}
Ok(named)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub enum MarshalSpec {
Simple(u8),
Array {
elem_type: Option<u8>,
param_num: Option<u32>,
num_elem: Option<u32>,
param_num_multiplier: Option<u32>,
},
FixedSysString { size: u32 },
FixedArray { size: u32, elem_type: Option<u8> },
CustomMarshaler {
guid: String,
unmanaged_type: String,
managed_type: String,
cookie: String,
},
}
pub mod native_type {
pub const VOID: u8 = 0x01;
pub const BOOLEAN: u8 = 0x02;
pub const I1: u8 = 0x03;
pub const U1: u8 = 0x04;
pub const I2: u8 = 0x05;
pub const U2: u8 = 0x06;
pub const I4: u8 = 0x07;
pub const U4: u8 = 0x08;
pub const I8: u8 = 0x09;
pub const U8: u8 = 0x0a;
pub const R4: u8 = 0x0b;
pub const R8: u8 = 0x0c;
pub const LPSTR: u8 = 0x14;
pub const LPWSTR: u8 = 0x15;
pub const LPTSTR: u8 = 0x16;
pub const FIXEDSYSSTRING: u8 = 0x17;
pub const IUNKNOWN: u8 = 0x19;
pub const IDISPATCH: u8 = 0x1a;
pub const STRUCT: u8 = 0x1b;
pub const INTF: u8 = 0x1c;
pub const SAFEARRAY: u8 = 0x1d;
pub const FIXEDARRAY: u8 = 0x1e;
pub const INT: u8 = 0x1f;
pub const UINT: u8 = 0x20;
pub const BYVALSTR: u8 = 0x22;
pub const ANSIBSTR: u8 = 0x23;
pub const TBSTR: u8 = 0x24;
pub const VARIANTBOOL: u8 = 0x25;
pub const FUNC: u8 = 0x26;
pub const ASANY: u8 = 0x28;
pub const ARRAY: u8 = 0x2a;
pub const LPSTRUCT: u8 = 0x2b;
pub const CUSTOMMARSHALER: u8 = 0x2c;
pub const ERROR: u8 = 0x2d;
}
impl MarshalSpec {
pub fn decode(blob: &[u8]) -> Result<Self> {
let mut r = Reader::new(blob);
let tag = r.read_u8()?;
match tag {
native_type::ARRAY => {
let elem_type = if r.has_more() {
Some(r.read_u8()?)
} else {
None
};
let param_num = if r.has_more() {
Some(r.read_compressed_uint()? as u32)
} else {
None
};
let num_elem = if r.has_more() {
Some(r.read_compressed_uint()? as u32)
} else {
None
};
let param_num_multiplier = if r.has_more() {
Some(r.read_compressed_uint()? as u32)
} else {
None
};
Ok(Self::Array {
elem_type,
param_num,
num_elem,
param_num_multiplier,
})
}
native_type::FIXEDSYSSTRING => Ok(Self::FixedSysString {
size: r.read_compressed_uint()? as u32,
}),
native_type::FIXEDARRAY => {
let size = r.read_compressed_uint()? as u32;
let elem_type = if r.has_more() {
Some(r.read_u8()?)
} else {
None
};
Ok(Self::FixedArray { size, elem_type })
}
native_type::CUSTOMMARSHALER => {
Ok(Self::CustomMarshaler {
guid: r.read_ser_string_required()?,
unmanaged_type: r.read_ser_string_required()?,
managed_type: r.read_ser_string_required()?,
cookie: r.read_ser_string_required()?,
})
}
other => Ok(Self::Simple(other)),
}
}
}
struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn remaining(&self) -> &'a [u8] {
&self.data[self.pos..]
}
fn has_more(&self) -> bool {
self.pos < self.data.len()
}
fn skip(&mut self, n: usize) -> Result<()> {
let end = self
.pos
.checked_add(n)
.ok_or(Error::NotEnoughData(self.data.len(), n))?;
if end > self.data.len() {
return Err(Error::NotEnoughData(self.data.len(), end));
}
self.pos = end;
Ok(())
}
fn read_u8(&mut self) -> Result<u8> {
let b = *self
.data
.get(self.pos)
.ok_or(Error::NotEnoughData(self.data.len(), self.pos + 1))?;
self.pos += 1;
Ok(b)
}
fn read_u16_le(&mut self) -> Result<u16> {
let end = self.pos + 2;
if end > self.data.len() {
return Err(Error::NotEnoughData(self.data.len(), end));
}
let v = u16::from_le_bytes([self.data[self.pos], self.data[self.pos + 1]]);
self.pos = end;
Ok(v)
}
fn read_u32_le(&mut self) -> Result<u32> {
let end = self.pos + 4;
if end > self.data.len() {
return Err(Error::NotEnoughData(self.data.len(), end));
}
let v = u32::from_le_bytes([
self.data[self.pos],
self.data[self.pos + 1],
self.data[self.pos + 2],
self.data[self.pos + 3],
]);
self.pos = end;
Ok(v)
}
fn read_i8(&mut self) -> Result<i8> {
Ok(self.read_u8()? as i8)
}
fn read_i16_le(&mut self) -> Result<i16> {
Ok(self.read_u16_le()? as i16)
}
fn read_i32_le(&mut self) -> Result<i32> {
Ok(self.read_u32_le()? as i32)
}
fn read_u64_le(&mut self) -> Result<u64> {
let end = self.pos + 8;
if end > self.data.len() {
return Err(Error::NotEnoughData(self.data.len(), end));
}
let mut buf = [0u8; 8];
buf.copy_from_slice(&self.data[self.pos..end]);
self.pos = end;
Ok(u64::from_le_bytes(buf))
}
fn read_i64_le(&mut self) -> Result<i64> {
Ok(self.read_u64_le()? as i64)
}
fn read_f32_le(&mut self) -> Result<f32> {
let bits = self.read_u32_le()?;
Ok(f32::from_bits(bits))
}
fn read_f64_le(&mut self) -> Result<f64> {
let bits = self.read_u64_le()?;
Ok(f64::from_bits(bits))
}
fn read_compressed_uint(&mut self) -> Result<usize> {
let (v, len) = crate::utils::read_compressed_usize(self.remaining())?;
self.pos += len;
Ok(v)
}
fn read_ser_string(&mut self) -> Result<Option<String>> {
let first = *self
.data
.get(self.pos)
.ok_or(Error::NotEnoughData(self.data.len(), self.pos + 1))?;
if first == 0xff {
self.pos += 1;
return Ok(None);
}
let len = self.read_compressed_uint()?;
let end = self
.pos
.checked_add(len)
.ok_or(Error::NotEnoughData(self.data.len(), self.pos + len))?;
if end > self.data.len() {
return Err(Error::NotEnoughData(self.data.len(), end));
}
let s = std::str::from_utf8(&self.data[self.pos..end])
.map_err(|e| Error::FormatError(format!("SerString utf8: {e}")))?
.to_string();
self.pos = end;
Ok(Some(s))
}
fn read_ser_string_required(&mut self) -> Result<String> {
Ok(self.read_ser_string()?.unwrap_or_default())
}
fn read_value(&mut self, ty: u8) -> Result<Value> {
match ElementType::from_u8(ty) {
Some(ElementType::Boolean) => Ok(Value::Boolean(self.read_u8()? != 0)),
Some(ElementType::Char) => Ok(Value::Char(self.read_u16_le()?)),
Some(ElementType::I1) => Ok(Value::I1(self.read_i8()?)),
Some(ElementType::U1) => Ok(Value::U1(self.read_u8()?)),
Some(ElementType::I2) => Ok(Value::I2(self.read_i16_le()?)),
Some(ElementType::U2) => Ok(Value::U2(self.read_u16_le()?)),
Some(ElementType::I4) => Ok(Value::I4(self.read_i32_le()?)),
Some(ElementType::U4) => Ok(Value::U4(self.read_u32_le()?)),
Some(ElementType::I8) => Ok(Value::I8(self.read_i64_le()?)),
Some(ElementType::U8) => Ok(Value::U8(self.read_u64_le()?)),
Some(ElementType::R4) => Ok(Value::R4(self.read_f32_le()?)),
Some(ElementType::R8) => Ok(Value::R8(self.read_f64_le()?)),
Some(ElementType::String) => Ok(Value::String(self.read_ser_string()?)),
Some(ElementType::Type) => Ok(Value::Type(self.read_ser_string()?)),
Some(ElementType::SzArray) => {
let len = self.read_u32_le()?;
if len == 0xffff_ffff {
return Ok(Value::Array(None));
}
Ok(Value::Raw(
ElementType::SzArray as u8,
self.remaining().to_vec(),
))
}
_ => Ok(Value::Raw(ty, self.remaining().to_vec())),
}
}
fn read_named_arg(&mut self) -> Result<NamedArg> {
let kind_byte = self.read_u8()?;
let kind = ElementType::from_u8(kind_byte).ok_or_else(|| {
Error::FormatError(format!(
"CustomAttribute named: bad field-or-prop byte 0x{kind_byte:02x}"
))
})?;
if !matches!(kind, ElementType::Field | ElementType::Property) {
return Err(Error::FormatError(format!(
"CustomAttribute named: expected Field/Property, got {kind:?}"
)));
}
let elem_type = self.read_u8()?;
let inner_elem_type = match ElementType::from_u8(elem_type) {
Some(ElementType::SzArray) => Some(self.read_u8()?),
Some(ElementType::Enum) => {
let _enum_type_name = self.read_ser_string()?;
None
}
_ => None,
};
let name = self.read_ser_string()?.unwrap_or_default();
let value_ty = match inner_elem_type {
Some(inner) if elem_type == ElementType::SzArray as u8 => {
let len = self.read_u32_le()?;
if len == 0xffff_ffff {
Value::Array(None)
} else {
let mut items = Vec::with_capacity(len as usize);
for _ in 0..len {
items.push(self.read_value(inner)?);
}
Value::Array(Some(items))
}
}
_ if elem_type == ElementType::Enum as u8 => {
self.read_value(ElementType::I4 as u8)?
}
_ => self.read_value(elem_type)?,
};
Ok(NamedArg {
kind,
elem_type,
inner_elem_type,
name,
value: value_ty,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn custom_attribute_empty() {
let blob = [0x01, 0x00, 0x00, 0x00];
let ca = CustomAttribute::decode_with_types(&blob, &[]).unwrap();
assert!(ca.fixed.is_empty());
assert!(ca.named.is_empty());
}
#[test]
fn custom_attribute_fixed_i4() {
let blob = [
0x01, 0x00, 0x78, 0x56, 0x34, 0x12, 0x00, 0x00, ];
let ca = CustomAttribute::decode_with_types(&blob, &[ElementType::I4 as u8]).unwrap();
assert_eq!(ca.fixed, vec![Value::I4(0x1234_5678)]);
}
#[test]
fn custom_attribute_fixed_string() {
let blob = [
0x01, 0x00, 0x02, b'h', b'i', 0x00, 0x00, ];
let ca = CustomAttribute::decode_with_types(&blob, &[ElementType::String as u8]).unwrap();
assert_eq!(ca.fixed, vec![Value::String(Some("hi".to_string()))]);
}
#[test]
fn custom_attribute_named_bool() {
let blob = [
0x01,
0x00, 0x01,
0x00, ElementType::Property as u8,
ElementType::Boolean as u8,
0x03,
b'F',
b'o',
b'o', 0x01, ];
let ca = CustomAttribute::decode_with_types(&blob, &[]).unwrap();
assert_eq!(ca.named.len(), 1);
assert_eq!(ca.named[0].name, "Foo");
assert_eq!(ca.named[0].value, Value::Boolean(true));
}
#[test]
fn marshal_spec_simple_lpwstr() {
let ms = MarshalSpec::decode(&[native_type::LPWSTR]).unwrap();
assert_eq!(ms, MarshalSpec::Simple(native_type::LPWSTR));
}
#[test]
fn marshal_spec_array_with_param_num() {
let blob = [native_type::ARRAY, native_type::I4, 0x02];
let ms = MarshalSpec::decode(&blob).unwrap();
assert!(matches!(
ms,
MarshalSpec::Array {
elem_type: Some(native_type::I4),
param_num: Some(2),
num_elem: None,
param_num_multiplier: None,
}
));
}
#[test]
fn marshal_spec_fixed_sys_string() {
let blob = [native_type::FIXEDSYSSTRING, 0x40];
let ms = MarshalSpec::decode(&blob).unwrap();
assert!(matches!(ms, MarshalSpec::FixedSysString { size: 64 }));
}
}