Skip to main content

threatflux_binary_analysis/utils/
patterns.rs

1//! Bounded pattern matching utilities for binary data.
2//!
3//! Exact bytes, ASCII string matching, hexadecimal wildcards, and leading magic
4//! signatures are implemented. Regex and structural variants return
5//! [`crate::BinaryError::FeatureNotAvailable`] instead of silently producing no
6//! matches.
7
8use crate::{BinaryError, Result};
9use std::collections::HashMap;
10
11/// Default aggregate owned-output limit for a pattern search: 16 MiB.
12pub const DEFAULT_MAX_MATCH_OUTPUT_BYTES: usize = 16 * 1024 * 1024;
13
14/// Pattern matcher for binary data
15pub struct PatternMatcher {
16    patterns: Vec<Pattern>,
17    config: MatchConfig,
18}
19
20/// Pattern matching configuration
21#[derive(Debug, Clone)]
22pub struct MatchConfig {
23    /// Case-sensitive byte matching for string patterns. When disabled, matching
24    /// uses ASCII case folding so byte offsets always refer to the original input.
25    pub case_sensitive: bool,
26    /// Maximum number of matches to find
27    pub max_matches: usize,
28    /// Maximum aggregate bytes owned by returned matches and category buckets.
29    ///
30    /// This includes cloned pattern metadata and matched bytes in both result
31    /// collections. Exceeding the limit fails the search instead of returning a
32    /// partial result.
33    pub max_output_bytes: usize,
34    /// Enable wildcard matching
35    pub enable_wildcards: bool,
36    /// Minimum pattern length
37    pub min_pattern_length: usize,
38}
39
40impl Default for MatchConfig {
41    fn default() -> Self {
42        Self {
43            case_sensitive: true,
44            max_matches: 1000,
45            max_output_bytes: DEFAULT_MAX_MATCH_OUTPUT_BYTES,
46            enable_wildcards: true,
47            min_pattern_length: 3,
48        }
49    }
50}
51
52/// A pattern to search for
53#[derive(Debug, Clone)]
54pub struct Pattern {
55    /// Pattern name/identifier
56    pub name: String,
57    /// Pattern type
58    pub pattern_type: PatternType,
59    /// Pattern data
60    pub data: PatternData,
61    /// Pattern category
62    pub category: PatternCategory,
63    /// Description
64    pub description: String,
65}
66
67/// Types of patterns
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum PatternType {
70    /// Exact byte sequence
71    Bytes,
72    /// String pattern
73    String,
74    /// Regular expression
75    Regex,
76    /// Hex pattern with wildcards
77    HexWildcard,
78    /// Magic signature
79    Magic,
80    /// Structural pattern
81    Structural,
82}
83
84/// Pattern data
85#[derive(Debug, Clone)]
86pub enum PatternData {
87    /// Raw bytes
88    Bytes(Vec<u8>),
89    /// String data
90    String(String),
91    /// Hex pattern with wildcards (? for wildcard)
92    HexWildcard(String),
93    /// Regular expression pattern
94    Regex(String),
95}
96
97/// Pattern categories
98#[derive(Debug, Clone, PartialEq, Eq, Hash)]
99pub enum PatternCategory {
100    /// File format signatures
101    FileFormat,
102    /// Compiler signatures
103    Compiler,
104    /// Packer signatures
105    Packer,
106    /// Cryptographic constants
107    Crypto,
108    /// Malware signatures
109    Malware,
110    /// API strings
111    Api,
112    /// Debug information
113    Debug,
114    /// Copyright/version strings
115    Metadata,
116    /// Network protocols
117    Network,
118    /// Custom patterns
119    Custom,
120}
121
122/// Pattern match result
123#[derive(Debug, Clone)]
124pub struct PatternMatch {
125    /// Pattern that matched
126    pub pattern: Pattern,
127    /// Offset where match was found
128    pub offset: usize,
129    /// Length of the match
130    pub length: usize,
131    /// Matched data
132    pub data: Vec<u8>,
133    /// Confidence score (0.0 - 1.0)
134    pub confidence: f64,
135}
136
137struct MatchOutputBudget {
138    limit: usize,
139    used: usize,
140}
141
142impl MatchOutputBudget {
143    fn new(limit: usize) -> Self {
144        Self { limit, used: 0 }
145    }
146
147    fn claim(&mut self, pattern: &Pattern, matched_bytes: usize) -> Result<()> {
148        let pattern_bytes = pattern
149            .name
150            .len()
151            .checked_add(pattern.description.len())
152            .and_then(|size| size.checked_add(pattern_data_len(&pattern.data)))
153            .ok_or_else(pattern_output_overflow)?;
154        let one_match = std::mem::size_of::<PatternMatch>()
155            .checked_add(pattern_bytes)
156            .and_then(|size| size.checked_add(matched_bytes))
157            .ok_or_else(pattern_output_overflow)?;
158
159        // SearchResults owns every match twice: once in `matches` and once in
160        // its category bucket. Reserve both copies before constructing either.
161        let owned_bytes = one_match
162            .checked_mul(2)
163            .ok_or_else(pattern_output_overflow)?;
164        let next_used = self
165            .used
166            .checked_add(owned_bytes)
167            .ok_or_else(pattern_output_overflow)?;
168        if next_used > self.limit {
169            return Err(BinaryError::invalid_data(format!(
170                "pattern-match output would use {next_used} bytes, exceeding the configured {}-byte limit",
171                self.limit
172            )));
173        }
174
175        self.used = next_used;
176        Ok(())
177    }
178}
179
180fn pattern_data_len(data: &PatternData) -> usize {
181    match data {
182        PatternData::Bytes(bytes) => bytes.len(),
183        PatternData::String(value)
184        | PatternData::HexWildcard(value)
185        | PatternData::Regex(value) => value.len(),
186    }
187}
188
189fn pattern_output_overflow() -> BinaryError {
190    BinaryError::invalid_data("pattern-match output size overflows usize")
191}
192
193fn reserve_match_slot(matches: &mut Vec<PatternMatch>) -> Result<()> {
194    matches
195        .try_reserve(1)
196        .map_err(|_| BinaryError::invalid_data("unable to allocate pattern-match output"))
197}
198
199/// Pattern search results
200#[derive(Debug, Clone)]
201pub struct SearchResults {
202    /// All matches found
203    pub matches: Vec<PatternMatch>,
204    /// Matches grouped by category
205    pub by_category: crate::types::PatternMatchMap,
206    /// Total bytes searched
207    pub bytes_searched: usize,
208    /// Search duration
209    pub duration_ms: u64,
210}
211
212impl Default for PatternMatcher {
213    fn default() -> Self {
214        Self::new()
215    }
216}
217
218impl PatternMatcher {
219    /// Create a new pattern matcher
220    pub fn new() -> Self {
221        Self {
222            patterns: Vec::new(),
223            config: MatchConfig::default(),
224        }
225    }
226
227    /// Create pattern matcher with configuration
228    pub fn with_config(config: MatchConfig) -> Self {
229        Self {
230            patterns: Vec::new(),
231            config,
232        }
233    }
234
235    /// Add a pattern to search for
236    pub fn add_pattern(&mut self, pattern: Pattern) {
237        self.patterns.push(pattern);
238    }
239
240    /// Add multiple patterns
241    pub fn add_patterns(&mut self, patterns: Vec<Pattern>) {
242        self.patterns.extend(patterns);
243    }
244
245    /// Load built-in pattern sets
246    pub fn load_builtin_patterns(&mut self, categories: &[PatternCategory]) {
247        for category in categories {
248            let patterns = get_builtin_patterns(category);
249            self.add_patterns(patterns);
250        }
251    }
252
253    /// Search for all patterns in the given data
254    pub fn search(&self, data: &[u8]) -> Result<SearchResults> {
255        let start_time = std::time::Instant::now();
256        let mut matches = Vec::new();
257        let mut by_category: crate::types::PatternMatchMap = HashMap::new();
258        let mut output_budget = MatchOutputBudget::new(self.config.max_output_bytes);
259
260        if self.config.max_matches == 0 {
261            return Ok(SearchResults {
262                matches,
263                by_category,
264                bytes_searched: data.len(),
265                duration_ms: start_time.elapsed().as_millis() as u64,
266            });
267        }
268
269        for pattern in &self.patterns {
270            let remaining_matches = self.config.max_matches - matches.len();
271            let pattern_matches =
272                self.search_pattern(data, pattern, remaining_matches, &mut output_budget)?;
273
274            for pattern_match in pattern_matches {
275                let category_matches = by_category
276                    .entry(pattern_match.pattern.category.clone())
277                    .or_default();
278                category_matches.try_reserve(1).map_err(|_| {
279                    BinaryError::invalid_data("unable to allocate categorized pattern matches")
280                })?;
281                matches.try_reserve(1).map_err(|_| {
282                    BinaryError::invalid_data("unable to allocate pattern-match output")
283                })?;
284
285                category_matches.push(pattern_match.clone());
286                matches.push(pattern_match);
287
288                if matches.len() >= self.config.max_matches {
289                    break;
290                }
291            }
292
293            if matches.len() >= self.config.max_matches {
294                break;
295            }
296        }
297
298        let duration = start_time.elapsed();
299
300        Ok(SearchResults {
301            matches,
302            by_category,
303            bytes_searched: data.len(),
304            duration_ms: duration.as_millis() as u64,
305        })
306    }
307
308    /// Search for a specific pattern in data
309    fn search_pattern(
310        &self,
311        data: &[u8],
312        pattern: &Pattern,
313        match_limit: usize,
314        output_budget: &mut MatchOutputBudget,
315    ) -> Result<Vec<PatternMatch>> {
316        if match_limit == 0 {
317            return Ok(Vec::new());
318        }
319
320        match &pattern.pattern_type {
321            PatternType::Bytes => self.search_bytes(data, pattern, match_limit, output_budget),
322            PatternType::String => self.search_string(data, pattern, match_limit, output_budget),
323            PatternType::HexWildcard => {
324                self.search_hex_wildcard(data, pattern, match_limit, output_budget)
325            }
326            PatternType::Magic => self.search_magic(data, pattern, match_limit, output_budget),
327            PatternType::Regex => self.search_regex(data, pattern),
328            PatternType::Structural => self.search_structural(data, pattern),
329        }
330    }
331
332    /// Search for exact byte sequences
333    fn search_bytes(
334        &self,
335        data: &[u8],
336        pattern: &Pattern,
337        match_limit: usize,
338        output_budget: &mut MatchOutputBudget,
339    ) -> Result<Vec<PatternMatch>> {
340        let mut matches = Vec::new();
341
342        if let PatternData::Bytes(pattern_bytes) = &pattern.data {
343            if pattern_bytes.is_empty() || pattern_bytes.len() < self.config.min_pattern_length {
344                return Ok(matches);
345            }
346
347            for (offset, candidate) in data.windows(pattern_bytes.len()).enumerate() {
348                if candidate == pattern_bytes {
349                    output_budget.claim(pattern, candidate.len())?;
350                    reserve_match_slot(&mut matches)?;
351                    matches.push(PatternMatch {
352                        pattern: pattern.clone(),
353                        offset,
354                        length: pattern_bytes.len(),
355                        data: candidate.to_vec(),
356                        confidence: 1.0,
357                    });
358
359                    if matches.len() >= match_limit {
360                        break;
361                    }
362                }
363            }
364        } else {
365            return Err(pattern_data_mismatch(pattern, "bytes"));
366        }
367
368        Ok(matches)
369    }
370
371    /// Search for string patterns
372    fn search_string(
373        &self,
374        data: &[u8],
375        pattern: &Pattern,
376        match_limit: usize,
377        output_budget: &mut MatchOutputBudget,
378    ) -> Result<Vec<PatternMatch>> {
379        let mut matches = Vec::new();
380
381        if let PatternData::String(pattern_str) = &pattern.data {
382            let pattern_bytes = pattern_str.as_bytes();
383            if pattern_bytes.is_empty() || pattern_bytes.len() < self.config.min_pattern_length {
384                return Ok(matches);
385            }
386
387            for (offset, candidate) in data.windows(pattern_bytes.len()).enumerate() {
388                let matched = if self.config.case_sensitive {
389                    candidate == pattern_bytes
390                } else {
391                    candidate.eq_ignore_ascii_case(pattern_bytes)
392                };
393
394                if matched {
395                    output_budget.claim(pattern, candidate.len())?;
396                    reserve_match_slot(&mut matches)?;
397                    matches.push(PatternMatch {
398                        pattern: pattern.clone(),
399                        offset,
400                        length: pattern_bytes.len(),
401                        data: candidate.to_vec(),
402                        confidence: 1.0,
403                    });
404
405                    if matches.len() >= match_limit {
406                        break;
407                    }
408                }
409            }
410        } else {
411            return Err(pattern_data_mismatch(pattern, "string"));
412        }
413
414        Ok(matches)
415    }
416
417    /// Search for hex patterns with wildcards
418    fn search_hex_wildcard(
419        &self,
420        data: &[u8],
421        pattern: &Pattern,
422        match_limit: usize,
423        output_budget: &mut MatchOutputBudget,
424    ) -> Result<Vec<PatternMatch>> {
425        let mut matches = Vec::new();
426
427        if let PatternData::HexWildcard(hex_pattern) = &pattern.data {
428            if !self.config.enable_wildcards {
429                return Ok(matches);
430            }
431
432            let compiled_pattern = compile_hex_wildcard(hex_pattern)?;
433            if compiled_pattern.is_empty()
434                || compiled_pattern.len() < self.config.min_pattern_length
435            {
436                return Ok(matches);
437            }
438
439            for (start, candidate) in data.windows(compiled_pattern.len()).enumerate() {
440                if hex_wildcard_matches(candidate, &compiled_pattern) {
441                    output_budget.claim(pattern, candidate.len())?;
442                    reserve_match_slot(&mut matches)?;
443                    matches.push(PatternMatch {
444                        pattern: pattern.clone(),
445                        offset: start,
446                        length: compiled_pattern.len(),
447                        data: candidate.to_vec(),
448                        confidence: 1.0,
449                    });
450
451                    if matches.len() >= match_limit {
452                        break;
453                    }
454                }
455            }
456        } else {
457            return Err(pattern_data_mismatch(pattern, "hex wildcard"));
458        }
459
460        Ok(matches)
461    }
462
463    /// Search for magic signatures
464    fn search_magic(
465        &self,
466        data: &[u8],
467        pattern: &Pattern,
468        match_limit: usize,
469        output_budget: &mut MatchOutputBudget,
470    ) -> Result<Vec<PatternMatch>> {
471        // Magic signatures are typically at the beginning of files
472        let mut matches = Vec::new();
473
474        if let PatternData::Bytes(magic_bytes) = &pattern.data {
475            if match_limit > 0 && !magic_bytes.is_empty() && data.starts_with(magic_bytes) {
476                output_budget.claim(pattern, magic_bytes.len())?;
477                reserve_match_slot(&mut matches)?;
478                matches.push(PatternMatch {
479                    pattern: pattern.clone(),
480                    offset: 0,
481                    length: magic_bytes.len(),
482                    data: magic_bytes.clone(),
483                    confidence: 1.0,
484                });
485            }
486        } else {
487            return Err(pattern_data_mismatch(pattern, "magic bytes"));
488        }
489
490        Ok(matches)
491    }
492
493    /// Search using regular expressions
494    fn search_regex(&self, _data: &[u8], _pattern: &Pattern) -> Result<Vec<PatternMatch>> {
495        Err(BinaryError::feature_not_available("regex pattern matching"))
496    }
497
498    /// Search for structural patterns
499    fn search_structural(&self, _data: &[u8], _pattern: &Pattern) -> Result<Vec<PatternMatch>> {
500        Err(BinaryError::feature_not_available(
501            "structural pattern matching",
502        ))
503    }
504}
505
506fn pattern_data_mismatch(pattern: &Pattern, expected: &str) -> BinaryError {
507    BinaryError::invalid_data(format!(
508        "pattern '{}' requires {expected} data for {:?} matching",
509        pattern.name, pattern.pattern_type
510    ))
511}
512
513/// Compile hex wildcard pattern
514fn compile_hex_wildcard(pattern: &str) -> crate::types::HexPatternResult {
515    let clean_pattern: String = pattern
516        .chars()
517        .filter(|ch| !ch.is_ascii_whitespace())
518        .collect();
519
520    if !clean_pattern.is_ascii() {
521        return Err(BinaryError::invalid_data(
522            "Hex pattern must contain only ASCII hexadecimal bytes, wildcards, and whitespace",
523        ));
524    }
525
526    if !clean_pattern.len().is_multiple_of(2) {
527        return Err(BinaryError::invalid_data(
528            "Hex pattern must have even length",
529        ));
530    }
531
532    let mut compiled = Vec::with_capacity(clean_pattern.len() / 2);
533    for pair in clean_pattern.as_bytes().chunks_exact(2) {
534        if pair == b"??" {
535            compiled.push(None); // Wildcard
536        } else {
537            let hex_byte = std::str::from_utf8(pair)
538                .map_err(|_| BinaryError::invalid_data("Hex pattern contains invalid UTF-8"))?;
539            let byte_value = u8::from_str_radix(hex_byte, 16)
540                .map_err(|_| BinaryError::invalid_data(format!("Invalid hex byte: {hex_byte}")))?;
541            compiled.push(Some(byte_value));
542        }
543    }
544
545    Ok(compiled)
546}
547
548/// Check if data matches hex wildcard pattern
549fn hex_wildcard_matches(data: &[u8], pattern: &crate::types::HexPattern) -> bool {
550    if data.len() != pattern.len() {
551        return false;
552    }
553
554    for (i, &byte) in data.iter().enumerate() {
555        match pattern[i] {
556            Some(expected) if expected != byte => return false,
557            None => continue, // Wildcard matches anything
558            _ => continue,
559        }
560    }
561
562    true
563}
564
565/// Get built-in patterns for a category
566fn get_builtin_patterns(category: &PatternCategory) -> Vec<Pattern> {
567    match category {
568        PatternCategory::FileFormat => get_file_format_patterns(),
569        PatternCategory::Compiler => get_compiler_patterns(),
570        PatternCategory::Packer => get_packer_patterns(),
571        PatternCategory::Crypto => get_crypto_patterns(),
572        PatternCategory::Malware => get_malware_patterns(),
573        PatternCategory::Api => get_api_patterns(),
574        _ => Vec::new(),
575    }
576}
577
578/// File format signature patterns
579fn get_file_format_patterns() -> Vec<Pattern> {
580    vec![
581        Pattern {
582            name: "PE_MZ".to_string(),
583            pattern_type: PatternType::Magic,
584            data: PatternData::Bytes(b"MZ".to_vec()),
585            category: PatternCategory::FileFormat,
586            description: "DOS/PE executable signature".to_string(),
587        },
588        Pattern {
589            name: "ELF".to_string(),
590            pattern_type: PatternType::Magic,
591            data: PatternData::Bytes(b"\x7fELF".to_vec()),
592            category: PatternCategory::FileFormat,
593            description: "ELF executable signature".to_string(),
594        },
595        Pattern {
596            name: "Mach_O_32".to_string(),
597            pattern_type: PatternType::Magic,
598            data: PatternData::Bytes(vec![0xfe, 0xed, 0xfa, 0xce]),
599            category: PatternCategory::FileFormat,
600            description: "Mach-O 32-bit signature".to_string(),
601        },
602        Pattern {
603            name: "Mach_O_64".to_string(),
604            pattern_type: PatternType::Magic,
605            data: PatternData::Bytes(vec![0xfe, 0xed, 0xfa, 0xcf]),
606            category: PatternCategory::FileFormat,
607            description: "Mach-O 64-bit signature".to_string(),
608        },
609    ]
610}
611
612/// Compiler signature patterns
613fn get_compiler_patterns() -> Vec<Pattern> {
614    vec![
615        Pattern {
616            name: "GCC".to_string(),
617            pattern_type: PatternType::String,
618            data: PatternData::String("GCC:".to_string()),
619            category: PatternCategory::Compiler,
620            description: "GCC compiler signature".to_string(),
621        },
622        Pattern {
623            name: "MSVC".to_string(),
624            pattern_type: PatternType::String,
625            data: PatternData::String("Microsoft C/C++".to_string()),
626            category: PatternCategory::Compiler,
627            description: "Microsoft Visual C++ signature".to_string(),
628        },
629    ]
630}
631
632/// Packer signature patterns
633fn get_packer_patterns() -> Vec<Pattern> {
634    vec![Pattern {
635        name: "UPX".to_string(),
636        pattern_type: PatternType::String,
637        data: PatternData::String("UPX!".to_string()),
638        category: PatternCategory::Packer,
639        description: "UPX packer signature".to_string(),
640    }]
641}
642
643/// Cryptographic constants patterns
644fn get_crypto_patterns() -> Vec<Pattern> {
645    vec![Pattern {
646        name: "MD5_Init".to_string(),
647        pattern_type: PatternType::Bytes,
648        data: PatternData::Bytes(vec![0x01, 0x23, 0x45, 0x67]), // MD5 initial value
649        category: PatternCategory::Crypto,
650        description: "MD5 initialization constants".to_string(),
651    }]
652}
653
654/// Malware signature patterns
655fn get_malware_patterns() -> Vec<Pattern> {
656    vec![Pattern {
657        name: "Suspicious_API".to_string(),
658        pattern_type: PatternType::String,
659        data: PatternData::String("VirtualAllocEx".to_string()),
660        category: PatternCategory::Malware,
661        description: "Suspicious Windows API call".to_string(),
662    }]
663}
664
665/// API string patterns
666fn get_api_patterns() -> Vec<Pattern> {
667    vec![Pattern {
668        name: "CreateProcess".to_string(),
669        pattern_type: PatternType::String,
670        data: PatternData::String("CreateProcessA".to_string()),
671        category: PatternCategory::Api,
672        description: "Windows CreateProcess API".to_string(),
673    }]
674}
675
676#[cfg(test)]
677mod tests {
678    use super::*;
679
680    // ==============================
681    // Pattern Matcher Creation Tests
682    // ==============================
683
684    #[test]
685    fn test_pattern_matcher_creation() {
686        let matcher = PatternMatcher::new();
687        assert_eq!(matcher.patterns.len(), 0);
688        assert!(matcher.config.case_sensitive);
689        assert_eq!(matcher.config.max_matches, 1000);
690        assert_eq!(
691            matcher.config.max_output_bytes,
692            DEFAULT_MAX_MATCH_OUTPUT_BYTES
693        );
694        assert!(matcher.config.enable_wildcards);
695        assert_eq!(matcher.config.min_pattern_length, 3);
696    }
697
698    #[test]
699    fn test_pattern_matcher_default() {
700        let matcher = PatternMatcher::default();
701        assert_eq!(matcher.patterns.len(), 0);
702    }
703
704    #[test]
705    fn test_pattern_matcher_with_config() {
706        let config = MatchConfig {
707            case_sensitive: false,
708            max_matches: 500,
709            max_output_bytes: 1024,
710            enable_wildcards: false,
711            min_pattern_length: 5,
712        };
713        let matcher = PatternMatcher::with_config(config.clone());
714        assert!(!matcher.config.case_sensitive);
715        assert_eq!(matcher.config.max_matches, 500);
716        assert_eq!(matcher.config.max_output_bytes, 1024);
717        assert!(!matcher.config.enable_wildcards);
718        assert_eq!(matcher.config.min_pattern_length, 5);
719    }
720
721    #[test]
722    fn test_match_config_default() {
723        let config = MatchConfig::default();
724        assert!(config.case_sensitive);
725        assert_eq!(config.max_matches, 1000);
726        assert_eq!(config.max_output_bytes, DEFAULT_MAX_MATCH_OUTPUT_BYTES);
727        assert!(config.enable_wildcards);
728        assert_eq!(config.min_pattern_length, 3);
729    }
730
731    // ==============================
732    // Pattern Addition Tests
733    // ==============================
734
735    #[test]
736    fn test_add_single_pattern() {
737        let mut matcher = PatternMatcher::new();
738        let pattern = create_test_pattern(
739            "test",
740            PatternType::Bytes,
741            PatternData::Bytes(b"test".to_vec()),
742        );
743
744        matcher.add_pattern(pattern);
745        assert_eq!(matcher.patterns.len(), 1);
746        assert_eq!(matcher.patterns[0].name, "test");
747    }
748
749    #[test]
750    fn test_add_multiple_patterns() {
751        let mut matcher = PatternMatcher::new();
752        let patterns = vec![
753            create_test_pattern(
754                "test1",
755                PatternType::Bytes,
756                PatternData::Bytes(b"test1".to_vec()),
757            ),
758            create_test_pattern(
759                "test2",
760                PatternType::String,
761                PatternData::String("test2".to_string()),
762            ),
763        ];
764
765        matcher.add_patterns(patterns);
766        assert_eq!(matcher.patterns.len(), 2);
767    }
768
769    // ==============================
770    // Hex Wildcard Compilation Tests
771    // ==============================
772
773    #[test]
774    fn test_hex_wildcard_compilation() {
775        let pattern = "48 65 ?? 6c 6f";
776        let compiled = compile_hex_wildcard(pattern).unwrap();
777
778        assert_eq!(compiled.len(), 5);
779        assert_eq!(compiled[0], Some(0x48));
780        assert_eq!(compiled[1], Some(0x65));
781        assert_eq!(compiled[2], None);
782        assert_eq!(compiled[3], Some(0x6c));
783        assert_eq!(compiled[4], Some(0x6f));
784    }
785
786    #[test]
787    fn test_hex_wildcard_compilation_no_spaces() {
788        let pattern = "48656c6f";
789        let compiled = compile_hex_wildcard(pattern).unwrap();
790
791        assert_eq!(compiled.len(), 4);
792        assert_eq!(compiled[0], Some(0x48));
793        assert_eq!(compiled[1], Some(0x65));
794        assert_eq!(compiled[2], Some(0x6c));
795        assert_eq!(compiled[3], Some(0x6f));
796    }
797
798    #[test]
799    fn test_hex_wildcard_compilation_with_newlines() {
800        let pattern = "48 65\n?? 6c\n6f";
801        let compiled = compile_hex_wildcard(pattern).unwrap();
802
803        assert_eq!(compiled.len(), 5);
804        assert_eq!(compiled[2], None);
805    }
806
807    #[test]
808    fn test_hex_wildcard_compilation_error_odd_length() {
809        let pattern = "48 65 6";
810        let result = compile_hex_wildcard(pattern);
811        assert!(result.is_err());
812    }
813
814    #[test]
815    fn test_hex_wildcard_compilation_error_invalid_hex() {
816        let pattern = "48 65 XY 6c";
817        let result = compile_hex_wildcard(pattern);
818        assert!(result.is_err());
819    }
820
821    #[test]
822    fn test_hex_wildcard_compilation_rejects_non_ascii_without_panicking() {
823        let result = compile_hex_wildcard("€?");
824        assert!(result.is_err());
825    }
826
827    #[test]
828    fn test_hex_wildcard_compilation_all_wildcards() {
829        let pattern = "?? ?? ??";
830        let compiled = compile_hex_wildcard(pattern).unwrap();
831
832        assert_eq!(compiled.len(), 3);
833        assert_eq!(compiled[0], None);
834        assert_eq!(compiled[1], None);
835        assert_eq!(compiled[2], None);
836    }
837
838    #[test]
839    fn test_hex_wildcard_matching() {
840        let data = &[0x48, 0x65, 0x78, 0x6c, 0x6f]; // "Hexlo"
841        let pattern = vec![Some(0x48), Some(0x65), None, Some(0x6c), Some(0x6f)];
842
843        assert!(hex_wildcard_matches(data, &pattern));
844
845        let wrong_pattern = vec![Some(0x48), Some(0x65), None, Some(0x6c), Some(0x70)];
846        assert!(!hex_wildcard_matches(data, &wrong_pattern));
847    }
848
849    #[test]
850    fn test_hex_wildcard_matching_length_mismatch() {
851        let data = &[0x48, 0x65, 0x78];
852        let pattern = vec![Some(0x48), Some(0x65), None, Some(0x6c)];
853
854        assert!(!hex_wildcard_matches(data, &pattern));
855    }
856
857    #[test]
858    fn test_hex_wildcard_matching_empty() {
859        let data = &[];
860        let pattern = vec![];
861
862        assert!(hex_wildcard_matches(data, &pattern));
863    }
864
865    // ==============================
866    // Byte Pattern Search Tests
867    // ==============================
868
869    #[test]
870    fn test_byte_pattern_search() {
871        let mut matcher = PatternMatcher::new();
872
873        let pattern = Pattern {
874            name: "test".to_string(),
875            pattern_type: PatternType::Bytes,
876            data: PatternData::Bytes(b"hello".to_vec()),
877            category: PatternCategory::Custom,
878            description: "Test pattern".to_string(),
879        };
880
881        matcher.add_pattern(pattern);
882
883        let data = b"This is a hello world test";
884        let results = matcher.search(data).unwrap();
885
886        assert_eq!(results.matches.len(), 1);
887        assert_eq!(results.matches[0].offset, 10);
888        assert_eq!(results.matches[0].length, 5);
889        assert_eq!(results.matches[0].data, b"hello");
890        assert_eq!(results.matches[0].confidence, 1.0);
891    }
892
893    #[test]
894    fn test_byte_pattern_search_multiple_matches() {
895        let mut matcher = PatternMatcher::new();
896        let pattern = create_test_pattern(
897            "test",
898            PatternType::Bytes,
899            PatternData::Bytes(b"abc".to_vec()),
900        );
901        matcher.add_pattern(pattern);
902
903        let data = b"abcabcabc";
904        let results = matcher.search(data).unwrap();
905
906        assert_eq!(results.matches.len(), 3); // Non-overlapping matches
907    }
908
909    #[test]
910    fn test_byte_pattern_search_overlapping_matches() {
911        let mut matcher = PatternMatcher::new();
912        let pattern = create_test_pattern(
913            "test",
914            PatternType::Bytes,
915            PatternData::Bytes(b"aaa".to_vec()),
916        );
917        matcher.add_pattern(pattern);
918
919        let data = b"aaaaa";
920        let results = matcher.search(data).unwrap();
921
922        assert_eq!(results.matches.len(), 3); // Overlapping matches at positions 0, 1, 2
923    }
924
925    #[test]
926    fn test_byte_pattern_search_no_match() {
927        let mut matcher = PatternMatcher::new();
928        let pattern = create_test_pattern(
929            "test",
930            PatternType::Bytes,
931            PatternData::Bytes(b"xyz".to_vec()),
932        );
933        matcher.add_pattern(pattern);
934
935        let data = b"hello world";
936        let results = matcher.search(data).unwrap();
937
938        assert_eq!(results.matches.len(), 0);
939    }
940
941    #[test]
942    fn test_byte_pattern_search_too_short() {
943        let mut matcher = PatternMatcher::new();
944        let pattern = create_test_pattern(
945            "test",
946            PatternType::Bytes,
947            PatternData::Bytes(b"ab".to_vec()),
948        );
949        matcher.add_pattern(pattern);
950
951        let data = b"hello world";
952        let results = matcher.search(data).unwrap();
953
954        assert_eq!(results.matches.len(), 0); // Pattern too short (< min_pattern_length)
955    }
956
957    #[test]
958    fn test_byte_pattern_search_max_matches_limit() {
959        let config = MatchConfig {
960            max_matches: 2,
961            ..Default::default()
962        };
963        let mut matcher = PatternMatcher::with_config(config);
964        let pattern = create_test_pattern(
965            "test",
966            PatternType::Bytes,
967            PatternData::Bytes(b"test".to_vec()),
968        );
969        matcher.add_pattern(pattern);
970
971        let data = b"test test test test";
972        let results = matcher.search(data).unwrap();
973
974        assert_eq!(results.matches.len(), 2); // Limited by max_matches
975    }
976
977    #[test]
978    fn repeated_large_pattern_fails_before_amplifying_owned_output() {
979        const PATTERN_SIZE: usize = 64 * 1024;
980        let config = MatchConfig {
981            max_matches: 1_000,
982            max_output_bytes: 1024 * 1024,
983            ..Default::default()
984        };
985        let mut matcher = PatternMatcher::with_config(config);
986        matcher.add_pattern(create_test_pattern(
987            "large-repeated-pattern",
988            PatternType::Bytes,
989            PatternData::Bytes(vec![b'A'; PATTERN_SIZE]),
990        ));
991        let data = vec![b'A'; PATTERN_SIZE + 16];
992
993        let error = matcher.search(&data).unwrap_err();
994
995        assert!(error.to_string().contains("pattern-match output would use"));
996        assert!(error.to_string().contains("1048576-byte limit"));
997    }
998
999    #[test]
1000    fn test_zero_max_matches_returns_no_matches() {
1001        let config = MatchConfig {
1002            max_matches: 0,
1003            min_pattern_length: 0,
1004            ..Default::default()
1005        };
1006        let mut matcher = PatternMatcher::with_config(config);
1007        matcher.add_pattern(create_test_pattern(
1008            "test",
1009            PatternType::Bytes,
1010            PatternData::Bytes(b"test".to_vec()),
1011        ));
1012
1013        let results = matcher.search(b"test").unwrap();
1014        assert!(results.matches.is_empty());
1015        assert!(results.by_category.is_empty());
1016    }
1017
1018    #[test]
1019    fn test_empty_byte_pattern_does_not_panic_or_match() {
1020        let config = MatchConfig {
1021            min_pattern_length: 0,
1022            ..Default::default()
1023        };
1024        let mut matcher = PatternMatcher::with_config(config);
1025        matcher.add_pattern(create_test_pattern(
1026            "empty",
1027            PatternType::Bytes,
1028            PatternData::Bytes(Vec::new()),
1029        ));
1030
1031        assert!(matcher.search(b"data").unwrap().matches.is_empty());
1032    }
1033
1034    // ==============================
1035    // String Pattern Search Tests
1036    // ==============================
1037
1038    #[test]
1039    fn test_string_pattern_search_case_sensitive() {
1040        let mut matcher = PatternMatcher::new();
1041        let pattern = create_test_pattern(
1042            "test",
1043            PatternType::String,
1044            PatternData::String("Hello".to_string()),
1045        );
1046        matcher.add_pattern(pattern);
1047
1048        let data = b"Say Hello to the world";
1049        let results = matcher.search(data).unwrap();
1050
1051        assert_eq!(results.matches.len(), 1);
1052        assert_eq!(results.matches[0].offset, 4);
1053    }
1054
1055    #[test]
1056    fn test_string_pattern_search_case_insensitive() {
1057        let config = MatchConfig {
1058            case_sensitive: false,
1059            ..Default::default()
1060        };
1061        let mut matcher = PatternMatcher::with_config(config);
1062        let pattern = create_test_pattern(
1063            "test",
1064            PatternType::String,
1065            PatternData::String("HELLO".to_string()),
1066        );
1067        matcher.add_pattern(pattern);
1068
1069        let data = b"Say hello to the world";
1070        let results = matcher.search(data).unwrap();
1071
1072        assert_eq!(results.matches.len(), 1);
1073        assert_eq!(results.matches[0].offset, 4);
1074    }
1075
1076    #[test]
1077    fn test_case_insensitive_offsets_stay_on_original_bytes() {
1078        let config = MatchConfig {
1079            case_sensitive: false,
1080            min_pattern_length: 1,
1081            ..Default::default()
1082        };
1083        let mut matcher = PatternMatcher::with_config(config);
1084        matcher.add_pattern(create_test_pattern(
1085            "ascii-suffix",
1086            PatternType::String,
1087            PatternData::String("a".to_string()),
1088        ));
1089
1090        // Unicode lowercase expansion used to shift the search offset and then
1091        // panic while slicing the original byte string.
1092        let results = matcher.search("İA".as_bytes()).unwrap();
1093        assert_eq!(results.matches.len(), 1);
1094        assert_eq!(results.matches[0].offset, 2);
1095        assert_eq!(results.matches[0].data, b"A");
1096    }
1097
1098    #[test]
1099    fn test_string_pattern_search_invalid_utf8() {
1100        let mut matcher = PatternMatcher::new();
1101        let pattern = create_test_pattern(
1102            "test",
1103            PatternType::String,
1104            PatternData::String("test".to_string()),
1105        );
1106        matcher.add_pattern(pattern);
1107
1108        let data = &[0xFF, 0xFE, 0xFD, 0xFC]; // Invalid UTF-8
1109        let results = matcher.search(data).unwrap();
1110
1111        assert_eq!(results.matches.len(), 0); // No matches for invalid UTF-8
1112    }
1113
1114    #[test]
1115    fn test_string_pattern_search_too_short() {
1116        let mut matcher = PatternMatcher::new();
1117        let pattern = create_test_pattern(
1118            "test",
1119            PatternType::String,
1120            PatternData::String("ab".to_string()),
1121        );
1122        matcher.add_pattern(pattern);
1123
1124        let data = b"hello ab world";
1125        let results = matcher.search(data).unwrap();
1126
1127        assert_eq!(results.matches.len(), 0); // Pattern too short
1128    }
1129
1130    // ==============================
1131    // Hex Wildcard Pattern Search Tests
1132    // ==============================
1133
1134    #[test]
1135    fn test_hex_wildcard_pattern_search() {
1136        let mut matcher = PatternMatcher::new();
1137        let pattern = create_test_pattern(
1138            "test",
1139            PatternType::HexWildcard,
1140            PatternData::HexWildcard("48 65 ?? 6c 6f".to_string()),
1141        );
1142        matcher.add_pattern(pattern);
1143
1144        let data = b"Hello"; // 48 65 6c 6c 6f
1145        let results = matcher.search(data).unwrap();
1146
1147        assert_eq!(results.matches.len(), 1);
1148        assert_eq!(results.matches[0].offset, 0);
1149        assert_eq!(results.matches[0].length, 5);
1150    }
1151
1152    #[test]
1153    fn test_hex_wildcard_pattern_search_invalid_pattern() {
1154        let mut matcher = PatternMatcher::new();
1155        let pattern = create_test_pattern(
1156            "test",
1157            PatternType::HexWildcard,
1158            PatternData::HexWildcard("48 65 X".to_string()),
1159        );
1160        matcher.add_pattern(pattern);
1161
1162        let data = b"Hello";
1163        let results = matcher.search(data);
1164
1165        assert!(results.is_err()); // Invalid hex pattern should error
1166    }
1167
1168    #[test]
1169    fn test_hex_wildcard_search_respects_disabled_config() {
1170        let config = MatchConfig {
1171            enable_wildcards: false,
1172            ..Default::default()
1173        };
1174        let mut matcher = PatternMatcher::with_config(config);
1175        matcher.add_pattern(create_test_pattern(
1176            "disabled",
1177            PatternType::HexWildcard,
1178            PatternData::HexWildcard("48 65 ?? 6c 6f".to_string()),
1179        ));
1180
1181        assert!(matcher.search(b"Hello").unwrap().matches.is_empty());
1182    }
1183
1184    #[test]
1185    fn test_pattern_type_and_data_mismatch_is_an_error() {
1186        let mut matcher = PatternMatcher::new();
1187        matcher.add_pattern(create_test_pattern(
1188            "mismatch",
1189            PatternType::Bytes,
1190            PatternData::String("bytes".to_string()),
1191        ));
1192
1193        let error = matcher.search(b"bytes").unwrap_err();
1194        assert!(error.to_string().contains("requires bytes data"));
1195    }
1196
1197    // ==============================
1198    // Magic Pattern Search Tests
1199    // ==============================
1200
1201    #[test]
1202    fn test_magic_pattern_search_match() {
1203        let mut matcher = PatternMatcher::new();
1204        let pattern =
1205            create_test_pattern("PE", PatternType::Magic, PatternData::Bytes(b"MZ".to_vec()));
1206        matcher.add_pattern(pattern);
1207
1208        let data = b"MZ\x90\x00\x03\x00"; // PE header start
1209        let results = matcher.search(data).unwrap();
1210
1211        assert_eq!(results.matches.len(), 1);
1212        assert_eq!(results.matches[0].offset, 0);
1213        assert_eq!(results.matches[0].length, 2);
1214    }
1215
1216    #[test]
1217    fn test_magic_pattern_search_no_match_wrong_position() {
1218        let mut matcher = PatternMatcher::new();
1219        let pattern =
1220            create_test_pattern("PE", PatternType::Magic, PatternData::Bytes(b"MZ".to_vec()));
1221        matcher.add_pattern(pattern);
1222
1223        let data = b"XXMZ"; // Magic not at beginning
1224        let results = matcher.search(data).unwrap();
1225
1226        assert_eq!(results.matches.len(), 0); // Magic patterns only match at offset 0
1227    }
1228
1229    #[test]
1230    fn test_magic_pattern_search_too_short() {
1231        let mut matcher = PatternMatcher::new();
1232        let pattern =
1233            create_test_pattern("PE", PatternType::Magic, PatternData::Bytes(b"MZ".to_vec()));
1234        matcher.add_pattern(pattern);
1235
1236        let data = b"M"; // Too short
1237        let results = matcher.search(data).unwrap();
1238
1239        assert_eq!(results.matches.len(), 0);
1240    }
1241
1242    // ==============================
1243    // Regex and Structural Pattern Tests
1244    // ==============================
1245
1246    #[test]
1247    fn test_regex_pattern_search_reports_unavailable_feature() {
1248        let mut matcher = PatternMatcher::new();
1249        let pattern = create_test_pattern(
1250            "test",
1251            PatternType::Regex,
1252            PatternData::Regex("test.*".to_string()),
1253        );
1254        matcher.add_pattern(pattern);
1255
1256        let data = b"test pattern";
1257        let error = matcher.search(data).unwrap_err();
1258        assert!(error.to_string().contains("regex pattern matching"));
1259    }
1260
1261    #[test]
1262    fn test_structural_pattern_search_reports_unavailable_feature() {
1263        let mut matcher = PatternMatcher::new();
1264        let pattern = create_test_pattern(
1265            "test",
1266            PatternType::Structural,
1267            PatternData::Bytes(b"test".to_vec()),
1268        );
1269        matcher.add_pattern(pattern);
1270
1271        let data = b"test pattern";
1272        let error = matcher.search(data).unwrap_err();
1273        assert!(error.to_string().contains("structural pattern matching"));
1274    }
1275
1276    // ==============================
1277    // Built-in Pattern Tests
1278    // ==============================
1279
1280    #[test]
1281    fn test_builtin_patterns() {
1282        let patterns = get_file_format_patterns();
1283        assert!(!patterns.is_empty());
1284
1285        // Check for PE signature
1286        let pe_pattern = patterns.iter().find(|p| p.name == "PE_MZ");
1287        assert!(pe_pattern.is_some());
1288    }
1289
1290    #[test]
1291    fn test_file_format_patterns() {
1292        let patterns = get_file_format_patterns();
1293        assert!(patterns.len() >= 4);
1294
1295        let names: Vec<&str> = patterns.iter().map(|p| p.name.as_str()).collect();
1296        assert!(names.contains(&"PE_MZ"));
1297        assert!(names.contains(&"ELF"));
1298        assert!(names.contains(&"Mach_O_32"));
1299        assert!(names.contains(&"Mach_O_64"));
1300    }
1301
1302    #[test]
1303    fn test_compiler_patterns() {
1304        let patterns = get_compiler_patterns();
1305        assert!(!patterns.is_empty());
1306
1307        let names: Vec<&str> = patterns.iter().map(|p| p.name.as_str()).collect();
1308        assert!(names.contains(&"GCC"));
1309        assert!(names.contains(&"MSVC"));
1310    }
1311
1312    #[test]
1313    fn test_packer_patterns() {
1314        let patterns = get_packer_patterns();
1315        assert!(!patterns.is_empty());
1316
1317        let names: Vec<&str> = patterns.iter().map(|p| p.name.as_str()).collect();
1318        assert!(names.contains(&"UPX"));
1319    }
1320
1321    #[test]
1322    fn test_crypto_patterns() {
1323        let patterns = get_crypto_patterns();
1324        assert!(!patterns.is_empty());
1325
1326        let names: Vec<&str> = patterns.iter().map(|p| p.name.as_str()).collect();
1327        assert!(names.contains(&"MD5_Init"));
1328    }
1329
1330    #[test]
1331    fn test_malware_patterns() {
1332        let patterns = get_malware_patterns();
1333        assert!(!patterns.is_empty());
1334
1335        let names: Vec<&str> = patterns.iter().map(|p| p.name.as_str()).collect();
1336        assert!(names.contains(&"Suspicious_API"));
1337    }
1338
1339    #[test]
1340    fn test_api_patterns() {
1341        let patterns = get_api_patterns();
1342        assert!(!patterns.is_empty());
1343
1344        let names: Vec<&str> = patterns.iter().map(|p| p.name.as_str()).collect();
1345        assert!(names.contains(&"CreateProcess"));
1346    }
1347
1348    #[test]
1349    fn test_get_builtin_patterns_unknown_category() {
1350        let patterns = get_builtin_patterns(&PatternCategory::Debug);
1351        assert!(patterns.is_empty()); // Debug not implemented
1352
1353        let patterns = get_builtin_patterns(&PatternCategory::Network);
1354        assert!(patterns.is_empty()); // Network not implemented
1355    }
1356
1357    #[test]
1358    fn test_load_builtin_patterns() {
1359        let mut matcher = PatternMatcher::new();
1360        let categories = vec![
1361            PatternCategory::FileFormat,
1362            PatternCategory::Compiler,
1363            PatternCategory::Packer,
1364        ];
1365
1366        matcher.load_builtin_patterns(&categories);
1367        assert!(!matcher.patterns.is_empty());
1368
1369        // Verify patterns from all categories are loaded
1370        let format_count = matcher
1371            .patterns
1372            .iter()
1373            .filter(|p| p.category == PatternCategory::FileFormat)
1374            .count();
1375        let compiler_count = matcher
1376            .patterns
1377            .iter()
1378            .filter(|p| p.category == PatternCategory::Compiler)
1379            .count();
1380        let packer_count = matcher
1381            .patterns
1382            .iter()
1383            .filter(|p| p.category == PatternCategory::Packer)
1384            .count();
1385
1386        assert!(format_count > 0);
1387        assert!(compiler_count > 0);
1388        assert!(packer_count > 0);
1389    }
1390
1391    // ==============================
1392    // Search Results Tests
1393    // ==============================
1394
1395    #[test]
1396    fn test_search_results_structure() {
1397        let mut matcher = PatternMatcher::new();
1398        let pattern1 = create_test_pattern(
1399            "test1",
1400            PatternType::Bytes,
1401            PatternData::Bytes(b"test".to_vec()),
1402        );
1403        let pattern2 = create_test_pattern(
1404            "test2",
1405            PatternType::String,
1406            PatternData::String("hello".to_string()),
1407        );
1408
1409        matcher.add_pattern(pattern1);
1410        matcher.add_pattern(pattern2);
1411
1412        let data = b"This is a test and hello world";
1413        let results = matcher.search(data).unwrap();
1414
1415        assert_eq!(results.matches.len(), 2);
1416        assert_eq!(results.bytes_searched, data.len());
1417        assert!(results.duration_ms < 10000); // Should complete quickly in tests
1418        assert_eq!(results.by_category.len(), 1); // Both patterns are Custom category
1419        assert_eq!(results.by_category[&PatternCategory::Custom].len(), 2);
1420    }
1421
1422    #[test]
1423    fn test_search_results_empty() {
1424        let matcher = PatternMatcher::new();
1425        let data = b"test data";
1426        let results = matcher.search(data).unwrap();
1427
1428        assert_eq!(results.matches.len(), 0);
1429        assert_eq!(results.bytes_searched, data.len());
1430        assert!(results.by_category.is_empty());
1431    }
1432
1433    #[test]
1434    fn test_search_results_category_grouping() {
1435        let mut matcher = PatternMatcher::new();
1436        let pattern1 = Pattern {
1437            name: "pe".to_string(),
1438            pattern_type: PatternType::Magic,
1439            data: PatternData::Bytes(b"MZ".to_vec()),
1440            category: PatternCategory::FileFormat,
1441            description: "PE header".to_string(),
1442        };
1443        let pattern2 = Pattern {
1444            name: "gcc".to_string(),
1445            pattern_type: PatternType::String,
1446            data: PatternData::String("GCC".to_string()),
1447            category: PatternCategory::Compiler,
1448            description: "GCC compiler".to_string(),
1449        };
1450
1451        matcher.add_pattern(pattern1);
1452        matcher.add_pattern(pattern2);
1453
1454        let data = b"MZ This binary was compiled with GCC";
1455        let results = matcher.search(data).unwrap();
1456
1457        assert_eq!(results.matches.len(), 2);
1458        assert_eq!(results.by_category.len(), 2);
1459        assert!(
1460            results
1461                .by_category
1462                .contains_key(&PatternCategory::FileFormat)
1463        );
1464        assert!(results.by_category.contains_key(&PatternCategory::Compiler));
1465    }
1466
1467    // ==============================
1468    // Pattern Data Type Tests
1469    // ==============================
1470
1471    #[test]
1472    fn test_pattern_types_equality() {
1473        assert_eq!(PatternType::Bytes, PatternType::Bytes);
1474        assert_ne!(PatternType::Bytes, PatternType::String);
1475    }
1476
1477    #[test]
1478    fn test_pattern_categories_equality() {
1479        assert_eq!(PatternCategory::FileFormat, PatternCategory::FileFormat);
1480        assert_ne!(PatternCategory::FileFormat, PatternCategory::Compiler);
1481    }
1482
1483    // ==============================
1484    // Edge Cases and Error Handling Tests
1485    // ==============================
1486
1487    #[test]
1488    fn test_search_empty_data() {
1489        let mut matcher = PatternMatcher::new();
1490        let pattern = create_test_pattern(
1491            "test",
1492            PatternType::Bytes,
1493            PatternData::Bytes(b"test".to_vec()),
1494        );
1495        matcher.add_pattern(pattern);
1496
1497        let data = b"";
1498        let results = matcher.search(data).unwrap();
1499
1500        assert_eq!(results.matches.len(), 0);
1501        assert_eq!(results.bytes_searched, 0);
1502    }
1503
1504    #[test]
1505    fn test_search_large_data() {
1506        let mut matcher = PatternMatcher::new();
1507        let pattern = create_test_pattern(
1508            "test",
1509            PatternType::Bytes,
1510            PatternData::Bytes(b"needle".to_vec()),
1511        );
1512        matcher.add_pattern(pattern);
1513
1514        let mut data = vec![b'X'; 100000];
1515        data.extend_from_slice(b"needle");
1516        data.extend_from_slice(&vec![b'Y'; 100000]);
1517
1518        let results = matcher.search(&data).unwrap();
1519
1520        assert_eq!(results.matches.len(), 1);
1521        assert_eq!(results.matches[0].offset, 100000);
1522    }
1523
1524    #[test]
1525    fn test_pattern_match_structure() {
1526        let mut matcher = PatternMatcher::new();
1527        let pattern = create_test_pattern(
1528            "test",
1529            PatternType::Bytes,
1530            PatternData::Bytes(b"test".to_vec()),
1531        );
1532        matcher.add_pattern(pattern.clone());
1533
1534        let data = b"find test here";
1535        let results = matcher.search(data).unwrap();
1536
1537        assert_eq!(results.matches.len(), 1);
1538        let m = &results.matches[0];
1539        assert_eq!(m.pattern.name, pattern.name);
1540        assert_eq!(m.offset, 5);
1541        assert_eq!(m.length, 4);
1542        assert_eq!(m.data, b"test");
1543        assert_eq!(m.confidence, 1.0);
1544    }
1545
1546    #[test]
1547    fn test_multiple_pattern_types_search() {
1548        let mut matcher = PatternMatcher::new();
1549
1550        // Add different pattern types
1551        matcher.add_pattern(create_test_pattern(
1552            "bytes",
1553            PatternType::Bytes,
1554            PatternData::Bytes(b"test".to_vec()),
1555        ));
1556        matcher.add_pattern(create_test_pattern(
1557            "string",
1558            PatternType::String,
1559            PatternData::String("hello".to_string()),
1560        ));
1561        matcher.add_pattern(create_test_pattern(
1562            "magic",
1563            PatternType::Magic,
1564            PatternData::Bytes(b"MZ".to_vec()),
1565        ));
1566        matcher.add_pattern(create_test_pattern(
1567            "hex",
1568            PatternType::HexWildcard,
1569            PatternData::HexWildcard("77 6F ?? 6C 64".to_string()),
1570        ));
1571
1572        let data = b"MZ test hello world";
1573        let results = matcher.search(data).unwrap();
1574
1575        assert!(results.matches.len() >= 3); // At least bytes, string, magic should match
1576    }
1577
1578    // ==============================
1579    // Helper Functions
1580    // ==============================
1581
1582    fn create_test_pattern(name: &str, pattern_type: PatternType, data: PatternData) -> Pattern {
1583        Pattern {
1584            name: name.to_string(),
1585            pattern_type,
1586            data,
1587            category: PatternCategory::Custom,
1588            description: format!("Test pattern: {}", name),
1589        }
1590    }
1591}