Loading floss/hcidoc/src/groups/connections.rs +68 −35 Original line number Diff line number Diff line Loading @@ -8,9 +8,9 @@ use crate::engine::{Rule, RuleGroup, Signal}; use crate::parser::{Packet, PacketChild}; use bt_packets::hci::{ Acl, AclCommandChild, Address, CommandChild, CommandStatus, ConnectionManagementCommandChild, ErrorCode, Event, EventChild, LeConnectionManagementCommandChild, LeMetaEventChild, NumberOfCompletedPackets, OpCode, ScoConnectionCommandChild, SecurityCommandChild, SubeventCode, DisconnectReason, ErrorCode, Event, EventChild, LeConnectionManagementCommandChild, LeMetaEventChild, NumberOfCompletedPackets, OpCode, ScoConnectionCommandChild, SecurityCommandChild, SubeventCode, }; enum ConnectionSignal { Loading Loading @@ -56,9 +56,6 @@ impl NocpData { /// Keeps track of connections and identifies odd disconnections. struct OddDisconnectionsRule { /// Timestamp on first packet in current log. start_of_log: Option<NaiveDateTime>, /// Handles that had successful complete connections. The value has the timestamp of the /// connection completion and the address of the device. active_handles: HashMap<ConnectionHandle, (NaiveDateTime, Address)>, Loading Loading @@ -86,7 +83,6 @@ struct OddDisconnectionsRule { impl OddDisconnectionsRule { pub fn new() -> Self { OddDisconnectionsRule { start_of_log: None, active_handles: HashMap::new(), connection_attempt: HashMap::new(), last_connection_attempt: None, Loading @@ -100,7 +96,7 @@ impl OddDisconnectionsRule { } } pub fn process_classic_connection( fn process_classic_connection( &mut self, conn: &ConnectionManagementCommandChild, packet: &Packet, Loading @@ -127,11 +123,7 @@ impl OddDisconnectionsRule { } } pub fn process_sco_connection( &mut self, sco_conn: &ScoConnectionCommandChild, packet: &Packet, ) { fn process_sco_connection(&mut self, sco_conn: &ScoConnectionCommandChild, packet: &Packet) { let handle = match sco_conn { ScoConnectionCommandChild::SetupSynchronousConnection(ssc) => { ssc.get_connection_handle() Loading Loading @@ -178,7 +170,7 @@ impl OddDisconnectionsRule { } } pub fn process_le_conn_connection( fn process_le_conn_connection( &mut self, le_conn: &LeConnectionManagementCommandChild, packet: &Packet, Loading Loading @@ -206,7 +198,7 @@ impl OddDisconnectionsRule { } } pub fn process_command_status(&mut self, cs: &CommandStatus, packet: &Packet) { fn process_command_status(&mut self, cs: &CommandStatus, packet: &Packet) { // Clear last connection attempt since it was successful. let last_address = match cs.get_command_op_code() { OpCode::CreateConnection | OpCode::AcceptConnectionRequest => { Loading Loading @@ -264,7 +256,7 @@ impl OddDisconnectionsRule { } } pub fn process_event(&mut self, ev: &Event, packet: &Packet) { fn process_event(&mut self, ev: &Event, packet: &Packet) { match ev.specialize() { EventChild::ConnectionComplete(cc) => { match self.connection_attempt.remove(&cc.get_bd_addr()) { Loading Loading @@ -322,16 +314,8 @@ impl OddDisconnectionsRule { } } None => { self.reportable.push(( packet.ts, format!( "DisconnectionComplete for unknown handle {} with status={:?}", dsc.get_connection_handle(), dsc.get_status() ), )); } // No issue if none, probably device is connected before snoop started. None => (), } // Remove nocp information for handles that were removed. Loading Loading @@ -413,7 +397,7 @@ impl OddDisconnectionsRule { } } pub fn process_acl_tx(&mut self, acl_tx: &Acl, packet: &Packet) { fn process_acl_tx(&mut self, acl_tx: &Acl, packet: &Packet) { let handle = acl_tx.get_handle(); // Insert empty Nocp data for handle if it doesn't exist. Loading @@ -426,7 +410,7 @@ impl OddDisconnectionsRule { } } pub fn process_nocp(&mut self, nocp: &NumberOfCompletedPackets, packet: &Packet) { fn process_nocp(&mut self, nocp: &NumberOfCompletedPackets, packet: &Packet) { let ts = &packet.ts; for completed_packet in nocp.get_completed_packets() { let handle = completed_packet.connection_handle; Loading Loading @@ -456,28 +440,52 @@ impl OddDisconnectionsRule { } } } fn process_reset(&mut self) { self.active_handles.clear(); self.connection_attempt.clear(); self.last_connection_attempt = None; self.le_connection_attempt.clear(); self.last_le_connection_attempt = None; self.sco_connection_attempt.clear(); self.last_sco_connection_attempt = None; self.nocp_by_handle.clear(); } } impl Rule for OddDisconnectionsRule { fn process(&mut self, packet: &Packet) { if self.start_of_log.is_none() { self.start_of_log = Some(packet.ts.clone()); } match &packet.inner { PacketChild::HciCommand(cmd) => match cmd.specialize() { CommandChild::AclCommand(aclpkt) => match aclpkt.specialize() { AclCommandChild::ConnectionManagementCommand(conn) => { self.process_classic_connection(&conn.specialize(), packet) self.process_classic_connection(&conn.specialize(), packet); } AclCommandChild::ScoConnectionCommand(sco_conn) => { self.process_sco_connection(&sco_conn.specialize(), packet) self.process_sco_connection(&sco_conn.specialize(), packet); } AclCommandChild::LeConnectionManagementCommand(le_conn) => { self.process_le_conn_connection(&le_conn.specialize(), packet) self.process_le_conn_connection(&le_conn.specialize(), packet); } AclCommandChild::Disconnect(dc_conn) => { // If reason is power off, the host might not wait for connection complete event if dc_conn.get_reason() == DisconnectReason::RemoteDeviceTerminatedConnectionPowerOff { let handle = dc_conn.get_connection_handle(); self.active_handles.remove(&handle); self.nocp_by_handle.remove(&handle); } } // end acl pkt _ => (), }, CommandChild::Reset(_) => { self.process_reset(); } // end hci command _ => (), }, Loading @@ -493,6 +501,8 @@ impl Rule for OddDisconnectionsRule { | OpCode::LeExtendedCreateConnection => { self.process_command_status(&cs, packet); } // end command status _ => (), }, Loading @@ -513,6 +523,7 @@ impl Rule for OddDisconnectionsRule { self.process_nocp(&nocp, packet); } // end hci event _ => (), }, Loading Loading @@ -639,6 +650,23 @@ impl Rule for LinkKeyMismatchRule { }, PacketChild::HciCommand(cmd) => match cmd.specialize() { CommandChild::AclCommand(cmd) => match cmd.specialize() { AclCommandChild::Disconnect(cmd) => { // If reason is power off, the host might not wait for connection complete event if cmd.get_reason() == DisconnectReason::RemoteDeviceTerminatedConnectionPowerOff { if let Some(address) = self.handles.remove(&cmd.get_connection_handle()) { self.states.remove(&address); } } } // CommandChild::AclCommand(cmd).specialize() _ => {} }, CommandChild::SecurityCommand(cmd) => match cmd.specialize() { SecurityCommandChild::LinkKeyRequestReply(cmd) => { let address = cmd.get_bd_addr(); Loading @@ -655,6 +683,11 @@ impl Rule for LinkKeyMismatchRule { _ => {} }, CommandChild::Reset(_) => { self.states.clear(); self.handles.clear(); } // PacketChild::HciCommand(cmd).specialize() _ => {} }, Loading floss/hcidoc/src/groups/informational.rs +23 −2 Original line number Diff line number Diff line Loading @@ -8,8 +8,8 @@ use std::io::Write; use crate::engine::{Rule, RuleGroup, Signal}; use crate::parser::{Packet, PacketChild}; use bt_packets::hci::{ AclCommandChild, Address, CommandChild, ConnectionManagementCommandChild, ErrorCode, EventChild, GapData, GapDataType, LeMetaEventChild, AclCommandChild, Address, CommandChild, ConnectionManagementCommandChild, DisconnectReason, ErrorCode, EventChild, GapData, GapDataType, LeMetaEventChild, }; /// Valid values are in the range 0x0000-0x0EFF. Loading Loading @@ -241,6 +241,14 @@ impl InformationalRule { self.handles.remove(&handle); } fn report_reset(&mut self, ts: NaiveDateTime) { // report_connection_end removes the entries from the map, so store all the keys first. let handles: Vec<ConnectionHandle> = self.handles.keys().cloned().collect(); for handle in handles { self.report_connection_end(handle, ts); } } fn process_gap_data(&mut self, address: &Address, data: &GapData) { match data.data_type { GapDataType::CompleteLocalName | GapDataType::ShortenedLocalName => { Loading Loading @@ -365,6 +373,10 @@ impl Rule for InformationalRule { }, PacketChild::HciCommand(cmd) => match cmd.specialize() { CommandChild::Reset(_cmd) => { self.report_reset(packet.ts); } CommandChild::AclCommand(cmd) => match cmd.specialize() { AclCommandChild::ConnectionManagementCommand(cmd) => match cmd.specialize() { ConnectionManagementCommandChild::CreateConnection(cmd) => { Loading @@ -381,6 +393,15 @@ impl Rule for InformationalRule { _ => {} }, AclCommandChild::Disconnect(cmd) => { // If reason is power off, the host might not wait for connection complete event if cmd.get_reason() == DisconnectReason::RemoteDeviceTerminatedConnectionPowerOff { self.report_connection_end(cmd.get_connection_handle(), packet.ts); } } // CommandChild::AclCommand(cmd).specialize() _ => {} }, Loading floss/hcidoc/src/main.rs +40 −7 Original line number Diff line number Diff line Loading @@ -18,12 +18,25 @@ fn main() { Arg::new("filename") .help("Path to the snoop log. If omitted, read from stdin instead."), ) .arg( Arg::new("ignore-unknown") .long("ignore-unknown") .action(ArgAction::SetTrue) .help("Don't print warning for unknown opcodes"), ) .arg( Arg::new("signals") .short('s') .long("signals") .action(ArgAction::SetTrue) .help("Report signals from active rules."), ) .arg( Arg::new("signals-only") .long("signals-only") .action(ArgAction::SetTrue) .help("Only print signals from active rules, don't print other events."), ) .get_matches(); let filename = match matches.get_one::<String>("filename") { Loading @@ -31,11 +44,25 @@ fn main() { None => "", }; let report_signals = match matches.get_one::<bool>("signals") { let ignore_unknown_opcode = match matches.get_one::<bool>("ignore-unknown") { Some(v) => *v, None => false, }; let mut report_signals = match matches.get_one::<bool>("signals") { Some(v) => *v, None => false, }; let report_only_signals = match matches.get_one::<bool>("signals-only") { Some(v) => *v, None => false, }; if report_only_signals { report_signals = true; } let mut parser = match LogParser::new(filename) { Ok(p) => p, Err(e) => { Loading Loading @@ -70,16 +97,22 @@ fn main() { for (pos, v) in parser.get_snoop_iterator().expect("Not a linux snoop file").enumerate() { match Packet::try_from((pos, &v)) { Ok(p) => engine.process(p), Err(e) => match v.opcode() { Err(e) => { if !ignore_unknown_opcode { match v.opcode() { LinuxSnoopOpcodes::Command | LinuxSnoopOpcodes::Event => { eprintln!("#{}: {}", pos, e); } _ => (), }, } } } } } if !report_only_signals { engine.report(&mut writer); } if report_signals { let _ = writeln!(&mut writer, "### Signals ###"); engine.report_signals(&mut writer); Loading Loading
floss/hcidoc/src/groups/connections.rs +68 −35 Original line number Diff line number Diff line Loading @@ -8,9 +8,9 @@ use crate::engine::{Rule, RuleGroup, Signal}; use crate::parser::{Packet, PacketChild}; use bt_packets::hci::{ Acl, AclCommandChild, Address, CommandChild, CommandStatus, ConnectionManagementCommandChild, ErrorCode, Event, EventChild, LeConnectionManagementCommandChild, LeMetaEventChild, NumberOfCompletedPackets, OpCode, ScoConnectionCommandChild, SecurityCommandChild, SubeventCode, DisconnectReason, ErrorCode, Event, EventChild, LeConnectionManagementCommandChild, LeMetaEventChild, NumberOfCompletedPackets, OpCode, ScoConnectionCommandChild, SecurityCommandChild, SubeventCode, }; enum ConnectionSignal { Loading Loading @@ -56,9 +56,6 @@ impl NocpData { /// Keeps track of connections and identifies odd disconnections. struct OddDisconnectionsRule { /// Timestamp on first packet in current log. start_of_log: Option<NaiveDateTime>, /// Handles that had successful complete connections. The value has the timestamp of the /// connection completion and the address of the device. active_handles: HashMap<ConnectionHandle, (NaiveDateTime, Address)>, Loading Loading @@ -86,7 +83,6 @@ struct OddDisconnectionsRule { impl OddDisconnectionsRule { pub fn new() -> Self { OddDisconnectionsRule { start_of_log: None, active_handles: HashMap::new(), connection_attempt: HashMap::new(), last_connection_attempt: None, Loading @@ -100,7 +96,7 @@ impl OddDisconnectionsRule { } } pub fn process_classic_connection( fn process_classic_connection( &mut self, conn: &ConnectionManagementCommandChild, packet: &Packet, Loading @@ -127,11 +123,7 @@ impl OddDisconnectionsRule { } } pub fn process_sco_connection( &mut self, sco_conn: &ScoConnectionCommandChild, packet: &Packet, ) { fn process_sco_connection(&mut self, sco_conn: &ScoConnectionCommandChild, packet: &Packet) { let handle = match sco_conn { ScoConnectionCommandChild::SetupSynchronousConnection(ssc) => { ssc.get_connection_handle() Loading Loading @@ -178,7 +170,7 @@ impl OddDisconnectionsRule { } } pub fn process_le_conn_connection( fn process_le_conn_connection( &mut self, le_conn: &LeConnectionManagementCommandChild, packet: &Packet, Loading Loading @@ -206,7 +198,7 @@ impl OddDisconnectionsRule { } } pub fn process_command_status(&mut self, cs: &CommandStatus, packet: &Packet) { fn process_command_status(&mut self, cs: &CommandStatus, packet: &Packet) { // Clear last connection attempt since it was successful. let last_address = match cs.get_command_op_code() { OpCode::CreateConnection | OpCode::AcceptConnectionRequest => { Loading Loading @@ -264,7 +256,7 @@ impl OddDisconnectionsRule { } } pub fn process_event(&mut self, ev: &Event, packet: &Packet) { fn process_event(&mut self, ev: &Event, packet: &Packet) { match ev.specialize() { EventChild::ConnectionComplete(cc) => { match self.connection_attempt.remove(&cc.get_bd_addr()) { Loading Loading @@ -322,16 +314,8 @@ impl OddDisconnectionsRule { } } None => { self.reportable.push(( packet.ts, format!( "DisconnectionComplete for unknown handle {} with status={:?}", dsc.get_connection_handle(), dsc.get_status() ), )); } // No issue if none, probably device is connected before snoop started. None => (), } // Remove nocp information for handles that were removed. Loading Loading @@ -413,7 +397,7 @@ impl OddDisconnectionsRule { } } pub fn process_acl_tx(&mut self, acl_tx: &Acl, packet: &Packet) { fn process_acl_tx(&mut self, acl_tx: &Acl, packet: &Packet) { let handle = acl_tx.get_handle(); // Insert empty Nocp data for handle if it doesn't exist. Loading @@ -426,7 +410,7 @@ impl OddDisconnectionsRule { } } pub fn process_nocp(&mut self, nocp: &NumberOfCompletedPackets, packet: &Packet) { fn process_nocp(&mut self, nocp: &NumberOfCompletedPackets, packet: &Packet) { let ts = &packet.ts; for completed_packet in nocp.get_completed_packets() { let handle = completed_packet.connection_handle; Loading Loading @@ -456,28 +440,52 @@ impl OddDisconnectionsRule { } } } fn process_reset(&mut self) { self.active_handles.clear(); self.connection_attempt.clear(); self.last_connection_attempt = None; self.le_connection_attempt.clear(); self.last_le_connection_attempt = None; self.sco_connection_attempt.clear(); self.last_sco_connection_attempt = None; self.nocp_by_handle.clear(); } } impl Rule for OddDisconnectionsRule { fn process(&mut self, packet: &Packet) { if self.start_of_log.is_none() { self.start_of_log = Some(packet.ts.clone()); } match &packet.inner { PacketChild::HciCommand(cmd) => match cmd.specialize() { CommandChild::AclCommand(aclpkt) => match aclpkt.specialize() { AclCommandChild::ConnectionManagementCommand(conn) => { self.process_classic_connection(&conn.specialize(), packet) self.process_classic_connection(&conn.specialize(), packet); } AclCommandChild::ScoConnectionCommand(sco_conn) => { self.process_sco_connection(&sco_conn.specialize(), packet) self.process_sco_connection(&sco_conn.specialize(), packet); } AclCommandChild::LeConnectionManagementCommand(le_conn) => { self.process_le_conn_connection(&le_conn.specialize(), packet) self.process_le_conn_connection(&le_conn.specialize(), packet); } AclCommandChild::Disconnect(dc_conn) => { // If reason is power off, the host might not wait for connection complete event if dc_conn.get_reason() == DisconnectReason::RemoteDeviceTerminatedConnectionPowerOff { let handle = dc_conn.get_connection_handle(); self.active_handles.remove(&handle); self.nocp_by_handle.remove(&handle); } } // end acl pkt _ => (), }, CommandChild::Reset(_) => { self.process_reset(); } // end hci command _ => (), }, Loading @@ -493,6 +501,8 @@ impl Rule for OddDisconnectionsRule { | OpCode::LeExtendedCreateConnection => { self.process_command_status(&cs, packet); } // end command status _ => (), }, Loading @@ -513,6 +523,7 @@ impl Rule for OddDisconnectionsRule { self.process_nocp(&nocp, packet); } // end hci event _ => (), }, Loading Loading @@ -639,6 +650,23 @@ impl Rule for LinkKeyMismatchRule { }, PacketChild::HciCommand(cmd) => match cmd.specialize() { CommandChild::AclCommand(cmd) => match cmd.specialize() { AclCommandChild::Disconnect(cmd) => { // If reason is power off, the host might not wait for connection complete event if cmd.get_reason() == DisconnectReason::RemoteDeviceTerminatedConnectionPowerOff { if let Some(address) = self.handles.remove(&cmd.get_connection_handle()) { self.states.remove(&address); } } } // CommandChild::AclCommand(cmd).specialize() _ => {} }, CommandChild::SecurityCommand(cmd) => match cmd.specialize() { SecurityCommandChild::LinkKeyRequestReply(cmd) => { let address = cmd.get_bd_addr(); Loading @@ -655,6 +683,11 @@ impl Rule for LinkKeyMismatchRule { _ => {} }, CommandChild::Reset(_) => { self.states.clear(); self.handles.clear(); } // PacketChild::HciCommand(cmd).specialize() _ => {} }, Loading
floss/hcidoc/src/groups/informational.rs +23 −2 Original line number Diff line number Diff line Loading @@ -8,8 +8,8 @@ use std::io::Write; use crate::engine::{Rule, RuleGroup, Signal}; use crate::parser::{Packet, PacketChild}; use bt_packets::hci::{ AclCommandChild, Address, CommandChild, ConnectionManagementCommandChild, ErrorCode, EventChild, GapData, GapDataType, LeMetaEventChild, AclCommandChild, Address, CommandChild, ConnectionManagementCommandChild, DisconnectReason, ErrorCode, EventChild, GapData, GapDataType, LeMetaEventChild, }; /// Valid values are in the range 0x0000-0x0EFF. Loading Loading @@ -241,6 +241,14 @@ impl InformationalRule { self.handles.remove(&handle); } fn report_reset(&mut self, ts: NaiveDateTime) { // report_connection_end removes the entries from the map, so store all the keys first. let handles: Vec<ConnectionHandle> = self.handles.keys().cloned().collect(); for handle in handles { self.report_connection_end(handle, ts); } } fn process_gap_data(&mut self, address: &Address, data: &GapData) { match data.data_type { GapDataType::CompleteLocalName | GapDataType::ShortenedLocalName => { Loading Loading @@ -365,6 +373,10 @@ impl Rule for InformationalRule { }, PacketChild::HciCommand(cmd) => match cmd.specialize() { CommandChild::Reset(_cmd) => { self.report_reset(packet.ts); } CommandChild::AclCommand(cmd) => match cmd.specialize() { AclCommandChild::ConnectionManagementCommand(cmd) => match cmd.specialize() { ConnectionManagementCommandChild::CreateConnection(cmd) => { Loading @@ -381,6 +393,15 @@ impl Rule for InformationalRule { _ => {} }, AclCommandChild::Disconnect(cmd) => { // If reason is power off, the host might not wait for connection complete event if cmd.get_reason() == DisconnectReason::RemoteDeviceTerminatedConnectionPowerOff { self.report_connection_end(cmd.get_connection_handle(), packet.ts); } } // CommandChild::AclCommand(cmd).specialize() _ => {} }, Loading
floss/hcidoc/src/main.rs +40 −7 Original line number Diff line number Diff line Loading @@ -18,12 +18,25 @@ fn main() { Arg::new("filename") .help("Path to the snoop log. If omitted, read from stdin instead."), ) .arg( Arg::new("ignore-unknown") .long("ignore-unknown") .action(ArgAction::SetTrue) .help("Don't print warning for unknown opcodes"), ) .arg( Arg::new("signals") .short('s') .long("signals") .action(ArgAction::SetTrue) .help("Report signals from active rules."), ) .arg( Arg::new("signals-only") .long("signals-only") .action(ArgAction::SetTrue) .help("Only print signals from active rules, don't print other events."), ) .get_matches(); let filename = match matches.get_one::<String>("filename") { Loading @@ -31,11 +44,25 @@ fn main() { None => "", }; let report_signals = match matches.get_one::<bool>("signals") { let ignore_unknown_opcode = match matches.get_one::<bool>("ignore-unknown") { Some(v) => *v, None => false, }; let mut report_signals = match matches.get_one::<bool>("signals") { Some(v) => *v, None => false, }; let report_only_signals = match matches.get_one::<bool>("signals-only") { Some(v) => *v, None => false, }; if report_only_signals { report_signals = true; } let mut parser = match LogParser::new(filename) { Ok(p) => p, Err(e) => { Loading Loading @@ -70,16 +97,22 @@ fn main() { for (pos, v) in parser.get_snoop_iterator().expect("Not a linux snoop file").enumerate() { match Packet::try_from((pos, &v)) { Ok(p) => engine.process(p), Err(e) => match v.opcode() { Err(e) => { if !ignore_unknown_opcode { match v.opcode() { LinuxSnoopOpcodes::Command | LinuxSnoopOpcodes::Event => { eprintln!("#{}: {}", pos, e); } _ => (), }, } } } } } if !report_only_signals { engine.report(&mut writer); } if report_signals { let _ = writeln!(&mut writer, "### Signals ###"); engine.report_signals(&mut writer); Loading