use std::collections::hash_map::Entry;
use std::rc::Rc;
use once_cell::unsync::OnceCell;
use windows::core::GUID;
use windows::Win32::System::Diagnostics::Etw::{self, EVENT_HEADER_FLAG_64_BIT_HEADER};
use super::etw_types::{DecodingSource, EventRecord, TraceEventInfoRaw};
use super::property::PropertyIter;
use super::tdh_types::Property;
use super::{tdh, FastHashMap};
#[derive(Debug)]
pub enum SchemaError {
ParseError,
TdhNativeError(tdh::TdhNativeError),
}
impl From<tdh::TdhNativeError> for SchemaError {
fn from(err: tdh::TdhNativeError) -> Self {
SchemaError::TdhNativeError(err)
}
}
type SchemaResult<T> = Result<T, SchemaError>;
#[derive(Debug, Eq, PartialEq, Hash)]
struct SchemaKey {
provider: GUID,
id: u16,
version: u8,
level: u8,
opcode: u8,
}
struct TraceLoggingProviderIds {
ids: FastHashMap<Vec<u8>, u16>,
next_id: u16,
}
impl TraceLoggingProviderIds {
fn new() -> Self {
TraceLoggingProviderIds {
ids: FastHashMap::default(),
next_id: 1,
}
}
}
impl SchemaKey {
pub fn new(event: &EventRecord, locator: &mut SchemaLocator) -> Self {
let mut id = event.EventHeader.EventDescriptor.Id;
if event.ExtendedDataCount > 0 {
let extended = unsafe {
std::slice::from_raw_parts(event.ExtendedData, event.ExtendedDataCount as usize)
};
for e in extended {
if e.ExtType as u32 == Etw::EVENT_HEADER_EXT_TYPE_EVENT_SCHEMA_TL {
let provider = locator
.tracelogging_providers
.entry(event.EventHeader.ProviderId)
.or_insert(TraceLoggingProviderIds::new());
let data = unsafe {
std::slice::from_raw_parts(e.DataPtr as *const u8, e.DataSize as usize)
};
if let Some(metadata_id) = provider.ids.get(data) {
assert_ne!(id, *metadata_id);
id = *metadata_id;
} else {
provider.ids.insert(data.to_vec(), provider.next_id);
id = provider.next_id;
provider.next_id += 1;
}
}
}
}
SchemaKey {
provider: event.EventHeader.ProviderId,
id,
version: event.EventHeader.EventDescriptor.Version,
level: event.EventHeader.EventDescriptor.Level,
opcode: event.EventHeader.EventDescriptor.Opcode,
}
}
}
#[derive(Default)]
pub struct SchemaLocator {
schemas: FastHashMap<SchemaKey, Rc<Schema>>,
tracelogging_providers: FastHashMap<GUID, TraceLoggingProviderIds>,
}
pub trait EventSchema {
fn decoding_source(&self) -> DecodingSource;
fn provider_guid(&self) -> GUID;
fn event_id(&self) -> u16;
fn opcode(&self) -> u8;
fn event_version(&self) -> u8;
fn provider_name(&self) -> String;
fn task_name(&self) -> String;
fn opcode_name(&self) -> String;
fn level(&self) -> u8;
fn property_count(&self) -> u32;
fn property(&self, index: u32) -> Property;
fn event_message(&self) -> Option<String> {
None
}
fn is_event_metadata(&self) -> bool {
false
}
}
impl std::fmt::Debug for SchemaLocator {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
}
impl SchemaLocator {
pub fn new() -> Self {
SchemaLocator {
schemas: FastHashMap::default(),
tracelogging_providers: FastHashMap::default(),
}
}
pub fn add_custom_schema(&mut self, schema: Box<dyn EventSchema>) {
let key = SchemaKey {
provider: schema.provider_guid(),
id: schema.event_id(),
opcode: schema.opcode(),
version: schema.event_version(),
level: schema.level(),
};
self.schemas.insert(key, Rc::new(Schema::new(schema)));
}
pub fn event_schema<'a>(&mut self, event: &'a EventRecord) -> SchemaResult<TypedEvent<'a>> {
let key = SchemaKey::new(event, self);
let info = match self.schemas.entry(key) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let info = Box::new(tdh::schema_from_tdh(event)?);
entry.insert(Rc::new(Schema::new(info)))
}
}
.clone();
if info.event_schema.is_event_metadata() {
let event_info = TraceEventInfoRaw::new(event.user_buffer().to_owned());
self.add_custom_schema(Box::new(event_info));
}
Ok(TypedEvent::new(event, info))
}
}
pub struct Schema {
pub event_schema: Box<dyn EventSchema>,
properties: OnceCell<PropertyIter>,
name: OnceCell<String>,
}
impl Schema {
fn new(event_schema: Box<dyn EventSchema>) -> Self {
Schema {
event_schema,
properties: OnceCell::new(),
name: OnceCell::new(),
}
}
pub(crate) fn properties(&self) -> &PropertyIter {
self.properties.get_or_init(|| PropertyIter::new(self))
}
pub(crate) fn name(&self) -> &str {
self.name.get_or_init(|| {
format!(
"{}/{}/{}",
self.event_schema.provider_name(),
self.event_schema.task_name(),
self.event_schema.opcode_name()
)
})
}
}
pub struct TypedEvent<'a> {
record: &'a EventRecord,
pub(crate) schema: Rc<Schema>,
}
impl<'a> TypedEvent<'a> {
pub fn new(record: &'a EventRecord, schema: Rc<Schema>) -> Self {
TypedEvent { record, schema }
}
pub(crate) fn user_buffer(&self) -> &[u8] {
self.record.user_buffer()
}
pub(crate) fn record(&self) -> &EventRecord {
self.record
}
pub fn event_id(&self) -> u16 {
self.record.EventHeader.EventDescriptor.Id
}
pub fn opcode(&self) -> u8 {
self.record.EventHeader.EventDescriptor.Opcode
}
pub fn event_flags(&self) -> u16 {
self.record.EventHeader.Flags
}
pub fn is_64bit(&self) -> bool {
(self.record.EventHeader.Flags & EVENT_HEADER_FLAG_64_BIT_HEADER as u16) != 0
}
pub fn event_version(&self) -> u8 {
self.record.EventHeader.EventDescriptor.Version
}
pub fn process_id(&self) -> u32 {
self.record.EventHeader.ProcessId
}
pub fn thread_id(&self) -> u32 {
self.record.EventHeader.ThreadId
}
pub fn timestamp(&self) -> i64 {
self.record.EventHeader.TimeStamp
}
pub fn activity_id(&self) -> GUID {
self.record.EventHeader.ActivityId
}
pub fn decoding_source(&self) -> DecodingSource {
self.schema.event_schema.decoding_source()
}
pub fn provider_name(&self) -> String {
self.schema.event_schema.provider_name()
}
pub fn task_name(&self) -> String {
self.schema.event_schema.task_name()
}
pub fn opcode_name(&self) -> String {
self.schema.event_schema.opcode_name()
}
pub fn property_count(&self) -> u32 {
self.schema.event_schema.property_count()
}
pub fn property(&self, index: u32) -> Property {
self.schema.event_schema.property(index)
}
pub fn name(&self) -> &str {
self.schema.name()
}
pub fn event_message(&self) -> Option<String> {
self.schema.event_schema.event_message()
}
}
impl PartialEq for TypedEvent<'_> {
fn eq(&self, other: &Self) -> bool {
self.schema.event_schema.event_id() == other.schema.event_schema.event_id()
&& self.schema.event_schema.provider_guid() == other.schema.event_schema.provider_guid()
&& self.schema.event_schema.event_version() == other.schema.event_schema.event_version()
}
}
impl Eq for TypedEvent<'_> {}