#![allow(clippy::uninlined_format_args)]
#![allow(dead_code)]
use std::io::Write;
use std::time::{Duration, Instant};
use tempfile::NamedTempFile;
use threatflux_binary_analysis::types::*;
use threatflux_binary_analysis::{AnalysisConfig, BinaryAnalyzer};
pub struct PerformanceTester {
name: String,
start: Instant,
}
impl PerformanceTester {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
start: Instant::now(),
}
}
pub fn finish(self) -> Duration {
let duration = self.start.elapsed();
println!("Performance test '{}': {:?}", self.name, duration);
duration
}
pub fn assert_under(self, max_duration: Duration) {
let name = self.name.clone();
let duration = self.finish();
assert!(
duration <= max_duration,
"Performance test '{}' took {:?}, expected under {:?}",
name,
duration,
max_duration
);
}
}
pub fn create_temp_binary(data: &[u8]) -> NamedTempFile {
let mut file = NamedTempFile::new().expect("Failed to create temp file");
file.write_all(data).expect("Failed to write to temp file");
file.flush().expect("Failed to flush temp file");
file
}
pub type HelperResult<T> = Result<T, Box<dyn std::error::Error>>;
pub fn analyze_with_default(data: &[u8]) -> HelperResult<AnalysisResult> {
let analyzer = BinaryAnalyzer::new();
Ok(analyzer.analyze(data)?)
}
pub fn analyze_with_config(data: &[u8], config: AnalysisConfig) -> HelperResult<AnalysisResult> {
let analyzer = BinaryAnalyzer::with_config(config);
Ok(analyzer.analyze(data)?)
}
pub fn analyze_minimal(data: &[u8]) -> HelperResult<AnalysisResult> {
let config = AnalysisConfig {
enable_disassembly: false,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: threatflux_binary_analysis::DisassemblyEngine::Auto,
enable_control_flow: false,
enable_entropy: false,
enable_symbols: false,
max_analysis_size: 1024,
architecture_hint: None,
..Default::default()
};
analyze_with_config(data, config)
}
pub fn analyze_maximal(data: &[u8]) -> HelperResult<AnalysisResult> {
let config = AnalysisConfig {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: threatflux_binary_analysis::DisassemblyEngine::Auto,
enable_control_flow: true,
enable_entropy: true,
enable_symbols: true,
max_analysis_size: 100 * 1024 * 1024,
architecture_hint: None,
..Default::default()
};
analyze_with_config(data, config)
}
pub fn verify_analysis_completeness(result: &AnalysisResult) {
assert_ne!(
result.format,
BinaryFormat::Unknown,
"Format should be detected"
);
assert_ne!(
result.architecture,
Architecture::Unknown,
"Architecture should be detected"
);
assert_eq!(result.metadata.format, result.format);
assert_eq!(result.metadata.architecture, result.architecture);
assert!(result.metadata.size > 0, "File size should be positive");
let _ = result.sections.len();
let _ = result.symbols.len();
let _ = result.imports.len();
let _ = result.exports.len();
}
pub fn verify_metadata_validity(metadata: &BinaryMetadata) {
assert!(metadata.size > 0, "File size should be positive");
assert_ne!(metadata.format, BinaryFormat::Unknown);
assert_ne!(metadata.architecture, Architecture::Unknown);
if let Some(entry) = metadata.entry_point {
assert!(entry > 0, "Entry point should be positive if set");
assert!(entry < u64::MAX, "Entry point should be reasonable");
}
if let Some(base) = metadata.base_address {
assert!(base > 0, "Base address should be positive if set");
}
if let Some(timestamp) = metadata.timestamp {
assert!(timestamp > 0, "Timestamp should be positive if set");
assert!(timestamp < u64::MAX, "Timestamp should be reasonable");
}
verify_security_features(&metadata.security_features);
}
pub fn verify_security_features(features: &SecurityFeatures) {
let _nx = features.nx_bit;
let _aslr = features.aslr;
let _canary = features.stack_canary;
let _cfi = features.cfi;
let _fortify = features.fortify;
let _pie = features.pie;
let _relro = features.relro;
let _signed = features.signed;
}
pub fn verify_sections_validity(sections: &[Section]) {
for (i, section) in sections.iter().enumerate() {
assert!(
!section.name.is_empty(),
"Section {} name should not be empty",
i
);
assert!(section.size > 0, "Section {} size should be positive", i);
assert!(
section.address < u64::MAX,
"Section {} address should be reasonable",
i
);
if let Some(ref data) = section.data {
assert!(
data.len() <= section.size as usize,
"Section {} data size exceeds section size",
i
);
}
let _read = section.permissions.read;
let _write = section.permissions.write;
let _execute = section.permissions.execute;
}
}
pub fn verify_symbols_validity(symbols: &[Symbol]) {
for (i, symbol) in symbols.iter().enumerate() {
assert!(
!symbol.name.is_empty(),
"Symbol {} name should not be empty",
i
);
assert!(
symbol.address < u64::MAX,
"Symbol {} address should be reasonable",
i
);
assert!(
symbol.size < u64::MAX,
"Symbol {} size should be reasonable",
i
);
if let Some(ref demangled) = symbol.demangled_name {
assert!(
!demangled.is_empty(),
"Symbol {} demangled name should not be empty",
i
);
}
if let Some(section_idx) = symbol.section_index {
assert!(
section_idx < 10000,
"Symbol {} section index should be reasonable",
i
);
}
}
}
pub fn verify_imports_validity(imports: &[Import]) {
for (i, import) in imports.iter().enumerate() {
assert!(
!import.name.is_empty(),
"Import {} name should not be empty",
i
);
if let Some(ref library) = import.library {
assert!(
!library.is_empty(),
"Import {} library name should not be empty",
i
);
}
if let Some(address) = import.address {
assert!(
address > 0,
"Import {} address should be positive if set",
i
);
assert!(
address < u64::MAX,
"Import {} address should be reasonable",
i
);
}
if let Some(ordinal) = import.ordinal {
assert!(
ordinal > 0,
"Import {} ordinal should be positive if set",
i
);
}
}
}
pub fn verify_exports_validity(exports: &[Export]) {
for (i, export) in exports.iter().enumerate() {
assert!(
!export.name.is_empty(),
"Export {} name should not be empty",
i
);
assert!(
export.address > 0,
"Export {} address should be positive",
i
);
assert!(
export.address < u64::MAX,
"Export {} address should be reasonable",
i
);
if let Some(ordinal) = export.ordinal {
assert!(
ordinal > 0,
"Export {} ordinal should be positive if set",
i
);
}
if let Some(ref forwarded) = export.forwarded_name {
assert!(
!forwarded.is_empty(),
"Export {} forwarded name should not be empty",
i
);
}
}
}
pub fn verify_instructions_validity(instructions: &[Instruction]) {
for (i, instruction) in instructions.iter().enumerate() {
assert!(
instruction.address > 0,
"Instruction {} address should be positive",
i
);
assert!(
instruction.address < u64::MAX,
"Instruction {} address should be reasonable",
i
);
assert!(
!instruction.bytes.is_empty(),
"Instruction {} bytes should not be empty",
i
);
assert!(
instruction.size > 0,
"Instruction {} size should be positive",
i
);
assert!(
instruction.size <= 16,
"Instruction {} size should be reasonable",
i
);
assert_eq!(
instruction.bytes.len(),
instruction.size,
"Instruction {} bytes length should match size",
i
);
assert!(
!instruction.mnemonic.is_empty(),
"Instruction {} mnemonic should not be empty",
i
);
}
}
pub fn test_consistency<F, R>(test_fn: F, iterations: usize) -> Vec<R>
where
F: Fn() -> R,
R: Clone,
{
let mut results = Vec::with_capacity(iterations);
for _ in 0..iterations {
results.push(test_fn());
}
results
}
pub fn test_memory_usage<F>(test_fn: F, iterations: usize)
where
F: Fn(),
{
for _ in 0..iterations {
test_fn();
}
}
pub fn stress_test_config() -> AnalysisConfig {
AnalysisConfig {
enable_disassembly: true,
#[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: threatflux_binary_analysis::DisassemblyEngine::Auto,
enable_control_flow: true,
enable_entropy: true,
enable_symbols: true,
max_analysis_size: 1024 * 1024, architecture_hint: None,
..Default::default()
}
}
pub fn performance_test_config() -> AnalysisConfig {
AnalysisConfig {
enable_disassembly: false, #[cfg(any(feature = "disasm-capstone", feature = "disasm-iced"))]
disassembly_engine: threatflux_binary_analysis::DisassemblyEngine::Auto,
enable_control_flow: false,
enable_entropy: false,
enable_symbols: true, max_analysis_size: 10 * 1024 * 1024, architecture_hint: None,
..Default::default()
}
}
pub fn test_error_handling() {
let analyzer = BinaryAnalyzer::new();
let result = analyzer.analyze(&[]);
assert!(result.is_err(), "Empty data should cause error");
let _result = analyzer.analyze(&[0x00]);
let _result = analyzer.analyze(&[0xff, 0xff, 0xff, 0xff]);
}
pub fn compare_analysis_results(result1: &AnalysisResult, result2: &AnalysisResult) {
assert_eq!(result1.format, result2.format);
assert_eq!(result1.architecture, result2.architecture);
assert_eq!(result1.entry_point, result2.entry_point);
assert_eq!(result1.metadata.format, result2.metadata.format);
assert_eq!(result1.metadata.architecture, result2.metadata.architecture);
assert_eq!(result1.metadata.size, result2.metadata.size);
assert_eq!(result1.metadata.entry_point, result2.metadata.entry_point);
assert_eq!(result1.sections.len(), result2.sections.len());
assert_eq!(result1.symbols.len(), result2.symbols.len());
}