blob: c62afc4525351111421a48f9cba6d897372d89db [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.
use {
failure::{err_msg, Error},
fidl_fuchsia_bluetooth_snoop::{Timestamp, PacketType, SnoopPacket},
fuchsia_async as fasync,
fuchsia_bluetooth::hci,
fuchsia_zircon::{Channel, MessageBuf},
futures::{task::LocalWaker, Poll, Stream},
std::{
fs::OpenOptions,
marker::Unpin,
path::PathBuf,
pin::Pin,
time::{SystemTime, UNIX_EPOCH},
},
};
/// A wrapper type for the bitmask representing flags in the snoop channel protocol.
struct HciFlags(u8);
impl HciFlags {
const IS_RECEIVED: u8 = 0b100;
const PACKET_TYPE_CMD: u8 = 0b00;
const PACKET_TYPE_EVENT: u8 = 0b01;
// Included for completeness. Used in tests
#[allow(dead_code)]
const PACKET_TYPE_DATA: u8 = 0b10;
/// Does the packet represent an HCI event sent from the controller to the host?
fn is_received(&self) -> bool {
self.0 & HciFlags::IS_RECEIVED == HciFlags::IS_RECEIVED
}
/// Returns the packet type.
fn hci_packet_type(&self) -> PacketType {
match self.0 & 0b11 {
HciFlags::PACKET_TYPE_CMD => PacketType::Cmd,
HciFlags::PACKET_TYPE_EVENT => PacketType::Event,
_ => PacketType::Data,
}
}
}
/// A Snooper provides a `Stream` associated with the snoop channel for a single HCI device. This
/// stream can be polled for `SnoopPacket`s coming off the channel.
pub(crate) struct Snooper {
pub device_name: String,
pub device_path: PathBuf,
pub chan: fuchsia_async::Channel,
}
impl Snooper {
/// Create a new snooper from a device path. This opens a new snoop channel, returning an error
/// if the devices doesn't exist or the channel cannot be created.
#[allow(dead_code)] // used in future
pub fn new(device_path: PathBuf) -> Result<Snooper, Error> {
let hci_device = OpenOptions::new()
.read(true)
.write(true)
.open(&device_path)?;
let channel = Channel::from(hci::open_snoop_channel(&hci_device)?);
Snooper::from_channel(channel, device_path)
}
/// Take a channel and wrap it in a `Snooper`.
#[allow(dead_code)] // used in tests
pub fn from_channel(chan: Channel, device_path: PathBuf) -> Result<Snooper, Error> {
let chan = fasync::Channel::from_channel(chan)?;
let device_name = device_path
.file_name()
.ok_or(err_msg("A device has no name"))?
.to_string_lossy()
.into_owned();
Ok(Snooper {
device_name,
device_path,
chan,
})
}
/// Parse a raw byte buffer and return a SnoopPacket. Returns `None` if the buffer does not
/// contain a valid packet.
pub fn build_pkt(buf: MessageBuf) -> Option<SnoopPacket> {
let duration = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
if buf.bytes().is_empty() {
return None;
}
let flags = HciFlags(buf.bytes()[0]);
let payload = buf.bytes()[1..].to_vec();
let pkt = SnoopPacket {
is_received: flags.is_received(),
type_: flags.hci_packet_type(),
timestamp: Timestamp {
subsec_nanos: duration.subsec_nanos(),
seconds: duration.as_secs(),
},
original_len: payload.len() as u32,
payload,
};
Some(pkt)
}
}
impl Unpin for Snooper {}
impl Stream for Snooper {
type Item = (String, SnoopPacket);
fn poll_next(self: Pin<&mut Self>, lw: &LocalWaker) -> Poll<Option<Self::Item>> {
let mut buf = MessageBuf::new();
match self.chan.recv_from(&mut buf, lw) {
Poll::Ready(_t) => {
let item = Snooper::build_pkt(buf).map(|pkt| (self.device_name.clone(), pkt));
Poll::Ready(item)
},
Poll::Pending => Poll::Pending,
}
}
}
#[cfg(test)]
mod tests {
use {
super::*,
fuchsia_async as fasync,
fuchsia_zircon::Channel,
futures::StreamExt,
std::{path::PathBuf, task::Poll},
};
#[test]
fn test_from_channel() {
let _exec = fasync::Executor::new();
let (channel, _) = Channel::create().unwrap();
let snooper = Snooper::from_channel(channel, PathBuf::from("/a/b/c")).unwrap();
assert_eq!(snooper.device_name, "c");
assert_eq!(snooper.device_path, PathBuf::from("/a/b/c"));
}
#[test]
fn test_build_pkt() {
let flags = HciFlags::IS_RECEIVED;
let buf = MessageBuf::new_with(vec![flags], vec![]);
let pkt = Snooper::build_pkt(buf).unwrap();
assert!(pkt.is_received);
assert!(pkt.payload.is_empty());
assert!(pkt.timestamp.seconds > 0);
assert_eq!(pkt.type_, PacketType::Cmd);
let flags = HciFlags::PACKET_TYPE_DATA;
let buf = MessageBuf::new_with(vec![flags, 0, 1, 2], vec![]);
let pkt = Snooper::build_pkt(buf).unwrap();
assert!(!pkt.is_received);
assert_eq!(pkt.payload, vec![0, 1, 2]);
assert_eq!(pkt.type_, PacketType::Data);
}
#[test]
fn test_snoop_stream() {
let mut exec = fasync::Executor::new().unwrap();
let (tx, rx) = Channel::create().unwrap();
let mut snooper = Snooper::from_channel(rx, PathBuf::from("/a/b/c")).unwrap();
let flags = HciFlags::IS_RECEIVED | HciFlags::PACKET_TYPE_EVENT;
tx.write(&[flags, 0, 1, 2], &mut vec![]).unwrap();
tx.write(&[0, 3, 4, 5], &mut vec![]).unwrap();
let item_1 = exec.run_until_stalled(&mut snooper.next());
let item_2 = exec.run_until_stalled(&mut snooper.next());
assert!(item_1.is_ready());
assert!(item_2.is_ready());
match (item_1, item_2) {
(Poll::Ready(item_1), Poll::Ready(item_2)) => {
assert_eq!(item_1.unwrap().1.payload, vec![0, 1, 2]);
assert_eq!(item_2.unwrap().1.payload, vec![3, 4, 5]);
}
_ => panic!("failed to build both packets 1 and 2 from snoop stream"),
}
let item_3 = exec.run_until_stalled(&mut snooper.next());
assert!(item_3.is_pending());
}
}