Loading floss/hcidoc/src/engine.rs +37 −3 Original line number Diff line number Diff line //! Handles stream processing of commands and events. use chrono::NaiveDateTime; use std::collections::HashMap; use std::io::Write; use crate::parser::Packet; /// Signals are pre-defined indicators that are seen in a packet stream. pub struct Signal { /// Where in the packet stream we see this signal. pub index: usize, /// Timestamp where this signal is seen. pub ts: NaiveDateTime, /// Tag identifying the signal. Signals must be pre-defined so we're going /// to enforce a static lifetime here. pub tag: &'static str, } /// Trait that describes a single rule processor. A rule should be used to represent a certain type /// of analysis (for example: ACL Connections rule may keep track of all ACL connections and report /// on failed connections). Loading @@ -16,6 +30,12 @@ pub trait Rule { /// report on the instances of this rule that were discovered or any error conditions that are /// relevant to this rule. fn report(&self, writer: &mut dyn Write); /// Report on any signals seen by this rule on the input stream so far. Signals are /// structured indicators that specify a specific type of condition that are pre-defined and /// used to bucket interesting behavior. Not all reportable events are signals but all signals /// are reportable events. fn report_signals(&self) -> &[Signal]; } /// Grouping of rules. This is used to make it easier to enable/disable certain rules for Loading @@ -39,11 +59,19 @@ impl RuleGroup { } } pub fn report(&mut self, writer: &mut dyn Write) { pub fn report(&self, writer: &mut dyn Write) { for rule in &self.rules { rule.report(writer); } } pub fn report_signals(&self, writer: &mut dyn Write) { for rule in &self.rules { for signal in rule.report_signals() { let _ = writeln!(writer, "({}, {}, {})", signal.index, signal.ts, signal.tag); } } } } /// Main entry point to process input data and run rules on them. pub struct RuleEngine { Loading @@ -66,9 +94,15 @@ impl RuleEngine { } } pub fn report(&mut self, writer: &mut dyn Write) { for group in self.groups.values_mut() { pub fn report(&self, writer: &mut dyn Write) { for group in self.groups.values() { group.report(writer); } } pub fn report_signals(&self, writer: &mut dyn Write) { for group in self.groups.values() { group.report_signals(writer); } } } floss/hcidoc/src/groups/connections.rs +35 −1 Original line number Diff line number Diff line ///! Rule group for tracking connection related issues. use chrono::NaiveDateTime; use std::collections::{HashMap, VecDeque}; use std::convert::Into; use std::io::Write; use crate::engine::{Rule, RuleGroup}; use crate::engine::{Rule, RuleGroup, Signal}; use crate::parser::{Packet, PacketChild}; use bt_packets::custom_types::Address; use bt_packets::hci::{ Loading @@ -13,6 +14,20 @@ use bt_packets::hci::{ ScoConnectionCommandChild, SubeventCode, }; enum ConnectionSignal { NocpTimeout, NocpDisconnect, } impl Into<&'static str> for ConnectionSignal { fn into(self) -> &'static str { match self { ConnectionSignal::NocpTimeout => "Nocp", ConnectionSignal::NocpDisconnect => "Nocp", } } } /// Valid values are in the range 0x0000-0x0EFF. pub type ConnectionHandle = u16; Loading Loading @@ -60,6 +75,9 @@ struct OddDisconnectionsRule { /// identify bursts. nocp_by_handle: HashMap<ConnectionHandle, NocpData>, /// Pre-defined signals discovered in the logs. signals: Vec<Signal>, /// Interesting occurrences surfaced by this rule. reportable: Vec<(NaiveDateTime, String)>, } Loading @@ -76,6 +94,7 @@ impl OddDisconnectionsRule { sco_connection_attempt: HashMap::new(), last_sco_connection_attempt: None, nocp_by_handle: HashMap::new(), signals: vec![], reportable: vec![], } } Loading Loading @@ -285,6 +304,12 @@ impl OddDisconnectionsRule { match self.nocp_by_handle.get_mut(&handle) { Some(nocp_data) => { if let Some(acl_front_ts) = nocp_data.inflight_acl_ts.pop_front() { self.signals.push(Signal { index: packet.index, ts: packet.ts.clone(), tag: ConnectionSignal::NocpDisconnect.into(), }); self.reportable.push(( packet.ts, format!("DisconnectionComplete for handle({}) showed incomplete in-flight ACL at {}", Loading Loading @@ -411,6 +436,11 @@ impl OddDisconnectionsRule { if let Some(acl_front_ts) = nocp_data.inflight_acl_ts.pop_front() { let duration_since_acl = ts.signed_duration_since(acl_front_ts); if duration_since_acl.num_milliseconds() > NOCP_CORRELATION_TIME_MS { self.signals.push(Signal { index: packet.index, ts: packet.ts.clone(), tag: ConnectionSignal::NocpTimeout.into(), }); self.reportable.push(( packet.ts, format!( Loading Loading @@ -502,6 +532,10 @@ impl Rule for OddDisconnectionsRule { } } } fn report_signals(&self) -> &[Signal] { self.signals.as_slice() } } /// Get a rule group with connection rules. Loading floss/hcidoc/src/main.rs +17 −2 Original line number Diff line number Diff line #[macro_use] extern crate num_derive; use clap::{Arg, Command}; use clap::{Arg, ArgAction, Command}; use std::io::Write; mod engine; Loading @@ -18,6 +18,12 @@ fn main() { .author("Abhishek Pandit-Subedi <abhishekpandit@google.com>") .about("Analyzes a linux HCI snoop log for specific behaviors and errors.") .arg(Arg::new("filename")) .arg( Arg::new("signals") .short('s') .action(ArgAction::SetTrue) .help("Report signals from active rules."), ) .get_matches(); let filename = match matches.get_one::<String>("filename") { Loading @@ -28,6 +34,11 @@ fn main() { } }; let report_signals = match matches.get_one::<bool>("signals") { Some(v) => *v, None => false, }; let mut parser = match LogParser::new(filename.as_str()) { Ok(p) => p, Err(e) => { Loading @@ -53,7 +64,7 @@ fn main() { if let LogType::LinuxSnoop(_header) = log_type { for (pos, v) in parser.get_snoop_iterator().expect("Not a linux snoop file").enumerate() { match Packet::try_from(&v) { match Packet::try_from((pos, &v)) { Ok(p) => engine.process(p), Err(e) => match v.opcode() { LinuxSnoopOpcodes::CommandPacket | LinuxSnoopOpcodes::EventPacket => { Loading @@ -65,5 +76,9 @@ fn main() { } engine.report(&mut writer); if report_signals { let _ = writeln!(&mut writer, "### Signals ###"); engine.report_signals(&mut writer); } } } floss/hcidoc/src/parser.rs +13 −8 Original line number Diff line number Diff line Loading @@ -107,7 +107,7 @@ pub struct LinuxSnoopPacket { } impl LinuxSnoopPacket { pub fn index(&self) -> u16 { pub fn adapter_index(&self) -> u16 { (self.flags >> 16).try_into().unwrap_or(0u16) } Loading Loading @@ -327,25 +327,30 @@ pub struct Packet { /// Which adapter this packet is for. Unassociated packets should use 0xFFFE. pub adapter_index: u16, /// Packet number in current stream. pub index: usize, /// Inner data for this packet. pub inner: PacketChild, } impl<'a> TryFrom<&'a LinuxSnoopPacket> for Packet { impl<'a> TryFrom<(usize, &'a LinuxSnoopPacket)> for Packet { type Error = String; fn try_from(item: &'a LinuxSnoopPacket) -> Result<Self, Self::Error> { match PacketChild::try_from(item) { fn try_from(item: (usize, &'a LinuxSnoopPacket)) -> Result<Self, Self::Error> { let (index, packet) = item; match PacketChild::try_from(packet) { Ok(inner) => { let base_ts = i64::try_from(item.timestamp_magic_us) let base_ts = i64::try_from(packet.timestamp_magic_us) .map_err(|e| format!("u64 conversion error: {}", e))?; let ts_secs = (base_ts / USECS_TO_SECS) + LINUX_SNOOP_OFFSET_TO_UNIXTIME_SECS; let ts_nsecs = u32::try_from((base_ts % USECS_TO_SECS) * 1000).unwrap_or(0); let ts = NaiveDateTime::from_timestamp(ts_secs, ts_nsecs); let adapter_index = item.index(); let ts = NaiveDateTime::from_timestamp_opt(ts_secs, ts_nsecs) .ok_or(format!("timestamp conversion error: {}", base_ts))?; let adapter_index = packet.adapter_index(); Ok(Packet { ts, adapter_index, inner }) Ok(Packet { ts, adapter_index, index, inner }) } Err(e) => Err(e), Loading Loading
floss/hcidoc/src/engine.rs +37 −3 Original line number Diff line number Diff line //! Handles stream processing of commands and events. use chrono::NaiveDateTime; use std::collections::HashMap; use std::io::Write; use crate::parser::Packet; /// Signals are pre-defined indicators that are seen in a packet stream. pub struct Signal { /// Where in the packet stream we see this signal. pub index: usize, /// Timestamp where this signal is seen. pub ts: NaiveDateTime, /// Tag identifying the signal. Signals must be pre-defined so we're going /// to enforce a static lifetime here. pub tag: &'static str, } /// Trait that describes a single rule processor. A rule should be used to represent a certain type /// of analysis (for example: ACL Connections rule may keep track of all ACL connections and report /// on failed connections). Loading @@ -16,6 +30,12 @@ pub trait Rule { /// report on the instances of this rule that were discovered or any error conditions that are /// relevant to this rule. fn report(&self, writer: &mut dyn Write); /// Report on any signals seen by this rule on the input stream so far. Signals are /// structured indicators that specify a specific type of condition that are pre-defined and /// used to bucket interesting behavior. Not all reportable events are signals but all signals /// are reportable events. fn report_signals(&self) -> &[Signal]; } /// Grouping of rules. This is used to make it easier to enable/disable certain rules for Loading @@ -39,11 +59,19 @@ impl RuleGroup { } } pub fn report(&mut self, writer: &mut dyn Write) { pub fn report(&self, writer: &mut dyn Write) { for rule in &self.rules { rule.report(writer); } } pub fn report_signals(&self, writer: &mut dyn Write) { for rule in &self.rules { for signal in rule.report_signals() { let _ = writeln!(writer, "({}, {}, {})", signal.index, signal.ts, signal.tag); } } } } /// Main entry point to process input data and run rules on them. pub struct RuleEngine { Loading @@ -66,9 +94,15 @@ impl RuleEngine { } } pub fn report(&mut self, writer: &mut dyn Write) { for group in self.groups.values_mut() { pub fn report(&self, writer: &mut dyn Write) { for group in self.groups.values() { group.report(writer); } } pub fn report_signals(&self, writer: &mut dyn Write) { for group in self.groups.values() { group.report_signals(writer); } } }
floss/hcidoc/src/groups/connections.rs +35 −1 Original line number Diff line number Diff line ///! Rule group for tracking connection related issues. use chrono::NaiveDateTime; use std::collections::{HashMap, VecDeque}; use std::convert::Into; use std::io::Write; use crate::engine::{Rule, RuleGroup}; use crate::engine::{Rule, RuleGroup, Signal}; use crate::parser::{Packet, PacketChild}; use bt_packets::custom_types::Address; use bt_packets::hci::{ Loading @@ -13,6 +14,20 @@ use bt_packets::hci::{ ScoConnectionCommandChild, SubeventCode, }; enum ConnectionSignal { NocpTimeout, NocpDisconnect, } impl Into<&'static str> for ConnectionSignal { fn into(self) -> &'static str { match self { ConnectionSignal::NocpTimeout => "Nocp", ConnectionSignal::NocpDisconnect => "Nocp", } } } /// Valid values are in the range 0x0000-0x0EFF. pub type ConnectionHandle = u16; Loading Loading @@ -60,6 +75,9 @@ struct OddDisconnectionsRule { /// identify bursts. nocp_by_handle: HashMap<ConnectionHandle, NocpData>, /// Pre-defined signals discovered in the logs. signals: Vec<Signal>, /// Interesting occurrences surfaced by this rule. reportable: Vec<(NaiveDateTime, String)>, } Loading @@ -76,6 +94,7 @@ impl OddDisconnectionsRule { sco_connection_attempt: HashMap::new(), last_sco_connection_attempt: None, nocp_by_handle: HashMap::new(), signals: vec![], reportable: vec![], } } Loading Loading @@ -285,6 +304,12 @@ impl OddDisconnectionsRule { match self.nocp_by_handle.get_mut(&handle) { Some(nocp_data) => { if let Some(acl_front_ts) = nocp_data.inflight_acl_ts.pop_front() { self.signals.push(Signal { index: packet.index, ts: packet.ts.clone(), tag: ConnectionSignal::NocpDisconnect.into(), }); self.reportable.push(( packet.ts, format!("DisconnectionComplete for handle({}) showed incomplete in-flight ACL at {}", Loading Loading @@ -411,6 +436,11 @@ impl OddDisconnectionsRule { if let Some(acl_front_ts) = nocp_data.inflight_acl_ts.pop_front() { let duration_since_acl = ts.signed_duration_since(acl_front_ts); if duration_since_acl.num_milliseconds() > NOCP_CORRELATION_TIME_MS { self.signals.push(Signal { index: packet.index, ts: packet.ts.clone(), tag: ConnectionSignal::NocpTimeout.into(), }); self.reportable.push(( packet.ts, format!( Loading Loading @@ -502,6 +532,10 @@ impl Rule for OddDisconnectionsRule { } } } fn report_signals(&self) -> &[Signal] { self.signals.as_slice() } } /// Get a rule group with connection rules. Loading
floss/hcidoc/src/main.rs +17 −2 Original line number Diff line number Diff line #[macro_use] extern crate num_derive; use clap::{Arg, Command}; use clap::{Arg, ArgAction, Command}; use std::io::Write; mod engine; Loading @@ -18,6 +18,12 @@ fn main() { .author("Abhishek Pandit-Subedi <abhishekpandit@google.com>") .about("Analyzes a linux HCI snoop log for specific behaviors and errors.") .arg(Arg::new("filename")) .arg( Arg::new("signals") .short('s') .action(ArgAction::SetTrue) .help("Report signals from active rules."), ) .get_matches(); let filename = match matches.get_one::<String>("filename") { Loading @@ -28,6 +34,11 @@ fn main() { } }; let report_signals = match matches.get_one::<bool>("signals") { Some(v) => *v, None => false, }; let mut parser = match LogParser::new(filename.as_str()) { Ok(p) => p, Err(e) => { Loading @@ -53,7 +64,7 @@ fn main() { if let LogType::LinuxSnoop(_header) = log_type { for (pos, v) in parser.get_snoop_iterator().expect("Not a linux snoop file").enumerate() { match Packet::try_from(&v) { match Packet::try_from((pos, &v)) { Ok(p) => engine.process(p), Err(e) => match v.opcode() { LinuxSnoopOpcodes::CommandPacket | LinuxSnoopOpcodes::EventPacket => { Loading @@ -65,5 +76,9 @@ fn main() { } engine.report(&mut writer); if report_signals { let _ = writeln!(&mut writer, "### Signals ###"); engine.report_signals(&mut writer); } } }
floss/hcidoc/src/parser.rs +13 −8 Original line number Diff line number Diff line Loading @@ -107,7 +107,7 @@ pub struct LinuxSnoopPacket { } impl LinuxSnoopPacket { pub fn index(&self) -> u16 { pub fn adapter_index(&self) -> u16 { (self.flags >> 16).try_into().unwrap_or(0u16) } Loading Loading @@ -327,25 +327,30 @@ pub struct Packet { /// Which adapter this packet is for. Unassociated packets should use 0xFFFE. pub adapter_index: u16, /// Packet number in current stream. pub index: usize, /// Inner data for this packet. pub inner: PacketChild, } impl<'a> TryFrom<&'a LinuxSnoopPacket> for Packet { impl<'a> TryFrom<(usize, &'a LinuxSnoopPacket)> for Packet { type Error = String; fn try_from(item: &'a LinuxSnoopPacket) -> Result<Self, Self::Error> { match PacketChild::try_from(item) { fn try_from(item: (usize, &'a LinuxSnoopPacket)) -> Result<Self, Self::Error> { let (index, packet) = item; match PacketChild::try_from(packet) { Ok(inner) => { let base_ts = i64::try_from(item.timestamp_magic_us) let base_ts = i64::try_from(packet.timestamp_magic_us) .map_err(|e| format!("u64 conversion error: {}", e))?; let ts_secs = (base_ts / USECS_TO_SECS) + LINUX_SNOOP_OFFSET_TO_UNIXTIME_SECS; let ts_nsecs = u32::try_from((base_ts % USECS_TO_SECS) * 1000).unwrap_or(0); let ts = NaiveDateTime::from_timestamp(ts_secs, ts_nsecs); let adapter_index = item.index(); let ts = NaiveDateTime::from_timestamp_opt(ts_secs, ts_nsecs) .ok_or(format!("timestamp conversion error: {}", base_ts))?; let adapter_index = packet.adapter_index(); Ok(Packet { ts, adapter_index, inner }) Ok(Packet { ts, adapter_index, index, inner }) } Err(e) => Err(e), Loading