#![warn(
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
non_ascii_idents,
trivial_casts,
unused,
unused_qualifications
)]
#![deny(unsafe_code)]
pub mod error;
mod buffer;
mod hid;
pub use hid::{Device as HidDevice, DeviceInfo as HidDeviceInfo};
use std::{convert::TryFrom, fmt, time::Duration};
use ctaphid_types::{
Capabilities, Channel, Command, DeviceVersion, InitResponse, Message, ParseError, VendorCommand,
};
use rand_core::{OsRng, RngCore};
use error::{CommandError, Error, RequestError, ResponseError};
pub struct Device<D: HidDevice> {
device: D,
device_info: D::Info,
channel: Channel,
protocol_version: u8,
device_version: DeviceVersion,
capabilities: Capabilities,
buffer: buffer::MessageBuffer,
timeout: Option<Duration>,
}
impl<D: HidDevice> Device<D> {
pub fn new(device: D, device_info: D::Info) -> Result<Self, Error> {
Self::with_options(device, device_info, Default::default())
}
pub fn with_options(device: D, device_info: D::Info, options: Options) -> Result<Self, Error> {
let buffer = buffer::MessageBuffer::new(device_info.packet_size());
let mut device = Self {
device,
device_info,
channel: Channel::BROADCAST,
protocol_version: Default::default(),
device_version: Default::default(),
capabilities: Default::default(),
buffer,
timeout: options.timeout,
};
let response = device.init(options.nonce)?;
device.channel = response.channel;
device.protocol_version = response.protocol_version;
device.device_version = response.device_version;
device.capabilities = response.capabilities;
Ok(device)
}
pub fn ping(&self, data: &[u8]) -> Result<(), Error> {
let response = self.transaction(Command::Ping, data)?;
if data == response {
Ok(())
} else {
Err(Error::from(CommandError::InvalidPingData))
}
}
pub fn wink(&self) -> Result<(), Error> {
if !self.capabilities.has_wink() {
return Err(CommandError::NotSupported(Command::Wink).into());
}
let response = self.transaction(Command::Wink, &[])?;
if response.is_empty() {
Ok(())
} else {
Err(ResponseError::UnexpectedResponseData(response).into())
}
}
pub fn ctap1(&self, data: &[u8]) -> Result<Vec<u8>, Error> {
if !self.capabilities.has_msg() {
return Err(CommandError::NotSupported(Command::Message).into());
}
self.transaction(Command::Message, data)
}
pub fn ctap2(&self, command: u8, data: &[u8]) -> Result<Vec<u8>, Error> {
if !self.capabilities.has_cbor() {
return Err(CommandError::NotSupported(Command::Cbor).into());
}
let data = &[&[command], data].concat();
let response = self.transaction(Command::Cbor, data)?;
if response.is_empty() {
Err(ResponseError::PacketParsingFailed(ParseError::NotEnoughData).into())
} else if response[0] != 0 {
Err(CommandError::CborError(response[0]).into())
} else {
Ok(response[1..].to_owned())
}
}
pub fn lock(&self, duration: Duration) -> Result<(), Error> {
let mut duration = u8::try_from(duration.as_secs()).unwrap_or(u8::MAX);
if duration > 10 {
duration = 10;
}
let response = self.transaction(Command::Lock, &[duration])?;
if response.is_empty() {
Ok(())
} else {
Err(ResponseError::UnexpectedResponseData(response).into())
}
}
pub fn vendor_command(&self, command: VendorCommand, data: &[u8]) -> Result<Vec<u8>, Error> {
self.transaction(Command::Vendor(command), data)
}
fn init(&self, nonce: [u8; 8]) -> Result<InitResponse<Vec<u8>>, Error> {
self.send_message(Command::Init, &nonce)?;
loop {
let response = self.receive_message(Command::Init)?;
let response = InitResponse::try_from(response.as_slice())
.map_err(ResponseError::PacketParsingFailed)?;
if nonce == response.nonce {
return Ok(response);
}
}
}
fn transaction(&self, command: Command, data: &[u8]) -> Result<Vec<u8>, Error> {
self.send_message(command, data)?;
let response = self.receive_message(command)?;
Ok(response)
}
fn send_message(&self, command: Command, data: &[u8]) -> Result<(), RequestError> {
let message = Message {
channel: self.channel,
command,
data,
};
self.buffer.send_message(&self.device, message)
}
fn receive_message(&self, command: Command) -> Result<Vec<u8>, ResponseError> {
self.buffer
.receive_message(&self.device, self.channel, command, self.timeout)
.map(|message| message.data)
}
pub fn info(&self) -> &D::Info {
&self.device_info
}
pub fn protocol_version(&self) -> u8 {
self.protocol_version
}
pub fn device_version(&self) -> DeviceVersion {
self.device_version
}
pub fn capabilities(&self) -> Capabilities {
self.capabilities
}
}
impl<D: HidDevice> fmt::Debug for Device<D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Device")
.field("device_info", &self.device_info)
.field("channel", &self.channel)
.field("protocol_version", &self.protocol_version)
.field("device_version", &self.device_version)
.field("capabilities", &self.capabilities)
.finish()
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
#[allow(missing_copy_implementations)]
pub struct Options {
pub nonce: [u8; 8],
pub timeout: Option<Duration>,
}
impl Options {
pub fn new() -> Self {
Self::default()
}
pub fn with_rng(rng: &mut dyn RngCore) -> Self {
let mut nonce = [0; 8];
rng.fill_bytes(&mut nonce);
Self {
nonce,
timeout: Some(Duration::from_secs(1)),
}
}
}
impl Default for Options {
fn default() -> Self {
Self::with_rng(&mut OsRng)
}
}
pub fn is_known_device<D: HidDeviceInfo>(device: &D) -> bool {
matches!(
(device.vendor_id(), device.product_id()),
(0x1209, 0xbeee) |
(0x20a0, 0x42b1) |
(0x20a0, 0x42b2)
)
}