blob: b1f9b1679531c53872ecb666d781bf428704b10e [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#![feature(async_await, await_macro, futures_api)]
use {
byteorder::{BigEndian, WriteBytesExt},
failure::{Error, ResultExt},
fidl_fuchsia_bluetooth_snoop::{PacketType, SnoopEvent, SnoopMarker, SnoopPacket},
fuchsia_app::client::connect_to_service,
fuchsia_async as fasync,
fuchsia_bluetooth::error::Error as BTError,
futures::TryStreamExt,
std::{fmt, fs::File, io, path::Path},
structopt::StructOpt,
};
const PCAP_CMD: u8 = 0x01;
const PCAP_DATA: u8 = 0x02;
const PCAP_EVENT: u8 = 0x04;
enum Format {
Pcap,
Pretty,
}
impl fmt::Display for Format {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let format = match self {
Format::Pcap => "pcap",
Format::Pretty => "pretty",
};
write!(f, "{}", format)
}
}
fn parse_format(value: &str) -> Format {
if value == "pcap" {
Format::Pcap
} else if value == "pretty" {
Format::Pretty
} else {
eprintln!("Unrecognized format. Using pcap");
Format::Pcap
}
}
// Format described in https://wiki.wireshark.org/Development/LibpcapFileFormat#Global_Header
pub fn pcap_header() -> Vec<u8> {
let mut wtr = vec![];
wtr.write_u32::<BigEndian>(0xa1b2c3d4).unwrap(); // Magic number
wtr.write_u16::<BigEndian>(2).unwrap(); // Major Version
wtr.write_u16::<BigEndian>(4).unwrap(); // Minor Version
wtr.write_i32::<BigEndian>(0).unwrap(); // Timezone: GMT
wtr.write_u32::<BigEndian>(0).unwrap(); // Sigfigs
wtr.write_u32::<BigEndian>(65535).unwrap(); // Max packet length
wtr.write_u32::<BigEndian>(201).unwrap(); // Protocol: BLUETOOTH_HCI_H4_WITH_PHDR
wtr
}
// Format described in
// https://wiki.wireshark.org/Development/LibpcapFileFormat#Record_.28Packet.29_Header
pub fn to_pcap_fmt(pkt: SnoopPacket) -> Vec<u8> {
let mut wtr = vec![];
wtr.write_u32::<BigEndian>(pkt.timestamp.seconds as u32).unwrap(); // timestamp seconds
let microseconds = pkt.timestamp.subsec_nanos / 1_000 as u32; // timestamp microseconds
wtr.write_u32::<BigEndian>(microseconds).unwrap();
// length is len(payload) + 4 octets for is_received + 1 octet for packet type
wtr.write_u32::<BigEndian>((pkt.payload.len() + 5) as u32).unwrap(); // number of octets of packet saved
wtr.write_u32::<BigEndian>((pkt.original_len + 5) as u32).unwrap(); // actual length of packet
wtr.write_u32::<BigEndian>(pkt.is_received as u32).unwrap();
match pkt.type_ {
PacketType::Cmd => {
wtr.write_u8(PCAP_CMD).unwrap();
}
PacketType::Data => {
wtr.write_u8(PCAP_DATA).unwrap();
}
PacketType::Event => {
wtr.write_u8(PCAP_EVENT).unwrap();
}
}
wtr.extend(&pkt.payload);
wtr
}
// Pretty print packet metadata with hex representation of payload
fn to_pretty_fmt(pkt: SnoopPacket) -> String {
let payload = pkt
.payload
.iter()
.map(|byte| format!("{:x}", byte))
.collect::<Vec<String>>()
.join("");
format!(
"{}.{:09}: {:5} {} {}\n",
pkt.timestamp.seconds,
pkt.timestamp.subsec_nanos,
format!("{:?}", pkt.type_),
if pkt.is_received { "RX" } else { "TX" },
payload
)
}
/// Define the command line arguments that the tool accepts.
#[derive(StructOpt)]
#[structopt(
version = "0.2.0",
author = "Fuchsia Bluetooth Team",
about = "Snoop Bluetooth controller packets"
)]
struct Opt {
#[structopt(
short = "d",
long = "dump",
help = "dump the available history of snoop packets"
)]
dump: bool,
#[structopt(
short = "f",
long = "format",
default_value = "pcap",
parse(from_str = "parse_format"),
help = "file format. options: [pcap, pretty]"
)]
format: Format,
#[structopt(
short = "c",
long = "count",
help = "exit after N packets have been recorded."
)]
count: Option<u64>,
#[structopt(
long = "device",
help = "request snoop log for a single device by name."
)]
device: Option<String>,
#[structopt(
short = "o",
long = "output",
help = "output location. Default: stdout"
)]
output: Option<String>,
#[structopt(
short = "t",
long = "truncate",
help = "truncate packets to N bytes before outputting them."
)]
truncate: Option<usize>,
}
/// Construct and print a human friendly message relaying the behavior the tool has been invoked
/// to use.
fn print_opts(opts: &Opt) {
let action = if opts.dump { "Dumping" } else { "Following" };
let device = if let Some(ref device) = opts.device { device } else { "all devices" };
let truncate = if let Some(size) = opts.truncate {
format!("Truncating packets to {} bytes. ", size)
} else {
String::new()
};
let count = if opts.count.is_some() {
format!("up to {} ", opts.count.unwrap())
} else {
"".into()
};
eprintln!(
"{action} snoop log for \"{device}\". {truncation}Outputting {count}packets to {output}.",
action=action,
device=device,
truncation=truncate,
count=count,
output=opts.output.as_ref().unwrap_or(&"stdout".to_string()),
);
}
fn main_res() -> Result<(), Error> {
// Parse and transform command line arguments.
let opts = Opt::from_args();
print_opts(&opts);
let Opt {
dump,
format,
truncate,
count,
device,
output,
} = opts;
let follow = !dump;
let mut out = match output {
Some(ref s) => {
let path = Path::new(s);
Box::new(File::create(&path).unwrap()) as Box<io::Write>
}
None => Box::new(io::stdout()) as Box<io::Write>,
};
// create and run the main future
let main_future = async {
let snoop_svc = connect_to_service::<SnoopMarker>()
.context("failed to connect to bluetooth snoop interface")?;
let mut evt_stream = snoop_svc.take_event_stream();
match format {
Format::Pcap => out.write(pcap_header().as_slice())?,
Format::Pretty => 0,
};
out.flush()?;
// transform device from Option<String> to Option<&str>
let dev_str = device.as_ref().map(|d| d.as_str());
// Send request to start receiving snoop packets
let status = await!(snoop_svc.start(follow, dev_str))?;
if let Some(e) = status.error {
return Err(BTError::from(*e).into());
}
// Receive snoop packet events and output them in the requested format.
let mut pkt_count = 0;
while let Some(evt) = await!(evt_stream.try_next()).expect("failed to fetch event") {
let SnoopEvent::OnPacket { host_device: _, mut packet } = evt;
if let Some(size) = truncate {
packet.payload.truncate(size);
}
match format {
Format::Pcap => out.write(to_pcap_fmt(packet).as_slice())?,
Format::Pretty => out.write(to_pretty_fmt(packet).as_bytes())?,
};
out.flush()?;
if let Some(count) = count {
pkt_count += 1;
if pkt_count == count {
break;
}
}
}
Ok(())
};
fasync::Executor::new()
.context("error creating event loop")?
.run_singlethreaded(main_future)
}
fn main() {
if let Err(e) = main_res() {
eprintln!("Error: {}", e);
}
}