blob: 3376e535c64522c174051eae02df6c26aa86dbce [file] [log] [blame]
// Copyright 2021, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Rootcanal HAL
//! This connects to "rootcanal" which provides a simulated
//! Nfc chip as well as a simulated environment.
use crate::internal::InnerHal;
use crate::{is_control_packet, Hal, HalEvent, HalEventRegistry, HalEventStatus, Result};
use bytes::{BufMut, BytesMut};
use log::{debug, error};
use nfc_packets::nci::{DataPacket, NciPacket};
use pdl_runtime::Packet;
use std::convert::TryInto;
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpStream;
use tokio::select;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
/// Initialize the module
pub async fn init() -> Hal {
let (raw_hal, inner_hal) = InnerHal::new();
let (reader, writer) = TcpStream::connect("127.0.0.1:7000")
.await
.expect("unable to create stream to rootcanal")
.into_split();
let reader = BufReader::new(reader);
tokio::spawn(dispatch_incoming(inner_hal.in_cmd_tx, inner_hal.in_data_tx, reader));
tokio::spawn(dispatch_outgoing(
raw_hal.hal_events.clone(),
inner_hal.out_cmd_rx,
inner_hal.out_data_rx,
writer,
));
raw_hal
}
/// Send NCI events received from the HAL to the NCI layer
async fn dispatch_incoming<R>(
in_cmd_tx: UnboundedSender<NciPacket>,
in_data_tx: UnboundedSender<DataPacket>,
mut reader: R,
) -> Result<()>
where
R: AsyncReadExt + Unpin,
{
loop {
let mut buffer = BytesMut::with_capacity(1024);
let len: usize = reader.read_u16().await?.into();
buffer.resize(len, 0);
reader.read_exact(&mut buffer).await?;
let frozen = buffer.freeze();
debug!("{:?}", &frozen);
if is_control_packet(&frozen[..]) {
match NciPacket::parse(&frozen) {
Ok(p) => {
if in_cmd_tx.send(p).is_err() {
break;
}
}
Err(e) => error!("dropping invalid cmd event packet: {}: {:02x}", e, frozen),
}
} else {
match DataPacket::parse(&frozen) {
Ok(p) => {
if in_data_tx.send(p).is_err() {
break;
}
}
Err(e) => error!("dropping invalid data event packet: {}: {:02x}", e, frozen),
}
}
}
debug!("Dispatch incoming finished.");
Ok(())
}
/// Send commands received from the NCI later to rootcanal
async fn dispatch_outgoing<W>(
mut hal_events: HalEventRegistry,
mut out_cmd_rx: UnboundedReceiver<NciPacket>,
mut out_data_rx: UnboundedReceiver<DataPacket>,
mut writer: W,
) -> Result<()>
where
W: AsyncWriteExt + Unpin,
{
loop {
select! {
Some(cmd) = out_cmd_rx.recv() => write_nci(&mut writer, cmd).await?,
Some(data) = out_data_rx.recv() => write_nci(&mut writer, data).await?,
else => break,
}
}
writer.shutdown().await?;
if let Some(evt) = hal_events.unregister(HalEvent::CloseComplete).await {
evt.send(HalEventStatus::Success).unwrap();
}
debug!("Dispatch outgoing finished.");
Ok(())
}
async fn write_nci<W, P>(writer: &mut W, cmd: P) -> Result<()>
where
W: AsyncWriteExt + Unpin,
P: Packet,
{
let b = cmd.encode_to_bytes().unwrap();
let mut data = BytesMut::with_capacity(b.len() + 2);
data.put_u16(b.len().try_into().unwrap());
data.extend(b);
writer.write_all(&data[..]).await?;
debug!("Sent {:?}", data);
Ok(())
}