use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{ModuleRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
pub struct ModuleBuilder {
generation: Option<u32>,
name: Option<String>,
mvid: Option<[u8; 16]>,
encid: Option<[u8; 16]>,
encbaseid: Option<[u8; 16]>,
}
impl Default for ModuleBuilder {
fn default() -> Self {
Self::new()
}
}
impl ModuleBuilder {
#[must_use]
pub fn new() -> Self {
Self {
generation: None,
name: None,
mvid: None,
encid: None,
encbaseid: None,
}
}
#[must_use]
pub fn generation(mut self, generation: u32) -> Self {
self.generation = Some(generation);
self
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn mvid(mut self, mvid: &[u8; 16]) -> Self {
self.mvid = Some(*mvid);
self
}
#[must_use]
pub fn encid(mut self, encid: &[u8; 16]) -> Self {
self.encid = Some(*encid);
self
}
#[must_use]
pub fn encbaseid(mut self, encbaseid: &[u8; 16]) -> Self {
self.encbaseid = Some(*encbaseid);
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let name = self
.name
.ok_or_else(|| Error::ModificationInvalid("name field is required".to_string()))?;
let existing_count = assembly.next_rid(TableId::Module)? - 1;
if existing_count > 0 {
return Err(Error::ModificationInvalid(
"Module table already contains an entry. Only one module per assembly is allowed."
.to_string(),
));
}
let name_index = assembly.string_add(&name)?.placeholder();
let mvid_index = if let Some(mvid) = self.mvid {
assembly.guid_add(&mvid)?.placeholder()
} else {
let new_mvid = generate_random_guid();
assembly.guid_add(&new_mvid)?.placeholder()
};
let encid_index = if let Some(encid) = self.encid {
assembly.guid_add(&encid)?.placeholder()
} else {
0 };
let encbaseid_index = if let Some(encbaseid) = self.encbaseid {
assembly.guid_add(&encbaseid)?.placeholder()
} else {
0 };
let module_raw = ModuleRaw {
rid: 0,
token: Token::new(0),
offset: 0,
generation: self.generation.unwrap_or(0), name: name_index,
mvid: mvid_index,
encid: encid_index,
encbaseid: encbaseid_index,
};
assembly.table_row_add(TableId::Module, TableDataOwned::Module(module_raw))
}
}
fn generate_random_guid() -> [u8; 16] {
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
static COUNTER: AtomicU64 = AtomicU64::new(1);
let timestamp = u64::try_from(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos(),
)
.unwrap_or_else(|_| {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
});
let counter = COUNTER.fetch_add(1, Ordering::SeqCst);
let combined = timestamp.wrapping_add(counter);
let mut guid = [0u8; 16];
guid[0..8].copy_from_slice(&combined.to_le_bytes());
guid[8..16].copy_from_slice(&(!combined).to_le_bytes());
guid[6] = (guid[6] & 0x0F) | 0x40; guid[8] = (guid[8] & 0x3F) | 0x80;
guid
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::factories::table::assemblyref::get_test_assembly;
#[test]
fn test_module_builder_basic() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = ModuleBuilder::new()
.name("TestModule.dll")
.build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Module table already contains an entry"));
Ok(())
}
#[test]
fn test_module_builder_with_mvid() -> Result<()> {
let mut assembly = get_test_assembly()?;
let mvid = [
0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
0x77, 0x88,
];
let result = ModuleBuilder::new()
.name("TestModule.dll")
.mvid(&mvid)
.build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Module table already contains an entry"));
Ok(())
}
#[test]
fn test_module_builder_with_enc_support() -> Result<()> {
let mut assembly = get_test_assembly()?;
let encid = [
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99,
];
let encbaseid = [
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
0xFF, 0x00,
];
let result = ModuleBuilder::new()
.name("DebugModule.dll")
.encid(&encid)
.encbaseid(&encbaseid)
.build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Module table already contains an entry"));
Ok(())
}
#[test]
fn test_module_builder_missing_name() {
let mut assembly = get_test_assembly().unwrap();
let result = ModuleBuilder::new().build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("name field is required"));
}
#[test]
fn test_module_builder_generation() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = ModuleBuilder::new()
.name("TestModule.dll")
.generation(0) .build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Module table already contains an entry"));
Ok(())
}
#[test]
fn test_module_builder_default() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = ModuleBuilder::default()
.name("DefaultModule.dll")
.build(&mut assembly);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Module table already contains an entry"));
Ok(())
}
#[test]
fn test_guid_generation() {
let guid1 = generate_random_guid();
let guid2 = generate_random_guid();
assert_ne!(guid1, guid2);
assert_eq!(guid1[6] & 0xF0, 0x40); assert_eq!(guid1[8] & 0xC0, 0x80); assert_eq!(guid2[6] & 0xF0, 0x40); assert_eq!(guid2[8] & 0xC0, 0x80); }
}