blob: b3188aae0b0ae43daeeec50e61c4e2cd5b23394f [file] [log] [blame]
//! BT snoop logger
use crate::hal::internal::RawHal;
use bt_common::sys_prop;
use bt_packets::hci::{AclPacket, CommandPacket, EventPacket, IsoPacket, Packet, ScoPacket};
use bytes::{BufMut, Bytes, BytesMut};
use gddi::{module, part_out, provides, Stoppable};
use log::error;
use std::convert::TryFrom;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::fs::{remove_file, rename, File};
use tokio::io::AsyncWriteExt;
use tokio::runtime::Runtime;
use tokio::select;
use tokio::sync::mpsc::{channel, Receiver, Sender, UnboundedReceiver};
use tokio::sync::Mutex;
#[part_out]
#[derive(Clone, Stoppable)]
struct Hal {
control: ControlHal,
acl: AclHal,
sco: ScoHal,
iso: IsoHal,
}
/// Command & event tx/rx
#[derive(Clone, Stoppable)]
pub struct ControlHal {
/// Transmit end
pub tx: Sender<CommandPacket>,
/// Receive end
pub rx: Arc<Mutex<Receiver<EventPacket>>>,
}
/// Acl tx/rx
#[derive(Clone, Stoppable)]
pub struct AclHal {
/// Transmit end
pub tx: Sender<AclPacket>,
/// Receive end
pub rx: Arc<Mutex<Receiver<AclPacket>>>,
}
/// Sco tx/rx
#[derive(Clone, Stoppable)]
pub struct ScoHal {
/// Transmit end
pub tx: Sender<ScoPacket>,
/// Receive end
pub rx: Arc<Mutex<Receiver<ScoPacket>>>,
}
/// Iso tx/rx
#[derive(Clone, Stoppable)]
pub struct IsoHal {
/// Transmit end
pub tx: Sender<IsoPacket>,
/// Receive end
pub rx: Arc<Mutex<Receiver<IsoPacket>>>,
}
/// The different modes snoop logging can be in
#[derive(Clone)]
pub enum SnoopMode {
/// All logs disabled
Disabled,
/// Only sanitized logs
Filtered,
/// Log everything
Full,
}
/// There was an error parsing the mode from a string
pub struct SnoopModeParseError;
impl std::str::FromStr for SnoopMode {
type Err = SnoopModeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"disabled" => Ok(SnoopMode::Disabled),
"filtered" => Ok(SnoopMode::Filtered),
"full" => Ok(SnoopMode::Full),
_ => Err(SnoopModeParseError),
}
}
}
/// All snoop logging config
#[derive(Clone, Stoppable)]
pub struct SnoopConfig {
path: String,
max_packets_per_file: u32,
mode: SnoopMode,
}
impl SnoopConfig {
/// Constructs a new snoop config
pub fn new() -> Self {
Self {
path: "/data/misc/bluetooth/logs/btsnoop_hci.log".to_string(),
max_packets_per_file: sys_prop::get_u32("persist.bluetooth.btsnoopsize")
.unwrap_or(0xFFFF),
mode: get_configured_snoop_mode().parse().unwrap_or(SnoopMode::Disabled),
}
}
/// Overwrites the laoded log path with the provided one
pub fn set_path(&mut self, value: String) {
self.path = value;
}
/// Overwrites the loaded mode with the provided one
pub fn set_mode(&mut self, value: SnoopMode) {
self.mode = value;
}
}
impl Default for SnoopConfig {
fn default() -> Self {
Self::new()
}
}
fn get_configured_snoop_mode() -> String {
sys_prop::get("persist.bluetooth.btsnooplogmode").unwrap_or(if sys_prop::get_debuggable() {
sys_prop::get("persist.bluetooth.btsnoopdefaultmode").unwrap_or_default()
} else {
String::default()
})
}
module! {
snoop_module,
providers {
parts Hal => provide_snooped_hal,
},
}
#[provides]
async fn provide_snooped_hal(config: SnoopConfig, raw_hal: RawHal, rt: Arc<Runtime>) -> Hal {
let (cmd_down_tx, mut cmd_down_rx) = channel::<CommandPacket>(10);
let (evt_up_tx, evt_up_rx) = channel::<EventPacket>(10);
let (acl_down_tx, mut acl_down_rx) = channel::<AclPacket>(10);
let (acl_up_tx, acl_up_rx) = channel::<AclPacket>(10);
let (sco_down_tx, mut sco_down_rx) = channel::<ScoPacket>(10);
let (sco_up_tx, sco_up_rx) = channel::<ScoPacket>(10);
let (iso_down_tx, mut iso_down_rx) = channel::<IsoPacket>(10);
let (iso_up_tx, iso_up_rx) = channel::<IsoPacket>(10);
rt.spawn(async move {
let mut logger = SnoopLogger::new(config).await;
loop {
select! {
Some(evt) = consume(&raw_hal.evt_rx) => {
if let Err(e) = evt_up_tx.send(evt.clone()).await {
error!("evt channel closed {:?}", e);
break;
}
logger.log(Type::Evt, Direction::Up, evt.to_bytes()).await;
},
Some(cmd) = cmd_down_rx.recv() => {
if let Err(e) = raw_hal.cmd_tx.send(cmd.clone()) {
error!("cmd channel closed {:?}", e);
break;
}
logger.log(Type::Cmd, Direction::Down, cmd.to_bytes()).await;
},
Some(acl) = acl_down_rx.recv() => {
if let Err(e) = raw_hal.acl_tx.send(acl.clone()) {
error!("acl down channel closed {:?}", e);
break;
}
logger.log(Type::Acl, Direction::Down, acl.to_bytes()).await;
},
Some(acl) = consume(&raw_hal.acl_rx) => {
if let Err(e) = acl_up_tx.send(acl.clone()).await {
error!("acl up channel closed {:?}", e);
break;
}
logger.log(Type::Acl, Direction::Up, acl.to_bytes()).await;
},
Some(sco) = sco_down_rx.recv() => {
if let Err(e) = raw_hal.sco_tx.send(sco.clone()) {
error!("sco down channel closed {:?}", e);
break;
}
logger.log(Type::Sco, Direction::Down, sco.to_bytes()).await;
},
Some(sco) = consume(&raw_hal.sco_rx) => {
if let Err(e) = sco_up_tx.send(sco.clone()).await {
error!("sco up channel closed {:?}", e);
break;
}
logger.log(Type::Sco, Direction::Up, sco.to_bytes()).await;
},
Some(iso) = iso_down_rx.recv() => {
if let Err(e) = raw_hal.iso_tx.send(iso.clone()) {
error!("iso down channel closed {:?}", e);
break;
}
logger.log(Type::Iso, Direction::Down, iso.to_bytes()).await;
},
Some(iso) = consume(&raw_hal.iso_rx) => {
if let Err(e) = iso_up_tx.send(iso.clone()).await {
error!("iso up channel closed {:?}", e);
break;
}
logger.log(Type::Iso, Direction::Up, iso.to_bytes()).await;
},
else => break,
}
}
});
Hal {
control: ControlHal { tx: cmd_down_tx, rx: Arc::new(Mutex::new(evt_up_rx)) },
acl: AclHal { tx: acl_down_tx, rx: Arc::new(Mutex::new(acl_up_rx)) },
sco: ScoHal { tx: sco_down_tx, rx: Arc::new(Mutex::new(sco_up_rx)) },
iso: IsoHal { tx: iso_down_tx, rx: Arc::new(Mutex::new(iso_up_rx)) },
}
}
async fn consume<T>(rx: &Arc<Mutex<UnboundedReceiver<T>>>) -> Option<T> {
rx.lock().await.recv().await
}
#[allow(unused)]
enum Type {
Cmd = 1,
Acl,
Sco,
Evt,
Iso,
}
enum Direction {
Up,
Down,
}
struct SnoopLogger {
config: SnoopConfig,
file: Option<File>,
packets: u32,
}
// micros since 0000-01-01
const SNOOP_EPOCH_DELTA: u64 = 0x00dcddb30f2f8000;
impl SnoopLogger {
async fn new(mut config: SnoopConfig) -> Self {
// filtered snoop is not available at this time
if let SnoopMode::Filtered = config.mode {
config.mode = SnoopMode::Disabled;
}
remove_file(&config.path).await.ok();
remove_file(config.path.clone() + ".last").await.ok();
if let SnoopMode::Disabled = config.mode {
remove_file(config.path.clone() + ".filtered").await.ok();
remove_file(config.path.clone() + ".filtered.last").await.ok();
}
let mut ret = Self { config, file: None, packets: 0 };
ret.open_next_file().await;
ret
}
async fn log(&mut self, t: Type, dir: Direction, bytes: Bytes) {
if let SnoopMode::Disabled = self.config.mode {
return;
}
let mut flags = 0;
if let Direction::Up = dir {
flags |= 0b01;
}
if let Type::Cmd | Type::Evt = t {
flags |= 0b10;
}
let timestamp: u64 = u64::try_from(
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_micros(),
)
.unwrap()
+ SNOOP_EPOCH_DELTA;
// Add one for the type byte
let length = u32::try_from(bytes.len()).unwrap() + 1;
let mut buffer = BytesMut::new();
buffer.put_u32(length); // original length
buffer.put_u32(length); // captured length
buffer.put_u32(flags); // flags
buffer.put_u32(0); // dropped packets
buffer.put_u64(timestamp); // timestamp
buffer.put_u8(t as u8); // type
buffer.put(bytes);
self.packets += 1;
if self.packets > self.config.max_packets_per_file {
self.open_next_file().await;
}
if let Some(file) = &mut self.file {
if file.write_all(&buffer).await.is_err() {
error!("Failed to write");
}
if file.flush().await.is_err() {
error!("Failed to flush");
}
} else {
panic!("Logging without a backing file");
}
}
async fn close_file(&mut self) {
if let Some(file) = &mut self.file {
file.flush().await.ok();
self.file = None;
}
self.packets = 0;
}
async fn open_next_file(&mut self) {
self.close_file().await;
rename(&self.config.path, self.config.path.clone() + ".last").await.ok();
let mut file = File::create(&self.config.path).await.expect("could not open snoop log");
file.write_all(b"btsnoop\x00\x00\x00\x00\x01\x00\x00\x03\xea")
.await
.expect("could not write snoop header");
if file.flush().await.is_err() {
error!("Failed to flush");
}
self.file = Some(file);
}
}