boreal/scanner/params.rs
1//! Parameters applicable to a scan.
2
3use std::time::Duration;
4
5use crate::memory::MemoryParams;
6
7/// Parameters used to configure a scan.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct ScanParams {
10 /// Compute full matches on matching rules.
11 pub(crate) compute_full_matches: bool,
12
13 /// Max length of the matches returned in matching rules.
14 pub(crate) match_max_length: usize,
15
16 /// Max number of matches for a given string.
17 pub(crate) string_max_nb_matches: u32,
18
19 /// Max duration for a scan before it is aborted.
20 pub(crate) timeout_duration: Option<Duration>,
21
22 /// Compute statistics on scanning.
23 ///
24 /// This requires the `profiling` feature.
25 pub(crate) compute_statistics: bool,
26
27 /// Scanning mode of fragmented memory.
28 pub(crate) fragmented_scan_mode: FragmentedScanMode,
29
30 /// Scanned bytes are part of a process memory.
31 pub(crate) process_memory: bool,
32
33 /// Maximum size of a fetched region.
34 pub(crate) max_fetched_region_size: usize,
35
36 /// Size of memory chunks to scan.
37 pub(crate) memory_chunk_size: Option<usize>,
38
39 /// Bitflag of which events are enabled in the scan callback.
40 pub(crate) callback_events: CallbackEvents,
41
42 /// Include not matched rules into results.
43 pub(crate) include_not_matched_rules: bool,
44}
45
46/// Scan mode to use on fragmented memory, including process scanning.
47///
48/// There are several different ways to handle how multiple
49/// disjointed memory regions are scanned. Use this parameter to
50/// change how this scanning is done.
51#[derive(PartialEq, Eq, Copy, Clone, Debug)]
52pub struct FragmentedScanMode {
53 /// Modules can parse scanned memory to generate dynamic values.
54 ///
55 /// If true, some modules (pe, elf, macho, etc) will parse
56 /// each region to generate dynamic values. For example, the pe
57 /// module will parse each region to detect which region
58 /// contains a PE header, and generate dynamic values accordingly
59 /// once found.
60 ///
61 /// Generally, these module will stop parsing regions once a
62 /// region matching their filetype is found, but their behavior
63 /// can differ.
64 ///
65 /// Enabling this parameter disables the no-scan optimization.
66 pub(crate) modules_dynamic_values: bool,
67
68 /// Regions can be fetched multiple times.
69 ///
70 /// If true, conditions that uses offsets into the scanned memory
71 /// can be evaluated, and may thus cause refetches of regions.
72 ///
73 /// If false, regions are fetched only once: to scan for strings
74 /// occurrences, as well as possibly evaluate modules dynamic
75 /// values.
76 ///
77 /// Enabling this parameter disables the no-scan optimization.
78 pub(crate) can_refetch_regions: bool,
79}
80
81impl FragmentedScanMode {
82 /// Legacy mode, i.e. same behavior as YARA.
83 ///
84 /// This mode ensures that the behavior is identical to a scan
85 /// done by libyara, and is set as the default for this reason.
86 /// However, the legacy behavior tends to actually be quite
87 /// surprising compared to initial expectations.
88 ///
89 /// In this mode:
90 /// - String scanning is done on each region, and results are
91 /// accumulated.
92 /// - File scanning modules (PE, ELF, etc) parses each region
93 /// until one region matches, then ignores the subsequent
94 /// regions.
95 /// - Conditions that depend on offsets will trigger new
96 /// fetches of data. For example, use of `uint32(offset)`
97 /// or `hash.md5sum(offset, length)` will cause a new
98 /// fetch of this data, separate from the fetch done
99 /// for string scanning. This **can** add up if many
100 /// such conditions are used, causing higher memory usage
101 /// and longer scan durations.
102 /// - The filesize condition is undefined.
103 ///
104 /// In addition, the no-scan optimization is disabled in
105 /// this mode.
106 #[must_use]
107 pub fn legacy() -> Self {
108 Self {
109 modules_dynamic_values: true,
110 can_refetch_regions: true,
111 }
112 }
113
114 /// Fast mode.
115 ///
116 /// In this mode, most of the more surprising or ill-defined
117 /// semantics of the legacy mode are updated to guarantee
118 /// a faster scan. This includes disabling additional fetches
119 /// of data as well as disabling file scanning modules.
120 ///
121 /// In this mode:
122 /// - String scanning is done on each region, and results are
123 /// accumulated.
124 /// - File scanning modules (PE, ELF, etc) do not
125 /// scan the regions, so they act as if they did not parse
126 /// a compatible file.
127 /// - Conditions that depend on offsets evaluate to undefined.
128 /// For example, use of `uint32(offset)`
129 /// or `hash.md5sum(offset, length)` will evaluate to
130 /// the undefined value.
131 /// - The filesize condition is undefined.
132 ///
133 /// The no-scan optimization is enabled in this mode.
134 #[must_use]
135 pub fn fast() -> Self {
136 Self {
137 modules_dynamic_values: false,
138 can_refetch_regions: false,
139 }
140 }
141
142 /// Single-pass mode.
143 ///
144 /// In this mode, a single pass on the regions is guaranteed,
145 /// ensuring that each region is fetched only once. This
146 /// means scanning time should scale according to both the
147 /// number of strings and the sizes of the scanned data,
148 /// without risking pathological scanning times due to some
149 /// rules triggering refetches of memory regions.
150 ///
151 /// This mode has the same semantics as the legacy mode, but
152 /// conditions depending on offsets in the scanned data will
153 /// all evaluate to undefined.
154 ///
155 /// In this mode:
156 /// - String scanning is done on each region, and results are
157 /// accumulated.
158 /// - File scanning modules (PE, ELF, etc) parses each region
159 /// until one region matches, then ignores the subsequent
160 /// regions.
161 /// - Conditions that depend on offsets evaluate to undefined.
162 /// For example, use of `uint32(offset)`
163 /// or `hash.md5sum(offset, length)` will evaluate to
164 /// the undefined value.
165 /// - The filesize condition is undefined.
166 ///
167 /// The no-scan optimization is disabled in this mode.
168 #[must_use]
169 pub fn single_pass() -> Self {
170 Self {
171 modules_dynamic_values: true,
172 can_refetch_regions: false,
173 }
174 }
175}
176
177impl Default for ScanParams {
178 fn default() -> Self {
179 Self {
180 compute_full_matches: false,
181 match_max_length: 512,
182 string_max_nb_matches: 1_000,
183 timeout_duration: None,
184 compute_statistics: false,
185 process_memory: false,
186 max_fetched_region_size: 1024 * 1024 * 1024,
187 memory_chunk_size: None,
188 fragmented_scan_mode: FragmentedScanMode::legacy(),
189 callback_events: CallbackEvents::RULE_MATCH,
190 include_not_matched_rules: false,
191 }
192 }
193}
194
195impl ScanParams {
196 /// Compute full matches on matching rules.
197 ///
198 /// By default, matching rules may not report all of the string matches:
199 ///
200 /// - a rule may match when a variable is found, without needing to find all its matches
201 /// - finding out if a regex matches is cheaper than computing the offset and length of its
202 /// matches
203 /// - etc
204 ///
205 /// Therefore, the [`crate::scanner::ScanResult`] object may not contain what a user would
206 /// expect.
207 ///
208 /// Setting this parameter to true ensures that for every matching rules, all of the
209 /// variable matches are computed and reported.
210 #[must_use]
211 pub fn compute_full_matches(mut self, compute_full_matches: bool) -> Self {
212 self.compute_full_matches = compute_full_matches;
213 self
214 }
215
216 /// Max length of the matches returned in matching rules.
217 ///
218 /// This is the max length of [`crate::scanner::StringMatch::data`].
219 ///
220 /// The default value is `512`.
221 #[must_use]
222 pub fn match_max_length(mut self, match_max_length: usize) -> Self {
223 self.match_max_length = match_max_length;
224 self
225 }
226
227 /// Max number of matches for a given string.
228 ///
229 /// Matches that would occur after this value are not reported. This means that `#a` can never
230 /// be greater than this value, and `!a[i]` or `@a[i]` where `i` is greater than this value is
231 /// always undefined.
232 ///
233 /// The default value is `1_000`.
234 #[must_use]
235 pub fn string_max_nb_matches(mut self, string_max_nb_matches: u32) -> Self {
236 self.string_max_nb_matches = string_max_nb_matches;
237 self
238 }
239
240 /// Maximum duration of a scan before it is stopped.
241 ///
242 /// If a scan lasts longer that the timeout, the scan will be stopped, and only results
243 /// computed before the timeout will be returned.
244 ///
245 /// By default, no timeout is set.
246 #[must_use]
247 pub fn timeout_duration(mut self, timeout_duration: Option<Duration>) -> Self {
248 self.timeout_duration = timeout_duration;
249 self
250 }
251
252 /// Compute statistics during scanning.
253 ///
254 /// This option allows retrieve statistics related to the scanning of bytes.
255 ///
256 /// This requires the `profiling` feature.
257 ///
258 /// Default value is false.
259 #[must_use]
260 pub fn compute_statistics(mut self, compute_statistics: bool) -> Self {
261 self.compute_statistics = compute_statistics;
262 self
263 }
264
265 /// Scanned bytes are part of the memory of a process.
266 ///
267 /// This has an impact of the behavior of some modules. For example, some file analysis
268 /// modules such as `pe` or `elf` will depend on this flag to decide whether to use the
269 /// virtual address values (if this flag is true), or the file offset values (if it is
270 /// false).
271 ///
272 /// This is always true when using the APIs to scan a process, regardless of this
273 /// parameter. It is false in other APIs, unless this parameter is set.
274 ///
275 /// One reason to use this parameter is for example to modify how a process regions
276 /// are fetched or filtered, but still rely on the same scanning behavior. The
277 /// [`crate::Scanner::scan_mem`] or [`crate::Scanner::scan_fragmented`] can then be used,
278 /// and the scan will evaluate as if [`crate::Scanner::scan_process`] was called.
279 #[must_use]
280 pub fn process_memory(mut self, process_memory: bool) -> Self {
281 self.process_memory = process_memory;
282 self
283 }
284
285 /// Maximum size of a fetched region.
286 ///
287 /// This parameter applies to fragmented memory scanning, using either the
288 /// [`crate::Scanner::scan_fragmented`] or [`crate::Scanner::scan_process`]
289 /// function.
290 ///
291 /// If a region is larger than this value, only this size will be
292 /// fetched. For example, if this value is 50MB and a region has a size
293 /// of 80MB, then only the first 50MB will be scanned, and the trailing
294 /// 30MB left will not be scanned.
295 ///
296 /// This parameter exists as a safeguard, to ensure that memory
297 /// consumption will never go above this limit. You may however prefer
298 /// tweaking the [`ScanParams::memory_chunk_size`] parameter, to bound
299 /// memory consumption while still ensuring every byte is scanned.
300 ///
301 /// Please note that this value may be adjusted to ensure it is a
302 /// multiple of the page size.
303 ///
304 /// By default, this parameter is set to 1GB.
305 #[must_use]
306 pub fn max_fetched_region_size(mut self, max_fetched_region_size: usize) -> Self {
307 self.max_fetched_region_size = max_fetched_region_size;
308 self
309 }
310
311 /// Size of memory chunks to scan.
312 ///
313 /// This parameter bounds the size of the chunks of memory that are
314 /// scanned. This only applies to fragmented memory (using either
315 /// [`crate::Scanner::scan_fragmented`] or
316 /// [`crate::Scanner::scan_process`]) and does not apply when
317 /// scanning a contiguous slice of bytes (scanning a file or a
318 /// byteslice).
319 ///
320 /// When this parameter is set, every region that is scanned is
321 /// split into chunks of this size maximum, and each chunk is
322 /// scanned independently. For example, if a process has a region
323 /// of size 80MB, and this parameter is set to 30MB, then:
324 ///
325 /// - the first 30MB are first fetched and scanned
326 /// - the next 30MB are then fetched and scanned
327 /// - the last 20MB are then fetched and scanned
328 ///
329 /// This parameter thus allows setting a bound on the memory
330 /// consumption of fragmented memory scanning, while still
331 /// scanning all of the bytes available.
332 ///
333 /// Note however than setting this parameter can cause false
334 /// negatives, as string scanning does not handle strings that
335 /// are split between different chunks. For example, when
336 /// scanning for the string `boreal`, if one chunk ends with
337 /// `bor`, and the next one starts with `eal`, the string will
338 /// **not** match.
339 ///
340 /// Please note that, if set, this value may be adjusted to ensure it
341 /// is a multiple of the page size.
342 ///
343 /// By default, this parameter is unset.
344 #[must_use]
345 pub fn memory_chunk_size(mut self, memory_chunk_size: Option<usize>) -> Self {
346 self.memory_chunk_size = memory_chunk_size;
347 self
348 }
349
350 /// Scan mode on fragmented memory, including process memory.
351 ///
352 /// This parameter configures how fragmented memory is scanned.
353 /// See [`FragmentedScanMode`] for more details.
354 ///
355 /// This parameter is used for both the [`crate::Scanner::scan_process`]
356 /// and the [`crate::Scanner::scan_fragmented`] APIs.
357 ///
358 /// By default, this parameter uses the legacy scan mode.
359 #[must_use]
360 pub fn fragmented_scan_mode(mut self, mode: FragmentedScanMode) -> Self {
361 self.fragmented_scan_mode = mode;
362 self
363 }
364
365 /// Bitflag of which events are enabled in the scan callback.
366 ///
367 /// By default, only [`crate::scanner::ScanEvent::RuleMatch`] is enabled.
368 /// Use [`ScanParams::callback_events`] with a bitflag of valuesof this
369 /// enum to enable additional events.
370 ///
371 /// ```
372 /// use boreal::scanner::{CallbackEvents, ScanParams};
373 /// # let mut scanner = boreal::Compiler::new().finalize();
374 ///
375 /// scanner.set_scan_params(
376 /// ScanParams::default()
377 /// .callback_events(CallbackEvents::RULE_MATCH | CallbackEvents::MODULE_IMPORT),
378 /// );
379 /// ```
380 #[must_use]
381 pub fn callback_events(mut self, callback_events: CallbackEvents) -> Self {
382 self.callback_events = callback_events;
383 self
384 }
385
386 /// Include rules that do not match in results.
387 ///
388 /// If set, scan results will include both rules that matched and rules that did not
389 /// match. The field [`crate::scanner::EvaluatedRule::matched`] can be used to
390 /// distinguish the two.
391 ///
392 /// If using the callback API, the [`CallbackEvents::RULE_NO_MATCH`] flag must
393 /// also be set.
394 ///
395 /// It is *not* recommended to set this field, as it may slow down the overall scan.
396 /// Notably, setting this parameter disables the no scan optimization.
397 #[must_use]
398 pub fn include_not_matched_rules(mut self, include_not_matched_rules: bool) -> Self {
399 self.include_not_matched_rules = include_not_matched_rules;
400 self
401 }
402
403 /// Returns whether full matches are computed on matching rules.
404 #[must_use]
405 pub fn get_compute_full_matches(&self) -> bool {
406 self.compute_full_matches
407 }
408
409 /// Returns the maxiumum length of the matches returned in matching rules.
410 #[must_use]
411 pub fn get_match_max_length(&self) -> usize {
412 self.match_max_length
413 }
414
415 /// Returns the maximum number of matches for a given string.
416 #[must_use]
417 pub fn get_string_max_nb_matches(&self) -> u32 {
418 self.string_max_nb_matches
419 }
420
421 /// Returns the maximum duration of a scan before it is stopped.
422 #[must_use]
423 pub fn get_timeout_duration(&self) -> Option<&Duration> {
424 self.timeout_duration.as_ref()
425 }
426
427 /// Returns whether statistics are computed during scanning.
428 #[must_use]
429 pub fn get_compute_statistics(&self) -> bool {
430 self.compute_statistics
431 }
432
433 /// Returns whether scanned bytes are considered part of the memory of a process.
434 #[must_use]
435 pub fn get_process_memory(&self) -> bool {
436 self.process_memory
437 }
438
439 /// Returns the maximum size of a fetched region.
440 #[must_use]
441 pub fn get_max_fetched_region_size(&self) -> usize {
442 self.max_fetched_region_size
443 }
444
445 /// Returns the size of memory chunks to scan.
446 #[must_use]
447 pub fn get_memory_chunk_size(&self) -> Option<usize> {
448 self.memory_chunk_size
449 }
450
451 /// Returns the scan mode for fragmented memory.
452 #[must_use]
453 pub fn get_fragmented_scan_mode(&self) -> FragmentedScanMode {
454 self.fragmented_scan_mode
455 }
456
457 /// Returns the bitflag of which events are enabled in the scan callback.
458 #[must_use]
459 pub fn get_callback_events(&self) -> CallbackEvents {
460 self.callback_events
461 }
462
463 /// Returns whether rules that do not match are included in results.
464 #[must_use]
465 pub fn get_include_not_matched_rules(&self) -> bool {
466 self.include_not_matched_rules
467 }
468
469 pub(crate) fn to_memory_params(&self) -> MemoryParams {
470 MemoryParams {
471 max_fetched_region_size: self.max_fetched_region_size,
472 memory_chunk_size: self.memory_chunk_size,
473 can_refetch_regions: self.fragmented_scan_mode.can_refetch_regions,
474 }
475 }
476}
477
478/// Bitflag values of callback events.
479///
480/// See [`ScanParams::callback_events`] for more details.
481#[derive(Copy, Clone, Debug, PartialEq, Eq)]
482pub struct CallbackEvents(pub(crate) u32);
483
484impl CallbackEvents {
485 /// Enables the [`crate::scanner::ScanEvent::RuleMatch`] events.
486 pub const RULE_MATCH: CallbackEvents = CallbackEvents(0b0000_0001);
487
488 /// Enables the [`crate::scanner::ScanEvent::RuleNoMatch`] events.
489 ///
490 /// The [`ScanParams::include_not_matched_rules`] parameter must also be set to true
491 /// to received those events.
492 pub const RULE_NO_MATCH: CallbackEvents = CallbackEvents(0b0000_0010);
493
494 /// Enables the [`crate::scanner::ScanEvent::ModuleImport`] events.
495 pub const MODULE_IMPORT: CallbackEvents = CallbackEvents(0b0000_0100);
496
497 /// Enables the [`crate::scanner::ScanEvent::ScanStatistics`] events.
498 ///
499 /// The [`ScanParams::compute_statistics`] parameter must be set to true, and
500 /// the `profiling` feature must have been enabled during compilation.
501 pub const SCAN_STATISTICS: CallbackEvents = CallbackEvents(0b0000_1000);
502
503 /// Enables the [`crate::scanner::ScanEvent::StringReachedMatchLimit`] events.
504 pub const STRING_REACHED_MATCH_LIMIT: CallbackEvents = CallbackEvents(0b0001_0000);
505
506 /// Return an empty bitflag
507 #[must_use]
508 pub fn empty() -> Self {
509 Self(0)
510 }
511}
512
513impl std::ops::BitOr for CallbackEvents {
514 type Output = Self;
515
516 fn bitor(self, other: Self) -> Self {
517 Self(self.0 | other.0)
518 }
519}
520
521impl std::ops::BitAnd for CallbackEvents {
522 type Output = Self;
523
524 fn bitand(self, other: Self) -> Self {
525 Self(self.0 & other.0)
526 }
527}
528
529impl std::ops::BitOrAssign for CallbackEvents {
530 fn bitor_assign(&mut self, other: Self) {
531 self.0.bitor_assign(other.0);
532 }
533}
534
535impl std::ops::BitAndAssign for CallbackEvents {
536 fn bitand_assign(&mut self, other: Self) {
537 self.0.bitand_assign(other.0);
538 }
539}
540
541#[cfg(feature = "serialize")]
542mod wire {
543 use std::io;
544 use std::time::Duration;
545
546 use crate::wire::{Deserialize, Serialize};
547
548 use super::{CallbackEvents, FragmentedScanMode, ScanParams};
549
550 impl Serialize for ScanParams {
551 fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
552 self.compute_full_matches.serialize(writer)?;
553 self.match_max_length.serialize(writer)?;
554 self.string_max_nb_matches.serialize(writer)?;
555 self.timeout_duration
556 .map(|v| (v.as_secs(), v.subsec_nanos()))
557 .serialize(writer)?;
558 self.compute_statistics.serialize(writer)?;
559 self.fragmented_scan_mode.serialize(writer)?;
560 self.process_memory.serialize(writer)?;
561 self.max_fetched_region_size.serialize(writer)?;
562 self.memory_chunk_size.serialize(writer)?;
563 self.callback_events.0.serialize(writer)?;
564 self.include_not_matched_rules.serialize(writer)?;
565 Ok(())
566 }
567 }
568
569 impl Deserialize for ScanParams {
570 fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
571 let compute_full_matches = bool::deserialize_reader(reader)?;
572 let match_max_length = usize::deserialize_reader(reader)?;
573 let string_max_nb_matches = u32::deserialize_reader(reader)?;
574 let timeout_duration = <Option<(u64, u32)>>::deserialize_reader(reader)?;
575 let compute_statistics = bool::deserialize_reader(reader)?;
576 let fragmented_scan_mode = FragmentedScanMode::deserialize_reader(reader)?;
577 let process_memory = bool::deserialize_reader(reader)?;
578 let max_fetched_region_size = usize::deserialize_reader(reader)?;
579 let memory_chunk_size = <Option<usize>>::deserialize_reader(reader)?;
580 let callback_events = u32::deserialize_reader(reader)?;
581 let include_not_matched_rules = bool::deserialize_reader(reader)?;
582 Ok(Self {
583 compute_full_matches,
584 match_max_length,
585 string_max_nb_matches,
586 timeout_duration: timeout_duration.map(|(secs, nanos)| Duration::new(secs, nanos)),
587 compute_statistics,
588 fragmented_scan_mode,
589 process_memory,
590 max_fetched_region_size,
591 memory_chunk_size,
592 callback_events: CallbackEvents(callback_events),
593 include_not_matched_rules,
594 })
595 }
596 }
597
598 impl Serialize for FragmentedScanMode {
599 fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
600 self.modules_dynamic_values.serialize(writer)?;
601 self.can_refetch_regions.serialize(writer)?;
602 Ok(())
603 }
604 }
605
606 impl Deserialize for FragmentedScanMode {
607 fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
608 let modules_dynamic_values = bool::deserialize_reader(reader)?;
609 let can_refetch_regions = bool::deserialize_reader(reader)?;
610 Ok(Self {
611 modules_dynamic_values,
612 can_refetch_regions,
613 })
614 }
615 }
616
617 #[cfg(test)]
618 mod tests {
619 use super::*;
620 use crate::wire::tests::test_round_trip;
621
622 #[test]
623 fn test_wire_scan_params() {
624 test_round_trip(
625 &ScanParams {
626 compute_full_matches: true,
627 match_max_length: 23,
628 string_max_nb_matches: 12,
629 timeout_duration: Some(Duration::from_millis(1_290_874)),
630 compute_statistics: false,
631 fragmented_scan_mode: FragmentedScanMode {
632 modules_dynamic_values: false,
633 can_refetch_regions: true,
634 },
635 process_memory: true,
636 max_fetched_region_size: 29_392,
637 memory_chunk_size: Some(128),
638 callback_events: CallbackEvents::RULE_MATCH | CallbackEvents::MODULE_IMPORT,
639 include_not_matched_rules: true,
640 },
641 &[0, 1, 9, 13, 26, 27, 29, 30, 38, 47, 51],
642 );
643
644 test_round_trip(
645 &FragmentedScanMode {
646 modules_dynamic_values: true,
647 can_refetch_regions: false,
648 },
649 &[0, 1],
650 );
651 }
652 }
653}
654
655#[cfg(test)]
656mod tests {
657 use super::*;
658 use crate::test_helpers::test_type_traits;
659
660 #[test]
661 fn test_types_traits() {
662 test_type_traits(ScanParams::default());
663 }
664
665 #[test]
666 fn test_getters() {
667 let params = ScanParams::default();
668
669 let params = params.compute_full_matches(true);
670 assert!(params.get_compute_full_matches());
671
672 let params = params.match_max_length(3);
673 assert_eq!(params.get_match_max_length(), 3);
674
675 let params = params.string_max_nb_matches(3);
676 assert_eq!(params.get_string_max_nb_matches(), 3);
677
678 let params = params.timeout_duration(Some(Duration::from_secs(4)));
679 assert_eq!(params.get_timeout_duration(), Some(&Duration::from_secs(4)));
680
681 let params = params.compute_statistics(true);
682 assert!(params.get_compute_statistics());
683
684 let params = params.process_memory(true);
685 assert!(params.get_process_memory());
686
687 let params = params.max_fetched_region_size(100);
688 assert_eq!(params.get_max_fetched_region_size(), 100);
689
690 let params = params.memory_chunk_size(Some(200));
691 assert_eq!(params.get_memory_chunk_size(), Some(200));
692
693 let params = params.fragmented_scan_mode(FragmentedScanMode::fast());
694 assert_eq!(
695 params.get_fragmented_scan_mode(),
696 FragmentedScanMode::fast()
697 );
698
699 let params =
700 params.callback_events(CallbackEvents::RULE_MATCH | CallbackEvents::MODULE_IMPORT);
701 assert_eq!(
702 params.get_callback_events(),
703 CallbackEvents::RULE_MATCH | CallbackEvents::MODULE_IMPORT
704 );
705
706 let params = params.include_not_matched_rules(true);
707 assert!(params.get_include_not_matched_rules());
708 }
709
710 #[test]
711 fn test_callback_events_ops() {
712 let a = CallbackEvents::RULE_MATCH;
713 let b = CallbackEvents::MODULE_IMPORT;
714
715 assert_eq!(a | b, CallbackEvents(0b101));
716 assert_eq!(a & b, CallbackEvents(0b000));
717
718 let mut c = a;
719 c |= b;
720 assert_eq!(c, CallbackEvents(0b101));
721 assert_eq!(c & a, CallbackEvents(0b01));
722 c &= b;
723 assert_eq!(c, CallbackEvents(0b100));
724 }
725}