Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turbofish operator for try_parse #68

Merged
merged 1 commit into from
Nov 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion examples/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down
4 changes: 2 additions & 2 deletions examples/kernel_trace.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand All @@ -17,7 +17,7 @@ fn main() {
println!("ProviderName: {}", name);
let parser = Parser::create(record, &schema);
// Fully Qualified Syntax for Disambiguation
match TryParse::<String>::try_parse(&parser, "FileName") {
match parser.try_parse::<String>("FileName") {
Ok(filename) => println!("FileName: {}", filename),
Err(err) => println!("Error: {:?} getting Filename", err),
};
Expand Down
2 changes: 1 addition & 1 deletion examples/multiple_providers.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down
2 changes: 1 addition & 1 deletion examples/user_trace.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down
4 changes: 1 addition & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
//!
Expand All @@ -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();
Expand Down
3 changes: 1 addition & 2 deletions src/native/tdh_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
113 changes: 71 additions & 42 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -68,22 +70,6 @@ impl From<std::array::TryFromSliceError> for ParserError {

type ParserResult<T> = Result<T, ParserError>;

/// 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<T> {
/// 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<T, ParserError>;
}

#[derive(Default)]
/// Cache of the properties we've extracted already
Expand All @@ -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<String> = parser.try_parse("PropertyName").ok();
///
/// // ...or to use the turbofish operator
/// match parser.try_parse::<u32>("OtherPropertyName") {
/// Ok(_) => println!("OtherPropertyName is a valid u32"),
/// Err(_) => println!("OtherPropertyName is invalid"),
/// }
/// };
/// ```
#[allow(dead_code)]
pub struct Parser<'schema, 'record> {
properties: &'schema [Property],
Expand All @@ -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,
Expand Down Expand Up @@ -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.<br/>
/// In case this type is not compatible with the ETW type, [`ParserError::InvalidType`] is returned.
pub fn try_parse<T>(&self, name: &str) -> ParserResult<T>
where Parser<'schema, 'record>: private::TryParse<T>
{
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<T> {
/// 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<T, ParserError>;
}
}

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
Expand Down Expand Up @@ -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);
Expand All @@ -307,8 +336,8 @@ impl_try_parse_primitive!(isize);
/// ```
///
/// [TdhInTypes]: TdhInType
impl TryParse<String> for Parser<'_, '_> {
fn try_parse(&self, name: &str) -> ParserResult<String> {
impl private::TryParse<String> for Parser<'_, '_> {
fn try_parse_impl(&self, name: &str) -> ParserResult<String> {
let prop_slice = self.find_property(name)?;

// TODO: Handle errors and type checking better
Expand All @@ -334,8 +363,8 @@ impl TryParse<String> for Parser<'_, '_> {
}
}

impl TryParse<GUID> for Parser<'_, '_> {
fn try_parse(&self, name: &str) -> Result<GUID, ParserError> {
impl private::TryParse<GUID> for Parser<'_, '_> {
fn try_parse_impl(&self, name: &str) -> Result<GUID, ParserError> {
let prop_slice = self.find_property(name)?;

let guid_string = utils::parse_utf16_guid(prop_slice.buffer);
Expand All @@ -348,8 +377,8 @@ impl TryParse<GUID> for Parser<'_, '_> {
}
}

impl TryParse<IpAddr> for Parser<'_, '_> {
fn try_parse(&self, name: &str) -> ParserResult<IpAddr> {
impl private::TryParse<IpAddr> for Parser<'_, '_> {
fn try_parse_impl(&self, name: &str) -> ParserResult<IpAddr> {
let prop_slice = self.find_property(name)?;

if prop_slice.property.out_type() != TdhOutType::OutTypeIpv4
Expand Down Expand Up @@ -416,23 +445,23 @@ impl std::fmt::Display for Pointer {
}
}

impl TryParse<Pointer> for Parser<'_, '_> {
fn try_parse(&self, name: &str) -> ParserResult<Pointer> {
impl private::TryParse<Pointer> for Parser<'_, '_> {
fn try_parse_impl(&self, name: &str) -> ParserResult<Pointer> {
let prop_slice = self.find_property(name)?;

let mut res = Pointer::default();
if prop_slice.buffer.len() == std::mem::size_of::<u32>() {
res.0 = TryParse::<u32>::try_parse(self, name)? as usize;
res.0 = private::TryParse::<u32>::try_parse_impl(self, name)? as usize;
} else {
res.0 = TryParse::<u64>::try_parse(self, name)? as usize;
res.0 = private::TryParse::<u64>::try_parse_impl(self, name)? as usize;
}

Ok(res)
}
}

impl TryParse<Vec<u8>> for Parser<'_, '_> {
fn try_parse(&self, name: &str) -> Result<Vec<u8>, ParserError> {
impl private::TryParse<Vec<u8>> for Parser<'_, '_> {
fn try_parse_impl(&self, name: &str) -> Result<Vec<u8>, ParserError> {
let prop_slice = self.find_property(name)?;
Ok(prop_slice.buffer.to_vec())
}
Expand Down
6 changes: 3 additions & 3 deletions tests/dns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<u32, _> = parser.try_parse("QueryName");
let wrong_type = parser.try_parse::<u32>("QueryName");
assert!(wrong_type.is_err());
}

// Giving an unknown property should properly error out
let wrong_name: Result<u32, _> = parser.try_parse("NoSuchProperty");
let wrong_name = parser.try_parse::<u32>("NoSuchProperty");
assert!(wrong_name.is_err());
}

Expand Down
4 changes: 2 additions & 2 deletions tests/kernel_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<String, _> = parser.try_parse("FileName");
let filename = parser.try_parse::<String>("FileName");
println!(" this one's for us: {:?}", filename);
if let Ok(filename) = filename {
if filename.ends_with(TEST_LIBRARY_NAME) {
Expand Down