blob: 8206ed2aadec04d7c6697584ea8addd7f0b9c62c [file] [log] [blame]
// Copyright 2020 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 {
anyhow::{anyhow, Error},
fidl_fuchsia_bluetooth_bredr::{ChannelMode, ChannelParameters},
fuchsia_async as fasync, fuchsia_zircon as zx,
futures::channel::{mpsc, oneshot},
std::{
cmp::PartialEq,
collections::{hash_map::Iter, HashMap},
convert::TryFrom,
fmt::Debug,
iter::IntoIterator,
},
};
/// The default buffer size for the mpsc channels used to relay user data
/// packets to be sent to the remote peer.
/// This value is arbitrarily chosen and should be enough to send buffers
/// from multiple `Cmd::Send` commands.
const USER_DATA_BUFFER_SIZE: usize = 50;
/// The ServerChannel number of the service advertised by the remote peer. This
/// is used as an identifier for the RFCOMM service.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct ServerChannelNumber(pub u8);
impl TryFrom<u8> for ServerChannelNumber {
type Error = Error;
fn try_from(src: u8) -> Result<Self, Self::Error> {
if src < 1 || src > 30 {
return Err(anyhow!("Server channel must be [1, 30]"));
}
Ok(ServerChannelNumber(src))
}
}
#[derive(Debug, PartialEq)]
pub struct IncrementedIdMap<T> {
next_id: u32,
map: HashMap<u32, T>,
}
impl<T: Debug> IncrementedIdMap<T> {
pub fn new() -> IncrementedIdMap<T> {
IncrementedIdMap { next_id: 0, map: HashMap::new() }
}
pub fn map(&self) -> &HashMap<u32, T> {
&self.map
}
/// Returns id assigned.
pub fn insert(&mut self, value: T) -> u32 {
let id = self.next_id;
self.next_id += 1;
assert!(self.map.insert(id, value).is_none());
id
}
pub fn remove(&mut self, id: &u32) -> Option<T> {
self.map.remove(id)
}
}
impl<'a, T> IntoIterator for &'a IncrementedIdMap<T> {
type Item = (&'a u32, &'a T);
type IntoIter = Iter<'a, u32, T>;
fn into_iter(self) -> Self::IntoIter {
self.map.iter()
}
}
#[derive(Debug)]
pub struct L2capChannel {
pub socket: zx::Socket,
pub mode: ChannelMode,
pub max_tx_sdu_size: u16,
}
#[derive(Debug)]
pub struct SdpService {
pub advertisement_stopper: oneshot::Sender<()>,
pub params: ChannelParameters,
}
/// Tracks all state local to the command line tool.
pub struct ProfileState {
/// Currently connected L2CAP channels.
pub l2cap_channels: IncrementedIdMap<L2capChannel>,
/// Currently active service advertisements.
pub services: IncrementedIdMap<SdpService>,
/// The current RFCOMM state.
pub rfcomm: RfcommState,
}
impl ProfileState {
pub fn new() -> ProfileState {
ProfileState {
l2cap_channels: IncrementedIdMap::new(),
services: IncrementedIdMap::new(),
rfcomm: RfcommState::new(),
}
}
pub fn reset(&mut self) {
// Dropping the services will stop the advertisements.
self.services = IncrementedIdMap::new();
// Dropping the L2CAP sockets will disconnect channels.
self.l2cap_channels = IncrementedIdMap::new();
// Resetting the RFCOMM state will cancel any active service advertisement &
// search and disconnect the channels.
self.rfcomm = RfcommState::new();
}
}
/// A payload containing user data to be sent over an RFCOMM channel.
type UserData = Vec<u8>;
/// Tracks the state local to the command line tool.
pub struct RfcommState {
/// The task representing the RFCOMM service advertisement and search.
pub service: Option<fasync::Task<()>>,
/// The currently connected and active RFCOMM channels. Each channel
/// is represented by a sender that is used to send user data to the
/// remote peer.
active_channels: HashMap<ServerChannelNumber, mpsc::Sender<UserData>>,
}
impl RfcommState {
pub fn new() -> Self {
Self { active_channels: HashMap::new(), service: None }
}
/// Creates a new RFCOMM channel for the provided `server_channel`. Returns a
/// receiver for user data to be sent to the remote peer.
pub fn create_channel(
&mut self,
server_channel: ServerChannelNumber,
) -> mpsc::Receiver<UserData> {
let (sender, receiver) = mpsc::channel(USER_DATA_BUFFER_SIZE);
self.active_channels.insert(server_channel, sender);
receiver
}
/// Removes the RFCOMM channel for the provided `server_channel`. Returns true if
/// the channel was removed.
pub fn remove_channel(&mut self, server_channel: ServerChannelNumber) -> bool {
self.active_channels.remove(&server_channel).is_some()
}
/// Sends the `user_data` buf to the peer that provides the service identified
/// by the `server_channel`. Returns the result of the send operation.
pub fn send_user_data(
&mut self,
server_channel: ServerChannelNumber,
user_data: UserData,
) -> Result<(), Error> {
self.active_channels
.get_mut(&server_channel)
.ok_or(anyhow!("No registered server channel"))
.and_then(|sender| sender.try_send(user_data).map_err(|e| anyhow!("{:?}", e)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::{task::Poll, StreamExt};
use matches::assert_matches;
#[test]
fn incremented_id_map() {
let mut numbers = IncrementedIdMap::<i32>::new();
assert_eq!(0, numbers.insert(0));
assert_eq!(1, numbers.insert(1));
assert_eq!(2, numbers.map().len());
assert_eq!(Some(&0i32), numbers.map().get(&0u32));
assert_eq!(Some(&1i32), numbers.map().get(&1u32));
}
#[test]
fn send_rfcomm_data_is_received_by_peer() {
let mut exec = fasync::Executor::new().unwrap();
let mut state = ProfileState::new();
assert!(state.rfcomm.active_channels.is_empty());
// Registering channel is OK.
let server_channel = ServerChannelNumber(5);
let mut receiver = state.rfcomm.create_channel(server_channel);
assert!(state.rfcomm.active_channels.contains_key(&server_channel));
let mut received_bytes = Box::pin(receiver.next());
assert!(exec.run_until_stalled(&mut received_bytes).is_pending());
// Sending user data to the peer should be successful.
let random_user_data = vec![0x00, 0x02, 0x04, 0x06, 0x08];
assert_matches!(
state.rfcomm.send_user_data(server_channel, random_user_data.clone()),
Ok(_)
);
match exec.run_until_stalled(&mut received_bytes) {
Poll::Ready(Some(buf)) => {
assert_eq!(buf, random_user_data);
}
x => panic!("Expected ready with data but got: {:?}", x),
}
// Removing channel is OK.
assert!(state.rfcomm.remove_channel(server_channel));
// Trying to send more data on the closed channel should fail immediately.
assert_matches!(state.rfcomm.send_user_data(server_channel, vec![0x09]), Err(_));
}
}