use crate::{
metadata::customattributes::{
CustomAttributeArgument, CustomAttributeNamedArgument, CustomAttributeValue,
NAMED_ARG_TYPE, SERIALIZATION_TYPE,
},
utils::write_compressed_uint,
Result,
};
pub fn encode_custom_attribute_value(value: &CustomAttributeValue) -> Result<Vec<u8>> {
let mut buffer = Vec::new();
buffer.extend_from_slice(&[0x01, 0x00]);
encode_fixed_arguments(&value.fixed_args, &mut buffer)?;
let named_count = u16::try_from(value.named_args.len()).map_err(|_| {
malformed_error!(
"Too many named arguments: {} exceeds u16 maximum (65535)",
value.named_args.len()
)
})?;
buffer.extend_from_slice(&named_count.to_le_bytes());
encode_named_arguments(&value.named_args, &mut buffer)?;
Ok(buffer)
}
fn encode_fixed_arguments(args: &[CustomAttributeArgument], buffer: &mut Vec<u8>) -> Result<()> {
for arg in args {
encode_custom_attribute_argument(arg, buffer)?;
}
Ok(())
}
fn encode_named_arguments(
args: &[CustomAttributeNamedArgument],
buffer: &mut Vec<u8>,
) -> Result<()> {
for arg in args {
match &arg.value {
CustomAttributeArgument::Array(_) => {
return Err(malformed_error!(
"Array arguments are not supported in named arguments"
));
}
CustomAttributeArgument::Enum(_, _) => {
return Err(malformed_error!(
"Enum arguments are not supported in named arguments"
));
}
_ => {} }
if arg.is_field {
buffer.push(NAMED_ARG_TYPE::FIELD);
} else {
buffer.push(NAMED_ARG_TYPE::PROPERTY);
}
let type_tag = get_serialization_type_tag(&arg.value)?;
buffer.push(type_tag);
write_string(buffer, &arg.name)?;
encode_custom_attribute_argument(&arg.value, buffer)?;
}
Ok(())
}
pub fn encode_custom_attribute_argument(
arg: &CustomAttributeArgument,
buffer: &mut Vec<u8>,
) -> Result<()> {
match arg {
CustomAttributeArgument::Void => {
}
CustomAttributeArgument::Bool(value) => {
buffer.push(u8::from(*value));
}
CustomAttributeArgument::Char(value) => {
let code_point = *value as u32;
if code_point > 0xFFFF {
return Err(malformed_error!(
"Character U+{:04X} is outside the Basic Multilingual Plane and cannot be \
encoded as a .NET char (which is limited to U+0000..U+FFFF)",
code_point
));
}
#[allow(clippy::cast_possible_truncation)] let utf16_val = *value as u16;
buffer.extend_from_slice(&utf16_val.to_le_bytes());
}
CustomAttributeArgument::I1(value) => {
#[allow(clippy::cast_sign_loss)]
buffer.push(*value as u8);
}
CustomAttributeArgument::U1(value) => {
buffer.push(*value);
}
CustomAttributeArgument::I2(value) => {
buffer.extend_from_slice(&value.to_le_bytes());
}
CustomAttributeArgument::U2(value) => {
buffer.extend_from_slice(&value.to_le_bytes());
}
CustomAttributeArgument::I4(value) => {
buffer.extend_from_slice(&value.to_le_bytes());
}
CustomAttributeArgument::U4(value) => {
buffer.extend_from_slice(&value.to_le_bytes());
}
CustomAttributeArgument::I8(value) => {
buffer.extend_from_slice(&value.to_le_bytes());
}
CustomAttributeArgument::U8(value) => {
buffer.extend_from_slice(&value.to_le_bytes());
}
CustomAttributeArgument::R4(value) => {
buffer.extend_from_slice(&value.to_le_bytes());
}
CustomAttributeArgument::R8(value) => {
buffer.extend_from_slice(&value.to_le_bytes());
}
CustomAttributeArgument::I(value) => {
#[cfg(target_pointer_width = "32")]
{
buffer.extend_from_slice(
&i32::try_from(*value).unwrap_or(*value as i32).to_le_bytes(),
);
}
#[cfg(not(target_pointer_width = "32"))]
{
buffer.extend_from_slice(&(*value as i64).to_le_bytes());
}
}
CustomAttributeArgument::U(value) => {
#[cfg(target_pointer_width = "32")]
{
buffer.extend_from_slice(
&u32::try_from(*value).unwrap_or(*value as u32).to_le_bytes(),
);
}
#[cfg(not(target_pointer_width = "32"))]
{
buffer.extend_from_slice(&(*value as u64).to_le_bytes());
}
}
CustomAttributeArgument::String(value) | CustomAttributeArgument::Type(value) => {
write_string(buffer, value)?;
}
CustomAttributeArgument::Array(elements) => {
let len = u32::try_from(elements.len()).map_err(|_| {
malformed_error!("Array too large: {} exceeds u32 maximum", elements.len())
})?;
write_compressed_uint(len, buffer);
for element in elements {
encode_custom_attribute_argument(element, buffer)?;
}
}
CustomAttributeArgument::Enum(_, underlying_value) => {
encode_custom_attribute_argument(underlying_value, buffer)?;
}
}
Ok(())
}
fn get_serialization_type_tag(arg: &CustomAttributeArgument) -> Result<u8> {
let tag = match arg {
CustomAttributeArgument::Void => {
return Err(malformed_error!(
"Void arguments are not supported in custom attributes"
));
}
CustomAttributeArgument::Bool(_) => SERIALIZATION_TYPE::BOOLEAN,
CustomAttributeArgument::Char(_) => SERIALIZATION_TYPE::CHAR,
CustomAttributeArgument::I1(_) => SERIALIZATION_TYPE::I1,
CustomAttributeArgument::U1(_) => SERIALIZATION_TYPE::U1,
CustomAttributeArgument::I2(_) => SERIALIZATION_TYPE::I2,
CustomAttributeArgument::U2(_) => SERIALIZATION_TYPE::U2,
CustomAttributeArgument::I4(_) => SERIALIZATION_TYPE::I4,
CustomAttributeArgument::U4(_) => SERIALIZATION_TYPE::U4,
CustomAttributeArgument::I8(_) => SERIALIZATION_TYPE::I8,
CustomAttributeArgument::U8(_) => SERIALIZATION_TYPE::U8,
CustomAttributeArgument::R4(_) => SERIALIZATION_TYPE::R4,
CustomAttributeArgument::R8(_) => SERIALIZATION_TYPE::R8,
CustomAttributeArgument::I(_) => {
if cfg!(target_pointer_width = "32") {
SERIALIZATION_TYPE::I4
} else {
SERIALIZATION_TYPE::I8
}
}
CustomAttributeArgument::U(_) => {
if cfg!(target_pointer_width = "32") {
SERIALIZATION_TYPE::U4
} else {
SERIALIZATION_TYPE::U8
}
}
CustomAttributeArgument::String(_) => SERIALIZATION_TYPE::STRING,
CustomAttributeArgument::Type(_) => SERIALIZATION_TYPE::TYPE,
CustomAttributeArgument::Array(_) => SERIALIZATION_TYPE::SZARRAY,
CustomAttributeArgument::Enum(_, _) => SERIALIZATION_TYPE::ENUM,
};
Ok(tag)
}
fn write_string(buffer: &mut Vec<u8>, value: &str) -> Result<()> {
if value.is_empty() {
write_compressed_uint(0, buffer);
} else {
let utf8_bytes = value.as_bytes();
let len = u32::try_from(utf8_bytes.len()).map_err(|_| {
malformed_error!(
"String too long: {} bytes exceeds u32 maximum",
utf8_bytes.len()
)
})?;
write_compressed_uint(len, buffer);
buffer.extend_from_slice(utf8_bytes);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::customattributes::{CustomAttributeNamedArgument, CustomAttributeValue};
#[test]
fn test_encode_simple_custom_attribute() {
let custom_attr = CustomAttributeValue {
fixed_args: vec![CustomAttributeArgument::String("Test".to_string())],
named_args: vec![],
};
let result = encode_custom_attribute_value(&custom_attr);
assert!(
result.is_ok(),
"Simple custom attribute encoding should succeed"
);
let encoded = result.unwrap();
assert!(!encoded.is_empty(), "Encoded data should not be empty");
assert_eq!(encoded[0], 0x01, "First byte should be 0x01");
assert_eq!(encoded[1], 0x00, "Second byte should be 0x00");
let last_byte = encoded[encoded.len() - 1];
assert_eq!(last_byte, 0x00, "Named argument count should be 0");
}
#[test]
fn test_encode_boolean_argument() {
let mut buffer = Vec::new();
let arg = CustomAttributeArgument::Bool(true);
let result = encode_custom_attribute_argument(&arg, &mut buffer);
assert!(result.is_ok(), "Boolean encoding should succeed");
assert_eq!(buffer, vec![1], "True should encode as 1");
buffer.clear();
let arg = CustomAttributeArgument::Bool(false);
let result = encode_custom_attribute_argument(&arg, &mut buffer);
assert!(result.is_ok(), "Boolean encoding should succeed");
assert_eq!(buffer, vec![0], "False should encode as 0");
}
#[test]
fn test_encode_integer_arguments() {
let mut buffer = Vec::new();
let arg = CustomAttributeArgument::I4(0x12345678);
let result = encode_custom_attribute_argument(&arg, &mut buffer);
assert!(result.is_ok(), "I4 encoding should succeed");
assert_eq!(
buffer,
vec![0x78, 0x56, 0x34, 0x12],
"I4 should be little-endian"
);
buffer.clear();
let arg = CustomAttributeArgument::U2(0x1234);
let result = encode_custom_attribute_argument(&arg, &mut buffer);
assert!(result.is_ok(), "U2 encoding should succeed");
assert_eq!(buffer, vec![0x34, 0x12], "U2 should be little-endian");
}
#[test]
fn test_encode_string_argument() {
let mut buffer = Vec::new();
let arg = CustomAttributeArgument::String("Hello".to_string());
let result = encode_custom_attribute_argument(&arg, &mut buffer);
assert!(result.is_ok(), "String encoding should succeed");
let expected = vec![5, b'H', b'e', b'l', b'l', b'o'];
assert_eq!(buffer, expected, "String should encode with length prefix");
}
#[test]
fn test_encode_array_argument() {
let mut buffer = Vec::new();
let arg = CustomAttributeArgument::Array(vec![
CustomAttributeArgument::I4(1),
CustomAttributeArgument::I4(2),
]);
let result = encode_custom_attribute_argument(&arg, &mut buffer);
assert!(result.is_ok(), "Array encoding should succeed");
let expected = vec![
2, 1, 0, 0, 0, 2, 0, 0, 0, ];
assert_eq!(
buffer, expected,
"Array should encode with length and elements"
);
}
#[test]
fn test_encode_named_argument() {
let named_args = vec![CustomAttributeNamedArgument {
is_field: false, name: "Value".to_string(),
arg_type: "String".to_string(),
value: CustomAttributeArgument::String("Test".to_string()),
}];
let mut buffer = Vec::new();
let result = encode_named_arguments(&named_args, &mut buffer);
assert!(result.is_ok(), "Named argument encoding should succeed");
assert!(!buffer.is_empty(), "Named argument should produce data");
assert_eq!(buffer[0], 0x54, "Should start with PROPERTY marker");
assert_eq!(
buffer[1],
SERIALIZATION_TYPE::STRING,
"Should have STRING type tag"
);
}
#[test]
fn test_encode_compressed_uint() {
let mut buffer = Vec::new();
write_compressed_uint(42, &mut buffer);
assert_eq!(buffer, vec![42], "Small values should use single byte");
buffer.clear();
write_compressed_uint(0x1234, &mut buffer);
assert_eq!(
buffer,
vec![0x80 | 0x12, 0x34],
"Medium values should use two bytes"
);
buffer.clear();
write_compressed_uint(0x12345678, &mut buffer);
assert_eq!(
buffer,
vec![0xC0 | 0x12, 0x34, 0x56, 0x78],
"Large values should use four bytes"
);
}
#[test]
fn test_get_serialization_type_tag() {
assert_eq!(
get_serialization_type_tag(&CustomAttributeArgument::Bool(true)).unwrap(),
SERIALIZATION_TYPE::BOOLEAN
);
assert_eq!(
get_serialization_type_tag(&CustomAttributeArgument::String("test".to_string()))
.unwrap(),
SERIALIZATION_TYPE::STRING
);
assert_eq!(
get_serialization_type_tag(&CustomAttributeArgument::I4(42)).unwrap(),
SERIALIZATION_TYPE::I4
);
}
#[test]
fn test_encode_complete_custom_attribute_with_named_args() {
let custom_attr = CustomAttributeValue {
fixed_args: vec![CustomAttributeArgument::String("Debug".to_string())],
named_args: vec![CustomAttributeNamedArgument {
is_field: false,
name: "Name".to_string(),
arg_type: "String".to_string(),
value: CustomAttributeArgument::String("TestName".to_string()),
}],
};
let result = encode_custom_attribute_value(&custom_attr);
assert!(
result.is_ok(),
"Complete custom attribute encoding should succeed"
);
let encoded = result.unwrap();
assert!(
encoded.len() > 10,
"Complete attribute should be substantial"
);
assert_eq!(encoded[0], 0x01, "Should start with prolog");
assert_eq!(encoded[1], 0x00, "Should start with prolog");
}
#[test]
fn test_debug_named_args_encoding() {
let custom_attr = CustomAttributeValue {
fixed_args: vec![],
named_args: vec![CustomAttributeNamedArgument {
is_field: true,
name: "FieldValue".to_string(),
arg_type: "I4".to_string(),
value: CustomAttributeArgument::I4(42),
}],
};
let encoded = encode_custom_attribute_value(&custom_attr).unwrap();
if encoded.len() >= 6 {
assert_eq!(encoded[0], 0x01);
assert_eq!(encoded[1], 0x00);
assert_eq!(encoded[2], 0x01);
assert_eq!(encoded[3], 0x00);
assert_eq!(encoded[4], 0x53);
assert_eq!(encoded[5], 0x08);
}
}
#[test]
fn test_debug_type_args_encoding() {
let custom_attr = CustomAttributeValue {
fixed_args: vec![CustomAttributeArgument::Type("System.String".to_string())],
named_args: vec![],
};
let encoded = encode_custom_attribute_value(&custom_attr).unwrap();
let mut pos = 0;
assert_eq!(encoded[pos], 0x01);
assert_eq!(encoded[pos + 1], 0x00);
pos += 2;
if pos < encoded.len() {
let str_len = encoded[pos];
pos += 1;
if pos + str_len as usize <= encoded.len() {
let string_bytes = &encoded[pos..pos + str_len as usize];
let string_str = String::from_utf8_lossy(string_bytes);
assert_eq!(string_str, "System.String");
pos += str_len as usize;
}
}
if pos + 1 < encoded.len() {
assert_eq!(encoded[pos], 0x00);
assert_eq!(encoded[pos + 1], 0x00);
}
}
}