1use super::decode::{DecodedEvent, EventField, decode_trace_event};
4use crate::types::{ProcessId, ThreadId};
5use std::time::SystemTime;
6use windows::core::GUID;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum SystemProvider {
22 Process,
26
27 Registry,
32
33 Network,
38
39 FileIo,
44
45 ImageLoad,
50}
51
52impl SystemProvider {
53 pub(crate) fn trace_flags(self) -> u32 {
55 use windows::Win32::System::Diagnostics::Etw::*;
56 match self {
57 SystemProvider::Process => (EVENT_TRACE_FLAG_PROCESS | EVENT_TRACE_FLAG_THREAD).0,
58 SystemProvider::Registry => EVENT_TRACE_FLAG_REGISTRY.0,
59 SystemProvider::Network => EVENT_TRACE_FLAG_NETWORK_TCPIP.0,
60 SystemProvider::FileIo => (EVENT_TRACE_FLAG_FILE_IO | EVENT_TRACE_FLAG_FILE_IO_INIT).0,
61 SystemProvider::ImageLoad => EVENT_TRACE_FLAG_IMAGE_LOAD.0,
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
68#[repr(u8)]
69pub enum TraceLevel {
70 Critical = 1,
72 Error = 2,
74 Warning = 3,
76 Info = 4,
78 Verbose = 5,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct ThreadContext {
85 pub process_id: ProcessId,
87 pub thread_id: ThreadId,
89}
90
91impl ThreadContext {
92 pub fn new(process_id: ProcessId, thread_id: ThreadId) -> Self {
94 Self {
95 process_id,
96 thread_id,
97 }
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct StackTrace {
104 pub match_id: u64,
106 pub frames: Vec<u64>,
108}
109
110impl StackTrace {
111 pub fn new(match_id: u64, frames: Vec<u64>) -> Self {
113 Self { match_id, frames }
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub struct CpuSample {
120 pub processor_number: u8,
122}
123
124impl CpuSample {
125 pub fn new(processor_number: u8) -> Self {
127 Self { processor_number }
128 }
129}
130
131#[derive(Debug, Clone)]
138pub struct TraceEvent {
139 pub id: u16,
141
142 pub version: u8,
144
145 pub opcode: u8,
147
148 pub level: u8,
150
151 pub provider_guid: GUID,
153
154 pub process_id: ProcessId,
156
157 pub thread_id: ThreadId,
159
160 pub timestamp: SystemTime,
162
163 pub data: Vec<u8>,
165
166 pub thread_context: Option<ThreadContext>,
168
169 pub stack_trace: Option<StackTrace>,
171
172 pub cpu_sample: Option<CpuSample>,
174
175 fields: Option<Vec<EventField>>,
177}
178
179impl TraceEvent {
180 pub fn from_event_record(
184 record: &windows::Win32::System::Diagnostics::Etw::EVENT_RECORD,
185 ) -> Self {
186 Self::from_event_record_with_fields(record, None)
187 }
188
189 pub(crate) fn from_event_record_with_fields(
191 record: &windows::Win32::System::Diagnostics::Etw::EVENT_RECORD,
192 fields: Option<Vec<EventField>>,
193 ) -> Self {
194 let header = &record.EventHeader;
195 let desc = &header.EventDescriptor;
196
197 let timestamp = filetime_to_systemtime(header.TimeStamp);
198
199 let data = if record.UserDataLength > 0 && !record.UserData.is_null() {
200 unsafe {
201 std::slice::from_raw_parts(
202 record.UserData as *const u8,
203 record.UserDataLength as usize,
204 )
205 .to_vec()
206 }
207 } else {
208 Vec::new()
209 };
210
211 TraceEvent {
212 id: desc.Id,
213 version: desc.Version,
214 opcode: desc.Opcode,
215 level: desc.Level,
216 provider_guid: header.ProviderId,
217 process_id: ProcessId::new(header.ProcessId),
218 thread_id: ThreadId::new(header.ThreadId),
219 timestamp,
220 data,
221 thread_context: None,
222 stack_trace: None,
223 cpu_sample: None,
224 fields,
225 }
226 }
227
228 pub fn decode(&self) -> DecodedEvent {
234 decode_trace_event(self)
235 }
236
237 pub fn fields(&self) -> Option<&[EventField]> {
239 self.fields.as_deref()
240 }
241}
242
243fn filetime_to_systemtime(filetime: i64) -> SystemTime {
245 const FILETIME_TO_UNIX_EPOCH: i64 = 116_444_736_000_000_000;
247 const NANOS_PER_100NS: u32 = 100;
248
249 let intervals_since_unix = filetime.saturating_sub(FILETIME_TO_UNIX_EPOCH);
250 let seconds = (intervals_since_unix / 10_000_000) as u64;
251 let nanos = ((intervals_since_unix % 10_000_000) * NANOS_PER_100NS as i64) as u32;
252
253 SystemTime::UNIX_EPOCH + std::time::Duration::new(seconds, nanos)
254}
255
256#[cfg(test)]
257mod tests {
258 use super::{DecodedEvent, TraceEvent};
259 use crate::etw::{
260 EventField, EventFieldValue, FileIoOperation, RegistryOperation, TcpOperation,
261 };
262 use crate::types::{ProcessId, ThreadId};
263 use std::time::SystemTime;
264 use windows::Win32::System::Diagnostics::Etw::{
265 FileIoGuid, ImageLoadGuid, ProcessGuid, RegistryGuid, TcpIpGuid,
266 };
267
268 #[test]
269 fn decode_process_v0_start_event() {
270 let event = TraceEvent {
271 id: 0,
272 version: 0,
273 opcode: 1,
274 level: 0,
275 provider_guid: ProcessGuid,
276 process_id: ProcessId::new(672),
277 thread_id: ThreadId::new(0),
278 timestamp: SystemTime::UNIX_EPOCH,
279 data: vec![
280 160, 2, 0, 0, 220, 7, 0, 0, 0, 0, 0, 0, 96, 140, 79, 210, 6, 180, 255, 255, 0, 0,
281 0, 0, 0, 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 54, 245, 194, 143, 120, 21,
282 213, 94, 151, 105, 93, 135, 89, 4, 0, 0, 99, 109, 100, 46, 101, 120, 101, 0, 0, 0,
283 0, 0,
284 ],
285 thread_context: None,
286 stack_trace: None,
287 cpu_sample: None,
288 fields: None,
289 };
290
291 match event.decode() {
292 DecodedEvent::ProcessStart(decoded) => {
293 assert_eq!(decoded.process_id, ProcessId::new(672));
294 assert_eq!(decoded.parent_process_id, ProcessId::new(2012));
295 assert_eq!(decoded.image_file_name, "cmd.exe");
296 }
297 other => panic!("unexpected decode result: {other:?}"),
298 }
299 }
300
301 #[test]
302 fn decode_image_v2_v3_v4_load_event() {
303 let event = TraceEvent {
304 id: 0,
305 version: 3,
306 opcode: 10,
307 level: 0,
308 provider_guid: ImageLoadGuid,
309 process_id: ProcessId::new(3996),
310 thread_id: ThreadId::new(0),
311 timestamp: SystemTime::UNIX_EPOCH,
312 data: vec![
313 0, 0, 8, 72, 251, 127, 0, 0, 0, 32, 8, 0, 0, 0, 0, 0, 156, 15, 0, 0, 70, 110, 8, 0,
314 255, 233, 215, 246, 12, 1, 0, 0, 0, 0, 8, 72, 251, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0,
315 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 68, 0, 101, 0, 118, 0, 105, 0, 99, 0, 101, 0, 92,
316 0, 72, 0, 97, 0, 114, 0, 100, 0, 100, 0, 105, 0, 115, 0, 107, 0, 86, 0, 111, 0,
317 108, 0, 117, 0, 109, 0, 101, 0, 50, 0, 92, 0, 87, 0, 105, 0, 110, 0, 100, 0, 111,
318 0, 119, 0, 115, 0, 92, 0, 83, 0, 121, 0, 115, 0, 116, 0, 101, 0, 109, 0, 51, 0, 50,
319 0, 92, 0, 98, 0, 99, 0, 114, 0, 121, 0, 112, 0, 116, 0, 112, 0, 114, 0, 105, 0,
320 109, 0, 105, 0, 116, 0, 105, 0, 118, 0, 101, 0, 115, 0, 46, 0, 100, 0, 108, 0, 108,
321 0, 0, 0,
322 ],
323 thread_context: None,
324 stack_trace: None,
325 cpu_sample: None,
326 fields: None,
327 };
328
329 for version in [2u8, 3u8, 4u8] {
330 let mut candidate = event.clone();
331 candidate.version = version;
332
333 match candidate.decode() {
334 DecodedEvent::ImageLoad(decoded) => {
335 assert_eq!(decoded.process_id, ProcessId::new(3996));
336 assert!(decoded.file_name.ends_with("bcryptprimitives.dll"));
337 assert_eq!(decoded.version, version);
338 }
339 other => panic!("unexpected decode result for version {version}: {other:?}"),
340 }
341 }
342 }
343
344 #[test]
345 fn decode_generic_from_preparsed_fields() {
346 let event = TraceEvent {
347 id: 999,
348 version: 1,
349 opcode: 42,
350 level: 4,
351 provider_guid: windows::core::GUID::from_u128(0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa),
352 process_id: ProcessId::new(1),
353 thread_id: ThreadId::new(1),
354 timestamp: SystemTime::UNIX_EPOCH,
355 data: Vec::new(),
356 thread_context: None,
357 stack_trace: None,
358 cpu_sample: None,
359 fields: Some(vec![EventField {
360 name: "Example".to_string(),
361 value: EventFieldValue::U32(7),
362 }]),
363 };
364
365 match event.decode() {
366 DecodedEvent::Generic(fields) => {
367 assert_eq!(fields.len(), 1);
368 assert_eq!(fields[0].name, "Example");
369 }
370 other => panic!("unexpected decode result: {other:?}"),
371 }
372 }
373
374 #[test]
375 fn decode_typed_tcp_from_preparsed_fields() {
376 let event = TraceEvent {
377 id: 10,
378 version: 2,
379 opcode: 10,
380 level: 4,
381 provider_guid: TcpIpGuid,
382 process_id: ProcessId::new(1234),
383 thread_id: ThreadId::new(1),
384 timestamp: SystemTime::UNIX_EPOCH,
385 data: Vec::new(),
386 thread_context: None,
387 stack_trace: None,
388 cpu_sample: None,
389 fields: Some(vec![
390 EventField {
391 name: "PID".to_string(),
392 value: EventFieldValue::U32(1234),
393 },
394 EventField {
395 name: "saddr".to_string(),
396 value: EventFieldValue::String("10.0.0.10".to_string()),
397 },
398 EventField {
399 name: "sport".to_string(),
400 value: EventFieldValue::U16(5050),
401 },
402 EventField {
403 name: "daddr".to_string(),
404 value: EventFieldValue::String("1.1.1.1".to_string()),
405 },
406 EventField {
407 name: "dport".to_string(),
408 value: EventFieldValue::U16(443),
409 },
410 ]),
411 };
412
413 match event.decode() {
414 DecodedEvent::Tcp(tcp) => {
415 assert_eq!(tcp.operation, TcpOperation::Send);
416 assert_eq!(tcp.process_id, Some(ProcessId::new(1234)));
417 assert_eq!(tcp.destination_port, Some(443));
418 }
419 other => panic!("unexpected decode result: {other:?}"),
420 }
421 }
422
423 #[test]
424 fn decode_typed_registry_from_preparsed_fields() {
425 let event = TraceEvent {
426 id: 14,
427 version: 1,
428 opcode: 14,
429 level: 4,
430 provider_guid: RegistryGuid,
431 process_id: ProcessId::new(5678),
432 thread_id: ThreadId::new(1),
433 timestamp: SystemTime::UNIX_EPOCH,
434 data: Vec::new(),
435 thread_context: None,
436 stack_trace: None,
437 cpu_sample: None,
438 fields: Some(vec![
439 EventField {
440 name: "ProcessId".to_string(),
441 value: EventFieldValue::U32(5678),
442 },
443 EventField {
444 name: "KeyName".to_string(),
445 value: EventFieldValue::String("\\Registry\\Machine\\SOFTWARE".to_string()),
446 },
447 EventField {
448 name: "ValueName".to_string(),
449 value: EventFieldValue::String("TestValue".to_string()),
450 },
451 EventField {
452 name: "Status".to_string(),
453 value: EventFieldValue::U32(0),
454 },
455 ]),
456 };
457
458 match event.decode() {
459 DecodedEvent::Registry(reg) => {
460 assert_eq!(reg.operation, RegistryOperation::SetValue);
461 assert_eq!(reg.process_id, Some(ProcessId::new(5678)));
462 assert_eq!(reg.value_name.as_deref(), Some("TestValue"));
463 }
464 other => panic!("unexpected decode result: {other:?}"),
465 }
466 }
467
468 #[test]
469 fn decode_typed_fileio_from_preparsed_fields() {
470 let event = TraceEvent {
471 id: 32,
472 version: 1,
473 opcode: 32,
474 level: 4,
475 provider_guid: FileIoGuid,
476 process_id: ProcessId::new(2222),
477 thread_id: ThreadId::new(1),
478 timestamp: SystemTime::UNIX_EPOCH,
479 data: Vec::new(),
480 thread_context: None,
481 stack_trace: None,
482 cpu_sample: None,
483 fields: Some(vec![
484 EventField {
485 name: "ProcessId".to_string(),
486 value: EventFieldValue::U32(2222),
487 },
488 EventField {
489 name: "OpenPath".to_string(),
490 value: EventFieldValue::String("C:\\Temp\\test.txt".to_string()),
491 },
492 EventField {
493 name: "FileObject".to_string(),
494 value: EventFieldValue::U64(0x1000),
495 },
496 EventField {
497 name: "IrpPtr".to_string(),
498 value: EventFieldValue::Pointer(0x2000),
499 },
500 EventField {
501 name: "CreateOptions".to_string(),
502 value: EventFieldValue::U32(0x20),
503 },
504 ]),
505 };
506
507 match event.decode() {
508 DecodedEvent::FileIo(file) => {
509 assert_eq!(file.operation, FileIoOperation::Create);
510 assert_eq!(file.process_id, Some(ProcessId::new(2222)));
511 assert_eq!(file.open_path.as_deref(), Some("C:\\Temp\\test.txt"));
512 assert_eq!(file.file_object, Some(0x1000));
513 assert_eq!(file.irp_ptr, Some(0x2000));
514 }
515 other => panic!("unexpected decode result: {other:?}"),
516 }
517 }
518}