threatflux_binary_analysis/
error.rs1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, BinaryError>;
7
8#[derive(Error, Debug)]
10#[non_exhaustive]
11pub enum BinaryError {
12 #[error("Failed to parse binary format: {0}")]
14 ParseError(String),
15
16 #[error("Unsupported binary format: {0}")]
18 UnsupportedFormat(String),
19
20 #[error("Unsupported architecture: {0}")]
22 UnsupportedArchitecture(String),
23
24 #[error("Invalid binary data: {0}")]
26 InvalidData(String),
27
28 #[error("Input size {actual} bytes exceeds the configured limit of {limit} bytes")]
30 InputTooLarge {
31 actual: usize,
33 limit: usize,
35 },
36
37 #[error("Disassembly failed: {0}")]
39 DisassemblyError(String),
40
41 #[error("Control flow analysis failed: {0}")]
43 ControlFlowError(String),
44
45 #[error("Symbol resolution failed: {0}")]
47 SymbolError(String),
48
49 #[error("Entropy analysis failed: {0}")]
51 EntropyError(String),
52
53 #[error("I/O error: {0}")]
55 IoError(#[from] std::io::Error),
56
57 #[error("Memory mapping error: {0}")]
59 MemoryMapError(String),
60
61 #[error("Configuration error: {0}")]
63 ConfigError(String),
64
65 #[error("Feature not available: {0} (try enabling the corresponding feature flag)")]
67 FeatureNotAvailable(String),
68
69 #[error("Internal error: {0}")]
71 Internal(String),
72}
73
74#[cfg(any(feature = "elf", feature = "macho", feature = "pe"))]
75impl From<goblin::error::Error> for BinaryError {
76 fn from(err: goblin::error::Error) -> Self {
77 BinaryError::ParseError(err.to_string())
78 }
79}
80
81#[cfg(feature = "disasm-capstone")]
82impl From<capstone::Error> for BinaryError {
83 fn from(err: capstone::Error) -> Self {
84 BinaryError::DisassemblyError(err.to_string())
85 }
86}
87
88#[cfg(feature = "wasm")]
89impl From<wasmparser::BinaryReaderError> for BinaryError {
90 fn from(err: wasmparser::BinaryReaderError) -> Self {
91 BinaryError::ParseError(format!("WASM parse error: {}", err))
92 }
93}
94
95impl BinaryError {
96 pub fn parse<S: Into<String>>(msg: S) -> Self {
98 Self::ParseError(msg.into())
99 }
100
101 pub fn unsupported_format<S: Into<String>>(format: S) -> Self {
103 Self::UnsupportedFormat(format.into())
104 }
105
106 pub fn unsupported_arch<S: Into<String>>(arch: S) -> Self {
108 Self::UnsupportedArchitecture(arch.into())
109 }
110
111 pub fn invalid_data<S: Into<String>>(msg: S) -> Self {
113 Self::InvalidData(msg.into())
114 }
115
116 pub fn input_too_large(actual: usize, limit: usize) -> Self {
118 Self::InputTooLarge { actual, limit }
119 }
120
121 pub fn disassembly<S: Into<String>>(msg: S) -> Self {
123 Self::DisassemblyError(msg.into())
124 }
125
126 pub fn control_flow<S: Into<String>>(msg: S) -> Self {
128 Self::ControlFlowError(msg.into())
129 }
130
131 pub fn symbol<S: Into<String>>(msg: S) -> Self {
133 Self::SymbolError(msg.into())
134 }
135
136 pub fn entropy<S: Into<String>>(msg: S) -> Self {
138 Self::EntropyError(msg.into())
139 }
140
141 pub fn memory_map<S: Into<String>>(msg: S) -> Self {
143 Self::MemoryMapError(msg.into())
144 }
145
146 pub fn config<S: Into<String>>(msg: S) -> Self {
148 Self::ConfigError(msg.into())
149 }
150
151 pub fn feature_not_available<S: Into<String>>(feature: S) -> Self {
153 Self::FeatureNotAvailable(feature.into())
154 }
155
156 pub fn internal<S: Into<String>>(msg: S) -> Self {
158 Self::Internal(msg.into())
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 use std::error::Error;
166 use std::io;
167
168 #[test]
169 fn test_binary_error_parse_creation() {
170 let error = BinaryError::parse("test message");
171 assert!(matches!(error, BinaryError::ParseError(_)));
172 assert_eq!(
173 error.to_string(),
174 "Failed to parse binary format: test message"
175 );
176 }
177
178 #[test]
179 fn test_binary_error_parse_creation_string() {
180 let msg = String::from("dynamic string");
181 let error = BinaryError::parse(msg);
182 assert!(matches!(error, BinaryError::ParseError(_)));
183 assert_eq!(
184 error.to_string(),
185 "Failed to parse binary format: dynamic string"
186 );
187 }
188
189 #[test]
190 fn test_binary_error_unsupported_format_creation() {
191 let error = BinaryError::unsupported_format("CUSTOM");
192 assert!(matches!(error, BinaryError::UnsupportedFormat(_)));
193 assert_eq!(error.to_string(), "Unsupported binary format: CUSTOM");
194 }
195
196 #[test]
197 fn test_binary_error_unsupported_arch_creation() {
198 let error = BinaryError::unsupported_arch("ARM32");
199 assert!(matches!(error, BinaryError::UnsupportedArchitecture(_)));
200 assert_eq!(error.to_string(), "Unsupported architecture: ARM32");
201 }
202
203 #[test]
204 fn test_binary_error_invalid_data_creation() {
205 let error = BinaryError::invalid_data("corrupt header");
206 assert!(matches!(error, BinaryError::InvalidData(_)));
207 assert_eq!(error.to_string(), "Invalid binary data: corrupt header");
208 }
209
210 #[test]
211 fn test_binary_error_disassembly_creation() {
212 let error = BinaryError::disassembly("unable to decode instruction");
213 assert!(matches!(error, BinaryError::DisassemblyError(_)));
214 assert_eq!(
215 error.to_string(),
216 "Disassembly failed: unable to decode instruction"
217 );
218 }
219
220 #[test]
221 fn test_binary_error_control_flow_creation() {
222 let error = BinaryError::control_flow("circular dependency detected");
223 assert!(matches!(error, BinaryError::ControlFlowError(_)));
224 assert_eq!(
225 error.to_string(),
226 "Control flow analysis failed: circular dependency detected"
227 );
228 }
229
230 #[test]
231 fn test_binary_error_symbol_creation() {
232 let error = BinaryError::symbol("undefined reference");
233 assert!(matches!(error, BinaryError::SymbolError(_)));
234 assert_eq!(
235 error.to_string(),
236 "Symbol resolution failed: undefined reference"
237 );
238 }
239
240 #[test]
241 fn test_binary_error_entropy_creation() {
242 let error = BinaryError::entropy("insufficient data");
243 assert!(matches!(error, BinaryError::EntropyError(_)));
244 assert_eq!(
245 error.to_string(),
246 "Entropy analysis failed: insufficient data"
247 );
248 }
249
250 #[test]
251 fn test_binary_error_memory_map_creation() {
252 let error = BinaryError::memory_map("permission denied");
253 assert!(matches!(error, BinaryError::MemoryMapError(_)));
254 assert_eq!(error.to_string(), "Memory mapping error: permission denied");
255 }
256
257 #[test]
258 fn test_binary_error_config_creation() {
259 let error = BinaryError::config("invalid configuration value");
260 assert!(matches!(error, BinaryError::ConfigError(_)));
261 assert_eq!(
262 error.to_string(),
263 "Configuration error: invalid configuration value"
264 );
265 }
266
267 #[test]
268 fn test_binary_error_feature_not_available_creation() {
269 let error = BinaryError::feature_not_available("disasm-capstone");
270 assert!(matches!(error, BinaryError::FeatureNotAvailable(_)));
271 assert_eq!(
272 error.to_string(),
273 "Feature not available: disasm-capstone (try enabling the corresponding feature flag)"
274 );
275 }
276
277 #[test]
278 fn test_binary_error_internal_creation() {
279 let error = BinaryError::internal("unexpected state");
280 assert!(matches!(error, BinaryError::Internal(_)));
281 assert_eq!(error.to_string(), "Internal error: unexpected state");
282 }
283
284 #[test]
285 fn test_binary_error_display_all_variants() {
286 let test_cases = vec![
287 (
288 BinaryError::ParseError("parse issue".to_string()),
289 "Failed to parse binary format: parse issue",
290 ),
291 (
292 BinaryError::UnsupportedFormat("UNKNOWN".to_string()),
293 "Unsupported binary format: UNKNOWN",
294 ),
295 (
296 BinaryError::UnsupportedArchitecture("SPARC".to_string()),
297 "Unsupported architecture: SPARC",
298 ),
299 (
300 BinaryError::InvalidData("malformed".to_string()),
301 "Invalid binary data: malformed",
302 ),
303 (
304 BinaryError::DisassemblyError("decode failed".to_string()),
305 "Disassembly failed: decode failed",
306 ),
307 (
308 BinaryError::ControlFlowError("analysis failed".to_string()),
309 "Control flow analysis failed: analysis failed",
310 ),
311 (
312 BinaryError::SymbolError("not found".to_string()),
313 "Symbol resolution failed: not found",
314 ),
315 (
316 BinaryError::EntropyError("low entropy".to_string()),
317 "Entropy analysis failed: low entropy",
318 ),
319 (
320 BinaryError::MemoryMapError("map failed".to_string()),
321 "Memory mapping error: map failed",
322 ),
323 (
324 BinaryError::ConfigError("bad config".to_string()),
325 "Configuration error: bad config",
326 ),
327 (
328 BinaryError::FeatureNotAvailable("test-feature".to_string()),
329 "Feature not available: test-feature (try enabling the corresponding feature flag)",
330 ),
331 (
332 BinaryError::Internal("bug detected".to_string()),
333 "Internal error: bug detected",
334 ),
335 ];
336
337 for (error, expected) in test_cases {
338 assert_eq!(error.to_string(), expected);
339 }
340 }
341
342 #[test]
343 fn test_binary_error_debug_formatting() {
344 let error = BinaryError::ParseError("test".to_string());
345 let debug_str = format!("{:?}", error);
346 assert!(debug_str.contains("ParseError"));
347 assert!(debug_str.contains("test"));
348 }
349
350 #[test]
351 fn test_from_io_error() {
352 let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
353 let binary_err: BinaryError = io_err.into();
354
355 assert!(matches!(binary_err, BinaryError::IoError(_)));
356 assert!(binary_err.to_string().contains("I/O error"));
357 assert!(binary_err.to_string().contains("file not found"));
358 }
359
360 #[cfg(any(feature = "elf", feature = "macho", feature = "pe"))]
361 #[test]
362 fn test_from_goblin_error() {
363 let goblin_err = goblin::error::Error::Malformed("invalid header".to_string());
364 let binary_err: BinaryError = goblin_err.into();
365
366 assert!(matches!(binary_err, BinaryError::ParseError(_)));
367 assert!(
368 binary_err
369 .to_string()
370 .contains("Failed to parse binary format")
371 );
372 assert!(binary_err.to_string().contains("invalid header"));
373 }
374
375 #[test]
376 fn test_error_source_chain_io() {
377 let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "access denied");
378 let binary_err: BinaryError = io_err.into();
379
380 assert!(binary_err.source().is_some());
382 let source = binary_err.source().unwrap();
383 assert!(source.to_string().contains("access denied"));
384 }
385
386 #[test]
387 fn test_error_source_chain_root() {
388 let parse_err = BinaryError::ParseError("test".to_string());
389 assert!(parse_err.source().is_none());
390 }
391
392 #[test]
393 fn test_result_type_alias() {
394 fn test_function() -> Result<i32> {
395 Ok(42)
396 }
397
398 let result = test_function();
399 assert!(result.is_ok());
400 assert_eq!(result.unwrap(), 42);
401 }
402
403 #[test]
404 fn test_result_type_alias_error() {
405 fn test_function() -> Result<i32> {
406 Err(BinaryError::parse("test error"))
407 }
408
409 let result = test_function();
410 assert!(result.is_err());
411 let error = result.unwrap_err();
412 assert!(matches!(error, BinaryError::ParseError(_)));
413 }
414
415 #[cfg(feature = "disasm-capstone")]
417 #[test]
418 fn test_from_capstone_error() {
419 use capstone::Error as CapstoneError;
420
421 let capstone_err = CapstoneError::InvalidHandle;
422 let binary_err: BinaryError = capstone_err.into();
423
424 assert!(matches!(binary_err, BinaryError::DisassemblyError(_)));
425 assert!(binary_err.to_string().contains("Disassembly failed"));
426 }
427
428 #[cfg(feature = "wasm")]
429 #[test]
430 fn test_from_wasmparser_error() {
431 use wasmparser::BinaryReader;
432
433 let invalid_wasm = vec![0x00, 0x61, 0x73]; let mut reader = BinaryReader::new(&invalid_wasm, 0);
436
437 let wasm_result = reader.read_u32();
439 assert!(wasm_result.is_err());
440
441 let wasm_err = wasm_result.unwrap_err();
442 let binary_err: BinaryError = wasm_err.into();
443
444 assert!(matches!(binary_err, BinaryError::ParseError(_)));
445 assert!(
446 binary_err
447 .to_string()
448 .contains("Failed to parse binary format")
449 );
450 assert!(binary_err.to_string().contains("WASM parse error"));
451 }
452
453 #[test]
455 fn test_error_propagation_chain() {
456 fn inner_function() -> std::io::Result<()> {
457 Err(io::Error::new(
458 io::ErrorKind::UnexpectedEof,
459 "truncated file",
460 ))
461 }
462
463 fn outer_function() -> Result<()> {
464 inner_function()?;
465 Ok(())
466 }
467
468 let result = outer_function();
469 assert!(result.is_err());
470 let error = result.unwrap_err();
471 assert!(matches!(error, BinaryError::IoError(_)));
472 assert!(error.source().is_some());
473 }
474
475 #[test]
476 fn test_error_equality_and_comparison() {
477 let err1 = BinaryError::ParseError("test".to_string());
478 let err2 = BinaryError::ParseError("test".to_string());
479 let err3 = BinaryError::ParseError("different".to_string());
480 let err4 = BinaryError::InvalidData("test".to_string());
481
482 assert_eq!(err1.to_string(), err2.to_string());
484 assert_ne!(err1.to_string(), err3.to_string());
485 assert_ne!(err1.to_string(), err4.to_string());
486 }
487
488 #[test]
489 fn test_error_send_sync() {
490 fn assert_send<T: Send>() {}
491 fn assert_sync<T: Sync>() {}
492
493 assert_send::<BinaryError>();
494 assert_sync::<BinaryError>();
495 }
496
497 #[test]
498 fn test_comprehensive_constructor_parameters() {
499 let _e1 = BinaryError::parse("str literal");
501 let _e2 = BinaryError::parse(String::from("owned string"));
502 let _e3 = BinaryError::parse("str reference".to_string());
503
504 let _e4 = BinaryError::internal("");
506 assert_eq!(_e4.to_string(), "Internal error: ");
507
508 let _e5 = BinaryError::config("配置错误");
510 assert!(_e5.to_string().contains("配置错误"));
511 }
512
513 #[test]
514 fn test_error_in_threaded_context() {
515 use std::thread;
516
517 let handle = thread::spawn(|| {
518 let error = BinaryError::parse("thread error");
519 error.to_string()
520 });
521
522 let result = handle.join().unwrap();
523 assert_eq!(result, "Failed to parse binary format: thread error");
524 }
525}