use std::collections::HashMap;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::sync::OnceLock;
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
pub use crate::types::{ProcessId, ThreadId};
#[derive(Debug, Clone, Copy)]
struct CaseInsensitiveKey<'a>(&'a str);
impl<'a> Hash for CaseInsensitiveKey<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
for ch in self.0.chars() {
ch.to_ascii_lowercase().hash(state);
}
}
}
impl<'a> PartialEq for CaseInsensitiveKey<'a> {
fn eq(&self, other: &Self) -> bool {
self.0.eq_ignore_ascii_case(other.0)
}
}
impl<'a> Eq for CaseInsensitiveKey<'a> {}
static PATH_CACHE: OnceLock<HashMap<CaseInsensitiveKey<'static>, &'static str>> = OnceLock::new();
const COMMON_PATHS: &[&str] = &[
"C:\\Windows\\System32\\kernel32.dll",
"C:\\Windows\\System32\\ntdll.dll",
"C:\\Windows\\System32\\msvcrt.dll",
"C:\\Windows\\System32\\advapi32.dll",
"C:\\Windows\\System32\\user32.dll",
"C:\\Windows\\System32\\gdi32.dll",
"C:\\Windows\\System32\\ws2_32.dll",
"C:\\Windows\\System32\\shell32.dll",
"C:\\Windows\\System32\\ole32.dll",
"C:\\Windows\\System32\\oleaut32.dll",
"C:\\Windows\\System32\\comctl32.dll",
"C:\\Windows\\System32\\comdlg32.dll",
"C:\\Windows\\System32\\winmm.dll",
"C:\\Windows\\System32\\shlwapi.dll",
"C:\\Windows\\System32\\urlmon.dll",
"C:\\Windows\\System32\\wininet.dll",
"C:\\Windows\\System32\\msi.dll",
"C:\\Windows\\System32\\crypt32.dll",
"C:\\Windows\\System32\\cryptbase.dll",
"C:\\Windows\\System32\\cryptnet.dll",
"C:\\Windows\\System32\\ncrypt.dll",
"C:\\Windows\\System32\\bcryptprimitives.dll",
"C:\\Windows\\System32\\secur32.dll",
"C:\\Windows\\System32\\sspicli.dll",
"C:\\Windows\\System32\\ntsecapi.dll",
"C:\\Windows\\System32\\wlanapi.dll",
"C:\\Windows\\System32\\netapi32.dll",
"C:\\Windows\\System32\\iphlpapi.dll",
"C:\\Windows\\System32\\dnsapi.dll",
"C:\\Windows\\System32\\nsi.dll",
"C:\\Windows\\System32\\setupapi.dll",
"C:\\Windows\\System32\\cfgmgr32.dll",
"C:\\Windows\\System32\\regapi.dll",
"C:\\Windows\\System32\\opengl32.dll",
"C:\\Windows\\System32\\services.exe",
"C:\\Windows\\System32\\lsass.exe",
"C:\\Windows\\System32\\csrss.exe",
"C:\\Windows\\System32\\svchost.exe",
"C:\\Windows\\System32\\rundll32.exe",
"C:\\Windows\\System32\\cmd.exe",
"C:\\Windows\\System32\\notepad.exe",
"C:\\Windows\\System32\\regedit.exe",
"C:\\Windows\\System32\\conhost.exe",
"C:\\Windows\\SysWOW64\\kernel32.dll",
"C:\\Windows\\SysWOW64\\ntdll.dll",
"C:\\Windows\\SysWOW64\\msvcrt.dll",
"C:\\Windows\\SysWOW64\\advapi32.dll",
"C:\\Windows\\SysWOW64\\user32.dll",
"C:\\Windows\\SysWOW64\\gdi32.dll",
"C:\\Windows\\SysWOW64\\ws2_32.dll",
"C:\\Windows\\SysWOW64\\shell32.dll",
"C:\\Windows\\SysWOW64\\ole32.dll",
"C:\\Windows\\SysWOW64\\oleaut32.dll",
"C:\\Windows\\SysWOW64\\comctl32.dll",
"C:\\Windows\\SysWOW64\\comdlg32.dll",
"C:\\Windows\\SysWOW64\\crypt32.dll",
"C:\\Windows\\SysWOW64\\cryptbase.dll",
"C:\\Windows\\SysWOW64\\secur32.dll",
"C:\\Windows\\SysWOW64\\setupapi.dll",
"C:\\Program Files\\",
"C:\\Program Files (x86)\\",
"C:\\Windows\\",
"C:\\Windows\\System32\\",
"C:\\Windows\\SysWOW64\\",
];
fn init_path_cache() -> HashMap<CaseInsensitiveKey<'static>, &'static str> {
COMMON_PATHS
.iter()
.map(|&path| (CaseInsensitiveKey(path), path))
.collect()
}
#[derive(Debug, Clone)]
pub enum ImagePath {
Cached(&'static str),
Owned(String),
}
impl ImagePath {
pub fn new(path: impl Into<String>) -> Self {
let path_str = path.into();
if let Some(cached) = Self::find_cached(&path_str) {
return ImagePath::Cached(cached);
}
ImagePath::Owned(path_str)
}
pub fn from_str(path: &str) -> Self {
let path = path.trim_end_matches('\0');
if let Some(cached) = Self::find_cached_str(path) {
return ImagePath::Cached(cached);
}
ImagePath::Owned(path.to_string())
}
pub fn from_utf16(utf16_data: &[u16]) -> Self {
let path_string = String::from_utf16_lossy(utf16_data);
let path_str = path_string.trim_end_matches('\0');
let path_lower = path_str.to_lowercase();
if let Some(cached) = Self::find_cached_str(&path_lower) {
return ImagePath::Cached(cached);
}
ImagePath::Owned(path_str.to_string())
}
pub fn from_utf8(utf8_data: &[u8]) -> Option<Self> {
let path_str = std::str::from_utf8(utf8_data).ok()?;
let path_str = path_str.trim_end_matches('\0');
if let Some(cached) = Self::find_cached_str(path_str) {
return Some(ImagePath::Cached(cached));
}
Some(ImagePath::Owned(path_str.to_string()))
}
pub fn as_str(&self) -> &str {
match self {
ImagePath::Cached(s) => s,
ImagePath::Owned(s) => s.as_str(),
}
}
pub fn eq_case_insensitive(&self, other: &str) -> bool {
self.as_str().eq_ignore_ascii_case(other)
}
pub fn contains_case_insensitive(&self, needle: &str) -> bool {
self.as_str()
.to_lowercase()
.contains(&needle.to_lowercase())
}
pub fn ends_with_case_insensitive(&self, suffix: &str) -> bool {
let path_lower = self.as_str().to_lowercase();
let suffix_lower = suffix.to_lowercase();
path_lower.ends_with(&suffix_lower)
}
pub fn is_system_path(&self) -> bool {
let path_lower = self.as_str().to_lowercase();
path_lower.contains("\\windows\\") || path_lower.contains("\\winnt\\")
}
pub fn is_wow64(&self) -> bool {
self.contains_case_insensitive("\\SysWOW64\\")
}
pub fn file_name(&self) -> &str {
self.as_str().rsplit('\\').next().unwrap_or(self.as_str())
}
fn find_cached(path: &str) -> Option<&'static str> {
Self::find_cached_str(path)
}
fn find_cached_str(path: &str) -> Option<&'static str> {
let cache = PATH_CACHE.get_or_init(init_path_cache);
cache.get(&CaseInsensitiveKey(path)).copied()
}
pub fn cache_path(_path: &'static str) {
}
pub fn is_cached(&self) -> bool {
matches!(self, ImagePath::Cached(_))
}
}
impl fmt::Display for ImagePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl PartialEq for ImagePath {
fn eq(&self, other: &Self) -> bool {
self.eq_case_insensitive(other.as_str())
}
}
impl PartialEq<str> for ImagePath {
fn eq(&self, other: &str) -> bool {
self.eq_case_insensitive(other)
}
}
impl PartialEq<ImagePath> for str {
fn eq(&self, other: &ImagePath) -> bool {
other.eq_case_insensitive(self)
}
}
impl PartialEq<&str> for ImagePath {
fn eq(&self, other: &&str) -> bool {
self.eq_case_insensitive(other)
}
}
impl PartialEq<ImagePath> for &str {
fn eq(&self, other: &ImagePath) -> bool {
other.eq_case_insensitive(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessAccess {
QueryInformation,
QueryLimitedInformation,
VmRead,
VmWrite,
Terminate,
CreateThread,
AllAccess,
Custom(PROCESS_ACCESS_RIGHTS),
}
impl ProcessAccess {
pub(crate) fn to_windows(self) -> PROCESS_ACCESS_RIGHTS {
use windows::Win32::System::Threading::*;
const PROCESS_SYNCHRONIZE: PROCESS_ACCESS_RIGHTS = PROCESS_ACCESS_RIGHTS(0x0010_0000);
match self {
ProcessAccess::QueryInformation => PROCESS_QUERY_INFORMATION | PROCESS_SYNCHRONIZE,
ProcessAccess::QueryLimitedInformation => {
PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SYNCHRONIZE
}
ProcessAccess::VmRead => PROCESS_VM_READ | PROCESS_SYNCHRONIZE,
ProcessAccess::VmWrite => PROCESS_VM_WRITE | PROCESS_SYNCHRONIZE,
ProcessAccess::Terminate => PROCESS_TERMINATE | PROCESS_SYNCHRONIZE,
ProcessAccess::CreateThread => PROCESS_CREATE_THREAD | PROCESS_SYNCHRONIZE,
ProcessAccess::AllAccess => PROCESS_ALL_ACCESS,
ProcessAccess::Custom(rights) => rights,
}
}
}
#[derive(Debug, Clone)]
pub struct ProcessInfo {
pub pid: ProcessId,
pub parent_pid: Option<ProcessId>,
pub name: String,
pub thread_count: u32,
}
#[derive(Debug, Clone)]
pub struct ThreadInfo {
pub tid: ThreadId,
pub pid: ProcessId,
pub base_priority: i32,
}
#[derive(Debug, Clone)]
pub struct ModuleInfo {
pub name: String,
pub path: ImagePath,
pub base_address: usize,
pub size: u32,
}
#[derive(Debug, Clone)]
pub struct ProcessParameters {
pub command_line: String,
pub current_directory: String,
pub image_path: ImagePath,
}
#[derive(Debug, Clone, Copy)]
pub struct MemoryInfo {
pub working_set: usize,
pub peak_working_set: usize,
pub page_fault_count: u32,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ProcessCpuTimes {
pub user_time_100ns: u64,
pub kernel_time_100ns: u64,
pub total_time_100ns: u64,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ProcessMemoryMetrics {
pub working_set_bytes: usize,
pub peak_working_set_bytes: usize,
pub page_fault_count: u32,
pub private_usage_bytes: usize,
pub commit_usage_bytes: usize,
pub peak_commit_usage_bytes: usize,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ProcessMetrics {
pub memory: ProcessMemoryMetrics,
pub cpu: ProcessCpuTimes,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct HostMemoryMetrics {
pub total_physical_bytes: u64,
pub available_physical_bytes: u64,
pub total_virtual_bytes: u64,
pub available_virtual_bytes: u64,
pub memory_load_percent: u32,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct HostMetrics {
pub logical_cpu_count: u32,
pub memory: HostMemoryMetrics,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_image_path_cache_hit() {
let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert!(path.is_cached(), "kernel32.dll should be cached");
match path {
ImagePath::Cached(s) => {
assert_eq!(s, "C:\\Windows\\System32\\kernel32.dll");
}
_ => panic!("Expected Cached variant"),
}
}
#[test]
fn test_image_path_cache_miss() {
let path = ImagePath::from_str("C:\\Unknown\\Path\\custom.dll");
assert!(!path.is_cached(), "Unknown path should not be cached");
match path {
ImagePath::Owned(s) => {
assert_eq!(s, "C:\\Unknown\\Path\\custom.dll");
}
_ => panic!("Expected Owned variant"),
}
}
#[test]
fn test_image_path_case_insensitive_cache_hit() {
let path_upper = ImagePath::from_str("C:\\WINDOWS\\SYSTEM32\\KERNEL32.DLL");
let path_mixed = ImagePath::from_str("c:\\windows\\system32\\kernel32.dll");
assert!(path_upper.is_cached(), "Uppercase path should be cached");
assert!(path_mixed.is_cached(), "Lowercase path should be cached");
}
#[test]
fn test_image_path_new_allocates_then_caches() {
let path = ImagePath::new("C:\\Windows\\System32\\ntdll.dll");
assert!(path.is_cached(), "Common path should be cached with new()");
}
#[test]
fn test_image_path_from_utf16() {
let utf16: Vec<u16> = "C:\\Windows\\System32\\advapi32.dll"
.encode_utf16()
.collect();
let path = ImagePath::from_utf16(&utf16);
assert!(path.is_cached(), "UTF-16 common path should be cached");
assert_eq!(path.as_str(), "C:\\Windows\\System32\\advapi32.dll");
}
#[test]
fn test_image_path_from_utf16_case_insensitive() {
let utf16: Vec<u16> = "C:\\WINDOWS\\SYSTEM32\\USER32.DLL".encode_utf16().collect();
let path = ImagePath::from_utf16(&utf16);
assert!(path.is_cached(), "UTF-16 uppercase path should be cached");
}
#[test]
fn test_image_path_from_utf8() {
let path = ImagePath::from_utf8(b"C:\\Windows\\System32\\shell32.dll");
assert!(path.is_some(), "Valid UTF-8 should succeed");
let path = path.unwrap();
assert!(path.is_cached(), "UTF-8 common path should be cached");
}
#[test]
fn test_image_path_from_utf8_invalid() {
let invalid_utf8 = [0xFF, 0xFE];
let path = ImagePath::from_utf8(&invalid_utf8);
assert!(path.is_none(), "Invalid UTF-8 should return None");
}
#[test]
fn test_image_path_display() {
let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert_eq!(format!("{}", path), "C:\\Windows\\System32\\kernel32.dll");
}
#[test]
fn test_image_path_partial_eq_self() {
let path1 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
let path2 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert_eq!(path1, path2, "Same paths should be equal");
}
#[test]
fn test_image_path_partial_eq_different_case() {
let path1 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
let path2 = ImagePath::from_str("c:\\windows\\system32\\kernel32.dll");
assert_eq!(
path1, path2,
"Different case should be equal (case-insensitive)"
);
}
#[test]
fn test_image_path_eq_str() {
let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert_eq!(&path, "C:\\Windows\\System32\\kernel32.dll");
assert_eq!(&path, "c:\\windows\\system32\\kernel32.dll");
}
#[test]
fn test_image_path_eq_owned_vs_cached() {
let cached = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
let owned = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert!(cached.is_cached());
assert!(owned.is_cached());
assert_eq!(cached, owned);
}
#[test]
fn test_image_path_file_name() {
let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert_eq!(path.file_name(), "kernel32.dll");
}
#[test]
fn test_image_path_file_name_no_path() {
let path = ImagePath::from_str("kernel32.dll");
assert_eq!(path.file_name(), "kernel32.dll");
}
#[test]
fn test_image_path_is_system_path() {
let sys_path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert!(sys_path.is_system_path(), "Should detect Windows path");
let other_path = ImagePath::from_str("C:\\Program Files\\app.exe");
assert!(
!other_path.is_system_path(),
"Should not detect non-Windows path"
);
}
#[test]
fn test_image_path_is_system_path_case_insensitive() {
let sys_path = ImagePath::from_str("C:\\WINDOWS\\SYSTEM32\\kernel32.dll");
assert!(
sys_path.is_system_path(),
"Should detect Windows path (uppercase)"
);
}
#[test]
fn test_image_path_is_wow64() {
let wow64_path = ImagePath::from_str("C:\\Windows\\SysWOW64\\kernel32.dll");
assert!(wow64_path.is_wow64(), "Should detect SysWOW64 path");
let normal_path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert!(
!normal_path.is_wow64(),
"Should not detect System32 as WOW64"
);
}
#[test]
fn test_image_path_is_wow64_case_insensitive() {
let wow64_path = ImagePath::from_str("C:\\WINDOWS\\SYSWOW64\\kernel32.dll");
assert!(
wow64_path.is_wow64(),
"Should detect SysWOW64 path (uppercase)"
);
}
#[test]
fn test_image_path_ends_with_case_insensitive() {
let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert!(
path.ends_with_case_insensitive(".dll"),
"Should end with .dll"
);
assert!(
path.ends_with_case_insensitive(".DLL"),
"Should end with .DLL (case-insensitive)"
);
assert!(
!path.ends_with_case_insensitive(".exe"),
"Should not end with .exe"
);
}
#[test]
fn test_image_path_contains_case_insensitive() {
let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert!(
path.contains_case_insensitive("system32"),
"Should contain system32"
);
assert!(
path.contains_case_insensitive("SYSTEM32"),
"Should contain SYSTEM32 (case-insensitive)"
);
assert!(
path.contains_case_insensitive("kernel32"),
"Should contain kernel32"
);
assert!(
!path.contains_case_insensitive("syswow64"),
"Should not contain syswow64"
);
}
#[test]
fn test_image_path_eq_case_insensitive() {
let path = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert!(path.eq_case_insensitive("C:\\Windows\\System32\\kernel32.dll"));
assert!(path.eq_case_insensitive("c:\\windows\\system32\\kernel32.dll"));
assert!(path.eq_case_insensitive("C:\\WINDOWS\\SYSTEM32\\KERNEL32.DLL"));
}
#[test]
fn test_image_path_clone() {
let path1 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
let path2 = path1.clone();
assert_eq!(path1, path2);
assert_eq!(path1.as_str(), path2.as_str());
}
#[test]
fn test_cache_initialization() {
let path1 = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert!(path1.is_cached());
let path2 = ImagePath::from_str("C:\\Windows\\System32\\ntdll.dll");
assert!(path2.is_cached());
}
#[test]
fn test_multiple_cache_hits() {
let paths = vec![
"C:\\Windows\\System32\\kernel32.dll",
"C:\\Windows\\System32\\ntdll.dll",
"C:\\Windows\\System32\\msvcrt.dll",
"C:\\Windows\\SysWOW64\\kernel32.dll",
];
for p in paths {
let image_path = ImagePath::from_str(p);
assert!(image_path.is_cached(), "Path {} should be cached", p);
assert_eq!(image_path.as_str(), p);
}
}
#[test]
fn test_cache_with_owned_allocation() {
let cached = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
let owned = ImagePath::from_str("C:\\Custom\\unknown.dll");
assert!(cached.is_cached());
assert!(!owned.is_cached());
assert_eq!(cached.as_str(), "C:\\Windows\\System32\\kernel32.dll");
assert_eq!(owned.as_str(), "C:\\Custom\\unknown.dll");
}
#[test]
fn test_case_insensitive_key_hashing() {
let key1 = CaseInsensitiveKey("C:\\Windows\\System32\\kernel32.dll");
let key2 = CaseInsensitiveKey("c:\\windows\\system32\\kernel32.dll");
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher1 = DefaultHasher::new();
key1.hash(&mut hasher1);
let hash1 = hasher1.finish();
let mut hasher2 = DefaultHasher::new();
key2.hash(&mut hasher2);
let hash2 = hasher2.finish();
assert_eq!(
hash1, hash2,
"Case-insensitive keys should hash to the same value"
);
}
#[test]
fn test_process_id_from_u32() {
let id = ProcessId::from(1234u32);
assert_eq!(id.as_u32(), 1234);
}
#[test]
fn test_thread_id_from_u32() {
let id = ThreadId::from(5678u32);
assert_eq!(id.as_u32(), 5678);
}
#[test]
fn test_image_path_with_null_terminator() {
let path_with_null = "C:\\Windows\\System32\\kernel32.dll\0";
let image_path = ImagePath::from_str(path_with_null);
assert_eq!(image_path.as_str(), "C:\\Windows\\System32\\kernel32.dll");
assert!(
!image_path.as_str().ends_with('\0'),
"Null terminator should be stripped"
);
assert!(
image_path.is_cached(),
"Should be cached after null terminator is stripped"
);
let path_without_null = ImagePath::from_str("C:\\Windows\\System32\\kernel32.dll");
assert_eq!(image_path, path_without_null);
}
}