use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
tables::{AssemblyFlags, AssemblyRefRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
#[derive(Debug, Clone, Default)]
pub struct AssemblyRefBuilder {
name: Option<String>,
major_version: u32,
minor_version: u32,
build_number: u32,
revision_number: u32,
flags: u32,
public_key_or_token: Option<Vec<u8>>,
culture: Option<String>,
hash_value: Option<Vec<u8>>,
}
impl AssemblyRefBuilder {
#[must_use]
pub fn new() -> Self {
Self {
name: None,
major_version: 0,
minor_version: 0,
build_number: 0,
revision_number: 0,
flags: 0,
public_key_or_token: None,
culture: None,
hash_value: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn version(mut self, major: u32, minor: u32, build: u32, revision: u32) -> Self {
self.major_version = major;
self.minor_version = minor;
self.build_number = build;
self.revision_number = revision;
self
}
#[must_use]
pub fn flags(mut self, flags: u32) -> Self {
self.flags = flags;
self
}
#[must_use]
pub fn public_key(mut self, public_key: &[u8]) -> Self {
self.public_key_or_token = Some(public_key.to_vec());
self.flags |= AssemblyFlags::PUBLIC_KEY;
self
}
#[must_use]
pub fn public_key_token(mut self, token: &[u8]) -> Self {
self.public_key_or_token = Some(token.to_vec());
self.flags &= !AssemblyFlags::PUBLIC_KEY; self
}
#[must_use]
pub fn culture(mut self, culture: impl Into<String>) -> Self {
self.culture = Some(culture.into());
self
}
#[must_use]
pub fn hash_value(mut self, hash: &[u8]) -> Self {
self.hash_value = Some(hash.to_vec());
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let name = self.name.ok_or_else(|| {
Error::ModificationInvalid("Assembly name is required for AssemblyRef".to_string())
})?;
if name.is_empty() {
return Err(Error::ModificationInvalid(
"Assembly name cannot be empty for AssemblyRef".to_string(),
));
}
if self.major_version > 65535 {
return Err(Error::ModificationInvalid(
"Major version number must fit in 16 bits (0-65535)".to_string(),
));
}
if self.minor_version > 65535 {
return Err(Error::ModificationInvalid(
"Minor version number must fit in 16 bits (0-65535)".to_string(),
));
}
if self.build_number > 65535 {
return Err(Error::ModificationInvalid(
"Build number must fit in 16 bits (0-65535)".to_string(),
));
}
if self.revision_number > 65535 {
return Err(Error::ModificationInvalid(
"Revision number must fit in 16 bits (0-65535)".to_string(),
));
}
let name_index = assembly.string_get_or_add(&name)?.placeholder();
let culture_index = if let Some(culture) = self.culture {
if culture.is_empty() {
0 } else {
assembly.string_get_or_add(&culture)?.placeholder()
}
} else {
0 };
let public_key_or_token_index = if let Some(data) = self.public_key_or_token {
if data.is_empty() {
0
} else {
if (self.flags & AssemblyFlags::PUBLIC_KEY) == 0 && data.len() != 8 {
return Err(Error::ModificationInvalid(
"Public key token must be exactly 8 bytes".to_string(),
));
}
assembly.blob_add(&data)?.placeholder()
}
} else {
0
};
let hash_value_index = if let Some(hash) = self.hash_value {
if hash.is_empty() {
0
} else {
assembly.blob_add(&hash)?.placeholder()
}
} else {
0
};
let assembly_ref = AssemblyRefRaw {
rid: 0,
token: Token::new(0),
offset: 0,
major_version: self.major_version,
minor_version: self.minor_version,
build_number: self.build_number,
revision_number: self.revision_number,
flags: self.flags,
public_key_or_token: public_key_or_token_index,
name: name_index,
culture: culture_index,
hash_value: hash_value_index,
};
assembly.table_row_add(
TableId::AssemblyRef,
TableDataOwned::AssemblyRef(assembly_ref),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::ChangeRefKind, metadata::tables::AssemblyFlags,
test::factories::table::assemblyref::get_test_assembly,
};
#[test]
fn test_assemblyref_builder_basic() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = AssemblyRefBuilder::new()
.name("System.Core")
.version(4, 0, 0, 0)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
Ok(())
}
#[test]
fn test_assemblyref_builder_default() -> Result<()> {
let builder = AssemblyRefBuilder::default();
assert!(builder.name.is_none());
assert_eq!(builder.major_version, 0);
assert_eq!(builder.minor_version, 0);
assert_eq!(builder.build_number, 0);
assert_eq!(builder.revision_number, 0);
assert_eq!(builder.flags, 0);
Ok(())
}
#[test]
fn test_assemblyref_builder_missing_name() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = AssemblyRefBuilder::new()
.version(1, 0, 0, 0)
.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Assembly name is required"));
Ok(())
}
#[test]
fn test_assemblyref_builder_empty_name() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = AssemblyRefBuilder::new()
.name("")
.version(1, 0, 0, 0)
.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Assembly name cannot be empty"));
Ok(())
}
#[test]
fn test_assemblyref_builder_with_culture() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = AssemblyRefBuilder::new()
.name("LocalizedAssembly")
.version(1, 0, 0, 0)
.culture("en-US")
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
Ok(())
}
#[test]
fn test_assemblyref_builder_with_public_key_token() -> Result<()> {
let mut assembly = get_test_assembly()?;
let token_data = [0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89];
let ref_ = AssemblyRefBuilder::new()
.name("StrongNamedAssembly")
.version(2, 1, 0, 0)
.public_key_token(&token_data)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
Ok(())
}
#[test]
fn test_assemblyref_builder_with_public_key() -> Result<()> {
let mut assembly = get_test_assembly()?;
let public_key = vec![0x00, 0x24, 0x00, 0x00, 0x04, 0x80];
let ref_ = AssemblyRefBuilder::new()
.name("FullKeyAssembly")
.version(1, 2, 3, 4)
.public_key(&public_key)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
Ok(())
}
#[test]
fn test_assemblyref_builder_invalid_public_key_token_length() -> Result<()> {
let mut assembly = get_test_assembly()?;
let invalid_token = [0xB7, 0x7A, 0x5C];
let result = AssemblyRefBuilder::new()
.name("InvalidTokenAssembly")
.version(1, 0, 0, 0)
.public_key_token(&invalid_token)
.build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Public key token must be exactly 8 bytes"));
Ok(())
}
#[test]
fn test_assemblyref_builder_version_overflow() -> Result<()> {
let mut assembly = get_test_assembly()?;
let result = AssemblyRefBuilder::new()
.name("OverflowAssembly")
.version(70000, 0, 0, 0) .build(&mut assembly);
assert!(result.is_err());
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("Major version number must fit in 16 bits"));
Ok(())
}
#[test]
fn test_assemblyref_builder_with_flags() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = AssemblyRefBuilder::new()
.name("RetargetableAssembly")
.version(1, 0, 0, 0)
.flags(AssemblyFlags::RETARGETABLE)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
Ok(())
}
#[test]
fn test_assemblyref_builder_with_hash_value() -> Result<()> {
let mut assembly = get_test_assembly()?;
let hash = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
let ref_ = AssemblyRefBuilder::new()
.name("HashedAssembly")
.version(1, 0, 0, 0)
.hash_value(&hash)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
Ok(())
}
#[test]
fn test_assemblyref_builder_multiple_assembly_refs() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref1 = AssemblyRefBuilder::new()
.name("FirstAssembly")
.version(1, 0, 0, 0)
.build(&mut assembly)?;
let ref2 = AssemblyRefBuilder::new()
.name("SecondAssembly")
.version(2, 0, 0, 0)
.build(&mut assembly)?;
assert!(!std::sync::Arc::ptr_eq(&ref1, &ref2));
assert_eq!(ref1.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
assert_eq!(ref2.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
Ok(())
}
#[test]
fn test_assemblyref_builder_comprehensive() -> Result<()> {
let mut assembly = get_test_assembly()?;
let token_data = [0xB7, 0x7A, 0x5C, 0x56, 0x19, 0x34, 0xE0, 0x89];
let hash = vec![0xDE, 0xAD, 0xBE, 0xEF];
let ref_ = AssemblyRefBuilder::new()
.name("ComprehensiveAssembly")
.version(2, 1, 4, 8)
.culture("fr-FR")
.public_key_token(&token_data)
.hash_value(&hash)
.flags(AssemblyFlags::RETARGETABLE)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
Ok(())
}
#[test]
fn test_assemblyref_builder_fluent_api() -> Result<()> {
let mut assembly = get_test_assembly()?;
let ref_ = AssemblyRefBuilder::new()
.name("FluentAssembly")
.version(3, 1, 4, 1)
.culture("de-DE")
.flags(0x0001)
.build(&mut assembly)?;
assert_eq!(ref_.kind(), ChangeRefKind::TableRow(TableId::AssemblyRef));
Ok(())
}
#[test]
fn test_assemblyref_builder_clone() {
let builder1 = AssemblyRefBuilder::new()
.name("CloneTest")
.version(1, 2, 3, 4);
let builder2 = builder1.clone();
assert_eq!(builder1.name, builder2.name);
assert_eq!(builder1.major_version, builder2.major_version);
assert_eq!(builder1.minor_version, builder2.minor_version);
}
#[test]
fn test_assemblyref_builder_debug() {
let builder = AssemblyRefBuilder::new()
.name("DebugAssembly")
.version(1, 0, 0, 0);
let debug_str = format!("{builder:?}");
assert!(debug_str.contains("AssemblyRefBuilder"));
assert!(debug_str.contains("DebugAssembly"));
}
}