Skip to main content

windows_erg/evt/
render.rs

1//! Event rendering with publisher metadata caching.
2
3#![allow(dead_code)] // Some helpers may be used in future implementations
4
5use super::types::{
6    CorruptedEvent, Event, EventId, EventLevel, ProcessId, RecordId, RenderFormat, ThreadId,
7    intern_channel, intern_field_name, intern_provider,
8};
9use crate::error::Result;
10use quick_xml::Reader;
11use quick_xml::events::Event as XmlEvent;
12use std::borrow::Cow;
13use std::collections::HashMap;
14use std::ffi::c_void;
15use std::sync::{Arc, OnceLock, RwLock};
16use std::time::{Duration, SystemTime, UNIX_EPOCH};
17use windows::Win32::System::EventLog::*;
18use windows::core::PWSTR;
19
20// Static cache for publisher metadata handles to avoid repeated opens
21// Maps provider name -> EVT_HANDLE for metadata
22static PUBLISHER_METADATA_CACHE: OnceLock<Arc<RwLock<HashMap<String, EvtPublisherHandle>>>> =
23    OnceLock::new();
24
25// Wrapper for EVT_HANDLE that implements Drop for cleanup
26#[derive(Clone)]
27struct EvtPublisherHandle(EVT_HANDLE);
28
29impl Drop for EvtPublisherHandle {
30    fn drop(&mut self) {
31        if !self.0.is_invalid() {
32            unsafe {
33                let _ = EvtClose(self.0);
34            }
35        }
36    }
37}
38
39/// Get or create cached publisher metadata handle.
40#[allow(dead_code)] // Used in format_message which is WIP
41fn get_publisher_metadata(provider_name: &str) -> Result<Option<EvtPublisherHandle>> {
42    let cache = PUBLISHER_METADATA_CACHE.get_or_init(|| Arc::new(RwLock::new(HashMap::new())));
43
44    // Fast path - read lock (concurrent reads)
45    {
46        // Lock cannot be poisoned - allocations abort on OOM rather than panic
47        let map = cache.read().unwrap();
48        if let Some(handle) = map.get(provider_name) {
49            return Ok(Some(handle.clone()));
50        }
51    }
52
53    // Slow path - open metadata and cache with write lock
54    let provider_wide: Vec<u16> = provider_name
55        .encode_utf16()
56        .chain(std::iter::once(0))
57        .collect();
58
59    let handle = unsafe {
60        EvtOpenPublisherMetadata(
61            EVT_HANDLE::default(), // Local computer
62            PWSTR(provider_wide.as_ptr() as *mut u16),
63            PWSTR::null(), // Local publisher
64            0,             // Default locale
65            0,             // Flags
66        )
67    };
68
69    match handle {
70        Ok(h) => {
71            let wrapped = EvtPublisherHandle(h);
72            // Lock cannot be poisoned - allocations abort on OOM rather than panic
73            cache
74                .write()
75                .unwrap()
76                .insert(provider_name.to_string(), wrapped.clone());
77            Ok(Some(wrapped))
78        }
79        Err(_) => {
80            // Provider metadata not available (DLL missing, manifest unregistered, etc.)
81            // Return None for graceful degradation
82            Ok(None)
83        }
84    }
85}
86
87/// Render an event from a handle with selected format.
88pub fn render_event(
89    event_handle: EVT_HANDLE,
90    format: RenderFormat,
91    include_event_data: bool,
92    parse_message: bool,
93) -> std::result::Result<Event, CorruptedEvent> {
94    let mut event = match format {
95        RenderFormat::Values => render_event_values(event_handle)?,
96        RenderFormat::Xml => render_event_xml(event_handle)?,
97    };
98
99    // Extract EventData fields if requested (works best with XML format)
100    if include_event_data && format == RenderFormat::Xml {
101        // Re-render as XML to extract EventData
102        // (We already have the event from XML rendering above, just need to extract data fields)
103        // This is a small optimization opportunity - could cache the XML string
104        let mut buffer = vec![0u8; 16384];
105        let mut buffer_used = 0u32;
106        let mut prop_count = 0u32;
107
108        let result = unsafe {
109            EvtRender(
110                EVT_HANDLE::default(),
111                event_handle,
112                EvtRenderEventXml.0,
113                buffer.len() as u32,
114                Some(buffer.as_mut_ptr() as *mut c_void),
115                &mut buffer_used,
116                &mut prop_count,
117            )
118        };
119
120        if result.is_ok() {
121            let xml_bytes = &buffer[..buffer_used as usize];
122            let xml_str = String::from_utf16_lossy(unsafe {
123                std::slice::from_raw_parts(xml_bytes.as_ptr() as *const u16, xml_bytes.len() / 2)
124            });
125
126            event.data = extract_event_data_from_xml(&xml_str);
127        }
128    }
129
130    // Format message if requested
131    if parse_message {
132        event.formatted_message = format_message(event_handle, event.provider.as_ref())
133            .ok()
134            .flatten();
135    }
136
137    Ok(event)
138}
139
140/// Render event as individual values using EVT_RENDER_EVENT_VALUES.
141///
142/// This retrieves system and event data fields efficiently.
143fn render_event_values(event_handle: EVT_HANDLE) -> std::result::Result<Event, CorruptedEvent> {
144    let mut buffer = vec![0u8; 8192]; // Larger buffer for complete event data
145    let mut buffer_used = 0u32;
146    let mut prop_count = 0u32;
147
148    // Render event with system context to extract individual values
149    let result = unsafe {
150        EvtRender(
151            EVT_HANDLE::default(), // System context
152            event_handle,
153            EvtRenderEventValues.0,
154            buffer.len() as u32,
155            Some(buffer.as_mut_ptr() as *mut c_void),
156            &mut buffer_used,
157            &mut prop_count,
158        )
159    };
160
161    if result.is_err() {
162        return Err(CorruptedEvent {
163            record_id: None,
164            component: "EvtRender_Values".into(),
165            reason: "Failed to render event values".into(),
166        });
167    }
168
169    // Cast buffer to EVT_VARIANT array for property access
170    let variant_ptr = buffer.as_ptr() as *const EVT_VARIANT;
171    let variants = unsafe { std::slice::from_raw_parts(variant_ptr, prop_count as usize) };
172
173    // Extract properties from variants following Event Log schema order
174    // System properties are in fixed positions (0-9 are standard system fields)
175    let mut event = Event::default();
176
177    if !variants.is_empty() {
178        // Event ID (System/EventID)
179        if !variants.is_empty() {
180            let variant = &variants[0];
181            event.id = EventId::new(extract_u32_from_variant(variant).unwrap_or(0));
182        }
183
184        // Qualifiers/Event Level (System/Level)
185        if variants.len() > 1 {
186            let variant = &variants[1];
187            let level_val = extract_u8_from_variant(variant).unwrap_or(4);
188            event.level = EventLevel::from_code(level_val).unwrap_or(EventLevel::Verbose);
189        }
190
191        // Provider Name (System/Provider/@Name)
192        if variants.len() > 2 {
193            let variant = &variants[2];
194            if let Some(provider) = extract_string_from_variant(variant) {
195                event.provider = intern_provider(&provider);
196            }
197        }
198
199        // Channel (System/Channel)
200        if variants.len() > 3 {
201            let variant = &variants[3];
202            if let Some(channel) = extract_string_from_variant(variant) {
203                event.channel = intern_channel(&channel);
204            }
205        }
206
207        // Computer (System/Computer)
208        if variants.len() > 4 {
209            let variant = &variants[4];
210            if let Some(computer) = extract_string_from_variant(variant) {
211                event.computer = computer;
212            }
213        }
214
215        // Timestamp (System/TimeCreated/@SystemTime)
216        if variants.len() > 5 {
217            let variant = &variants[5];
218            if let Some(timestamp) = extract_systemtime_from_variant(variant) {
219                event.timestamp = Some(timestamp);
220            }
221        }
222
223        // Record ID (System/EventRecordID)
224        if variants.len() > 6 {
225            let variant = &variants[6];
226            if let Some(rid) = extract_u64_from_variant(variant) {
227                event.record_id = Some(RecordId::new(rid));
228            }
229        }
230
231        // Process ID (System/Execution/@ProcessID)
232        if variants.len() > 7 {
233            let variant = &variants[7];
234            if let Some(pid) = extract_u32_from_variant(variant) {
235                event.process_id = Some(ProcessId::new(pid));
236            }
237        }
238
239        // Thread ID (System/Execution/@ThreadID)
240        if variants.len() > 8 {
241            let variant = &variants[8];
242            if let Some(tid) = extract_u32_from_variant(variant) {
243                event.thread_id = Some(ThreadId::new(tid));
244            }
245        }
246
247        // Extract event data (user fields) if present
248        if variants.len() > 9 {
249            event.data = Some(HashMap::new());
250        }
251    }
252
253    Ok(event)
254}
255
256/// Render complete event as XML using quick-xml for robust parsing.
257///
258/// This includes all fields as an XML string for flexible parsing.
259fn render_event_xml(event_handle: EVT_HANDLE) -> std::result::Result<Event, CorruptedEvent> {
260    let mut buffer = vec![0u8; 16384]; // Larger buffer for complete XML
261    let mut buffer_used = 0u32;
262    let mut prop_count = 0u32;
263
264    let result = unsafe {
265        EvtRender(
266            EVT_HANDLE::default(),
267            event_handle,
268            EvtRenderEventXml.0,
269            buffer.len() as u32,
270            Some(buffer.as_mut_ptr() as *mut c_void),
271            &mut buffer_used,
272            &mut prop_count,
273        )
274    };
275
276    if result.is_err() {
277        return Err(CorruptedEvent {
278            record_id: None,
279            component: Cow::Borrowed("EvtRender_Xml"),
280            reason: Cow::Borrowed("Failed to render event as XML"),
281        });
282    }
283
284    // Convert buffer to XML string
285    let xml_bytes = &buffer[..buffer_used as usize];
286    let xml_str = String::from_utf16_lossy(unsafe {
287        std::slice::from_raw_parts(xml_bytes.as_ptr() as *const u16, xml_bytes.len() / 2)
288    });
289
290    // Parse using quick-xml for robust, standards-compliant parsing
291    parse_event_xml_with_quick_xml(&xml_str)
292}
293
294/// Parse Windows Event XML using quick-xml Reader API.
295///
296/// Robust event-driven parsing that handles edge cases properly.
297fn parse_event_xml_with_quick_xml(xml_str: &str) -> std::result::Result<Event, CorruptedEvent> {
298    let mut reader = Reader::from_str(xml_str);
299    reader.config_mut().trim_text(true);
300
301    let mut event = Event::default();
302    let mut buf = Vec::new();
303    let mut current_tag = String::new();
304    let mut in_system = false;
305
306    loop {
307        match reader.read_event_into(&mut buf) {
308            Ok(XmlEvent::Start(e)) => {
309                let tag_name = String::from_utf8_lossy(e.name().as_ref()).into_owned();
310
311                if tag_name == "System" {
312                    in_system = true;
313                } else if in_system {
314                    current_tag = tag_name.clone();
315
316                    // Handle elements with attributes
317                    if tag_name == "Provider" {
318                        for attr in e.attributes() {
319                            if let Ok(attr) = attr
320                                && attr.key.as_ref() == b"Name"
321                                && let Ok(value) = String::from_utf8(attr.value.to_vec())
322                            {
323                                event.provider = intern_provider(&value);
324                            }
325                        }
326                    } else if tag_name == "TimeCreated" {
327                        for attr in e.attributes() {
328                            if let Ok(attr) = attr
329                                && attr.key.as_ref() == b"SystemTime"
330                                && let Ok(value) = String::from_utf8(attr.value.to_vec())
331                            {
332                                event.timestamp = parse_iso8601_timestamp(&value);
333                            }
334                        }
335                    }
336                }
337            }
338            Ok(XmlEvent::End(e)) => {
339                let tag_name = String::from_utf8_lossy(e.name().as_ref()).into_owned();
340                if tag_name == "System" {
341                    in_system = false;
342                }
343                current_tag.clear();
344            }
345            Ok(XmlEvent::Text(e)) if in_system && !current_tag.is_empty() => {
346                let value = String::from_utf8_lossy(e.as_ref()).into_owned();
347
348                match current_tag.as_str() {
349                    "EventID" => {
350                        event.id = EventId::new(value.parse().unwrap_or(0));
351                    }
352                    "Level" => {
353                        let level_val: u8 = value.parse().unwrap_or(4);
354                        event.level =
355                            EventLevel::from_code(level_val).unwrap_or(EventLevel::Verbose);
356                    }
357                    "Channel" => {
358                        event.channel = intern_channel(&value);
359                    }
360                    "Computer" => {
361                        event.computer = value;
362                    }
363                    "EventRecordID" => {
364                        if let Ok(rid) = value.parse::<u64>() {
365                            event.record_id = Some(RecordId::new(rid));
366                        }
367                    }
368                    _ => {}
369                }
370            }
371            Ok(XmlEvent::Empty(e)) => {
372                // Handle self-closing tags like <Execution ProcessID="1234" ThreadID="5678"/>
373                let tag_name = String::from_utf8_lossy(e.name().as_ref()).into_owned();
374
375                if in_system && tag_name == "Execution" {
376                    for attr in e.attributes().flatten() {
377                        let attr_name = String::from_utf8_lossy(attr.key.as_ref());
378                        if let Ok(value) = String::from_utf8(attr.value.to_vec()) {
379                            match attr_name.as_ref() {
380                                "ProcessID" => {
381                                    if let Ok(pid) = value.parse::<u32>() {
382                                        event.process_id = Some(ProcessId::new(pid));
383                                    }
384                                }
385                                "ThreadID" => {
386                                    if let Ok(tid) = value.parse::<u32>() {
387                                        event.thread_id = Some(ThreadId::new(tid));
388                                    }
389                                }
390                                _ => {}
391                            }
392                        }
393                    }
394                }
395            }
396            Ok(XmlEvent::Eof) => break,
397            Err(_) => {
398                return Err(CorruptedEvent {
399                    record_id: event.record_id.map(|r| r.as_u64()),
400                    component: Cow::Borrowed("quick-xml"),
401                    reason: Cow::Borrowed("XML parsing error"),
402                });
403            }
404            _ => {}
405        }
406        buf.clear();
407    }
408
409    Ok(event)
410}
411
412/// Extract EventData fields from XML using quick-xml Reader API.
413///
414/// Returns HashMap with interned field names for performance.
415/// Silently skips malformed fields for defensive parsing.
416fn extract_event_data_from_xml(xml: &str) -> Option<HashMap<Cow<'static, str>, String>> {
417    let mut reader = Reader::from_str(xml);
418    reader.config_mut().trim_text(true);
419
420    let mut data_fields = HashMap::new();
421    let mut buf = Vec::new();
422    let mut in_event_data = false;
423    let mut current_field_name: Option<String> = None;
424
425    loop {
426        match reader.read_event_into(&mut buf) {
427            Ok(XmlEvent::Start(e)) if e.name().as_ref() == b"EventData" => {
428                in_event_data = true;
429            }
430            Ok(XmlEvent::End(e)) if e.name().as_ref() == b"EventData" => {
431                in_event_data = false;
432            }
433            Ok(XmlEvent::Start(e)) if in_event_data && e.name().as_ref() == b"Data" => {
434                // Extract Name attribute
435                current_field_name = e
436                    .attributes()
437                    .filter_map(|a| a.ok())
438                    .find(|attr| attr.key.as_ref() == b"Name")
439                    .and_then(|attr| String::from_utf8(attr.value.to_vec()).ok());
440            }
441            Ok(XmlEvent::Text(e)) if in_event_data && current_field_name.is_some() => {
442                // Extract value and intern field name
443                let value = String::from_utf8_lossy(e.as_ref()).into_owned();
444                let field_name = current_field_name.take().unwrap();
445                data_fields.insert(intern_field_name(&field_name), value);
446            }
447            Ok(XmlEvent::Eof) => break,
448            Err(_) => break, // Silently skip on parse error
449            _ => {}
450        }
451        buf.clear();
452    }
453
454    if data_fields.is_empty() {
455        None
456    } else {
457        Some(data_fields)
458    }
459}
460
461/// Parse Windows FILETIME string (ISO 8601 format) to SystemTime.
462///
463/// Parses format: "2024-01-15T10:30:45.123456Z"
464fn parse_iso8601_timestamp(time_str: &str) -> Option<SystemTime> {
465    // Ensure minimum length
466    if time_str.len() < 20 {
467        return None;
468    }
469
470    // Extract components (2024-01-15T10:30:45.123456Z)
471    let year: i32 = time_str.get(0..4)?.parse().ok()?;
472    let month: u32 = time_str.get(5..7)?.parse().ok()?;
473    let day: u32 = time_str.get(8..10)?.parse().ok()?;
474    let hour: u32 = time_str.get(11..13)?.parse().ok()?;
475    let minute: u32 = time_str.get(14..16)?.parse().ok()?;
476    let second: u32 = time_str.get(17..19)?.parse().ok()?;
477
478    // Extract microseconds if present
479    let microseconds: u32 = if time_str.len() > 20 && time_str.as_bytes()[19] == b'.' {
480        time_str.get(20..26)?.parse().ok().unwrap_or(0)
481    } else {
482        0
483    };
484
485    // Calculate days since Unix epoch (1970-01-01)
486    let mut days_since_epoch: i64 = 0;
487
488    // Add days for complete years
489    for y in 1970..year {
490        days_since_epoch += if is_leap_year(y) { 366 } else { 365 };
491    }
492
493    // Add days for complete months in current year
494    const DAYS_IN_MONTH: [u32; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
495    for m in 1..month {
496        days_since_epoch += DAYS_IN_MONTH[(m - 1) as usize] as i64;
497        // Add leap day if February and leap year
498        if m == 2 && is_leap_year(year) {
499            days_since_epoch += 1;
500        }
501    }
502
503    // Add remaining days
504    days_since_epoch += (day - 1) as i64;
505
506    // Calculate total seconds
507    let total_seconds =
508        days_since_epoch * 86400 + (hour as i64 * 3600) + (minute as i64 * 60) + second as i64;
509
510    // Build SystemTime
511    let duration =
512        Duration::from_secs(total_seconds as u64) + Duration::from_micros(microseconds as u64);
513    Some(UNIX_EPOCH + duration)
514}
515
516/// Check if a year is a leap year.
517fn is_leap_year(year: i32) -> bool {
518    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
519}
520
521// Helper functions for variant extraction
522#[allow(dead_code)]
523fn extract_u8_from_variant(variant: &EVT_VARIANT) -> Option<u8> {
524    unsafe {
525        if variant.Type == 2u32 {
526            // EvtVarTypeByte
527            Some(variant.Anonymous.ByteVal)
528        } else {
529            None
530        }
531    }
532}
533
534fn extract_u16_from_variant(variant: &EVT_VARIANT) -> Option<u16> {
535    unsafe {
536        if variant.Type == 6u32 {
537            // EvtVarTypeUInt16
538            Some(variant.Anonymous.UInt16Val)
539        } else {
540            None
541        }
542    }
543}
544
545fn extract_u32_from_variant(variant: &EVT_VARIANT) -> Option<u32> {
546    unsafe {
547        if variant.Type == 8u32 {
548            // EvtVarTypeUInt32
549            Some(variant.Anonymous.UInt32Val)
550        } else {
551            None
552        }
553    }
554}
555
556fn extract_u64_from_variant(variant: &EVT_VARIANT) -> Option<u64> {
557    unsafe {
558        if variant.Type == 10u32 {
559            // EvtVarTypeUInt64
560            Some(variant.Anonymous.UInt64Val)
561        } else {
562            None
563        }
564    }
565}
566
567fn extract_string_from_variant(variant: &EVT_VARIANT) -> Option<String> {
568    unsafe {
569        if variant.Type == 21u32 {
570            // EvtVarTypeString
571            let pwstr = variant.Anonymous.StringVal;
572            if !pwstr.is_null() {
573                let len = (0..).take_while(|&i| *pwstr.0.offset(i) != 0).count();
574                let slice = std::slice::from_raw_parts(pwstr.0, len);
575                return Some(String::from_utf16_lossy(slice).to_string());
576            }
577        }
578        None
579    }
580}
581
582fn extract_systemtime_from_variant(variant: &EVT_VARIANT) -> Option<SystemTime> {
583    unsafe {
584        if variant.Type == 29u32 {
585            // EvtVarTypeFileTime
586            let filetime = variant.Anonymous.FileTimeVal;
587            // Windows FILETIME is 100-nanosecond intervals since 1601-01-01
588            // Convert to SystemTime (Unix epoch)
589            const FILETIME_EPOCH_DIFF: u64 = 116444736000000000; // 100-nanosecond intervals from 1601 to 1970
590
591            if filetime > FILETIME_EPOCH_DIFF {
592                let unix_time_100ns = filetime - FILETIME_EPOCH_DIFF;
593                let seconds = unix_time_100ns / 10_000_000;
594                let nanos = (unix_time_100ns % 10_000_000) * 100;
595
596                return Some(UNIX_EPOCH + std::time::Duration::new(seconds, nanos as u32));
597            }
598        }
599        None
600    }
601}
602
603/// Format a message for an event using provider metadata.
604///
605/// This is optional and can be expensive if metadata isn't cached.
606/// Call only when message rendering is needed.
607pub fn format_message(event_handle: EVT_HANDLE, provider_name: &str) -> Result<Option<String>> {
608    // Get or cache the publisher metadata
609    let metadata = get_publisher_metadata(provider_name)?;
610
611    // Return None if metadata unavailable (graceful degradation)
612    let Some(metadata_handle) = metadata else {
613        return Ok(None);
614    };
615
616    // Use EvtFormatMessage to get the formatted message
617    let mut buffer_size = 4096u32;
618    let mut buffer: Vec<u16>;
619
620    loop {
621        buffer = vec![0u16; (buffer_size / 2) as usize];
622        let mut buffer_used = 0u32;
623
624        let result = unsafe {
625            EvtFormatMessage(
626                metadata_handle.0,
627                event_handle,
628                0,                       // No message ID (use event's own message)
629                None,                    // No values array
630                EvtFormatMessageEvent.0, // Format the event message
631                Some(&mut buffer[..]),
632                &mut buffer_used,
633            )
634        };
635
636        if result.is_ok() {
637            // Success - convert to string
638            let len = if buffer_used > 0 {
639                (buffer_used / 2) as usize
640            } else {
641                0
642            };
643            if len > 0 && len <= buffer.len() {
644                // Trim null terminator
645                let actual_len = if len > 0 && buffer[len - 1] == 0 {
646                    len - 1
647                } else {
648                    len
649                };
650                let message = String::from_utf16_lossy(&buffer[..actual_len]);
651                return Ok(Some(message));
652            }
653            return Ok(None);
654        }
655
656        // Check error code
657        let error = unsafe { windows::Win32::Foundation::GetLastError() };
658        if error.0 == 122 {
659            // ERROR_INSUFFICIENT_BUFFER
660            // Resize and try again
661            buffer_size = buffer_used;
662            if buffer_size > 1048576 {
663                // 1MB limit
664                return Ok(None); // Message too large, give up
665            }
666            continue;
667        } else {
668            // Other error - metadata might be unavailable or event has no message
669            return Ok(None);
670        }
671    }
672}
673
674#[cfg(test)]
675mod tests {
676    use super::*;
677
678    #[test]
679    fn test_publisher_metadata_cache_initialization() {
680        // Ensure cache initializes properly
681        let cache = PUBLISHER_METADATA_CACHE.get_or_init(|| Arc::new(RwLock::new(HashMap::new())));
682
683        let map = cache.read().unwrap();
684        assert!(map.is_empty());
685    }
686}