From 9df0f3bd565dfbf8c97d02969a17504c688bf381 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 18 Feb 2019 21:36:31 +0000 Subject: Refactor command execution into commands module This patch refactors the command execution. A command is represented by a struct implementing the Command trait. The enum_cmd macro is used to generate a mapping from the CommandId enum to a Command instance and to execute the command. The request and response data is manually converted from and to raw byte slices. As we do not have a standard library, we cannot create a Box from a CommandId. Instead, we directly delegate the execute method to the corresponding Command. --- src/commands.rs | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/device.rs | 49 +------------------ src/main.rs | 1 + src/util.rs | 29 +++++++++++ 4 files changed, 180 insertions(+), 47 deletions(-) create mode 100644 src/commands.rs diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..62e2983 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,148 @@ +// Copyright 2019 Robin Krahl +// SPDX-License-Identifier: GPL-3.0-or-later + +use core::default::Default; +use core::marker::Sized; + +use crate::device::CommandStatus; + +pub const COMMAND_LEN: usize = 53; + +enum_cmd! { + #[derive(Clone, Copy, Debug, PartialEq)] + pub enum CommandId { + GetStatus(GetStatusCommand) = 0, + ReadSlotName(ReadSlotNameCommand) = 2, + } +} + +trait RequestData: Sized { + fn from_bytes(data: &[u8]) -> Option; +} + +impl RequestData for () { + fn from_bytes(_data: &[u8]) -> Option { + Some(()) + } +} + +trait ResponseData { + fn write_bytes(&self, buf: &mut [u8]); +} + +trait Command { + type Request: RequestData; + type Response: ResponseData; + + fn execute(data: Self::Request) -> Result; + + fn execute_raw(data: &[u8], buf: &mut [u8]) -> CommandStatus { + if let Some(request) = Self::Request::from_bytes(data) { + match Self::execute(request) { + Ok(response) => { + response.write_bytes(buf); + CommandStatus::Ok + } + Err(status) => status, + } + } else { + CommandStatus::NotSupported + } + } +} + +#[derive(Debug, Default)] +struct GetStatusResponse { + firmware_version_minor: u8, + firmware_version_major: u8, + card_serial: u32, + config_numlock: u8, + config_capslock: u8, + config_enable_user_password: u8, + config_delete_user_password: u8, + buf: [u8; 10], +} + +impl ResponseData for GetStatusResponse { + fn write_bytes(&self, buf: &mut [u8]) { + let card_serial = self.card_serial.to_le_bytes(); + let data = &[ + self.firmware_version_minor, + self.firmware_version_major, + card_serial[0], + card_serial[1], + card_serial[2], + card_serial[3], + self.config_numlock, + self.config_capslock, + self.config_enable_user_password, + self.config_delete_user_password, + ]; + assert!(buf.len() >= data.len()); + buf[..data.len()].copy_from_slice(data); + } +} + +#[derive(Debug, Default)] +struct GetStatusCommand {} + +impl Command for GetStatusCommand { + type Request = (); + type Response = GetStatusResponse; + + fn execute(_data: Self::Request) -> Result { + let mut response: Self::Response = Default::default(); + response.firmware_version_minor = 1; + Ok(response) + } +} + +#[derive(Debug, Default)] +struct ReadSlotNameRequest { + internal_slot_number: u8, +} + +impl RequestData for ReadSlotNameRequest { + fn from_bytes(data: &[u8]) -> Option { + if data.is_empty() { + None + } else { + Some(ReadSlotNameRequest { + internal_slot_number: data[0], + }) + } + } +} + +#[derive(Debug, Default)] +struct ReadSlotNameResponse { + slot_name: [u8; 15], +} + +impl ResponseData for ReadSlotNameResponse { + fn write_bytes(&self, buf: &mut [u8]) { + assert!(buf.len() >= self.slot_name.len()); + buf[..self.slot_name.len()].copy_from_slice(&self.slot_name) + } +} + +#[derive(Debug, Default)] +struct ReadSlotNameCommand {} + +impl Command for ReadSlotNameCommand { + type Request = ReadSlotNameRequest; + type Response = ReadSlotNameResponse; + + fn execute(data: Self::Request) -> Result { + if data.internal_slot_number != 0x20 { + Err(CommandStatus::SlotNotProgrammed) + } else { + let mut response: Self::Response = Default::default(); + response.slot_name[0] = 0x74; // t + response.slot_name[1] = 0x65; // e + response.slot_name[2] = 0x73; // s + response.slot_name[3] = 0x74; // t + Ok(response) + } + } +} diff --git a/src/device.rs b/src/device.rs index a6bf9bf..300ce56 100644 --- a/src/device.rs +++ b/src/device.rs @@ -4,13 +4,13 @@ use usb_device::bus::{UsbBus, UsbBusAllocator}; use usb_device::device::{UsbDevice, UsbDeviceBuilder, UsbVidPid}; +use crate::commands::{CommandId, COMMAND_LEN}; use crate::hid::{HidDevice, Protocol, ReportType, Subclass}; use crate::util::TryFrom; const VID_CLAY_LOGIC: u16 = 0x20a0; const PID_NITROKEY_PRO: u16 = 0x4108; -const COMMAND_LEN: usize = 53; const REPORT_LEN: usize = 64; const REPORT_DESCRIPTOR: &[u8] = &[ 0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01, @@ -47,14 +47,6 @@ enum_u8! { } } -enum_u8! { - #[derive(Clone, Copy, Debug, PartialEq)] - pub enum CommandId { - GetStatus = 0, - ReadSlotName = 2, - } -} - pub struct Nitrokey { buf: [u8; REPORT_LEN], } @@ -65,43 +57,6 @@ impl Nitrokey { buf: [0; REPORT_LEN], } } - - fn execute_command(&self, command_id: CommandId, data: &[u8], buf: &mut [u8]) -> CommandStatus { - let data: &[u8] = match command_id { - CommandId::GetStatus => { - &[ - 1, // firmware_version_st.minor - 0, // firmware_version_st.major - 0, // card_serial[0] - 0, // card_serial[1] - 0, // card_serial[2] - 0, // card_serial[3] - 0, // numlock - 0, // capslock - 0, // enable_user_password - 0, // delete_user_password - ] - } - CommandId::ReadSlotName => { - assert!(data.len() > 1); - let slot_number = data[0]; - if slot_number != 0x20 { - return CommandStatus::SlotNotProgrammed; - } - - &[ - 0x74, // t - 0x65, // e - 0x73, // s - 0x74, // t - 0x00, // NULL - ] - } - }; - assert!(buf.len() >= data.len()); - buf[..data.len()].copy_from_slice(data); - CommandStatus::Ok - } } impl HidDevice for Nitrokey { @@ -131,7 +86,7 @@ impl HidDevice for Nitrokey { let device_status = DeviceStatus::Ok; let command_id = self.buf[0]; let command_status = if let Ok(command_id) = CommandId::try_from(command_id) { - self.execute_command(command_id, &self.buf[1..60], &mut buf) + command_id.execute(&self.buf[1..60], &mut buf) } else { CommandStatus::UnknownCommand }; diff --git a/src/main.rs b/src/main.rs index e10f4d2..caf7fdf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ extern crate panic_halt; #[macro_use] mod util; +mod commands; mod device; mod hid; diff --git a/src/util.rs b/src/util.rs index 4fc4448..9325e9e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -3,6 +3,35 @@ use core::marker::Sized; +macro_rules! enum_cmd { + ( + $(#[$outer:meta])* + pub enum $name:ident { + $($var:ident($cmd:ident) = $num:expr),+ + $(,)* + } + ) => { + enum_u8! { + $(#[$outer])* + pub enum $name { + $( + $var = $num, + )* + } + } + + impl $name { + pub fn execute(&self, data: &[u8], buf: &mut [u8]) -> CommandStatus { + match *self { + $( + $name::$var => $cmd::execute_raw(data, buf), + )* + } + } + } + }; +} + macro_rules! enum_u8 { ( $(#[$outer:meta])* -- cgit v1.2.1