Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 19941d00 authored by Abhishek Pandit-Subedi's avatar Abhishek Pandit-Subedi Committed by Automerger Merge Worker
Browse files

Merge "floss: Add signals to hcidoc" am: b844ad17

parents 754bcd1d b844ad17
Loading
Loading
Loading
Loading
+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).
@@ -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
@@ -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 {
@@ -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);
        }
    }
}
+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::{
@@ -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;

@@ -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)>,
}
@@ -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![],
        }
    }
@@ -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 {}",
@@ -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!(
@@ -502,6 +532,10 @@ impl Rule for OddDisconnectionsRule {
            }
        }
    }

    fn report_signals(&self) -> &[Signal] {
        self.signals.as_slice()
    }
}

/// Get a rule group with connection rules.
+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;
@@ -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") {
@@ -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) => {
@@ -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 => {
@@ -65,5 +76,9 @@ fn main() {
        }

        engine.report(&mut writer);
        if report_signals {
            let _ = writeln!(&mut writer, "### Signals ###");
            engine.report_signals(&mut writer);
        }
    }
}
+13 −8
Original line number Diff line number Diff line
@@ -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)
    }

@@ -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),