| //! Rootcanal HAL |
| //! This connects to "rootcanal" which provides a simulated |
| //! Bluetooth chip as well as a simulated environment. |
| |
| use crate::hal::internal::{InnerHal, RawHal}; |
| use crate::hal::{Result, H4_HEADER_SIZE}; |
| use bt_packets::hci::{AclPacket, CommandPacket, EventPacket, IsoPacket, Packet}; |
| use bytes::{BufMut, Bytes, BytesMut}; |
| use gddi::{module, provides, Stoppable}; |
| use num_derive::{FromPrimitive, ToPrimitive}; |
| use std::net::{IpAddr, SocketAddr}; |
| use std::str::FromStr; |
| use std::sync::Arc; |
| use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader}; |
| use tokio::net::TcpStream; |
| use tokio::runtime::Runtime; |
| use tokio::select; |
| use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; |
| |
| #[derive(FromPrimitive, ToPrimitive)] |
| enum HciPacketType { |
| Command = 0x01, |
| Acl = 0x02, |
| Sco = 0x03, |
| Event = 0x04, |
| Iso = 0x05, |
| } |
| |
| const SIZE_OF_EVENT_HEADER: usize = 2; |
| const _SIZE_OF_SCO_HEADER: usize = 3; |
| const SIZE_OF_ACL_HEADER: usize = 4; |
| const SIZE_OF_ISO_HEADER: usize = 4; |
| |
| module! { |
| rootcanal_hal_module, |
| providers { |
| RawHal => provide_rootcanal_hal, |
| } |
| } |
| |
| #[provides] |
| async fn provide_rootcanal_hal(config: RootcanalConfig, rt: Arc<Runtime>) -> RawHal { |
| let (raw_hal, inner_hal) = InnerHal::new(); |
| let (reader, writer) = TcpStream::connect(&config.to_socket_addr().unwrap()) |
| .await |
| .expect("unable to create stream to rootcanal") |
| .into_split(); |
| |
| rt.spawn(dispatch_incoming(inner_hal.evt_tx, inner_hal.acl_tx, inner_hal.iso_tx, reader)); |
| rt.spawn(dispatch_outgoing(inner_hal.cmd_rx, inner_hal.acl_rx, inner_hal.iso_rx, writer)); |
| |
| raw_hal |
| } |
| |
| /// Rootcanal configuration |
| #[derive(Clone, Debug, Default, Stoppable)] |
| pub struct RootcanalConfig { |
| address: String, |
| port: u16, |
| } |
| |
| impl RootcanalConfig { |
| /// Create a rootcanal config |
| pub fn new(address: &str, port: u16) -> Self { |
| Self { address: String::from(address), port } |
| } |
| |
| fn to_socket_addr(&self) -> Result<SocketAddr> { |
| Ok(SocketAddr::new(IpAddr::from_str(&self.address)?, self.port)) |
| } |
| } |
| |
| /// Send HCI events received from the HAL to the HCI layer |
| async fn dispatch_incoming<R>( |
| evt_tx: UnboundedSender<EventPacket>, |
| acl_tx: UnboundedSender<AclPacket>, |
| iso_tx: UnboundedSender<IsoPacket>, |
| reader: R, |
| ) -> Result<()> |
| where |
| R: AsyncReadExt + Unpin, |
| { |
| let mut reader = BufReader::new(reader); |
| loop { |
| let mut buffer = BytesMut::with_capacity(1024); |
| buffer.resize(H4_HEADER_SIZE, 0); |
| reader.read_exact(&mut buffer).await?; |
| if buffer[0] == HciPacketType::Event as u8 { |
| buffer.resize(SIZE_OF_EVENT_HEADER, 0); |
| reader.read_exact(&mut buffer).await?; |
| let len: usize = buffer[1].into(); |
| let mut payload = buffer.split_off(SIZE_OF_EVENT_HEADER); |
| payload.resize(len, 0); |
| reader.read_exact(&mut payload).await?; |
| buffer.unsplit(payload); |
| let frozen = buffer.freeze(); |
| match EventPacket::parse(&frozen) { |
| Ok(p) => evt_tx.send(p).unwrap(), |
| Err(e) => log::error!("dropping invalid event packet: {}: {:02x}", e, frozen), |
| } |
| } else if buffer[0] == HciPacketType::Acl as u8 { |
| buffer.resize(SIZE_OF_ACL_HEADER, 0); |
| reader.read_exact(&mut buffer).await?; |
| let len: usize = (buffer[2] as u16 + ((buffer[3] as u16) << 8)).into(); |
| let mut payload = buffer.split_off(SIZE_OF_ACL_HEADER); |
| payload.resize(len, 0); |
| reader.read_exact(&mut payload).await?; |
| buffer.unsplit(payload); |
| let frozen = buffer.freeze(); |
| match AclPacket::parse(&frozen) { |
| Ok(p) => acl_tx.send(p).unwrap(), |
| Err(e) => log::error!("dropping invalid ACL packet: {}: {:02x}", e, frozen), |
| } |
| } else if buffer[0] == HciPacketType::Iso as u8 { |
| buffer.resize(SIZE_OF_ISO_HEADER, 0); |
| reader.read_exact(&mut buffer).await?; |
| let len: usize = (buffer[2] as u16 + (((buffer[3] & 0x3f) as u16) << 8)).into(); |
| let mut payload = buffer.split_off(SIZE_OF_ISO_HEADER); |
| payload.resize(len, 0); |
| reader.read_exact(&mut payload).await?; |
| buffer.unsplit(payload); |
| let frozen = buffer.freeze(); |
| match IsoPacket::parse(&frozen) { |
| Ok(p) => iso_tx.send(p).unwrap(), |
| Err(e) => log::error!("dropping invalid ISO packet: {}: {:02x}", e, frozen), |
| } |
| } |
| } |
| } |
| |
| /// Send commands received from the HCI later to rootcanal |
| async fn dispatch_outgoing<W>( |
| mut cmd_rx: UnboundedReceiver<CommandPacket>, |
| mut acl_rx: UnboundedReceiver<AclPacket>, |
| mut iso_rx: UnboundedReceiver<IsoPacket>, |
| mut writer: W, |
| ) -> Result<()> |
| where |
| W: AsyncWriteExt + Unpin, |
| { |
| loop { |
| select! { |
| Some(cmd) = cmd_rx.recv() => write_with_type(&mut writer, HciPacketType::Command, cmd.to_bytes()).await?, |
| Some(acl) = acl_rx.recv() => write_with_type(&mut writer, HciPacketType::Acl, acl.to_bytes()).await?, |
| Some(iso) = iso_rx.recv() => write_with_type(&mut writer, HciPacketType::Iso, iso.to_bytes()).await?, |
| else => break, |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| async fn write_with_type<W>(writer: &mut W, t: HciPacketType, b: Bytes) -> Result<()> |
| where |
| W: AsyncWriteExt + Unpin, |
| { |
| let mut data = BytesMut::with_capacity(b.len() + 1); |
| data.put_u8(t as u8); |
| data.extend(b); |
| writer.write_all(&data[..]).await?; |
| |
| Ok(()) |
| } |