use crate::error::Error;
use std::io::{Read, Write};
pub const MAGIC: [u8; 6] = [0xD5, 0x80, 0xD4, 0xB4, 0xD5, 0x84];
pub const PROTOCOL_VERSION: [u8; 2] = [0x00, 0x05];
pub const RESPONSE_HEADER_LEN: usize = 2 + 3 + 2 + 2 + 2;
pub const RESPONSE_CODE_OK: u16 = 200;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperationCode {
ListOpsAndDeps = 1,
OperatorLogin = 2,
OperatorLogout = 3,
PrintReceipt = 4,
PrintLastReceipt = 5,
GetReturnableReceipt = 6,
SetupHeaderFooter = 7,
SetupHeaderLogo = 8,
PrintFiscalReport = 9,
PrintReturnReceipt = 10,
CashInOut = 11,
DateTime = 12,
ReceiptSample = 13,
HdmTimeSync = 14,
PaymentSystemsList = 15,
SingleEmark = 16,
}
#[derive(Debug)]
pub struct Request {
pub op: OperationCode,
pub payload: Vec<u8>,
}
impl Request {
pub fn encode(&self, writer: &mut impl Write) -> Result<(), Error> {
let len = u16::try_from(self.payload.len()).map_err(|_| {
Error::Transport(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"payload exceeds u16::MAX",
))
})?;
writer.write_all(&MAGIC)?;
writer.write_all(&PROTOCOL_VERSION)?;
writer.write_all(&[self.op as u8, 0])?;
writer.write_all(&len.to_be_bytes())?;
writer.write_all(&self.payload)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct ResponseHeader {
pub protocol_version: (u8, u8),
pub software_version: (u8, u8, u8),
pub code: u16,
pub payload_len: u16,
}
impl ResponseHeader {
pub fn read(reader: &mut impl Read) -> Result<Self, Error> {
let mut buf = [0u8; RESPONSE_HEADER_LEN];
reader.read_exact(&mut buf)?;
let [
pv_major,
pv_minor,
sw_major,
sw_minor,
sw_patch,
code_hi,
code_lo,
len_hi,
len_lo,
_r0,
_r1,
] = buf;
Ok(Self {
protocol_version: (pv_major, pv_minor),
software_version: (sw_major, sw_minor, sw_patch),
code: u16::from_be_bytes([code_hi, code_lo]),
payload_len: u16::from_be_bytes([len_hi, len_lo]),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn magic_is_hdm_in_utf8() {
assert_eq!(std::str::from_utf8(&MAGIC).unwrap(), "ՀԴՄ");
}
#[test]
fn request_encode_round_trips_through_header() {
let req = Request {
op: OperationCode::OperatorLogin,
payload: vec![0xAA; 32],
};
let mut buf = Vec::new();
req.encode(&mut buf).unwrap();
let [
m0,
m1,
m2,
m3,
m4,
m5,
pv0,
pv1,
op,
reserved,
len_hi,
len_lo,
..,
] = buf.as_slice()
else {
panic!("encoded buffer too short");
};
assert_eq!([*m0, *m1, *m2, *m3, *m4, *m5], MAGIC);
assert_eq!([*pv0, *pv1], PROTOCOL_VERSION);
assert_eq!(*op, OperationCode::OperatorLogin as u8);
assert_eq!(*reserved, 0);
assert_eq!(u16::from_be_bytes([*len_hi, *len_lo]), 32);
}
#[test]
fn response_header_decodes() {
let raw = [
0x00, 0x05, 0x02, 0x02, 0x10, 0x00, 0xC8, 0x00, 0x10, 0x00, 0x00,
];
let mut cursor = Cursor::new(raw);
let hdr = ResponseHeader::read(&mut cursor).unwrap();
assert_eq!(hdr.protocol_version, (0, 5));
assert_eq!(hdr.software_version, (2, 2, 16));
assert_eq!(hdr.code, RESPONSE_CODE_OK);
assert_eq!(hdr.payload_len, 16);
}
}