Skip to content

Commit

Permalink
Merge pull request #43 from cr8t/feature/version-request-response
Browse files Browse the repository at this point in the history
message: version request and response types
  • Loading branch information
decapod-atm authored May 9, 2024
2 parents dfce011 + 533b1d1 commit 3bc6a64
Show file tree
Hide file tree
Showing 8 changed files with 844 additions and 7 deletions.
30 changes: 30 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ pub enum Error {
InvalidCurrencyLen((usize, usize)),
InvalidDenominationLen((usize, usize)),
InvalidTicketLen((usize, usize)),
InvalidFirmwareVersionLen((usize, usize)),
InvalidVersionResponseLen((usize, usize)),
InvalidCString,
InvalidAsciiString,
InvalidUtf8String,
InvalidFirmwareVersion,
#[cfg(feature = "usb")]
Usb(String),
}
Expand Down Expand Up @@ -163,12 +167,38 @@ impl fmt::Display for Error {
Self::InvalidTicketLen((have, exp)) => {
write!(f, "invalid ticket length, have: {have}, expected: {exp}")
}
Self::InvalidFirmwareVersionLen((have, exp)) => {
write!(
f,
"invalid firmware version length, have: {have}, expected: {exp}"
)
}
Self::InvalidVersionResponseLen((have, exp)) => {
write!(
f,
"invalid version response length, have: {have}, expected: {exp}"
)
}
Self::InvalidAsciiString => write!(f, "invalid ASCII encoded string"),
Self::InvalidCString => write!(f, "invalid null-terminated C string"),
Self::InvalidUtf8String => write!(f, "invalid UTF-8 encoded string"),
Self::InvalidFirmwareVersion => write!(f, "invalid firmware version"),
#[cfg(feature = "usb")]
Self::Usb(err) => write!(f, "USB error: {err}"),
}
}
}

impl From<std::ffi::FromBytesUntilNulError> for Error {
fn from(_err: std::ffi::FromBytesUntilNulError) -> Self {
Self::InvalidCString
}
}

impl From<std::str::Utf8Error> for Error {
fn from(_err: std::str::Utf8Error) -> Self {
Self::InvalidUtf8String
}
}

impl std::error::Error for Error {}
2 changes: 2 additions & 0 deletions src/message/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod reset_request;
mod stack_request;
mod status_request;
mod uid_request;
mod version_request;

pub use idle_request::*;
pub use inhibit_request::*;
Expand All @@ -23,6 +24,7 @@ pub use reset_request::*;
pub use stack_request::*;
pub use status_request::*;
pub use uid_request::*;
pub use version_request::*;

/// Represents an event [Message] sent by the device.
#[repr(C)]
Expand Down
251 changes: 251 additions & 0 deletions src/message/request/version_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
use crate::{
Error, Message, MessageCode, MessageData, MessageType, RequestCode, RequestType, Result,
};

/// Represents a `Version` request message.
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VersionRequest;

impl VersionRequest {
/// Creates a new [VersionRequest].
pub const fn new() -> Self {
Self
}

/// Gets the [MessageType] for the [VersionRequest].
pub const fn message_type(&self) -> MessageType {
MessageType::Request(self.request_type())
}

/// Gets the [RequestType] for the [VersionRequest].
pub const fn request_type(&self) -> RequestType {
RequestType::Status
}

/// Gets the [MessageCode] for the [VersionRequest].
pub const fn message_code(&self) -> MessageCode {
MessageCode::Request(self.request_code())
}

/// Gets the [RequestCode] for the [VersionRequest].
pub const fn request_code(&self) -> RequestCode {
RequestCode::Version
}
}

impl Default for VersionRequest {
fn default() -> Self {
Self::new()
}
}

impl From<VersionRequest> for Message {
fn from(val: VersionRequest) -> Self {
Message::new().with_data(val.into())
}
}

impl From<&VersionRequest> for Message {
fn from(val: &VersionRequest) -> Self {
(*val).into()
}
}

impl From<VersionRequest> for MessageData {
fn from(val: VersionRequest) -> Self {
Self::new()
.with_message_type(val.message_type())
.with_message_code(val.message_code())
}
}

impl From<&VersionRequest> for MessageData {
fn from(val: &VersionRequest) -> Self {
(*val).into()
}
}

impl TryFrom<&Message> for VersionRequest {
type Error = Error;

fn try_from(val: &Message) -> Result<Self> {
val.data().try_into()
}
}

impl TryFrom<Message> for VersionRequest {
type Error = Error;

fn try_from(val: Message) -> Result<Self> {
(&val).try_into()
}
}

impl TryFrom<&MessageData> for VersionRequest {
type Error = Error;

fn try_from(val: &MessageData) -> Result<Self> {
let (exp_type, exp_code) = (
MessageType::Request(RequestType::Status),
MessageCode::Request(RequestCode::Version),
);

match (val.message_type(), val.message_code()) {
(msg_type, msg_code) if msg_type == exp_type && msg_code == exp_code => Ok(Self),
(msg_type, msg_code) => Err(Error::InvalidMessage((
(msg_type.into(), msg_code.into()),
(exp_type.into(), exp_code.into()),
))),
}
}
}

impl TryFrom<MessageData> for VersionRequest {
type Error = Error;

fn try_from(val: MessageData) -> Result<Self> {
(&val).try_into()
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{EventCode, EventType};

#[test]
fn test_version_request() -> Result<()> {
let exp_type = MessageType::Request(RequestType::Status);
let exp_code = MessageCode::Request(RequestCode::Version);

let msg_data = MessageData::new()
.with_message_type(exp_type)
.with_message_code(exp_code);
let msg = Message::new().with_data(msg_data);

let exp_req = VersionRequest::new();

assert_eq!(exp_req.message_type(), exp_type);
assert_eq!(exp_type.request_type(), Ok(exp_req.request_type()));

assert_eq!(exp_req.message_code(), exp_code);
assert_eq!(exp_code.request_code(), Ok(exp_req.request_code()));

assert_eq!(Message::from(exp_req), msg);
assert_eq!(VersionRequest::try_from(&msg), Ok(exp_req));

Ok(())
}

#[test]
fn test_version_request_invalid() -> Result<()> {
let invalid_types = [MessageType::Reserved]
.into_iter()
.chain((0x80..=0x8f).map(|m| MessageType::Event(EventType::from_u8(m))))
.chain(
[
RequestType::Operation,
RequestType::SetFeature,
RequestType::Reserved,
]
.map(MessageType::Request),
)
.collect::<Vec<MessageType>>();

let invalid_codes = [
RequestCode::Uid,
RequestCode::ProgramSignature,
RequestCode::SerialNumber,
RequestCode::ModelName,
RequestCode::Status,
RequestCode::Stack,
RequestCode::Idle,
RequestCode::Inhibit,
RequestCode::Collect,
RequestCode::Key,
RequestCode::EventResendInterval,
RequestCode::Reset,
RequestCode::Reject,
RequestCode::Hold,
RequestCode::AcceptorCollect,
RequestCode::DenominationDisable,
RequestCode::DirectionDisable,
RequestCode::CurrencyAssign,
RequestCode::CashBoxSize,
RequestCode::NearFull,
RequestCode::BarCode,
RequestCode::Insert,
RequestCode::ConditionalVend,
RequestCode::Pause,
RequestCode::NoteDataInfo,
RequestCode::RecyclerCollect,
RequestCode::Reserved,
]
.map(MessageCode::Request)
.into_iter()
.chain(
[
EventCode::PowerUp,
EventCode::PowerUpAcceptor,
EventCode::PowerUpStacker,
EventCode::Inhibit,
EventCode::ProgramSignature,
EventCode::Rejected,
EventCode::Collected,
EventCode::Clear,
EventCode::OperationError,
EventCode::Failure,
EventCode::NoteStay,
EventCode::PowerUpAcceptorAccepting,
EventCode::PowerUpStackerAccepting,
EventCode::Escrow,
EventCode::VendValid,
EventCode::AcceptorRejected,
EventCode::Returned,
EventCode::AcceptorCollected,
EventCode::Insert,
EventCode::ConditionalVend,
EventCode::Pause,
EventCode::Resume,
EventCode::AcceptorClear,
EventCode::AcceptorOperationError,
EventCode::AcceptorFailure,
EventCode::AcceptorNoteStay,
EventCode::FunctionAbeyance,
EventCode::Reserved,
]
.map(MessageCode::Event),
)
.collect::<Vec<MessageCode>>();

let exp_req = VersionRequest::new();
let exp_type = exp_req.message_type();
let exp_code = exp_req.message_code();

for &msg_type in invalid_types.iter() {
for &msg_code in invalid_codes.iter() {
let inval_data = MessageData::new()
.with_message_type(msg_type)
.with_message_code(msg_code);

let inval_type = MessageData::new()
.with_message_type(msg_type)
.with_message_code(exp_code);

let inval_code = MessageData::new()
.with_message_type(exp_type)
.with_message_code(msg_code);

for stack_data in [inval_data, inval_type, inval_code] {
assert!(VersionRequest::try_from(&stack_data).is_err());
assert!(
VersionRequest::try_from(Message::new().with_data(stack_data)).is_err()
);
}
}
}

Ok(())
}
}
2 changes: 2 additions & 0 deletions src/message/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ use crate::{Error, Message, Result};
mod response_code;
mod status_response;
mod uid_response;
mod version_response;

pub use response_code::*;
pub use status_response::*;
pub use uid_response::*;
pub use version_response::*;

/// Represents the generic response format for JCM host-device communication.
///
Expand Down
5 changes: 5 additions & 0 deletions src/message/response/response_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ impl ResponseCode {
}
}

/// Converts a [ResponseCode] into a [`u8`].
pub const fn to_u8(&self) -> u8 {
*self as u8
}

/// Gets the length of the [ResponseCode].
pub const fn len() -> usize {
mem::size_of::<u8>()
Expand Down
Loading

0 comments on commit 3bc6a64

Please sign in to comment.