1use std::borrow::Cow;
4use std::collections::HashMap;
5use std::sync::OnceLock;
6use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8pub use crate::types::{ProcessId, ThreadId};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
12pub struct EventId(u32);
13
14impl EventId {
15 pub fn new(id: u32) -> Self {
17 EventId(id)
18 }
19
20 pub fn as_u32(&self) -> u32 {
22 self.0
23 }
24}
25
26impl std::fmt::Display for EventId {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(f, "{}", self.0)
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub struct RecordId(u64);
35
36impl RecordId {
37 pub fn new(id: u64) -> Self {
39 RecordId(id)
40 }
41
42 pub fn as_u64(&self) -> u64 {
44 self.0
45 }
46}
47
48impl std::fmt::Display for RecordId {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 write!(f, "{}", self.0)
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
56pub enum EventLevel {
57 AuditSuccess = 0,
59 AuditFailure = 1,
61 Critical = 2,
63 Error = 3,
65 Warning = 4,
67 #[default]
69 Informational = 5,
70 Verbose = 6,
72}
73
74impl EventLevel {
75 pub fn from_code(code: u8) -> Option<Self> {
77 match code {
78 0 => Some(EventLevel::AuditSuccess),
79 1 => Some(EventLevel::AuditFailure),
80 2 => Some(EventLevel::Critical),
81 3 => Some(EventLevel::Error),
82 4 => Some(EventLevel::Warning),
83 5 => Some(EventLevel::Informational),
84 6 => Some(EventLevel::Verbose),
85 _ => None,
86 }
87 }
88}
89
90impl std::fmt::Display for EventLevel {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match self {
93 EventLevel::AuditSuccess => write!(f, "AuditSuccess"),
94 EventLevel::AuditFailure => write!(f, "AuditFailure"),
95 EventLevel::Critical => write!(f, "Critical"),
96 EventLevel::Error => write!(f, "Error"),
97 EventLevel::Warning => write!(f, "Warning"),
98 EventLevel::Informational => write!(f, "Informational"),
99 EventLevel::Verbose => write!(f, "Verbose"),
100 }
101 }
102}
103
104struct StringCache {
109 providers: HashMap<&'static str, Cow<'static, str>>,
110 channels: HashMap<&'static str, Cow<'static, str>>,
111}
112
113impl StringCache {
114 fn new() -> Self {
116 let mut cache = StringCache {
117 providers: HashMap::new(),
118 channels: HashMap::new(),
119 };
120
121 const COMMON_PROVIDERS: &[&str] = &[
123 "Security",
124 "System",
125 "Application",
126 "Microsoft-Windows-Sysmon/Operational",
127 "Microsoft-Windows-PowerShell/Operational",
128 "Microsoft-Windows-WinRM/Operational",
129 "Microsoft-Windows-DNS-Client/Operational",
130 "Perflib",
131 "Windows Defender",
132 "Google Chrome",
133 ];
134
135 const COMMON_CHANNELS: &[&str] = &[
137 "Security",
138 "System",
139 "Application",
140 "Operational",
141 "Analytic",
142 "Debug",
143 "Setup",
144 "Forwarded Events",
145 ];
146
147 for provider in COMMON_PROVIDERS {
148 cache.providers.insert(provider, Cow::Borrowed(provider));
149 }
150
151 for channel in COMMON_CHANNELS {
152 cache.channels.insert(channel, Cow::Borrowed(channel));
153 }
154
155 cache
156 }
157}
158
159static STRING_CACHE: OnceLock<StringCache> = OnceLock::new();
161
162pub fn intern_provider(name: &str) -> Cow<'static, str> {
167 let cache = STRING_CACHE.get_or_init(StringCache::new);
168 if let Some(cached) = cache.providers.get(name) {
169 return cached.clone();
170 }
171 Cow::Owned(name.to_string())
172}
173
174pub fn intern_channel(name: &str) -> Cow<'static, str> {
179 let cache = STRING_CACHE.get_or_init(StringCache::new);
180 if let Some(cached) = cache.channels.get(name) {
181 return cached.clone();
182 }
183 Cow::Owned(name.to_string())
184}
185
186pub fn intern_field_name(name: &str) -> Cow<'static, str> {
193 match name {
194 "SubjectUserName" => Cow::Borrowed("SubjectUserName"),
196 "SubjectDomainName" => Cow::Borrowed("SubjectDomainName"),
197 "SubjectUserSid" => Cow::Borrowed("SubjectUserSid"),
198 "SubjectLogonId" => Cow::Borrowed("SubjectLogonId"),
199 "TargetUserName" => Cow::Borrowed("TargetUserName"),
200 "TargetDomainName" => Cow::Borrowed("TargetDomainName"),
201 "TargetUserSid" => Cow::Borrowed("TargetUserSid"),
202 "TargetLogonId" => Cow::Borrowed("TargetLogonId"),
203 "LogonType" => Cow::Borrowed("LogonType"),
204 "IpAddress" => Cow::Borrowed("IpAddress"),
205 "IpPort" => Cow::Borrowed("IpPort"),
206 "WorkstationName" => Cow::Borrowed("WorkstationName"),
207 "AuthenticationPackageName" => Cow::Borrowed("AuthenticationPackageName"),
208
209 "ProcessName" => Cow::Borrowed("ProcessName"),
211 "ProcessId" => Cow::Borrowed("ProcessId"),
212 "CommandLine" => Cow::Borrowed("CommandLine"),
213 "NewProcessName" => Cow::Borrowed("NewProcessName"),
214 "NewProcessId" => Cow::Borrowed("NewProcessId"),
215 "ParentProcessName" => Cow::Borrowed("ParentProcessName"),
216
217 "ObjectName" => Cow::Borrowed("ObjectName"),
219 "AccessList" => Cow::Borrowed("AccessList"),
220 "PrivilegeList" => Cow::Borrowed("PrivilegeList"),
221
222 "Image" => Cow::Borrowed("Image"),
224 "ImageLoaded" => Cow::Borrowed("ImageLoaded"),
225 "ParentImage" => Cow::Borrowed("ParentImage"),
226 "ParentCommandLine" => Cow::Borrowed("ParentCommandLine"),
227 "ParentProcessId" => Cow::Borrowed("ParentProcessId"),
228 "Hashes" => Cow::Borrowed("Hashes"),
229 "User" => Cow::Borrowed("User"),
230 "IntegrityLevel" => Cow::Borrowed("IntegrityLevel"),
231
232 "SourceIp" => Cow::Borrowed("SourceIp"),
234 "SourcePort" => Cow::Borrowed("SourcePort"),
235 "SourceHostname" => Cow::Borrowed("SourceHostname"),
236 "DestinationIp" => Cow::Borrowed("DestinationIp"),
237 "DestinationPort" => Cow::Borrowed("DestinationPort"),
238 "DestinationHostname" => Cow::Borrowed("DestinationHostname"),
239 "Protocol" => Cow::Borrowed("Protocol"),
240
241 "TargetFilename" => Cow::Borrowed("TargetFilename"),
243 "TargetObject" => Cow::Borrowed("TargetObject"),
244 "Details" => Cow::Borrowed("Details"),
245
246 "ScriptBlockText" => Cow::Borrowed("ScriptBlockText"),
248 "Path" => Cow::Borrowed("Path"),
249 "MessageNumber" => Cow::Borrowed("MessageNumber"),
250 "MessageTotal" => Cow::Borrowed("MessageTotal"),
251
252 "EventID" => Cow::Borrowed("EventID"),
254 "Level" => Cow::Borrowed("Level"),
255 "Keywords" => Cow::Borrowed("Keywords"),
256 "Message" => Cow::Borrowed("Message"),
257 "Data" => Cow::Borrowed("Data"),
258
259 _ => Cow::Owned(name.to_string()),
261 }
262}
263
264#[derive(Debug, Clone, Default)]
266pub struct Event {
267 pub id: EventId,
269
270 pub level: EventLevel,
272
273 pub provider: Cow<'static, str>,
275
276 pub channel: Cow<'static, str>,
278
279 pub computer: String,
281
282 pub timestamp: Option<SystemTime>,
284
285 pub record_id: Option<RecordId>,
287
288 pub process_id: Option<ProcessId>,
290
291 pub thread_id: Option<ThreadId>,
293
294 pub data: Option<std::collections::HashMap<Cow<'static, str>, String>>,
296
297 pub formatted_message: Option<String>,
299}
300
301impl Event {
302 pub fn new() -> Self {
304 Event {
305 id: EventId(0),
306 level: EventLevel::Informational,
307 provider: Cow::Borrowed(""),
308 channel: Cow::Borrowed(""),
309 computer: String::new(),
310 timestamp: None,
311 record_id: None,
312 process_id: None,
313 thread_id: None,
314 data: None,
315 formatted_message: None,
316 }
317 }
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
322pub enum RenderFormat {
323 Values,
325 Xml,
327}
328
329#[derive(Debug, Clone)]
331pub struct CorruptedEvent {
332 pub record_id: Option<u64>,
334 pub component: Cow<'static, str>,
336 pub reason: Cow<'static, str>,
338}
339
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
342pub enum ChannelFilter {
343 All,
345 Operational,
347 AdminOrHigher,
349 IncludeAnalytic,
351}
352
353#[derive(Debug, Clone, Default)]
355pub struct EventQueryResult {
356 pub events: Vec<Event>,
358 pub corrupted: Vec<CorruptedEvent>,
360 pub total_processed: usize,
362}
363
364use crate::error::{Error, Result};
369use std::ffi::c_void;
370use windows::Win32::System::EventLog::*;
371
372pub fn extract_event_id(handle: EVT_HANDLE) -> Result<u32> {
376 extract_variant_field(handle, 0).and_then(|v| {
377 unsafe {
378 if v.Type == 8u32 {
379 Ok(v.Anonymous.UInt32Val)
381 } else {
382 Err(Error::Other(crate::error::OtherError::new(
383 "EventID field is not UInt32",
384 )))
385 }
386 }
387 })
388}
389
390pub fn extract_provider(handle: EVT_HANDLE) -> Result<String> {
392 extract_variant_field(handle, 2).and_then(|v| {
393 unsafe {
394 if v.Type == 21u32 {
395 let pwstr = v.Anonymous.StringVal;
397 if !pwstr.is_null() {
398 let len = (0..).take_while(|&i| *pwstr.0.offset(i) != 0).count();
399 let slice = std::slice::from_raw_parts(pwstr.0, len);
400 Ok(String::from_utf16_lossy(slice))
401 } else {
402 Err(Error::Other(crate::error::OtherError::new(
403 "Provider field is null",
404 )))
405 }
406 } else {
407 Err(Error::Other(crate::error::OtherError::new(
408 "Provider field is not string",
409 )))
410 }
411 }
412 })
413}
414
415pub fn extract_level(handle: EVT_HANDLE) -> Result<u8> {
417 extract_variant_field(handle, 1).and_then(|v| {
418 unsafe {
419 if v.Type == 2u32 {
420 Ok(v.Anonymous.ByteVal)
422 } else {
423 Err(Error::Other(crate::error::OtherError::new(
424 "Level field is not byte",
425 )))
426 }
427 }
428 })
429}
430
431pub fn extract_timestamp(handle: EVT_HANDLE) -> Result<SystemTime> {
433 extract_variant_field(handle, 5).and_then(|v| {
434 unsafe {
435 if v.Type == 17u32 {
436 let filetime = v.Anonymous.FileTimeVal;
438 let intervals = filetime * 100;
439 let duration = Duration::from_nanos(intervals);
440 let epoch = UNIX_EPOCH - Duration::from_secs(11644473600); Ok(epoch + duration)
442 } else {
443 Err(Error::Other(crate::error::OtherError::new(
444 "Timestamp field is not FILETIME",
445 )))
446 }
447 }
448 })
449}
450
451pub fn extract_record_id(handle: EVT_HANDLE) -> Result<u64> {
453 extract_variant_field(handle, 6).and_then(|v| {
454 unsafe {
455 if v.Type == 10u32 {
456 Ok(v.Anonymous.UInt64Val)
458 } else {
459 Err(Error::Other(crate::error::OtherError::new(
460 "RecordID field is not UInt64",
461 )))
462 }
463 }
464 })
465}
466
467fn extract_variant_field(event_handle: EVT_HANDLE, field_index: usize) -> Result<EVT_VARIANT> {
469 let mut buffer = vec![0u8; 8192];
470 let mut buffer_used = 0u32;
471 let mut prop_count = 0u32;
472
473 let result = unsafe {
474 EvtRender(
475 EVT_HANDLE::default(),
476 event_handle,
477 EvtRenderEventValues.0,
478 buffer.len() as u32,
479 Some(buffer.as_mut_ptr() as *mut c_void),
480 &mut buffer_used,
481 &mut prop_count,
482 )
483 };
484
485 if result.is_err() {
486 return Err(Error::Other(crate::error::OtherError::new(
487 "Failed to render event values",
488 )));
489 }
490
491 let variant_ptr = buffer.as_ptr() as *const EVT_VARIANT;
492 let variants = unsafe { std::slice::from_raw_parts(variant_ptr, prop_count as usize) };
493
494 if field_index >= variants.len() {
495 return Err(Error::Other(crate::error::OtherError::new(Cow::Owned(
496 format!("Field index {} out of range", field_index),
497 ))));
498 }
499
500 Ok(variants[field_index])
501}