blob: 1b05405a66612ee31de2a3fb6bbcf1d86e810a99 [file] [log] [blame]
//! This connects to "rootcanal" and provides a simulated
//! Nfc chip as well as a simulated environment.
use bytes::{BufMut, BytesMut};
use nfc_packets::nci;
use nfc_packets::nci::NciChild::{InitCommand, ResetCommand};
use nfc_packets::nci::{
ConfigStatus, NciVersion, ResetNotificationBuilder, ResetResponseBuilder, ResetTrigger,
ResetType,
};
use nfc_packets::nci::{InitResponseBuilder, NfccFeatures, RfInterface};
use nfc_packets::nci::{NciMsgType, NciPacket, Packet, PacketBoundaryFlag};
// use num_derive::{FromPrimitive, ToPrimitive};
use std::convert::TryInto;
use thiserror::Error;
use tokio::io;
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpListener;
/// Result type
type Result<T> = std::result::Result<T, RootcanalError>;
#[derive(Debug, Error)]
enum RootcanalError {
#[error("Termination request")]
TerminateTask,
#[error("Socket error")]
IoError(#[from] io::Error),
#[error("Unsupported command packet")]
UnsupportedCommand,
#[error("Packet did not parse correctly")]
InvalidPacket,
#[error("Packet type not supported")]
UnsupportedPacket,
}
const TERMINATION: u8 = 4u8;
#[tokio::main]
async fn main() -> io::Result<()> {
logger::init(
logger::Config::default().with_tag_on_device("nfc-rc").with_min_level(log::Level::Trace),
);
let listener = TcpListener::bind("127.0.0.1:54323").await?;
let (mut sock, _) = listener.accept().await?;
tokio::spawn(async move {
let (rd, mut wr) = sock.split();
let mut rd = BufReader::new(rd);
loop {
if let Err(e) = process(&mut rd, &mut wr).await {
match e {
RootcanalError::TerminateTask => break,
_ => panic!("Communication error: {:?}", e),
}
}
}
})
.await?;
Ok(())
}
async fn process<R, W>(reader: &mut R, writer: &mut W) -> Result<()>
where
R: AsyncReadExt + Unpin,
W: AsyncWriteExt + Unpin,
{
let mut buffer = BytesMut::with_capacity(1024);
let pkt_type = reader.read_u8().await?;
let len: usize = reader.read_u16().await?.into();
log::debug!("packet {} received len={}", &pkt_type, &len);
buffer.resize(len, 0);
reader.read_exact(&mut buffer).await?;
let frozen = buffer.freeze();
log::debug!("{:?}", &frozen);
if pkt_type == NciMsgType::Command as u8 {
match NciPacket::parse(&frozen) {
Ok(p) => command_response(writer, p).await,
Err(_) => Err(RootcanalError::InvalidPacket),
}
} else if pkt_type == TERMINATION {
Err(RootcanalError::TerminateTask)
} else {
Err(RootcanalError::UnsupportedPacket)
}
}
async fn command_response<W>(out: &mut W, cmd: NciPacket) -> Result<()>
where
W: AsyncWriteExt + Unpin,
{
let pbf = PacketBoundaryFlag::CompleteOrFinal;
match cmd.specialize() {
ResetCommand(rst) => {
write_nci(out, (ResetResponseBuilder { pbf, status: nci::Status::Ok }).build()).await?;
write_nci(
out,
(ResetNotificationBuilder {
pbf,
trigger: ResetTrigger::ResetCommand,
config_status: if rst.get_reset_type() == ResetType::KeepConfig {
ConfigStatus::ConfigKept
} else {
ConfigStatus::ConfigReset
},
nci_version: NciVersion::Version20,
manufacturer_id: 0,
payload: None,
})
.build(),
)
.await
}
InitCommand(_) => {
let nfcc_feat = [0u8; 5];
let rf_int = [0u8, 2];
write_nci(
out,
(InitResponseBuilder {
pbf,
status: nci::Status::Ok,
nfcc_features: NfccFeatures::parse(&nfcc_feat).unwrap(),
max_log_conns: 0,
max_rout_tbls_size: 0x0000,
max_ctrl_payload: 255,
max_data_payload: 255,
num_of_credits: 0,
max_nfcv_rf_frame_sz: 64,
rf_interface: vec![RfInterface::parse(&rf_int).unwrap()],
})
.build(),
)
.await
}
_ => Err(RootcanalError::UnsupportedCommand),
}
}
async fn write_nci<W, T>(writer: &mut W, rsp: T) -> Result<()>
where
W: AsyncWriteExt + Unpin,
T: Into<NciPacket>,
{
let pkt = rsp.into();
let pkt_type = pkt.get_mt() as u8;
let b = pkt.to_bytes();
let mut data = BytesMut::with_capacity(b.len() + 3);
data.put_u8(pkt_type);
data.put_u16(b.len().try_into().unwrap());
data.extend(b);
let frozen = data.freeze();
writer.write_all(frozen.as_ref()).await?;
log::debug!("command written");
Ok(())
}