use byteorder::{BigEndian, ByteOrder};
#[cfg(feature = "mockhsm")]
use byteorder::WriteBytesExt;
use failure::Error;
use super::{CommandType, Mac, SecureChannelError, SessionId, MAC_SIZE};
#[derive(Debug, Eq, PartialEq)]
pub struct ResponseMessage {
pub code: ResponseCode,
pub session_id: Option<SessionId>,
pub data: Vec<u8>,
pub mac: Option<Mac>,
}
impl ResponseMessage {
pub fn parse(mut bytes: Vec<u8>) -> Result<Self, Error> {
if bytes.len() < 3 {
fail!(
SecureChannelError::ProtocolError,
"response too short: {} (expected at least 3-bytes)",
bytes.len()
);
}
let code = ResponseCode::from_u8(bytes[0])?;
let length = BigEndian::read_u16(&bytes[1..3]) as usize;
if length + 3 != bytes.len() {
fail!(
SecureChannelError::ProtocolError,
"unexpected response length {} (expecting {})",
bytes.len() - 3,
length
);
}
bytes.drain(..3);
let session_id = if code.has_session_id() {
if bytes.is_empty() {
fail!(
SecureChannelError::ProtocolError,
"expected session ID but response data is empty"
);
}
Some(SessionId::new(bytes.remove(0))?)
} else {
None
};
let mac = if code.has_rmac() {
if bytes.len() < MAC_SIZE {
fail!(
SecureChannelError::ProtocolError,
"expected R-MAC for {:?} but response data is too short: {}",
code,
bytes.len(),
);
}
let mac_index = bytes.len() - MAC_SIZE;
Some(Mac::from_slice(&bytes.split_off(mac_index)))
} else {
None
};
Ok(Self {
code,
session_id,
data: bytes,
mac,
})
}
#[cfg(feature = "mockhsm")]
pub fn new<T>(code: ResponseCode, response_data: T) -> ResponseMessage
where
T: Into<Vec<u8>>,
{
ResponseMessage {
code,
session_id: None,
data: response_data.into(),
mac: None,
}
}
#[cfg(feature = "mockhsm")]
pub fn new_with_mac<D, M>(
code: ResponseCode,
session_id: SessionId,
response_data: D,
mac: M,
) -> Self
where
D: Into<Vec<u8>>,
M: Into<Mac>,
{
Self {
code,
session_id: Some(session_id),
data: response_data.into(),
mac: Some(mac.into()),
}
}
#[cfg(feature = "mockhsm")]
pub fn success<T>(command_type: CommandType, response_data: T) -> ResponseMessage
where
T: Into<Vec<u8>>,
{
Self::new(ResponseCode::Success(command_type), response_data)
}
pub fn is_ok(&self) -> bool {
match self.code {
ResponseCode::Success(_) => true,
_ => false,
}
}
#[cfg(feature = "mockhsm")]
pub fn error(message: &str) -> ResponseMessage {
ResponseMessage::new(ResponseCode::MemoryError, message.as_bytes())
}
pub fn is_err(&self) -> bool {
!self.is_ok()
}
pub fn command(&self) -> Option<CommandType> {
match self.code {
ResponseCode::Success(cmd) => Some(cmd),
_ => None,
}
}
pub fn len(&self) -> usize {
let mut result = self.data.len();
if self.session_id.is_some() {
result += 1;
}
if self.mac.is_some() {
result += MAC_SIZE;
}
result
}
}
#[cfg(feature = "mockhsm")]
impl Into<Vec<u8>> for ResponseMessage {
fn into(mut self) -> Vec<u8> {
let mut result = Vec::with_capacity(3 + self.len());
result.push(self.code.to_u8());
result.write_u16::<BigEndian>(self.len() as u16).unwrap();
if let Some(session_id) = self.session_id {
result.push(session_id.to_u8());
}
result.append(&mut self.data);
if let Some(mac) = self.mac {
result.extend_from_slice(mac.as_slice());
}
result
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ResponseCode {
Success(CommandType),
MemoryError,
InitError,
NetError,
ConnectorNotFound,
InvalidParams,
WrongLength,
BufferTooSmall,
CryptogramMismatch,
AuthSessionError,
MACMismatch,
DeviceOK,
DeviceInvalidCommand,
DeviceInvalidData,
DeviceInvalidSession,
DeviceAuthFail,
DeviceSessionsFull,
DeviceSessionFailed,
DeviceStorageFailed,
DeviceWrongLength,
DeviceInvalidPermission,
DeviceLogFull,
DeviceObjNotFound,
DeviceIDIllegal,
DeviceInvalidOTP,
DeviceDemoMode,
DeviceCmdUnexecuted,
GenericError,
DeviceObjectExists,
ConnectorError,
}
impl ResponseCode {
pub fn from_u8(byte: u8) -> Result<Self, Error> {
let code = (i16::from(byte) - 0x80) as i8;
if code >= 0 {
let command_type = CommandType::from_u8(code as u8)?;
return Ok(ResponseCode::Success(command_type));
}
Ok(match code {
-1 => ResponseCode::MemoryError,
-2 => ResponseCode::InitError,
-3 => ResponseCode::NetError,
-4 => ResponseCode::ConnectorNotFound,
-5 => ResponseCode::InvalidParams,
-6 => ResponseCode::WrongLength,
-7 => ResponseCode::BufferTooSmall,
-8 => ResponseCode::CryptogramMismatch,
-9 => ResponseCode::AuthSessionError,
-10 => ResponseCode::MACMismatch,
-11 => ResponseCode::DeviceOK,
-12 => ResponseCode::DeviceInvalidCommand,
-13 => ResponseCode::DeviceInvalidData,
-14 => ResponseCode::DeviceInvalidSession,
-15 => ResponseCode::DeviceAuthFail,
-16 => ResponseCode::DeviceSessionsFull,
-17 => ResponseCode::DeviceSessionFailed,
-18 => ResponseCode::DeviceStorageFailed,
-19 => ResponseCode::DeviceWrongLength,
-20 => ResponseCode::DeviceInvalidPermission,
-21 => ResponseCode::DeviceLogFull,
-22 => ResponseCode::DeviceObjNotFound,
-23 => ResponseCode::DeviceIDIllegal,
-24 => ResponseCode::DeviceInvalidOTP,
-25 => ResponseCode::DeviceDemoMode,
-26 => ResponseCode::DeviceCmdUnexecuted,
-27 => ResponseCode::GenericError,
-28 => ResponseCode::DeviceObjectExists,
-29 => ResponseCode::ConnectorError,
_ => fail!(
SecureChannelError::ProtocolError,
"invalid response code: {}",
code
),
})
}
pub fn to_u8(&self) -> u8 {
let code: i8 = match *self {
ResponseCode::Success(cmd_type) => cmd_type as i8,
ResponseCode::MemoryError => -1,
ResponseCode::InitError => -2,
ResponseCode::NetError => -3,
ResponseCode::ConnectorNotFound => -4,
ResponseCode::InvalidParams => -5,
ResponseCode::WrongLength => -6,
ResponseCode::BufferTooSmall => -7,
ResponseCode::CryptogramMismatch => -8,
ResponseCode::AuthSessionError => -9,
ResponseCode::MACMismatch => -10,
ResponseCode::DeviceOK => -11,
ResponseCode::DeviceInvalidCommand => -12,
ResponseCode::DeviceInvalidData => -13,
ResponseCode::DeviceInvalidSession => -14,
ResponseCode::DeviceAuthFail => -15,
ResponseCode::DeviceSessionsFull => -16,
ResponseCode::DeviceSessionFailed => -17,
ResponseCode::DeviceStorageFailed => -18,
ResponseCode::DeviceWrongLength => -19,
ResponseCode::DeviceInvalidPermission => -20,
ResponseCode::DeviceLogFull => -21,
ResponseCode::DeviceObjNotFound => -22,
ResponseCode::DeviceIDIllegal => -23,
ResponseCode::DeviceInvalidOTP => -24,
ResponseCode::DeviceDemoMode => -25,
ResponseCode::DeviceCmdUnexecuted => -26,
ResponseCode::GenericError => -27,
ResponseCode::DeviceObjectExists => -28,
ResponseCode::ConnectorError => -29,
};
(i16::from(code) + 0x80) as u8
}
pub fn has_session_id(&self) -> bool {
match *self {
ResponseCode::Success(cmd_type) => match cmd_type {
CommandType::CreateSession | CommandType::SessionMessage => true,
_ => false,
},
_ => false,
}
}
pub fn has_rmac(&self) -> bool {
match *self {
ResponseCode::Success(cmd_type) => match cmd_type {
CommandType::SessionMessage => true,
_ => false,
},
_ => false,
}
}
}