diff --git a/src/hid_common.rs b/src/hid_common.rs index c5258ef..794a5fa 100644 --- a/src/hid_common.rs +++ b/src/hid_common.rs @@ -12,4 +12,5 @@ pub struct DeviceInfo { pub path: PathBuf, pub usage_page: u16, pub usage: u16, + pub report_size: u16, } diff --git a/src/hid_linux.rs b/src/hid_linux.rs index 6d6ff44..0612cbd 100644 --- a/src/hid_linux.rs +++ b/src/hid_linux.rs @@ -83,5 +83,6 @@ fn path_to_device(path: &PathBuf) -> io::Result { path: device_path, usage_page, usage, + report_size, }) } diff --git a/src/lib.rs b/src/lib.rs index 6ca20b2..7be3529 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,14 +61,13 @@ use std::cmp; use std::u8; use std::u16; use std::fs; -use std::io::{Read, Write, Cursor}; +use std::io::{Write, Cursor}; use failure::{Fail, ResultExt}; use rand::prelude::*; use num_traits::FromPrimitive; use self::hid_linux as hid; use self::packet::CtapCommand; -use self::packet::Packet; pub use self::error::*; static BROADCAST_CID: [u8; 4] = [0xff, 0xff, 0xff, 0xff]; @@ -135,6 +134,10 @@ impl FidoDevice { if response.len() < 17 || response[0..8] != nonce { Err(FidoErrorKind::ParseCtap)? } + let flags = response[16]; + if flags & 0x04 == 0 { + Err(FidoErrorKind::DeviceUnsupported)? + } self.channel_id.copy_from_slice(&response[8..12]); let response = match self.cbor(cbor::Request::GetInfo)? { cbor::Response::GetInfo(resp) => resp, @@ -352,21 +355,13 @@ impl FidoDevice { let to_send = payload.len() as u16; let max_payload = (self.packet_size - 7) as usize; let (frame, payload) = payload.split_at(cmp::min(payload.len(), max_payload)); - { - let packet = packet::InitPacket::new(&self.channel_id, cmd, to_send, frame); - self.device.write(packet.to_wire_format()).context( - FidoErrorKind::WritePacket, - )?; - } + packet::write_init_packet(&mut self.device, 64, &self.channel_id, cmd, to_send, frame)?; if payload.is_empty() { return Ok(()); } let max_payload = (self.packet_size - 5) as usize; for (seq, frame) in (0..u8::MAX).zip(payload.chunks(max_payload)) { - let packet = packet::ContPacket::new(&self.channel_id, seq, frame); - self.device.write(packet.to_wire_format()).context( - FidoErrorKind::WritePacket, - )?; + packet::write_cont_packet(&mut self.device, 64, &self.channel_id, seq, frame)?; } self.device.flush().context(FidoErrorKind::WritePacket)?; Ok(()) @@ -374,43 +369,33 @@ impl FidoDevice { fn receive(&mut self, cmd: &CtapCommand) -> FidoResult> { let mut first_packet: Option = None; - let mut packet_size = 0; while first_packet.is_none() { - let mut buf = [0; 64]; - packet_size = self.device.read(&mut buf).context( - FidoErrorKind::ReadPacket, - )?; - let packet = packet::InitPacket::from_wire_format(&buf[0..packet_size]); - if packet.cmd() == CtapCommand::Error { + let packet = packet::InitPacket::from_reader(&mut self.device, 64)?; + if packet.cmd == CtapCommand::Error { Err( - packet::CtapError::from_u8(packet.payload()[0]) + packet::CtapError::from_u8(packet.payload[0]) .unwrap_or(packet::CtapError::Other) .context(FidoErrorKind::ParseCtap), )? } - if packet.cid() == self.channel_id && &packet.cmd() == cmd { + if packet.cid == self.channel_id && &packet.cmd == cmd { first_packet = Some(packet); } } let first_packet = first_packet.unwrap(); - let mut data = first_packet.payload()[0..(packet_size - 7)].to_vec(); - let mut to_read = (first_packet.size() as isize) - data.len() as isize; + let mut data = first_packet.payload; + let mut to_read = (first_packet.size as isize) - data.len() as isize; let mut seq = 0; while to_read > 0 { - let mut buf = [0; 64]; - let packet_size = self.device.read(&mut buf).context( - FidoErrorKind::ReadPacket, - )?; - let packet = packet::ContPacket::from_wire_format(&buf[0..packet_size]); - if packet.cid() != self.channel_id { + let packet = packet::ContPacket::from_reader(&mut self.device, 64, to_read as usize)?; + if packet.cid != self.channel_id { continue; } - if packet.seq() != seq { + if packet.seq != seq { Err(FidoErrorKind::InvalidSequence)? } - let payload_size = packet_size - 5; - to_read -= payload_size as isize; - data.extend(&packet.payload()[0..payload_size]); + to_read -= packet.payload.len() as isize; + data.extend(&packet.payload); seq += 1; } Ok(data) diff --git a/src/packet.rs b/src/packet.rs index 19825df..943e12c 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -5,6 +5,10 @@ // http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. use num_traits::{FromPrimitive, ToPrimitive}; +use failure::ResultExt; +use super::error::*; + +use std::io::{Read, Write}; static FRAME_INIT: u8 = 0x80; @@ -55,89 +59,121 @@ pub enum CtapError { Other = 0x7F, } -pub trait Packet { - fn from_wire_format(data: &[u8]) -> Self; - - fn to_wire_format(&self) -> &[u8]; +pub fn write_init_packet( + mut writer: W, + report_size: usize, + cid: &[u8], + cmd: &CtapCommand, + size: u16, + payload: &[u8], +) -> FidoResult<()> { + if cid.len() != 4 { + Err(FidoErrorKind::WritePacket)? + } + let mut packet = Vec::with_capacity(report_size); + packet.push(0); + packet.extend_from_slice(cid); + packet.push(FRAME_INIT | cmd.to_wire_format()); + packet.push(((size >> 8) & 0xff) as u8); + packet.push((size & 0xff) as u8); + packet.extend_from_slice(payload); + if packet.len() > report_size + 1 { + Err(FidoErrorKind::WritePacket)? + } + packet.resize(report_size + 1, 0); + writer.write_all(&packet).context( + FidoErrorKind::WritePacket, + )?; + Ok(()) } -pub struct InitPacket(pub [u8; 65]); +pub struct InitPacket { + pub cid: [u8; 4], + pub cmd: CtapCommand, + pub size: u16, + pub payload: Vec, +} impl InitPacket { - pub fn new(cid: &[u8], cmd: &CtapCommand, size: u16, payload: &[u8]) -> InitPacket { - let mut packet = InitPacket([0; 65]); - packet.0[1..5].copy_from_slice(cid); - packet.0[5] = FRAME_INIT | cmd.to_wire_format(); - packet.0[6] = ((size >> 8) & 0xff) as u8; - packet.0[7] = (size & 0xff) as u8; - packet.0[8..(payload.len() + 8)].copy_from_slice(payload); - packet - } - - pub fn cid(&self) -> &[u8] { - &self.0[1..5] - } - - pub fn cmd(&self) -> CtapCommand { - match CtapCommand::from_u8(self.0[5] ^ FRAME_INIT) { + pub fn from_reader(mut reader: R, report_size: usize) -> FidoResult { + let mut buf = Vec::with_capacity(report_size); + buf.resize(report_size, 0); + reader.read_exact(&mut buf[0..report_size]).context( + FidoErrorKind::ReadPacket, + )?; + let mut cid = [0; 4]; + cid.copy_from_slice(&buf[0..4]); + let cmd = match CtapCommand::from_u8(buf[4] ^ FRAME_INIT) { Some(cmd) => cmd, None => CtapCommand::Invalid, - } - } - - pub fn size(&self) -> u16 { - ((u16::from(self.0[6])) << 8) | u16::from(self.0[7]) - } - - pub fn payload(&self) -> &[u8] { - &self.0[8..65] + }; + let size = ((u16::from(buf[5])) << 8) | u16::from(buf[6]); + let payload_end = if (size as usize) >= (report_size - 7) { + report_size + } else { + size as usize + 7 + }; + let payload = buf.drain(7..payload_end).collect(); + Ok(InitPacket { + cid, + cmd, + size, + payload, + }) } } -impl Packet for InitPacket { - fn from_wire_format(data: &[u8]) -> InitPacket { - let mut packet = InitPacket([0; 65]); - packet.0[1..65].copy_from_slice(data); - packet +pub fn write_cont_packet( + mut writer: W, + report_size: usize, + cid: &[u8], + seq: u8, + payload: &[u8], +) -> FidoResult<()> { + if cid.len() != 4 { + Err(FidoErrorKind::WritePacket)? } - - fn to_wire_format(&self) -> &[u8] { - &self.0 + let mut packet = Vec::with_capacity(report_size); + packet.push(0); + packet.extend_from_slice(cid); + packet.push(seq); + packet.extend_from_slice(payload); + if packet.len() > report_size + 1 { + Err(FidoErrorKind::WritePacket)? } + packet.resize(report_size + 1, 0); + writer.write_all(&packet).context( + FidoErrorKind::WritePacket, + )?; + Ok(()) } -pub struct ContPacket(pub [u8; 65]); +pub struct ContPacket { + pub cid: [u8; 4], + pub seq: u8, + pub payload: Vec, +} impl ContPacket { - pub fn new(cid: &[u8], seq: u8, payload: &[u8]) -> ContPacket { - let mut packet = ContPacket([0; 65]); - packet.0[1..5].copy_from_slice(cid); - packet.0[5] = seq; - packet.0[6..(payload.len() + 6)].copy_from_slice(payload); - packet - } - - pub fn cid(&self) -> &[u8] { - &self.0[1..5] - } - - pub fn seq(&self) -> u8 { - self.0[5] - } - - pub fn payload(&self) -> &[u8] { - &self.0[6..65] - } -} - -impl Packet for ContPacket { - fn from_wire_format(data: &[u8]) -> ContPacket { - let mut packet = ContPacket([0; 65]); - packet.0[1..65].copy_from_slice(data); - packet - } - - fn to_wire_format(&self) -> &[u8] { - &self.0 + pub fn from_reader( + mut reader: R, + report_size: usize, + expected_data: usize, + ) -> FidoResult { + let mut buf = Vec::with_capacity(report_size); + buf.resize(report_size, 0); + reader.read_exact(&mut buf[0..report_size]).context( + FidoErrorKind::ReadPacket, + )?; + let mut cid = [0; 4]; + cid.copy_from_slice(&buf[0..4]); + let seq = buf[4]; + let payload_end = if expected_data >= (report_size - 5) { + report_size + } else { + expected_data + 5 + }; + let payload = buf.drain(5..payload_end).collect(); + Ok(ContPacket { cid, seq, payload }) } }