blob: bec8df41c17c8e7ade2fe0bbb998730d319f5a8f [file] [log] [blame]
//! Parsing of various Bluetooth packets.
use chrono::NaiveDateTime;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::cast::FromPrimitive;
use std::convert::TryFrom;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind, Read};
use bt_packets::hci::{Acl, AclChild, Command, Event};
use hcidoc_packets::l2cap::{
BasicFrame, BasicFrameChild, Control, ControlFrameChild, GroupFrameChild, LeControl,
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,
}
/// 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];
/// 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 and another 8 for additional info.
const LINUX_SNOOP_HEADER_SIZE: usize = 16;
impl TryFrom<&[u8]> for LinuxSnoopHeader {
type Error = String;
fn try_from(item: &[u8]) -> Result<Self, Self::Error> {
if item.len() != LINUX_SNOOP_HEADER_SIZE {
return Err(format!("Invalid size for snoop header: {}", item.len()));
}
let rest = item;
let (id_bytes, rest) = rest.split_at(8);
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()),
};
if header.id != LINUX_SNOOP_MAGIC {
return Err(format!("Id is not 'btsnoop'."));
}
if header.version != 1 {
return Err(format!("Version is not supported. Got {}.", header.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
));
}
Ok(header)
}
}
/// Opcodes for Linux snoop packets.
#[derive(Debug, FromPrimitive, ToPrimitive)]
#[repr(u16)]
pub enum LinuxSnoopOpcodes {
NewIndex = 0,
DeleteIndex,
Command,
Event,
AclTxPacket,
AclRxPacket,
ScoTxPacket,
ScoRxPacket,
OpenIndex,
CloseIndex,
IndexInfo,
VendorDiag,
SystemNote,
UserLogging,
CtrlOpen,
CtrlClose,
CtrlCommand,
CtrlEvent,
IsoTx,
IsoRx,
Invalid = 0xffff,
}
/// Linux snoop file packet format.
#[derive(Debug, Clone)]
pub struct LinuxSnoopPacket {
/// The original length of the captured packet as received via a network.
pub original_length: u32,
/// The length of the included data (can be smaller than original_length if
/// the received packet was truncated).
pub included_length: u32,
pub flags: u32,
pub drops: u32,
pub timestamp_magic_us: u64,
pub data: Vec<u8>,
}
impl LinuxSnoopPacket {
pub fn adapter_index(&self) -> u16 {
(self.flags >> 16).try_into().unwrap_or(0u16)
}
pub fn opcode(&self) -> LinuxSnoopOpcodes {
LinuxSnoopOpcodes::from_u32(self.flags & 0xffff).unwrap_or(LinuxSnoopOpcodes::Invalid)
}
}
/// 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 {
type Error = String;
fn try_from(item: &[u8]) -> Result<Self, Self::Error> {
if item.len() != LINUX_SNOOP_PACKET_PREAMBLE_SIZE {
return Err(format!("Wrong size for snoop packet preamble: {}", item.len()));
}
let rest = item;
let (orig_len_bytes, rest) = rest.split_at(std::mem::size_of::<u32>());
let (included_len_bytes, rest) = rest.split_at(std::mem::size_of::<u32>());
let (flags_bytes, rest) = rest.split_at(std::mem::size_of::<u32>());
let (drops_bytes, rest) = rest.split_at(std::mem::size_of::<u32>());
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 {
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![],
};
Ok(packet)
}
}
/// Reader for Linux snoop files.
pub struct LinuxSnoopReader<'a> {
fd: Box<dyn BufRead + 'a>,
}
impl<'a> LinuxSnoopReader<'a> {
fn new(fd: Box<dyn BufRead + 'a>) -> Self {
LinuxSnoopReader { fd }
}
}
impl<'a> Iterator for LinuxSnoopReader<'a> {
type Item = LinuxSnoopPacket;
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);
}
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];
match self.fd.read_exact(&mut rem_data[0..size]) {
Ok(()) => {
p.data = rem_data[0..size].to_vec();
Some(p)
}
Err(e) => {
eprintln!("Couldn't read any packet data: {}", e);
None
}
}
} else {
Some(p)
}
}
Err(_) => None,
}
}
}
/// What kind of log file is this?
#[derive(Clone, Debug)]
pub enum LogType {
/// Linux snoop file generated by something like `btmon`.
LinuxSnoop(LinuxSnoopHeader),
}
/// Parses different Bluetooth log types.
pub struct LogParser {
fd: Box<dyn BufRead>,
log_type: Option<LogType>,
}
impl<'a> LogParser {
pub fn new(filepath: &str) -> std::io::Result<Self> {
let 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 })
}
/// 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"))
}
}
/// 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(&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))))
}
}
/// Data owned by a packet.
#[derive(Debug, Clone)]
pub enum PacketChild {
HciCommand(Command),
HciEvent(Event),
AclTx(Acl),
AclRx(Acl),
}
impl<'a> TryFrom<&'a LinuxSnoopPacket> for PacketChild {
type Error = String;
fn try_from(item: &'a LinuxSnoopPacket) -> Result<Self, Self::Error> {
match item.opcode() {
LinuxSnoopOpcodes::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()) {
Ok(event) => Ok(PacketChild::HciEvent(event)),
Err(e) => Err(format!("Couldn't parse event: {:?}", e)),
},
LinuxSnoopOpcodes::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()) {
Ok(data) => Ok(PacketChild::AclRx(data)),
Err(e) => Err(format!("Couldn't parse acl rx: {:?}", e)),
},
// TODO(b/262928525) - Add packet handlers for more packet types.
_ => Err(format!("Unhandled packet opcode: {:?}", item.opcode())),
}
}
}
/// A single processable packet of data.
#[derive(Debug, Clone)]
pub struct Packet {
/// Timestamp of this packet
pub ts: NaiveDateTime,
/// 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<(usize, &'a LinuxSnoopPacket)> for Packet {
type Error = String;
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(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 adapter_index = packet.adapter_index();
Ok(Packet { ts, adapter_index, index, inner })
}
Err(e) => Err(e),
}
}
}
pub enum AclContent {
Control(Control),
LeControl(LeControl),
ConnectionlessData(u16, Vec<u8>),
StandardData(Vec<u8>),
None,
}
pub fn get_acl_content(acl: &Acl) -> AclContent {
match acl.specialize() {
AclChild::Payload(bytes) => match BasicFrame::parse(bytes.as_ref()) {
Ok(bf) => match bf.specialize() {
BasicFrameChild::ControlFrame(cf) => match cf.specialize() {
ControlFrameChild::Payload(p) => match Control::parse(p.as_ref()) {
Ok(control) => AclContent::Control(control),
Err(_) => AclContent::None,
},
_ => AclContent::None,
},
BasicFrameChild::LeControlFrame(lcf) => match lcf.specialize() {
LeControlFrameChild::Payload(p) => match LeControl::parse(p.as_ref()) {
Ok(le_control) => AclContent::LeControl(le_control),
Err(_) => AclContent::None,
},
_ => AclContent::None,
},
BasicFrameChild::GroupFrame(gf) => match gf.specialize() {
GroupFrameChild::Payload(p) => {
AclContent::ConnectionlessData(gf.get_psm(), p.to_vec())
}
_ => AclContent::None,
},
BasicFrameChild::Payload(p) => AclContent::StandardData(p.to_vec()),
_ => AclContent::None,
},
Err(_) => AclContent::None,
},
_ => AclContent::None,
}
}