use crate::{
metadata::marshalling::types::{
MarshallingInfo, NativeType, MAX_RECURSION_DEPTH, NATIVE_TYPE, VARIANT_TYPE,
},
utils::write_compressed_uint,
Error::RecursionLimit,
Result,
};
pub fn encode_marshalling_descriptor(info: &MarshallingInfo) -> Result<Vec<u8>> {
let mut encoder = MarshallingEncoder::new();
encoder.encode_descriptor(info)
}
pub struct MarshallingEncoder {
buffer: Vec<u8>,
depth: usize,
}
impl MarshallingEncoder {
#[must_use]
pub fn new() -> Self {
MarshallingEncoder {
buffer: Vec::new(),
depth: 0,
}
}
fn write_optional_compressed_uint(&mut self, value: Option<u32>) {
if let Some(v) = value {
write_compressed_uint(v, &mut self.buffer);
}
}
pub fn encode_native_type(&mut self, native_type: &NativeType) -> Result<()> {
self.depth += 1;
if self.depth >= MAX_RECURSION_DEPTH {
return Err(RecursionLimit(MAX_RECURSION_DEPTH));
}
match native_type {
NativeType::End => self.buffer.push(NATIVE_TYPE::END),
NativeType::Void => self.buffer.push(NATIVE_TYPE::VOID),
NativeType::Boolean => self.buffer.push(NATIVE_TYPE::BOOLEAN),
NativeType::I1 => self.buffer.push(NATIVE_TYPE::I1),
NativeType::U1 => self.buffer.push(NATIVE_TYPE::U1),
NativeType::I2 => self.buffer.push(NATIVE_TYPE::I2),
NativeType::U2 => self.buffer.push(NATIVE_TYPE::U2),
NativeType::I4 => self.buffer.push(NATIVE_TYPE::I4),
NativeType::U4 => self.buffer.push(NATIVE_TYPE::U4),
NativeType::I8 => self.buffer.push(NATIVE_TYPE::I8),
NativeType::U8 => self.buffer.push(NATIVE_TYPE::U8),
NativeType::R4 => self.buffer.push(NATIVE_TYPE::R4),
NativeType::R8 => self.buffer.push(NATIVE_TYPE::R8),
NativeType::SysChar => self.buffer.push(NATIVE_TYPE::SYSCHAR),
NativeType::Variant => self.buffer.push(NATIVE_TYPE::VARIANT),
NativeType::Currency => self.buffer.push(NATIVE_TYPE::CURRENCY),
NativeType::Decimal => self.buffer.push(NATIVE_TYPE::DECIMAL),
NativeType::Date => self.buffer.push(NATIVE_TYPE::DATE),
NativeType::Int => self.buffer.push(NATIVE_TYPE::INT),
NativeType::UInt => self.buffer.push(NATIVE_TYPE::UINT),
NativeType::Error => self.buffer.push(NATIVE_TYPE::ERROR),
NativeType::BStr => self.buffer.push(NATIVE_TYPE::BSTR),
NativeType::LPStr { size_param_index } => {
self.buffer.push(NATIVE_TYPE::LPSTR);
self.write_optional_compressed_uint(*size_param_index);
}
NativeType::LPWStr { size_param_index } => {
self.buffer.push(NATIVE_TYPE::LPWSTR);
self.write_optional_compressed_uint(*size_param_index);
}
NativeType::LPTStr { size_param_index } => {
self.buffer.push(NATIVE_TYPE::LPTSTR);
self.write_optional_compressed_uint(*size_param_index);
}
NativeType::LPUtf8Str { size_param_index } => {
self.buffer.push(NATIVE_TYPE::LPUTF8STR);
self.write_optional_compressed_uint(*size_param_index);
}
NativeType::FixedSysString { size } => {
self.buffer.push(NATIVE_TYPE::FIXEDSYSSTRING);
write_compressed_uint(*size, &mut self.buffer);
}
NativeType::ObjectRef => self.buffer.push(NATIVE_TYPE::OBJECTREF),
NativeType::IUnknown => self.buffer.push(NATIVE_TYPE::IUNKNOWN),
NativeType::IDispatch => self.buffer.push(NATIVE_TYPE::IDISPATCH),
NativeType::IInspectable => self.buffer.push(NATIVE_TYPE::IINSPECTABLE),
NativeType::Struct {
packing_size,
class_size,
} => {
self.buffer.push(NATIVE_TYPE::STRUCT);
if let Some(packing) = packing_size {
self.buffer.push(*packing);
}
self.write_optional_compressed_uint(*class_size);
}
NativeType::Interface { iid_param_index } => {
self.buffer.push(NATIVE_TYPE::INTERFACE);
self.write_optional_compressed_uint(*iid_param_index);
}
NativeType::SafeArray {
variant_type,
user_defined_name,
} => {
self.buffer.push(NATIVE_TYPE::SAFEARRAY);
if user_defined_name.is_some() || *variant_type != VARIANT_TYPE::EMPTY {
#[allow(clippy::cast_possible_truncation)]
{
self.buffer
.push((*variant_type & VARIANT_TYPE::TYPEMASK) as u8);
}
}
if let Some(user_defined_name) = user_defined_name {
self.buffer.extend_from_slice(user_defined_name.as_bytes());
self.buffer.push(0);
}
}
NativeType::FixedArray { size, element_type } => {
self.buffer.push(NATIVE_TYPE::FIXEDARRAY);
write_compressed_uint(*size, &mut self.buffer);
if let Some(elem_type) = element_type {
self.encode_native_type(elem_type)?;
}
}
NativeType::Array {
element_type,
num_param,
num_element,
} => {
self.buffer.push(NATIVE_TYPE::ARRAY);
self.encode_native_type(element_type)?;
self.write_optional_compressed_uint(*num_param);
self.write_optional_compressed_uint(*num_element);
}
NativeType::NestedStruct => self.buffer.push(NATIVE_TYPE::NESTEDSTRUCT),
NativeType::ByValStr { size } => {
self.buffer.push(NATIVE_TYPE::BYVALSTR);
write_compressed_uint(*size, &mut self.buffer);
}
NativeType::AnsiBStr => self.buffer.push(NATIVE_TYPE::ANSIBSTR),
NativeType::TBStr => self.buffer.push(NATIVE_TYPE::TBSTR),
NativeType::VariantBool => self.buffer.push(NATIVE_TYPE::VARIANTBOOL),
NativeType::Func => self.buffer.push(NATIVE_TYPE::FUNC),
NativeType::AsAny => self.buffer.push(NATIVE_TYPE::ASANY),
NativeType::LPStruct => self.buffer.push(NATIVE_TYPE::LPSTRUCT),
NativeType::CustomMarshaler {
guid,
native_type_name,
cookie,
type_reference,
} => {
self.buffer.push(NATIVE_TYPE::CUSTOMMARSHALER);
self.buffer.extend_from_slice(guid.as_bytes());
self.buffer.push(0);
self.buffer.extend_from_slice(native_type_name.as_bytes());
self.buffer.push(0);
self.buffer.extend_from_slice(cookie.as_bytes());
self.buffer.push(0);
self.buffer.extend_from_slice(type_reference.as_bytes());
self.buffer.push(0);
}
NativeType::HString => self.buffer.push(NATIVE_TYPE::HSTRING),
NativeType::Ptr { ref_type } => {
self.buffer.push(NATIVE_TYPE::PTR);
if let Some(ref_type) = ref_type {
self.encode_native_type(ref_type)?;
}
}
}
self.depth -= 1;
Ok(())
}
pub fn encode_descriptor(&mut self, info: &MarshallingInfo) -> Result<Vec<u8>> {
info.validate()?;
self.buffer.clear();
self.depth = 0;
self.encode_native_type(&info.primary_type)?;
for additional_type in &info.additional_types {
self.encode_native_type(additional_type)?;
}
if !info.additional_types.is_empty() {
self.buffer.push(NATIVE_TYPE::END);
}
Ok(self.buffer.clone())
}
pub fn encode_descriptor_into(
&mut self,
info: &MarshallingInfo,
output: &mut Vec<u8>,
) -> Result<()> {
info.validate()?;
self.buffer.clear();
self.depth = 0;
self.encode_native_type(&info.primary_type)?;
for additional_type in &info.additional_types {
self.encode_native_type(additional_type)?;
}
if !info.additional_types.is_empty() {
self.buffer.push(NATIVE_TYPE::END);
}
output.extend_from_slice(&self.buffer);
Ok(())
}
}
impl Default for MarshallingEncoder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::marshalling::parse_marshalling_descriptor;
#[test]
fn test_roundtrip_simple_types() {
let test_cases = vec![
NativeType::Void,
NativeType::Boolean,
NativeType::I1,
NativeType::U1,
NativeType::I2,
NativeType::U2,
NativeType::I4,
NativeType::U4,
NativeType::I8,
NativeType::U8,
NativeType::R4,
NativeType::R8,
NativeType::Int,
NativeType::UInt,
NativeType::VariantBool,
NativeType::IInspectable,
NativeType::HString,
NativeType::BStr,
NativeType::AnsiBStr,
NativeType::TBStr,
NativeType::IUnknown,
NativeType::IDispatch,
NativeType::NestedStruct,
NativeType::LPStruct,
NativeType::ObjectRef,
NativeType::Func,
NativeType::AsAny,
NativeType::SysChar,
NativeType::Variant,
NativeType::Currency,
NativeType::Decimal,
NativeType::Date,
NativeType::Error,
];
for original_type in test_cases {
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type);
assert_eq!(parsed.additional_types.len(), 0);
}
}
#[test]
fn test_roundtrip_string_types_with_parameters() {
let test_cases = vec![
NativeType::LPStr {
size_param_index: None,
},
NativeType::LPStr {
size_param_index: Some(5),
},
NativeType::LPWStr {
size_param_index: None,
},
NativeType::LPWStr {
size_param_index: Some(10),
},
NativeType::LPTStr {
size_param_index: None,
},
NativeType::LPTStr {
size_param_index: Some(3),
},
NativeType::LPUtf8Str {
size_param_index: None,
},
NativeType::LPUtf8Str {
size_param_index: Some(16),
},
];
for original_type in test_cases {
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type);
assert_eq!(parsed.additional_types.len(), 0);
}
}
#[test]
fn test_roundtrip_fixed_types_with_size() {
let test_cases = vec![
NativeType::FixedSysString { size: 32 },
NativeType::FixedSysString { size: 128 },
NativeType::ByValStr { size: 64 },
NativeType::ByValStr { size: 256 },
];
for original_type in test_cases {
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type);
assert_eq!(parsed.additional_types.len(), 0);
}
}
#[test]
fn test_roundtrip_struct_types() {
let test_cases = vec![
NativeType::Struct {
packing_size: None,
class_size: None,
},
NativeType::Struct {
packing_size: Some(4),
class_size: None,
},
NativeType::Struct {
packing_size: Some(8),
class_size: Some(128),
},
NativeType::Struct {
packing_size: Some(1),
class_size: Some(64),
},
];
for original_type in test_cases {
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type);
assert_eq!(parsed.additional_types.len(), 0);
}
}
#[test]
fn test_roundtrip_interface_types() {
let test_cases = vec![
NativeType::Interface {
iid_param_index: None,
},
NativeType::Interface {
iid_param_index: Some(1),
},
NativeType::Interface {
iid_param_index: Some(5),
},
];
for original_type in test_cases {
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type);
assert_eq!(parsed.additional_types.len(), 0);
}
}
#[test]
fn test_safe_array_encoding_debug() {
let simple_case = NativeType::SafeArray {
variant_type: VARIANT_TYPE::I4,
user_defined_name: None,
};
let info = MarshallingInfo {
primary_type: simple_case.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, simple_case);
let complex_case = NativeType::SafeArray {
variant_type: VARIANT_TYPE::EMPTY,
user_defined_name: Some("CustomStruct".to_string()),
};
let info = MarshallingInfo {
primary_type: complex_case.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, complex_case);
}
#[test]
fn test_roundtrip_safe_array_types() {
let test_cases = vec![
NativeType::SafeArray {
variant_type: VARIANT_TYPE::EMPTY,
user_defined_name: None,
},
NativeType::SafeArray {
variant_type: VARIANT_TYPE::I4,
user_defined_name: None,
},
NativeType::SafeArray {
variant_type: VARIANT_TYPE::BSTR,
user_defined_name: None,
},
NativeType::SafeArray {
variant_type: VARIANT_TYPE::I4,
user_defined_name: Some("MyCustomType".to_string()),
},
NativeType::SafeArray {
variant_type: VARIANT_TYPE::BSTR,
user_defined_name: Some("System.String".to_string()),
},
NativeType::SafeArray {
variant_type: VARIANT_TYPE::EMPTY,
user_defined_name: Some("CustomStruct".to_string()),
},
];
for (i, original_type) in test_cases.into_iter().enumerate() {
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type, "Test case {i} failed");
assert_eq!(parsed.additional_types.len(), 0);
}
}
#[test]
fn test_roundtrip_fixed_array_types() {
let test_cases = vec![
NativeType::FixedArray {
size: 10,
element_type: None,
},
NativeType::FixedArray {
size: 32,
element_type: Some(Box::new(NativeType::I4)),
},
NativeType::FixedArray {
size: 64,
element_type: Some(Box::new(NativeType::Boolean)),
},
];
for original_type in test_cases {
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type);
assert_eq!(parsed.additional_types.len(), 0);
}
}
#[test]
fn test_roundtrip_variable_array_types() {
let test_cases = vec![
NativeType::Array {
element_type: Box::new(NativeType::I4),
num_param: None,
num_element: None,
},
NativeType::Array {
element_type: Box::new(NativeType::I4),
num_param: Some(3),
num_element: None,
},
NativeType::Array {
element_type: Box::new(NativeType::I4),
num_param: Some(3),
num_element: Some(10),
},
NativeType::Array {
element_type: Box::new(NativeType::Boolean),
num_param: Some(5),
num_element: None,
},
];
for original_type in test_cases {
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type);
assert_eq!(parsed.additional_types.len(), 0);
}
}
#[test]
fn test_roundtrip_pointer_types() {
let test_cases = vec![
NativeType::Ptr { ref_type: None },
NativeType::Ptr {
ref_type: Some(Box::new(NativeType::I4)),
},
NativeType::Ptr {
ref_type: Some(Box::new(NativeType::Void)),
},
];
for original_type in test_cases {
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type);
assert_eq!(parsed.additional_types.len(), 0);
}
}
#[test]
fn test_roundtrip_custom_marshaler() {
let original_type = NativeType::CustomMarshaler {
guid: "ABCD1234-5678-90EF".to_string(),
native_type_name: "MyNativeType".to_string(),
cookie: "cookie_data".to_string(),
type_reference: "MyAssembly.MyMarshaler".to_string(),
};
let info = MarshallingInfo {
primary_type: original_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, original_type);
assert_eq!(parsed.additional_types.len(), 0);
}
#[test]
fn test_roundtrip_complex_nested_types() {
let complex_type = NativeType::Ptr {
ref_type: Some(Box::new(NativeType::Array {
element_type: Box::new(NativeType::LPWStr {
size_param_index: Some(5),
}),
num_param: Some(2),
num_element: Some(10),
})),
};
let info = MarshallingInfo {
primary_type: complex_type.clone(),
additional_types: vec![],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, complex_type);
assert_eq!(parsed.additional_types.len(), 0);
}
#[test]
fn test_roundtrip_descriptors_with_additional_types() {
let info = MarshallingInfo {
primary_type: NativeType::LPStr {
size_param_index: Some(1),
},
additional_types: vec![NativeType::Boolean, NativeType::I4],
};
let encoded = encode_marshalling_descriptor(&info).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, info.primary_type);
assert_eq!(parsed.additional_types.len(), 2);
assert_eq!(parsed.additional_types[0], NativeType::Boolean);
assert_eq!(parsed.additional_types[1], NativeType::I4);
}
#[test]
fn test_roundtrip_comprehensive_scenarios() {
let pinvoke_scenarios = vec![
MarshallingInfo {
primary_type: NativeType::I4, additional_types: vec![],
},
MarshallingInfo {
primary_type: NativeType::LPWStr {
size_param_index: None,
},
additional_types: vec![],
},
MarshallingInfo {
primary_type: NativeType::Ptr {
ref_type: Some(Box::new(NativeType::Struct {
packing_size: None,
class_size: None,
})),
},
additional_types: vec![],
},
];
for scenario in pinvoke_scenarios {
let encoded = encode_marshalling_descriptor(&scenario).unwrap();
let parsed = parse_marshalling_descriptor(&encoded).unwrap();
assert_eq!(parsed.primary_type, scenario.primary_type);
assert_eq!(
parsed.additional_types.len(),
scenario.additional_types.len()
);
for (i, expected) in scenario.additional_types.iter().enumerate() {
assert_eq!(parsed.additional_types[i], *expected);
}
}
}
#[test]
fn test_validation_struct_class_size_without_packing() {
let invalid = MarshallingInfo {
primary_type: NativeType::Struct {
packing_size: None,
class_size: Some(128),
},
additional_types: vec![],
};
let result = encode_marshalling_descriptor(&invalid);
assert!(result.is_err());
}
#[test]
fn test_validation_array_num_element_without_num_param() {
let invalid = MarshallingInfo {
primary_type: NativeType::Array {
element_type: Box::new(NativeType::I4),
num_param: None,
num_element: Some(10),
},
additional_types: vec![],
};
let result = encode_marshalling_descriptor(&invalid);
assert!(result.is_err());
}
#[test]
fn test_validation_nested_invalid() {
let invalid = MarshallingInfo {
primary_type: NativeType::Ptr {
ref_type: Some(Box::new(NativeType::Struct {
packing_size: None,
class_size: Some(64),
})),
},
additional_types: vec![],
};
let result = encode_marshalling_descriptor(&invalid);
assert!(result.is_err());
}
#[test]
fn test_encode_descriptor_into() {
let mut encoder = MarshallingEncoder::new();
let mut buffer = Vec::with_capacity(64);
let info = MarshallingInfo {
primary_type: NativeType::I4,
additional_types: vec![],
};
encoder.encode_descriptor_into(&info, &mut buffer).unwrap();
assert_eq!(buffer, vec![NATIVE_TYPE::I4]);
let info2 = MarshallingInfo {
primary_type: NativeType::Boolean,
additional_types: vec![],
};
encoder.encode_descriptor_into(&info2, &mut buffer).unwrap();
assert_eq!(buffer, vec![NATIVE_TYPE::I4, NATIVE_TYPE::BOOLEAN]);
}
}