Skip to content

Commit

Permalink
message: add version response type
Browse files Browse the repository at this point in the history
Adds the `VersionResponse` message type to represent the response to a
`VersionRequest` message.
  • Loading branch information
cr8t committed May 9, 2024
1 parent a67a31f commit 533b1d1
Show file tree
Hide file tree
Showing 6 changed files with 585 additions and 1 deletion.
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/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
301 changes: 301 additions & 0 deletions src/message/response/version_response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
use std::fmt;

use crate::{Error, Message, Response, ResponseCode, Result};

mod firmware_version;

pub use firmware_version::*;

/// Represents the response to a [VersionRequest](crate::VersionRequest).
#[repr(C)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VersionResponse {
code: ResponseCode,
firmware_version: FirmwareVersion,
}

impl VersionResponse {
/// Creates a new [VersionResponse].
pub const fn new() -> Self {
Self {
code: ResponseCode::new(),
firmware_version: FirmwareVersion::new(),
}
}

/// Gets the [ResponseCode] for the [VersionResponse].
pub const fn code(&self) -> ResponseCode {
self.code
}

/// Sets the [ResponseCode] for the [VersionResponse].
pub fn set_code(&mut self, code: ResponseCode) {
self.code = code;
}

/// Builder function that sets the [ResponseCode] for the [VersionResponse].
pub fn with_code(mut self, code: ResponseCode) -> Self {
self.set_code(code);
self
}

/// Gets the [FirmwareVersion] for the [VersionResponse].
pub const fn firmware_version(&self) -> &FirmwareVersion {
&self.firmware_version
}

/// Sets the [FirmwareVersion] for the [VersionResponse].
pub fn set_firmware_version(&mut self, firmware_version: FirmwareVersion) {
self.firmware_version = firmware_version;
}

/// Builder function that sets the [FirmwareVersion] for the [VersionResponse].
pub fn with_firmware_version(mut self, firmware_version: FirmwareVersion) -> Self {
self.set_firmware_version(firmware_version);
self
}

/// Gets the metadata length of the [VersionResponse].
pub const fn meta_len() -> usize {
ResponseCode::len()
}

/// Gets the length of the [VersionResponse].
pub fn len(&self) -> usize {
Self::meta_len() + self.firmware_version.len()
}

/// Gets whether the [VersionResponse] is empty.
pub fn is_empty(&self) -> bool {
self.code.is_empty() && self.firmware_version.is_empty()
}

/// Attempts to convert a byte buffer into a [VersionResponse].
pub fn from_bytes(buf: &[u8]) -> Result<Self> {
let meta_len = Self::meta_len();
let buf_len = buf.len();

match buf_len {
bl if bl < meta_len => Err(Error::InvalidResponseLen((buf_len, meta_len))),
bl if bl == meta_len => Ok(Self {
code: buf[0].try_into()?,
firmware_version: FirmwareVersion::new(),
}),
_ => Ok(Self {
code: buf[0].try_into()?,
firmware_version: buf[1..].try_into()?,
}),
}
}

/// Converts the [VersionResponse] into a byte iterator.
pub fn iter(&self) -> impl Iterator<Item = u8> + '_ {
[self.code.to_u8()]
.into_iter()
.chain(self.firmware_version.iter())
}

/// Attempts to convert the [VersionResponse] into a byte buffer.
pub fn to_bytes(&self, buf: &mut [u8]) -> Result<()> {
let len = self.len();
let buf_len = buf.len();

if buf_len < len {
Err(Error::InvalidVersionResponseLen((buf_len, len)))
} else {
buf.iter_mut()
.take(len)
.zip(self.iter())
.for_each(|(dst, src)| *dst = src);
Ok(())
}
}

/// Converts the [VersionResponse] into a byte vector.
pub fn into_bytes(&self) -> Vec<u8> {
self.iter().collect()
}
}

impl TryFrom<&[u8]> for VersionResponse {
type Error = Error;

fn try_from(val: &[u8]) -> Result<Self> {
Self::from_bytes(val)
}
}

impl<const N: usize> TryFrom<&[u8; N]> for VersionResponse {
type Error = Error;

fn try_from(val: &[u8; N]) -> Result<Self> {
Self::from_bytes(val.as_ref())
}
}

impl<const N: usize> TryFrom<[u8; N]> for VersionResponse {
type Error = Error;

fn try_from(val: [u8; N]) -> Result<Self> {
Self::from_bytes(val.as_ref())
}
}

impl From<&VersionResponse> for Response {
fn from(val: &VersionResponse) -> Self {
Self {
code: val.code,
additional: val.firmware_version.into_bytes(),
}
}
}

impl From<VersionResponse> for Response {
fn from(val: VersionResponse) -> Self {
(&val).into()
}
}

impl TryFrom<&Response> for VersionResponse {
type Error = Error;

fn try_from(val: &Response) -> Result<Self> {
match val.additional().len() {
0 => Ok(Self {
code: val.code,
firmware_version: FirmwareVersion::new(),
}),
_ => Ok(Self {
code: val.code,
firmware_version: val.additional().try_into()?,
}),
}
}
}

impl TryFrom<Response> for VersionResponse {
type Error = Error;

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

impl From<&VersionResponse> for Message {
fn from(val: &VersionResponse) -> Self {
use crate::{MessageData, VersionRequest};

MessageData::from(VersionRequest::new())
.with_additional(val.into_bytes().as_ref())
.into()
}
}

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

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

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

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

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

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

impl fmt::Display for VersionResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
write!(f, r#""code": {}, "#, self.code())?;
write!(f, r#""firmware_version": {}"#, self.firmware_version())?;
write!(f, "}}")
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_version_response() -> Result<()> {
let raw_code = ResponseCode::Ack as u8;
let raw_version = b"i(JPY)-100-SS 1 SomeVersion 01-25-01\0";
let raw: Vec<u8> = [raw_code]
.into_iter()
.chain(raw_version.iter().cloned())
.collect();

let exp_version = FirmwareVersion::try_from(raw_version.as_ref())?;

let exp = VersionResponse::new()
.with_code(ResponseCode::Ack)
.with_firmware_version(exp_version.clone());

let res = Response::new()
.with_code(ResponseCode::Ack)
.with_additional(raw_version.as_ref());

assert_eq!(VersionResponse::from_bytes(raw.as_ref())?, exp);
assert_eq!(VersionResponse::try_from(&res)?, exp);
assert_eq!(Response::from(&exp), res);

let out = exp.into_bytes();
assert_eq!(out, raw);

Ok(())
}

#[test]
fn test_version_response_invalid() -> Result<()> {
let raw_code = ResponseCode::Ack as u8;
let good_vers = FirmwareVersion::from_bytes(b"i(JPY)-100-SS 1 SomeVersion 01-25-01\0")?;
let bad_vers = [
// no date
b"i(JPY)-100-SS 1 SomeVersion\0".as_ref(),
// no date and version
b"i(JPY)-100-SS 1\0".as_ref(),
// no date, version, and interface number
b"i(JPY)-100-SS\0".as_ref(),
// no date, version, interface number, and name
b"\0".as_ref(),
];

let exp = VersionResponse::new()
.with_code(ResponseCode::Ack)
.with_firmware_version(good_vers.clone());
let exp_len = exp.len();
let mut out = vec![0u8; exp_len];

for ver in bad_vers.into_iter() {
let raw: Vec<u8> = [raw_code].into_iter().chain(ver.iter().cloned()).collect();
assert!(VersionResponse::from_bytes(raw.as_ref()).is_err());
assert!(exp.to_bytes(out[..raw.len()].as_mut()).is_err());
assert!(exp.to_bytes(&mut []).is_err());
assert!(VersionResponse::try_from(Response::new().with_additional(ver)).is_err());
assert!(VersionResponse::try_from(
Response::new()
.with_code(ResponseCode::Ack)
.with_additional(ver)
)
.is_err());
}

Ok(())
}
}
Loading

0 comments on commit 533b1d1

Please sign in to comment.