1use std::collections::HashSet;
7use std::path::Path;
8
9use crate::error::Result;
10use crate::pe::parse_pe_file;
11use crate::types::{ExportDiff, ExportInfo, NameMismatch, PeFile, VerificationReport};
12
13pub fn list_dll_exports(path: impl AsRef<Path>) -> Result<Vec<ExportInfo>> {
28 let pe = parse_pe_file(path.as_ref())?;
29 Ok(pe.exports().to_vec())
30}
31
32pub fn list_export_names(path: impl AsRef<Path>) -> Result<Vec<String>> {
34 let pe = parse_pe_file(path.as_ref())?;
35 Ok(pe.export_names())
36}
37
38pub fn verify_dll_exports(
49 path: impl AsRef<Path>,
50 expected: &[&str],
51) -> Result<VerificationReport> {
52 let pe = parse_pe_file(path.as_ref())?;
53 Ok(verify_pe_exports(&pe, expected))
54}
55
56pub fn verify_pe_exports(pe: &PeFile, expected: &[&str]) -> VerificationReport {
71 let export_names: HashSet<&str> = pe
72 .exports
73 .iter()
74 .filter_map(|e| e.name.as_deref())
75 .collect();
76
77 let expected_set: HashSet<&str> = expected.iter().copied().collect();
78
79 let mut found = Vec::new();
80 let mut missing = Vec::new();
81
82 for &name in expected {
83 if export_names.contains(name) {
84 found.push(name.to_string());
85 } else {
86 missing.push(name.to_string());
87 }
88 }
89
90 let mut unexpected: Vec<String> = export_names
91 .iter()
92 .filter(|n| !expected_set.contains(*n))
93 .map(|s| s.to_string())
94 .collect();
95 unexpected.sort();
96
97 let complete = missing.is_empty();
98
99 VerificationReport {
100 dll_path: pe.path.clone(),
101 total_exports: pe.exports.len(),
102 found,
103 missing,
104 complete,
105 unexpected,
106 mismatches: Vec::new(),
107 architecture: pe.architecture,
108 }
109}
110
111pub fn verify_dll_exports_strict(
116 path: impl AsRef<Path>,
117 expected: &[&str],
118 case_sensitive: bool,
119) -> Result<VerificationReport> {
120 let pe = parse_pe_file(path.as_ref())?;
121 let mut report = verify_pe_exports(&pe, expected);
122
123 if !case_sensitive {
124 let exports_lower: Vec<(String, &ExportInfo)> = pe
128 .exports
129 .iter()
130 .filter_map(|e| e.name.as_deref().map(|n| (n.to_lowercase(), e)))
131 .collect();
132
133 let mut found = Vec::new();
134 let mut missing = Vec::new();
135 let mut mismatches = Vec::new();
136
137 for &name in expected {
138 let key = name.to_lowercase();
139 if let Some((_, export)) = exports_lower.iter().find(|(lc, _)| *lc == key) {
141 found.push(name.to_string());
142 let actual_name = export
144 .name
145 .as_deref()
146 .expect("export has name (filtered above)");
147 if actual_name != name {
148 mismatches.push(NameMismatch {
149 expected: name.to_string(),
150 actual: actual_name.to_string(),
151 ordinal: export.ordinal,
152 });
153 }
154 } else {
155 missing.push(name.to_string());
156 }
157 }
158
159 let expected_lower_set: HashSet<String> =
160 expected.iter().map(|s| s.to_lowercase()).collect();
161 let mut unexpected: Vec<String> = pe
162 .exports
163 .iter()
164 .filter_map(|e| e.name.as_deref())
165 .filter(|n| !expected_lower_set.contains(&n.to_lowercase()))
166 .map(|s| s.to_string())
167 .collect();
168 unexpected.sort();
169
170 report.found = found;
171 report.missing = missing;
172 report.complete = report.missing.is_empty();
173 report.unexpected = unexpected;
174 report.mismatches = mismatches;
175 }
176
177 Ok(report)
178}
179
180pub fn export_diff(expected: &[&str], actual: &[ExportInfo]) -> ExportDiff {
198 let actual_names: HashSet<&str> = actual
199 .iter()
200 .filter_map(|e| e.name.as_deref())
201 .collect();
202
203 let expected_set: HashSet<&str> = expected.iter().copied().collect();
204
205 let missing = expected
206 .iter()
207 .filter(|e| !actual_names.contains(*e))
208 .map(|s| s.to_string())
209 .collect();
210
211 let mut extra: Vec<String> = actual_names
212 .iter()
213 .filter(|e| !expected_set.contains(*e))
214 .map(|s| s.to_string())
215 .collect();
216 extra.sort();
217
218 let common = expected
219 .iter()
220 .filter(|e| actual_names.contains(*e))
221 .map(|s| s.to_string())
222 .collect();
223
224 ExportDiff {
225 missing,
226 extra,
227 common,
228 }
229}