#![cfg_attr(not(feature = "std"), no_std)]
#[macro_use(format)]
extern crate alloc;
#[macro_use(bitfield)]
extern crate bitfield;
#[cfg(not(feature = "std"))]
pub(crate) use core as std;
#[cfg(feature = "std")]
#[allow(clippy::single_component_path_imports)]
pub(crate) use std;
use std::{fmt, ops::Not};
pub(crate) use currency_iso4217::Currency;
pub mod banknote;
pub mod cash;
pub mod denomination;
pub mod error;
pub mod hardware;
pub mod logging;
mod macros;
pub mod status;
pub use banknote::*;
pub use cash::*;
pub use denomination::*;
pub use error::*;
pub use hardware::*;
pub use logging::*;
pub use status::*;
pub mod advanced_bookmark_mode;
pub mod aux_command;
pub mod clear_audit_data;
pub mod extended_command;
pub mod extended_note_inhibits;
pub mod extended_note_specification;
pub mod extended_reply;
pub mod flash_download;
pub mod len;
pub mod note_retrieved;
pub mod omnibus;
pub mod part_number;
pub mod query_application_id;
pub mod query_application_part_number;
pub mod query_boot_part_number;
pub mod query_device_capabilities;
pub mod query_software_crc;
pub mod query_value_table;
pub mod query_variant_id;
pub mod query_variant_name;
pub mod query_variant_part_number;
pub mod set_escrow_timeout;
pub mod soft_reset;
pub mod variant;
pub use advanced_bookmark_mode::*;
pub use aux_command::*;
pub use clear_audit_data::*;
pub use extended_command::*;
pub use extended_note_inhibits::*;
pub use extended_note_specification::*;
pub use extended_reply::*;
pub use flash_download::*;
pub use note_retrieved::*;
pub use omnibus::*;
pub use part_number::*;
pub use query_application_id::*;
pub use query_application_part_number::*;
pub use query_boot_part_number::*;
pub use query_device_capabilities::*;
pub use query_software_crc::*;
pub use query_value_table::*;
pub use query_variant_id::*;
pub use query_variant_name::*;
pub use query_variant_part_number::*;
pub use set_escrow_timeout::*;
pub use soft_reset::*;
pub use variant::*;
pub use crate::error::{Error, JsonRpcError, JsonRpcResult, Result};
pub const STX: u8 = 0x02;
pub const ETX: u8 = 0x03;
pub const ENQ: u8 = 0x05;
pub const ENV_CURRENCY: &str = "BAU_CURRENCY";
#[cfg(feature = "usd")]
pub const DEFAULT_CURRENCY: Currency = Currency::USD;
#[cfg(feature = "cny")]
pub const DEFAULT_CURRENCY: Currency = Currency::CNY;
#[cfg(feature = "gbp")]
pub const DEFAULT_CURRENCY: Currency = Currency::GBP;
#[cfg(feature = "jpy")]
pub const DEFAULT_CURRENCY: Currency = Currency::JPY;
#[cfg(feature = "aud")]
pub const DEFAULT_CURRENCY: Currency = Currency::AUD;
#[cfg(feature = "cad")]
pub const DEFAULT_CURRENCY: Currency = Currency::CAD;
#[cfg(feature = "mxn")]
pub const DEFAULT_CURRENCY: Currency = Currency::MXN;
#[cfg(feature = "amd")]
pub const DEFAULT_CURRENCY: Currency = Currency::AMD;
#[cfg(feature = "std")]
pub fn bau_currency() -> Currency {
std::env::var(ENV_CURRENCY)
.unwrap_or(format!("{DEFAULT_CURRENCY}"))
.as_str()
.into()
}
#[cfg(not(feature = "std"))]
pub fn bau_currency() -> Currency {
DEFAULT_CURRENCY
}
pub fn checksum(data: &[u8]) -> u8 {
let mut sum = 0u8;
data.iter().for_each(|&b| sum ^= b);
sum
}
pub(crate) fn seven_bit_u16(b: &[u8]) -> u16 {
debug_assert_eq!(b.len(), 4);
let hi = ((b[0] & 0xf) << 4) | (b[1] & 0xf);
let lo = ((b[2] & 0xf) << 4) | (b[3] & 0xf);
u16::from_be_bytes([hi, lo])
}
pub(crate) fn u16_seven_bit(n: u16) -> [u8; 4] {
let b = n.to_be_bytes();
[b[0] >> 4, b[0] & 0xf, b[1] >> 4, b[1] & 0xf]
}
pub(crate) fn seven_bit_u8(b: &[u8]) -> u8 {
debug_assert_eq!(b.len(), 2);
((b[0] & 0xf) << 4) | (b[1] & 0xf)
}
pub(crate) fn u8_seven_bit(n: u8) -> [u8; 2] {
[n >> 4, n & 0xf]
}
bitfield! {
pub struct Control(u8);
u8;
pub acknak, set_acknak: 0;
pub device_type, set_device_type: 3, 1;
pub message_type, set_message_type: 6, 4;
}
impl From<u8> for Control {
fn from(b: u8) -> Self {
Self(b & 0b111_1111)
}
}
impl From<Control> for u8 {
fn from(c: Control) -> Self {
c.0
}
}
impl From<&Control> for u8 {
fn from(c: &Control) -> Self {
c.0
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AckNak {
Ack = 0b0,
Nak = 0b1,
}
impl Not for AckNak {
type Output = AckNak;
fn not(self) -> Self::Output {
Self::from(!(self as u8))
}
}
impl fmt::Display for AckNak {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", <&'static str>::from(self))
}
}
impl From<AckNak> for &'static str {
fn from(a: AckNak) -> &'static str {
match a {
AckNak::Ack => "ACK",
AckNak::Nak => "NAK",
}
}
}
impl From<&AckNak> for &'static str {
fn from(a: &AckNak) -> Self {
(*a).into()
}
}
impl From<bool> for AckNak {
fn from(b: bool) -> Self {
match b {
false => Self::Ack,
true => Self::Nak,
}
}
}
impl From<u8> for AckNak {
fn from(b: u8) -> Self {
match b & bitmask::ACK_NAK {
0b0 => Self::Ack,
0b1 => Self::Nak,
_ => unreachable!("invalid AckNak"),
}
}
}
impl From<AckNak> for bool {
fn from(a: AckNak) -> bool {
a == AckNak::Nak
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DeviceType {
BillAcceptor = 0b000,
BillRecycler = 0b001,
Reserved = 0b111,
}
impl fmt::Display for DeviceType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let dev_str: &'static str = self.into();
write!(f, "{}", dev_str)
}
}
impl From<DeviceType> for &'static str {
fn from(d: DeviceType) -> Self {
match d {
DeviceType::BillAcceptor => "BillAcceptor",
DeviceType::BillRecycler => "BillRecycler",
DeviceType::Reserved => "Reserved",
}
}
}
impl From<&DeviceType> for &'static str {
fn from(d: &DeviceType) -> Self {
(*d).into()
}
}
impl From<u8> for DeviceType {
fn from(b: u8) -> Self {
match b & bitmask::DEVICE_TYPE {
0b000 => Self::BillAcceptor,
0b001 => Self::BillRecycler,
_ => Self::Reserved,
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MessageType {
OmnibusCommand = 0b001,
OmnibusReply = 0b010,
OmnibusBookmark = 0b011,
Calibrate = 0b100,
FirmwareDownload = 0b101,
AuxCommand = 0b110,
Extended = 0b111,
Reserved = 0xff,
}
impl fmt::Display for MessageType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r#""{}""#, <&str>::from(self))
}
}
impl From<MessageType> for &'static str {
fn from(m: MessageType) -> &'static str {
match m {
MessageType::OmnibusCommand => "OmnibusCommand",
MessageType::OmnibusReply => "OmnibusReply",
MessageType::OmnibusBookmark => "OmnibusBookmark",
MessageType::Calibrate => "Calibrate",
MessageType::FirmwareDownload => "FirmwareDownload",
MessageType::AuxCommand => "AuxCommand",
MessageType::Extended => "Extended",
MessageType::Reserved => "Reserved",
}
}
}
impl From<&MessageType> for &'static str {
fn from(m: &MessageType) -> Self {
(*m).into()
}
}
impl From<u8> for MessageType {
fn from(b: u8) -> Self {
match b & bitmask::MESSAGE_TYPE {
0b001 => Self::OmnibusCommand,
0b010 => Self::OmnibusReply,
0b011 => Self::OmnibusBookmark,
0b100 => Self::Calibrate,
0b101 => Self::FirmwareDownload,
0b110 => Self::AuxCommand,
0b111 => Self::Extended,
_ => Self::Reserved,
}
}
}
pub mod index {
pub const STX: usize = 0;
pub const LEN: usize = 1;
pub const CONTROL: usize = 2;
pub const DATA: usize = 3;
pub const EXT_SUBTYPE: usize = 3;
}
pub(crate) mod bitmask {
pub const ACK_NAK: u8 = 0b1;
pub const DEVICE_TYPE: u8 = 0b111;
pub const MESSAGE_TYPE: u8 = 0b111;
}
pub trait MessageOps {
fn init(&mut self) {
let len = self.len();
let etx_index = self.etx_index();
let buf = self.buf_mut();
buf[index::STX] = STX;
buf[index::LEN] = len as u8;
buf[etx_index] = ETX;
}
fn buf(&self) -> &[u8];
fn buf_mut(&mut self) -> &mut [u8];
fn len(&self) -> usize {
self.buf().len()
}
fn is_empty(&self) -> bool {
let mut ret = 0;
self.buf().iter().for_each(|&b| ret ^= b);
ret == 0
}
fn data_len(&self) -> usize {
self.len() - len::METADATA
}
fn etx_index(&self) -> usize {
self.buf().len() - 2
}
fn chk_index(&self) -> usize {
self.buf().len() - 1
}
fn acknak(&self) -> AckNak {
Control(self.buf()[index::CONTROL]).acknak().into()
}
fn set_acknak(&mut self, acknak: AckNak) {
let mut control = Control(self.buf()[index::CONTROL]);
control.set_acknak(acknak.into());
self.buf_mut()[index::CONTROL] = control.into();
}
fn switch_acknak(&mut self) {
self.set_acknak(!self.acknak())
}
fn device_type(&self) -> DeviceType {
Control(self.buf()[index::CONTROL]).device_type().into()
}
fn set_device_type(&mut self, device_type: DeviceType) {
let mut control = Control(self.buf()[index::CONTROL]);
control.set_device_type(device_type as u8);
self.buf_mut()[index::CONTROL] = control.into();
}
fn message_type(&self) -> MessageType {
Control(self.buf()[index::CONTROL]).message_type().into()
}
fn set_message_type(&mut self, message_type: MessageType) {
let mut control = Control(self.buf()[index::CONTROL]);
control.set_message_type(message_type as u8);
self.buf_mut()[index::CONTROL] = control.into();
}
fn checksum(&self) -> u8 {
self.buf()[self.chk_index()]
}
fn checksum_bytes(&self) -> &[u8] {
self.buf()[index::LEN..self.etx_index()].as_ref()
}
fn calculate_checksum(&mut self) -> u8 {
let csum = checksum(self.checksum_bytes());
let csum_index = self.chk_index();
self.buf_mut()[csum_index] = csum;
csum
}
fn validate_checksum(&self) -> Result<()> {
let expected = checksum(self.checksum_bytes());
let current = self.buf()[self.chk_index()];
if expected == current {
Ok(())
} else {
Err(Error::failure(format!(
"invalid checksum, expected: {expected}, have: {current}"
)))
}
}
fn as_bytes(&mut self) -> &[u8] {
self.calculate_checksum();
self.buf()
}
fn as_bytes_mut(&mut self) -> &mut [u8] {
self.buf_mut()
}
fn as_bytes_unchecked(&self) -> &[u8] {
self.buf()
}
#[allow(clippy::wrong_self_convention)]
fn from_buf(&mut self, buf: &[u8]) -> Result<()> {
if buf.len() < self.len() {
return Err(Error::failure("invalid reply length"));
}
let stx = buf[index::STX];
if stx != STX {
return Err(Error::failure(format!(
"invalid STX byte, expected: {STX}, have: {stx}"
)));
}
let msg_len = buf[index::LEN] as usize;
if msg_len != self.len() {
return Err(Error::failure("invalid reply length"));
}
let etx = buf[msg_len - 2];
if etx != ETX {
return Err(Error::failure(format!(
"invalid ETX byte, expected: {ETX}, have: {etx}"
)));
}
validate_checksum(buf[..msg_len].as_ref())?;
let control = Control::from(buf[index::CONTROL]);
let msg_type = MessageType::from(control.message_type());
let exp_msg_type = self.message_type();
if msg_type != exp_msg_type {
return Err(Error::failure(format!(
"invalid message type, expected: {exp_msg_type}, have: {msg_type}"
)));
}
self.buf_mut().copy_from_slice(buf[..msg_len].as_ref());
Ok(())
}
}
pub fn validate_checksum(buf: &[u8]) -> Result<()> {
let len = buf.len();
if !(len::MIN_MESSAGE..=len::MAX_MESSAGE).contains(&len) {
return Err(Error::failure("invalid message length"));
}
let etx_index = len - 2;
let chk_index = len - 1;
let expected = checksum(buf[index::LEN..etx_index].as_ref());
let current = buf[chk_index];
if expected == current {
Ok(())
} else {
Err(Error::failure(format!(
"invalid checksum, expected: {expected}, have: {current}"
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_u16_seven_bit() {
let expected = 0x1234;
let expected_bytes = [0x1, 0x2, 0x3, 0x4];
assert_eq!(seven_bit_u16(expected_bytes.as_ref()), expected);
assert_eq!(u16_seven_bit(expected), expected_bytes);
assert_eq!(
u16_seven_bit(seven_bit_u16(expected_bytes.as_ref())),
expected_bytes
);
assert_eq!(seven_bit_u16(u16_seven_bit(expected).as_ref()), expected);
for num in 0..u16::MAX {
assert_eq!(seven_bit_u16(u16_seven_bit(num).as_ref()), num);
}
}
#[test]
fn test_u8_seven_bit() {
let expected = 0x54;
let expected_bytes = [0x5, 0x4];
assert_eq!(u8_seven_bit(expected), expected_bytes);
assert_eq!(seven_bit_u8(expected_bytes.as_ref()), expected);
}
}