1 //! Parsing of various Bluetooth packets. 2 use chrono::NaiveDateTime; 3 use num_traits::cast::FromPrimitive; 4 use std::convert::TryFrom; 5 use std::fs::File; 6 use std::io::{Error, ErrorKind, Read, Seek}; 7 8 use bt_packets::hci::{AclPacket, CommandPacket, EventPacket}; 9 10 /// Linux snoop file header format. This format is used by `btmon` on Linux systems that have bluez 11 /// installed. 12 #[derive(Clone, Copy, Debug)] 13 pub struct LinuxSnoopHeader { 14 id: [u8; 8], 15 version: u32, 16 data_type: u32, 17 } 18 19 /// Identifier for a Linux snoop file. In ASCII, this is 'btsnoop\0'. 20 const LINUX_SNOOP_MAGIC: [u8; 8] = [0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00]; 21 22 /// Snoop files in monitor format will have this value in link type. 23 const LINUX_SNOOP_MONITOR_TYPE: u32 = 2001; 24 25 /// Size of snoop header. 8 bytes for magic and another 8 for additional info. 26 const LINUX_SNOOP_HEADER_SIZE: usize = 16; 27 28 impl TryFrom<&[u8]> for LinuxSnoopHeader { 29 type Error = String; 30 try_from(item: &[u8]) -> Result<Self, Self::Error>31 fn try_from(item: &[u8]) -> Result<Self, Self::Error> { 32 if item.len() != LINUX_SNOOP_HEADER_SIZE { 33 return Err(format!("Invalid size for snoop header: {}", item.len())); 34 } 35 36 let rest = item; 37 let (id_bytes, rest) = rest.split_at(8); 38 let (version_bytes, rest) = rest.split_at(std::mem::size_of::<u32>()); 39 let (data_type_bytes, _rest) = rest.split_at(std::mem::size_of::<u32>()); 40 41 let header = LinuxSnoopHeader { 42 id: id_bytes.try_into().unwrap(), 43 version: u32::from_be_bytes(version_bytes.try_into().unwrap()), 44 data_type: u32::from_be_bytes(data_type_bytes.try_into().unwrap()), 45 }; 46 47 if header.id != LINUX_SNOOP_MAGIC { 48 return Err(format!("Id is not 'btsnoop'.")); 49 } 50 51 if header.version != 1 { 52 return Err(format!("Version is not supported. Got {}.", header.version)); 53 } 54 55 if header.data_type != LINUX_SNOOP_MONITOR_TYPE { 56 return Err(format!( 57 "Invalid data type in snoop file. We want monitor type ({}) but got {}", 58 LINUX_SNOOP_MONITOR_TYPE, header.data_type 59 )); 60 } 61 62 Ok(header) 63 } 64 } 65 66 /// Opcodes for Linux snoop packets. 67 #[derive(Debug, FromPrimitive, ToPrimitive)] 68 #[repr(u16)] 69 pub enum LinuxSnoopOpcodes { 70 NewIndex = 0, 71 DeleteIndex, 72 CommandPacket, 73 EventPacket, 74 AclTxPacket, 75 AclRxPacket, 76 ScoTxPacket, 77 ScoRxPacket, 78 OpenIndex, 79 CloseIndex, 80 IndexInfo, 81 VendorDiag, 82 SystemNote, 83 UserLogging, 84 CtrlOpen, 85 CtrlClose, 86 CtrlCommand, 87 CtrlEvent, 88 IsoTx, 89 IsoRx, 90 91 Invalid = 0xffff, 92 } 93 94 /// Linux snoop file packet format. 95 #[derive(Debug, Clone)] 96 pub struct LinuxSnoopPacket { 97 /// The original length of the captured packet as received via a network. 98 pub original_length: u32, 99 100 /// The length of the included data (can be smaller than original_length if 101 /// the received packet was truncated). 102 pub included_length: u32, 103 pub flags: u32, 104 pub drops: u32, 105 pub timestamp_magic_us: u64, 106 pub data: Vec<u8>, 107 } 108 109 impl LinuxSnoopPacket { adapter_index(&self) -> u16110 pub fn adapter_index(&self) -> u16 { 111 (self.flags >> 16).try_into().unwrap_or(0u16) 112 } 113 opcode(&self) -> LinuxSnoopOpcodes114 pub fn opcode(&self) -> LinuxSnoopOpcodes { 115 LinuxSnoopOpcodes::from_u32(self.flags & 0xffff).unwrap_or(LinuxSnoopOpcodes::Invalid) 116 } 117 } 118 119 /// Size of packet preamble (everything except the data). 120 const LINUX_SNOOP_PACKET_PREAMBLE_SIZE: usize = 24; 121 122 /// Maximum packet size for snoop is the max ACL size + 4 bytes. 123 const LINUX_SNOOP_MAX_PACKET_SIZE: usize = 1486 + 4; 124 125 /// Number of seconds from the year 1970 to the year 2000. 126 const LINUX_SNOOP_Y2K_OFFSET_IN_SECS: i64 = 946684800i64; 127 128 /// Snoop timestamps start at year 0 instead of 1970 like unix timestamps. This 129 /// offset is used to represent Jan 1, 2000 AD and can be used to convert back 130 /// to unixtime. 131 const LINUX_SNOOP_Y2K_EPOCH_USECS: i64 = 0x00E03AB44A676000i64; 132 133 /// Microseconds to seconds. 134 const USECS_TO_SECS: i64 = 1_000_000i64; 135 136 /// Offset from the snoop timestamp to unixtimestamp in seconds. This is a negative number. 137 const LINUX_SNOOP_OFFSET_TO_UNIXTIME_SECS: i64 = 138 LINUX_SNOOP_Y2K_OFFSET_IN_SECS - (LINUX_SNOOP_Y2K_EPOCH_USECS / USECS_TO_SECS); 139 140 // Expect specifically the pre-amble to be read here (and no data). 141 impl TryFrom<&[u8]> for LinuxSnoopPacket { 142 type Error = String; 143 try_from(item: &[u8]) -> Result<Self, Self::Error>144 fn try_from(item: &[u8]) -> Result<Self, Self::Error> { 145 if item.len() != LINUX_SNOOP_PACKET_PREAMBLE_SIZE { 146 return Err(format!("Wrong size for snoop packet preamble: {}", item.len())); 147 } 148 149 let rest = item; 150 let (orig_len_bytes, rest) = rest.split_at(std::mem::size_of::<u32>()); 151 let (included_len_bytes, rest) = rest.split_at(std::mem::size_of::<u32>()); 152 let (flags_bytes, rest) = rest.split_at(std::mem::size_of::<u32>()); 153 let (drops_bytes, rest) = rest.split_at(std::mem::size_of::<u32>()); 154 let (ts_bytes, _rest) = rest.split_at(std::mem::size_of::<u64>()); 155 156 // Note that all bytes are in big-endian because they're network order. 157 let packet = LinuxSnoopPacket { 158 original_length: u32::from_be_bytes(orig_len_bytes.try_into().unwrap()), 159 included_length: u32::from_be_bytes(included_len_bytes.try_into().unwrap()), 160 flags: u32::from_be_bytes(flags_bytes.try_into().unwrap()), 161 drops: u32::from_be_bytes(drops_bytes.try_into().unwrap()), 162 timestamp_magic_us: u64::from_be_bytes(ts_bytes.try_into().unwrap()), 163 data: vec![], 164 }; 165 166 Ok(packet) 167 } 168 } 169 170 /// Reader for Linux snoop files. 171 pub struct LinuxSnoopReader<'a> { 172 fd: &'a File, 173 } 174 175 impl<'a> LinuxSnoopReader<'a> { new(fd: &'a File) -> Self176 fn new(fd: &'a File) -> Self { 177 LinuxSnoopReader { fd } 178 } 179 } 180 181 impl<'a> Iterator for LinuxSnoopReader<'a> { 182 type Item = LinuxSnoopPacket; 183 next(&mut self) -> Option<Self::Item>184 fn next(&mut self) -> Option<Self::Item> { 185 let mut data = [0u8; LINUX_SNOOP_PACKET_PREAMBLE_SIZE]; 186 let bytes = match self.fd.read(&mut data) { 187 Ok(b) => b, 188 Err(e) => { 189 // |UnexpectedEof| could be seen since we're trying to read more 190 // data than is available (i.e. end of file). 191 if e.kind() != ErrorKind::UnexpectedEof { 192 eprintln!("Error reading snoop file: {:?}", e); 193 } 194 return None; 195 } 196 }; 197 198 match LinuxSnoopPacket::try_from(&data[0..bytes]) { 199 Ok(mut p) => { 200 if p.included_length > 0 { 201 let size: usize = p.included_length.try_into().unwrap(); 202 let mut rem_data = [0u8; LINUX_SNOOP_MAX_PACKET_SIZE]; 203 match self.fd.read(&mut rem_data[0..size]) { 204 Ok(b) => { 205 if b != size { 206 eprintln!( 207 "Size({}) doesn't match bytes read({}). Aborting...", 208 size, b 209 ); 210 return None; 211 } 212 213 p.data = rem_data[0..b].to_vec(); 214 Some(p) 215 } 216 Err(e) => { 217 eprintln!("Couldn't read any packet data: {}", e); 218 None 219 } 220 } 221 } else { 222 Some(p) 223 } 224 } 225 Err(_) => None, 226 } 227 } 228 } 229 230 /// What kind of log file is this? 231 #[derive(Clone, Debug)] 232 pub enum LogType { 233 /// Linux snoop file generated by something like `btmon`. 234 LinuxSnoop(LinuxSnoopHeader), 235 } 236 237 /// Parses different Bluetooth log types. 238 pub struct LogParser { 239 fd: File, 240 log_type: Option<LogType>, 241 } 242 243 impl<'a> LogParser { new(filepath: &str) -> std::io::Result<Self>244 pub fn new(filepath: &str) -> std::io::Result<Self> { 245 Ok(Self { fd: File::open(filepath)?, log_type: None }) 246 } 247 248 /// Check the log file type for the current log file. This rewinds the position of the file. 249 /// For a non-intrusive query, use |get_log_type|. read_log_type(&mut self) -> std::io::Result<LogType>250 pub fn read_log_type(&mut self) -> std::io::Result<LogType> { 251 let mut buf = [0; LINUX_SNOOP_HEADER_SIZE]; 252 253 // First rewind to start of the file. 254 self.fd.rewind()?; 255 let bytes = self.fd.read(&mut buf)?; 256 257 if let Ok(header) = LinuxSnoopHeader::try_from(&buf[0..bytes]) { 258 let log_type = LogType::LinuxSnoop(header); 259 self.log_type = Some(log_type.clone()); 260 Ok(log_type) 261 } else { 262 Err(Error::new(ErrorKind::Other, "Unsupported log file type")) 263 } 264 } 265 266 /// Get cached log type. To initially read the log type, use |read_log_type|. get_log_type(&self) -> Option<LogType>267 pub fn get_log_type(&self) -> Option<LogType> { 268 self.log_type.clone() 269 } 270 get_snoop_iterator(&mut self) -> Option<LinuxSnoopReader>271 pub fn get_snoop_iterator(&mut self) -> Option<LinuxSnoopReader> { 272 // Limit to LinuxSnoop files. 273 if !matches!(self.get_log_type()?, LogType::LinuxSnoop(_)) { 274 return None; 275 } 276 277 Some(LinuxSnoopReader::new(&mut self.fd)) 278 } 279 } 280 281 /// Data owned by a packet. 282 #[derive(Debug, Clone)] 283 pub enum PacketChild { 284 HciCommand(CommandPacket), 285 HciEvent(EventPacket), 286 AclTx(AclPacket), 287 AclRx(AclPacket), 288 } 289 290 impl<'a> TryFrom<&'a LinuxSnoopPacket> for PacketChild { 291 type Error = String; 292 try_from(item: &'a LinuxSnoopPacket) -> Result<Self, Self::Error>293 fn try_from(item: &'a LinuxSnoopPacket) -> Result<Self, Self::Error> { 294 match item.opcode() { 295 LinuxSnoopOpcodes::CommandPacket => match CommandPacket::parse(item.data.as_slice()) { 296 Ok(command) => Ok(PacketChild::HciCommand(command)), 297 Err(e) => Err(format!("Couldn't parse command: {:?}", e)), 298 }, 299 300 LinuxSnoopOpcodes::EventPacket => match EventPacket::parse(item.data.as_slice()) { 301 Ok(event) => Ok(PacketChild::HciEvent(event)), 302 Err(e) => Err(format!("Couldn't parse event: {:?}", e)), 303 }, 304 305 LinuxSnoopOpcodes::AclTxPacket => match AclPacket::parse(item.data.as_slice()) { 306 Ok(data) => Ok(PacketChild::AclTx(data)), 307 Err(e) => Err(format!("Couldn't parse acl tx: {:?}", e)), 308 }, 309 310 LinuxSnoopOpcodes::AclRxPacket => match AclPacket::parse(item.data.as_slice()) { 311 Ok(data) => Ok(PacketChild::AclRx(data)), 312 Err(e) => Err(format!("Couldn't parse acl rx: {:?}", e)), 313 }, 314 315 // TODO(b/262928525) - Add packet handlers for more packet types. 316 _ => Err(format!("Unhandled packet opcode: {:?}", item.opcode())), 317 } 318 } 319 } 320 321 /// A single processable packet of data. 322 #[derive(Debug, Clone)] 323 pub struct Packet { 324 /// Timestamp of this packet 325 pub ts: NaiveDateTime, 326 327 /// Which adapter this packet is for. Unassociated packets should use 0xFFFE. 328 pub adapter_index: u16, 329 330 /// Packet number in current stream. 331 pub index: usize, 332 333 /// Inner data for this packet. 334 pub inner: PacketChild, 335 } 336 337 impl<'a> TryFrom<(usize, &'a LinuxSnoopPacket)> for Packet { 338 type Error = String; 339 try_from(item: (usize, &'a LinuxSnoopPacket)) -> Result<Self, Self::Error>340 fn try_from(item: (usize, &'a LinuxSnoopPacket)) -> Result<Self, Self::Error> { 341 let (index, packet) = item; 342 match PacketChild::try_from(packet) { 343 Ok(inner) => { 344 let base_ts = i64::try_from(packet.timestamp_magic_us) 345 .map_err(|e| format!("u64 conversion error: {}", e))?; 346 347 let ts_secs = (base_ts / USECS_TO_SECS) + LINUX_SNOOP_OFFSET_TO_UNIXTIME_SECS; 348 let ts_nsecs = u32::try_from((base_ts % USECS_TO_SECS) * 1000).unwrap_or(0); 349 let ts = NaiveDateTime::from_timestamp_opt(ts_secs, ts_nsecs) 350 .ok_or(format!("timestamp conversion error: {}", base_ts))?; 351 let adapter_index = packet.adapter_index(); 352 353 Ok(Packet { ts, adapter_index, index, inner }) 354 } 355 356 Err(e) => Err(e), 357 } 358 } 359 } 360