use alloc::vec::Vec;
use uuid::Uuid;
use super::coded_index::{CodedIndex, MetadataTable};
use super::heaps::{BlobHeap, GuidHeap, StringsHeap};
use super::tables::{Tables, TypeRef};
use crate::error::{Result, malformed};
const GUID_STRING_LENGTH: usize = 36;
const CUSTOM_ATTRIBUTE_PROLOG: [u8; 2] = [0x01, 0x00];
pub(crate) fn module_version_ids(tables: &Tables, guids: Option<&GuidHeap<'_>>) -> Vec<Uuid> {
tables
.module
.iter()
.filter_map(|m| guids?.get(m.mvid))
.filter(|g| !g.is_nil())
.collect()
}
pub(crate) fn com_type_lib_id(
tables: &Tables,
strings: &StringsHeap<'_>,
blob: &BlobHeap<'_>,
) -> Result<Option<Uuid>> {
let Some(type_ref) = guid_attribute_row(&tables.type_ref, strings)? else {
return Ok(None);
};
let Some(member_ref) = member_ref_row(tables, type_ref) else {
return Ok(None);
};
for attribute in &tables.custom_attribute {
if CodedIndex::CustomAttributeType.decode(attribute.r#type)
!= Some((MetadataTable::MemberRef, member_ref))
{
continue;
}
if let Some(guid) = guid_in_blob(blob.get(attribute.value)?)? {
return Ok(Some(guid));
}
}
Ok(None)
}
fn guid_attribute_row(type_refs: &[TypeRef], strings: &StringsHeap<'_>) -> Result<Option<u32>> {
for (row, type_ref) in (1..).zip(type_refs) {
if strings.get(type_ref.type_name)? == "GuidAttribute"
&& strings.get(type_ref.type_namespace)? == "System.Runtime.InteropServices"
{
return Ok(Some(row));
}
}
Ok(None)
}
fn member_ref_row(tables: &Tables, type_ref: u32) -> Option<u32> {
(1..).zip(&tables.member_ref).find_map(|(row, m)| {
(CodedIndex::MemberRefParent.decode(m.class) == Some((MetadataTable::TypeRef, type_ref)))
.then_some(row)
})
}
fn guid_in_blob(value: &[u8]) -> Result<Option<Uuid>> {
let Some((prolog, rest)) = value.split_first_chunk::<2>() else {
return Ok(None);
};
let Some((&length, rest)) = rest.split_first() else {
return Ok(None);
};
if *prolog != CUSTOM_ATTRIBUTE_PROLOG || length as usize != GUID_STRING_LENGTH {
return Ok(None);
}
let text = rest
.get(..GUID_STRING_LENGTH)
.ok_or(malformed("GuidAttribute", "GUID string exceeds the blob"))?;
Uuid::try_parse_ascii(text)
.map(Some)
.map_err(|_| malformed("GuidAttribute", "invalid GUID string"))
}