Loading floss/hcidoc/src/main.rs +18 −28 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ mod parser; use crate::engine::RuleEngine; use crate::groups::{collisions, connections, controllers, informational}; use crate::parser::{LinuxSnoopOpcodes, LogParser, LogType, Packet}; use crate::parser::{LogParser, Packet, SnoopOpcodes}; fn main() { let matches = Command::new("hcidoc") Loading Loading @@ -63,7 +63,7 @@ fn main() { report_signals = true; } let mut parser = match LogParser::new(filename) { let parser = match LogParser::new(filename) { Ok(p) => p, Err(e) => { println!( Loading @@ -75,14 +75,6 @@ fn main() { } }; let log_type = match parser.read_log_type() { Ok(v) => v, Err(e) => { println!("Parsing {} failed: {}", filename, e); return; } }; // Create engine with default rule groups. let mut engine = RuleEngine::new(); engine.add_rule_group("Collisions".into(), collisions::get_collisions_group()); Loading @@ -93,14 +85,13 @@ fn main() { // Decide where to write output. let mut writer: Box<dyn Write> = Box::new(std::io::stdout()); 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((pos, &v)) { for (pos, v) in parser.get_snoop_iterator().enumerate() { match Packet::try_from((pos, &*v)) { Ok(p) => engine.process(p), Err(e) => { if !ignore_unknown_opcode { match v.opcode() { LinuxSnoopOpcodes::Command | LinuxSnoopOpcodes::Event => { SnoopOpcodes::Command | SnoopOpcodes::Event => { eprintln!("#{}: {}", pos, e); } _ => (), Loading @@ -118,4 +109,3 @@ fn main() { engine.report_signals(&mut writer); } } } floss/hcidoc/src/parser.rs +233 −151 Original line number Diff line number Diff line //! Parsing of various Bluetooth packets. use chrono::NaiveDateTime; use chrono::{DateTime, NaiveDateTime}; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::cast::FromPrimitive; use std::convert::TryFrom; Loading @@ -12,29 +12,32 @@ use hcidoc_packets::l2cap::{ LeControlFrameChild, }; /// Linux snoop file header format. This format is used by `btmon` on Linux systems that have bluez /// installed. #[derive(Clone, Copy, Debug)] pub struct LinuxSnoopHeader { id: [u8; 8], version: u32, data_type: u32, /// Snoop file header format. #[derive(Debug)] pub struct SnoopHeader { _id: [u8; 8], _version: u32, datalink_type: SnoopDatalinkType, } /// Identifier for a Linux snoop file. In ASCII, this is 'btsnoop\0'. const LINUX_SNOOP_MAGIC: [u8; 8] = [0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00]; /// Identifier for a snoop file. In ASCII, this is 'btsnoop\0'. const SNOOP_MAGIC: [u8; 8] = [0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00]; /// Snoop files in monitor format will have this value in link type. const LINUX_SNOOP_MONITOR_TYPE: u32 = 2001; /// Size of snoop header. 8 bytes for magic, 4 bytes for version, and 4 bytes for snoop type. const SNOOP_HEADER_SIZE: usize = 16; /// Size of snoop header. 8 bytes for magic and another 8 for additional info. const LINUX_SNOOP_HEADER_SIZE: usize = 16; #[derive(Debug, FromPrimitive, ToPrimitive)] #[repr(u32)] enum SnoopDatalinkType { H4Uart = 1002, LinuxMonitor = 2001, } impl TryFrom<&[u8]> for LinuxSnoopHeader { impl TryFrom<&[u8]> for SnoopHeader { type Error = String; fn try_from(item: &[u8]) -> Result<Self, Self::Error> { if item.len() != LINUX_SNOOP_HEADER_SIZE { if item.len() != SNOOP_HEADER_SIZE { return Err(format!("Invalid size for snoop header: {}", item.len())); } Loading @@ -43,35 +46,31 @@ impl TryFrom<&[u8]> for LinuxSnoopHeader { let (version_bytes, rest) = rest.split_at(std::mem::size_of::<u32>()); let (data_type_bytes, _rest) = rest.split_at(std::mem::size_of::<u32>()); let header = LinuxSnoopHeader { id: id_bytes.try_into().unwrap(), version: u32::from_be_bytes(version_bytes.try_into().unwrap()), data_type: u32::from_be_bytes(data_type_bytes.try_into().unwrap()), }; let id = id_bytes.try_into().unwrap(); let version = u32::from_be_bytes(version_bytes.try_into().unwrap()); let data_type = u32::from_be_bytes(data_type_bytes.try_into().unwrap()); if header.id != LINUX_SNOOP_MAGIC { if id != SNOOP_MAGIC { return Err(format!("Id is not 'btsnoop'.")); } if header.version != 1 { return Err(format!("Version is not supported. Got {}.", header.version)); if version != 1 { return Err(format!("Version is not supported. Got {}.", version)); } if header.data_type != LINUX_SNOOP_MONITOR_TYPE { return Err(format!( "Invalid data type in snoop file. We want monitor type ({}) but got {}", LINUX_SNOOP_MONITOR_TYPE, header.data_type )); } let datalink_type = match SnoopDatalinkType::from_u32(data_type) { Some(datalink_type) => datalink_type, None => return Err(format!("Unsupported datalink type {}", data_type)), }; Ok(header) return Ok(SnoopHeader { _id: id, _version: version, datalink_type }); } } /// Opcodes for Linux snoop packets. /// Opcodes for snoop packets. #[derive(Debug, FromPrimitive, ToPrimitive)] #[repr(u16)] pub enum LinuxSnoopOpcodes { pub enum SnoopOpcodes { NewIndex = 0, DeleteIndex, Command, Loading @@ -96,9 +95,15 @@ pub enum LinuxSnoopOpcodes { Invalid = 0xffff, } /// Linux snoop file packet format. /// Size of packet preamble (everything except the data). const SNOOP_PACKET_PREAMBLE_SIZE: usize = 24; /// Number of microseconds from btsnoop zero to Linux epoch. const SNOOP_Y0_TO_Y1970_US: i64 = 62_168_256_000_000_000; /// Snoop file packet format. #[derive(Debug, Clone)] pub struct LinuxSnoopPacket { pub struct SnoopPacketPreamble { /// The original length of the captured packet as received via a network. pub original_length: u32, Loading @@ -107,47 +112,39 @@ pub struct LinuxSnoopPacket { pub included_length: u32, pub flags: u32, pub drops: u32, pub timestamp_magic_us: u64, pub data: Vec<u8>, pub timestamp_us: u64, } impl LinuxSnoopPacket { pub fn adapter_index(&self) -> u16 { (self.flags >> 16).try_into().unwrap_or(0u16) impl SnoopPacketPreamble { fn from_fd<'a>(fd: &mut Box<dyn BufRead + 'a>) -> Option<SnoopPacketPreamble> { let mut buf = [0u8; SNOOP_PACKET_PREAMBLE_SIZE]; match fd.read_exact(&mut buf) { Ok(()) => {} Err(e) => { // |UnexpectedEof| could be seen since we're trying to read more // data than is available (i.e. end of file). if e.kind() != ErrorKind::UnexpectedEof { eprintln!("Error reading preamble: {:?}", e); } return None; } }; pub fn opcode(&self) -> LinuxSnoopOpcodes { LinuxSnoopOpcodes::from_u32(self.flags & 0xffff).unwrap_or(LinuxSnoopOpcodes::Invalid) match SnoopPacketPreamble::try_from(&buf[0..SNOOP_PACKET_PREAMBLE_SIZE]) { Ok(preamble) => Some(preamble), Err(e) => { eprintln!("Error reading preamble: {}", e); None } } } } /// Size of packet preamble (everything except the data). const LINUX_SNOOP_PACKET_PREAMBLE_SIZE: usize = 24; /// Maximum packet size for snoop is the max ACL size + 4 bytes. const LINUX_SNOOP_MAX_PACKET_SIZE: usize = 1486 + 4; /// Number of seconds from the year 1970 to the year 2000. const LINUX_SNOOP_Y2K_OFFSET_IN_SECS: i64 = 946684800i64; /// Snoop timestamps start at year 0 instead of 1970 like unix timestamps. This /// offset is used to represent Jan 1, 2000 AD and can be used to convert back /// to unixtime. const LINUX_SNOOP_Y2K_EPOCH_USECS: i64 = 0x00E03AB44A676000i64; /// Microseconds to seconds. const USECS_TO_SECS: i64 = 1_000_000i64; /// Offset from the snoop timestamp to unixtimestamp in seconds. This is a negative number. const LINUX_SNOOP_OFFSET_TO_UNIXTIME_SECS: i64 = LINUX_SNOOP_Y2K_OFFSET_IN_SECS - (LINUX_SNOOP_Y2K_EPOCH_USECS / USECS_TO_SECS); // Expect specifically the pre-amble to be read here (and no data). impl TryFrom<&[u8]> for LinuxSnoopPacket { impl TryFrom<&[u8]> for SnoopPacketPreamble { type Error = String; fn try_from(item: &[u8]) -> Result<Self, Self::Error> { if item.len() != LINUX_SNOOP_PACKET_PREAMBLE_SIZE { if item.len() != SNOOP_PACKET_PREAMBLE_SIZE { return Err(format!("Wrong size for snoop packet preamble: {}", item.len())); } Loading @@ -159,19 +156,91 @@ impl TryFrom<&[u8]> for LinuxSnoopPacket { let (ts_bytes, _rest) = rest.split_at(std::mem::size_of::<u64>()); // Note that all bytes are in big-endian because they're network order. let packet = LinuxSnoopPacket { let preamble = SnoopPacketPreamble { original_length: u32::from_be_bytes(orig_len_bytes.try_into().unwrap()), included_length: u32::from_be_bytes(included_len_bytes.try_into().unwrap()), flags: u32::from_be_bytes(flags_bytes.try_into().unwrap()), drops: u32::from_be_bytes(drops_bytes.try_into().unwrap()), timestamp_magic_us: u64::from_be_bytes(ts_bytes.try_into().unwrap()), data: vec![], timestamp_us: u64::from_be_bytes(ts_bytes.try_into().unwrap()), }; Ok(packet) Ok(preamble) } } pub trait GeneralSnoopPacket { fn adapter_index(&self) -> u16; fn opcode(&self) -> SnoopOpcodes; fn preamble(&self) -> &SnoopPacketPreamble; fn data(&self) -> &Vec<u8>; fn get_timestamp(&self) -> Option<NaiveDateTime> { let preamble = self.preamble(); let ts_i64 = i64::try_from(preamble.timestamp_us).unwrap_or(i64::MAX); DateTime::from_timestamp_micros(ts_i64 - SNOOP_Y0_TO_Y1970_US).map(|date| date.naive_utc()) } } pub struct LinuxSnoopPacket { pub preamble: SnoopPacketPreamble, pub data: Vec<u8>, } impl GeneralSnoopPacket for LinuxSnoopPacket { fn adapter_index(&self) -> u16 { (self.preamble.flags >> 16).try_into().unwrap_or(0u16) } fn opcode(&self) -> SnoopOpcodes { SnoopOpcodes::from_u32(self.preamble.flags & 0xffff).unwrap_or(SnoopOpcodes::Invalid) } fn preamble(&self) -> &SnoopPacketPreamble { &self.preamble } fn data(&self) -> &Vec<u8> { &self.data } } pub struct H4SnoopPacket { pub preamble: SnoopPacketPreamble, pub data: Vec<u8>, pub pkt_type: u8, } impl GeneralSnoopPacket for H4SnoopPacket { fn adapter_index(&self) -> u16 { 0 } fn opcode(&self) -> SnoopOpcodes { match self.pkt_type { 0x01 => SnoopOpcodes::Command, 0x02 => match self.preamble.flags & 0x01 { 0x00 => SnoopOpcodes::AclTxPacket, _ => SnoopOpcodes::AclRxPacket, }, 0x03 => match self.preamble.flags & 0x01 { 0x00 => SnoopOpcodes::ScoTxPacket, _ => SnoopOpcodes::ScoRxPacket, }, 0x04 => SnoopOpcodes::Event, 0x05 => match self.preamble.flags & 0x01 { 0x00 => SnoopOpcodes::IsoTx, _ => SnoopOpcodes::IsoRx, }, _ => SnoopOpcodes::Invalid, } } fn preamble(&self) -> &SnoopPacketPreamble { &self.preamble } fn data(&self) -> &Vec<u8> { &self.data } } /// Maximum packet size for snoop is the max ACL size + 4 bytes. const SNOOP_MAX_PACKET_SIZE: usize = 1486 + 4; /// Reader for Linux snoop files. pub struct LinuxSnoopReader<'a> { fd: Box<dyn BufRead + 'a>, Loading @@ -184,31 +253,23 @@ impl<'a> LinuxSnoopReader<'a> { } impl<'a> Iterator for LinuxSnoopReader<'a> { type Item = LinuxSnoopPacket; type Item = Box<dyn GeneralSnoopPacket>; fn next(&mut self) -> Option<Self::Item> { let mut data = [0u8; LINUX_SNOOP_PACKET_PREAMBLE_SIZE]; match self.fd.read_exact(&mut data) { Ok(()) => {} Err(e) => { // |UnexpectedEof| could be seen since we're trying to read more // data than is available (i.e. end of file). if e.kind() != ErrorKind::UnexpectedEof { eprintln!("Error reading snoop file: {:?}", e); } let preamble = match SnoopPacketPreamble::from_fd(&mut self.fd) { Some(preamble) => preamble, None => { return None; } }; match LinuxSnoopPacket::try_from(&data[0..LINUX_SNOOP_PACKET_PREAMBLE_SIZE]) { Ok(mut p) => { if p.included_length > 0 { let size: usize = p.included_length.try_into().unwrap(); let mut rem_data = [0u8; LINUX_SNOOP_MAX_PACKET_SIZE]; if preamble.included_length > 0 { let size: usize = (preamble.included_length).try_into().unwrap(); let mut rem_data = [0u8; SNOOP_MAX_PACKET_SIZE]; match self.fd.read_exact(&mut rem_data[0..size]) { Ok(()) => { p.data = rem_data[0..size].to_vec(); Some(p) Some(Box::new(LinuxSnoopPacket { preamble, data: rem_data[0..size].to_vec() })) } Err(e) => { eprintln!("Couldn't read any packet data: {}", e); Loading @@ -216,67 +277,92 @@ impl<'a> Iterator for LinuxSnoopReader<'a> { } } } else { Some(p) Some(Box::new(LinuxSnoopPacket { preamble, data: vec![] })) } } Err(_) => None, } /// Reader for H4/UART/Android snoop files. pub struct H4SnoopReader<'a> { fd: Box<dyn BufRead + 'a>, } impl<'a> H4SnoopReader<'a> { fn new(fd: Box<dyn BufRead + 'a>) -> Self { H4SnoopReader { fd } } } /// What kind of log file is this? #[derive(Clone, Debug)] pub enum LogType { /// Linux snoop file generated by something like `btmon`. LinuxSnoop(LinuxSnoopHeader), impl<'a> Iterator for H4SnoopReader<'a> { type Item = Box<dyn GeneralSnoopPacket>; fn next(&mut self) -> Option<Self::Item> { let preamble = match SnoopPacketPreamble::from_fd(&mut self.fd) { Some(preamble) => preamble, None => { return None; } }; if preamble.included_length > 0 { let size: usize = (preamble.included_length - 1).try_into().unwrap(); let mut type_buf = [0u8; 1]; match self.fd.read_exact(&mut type_buf) { Ok(()) => {} Err(e) => { eprintln!("Couldn't read any packet data: {}", e); return None; } }; let mut rem_data = [0u8; SNOOP_MAX_PACKET_SIZE]; match self.fd.read_exact(&mut rem_data[0..size]) { Ok(()) => Some(Box::new(H4SnoopPacket { preamble, data: rem_data[0..size].to_vec(), pkt_type: type_buf[0], })), Err(e) => { eprintln!("Couldn't read any packet data: {}", e); None } } } else { eprintln!("Non-positive packet size: {}", preamble.included_length); None } } } /// Parses different Bluetooth log types. pub struct LogParser { fd: Box<dyn BufRead>, log_type: Option<LogType>, log_type: SnoopDatalinkType, } impl<'a> LogParser { pub fn new(filepath: &str) -> std::io::Result<Self> { let fd: Box<dyn BufRead>; let mut fd: Box<dyn BufRead>; if filepath.len() == 0 { fd = Box::new(BufReader::new(std::io::stdin())); } else { fd = Box::new(BufReader::new(File::open(filepath)?)); } Ok(Self { fd, log_type: None }) } let mut buf = [0; SNOOP_HEADER_SIZE]; fd.read_exact(&mut buf)?; /// Check the log file type for the current log file. This advances the read pointer. /// For a non-intrusive query, use |get_log_type|. pub fn read_log_type(&mut self) -> std::io::Result<LogType> { let mut buf = [0; LINUX_SNOOP_HEADER_SIZE]; self.fd.read_exact(&mut buf)?; if let Ok(header) = LinuxSnoopHeader::try_from(&buf[0..LINUX_SNOOP_HEADER_SIZE]) { let log_type = LogType::LinuxSnoop(header); self.log_type = Some(log_type.clone()); Ok(log_type) } else { Err(Error::new(ErrorKind::Other, "Unsupported log file type")) match SnoopHeader::try_from(&buf[0..SNOOP_HEADER_SIZE]) { Ok(header) => Ok(Self { fd, log_type: header.datalink_type }), Err(e) => Err(Error::new(ErrorKind::Other, e)), } } /// Get cached log type. To initially read the log type, use |read_log_type|. pub fn get_log_type(&self) -> Option<LogType> { self.log_type.clone() pub fn get_snoop_iterator(self) -> Box<dyn Iterator<Item = Box<dyn GeneralSnoopPacket>>> { let reader = Box::new(BufReader::new(self.fd)); match self.log_type { SnoopDatalinkType::H4Uart => Box::new(H4SnoopReader::new(reader)), SnoopDatalinkType::LinuxMonitor => Box::new(LinuxSnoopReader::new(reader)), } pub fn get_snoop_iterator(&mut self) -> Option<LinuxSnoopReader> { // Limit to LinuxSnoop files. if !matches!(self.get_log_type()?, LogType::LinuxSnoop(_)) { return None; } Some(LinuxSnoopReader::new(Box::new(BufReader::new(&mut self.fd)))) } } Loading @@ -291,37 +377,37 @@ pub enum PacketChild { SystemNote(String), } impl<'a> TryFrom<&'a LinuxSnoopPacket> for PacketChild { impl<'a> TryFrom<&'a dyn GeneralSnoopPacket> for PacketChild { type Error = String; fn try_from(item: &'a LinuxSnoopPacket) -> Result<Self, Self::Error> { fn try_from(item: &'a dyn GeneralSnoopPacket) -> Result<Self, Self::Error> { match item.opcode() { LinuxSnoopOpcodes::Command => match Command::parse(item.data.as_slice()) { SnoopOpcodes::Command => match Command::parse(item.data().as_slice()) { Ok(command) => Ok(PacketChild::HciCommand(command)), Err(e) => Err(format!("Couldn't parse command: {:?}", e)), }, LinuxSnoopOpcodes::Event => match Event::parse(item.data.as_slice()) { SnoopOpcodes::Event => match Event::parse(item.data().as_slice()) { Ok(event) => Ok(PacketChild::HciEvent(event)), Err(e) => Err(format!("Couldn't parse event: {:?}", e)), }, LinuxSnoopOpcodes::AclTxPacket => match Acl::parse(item.data.as_slice()) { SnoopOpcodes::AclTxPacket => match Acl::parse(item.data().as_slice()) { Ok(data) => Ok(PacketChild::AclTx(data)), Err(e) => Err(format!("Couldn't parse acl tx: {:?}", e)), }, LinuxSnoopOpcodes::AclRxPacket => match Acl::parse(item.data.as_slice()) { SnoopOpcodes::AclRxPacket => match Acl::parse(item.data().as_slice()) { Ok(data) => Ok(PacketChild::AclRx(data)), Err(e) => Err(format!("Couldn't parse acl rx: {:?}", e)), }, LinuxSnoopOpcodes::NewIndex => match NewIndex::parse(item.data.as_slice()) { SnoopOpcodes::NewIndex => match NewIndex::parse(item.data().as_slice()) { Ok(data) => Ok(PacketChild::NewIndex(data)), Err(e) => Err(format!("Couldn't parse new index: {:?}", e)), }, LinuxSnoopOpcodes::SystemNote => match String::from_utf8(item.data.to_vec()) { SnoopOpcodes::SystemNote => match String::from_utf8(item.data().to_vec()) { Ok(data) => Ok(PacketChild::SystemNote(data)), Err(e) => Err(format!("Couldn't parse system note: {:?}", e)), }, Loading @@ -348,22 +434,18 @@ pub struct Packet { pub inner: PacketChild, } impl<'a> TryFrom<(usize, &'a LinuxSnoopPacket)> for Packet { impl<'a> TryFrom<(usize, &'a dyn GeneralSnoopPacket)> for Packet { type Error = String; fn try_from(item: (usize, &'a LinuxSnoopPacket)) -> Result<Self, Self::Error> { fn try_from(item: (usize, &'a dyn GeneralSnoopPacket)) -> Result<Self, Self::Error> { let (index, packet) = item; match PacketChild::try_from(packet) { Ok(inner) => { 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_opt(ts_secs, ts_nsecs) .ok_or(format!("timestamp conversion error: {}", base_ts))?; let ts = packet.get_timestamp().ok_or(format!( "timestamp conversion error: {}", packet.preamble().timestamp_us ))?; let adapter_index = packet.adapter_index(); Ok(Packet { ts, adapter_index, index, inner }) } Loading Loading
floss/hcidoc/src/main.rs +18 −28 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ mod parser; use crate::engine::RuleEngine; use crate::groups::{collisions, connections, controllers, informational}; use crate::parser::{LinuxSnoopOpcodes, LogParser, LogType, Packet}; use crate::parser::{LogParser, Packet, SnoopOpcodes}; fn main() { let matches = Command::new("hcidoc") Loading Loading @@ -63,7 +63,7 @@ fn main() { report_signals = true; } let mut parser = match LogParser::new(filename) { let parser = match LogParser::new(filename) { Ok(p) => p, Err(e) => { println!( Loading @@ -75,14 +75,6 @@ fn main() { } }; let log_type = match parser.read_log_type() { Ok(v) => v, Err(e) => { println!("Parsing {} failed: {}", filename, e); return; } }; // Create engine with default rule groups. let mut engine = RuleEngine::new(); engine.add_rule_group("Collisions".into(), collisions::get_collisions_group()); Loading @@ -93,14 +85,13 @@ fn main() { // Decide where to write output. let mut writer: Box<dyn Write> = Box::new(std::io::stdout()); 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((pos, &v)) { for (pos, v) in parser.get_snoop_iterator().enumerate() { match Packet::try_from((pos, &*v)) { Ok(p) => engine.process(p), Err(e) => { if !ignore_unknown_opcode { match v.opcode() { LinuxSnoopOpcodes::Command | LinuxSnoopOpcodes::Event => { SnoopOpcodes::Command | SnoopOpcodes::Event => { eprintln!("#{}: {}", pos, e); } _ => (), Loading @@ -118,4 +109,3 @@ fn main() { engine.report_signals(&mut writer); } } }
floss/hcidoc/src/parser.rs +233 −151 Original line number Diff line number Diff line //! Parsing of various Bluetooth packets. use chrono::NaiveDateTime; use chrono::{DateTime, NaiveDateTime}; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::cast::FromPrimitive; use std::convert::TryFrom; Loading @@ -12,29 +12,32 @@ use hcidoc_packets::l2cap::{ LeControlFrameChild, }; /// Linux snoop file header format. This format is used by `btmon` on Linux systems that have bluez /// installed. #[derive(Clone, Copy, Debug)] pub struct LinuxSnoopHeader { id: [u8; 8], version: u32, data_type: u32, /// Snoop file header format. #[derive(Debug)] pub struct SnoopHeader { _id: [u8; 8], _version: u32, datalink_type: SnoopDatalinkType, } /// Identifier for a Linux snoop file. In ASCII, this is 'btsnoop\0'. const LINUX_SNOOP_MAGIC: [u8; 8] = [0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00]; /// Identifier for a snoop file. In ASCII, this is 'btsnoop\0'. const SNOOP_MAGIC: [u8; 8] = [0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00]; /// Snoop files in monitor format will have this value in link type. const LINUX_SNOOP_MONITOR_TYPE: u32 = 2001; /// Size of snoop header. 8 bytes for magic, 4 bytes for version, and 4 bytes for snoop type. const SNOOP_HEADER_SIZE: usize = 16; /// Size of snoop header. 8 bytes for magic and another 8 for additional info. const LINUX_SNOOP_HEADER_SIZE: usize = 16; #[derive(Debug, FromPrimitive, ToPrimitive)] #[repr(u32)] enum SnoopDatalinkType { H4Uart = 1002, LinuxMonitor = 2001, } impl TryFrom<&[u8]> for LinuxSnoopHeader { impl TryFrom<&[u8]> for SnoopHeader { type Error = String; fn try_from(item: &[u8]) -> Result<Self, Self::Error> { if item.len() != LINUX_SNOOP_HEADER_SIZE { if item.len() != SNOOP_HEADER_SIZE { return Err(format!("Invalid size for snoop header: {}", item.len())); } Loading @@ -43,35 +46,31 @@ impl TryFrom<&[u8]> for LinuxSnoopHeader { let (version_bytes, rest) = rest.split_at(std::mem::size_of::<u32>()); let (data_type_bytes, _rest) = rest.split_at(std::mem::size_of::<u32>()); let header = LinuxSnoopHeader { id: id_bytes.try_into().unwrap(), version: u32::from_be_bytes(version_bytes.try_into().unwrap()), data_type: u32::from_be_bytes(data_type_bytes.try_into().unwrap()), }; let id = id_bytes.try_into().unwrap(); let version = u32::from_be_bytes(version_bytes.try_into().unwrap()); let data_type = u32::from_be_bytes(data_type_bytes.try_into().unwrap()); if header.id != LINUX_SNOOP_MAGIC { if id != SNOOP_MAGIC { return Err(format!("Id is not 'btsnoop'.")); } if header.version != 1 { return Err(format!("Version is not supported. Got {}.", header.version)); if version != 1 { return Err(format!("Version is not supported. Got {}.", version)); } if header.data_type != LINUX_SNOOP_MONITOR_TYPE { return Err(format!( "Invalid data type in snoop file. We want monitor type ({}) but got {}", LINUX_SNOOP_MONITOR_TYPE, header.data_type )); } let datalink_type = match SnoopDatalinkType::from_u32(data_type) { Some(datalink_type) => datalink_type, None => return Err(format!("Unsupported datalink type {}", data_type)), }; Ok(header) return Ok(SnoopHeader { _id: id, _version: version, datalink_type }); } } /// Opcodes for Linux snoop packets. /// Opcodes for snoop packets. #[derive(Debug, FromPrimitive, ToPrimitive)] #[repr(u16)] pub enum LinuxSnoopOpcodes { pub enum SnoopOpcodes { NewIndex = 0, DeleteIndex, Command, Loading @@ -96,9 +95,15 @@ pub enum LinuxSnoopOpcodes { Invalid = 0xffff, } /// Linux snoop file packet format. /// Size of packet preamble (everything except the data). const SNOOP_PACKET_PREAMBLE_SIZE: usize = 24; /// Number of microseconds from btsnoop zero to Linux epoch. const SNOOP_Y0_TO_Y1970_US: i64 = 62_168_256_000_000_000; /// Snoop file packet format. #[derive(Debug, Clone)] pub struct LinuxSnoopPacket { pub struct SnoopPacketPreamble { /// The original length of the captured packet as received via a network. pub original_length: u32, Loading @@ -107,47 +112,39 @@ pub struct LinuxSnoopPacket { pub included_length: u32, pub flags: u32, pub drops: u32, pub timestamp_magic_us: u64, pub data: Vec<u8>, pub timestamp_us: u64, } impl LinuxSnoopPacket { pub fn adapter_index(&self) -> u16 { (self.flags >> 16).try_into().unwrap_or(0u16) impl SnoopPacketPreamble { fn from_fd<'a>(fd: &mut Box<dyn BufRead + 'a>) -> Option<SnoopPacketPreamble> { let mut buf = [0u8; SNOOP_PACKET_PREAMBLE_SIZE]; match fd.read_exact(&mut buf) { Ok(()) => {} Err(e) => { // |UnexpectedEof| could be seen since we're trying to read more // data than is available (i.e. end of file). if e.kind() != ErrorKind::UnexpectedEof { eprintln!("Error reading preamble: {:?}", e); } return None; } }; pub fn opcode(&self) -> LinuxSnoopOpcodes { LinuxSnoopOpcodes::from_u32(self.flags & 0xffff).unwrap_or(LinuxSnoopOpcodes::Invalid) match SnoopPacketPreamble::try_from(&buf[0..SNOOP_PACKET_PREAMBLE_SIZE]) { Ok(preamble) => Some(preamble), Err(e) => { eprintln!("Error reading preamble: {}", e); None } } } } /// Size of packet preamble (everything except the data). const LINUX_SNOOP_PACKET_PREAMBLE_SIZE: usize = 24; /// Maximum packet size for snoop is the max ACL size + 4 bytes. const LINUX_SNOOP_MAX_PACKET_SIZE: usize = 1486 + 4; /// Number of seconds from the year 1970 to the year 2000. const LINUX_SNOOP_Y2K_OFFSET_IN_SECS: i64 = 946684800i64; /// Snoop timestamps start at year 0 instead of 1970 like unix timestamps. This /// offset is used to represent Jan 1, 2000 AD and can be used to convert back /// to unixtime. const LINUX_SNOOP_Y2K_EPOCH_USECS: i64 = 0x00E03AB44A676000i64; /// Microseconds to seconds. const USECS_TO_SECS: i64 = 1_000_000i64; /// Offset from the snoop timestamp to unixtimestamp in seconds. This is a negative number. const LINUX_SNOOP_OFFSET_TO_UNIXTIME_SECS: i64 = LINUX_SNOOP_Y2K_OFFSET_IN_SECS - (LINUX_SNOOP_Y2K_EPOCH_USECS / USECS_TO_SECS); // Expect specifically the pre-amble to be read here (and no data). impl TryFrom<&[u8]> for LinuxSnoopPacket { impl TryFrom<&[u8]> for SnoopPacketPreamble { type Error = String; fn try_from(item: &[u8]) -> Result<Self, Self::Error> { if item.len() != LINUX_SNOOP_PACKET_PREAMBLE_SIZE { if item.len() != SNOOP_PACKET_PREAMBLE_SIZE { return Err(format!("Wrong size for snoop packet preamble: {}", item.len())); } Loading @@ -159,19 +156,91 @@ impl TryFrom<&[u8]> for LinuxSnoopPacket { let (ts_bytes, _rest) = rest.split_at(std::mem::size_of::<u64>()); // Note that all bytes are in big-endian because they're network order. let packet = LinuxSnoopPacket { let preamble = SnoopPacketPreamble { original_length: u32::from_be_bytes(orig_len_bytes.try_into().unwrap()), included_length: u32::from_be_bytes(included_len_bytes.try_into().unwrap()), flags: u32::from_be_bytes(flags_bytes.try_into().unwrap()), drops: u32::from_be_bytes(drops_bytes.try_into().unwrap()), timestamp_magic_us: u64::from_be_bytes(ts_bytes.try_into().unwrap()), data: vec![], timestamp_us: u64::from_be_bytes(ts_bytes.try_into().unwrap()), }; Ok(packet) Ok(preamble) } } pub trait GeneralSnoopPacket { fn adapter_index(&self) -> u16; fn opcode(&self) -> SnoopOpcodes; fn preamble(&self) -> &SnoopPacketPreamble; fn data(&self) -> &Vec<u8>; fn get_timestamp(&self) -> Option<NaiveDateTime> { let preamble = self.preamble(); let ts_i64 = i64::try_from(preamble.timestamp_us).unwrap_or(i64::MAX); DateTime::from_timestamp_micros(ts_i64 - SNOOP_Y0_TO_Y1970_US).map(|date| date.naive_utc()) } } pub struct LinuxSnoopPacket { pub preamble: SnoopPacketPreamble, pub data: Vec<u8>, } impl GeneralSnoopPacket for LinuxSnoopPacket { fn adapter_index(&self) -> u16 { (self.preamble.flags >> 16).try_into().unwrap_or(0u16) } fn opcode(&self) -> SnoopOpcodes { SnoopOpcodes::from_u32(self.preamble.flags & 0xffff).unwrap_or(SnoopOpcodes::Invalid) } fn preamble(&self) -> &SnoopPacketPreamble { &self.preamble } fn data(&self) -> &Vec<u8> { &self.data } } pub struct H4SnoopPacket { pub preamble: SnoopPacketPreamble, pub data: Vec<u8>, pub pkt_type: u8, } impl GeneralSnoopPacket for H4SnoopPacket { fn adapter_index(&self) -> u16 { 0 } fn opcode(&self) -> SnoopOpcodes { match self.pkt_type { 0x01 => SnoopOpcodes::Command, 0x02 => match self.preamble.flags & 0x01 { 0x00 => SnoopOpcodes::AclTxPacket, _ => SnoopOpcodes::AclRxPacket, }, 0x03 => match self.preamble.flags & 0x01 { 0x00 => SnoopOpcodes::ScoTxPacket, _ => SnoopOpcodes::ScoRxPacket, }, 0x04 => SnoopOpcodes::Event, 0x05 => match self.preamble.flags & 0x01 { 0x00 => SnoopOpcodes::IsoTx, _ => SnoopOpcodes::IsoRx, }, _ => SnoopOpcodes::Invalid, } } fn preamble(&self) -> &SnoopPacketPreamble { &self.preamble } fn data(&self) -> &Vec<u8> { &self.data } } /// Maximum packet size for snoop is the max ACL size + 4 bytes. const SNOOP_MAX_PACKET_SIZE: usize = 1486 + 4; /// Reader for Linux snoop files. pub struct LinuxSnoopReader<'a> { fd: Box<dyn BufRead + 'a>, Loading @@ -184,31 +253,23 @@ impl<'a> LinuxSnoopReader<'a> { } impl<'a> Iterator for LinuxSnoopReader<'a> { type Item = LinuxSnoopPacket; type Item = Box<dyn GeneralSnoopPacket>; fn next(&mut self) -> Option<Self::Item> { let mut data = [0u8; LINUX_SNOOP_PACKET_PREAMBLE_SIZE]; match self.fd.read_exact(&mut data) { Ok(()) => {} Err(e) => { // |UnexpectedEof| could be seen since we're trying to read more // data than is available (i.e. end of file). if e.kind() != ErrorKind::UnexpectedEof { eprintln!("Error reading snoop file: {:?}", e); } let preamble = match SnoopPacketPreamble::from_fd(&mut self.fd) { Some(preamble) => preamble, None => { return None; } }; match LinuxSnoopPacket::try_from(&data[0..LINUX_SNOOP_PACKET_PREAMBLE_SIZE]) { Ok(mut p) => { if p.included_length > 0 { let size: usize = p.included_length.try_into().unwrap(); let mut rem_data = [0u8; LINUX_SNOOP_MAX_PACKET_SIZE]; if preamble.included_length > 0 { let size: usize = (preamble.included_length).try_into().unwrap(); let mut rem_data = [0u8; SNOOP_MAX_PACKET_SIZE]; match self.fd.read_exact(&mut rem_data[0..size]) { Ok(()) => { p.data = rem_data[0..size].to_vec(); Some(p) Some(Box::new(LinuxSnoopPacket { preamble, data: rem_data[0..size].to_vec() })) } Err(e) => { eprintln!("Couldn't read any packet data: {}", e); Loading @@ -216,67 +277,92 @@ impl<'a> Iterator for LinuxSnoopReader<'a> { } } } else { Some(p) Some(Box::new(LinuxSnoopPacket { preamble, data: vec![] })) } } Err(_) => None, } /// Reader for H4/UART/Android snoop files. pub struct H4SnoopReader<'a> { fd: Box<dyn BufRead + 'a>, } impl<'a> H4SnoopReader<'a> { fn new(fd: Box<dyn BufRead + 'a>) -> Self { H4SnoopReader { fd } } } /// What kind of log file is this? #[derive(Clone, Debug)] pub enum LogType { /// Linux snoop file generated by something like `btmon`. LinuxSnoop(LinuxSnoopHeader), impl<'a> Iterator for H4SnoopReader<'a> { type Item = Box<dyn GeneralSnoopPacket>; fn next(&mut self) -> Option<Self::Item> { let preamble = match SnoopPacketPreamble::from_fd(&mut self.fd) { Some(preamble) => preamble, None => { return None; } }; if preamble.included_length > 0 { let size: usize = (preamble.included_length - 1).try_into().unwrap(); let mut type_buf = [0u8; 1]; match self.fd.read_exact(&mut type_buf) { Ok(()) => {} Err(e) => { eprintln!("Couldn't read any packet data: {}", e); return None; } }; let mut rem_data = [0u8; SNOOP_MAX_PACKET_SIZE]; match self.fd.read_exact(&mut rem_data[0..size]) { Ok(()) => Some(Box::new(H4SnoopPacket { preamble, data: rem_data[0..size].to_vec(), pkt_type: type_buf[0], })), Err(e) => { eprintln!("Couldn't read any packet data: {}", e); None } } } else { eprintln!("Non-positive packet size: {}", preamble.included_length); None } } } /// Parses different Bluetooth log types. pub struct LogParser { fd: Box<dyn BufRead>, log_type: Option<LogType>, log_type: SnoopDatalinkType, } impl<'a> LogParser { pub fn new(filepath: &str) -> std::io::Result<Self> { let fd: Box<dyn BufRead>; let mut fd: Box<dyn BufRead>; if filepath.len() == 0 { fd = Box::new(BufReader::new(std::io::stdin())); } else { fd = Box::new(BufReader::new(File::open(filepath)?)); } Ok(Self { fd, log_type: None }) } let mut buf = [0; SNOOP_HEADER_SIZE]; fd.read_exact(&mut buf)?; /// Check the log file type for the current log file. This advances the read pointer. /// For a non-intrusive query, use |get_log_type|. pub fn read_log_type(&mut self) -> std::io::Result<LogType> { let mut buf = [0; LINUX_SNOOP_HEADER_SIZE]; self.fd.read_exact(&mut buf)?; if let Ok(header) = LinuxSnoopHeader::try_from(&buf[0..LINUX_SNOOP_HEADER_SIZE]) { let log_type = LogType::LinuxSnoop(header); self.log_type = Some(log_type.clone()); Ok(log_type) } else { Err(Error::new(ErrorKind::Other, "Unsupported log file type")) match SnoopHeader::try_from(&buf[0..SNOOP_HEADER_SIZE]) { Ok(header) => Ok(Self { fd, log_type: header.datalink_type }), Err(e) => Err(Error::new(ErrorKind::Other, e)), } } /// Get cached log type. To initially read the log type, use |read_log_type|. pub fn get_log_type(&self) -> Option<LogType> { self.log_type.clone() pub fn get_snoop_iterator(self) -> Box<dyn Iterator<Item = Box<dyn GeneralSnoopPacket>>> { let reader = Box::new(BufReader::new(self.fd)); match self.log_type { SnoopDatalinkType::H4Uart => Box::new(H4SnoopReader::new(reader)), SnoopDatalinkType::LinuxMonitor => Box::new(LinuxSnoopReader::new(reader)), } pub fn get_snoop_iterator(&mut self) -> Option<LinuxSnoopReader> { // Limit to LinuxSnoop files. if !matches!(self.get_log_type()?, LogType::LinuxSnoop(_)) { return None; } Some(LinuxSnoopReader::new(Box::new(BufReader::new(&mut self.fd)))) } } Loading @@ -291,37 +377,37 @@ pub enum PacketChild { SystemNote(String), } impl<'a> TryFrom<&'a LinuxSnoopPacket> for PacketChild { impl<'a> TryFrom<&'a dyn GeneralSnoopPacket> for PacketChild { type Error = String; fn try_from(item: &'a LinuxSnoopPacket) -> Result<Self, Self::Error> { fn try_from(item: &'a dyn GeneralSnoopPacket) -> Result<Self, Self::Error> { match item.opcode() { LinuxSnoopOpcodes::Command => match Command::parse(item.data.as_slice()) { SnoopOpcodes::Command => match Command::parse(item.data().as_slice()) { Ok(command) => Ok(PacketChild::HciCommand(command)), Err(e) => Err(format!("Couldn't parse command: {:?}", e)), }, LinuxSnoopOpcodes::Event => match Event::parse(item.data.as_slice()) { SnoopOpcodes::Event => match Event::parse(item.data().as_slice()) { Ok(event) => Ok(PacketChild::HciEvent(event)), Err(e) => Err(format!("Couldn't parse event: {:?}", e)), }, LinuxSnoopOpcodes::AclTxPacket => match Acl::parse(item.data.as_slice()) { SnoopOpcodes::AclTxPacket => match Acl::parse(item.data().as_slice()) { Ok(data) => Ok(PacketChild::AclTx(data)), Err(e) => Err(format!("Couldn't parse acl tx: {:?}", e)), }, LinuxSnoopOpcodes::AclRxPacket => match Acl::parse(item.data.as_slice()) { SnoopOpcodes::AclRxPacket => match Acl::parse(item.data().as_slice()) { Ok(data) => Ok(PacketChild::AclRx(data)), Err(e) => Err(format!("Couldn't parse acl rx: {:?}", e)), }, LinuxSnoopOpcodes::NewIndex => match NewIndex::parse(item.data.as_slice()) { SnoopOpcodes::NewIndex => match NewIndex::parse(item.data().as_slice()) { Ok(data) => Ok(PacketChild::NewIndex(data)), Err(e) => Err(format!("Couldn't parse new index: {:?}", e)), }, LinuxSnoopOpcodes::SystemNote => match String::from_utf8(item.data.to_vec()) { SnoopOpcodes::SystemNote => match String::from_utf8(item.data().to_vec()) { Ok(data) => Ok(PacketChild::SystemNote(data)), Err(e) => Err(format!("Couldn't parse system note: {:?}", e)), }, Loading @@ -348,22 +434,18 @@ pub struct Packet { pub inner: PacketChild, } impl<'a> TryFrom<(usize, &'a LinuxSnoopPacket)> for Packet { impl<'a> TryFrom<(usize, &'a dyn GeneralSnoopPacket)> for Packet { type Error = String; fn try_from(item: (usize, &'a LinuxSnoopPacket)) -> Result<Self, Self::Error> { fn try_from(item: (usize, &'a dyn GeneralSnoopPacket)) -> Result<Self, Self::Error> { let (index, packet) = item; match PacketChild::try_from(packet) { Ok(inner) => { 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_opt(ts_secs, ts_nsecs) .ok_or(format!("timestamp conversion error: {}", base_ts))?; let ts = packet.get_timestamp().ok_or(format!( "timestamp conversion error: {}", packet.preamble().timestamp_us ))?; let adapter_index = packet.adapter_index(); Ok(Packet { ts, adapter_index, index, inner }) } Loading