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}