1use std::collections::HashMap;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::sync::OnceLock;
7use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
8
9pub use crate::types::{ProcessId, ThreadId};
11
12#[derive(Debug, Clone, Copy)]
15struct CaseInsensitiveKey<'a>(&'a str);
16
17impl<'a> Hash for CaseInsensitiveKey<'a> {
18 fn hash<H: Hasher>(&self, state: &mut H) {
19 for ch in self.0.chars() {
21 ch.to_ascii_lowercase().hash(state);
22 }
23 }
24}
25
26impl<'a> PartialEq for CaseInsensitiveKey<'a> {
27 fn eq(&self, other: &Self) -> bool {
28 self.0.eq_ignore_ascii_case(other.0)
29 }
30}
31
32impl<'a> Eq for CaseInsensitiveKey<'a> {}
33
34static PATH_CACHE: OnceLock<HashMap<CaseInsensitiveKey<'static>, &'static str>> = OnceLock::new();
37
38const COMMON_PATHS: &[&str] = &[
40 "C:\\Windows\\System32\\kernel32.dll",
42 "C:\\Windows\\System32\\ntdll.dll",
43 "C:\\Windows\\System32\\msvcrt.dll",
44 "C:\\Windows\\System32\\advapi32.dll",
45 "C:\\Windows\\System32\\user32.dll",
46 "C:\\Windows\\System32\\gdi32.dll",
47 "C:\\Windows\\System32\\ws2_32.dll",
48 "C:\\Windows\\System32\\shell32.dll",
49 "C:\\Windows\\System32\\ole32.dll",
50 "C:\\Windows\\System32\\oleaut32.dll",
51 "C:\\Windows\\System32\\comctl32.dll",
52 "C:\\Windows\\System32\\comdlg32.dll",
53 "C:\\Windows\\System32\\winmm.dll",
54 "C:\\Windows\\System32\\shlwapi.dll",
55 "C:\\Windows\\System32\\urlmon.dll",
56 "C:\\Windows\\System32\\wininet.dll",
57 "C:\\Windows\\System32\\msi.dll",
58 "C:\\Windows\\System32\\crypt32.dll",
59 "C:\\Windows\\System32\\cryptbase.dll",
60 "C:\\Windows\\System32\\cryptnet.dll",
61 "C:\\Windows\\System32\\ncrypt.dll",
62 "C:\\Windows\\System32\\bcryptprimitives.dll",
63 "C:\\Windows\\System32\\secur32.dll",
64 "C:\\Windows\\System32\\sspicli.dll",
65 "C:\\Windows\\System32\\ntsecapi.dll",
66 "C:\\Windows\\System32\\wlanapi.dll",
67 "C:\\Windows\\System32\\netapi32.dll",
68 "C:\\Windows\\System32\\iphlpapi.dll",
69 "C:\\Windows\\System32\\dnsapi.dll",
70 "C:\\Windows\\System32\\nsi.dll",
71 "C:\\Windows\\System32\\setupapi.dll",
72 "C:\\Windows\\System32\\cfgmgr32.dll",
73 "C:\\Windows\\System32\\regapi.dll",
74 "C:\\Windows\\System32\\opengl32.dll",
75 "C:\\Windows\\System32\\services.exe",
77 "C:\\Windows\\System32\\lsass.exe",
78 "C:\\Windows\\System32\\csrss.exe",
79 "C:\\Windows\\System32\\svchost.exe",
80 "C:\\Windows\\System32\\rundll32.exe",
81 "C:\\Windows\\System32\\cmd.exe",
82 "C:\\Windows\\System32\\notepad.exe",
83 "C:\\Windows\\System32\\regedit.exe",
84 "C:\\Windows\\System32\\conhost.exe",
85 "C:\\Windows\\SysWOW64\\kernel32.dll",
87 "C:\\Windows\\SysWOW64\\ntdll.dll",
88 "C:\\Windows\\SysWOW64\\msvcrt.dll",
89 "C:\\Windows\\SysWOW64\\advapi32.dll",
90 "C:\\Windows\\SysWOW64\\user32.dll",
91 "C:\\Windows\\SysWOW64\\gdi32.dll",
92 "C:\\Windows\\SysWOW64\\ws2_32.dll",
93 "C:\\Windows\\SysWOW64\\shell32.dll",
94 "C:\\Windows\\SysWOW64\\ole32.dll",
95 "C:\\Windows\\SysWOW64\\oleaut32.dll",
96 "C:\\Windows\\SysWOW64\\comctl32.dll",
97 "C:\\Windows\\SysWOW64\\comdlg32.dll",
98 "C:\\Windows\\SysWOW64\\crypt32.dll",
99 "C:\\Windows\\SysWOW64\\cryptbase.dll",
100 "C:\\Windows\\SysWOW64\\secur32.dll",
101 "C:\\Windows\\SysWOW64\\setupapi.dll",
102 "C:\\Program Files\\",
104 "C:\\Program Files (x86)\\",
105 "C:\\Windows\\",
106 "C:\\Windows\\System32\\",
107 "C:\\Windows\\SysWOW64\\",
108];
109
110fn init_path_cache() -> HashMap<CaseInsensitiveKey<'static>, &'static str> {
112 COMMON_PATHS
113 .iter()
114 .map(|&path| (CaseInsensitiveKey(path), path))
115 .collect()
116}
117
118#[derive(Debug, Clone)]
125pub enum ImagePath {
126 Cached(&'static str),
128 Owned(String),
130}
131
132impl ImagePath {
133 pub fn new(path: impl Into<String>) -> Self {
135 let path_str = path.into();
136
137 if let Some(cached) = Self::find_cached(&path_str) {
139 return ImagePath::Cached(cached);
140 }
141
142 ImagePath::Owned(path_str)
143 }
144
145 pub fn from_str(path: &str) -> Self {
153 let path = path.trim_end_matches('\0');
155
156 if let Some(cached) = Self::find_cached_str(path) {
158 return ImagePath::Cached(cached);
159 }
160
161 ImagePath::Owned(path.to_string())
163 }
164
165 pub fn from_utf16(utf16_data: &[u16]) -> Self {
171 let path_string = String::from_utf16_lossy(utf16_data);
173
174 let path_str = path_string.trim_end_matches('\0');
176 let path_lower = path_str.to_lowercase();
177
178 if let Some(cached) = Self::find_cached_str(&path_lower) {
180 return ImagePath::Cached(cached);
181 }
182
183 ImagePath::Owned(path_str.to_string())
185 }
186
187 pub fn from_utf8(utf8_data: &[u8]) -> Option<Self> {
192 let path_str = std::str::from_utf8(utf8_data).ok()?;
193
194 let path_str = path_str.trim_end_matches('\0');
196
197 if let Some(cached) = Self::find_cached_str(path_str) {
199 return Some(ImagePath::Cached(cached));
200 }
201
202 Some(ImagePath::Owned(path_str.to_string()))
204 }
205
206 pub fn as_str(&self) -> &str {
208 match self {
209 ImagePath::Cached(s) => s,
210 ImagePath::Owned(s) => s.as_str(),
211 }
212 }
213
214 pub fn eq_case_insensitive(&self, other: &str) -> bool {
216 self.as_str().eq_ignore_ascii_case(other)
217 }
218
219 pub fn contains_case_insensitive(&self, needle: &str) -> bool {
221 self.as_str()
222 .to_lowercase()
223 .contains(&needle.to_lowercase())
224 }
225
226 pub fn ends_with_case_insensitive(&self, suffix: &str) -> bool {
228 let path_lower = self.as_str().to_lowercase();
229 let suffix_lower = suffix.to_lowercase();
230 path_lower.ends_with(&suffix_lower)
231 }
232
233 pub fn is_system_path(&self) -> bool {
235 let path_lower = self.as_str().to_lowercase();
236 path_lower.contains("\\windows\\") || path_lower.contains("\\winnt\\")
237 }
238
239 pub fn is_wow64(&self) -> bool {
241 self.contains_case_insensitive("\\SysWOW64\\")
242 }
243
244 pub fn file_name(&self) -> &str {
246 self.as_str().rsplit('\\').next().unwrap_or(self.as_str())
247 }
248
249 fn find_cached(path: &str) -> Option<&'static str> {
251 Self::find_cached_str(path)
252 }
253
254 fn find_cached_str(path: &str) -> Option<&'static str> {
257 let cache = PATH_CACHE.get_or_init(init_path_cache);
258 cache.get(&CaseInsensitiveKey(path)).copied()
259 }
260
261 pub fn cache_path(_path: &'static str) {
265 }
267
268 pub fn is_cached(&self) -> bool {
270 matches!(self, ImagePath::Cached(_))
271 }
272}
273
274impl fmt::Display for ImagePath {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 write!(f, "{}", self.as_str())
277 }
278}
279
280impl PartialEq for ImagePath {
281 fn eq(&self, other: &Self) -> bool {
282 self.eq_case_insensitive(other.as_str())
283 }
284}
285
286impl PartialEq<str> for ImagePath {
287 fn eq(&self, other: &str) -> bool {
288 self.eq_case_insensitive(other)
289 }
290}
291
292impl PartialEq<ImagePath> for str {
293 fn eq(&self, other: &ImagePath) -> bool {
294 other.eq_case_insensitive(self)
295 }
296}
297
298impl PartialEq<&str> for ImagePath {
299 fn eq(&self, other: &&str) -> bool {
300 self.eq_case_insensitive(other)
301 }
302}
303
304impl PartialEq<ImagePath> for &str {
305 fn eq(&self, other: &ImagePath) -> bool {
306 other.eq_case_insensitive(self)
307 }
308}
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq)]
312pub enum ProcessAccess {
313 QueryInformation,
315 QueryLimitedInformation,
317 VmRead,
319 VmWrite,
321 Terminate,
323 CreateThread,
325 AllAccess,
327 Custom(PROCESS_ACCESS_RIGHTS),
329}
330
331impl ProcessAccess {
332 pub(crate) fn to_windows(self) -> PROCESS_ACCESS_RIGHTS {
333 use windows::Win32::System::Threading::*;
334
335 const PROCESS_SYNCHRONIZE: PROCESS_ACCESS_RIGHTS = PROCESS_ACCESS_RIGHTS(0x0010_0000);
336
337 match self {
338 ProcessAccess::QueryInformation => PROCESS_QUERY_INFORMATION | PROCESS_SYNCHRONIZE,
339 ProcessAccess::QueryLimitedInformation => {
340 PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SYNCHRONIZE
341 }
342 ProcessAccess::VmRead => PROCESS_VM_READ | PROCESS_SYNCHRONIZE,
343 ProcessAccess::VmWrite => PROCESS_VM_WRITE | PROCESS_SYNCHRONIZE,
344 ProcessAccess::Terminate => PROCESS_TERMINATE | PROCESS_SYNCHRONIZE,
345 ProcessAccess::CreateThread => PROCESS_CREATE_THREAD | PROCESS_SYNCHRONIZE,
346 ProcessAccess::AllAccess => PROCESS_ALL_ACCESS,
347 ProcessAccess::Custom(rights) => rights,
348 }
349 }
350}
351
352#[derive(Debug, Clone)]
354pub struct ProcessInfo {
355 pub pid: ProcessId,
357 pub parent_pid: Option<ProcessId>,
359 pub name: String,
361 pub thread_count: u32,
363}
364
365#[derive(Debug, Clone)]
367pub struct ThreadInfo {
368 pub tid: ThreadId,
370 pub pid: ProcessId,
372 pub base_priority: i32,
374}
375
376#[derive(Debug, Clone)]
378pub struct ModuleInfo {
379 pub name: String,
381 pub path: ImagePath,
383 pub base_address: usize,
385 pub size: u32,
387}
388
389#[derive(Debug, Clone)]
391pub struct ProcessParameters {
392 pub command_line: String,
394 pub current_directory: String,
396 pub image_path: ImagePath,
398}
399
400#[derive(Debug, Clone, Copy)]
402pub struct MemoryInfo {
403 pub working_set: usize,
405 pub peak_working_set: usize,
407 pub page_fault_count: u32,
409}
410
411#[derive(Debug, Clone, Copy, Default)]
415pub struct ProcessCpuTimes {
416 pub user_time_100ns: u64,
418 pub kernel_time_100ns: u64,
420 pub total_time_100ns: u64,
422}
423
424#[derive(Debug, Clone, Copy, Default)]
426pub struct ProcessMemoryMetrics {
427 pub working_set_bytes: usize,
429 pub peak_working_set_bytes: usize,
431 pub page_fault_count: u32,
433 pub private_usage_bytes: usize,
435 pub commit_usage_bytes: usize,
437 pub peak_commit_usage_bytes: usize,
439}
440
441#[derive(Debug, Clone, Copy, Default)]
443pub struct ProcessMetrics {
444 pub memory: ProcessMemoryMetrics,
446 pub cpu: ProcessCpuTimes,
448}
449
450#[derive(Debug, Clone, Copy, Default)]
452pub struct HostMemoryMetrics {
453 pub total_physical_bytes: u64,
455 pub available_physical_bytes: u64,
457 pub total_virtual_bytes: u64,
459 pub available_virtual_bytes: u64,
461 pub memory_load_percent: u32,
463}
464
465#[derive(Debug, Clone, Copy, Default)]
467pub struct HostMetrics {
468 pub logical_cpu_count: u32,
470 pub memory: HostMemoryMetrics,
472}
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn test_image_path_cache_hit() {
479 let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
481 assert!(path.is_cached(), "kernel32.dll should be cached");
482 match path {
483 ImagePath::Cached(s) => {
484 assert_eq!(s, "C:\\Windows\\System32\\kernel32.dll");
485 }
486 _ => panic!("Expected Cached variant"),
487 }
488 }
489
490 #[test]
491 fn test_image_path_cache_miss() {
492 let path = ImagePath::from_str("C:\\Unknown\\Path\\custom.dll");
494 assert!(!path.is_cached(), "Unknown path should not be cached");
495 match path {
496 ImagePath::Owned(s) => {
497 assert_eq!(s, "C:\\Unknown\\Path\\custom.dll");
498 }
499 _ => panic!("Expected Owned variant"),
500 }
501 }
502
503 #[test]
504 fn test_image_path_case_insensitive_cache_hit() {
505 let path_upper = ImagePath::from_str("C:\\WINDOWS\\SYSTEM32\\KERNEL32.DLL");
507 let path_mixed = ImagePath::from_str("c:\\windows\\system32\\kernel32.dll");
508
509 assert!(path_upper.is_cached(), "Uppercase path should be cached");
510 assert!(path_mixed.is_cached(), "Lowercase path should be cached");
511 }
512
513 #[test]
514 fn test_image_path_new_allocates_then_caches() {
515 let path = ImagePath::new("C:\\Windows\\System32\\ntdll.dll");
517 assert!(path.is_cached(), "Common path should be cached with new()");
518 }
519
520 #[test]
521 fn test_image_path_from_utf16() {
522 let utf16: Vec<u16> = "C:\\Windows\\System32\\advapi32.dll"
524 .encode_utf16()
525 .collect();
526 let path = ImagePath::from_utf16(&utf16);
527
528 assert!(path.is_cached(), "UTF-16 common path should be cached");
529 assert_eq!(path.as_str(), "C:\\Windows\\System32\\advapi32.dll");
530 }
531
532 #[test]
533 fn test_image_path_from_utf16_case_insensitive() {
534 let utf16: Vec<u16> = "C:\\WINDOWS\\SYSTEM32\\USER32.DLL".encode_utf16().collect();
536 let path = ImagePath::from_utf16(&utf16);
537
538 assert!(path.is_cached(), "UTF-16 uppercase path should be cached");
539 }
540
541 #[test]
542 fn test_image_path_from_utf8() {
543 let path = ImagePath::from_utf8(b"C:\\Windows\\System32\\shell32.dll");
545 assert!(path.is_some(), "Valid UTF-8 should succeed");
546 let path = path.unwrap();
547 assert!(path.is_cached(), "UTF-8 common path should be cached");
548 }
549
550 #[test]
551 fn test_image_path_from_utf8_invalid() {
552 let invalid_utf8 = [0xFF, 0xFE];
554 let path = ImagePath::from_utf8(&invalid_utf8);
555 assert!(path.is_none(), "Invalid UTF-8 should return None");
556 }
557
558 #[test]
559 fn test_image_path_display() {
560 let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
561 assert_eq!(format!("{}", path), "C:\\Windows\\System32\\kernel32.dll");
562 }
563
564 #[test]
565 fn test_image_path_partial_eq_self() {
566 let path1 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
567 let path2 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
568 assert_eq!(path1, path2, "Same paths should be equal");
569 }
570
571 #[test]
572 fn test_image_path_partial_eq_different_case() {
573 let path1 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
574 let path2 = ImagePath::from_str("c:\\windows\\system32\\kernel32.dll");
575 assert_eq!(
576 path1, path2,
577 "Different case should be equal (case-insensitive)"
578 );
579 }
580
581 #[test]
582 fn test_image_path_eq_str() {
583 let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
584 assert_eq!(&path, "C:\\Windows\\System32\\kernel32.dll");
585 assert_eq!(&path, "c:\\windows\\system32\\kernel32.dll");
586 }
587
588 #[test]
589 fn test_image_path_eq_owned_vs_cached() {
590 let cached = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
591 let owned = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
592
593 assert!(cached.is_cached());
595 assert!(owned.is_cached());
596 assert_eq!(cached, owned);
597 }
598
599 #[test]
600 fn test_image_path_file_name() {
601 let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
602 assert_eq!(path.file_name(), "kernel32.dll");
603 }
604
605 #[test]
606 fn test_image_path_file_name_no_path() {
607 let path = ImagePath::from_str("kernel32.dll");
608 assert_eq!(path.file_name(), "kernel32.dll");
609 }
610
611 #[test]
612 fn test_image_path_is_system_path() {
613 let sys_path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
614 assert!(sys_path.is_system_path(), "Should detect Windows path");
615
616 let other_path = ImagePath::from_str("C:\\Program Files\\app.exe");
617 assert!(
618 !other_path.is_system_path(),
619 "Should not detect non-Windows path"
620 );
621 }
622
623 #[test]
624 fn test_image_path_is_system_path_case_insensitive() {
625 let sys_path = ImagePath::from_str("C:\\WINDOWS\\SYSTEM32\\kernel32.dll");
626 assert!(
627 sys_path.is_system_path(),
628 "Should detect Windows path (uppercase)"
629 );
630 }
631
632 #[test]
633 fn test_image_path_is_wow64() {
634 let wow64_path = ImagePath::from_str("C:\\Windows\\SysWOW64\\kernel32.dll");
635 assert!(wow64_path.is_wow64(), "Should detect SysWOW64 path");
636
637 let normal_path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
638 assert!(
639 !normal_path.is_wow64(),
640 "Should not detect System32 as WOW64"
641 );
642 }
643
644 #[test]
645 fn test_image_path_is_wow64_case_insensitive() {
646 let wow64_path = ImagePath::from_str("C:\\WINDOWS\\SYSWOW64\\kernel32.dll");
647 assert!(
648 wow64_path.is_wow64(),
649 "Should detect SysWOW64 path (uppercase)"
650 );
651 }
652
653 #[test]
654 fn test_image_path_ends_with_case_insensitive() {
655 let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
656 assert!(
657 path.ends_with_case_insensitive(".dll"),
658 "Should end with .dll"
659 );
660 assert!(
661 path.ends_with_case_insensitive(".DLL"),
662 "Should end with .DLL (case-insensitive)"
663 );
664 assert!(
665 !path.ends_with_case_insensitive(".exe"),
666 "Should not end with .exe"
667 );
668 }
669
670 #[test]
671 fn test_image_path_contains_case_insensitive() {
672 let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
673 assert!(
674 path.contains_case_insensitive("system32"),
675 "Should contain system32"
676 );
677 assert!(
678 path.contains_case_insensitive("SYSTEM32"),
679 "Should contain SYSTEM32 (case-insensitive)"
680 );
681 assert!(
682 path.contains_case_insensitive("kernel32"),
683 "Should contain kernel32"
684 );
685 assert!(
686 !path.contains_case_insensitive("syswow64"),
687 "Should not contain syswow64"
688 );
689 }
690
691 #[test]
692 fn test_image_path_eq_case_insensitive() {
693 let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
694 assert!(path.eq_case_insensitive("C:\\Windows\\System32\\kernel32.dll"));
695 assert!(path.eq_case_insensitive("c:\\windows\\system32\\kernel32.dll"));
696 assert!(path.eq_case_insensitive("C:\\WINDOWS\\SYSTEM32\\KERNEL32.DLL"));
697 }
698
699 #[test]
700 fn test_image_path_clone() {
701 let path1 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
702 let path2 = path1.clone();
703
704 assert_eq!(path1, path2);
705 assert_eq!(path1.as_str(), path2.as_str());
706 }
707
708 #[test]
709 fn test_cache_initialization() {
710 let path1 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
712 assert!(path1.is_cached());
713
714 let path2 = ImagePath::from_str("C:\\Windows\\System32\\ntdll.dll");
716 assert!(path2.is_cached());
717 }
718
719 #[test]
720 fn test_multiple_cache_hits() {
721 let paths = vec![
723 "C:\\Windows\\System32\\kernel32.dll",
724 "C:\\Windows\\System32\\ntdll.dll",
725 "C:\\Windows\\System32\\msvcrt.dll",
726 "C:\\Windows\\SysWOW64\\kernel32.dll",
727 ];
728
729 for p in paths {
730 let image_path = ImagePath::from_str(p);
731 assert!(image_path.is_cached(), "Path {} should be cached", p);
732 assert_eq!(image_path.as_str(), p);
733 }
734 }
735
736 #[test]
737 fn test_cache_with_owned_allocation() {
738 let cached = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
740 let owned = ImagePath::from_str("C:\\Custom\\unknown.dll");
741
742 assert!(cached.is_cached());
743 assert!(!owned.is_cached());
744
745 assert_eq!(cached.as_str(), "C:\\Windows\\System32\\kernel32.dll");
746 assert_eq!(owned.as_str(), "C:\\Custom\\unknown.dll");
747 }
748
749 #[test]
750 fn test_case_insensitive_key_hashing() {
751 let key1 = CaseInsensitiveKey("C:\\Windows\\System32\\kernel32.dll");
753 let key2 = CaseInsensitiveKey("c:\\windows\\system32\\kernel32.dll");
754
755 use std::collections::hash_map::DefaultHasher;
756 use std::hash::{Hash, Hasher};
757
758 let mut hasher1 = DefaultHasher::new();
759 key1.hash(&mut hasher1);
760 let hash1 = hasher1.finish();
761
762 let mut hasher2 = DefaultHasher::new();
763 key2.hash(&mut hasher2);
764 let hash2 = hasher2.finish();
765
766 assert_eq!(
767 hash1, hash2,
768 "Case-insensitive keys should hash to the same value"
769 );
770 }
771
772 #[test]
773 fn test_process_id_from_u32() {
774 let id = ProcessId::from(1234u32);
775 assert_eq!(id.as_u32(), 1234);
776 }
777
778 #[test]
779 fn test_thread_id_from_u32() {
780 let id = ThreadId::from(5678u32);
781 assert_eq!(id.as_u32(), 5678);
782 }
783
784 #[test]
785 fn test_image_path_with_null_terminator() {
786 let path_with_null = "C:\\Windows\\System32\\kernel32.dll\0";
789 let image_path = ImagePath::from_str(path_with_null);
790
791 assert_eq!(image_path.as_str(), "C:\\Windows\\System32\\kernel32.dll");
794 assert!(
795 !image_path.as_str().ends_with('\0'),
796 "Null terminator should be stripped"
797 );
798
799 assert!(
801 image_path.is_cached(),
802 "Should be cached after null terminator is stripped"
803 );
804
805 let path_without_null = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
807 assert_eq!(image_path, path_without_null);
808 }
809}