use std::collections::HashSet;
use std::path::Path;
use crate::error::Result;
use crate::pe::parse_pe_file;
use crate::types::{ExportDiff, ExportInfo, NameMismatch, PeFile, VerificationReport};
pub fn list_dll_exports(path: impl AsRef<Path>) -> Result<Vec<ExportInfo>> {
let pe = parse_pe_file(path.as_ref())?;
Ok(pe.exports().to_vec())
}
pub fn list_export_names(path: impl AsRef<Path>) -> Result<Vec<String>> {
let pe = parse_pe_file(path.as_ref())?;
Ok(pe.export_names())
}
pub fn verify_dll_exports(
path: impl AsRef<Path>,
expected: &[&str],
) -> Result<VerificationReport> {
let pe = parse_pe_file(path.as_ref())?;
Ok(verify_pe_exports(&pe, expected))
}
pub fn verify_pe_exports(pe: &PeFile, expected: &[&str]) -> VerificationReport {
let export_names: HashSet<&str> = pe
.exports
.iter()
.filter_map(|e| e.name.as_deref())
.collect();
let expected_set: HashSet<&str> = expected.iter().copied().collect();
let mut found = Vec::new();
let mut missing = Vec::new();
for &name in expected {
if export_names.contains(name) {
found.push(name.to_string());
} else {
missing.push(name.to_string());
}
}
let mut unexpected: Vec<String> = export_names
.iter()
.filter(|n| !expected_set.contains(*n))
.map(|s| s.to_string())
.collect();
unexpected.sort();
let complete = missing.is_empty();
VerificationReport {
dll_path: pe.path.clone(),
total_exports: pe.exports.len(),
found,
missing,
complete,
unexpected,
mismatches: Vec::new(),
architecture: pe.architecture,
}
}
pub fn verify_dll_exports_strict(
path: impl AsRef<Path>,
expected: &[&str],
case_sensitive: bool,
) -> Result<VerificationReport> {
let pe = parse_pe_file(path.as_ref())?;
let mut report = verify_pe_exports(&pe, expected);
if !case_sensitive {
let exports_lower: Vec<(String, &ExportInfo)> = pe
.exports
.iter()
.filter_map(|e| e.name.as_deref().map(|n| (n.to_lowercase(), e)))
.collect();
let mut found = Vec::new();
let mut missing = Vec::new();
let mut mismatches = Vec::new();
for &name in expected {
let key = name.to_lowercase();
if let Some((_, export)) = exports_lower.iter().find(|(lc, _)| *lc == key) {
found.push(name.to_string());
let actual_name = export
.name
.as_deref()
.expect("export has name (filtered above)");
if actual_name != name {
mismatches.push(NameMismatch {
expected: name.to_string(),
actual: actual_name.to_string(),
ordinal: export.ordinal,
});
}
} else {
missing.push(name.to_string());
}
}
let expected_lower_set: HashSet<String> =
expected.iter().map(|s| s.to_lowercase()).collect();
let mut unexpected: Vec<String> = pe
.exports
.iter()
.filter_map(|e| e.name.as_deref())
.filter(|n| !expected_lower_set.contains(&n.to_lowercase()))
.map(|s| s.to_string())
.collect();
unexpected.sort();
report.found = found;
report.missing = missing;
report.complete = report.missing.is_empty();
report.unexpected = unexpected;
report.mismatches = mismatches;
}
Ok(report)
}
pub fn export_diff(expected: &[&str], actual: &[ExportInfo]) -> ExportDiff {
let actual_names: HashSet<&str> = actual
.iter()
.filter_map(|e| e.name.as_deref())
.collect();
let expected_set: HashSet<&str> = expected.iter().copied().collect();
let missing = expected
.iter()
.filter(|e| !actual_names.contains(*e))
.map(|s| s.to_string())
.collect();
let mut extra: Vec<String> = actual_names
.iter()
.filter(|e| !expected_set.contains(*e))
.map(|s| s.to_string())
.collect();
extra.sort();
let common = expected
.iter()
.filter(|e| actual_names.contains(*e))
.map(|s| s.to_string())
.collect();
ExportDiff {
missing,
extra,
common,
}
}