1use crate::{BinaryError, Result};
9use std::collections::HashMap;
10
11pub const DEFAULT_MAX_MATCH_OUTPUT_BYTES: usize = 16 * 1024 * 1024;
13
14pub struct PatternMatcher {
16 patterns: Vec<Pattern>,
17 config: MatchConfig,
18}
19
20#[derive(Debug, Clone)]
22pub struct MatchConfig {
23 pub case_sensitive: bool,
26 pub max_matches: usize,
28 pub max_output_bytes: usize,
34 pub enable_wildcards: bool,
36 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#[derive(Debug, Clone)]
54pub struct Pattern {
55 pub name: String,
57 pub pattern_type: PatternType,
59 pub data: PatternData,
61 pub category: PatternCategory,
63 pub description: String,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum PatternType {
70 Bytes,
72 String,
74 Regex,
76 HexWildcard,
78 Magic,
80 Structural,
82}
83
84#[derive(Debug, Clone)]
86pub enum PatternData {
87 Bytes(Vec<u8>),
89 String(String),
91 HexWildcard(String),
93 Regex(String),
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Hash)]
99pub enum PatternCategory {
100 FileFormat,
102 Compiler,
104 Packer,
106 Crypto,
108 Malware,
110 Api,
112 Debug,
114 Metadata,
116 Network,
118 Custom,
120}
121
122#[derive(Debug, Clone)]
124pub struct PatternMatch {
125 pub pattern: Pattern,
127 pub offset: usize,
129 pub length: usize,
131 pub data: Vec<u8>,
133 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 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#[derive(Debug, Clone)]
201pub struct SearchResults {
202 pub matches: Vec<PatternMatch>,
204 pub by_category: crate::types::PatternMatchMap,
206 pub bytes_searched: usize,
208 pub duration_ms: u64,
210}
211
212impl Default for PatternMatcher {
213 fn default() -> Self {
214 Self::new()
215 }
216}
217
218impl PatternMatcher {
219 pub fn new() -> Self {
221 Self {
222 patterns: Vec::new(),
223 config: MatchConfig::default(),
224 }
225 }
226
227 pub fn with_config(config: MatchConfig) -> Self {
229 Self {
230 patterns: Vec::new(),
231 config,
232 }
233 }
234
235 pub fn add_pattern(&mut self, pattern: Pattern) {
237 self.patterns.push(pattern);
238 }
239
240 pub fn add_patterns(&mut self, patterns: Vec<Pattern>) {
242 self.patterns.extend(patterns);
243 }
244
245 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 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 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 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 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 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 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 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 fn search_regex(&self, _data: &[u8], _pattern: &Pattern) -> Result<Vec<PatternMatch>> {
495 Err(BinaryError::feature_not_available("regex pattern matching"))
496 }
497
498 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
513fn 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); } 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
548fn 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, _ => continue,
559 }
560 }
561
562 true
563}
564
565fn 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
578fn 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
612fn 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
632fn 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
643fn 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]), category: PatternCategory::Crypto,
650 description: "MD5 initialization constants".to_string(),
651 }]
652}
653
654fn 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
665fn 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 #[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 #[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 #[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]; 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 #[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); }
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); }
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); }
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); }
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 #[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 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]; let results = matcher.search(data).unwrap();
1110
1111 assert_eq!(results.matches.len(), 0); }
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); }
1129
1130 #[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"; 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()); }
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 #[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"; 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"; let results = matcher.search(data).unwrap();
1225
1226 assert_eq!(results.matches.len(), 0); }
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"; let results = matcher.search(data).unwrap();
1238
1239 assert_eq!(results.matches.len(), 0);
1240 }
1241
1242 #[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 #[test]
1281 fn test_builtin_patterns() {
1282 let patterns = get_file_format_patterns();
1283 assert!(!patterns.is_empty());
1284
1285 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()); let patterns = get_builtin_patterns(&PatternCategory::Network);
1354 assert!(patterns.is_empty()); }
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 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 #[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); assert_eq!(results.by_category.len(), 1); 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 #[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 #[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 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); }
1577
1578 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}