diff --git a/examples/dns.rs b/examples/dns.rs index 3f12ae1..1a4fb10 100644 --- a/examples/dns.rs +++ b/examples/dns.rs @@ -8,7 +8,6 @@ use ferrisetw::parser::Parser; use ferrisetw::schema_locator::SchemaLocator; use ferrisetw::native::etw_types::EventRecord; use ferrisetw::trace::UserTrace; -use ferrisetw::parser::TryParse; use ferrisetw::schema::Schema; diff --git a/examples/kernel_trace.rs b/examples/kernel_trace.rs index 9cc639e..fdc11e7 100644 --- a/examples/kernel_trace.rs +++ b/examples/kernel_trace.rs @@ -1,5 +1,5 @@ use ferrisetw::native::etw_types::EventRecord; -use ferrisetw::parser::{Parser, TryParse}; +use ferrisetw::parser::Parser; use ferrisetw::provider::*; use ferrisetw::schema_locator::SchemaLocator; use ferrisetw::trace::*; @@ -17,7 +17,7 @@ fn main() { println!("ProviderName: {}", name); let parser = Parser::create(record, &schema); // Fully Qualified Syntax for Disambiguation - match TryParse::::try_parse(&parser, "FileName") { + match parser.try_parse::("FileName") { Ok(filename) => println!("FileName: {}", filename), Err(err) => println!("Error: {:?} getting Filename", err), }; diff --git a/examples/multiple_providers.rs b/examples/multiple_providers.rs index 65d99d0..fcbf3a4 100644 --- a/examples/multiple_providers.rs +++ b/examples/multiple_providers.rs @@ -1,5 +1,5 @@ use ferrisetw::native::etw_types::EventRecord; -use ferrisetw::parser::{Parser, Pointer, TryParse}; +use ferrisetw::parser::{Parser, Pointer}; use ferrisetw::provider::*; use ferrisetw::schema_locator::SchemaLocator; use ferrisetw::trace::*; diff --git a/examples/user_trace.rs b/examples/user_trace.rs index 3bcb066..866a6dd 100644 --- a/examples/user_trace.rs +++ b/examples/user_trace.rs @@ -1,5 +1,5 @@ use ferrisetw::native::etw_types::EventRecord; -use ferrisetw::parser::{Parser, TryParse}; +use ferrisetw::parser::Parser; use ferrisetw::provider::*; use ferrisetw::schema_locator::SchemaLocator; use ferrisetw::trace::*; diff --git a/src/lib.rs b/src/lib.rs index 4be9f55..86f94c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,6 @@ //! use ferrisetw::native::etw_types::EventRecord; //! use ferrisetw::schema_locator::SchemaLocator; //! use ferrisetw::parser::Parser; -//! use ferrisetw::parser::TryParse; //! use ferrisetw::provider::Provider; //! use ferrisetw::trace::{UserTrace, TraceTrait}; //! @@ -57,8 +56,7 @@ //! // Finally, properties for a given event can be retrieved using a Parser //! let parser = Parser::create(record, &schema); //! -//! // Type annotations or Fully Qualified Syntax are needed when calling TryParse -//! // Supported types implement the trait TryParse for Parser +//! // You'll need type inference to tell ferrisetw what type you want to parse into //! // In actual code, be sure to correctly handle Err values! //! let process_id: u32 = parser.try_parse("ProcessID").unwrap(); //! let image_name: String = parser.try_parse("ImageName").unwrap(); diff --git a/src/native/tdh_types.rs b/src/native/tdh_types.rs index f9a9cd5..c5e82c3 100644 --- a/src/native/tdh_types.rs +++ b/src/native/tdh_types.rs @@ -5,10 +5,9 @@ //! event //! //! This is a bit extra but is basically a redefinition of the In an Out TDH types following the -//! rust naming convention, it can also come in handy when implementing the [TryParse] trait for a type +//! rust naming convention, it can also come in handy when implementing the `TryParse` trait for a type //! to determine how to handle a [Property] based on this values //! -//! [TryParse]: crate::parser::TryParse //! [Property]: crate::native::tdh_types::Property use num_traits::FromPrimitive; diff --git a/src/parser.rs b/src/parser.rs index 530dc5e..44b6f06 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,8 @@ //! ETW Types Parser //! //! This module act as a helper to parse the Buffer from an ETW Event + + use crate::native::etw_types::EVENT_HEADER_FLAG_32_BIT_HEADER; use crate::native::etw_types::EventRecord; use crate::native::sddl; @@ -68,22 +70,6 @@ impl From for ParserError { type ParserResult = Result; -/// Trait to try and parse a type -/// -/// This trait has to be implemented in order to be able to parse a type we want to retrieve from -/// within an Event. -/// -/// An implementation for most of the Primitive Types is created by using a Macro, any other needed type -/// requires this trait to be implemented -// TODO: Find a way to use turbofish operator -pub trait TryParse { - /// Implement the `try_parse` function to provide a way to Parse `T` from an ETW event or - /// return an Error in case the type `T` can't be parsed - /// - /// # Arguments - /// * `name` - Name of the property to be found in the Schema - fn try_parse(&self, name: &str) -> Result; -} #[derive(Default)] /// Cache of the properties we've extracted already @@ -99,6 +85,27 @@ struct CachedSlices<'schema, 'record> { /// /// This structure provides a way to parse an ETW event (= extract its properties). /// Because properties may have variable length (e.g. strings), a `Parser` is only suited to a single [`EventRecord`] +/// +/// # Example +/// ``` +/// # use ferrisetw::native::etw_types::EventRecord; +/// # use ferrisetw::schema_locator::SchemaLocator; +/// # use ferrisetw::parser::Parser; +/// let my_callback = |record: &EventRecord, schema_locator: &SchemaLocator| { +/// let schema = schema_locator.event_schema(record).unwrap(); +/// let parser = Parser::create(record, &schema); +/// +/// // There are several ways to define the type requested for `try_parse` +/// // It is possible to use type inference... +/// let property1: Option = parser.try_parse("PropertyName").ok(); +/// +/// // ...or to use the turbofish operator +/// match parser.try_parse::("OtherPropertyName") { +/// Ok(_) => println!("OtherPropertyName is a valid u32"), +/// Err(_) => println!("OtherPropertyName is invalid"), +/// } +/// }; +/// ``` #[allow(dead_code)] pub struct Parser<'schema, 'record> { properties: &'schema [Property], @@ -111,17 +118,6 @@ impl<'schema, 'record> Parser<'schema, 'record> { /// /// # Arguments /// * `schema` - The [Schema] from the ETW Event we want to parse - /// - /// # Example - /// ``` - /// # use ferrisetw::native::etw_types::EventRecord; - /// # use ferrisetw::schema_locator::SchemaLocator; - /// # use ferrisetw::parser::Parser; - /// let my_callback = |record: &EventRecord, schema_locator: &SchemaLocator| { - /// let schema = schema_locator.event_schema(record).unwrap(); - /// let parser = Parser::create(record, &schema); - /// }; - /// ``` pub fn create(event_record: &'record EventRecord, schema: &'schema Schema) -> Self { Parser { record: &event_record, @@ -253,12 +249,45 @@ impl<'schema, 'record> Parser<'schema, 'record> { Err(ParserError::NotFound) } + + /// Return a property from the event, or an error in case the parsing failed. + /// + /// You must explicitly define `T`, the type you want to parse the property into.
+ /// In case this type is not compatible with the ETW type, [`ParserError::InvalidType`] is returned. + pub fn try_parse(&self, name: &str) -> ParserResult + where Parser<'schema, 'record>: private::TryParse + { + use crate::parser::private::TryParse; + self.try_parse_impl(name) + } +} + + + +mod private { + use super::*; + + /// Trait to try and parse a type + /// + /// This trait has to be implemented in order to be able to parse a type we want to retrieve from + /// within an Event. + /// + /// An implementation for most of the Primitive Types is created by using a Macro, any other needed type + /// requires this trait to be implemented + pub trait TryParse { + /// Implement the `try_parse` function to provide a way to Parse `T` from an ETW event or + /// return an Error in case the type `T` can't be parsed + /// + /// # Arguments + /// * `name` - Name of the property to be found in the Schema + fn try_parse_impl(&self, name: &str) -> Result; + } } macro_rules! impl_try_parse_primitive { ($T:ident) => { - impl TryParse<$T> for Parser<'_, '_> { - fn try_parse(&self, name: &str) -> ParserResult<$T> { + impl private::TryParse<$T> for Parser<'_, '_> { + fn try_parse_impl(&self, name: &str) -> ParserResult<$T> { let prop_slice = self.find_property(name)?; // TODO: Check In and Out type and do a better type checking @@ -298,7 +327,7 @@ impl_try_parse_primitive!(isize); /// ``` /// # use ferrisetw::native::etw_types::EventRecord; /// # use ferrisetw::schema_locator::SchemaLocator; -/// # use ferrisetw::parser::{Parser, TryParse}; +/// # use ferrisetw::parser::Parser; /// let my_callback = |record: &EventRecord, schema_locator: &SchemaLocator| { /// let schema = schema_locator.event_schema(record).unwrap(); /// let parser = Parser::create(record, &schema); @@ -307,8 +336,8 @@ impl_try_parse_primitive!(isize); /// ``` /// /// [TdhInTypes]: TdhInType -impl TryParse for Parser<'_, '_> { - fn try_parse(&self, name: &str) -> ParserResult { +impl private::TryParse for Parser<'_, '_> { + fn try_parse_impl(&self, name: &str) -> ParserResult { let prop_slice = self.find_property(name)?; // TODO: Handle errors and type checking better @@ -334,8 +363,8 @@ impl TryParse for Parser<'_, '_> { } } -impl TryParse for Parser<'_, '_> { - fn try_parse(&self, name: &str) -> Result { +impl private::TryParse for Parser<'_, '_> { + fn try_parse_impl(&self, name: &str) -> Result { let prop_slice = self.find_property(name)?; let guid_string = utils::parse_utf16_guid(prop_slice.buffer); @@ -348,8 +377,8 @@ impl TryParse for Parser<'_, '_> { } } -impl TryParse for Parser<'_, '_> { - fn try_parse(&self, name: &str) -> ParserResult { +impl private::TryParse for Parser<'_, '_> { + fn try_parse_impl(&self, name: &str) -> ParserResult { let prop_slice = self.find_property(name)?; if prop_slice.property.out_type() != TdhOutType::OutTypeIpv4 @@ -416,23 +445,23 @@ impl std::fmt::Display for Pointer { } } -impl TryParse for Parser<'_, '_> { - fn try_parse(&self, name: &str) -> ParserResult { +impl private::TryParse for Parser<'_, '_> { + fn try_parse_impl(&self, name: &str) -> ParserResult { let prop_slice = self.find_property(name)?; let mut res = Pointer::default(); if prop_slice.buffer.len() == std::mem::size_of::() { - res.0 = TryParse::::try_parse(self, name)? as usize; + res.0 = private::TryParse::::try_parse_impl(self, name)? as usize; } else { - res.0 = TryParse::::try_parse(self, name)? as usize; + res.0 = private::TryParse::::try_parse_impl(self, name)? as usize; } Ok(res) } } -impl TryParse> for Parser<'_, '_> { - fn try_parse(&self, name: &str) -> Result, ParserError> { +impl private::TryParse> for Parser<'_, '_> { + fn try_parse_impl(&self, name: &str) -> Result, ParserError> { let prop_slice = self.find_property(name)?; Ok(prop_slice.buffer.to_vec()) } diff --git a/tests/dns.rs b/tests/dns.rs index 16d8562..7406c88 100644 --- a/tests/dns.rs +++ b/tests/dns.rs @@ -8,7 +8,7 @@ use ferrisetw::native::etw_types::EventRecord; use ferrisetw::schema_locator::SchemaLocator; use ferrisetw::trace::UserTrace; use ferrisetw::trace::TraceTrait; -use ferrisetw::parser::{Parser, TryParse}; +use ferrisetw::parser::Parser; mod utils; use utils::{Status, TestKind}; @@ -123,12 +123,12 @@ fn check_a_few_cases(record: &EventRecord, parser: &Parser) { // Parsing with a wrong type should properly error out if record.event_id() == EVENT_ID_DNS_QUERY_INITIATED { let _right_type: String = parser.try_parse("QueryName").unwrap(); - let wrong_type: Result = parser.try_parse("QueryName"); + let wrong_type = parser.try_parse::("QueryName"); assert!(wrong_type.is_err()); } // Giving an unknown property should properly error out - let wrong_name: Result = parser.try_parse("NoSuchProperty"); + let wrong_name = parser.try_parse::("NoSuchProperty"); assert!(wrong_name.is_err()); } diff --git a/tests/kernel_trace.rs b/tests/kernel_trace.rs index af52430..73646de 100644 --- a/tests/kernel_trace.rs +++ b/tests/kernel_trace.rs @@ -5,7 +5,7 @@ use std::time::Duration; use ferrisetw::provider::{Provider, EventFilter}; use ferrisetw::native::etw_types::EventRecord; use ferrisetw::schema_locator::SchemaLocator; -use ferrisetw::parser::{Parser, TryParse}; +use ferrisetw::parser::Parser; use ferrisetw::trace::KernelTrace; use ferrisetw::provider::kernel_providers; @@ -88,7 +88,7 @@ fn generate_image_load_events() { fn has_seen_dll_load(record: &EventRecord, parser: &Parser) -> bool { if record.process_id() == std::process::id() { - let filename: Result = parser.try_parse("FileName"); + let filename = parser.try_parse::("FileName"); println!(" this one's for us: {:?}", filename); if let Ok(filename) = filename { if filename.ends_with(TEST_LIBRARY_NAME) {