threatflux_binary_analysis/
error.rs

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