1use std::io::{Read, Write};
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::Arc;
4use std::time;
5
6use serialport::{SerialPort, TTYPort};
7
8use ssp::{ResponseOps, Result};
9
10use super::device_handle::{BAUD_RATE, SERIAL_TIMEOUT_MS};
11
12pub struct MockDevice {
19 serial_path: String,
20 serial_port: TTYPort,
21 msg_type: ssp::MessageType,
22 response_status: ssp::ResponseStatus,
23}
24
25impl MockDevice {
26 pub fn new(
34 path: &str,
35 msg_type: ssp::MessageType,
36 response_status: ssp::ResponseStatus,
37 ) -> Result<Self> {
38 let serial_port = serialport::new(path, BAUD_RATE)
39 .flow_control(serialport::FlowControl::None)
41 .data_bits(serialport::DataBits::Eight)
43 .parity(serialport::Parity::None)
45 .stop_bits(serialport::StopBits::Two)
47 .timeout(time::Duration::from_millis(SERIAL_TIMEOUT_MS))
49 .open_native()?;
51
52 Ok(Self {
53 serial_path: path.into(),
54 serial_port,
55 msg_type,
56 response_status,
57 })
58 }
59
60 pub fn serial_path(&self) -> &str {
62 self.serial_path.as_str()
63 }
64
65 pub const fn msg_type(&self) -> ssp::MessageType {
67 self.msg_type
68 }
69
70 pub fn set_msg_type(&mut self, msg_type: ssp::MessageType) {
72 self.msg_type = msg_type;
73 }
74
75 pub const fn response_status(&self) -> ssp::ResponseStatus {
77 self.response_status
78 }
79
80 pub fn set_response_status(&mut self, response_status: ssp::ResponseStatus) {
82 self.response_status = response_status;
83 }
84
85 pub fn default_response(msg_type: ssp::MessageType) -> Option<Box<dyn ResponseOps>> {
87 match msg_type {
88 ssp::MessageType::SetInhibits => Some(Box::new(ssp::SetInhibitsResponse::new())),
89 ssp::MessageType::DisplayOn => Some(Box::new(ssp::DisplayOnResponse::new())),
90 ssp::MessageType::DisplayOff => Some(Box::new(ssp::DisplayOffResponse::new())),
91 ssp::MessageType::SetupRequest => Some(Box::new(ssp::SetupRequestResponse::new())),
92 ssp::MessageType::HostProtocolVersion => {
93 Some(Box::new(ssp::HostProtocolVersionResponse::new()))
94 }
95 ssp::MessageType::Poll => Some(Box::new(ssp::PollResponse::new())),
96 ssp::MessageType::Reject => Some(Box::new(ssp::RejectResponse::new())),
97 ssp::MessageType::Disable => Some(Box::new(ssp::DisableResponse::new())),
98 ssp::MessageType::Enable => Some(Box::new(ssp::EnableResponse::new())),
99 ssp::MessageType::SerialNumber => Some(Box::new(ssp::SerialNumberResponse::new())),
100 ssp::MessageType::UnitData => Some(Box::new(ssp::UnitDataResponse::new())),
101 ssp::MessageType::ChannelValueData => {
102 Some(Box::new(ssp::ChannelValueDataResponse::new()))
103 }
104 ssp::MessageType::Synchronisation => Some(Box::new(ssp::PollResponse::new())),
105 ssp::MessageType::LastRejectCode => Some(Box::new(ssp::LastRejectCodeResponse::new())),
106 ssp::MessageType::Hold => Some(Box::new(ssp::HoldResponse::new())),
107 ssp::MessageType::DatasetVersion => Some(Box::new(ssp::DatasetVersionResponse::new())),
108 ssp::MessageType::SetBarcodeReaderConfiguration => {
109 Some(Box::new(ssp::SetBarcodeReaderConfigurationResponse::new()))
110 }
111 ssp::MessageType::GetBarcodeReaderConfiguration => {
112 Some(Box::new(ssp::GetBarcodeReaderConfigurationResponse::new()))
113 }
114 ssp::MessageType::GetBarcodeInhibit => {
115 Some(Box::new(ssp::GetBarcodeInhibitResponse::new()))
116 }
117 ssp::MessageType::SetBarcodeInhibit => {
118 Some(Box::new(ssp::SetBarcodeInhibitResponse::new()))
119 }
120 ssp::MessageType::GetBarcodeData => Some(Box::new(ssp::GetBarcodeDataResponse::new())),
121 ssp::MessageType::Empty => Some(Box::new(ssp::EmptyResponse::new())),
122 ssp::MessageType::PayoutByDenomination => {
123 Some(Box::new(ssp::PayoutByDenominationResponse::new()))
124 }
125 ssp::MessageType::SmartEmpty => Some(Box::new(ssp::SmartEmptyResponse::new())),
126 ssp::MessageType::ConfigureBezel => Some(Box::new(ssp::ConfigureBezelResponse::new())),
127 ssp::MessageType::PollWithAck => Some(Box::new(ssp::PollWithAckResponse::new())),
128 ssp::MessageType::EventAck => Some(Box::new(ssp::EventAckResponse::new())),
129 ssp::MessageType::DisablePayout => Some(Box::new(ssp::DisablePayoutResponse::new())),
130 ssp::MessageType::EnablePayout => Some(Box::new(ssp::EnablePayoutResponse::new())),
131 _ => None,
133 }
134 }
135
136 pub fn response_data_len(msg_type: ssp::MessageType) -> usize {
138 match msg_type {
139 ssp::MessageType::SetInhibits => ssp::len::SET_INHIBITS_RESPONSE,
140 ssp::MessageType::DisplayOn => ssp::len::DISPLAY_ON_RESPONSE,
141 ssp::MessageType::DisplayOff => ssp::len::DISPLAY_OFF_RESPONSE,
142 ssp::MessageType::SetupRequest => ssp::len::SETUP_REQUEST_RESPONSE,
143 ssp::MessageType::HostProtocolVersion => ssp::len::HOST_PROTOCOL_VERSION_RESPONSE,
144 ssp::MessageType::Poll => ssp::len::POLL_RESPONSE,
145 ssp::MessageType::Reject => ssp::len::REJECT_RESPONSE,
146 ssp::MessageType::Disable => ssp::len::DISABLE_RESPONSE,
147 ssp::MessageType::Enable => ssp::len::ENABLE_RESPONSE,
148 ssp::MessageType::SerialNumber => ssp::len::SERIAL_NUMBER_RESPONSE,
149 ssp::MessageType::UnitData => ssp::len::UNIT_DATA_RESPONSE,
150 ssp::MessageType::ChannelValueData => ssp::len::CHANNEL_VALUE_DATA_RESPONSE,
151 ssp::MessageType::Synchronisation => ssp::len::SYNC_RESPONSE,
152 ssp::MessageType::LastRejectCode => ssp::len::LAST_REJECT_CODE_RESPONSE,
153 ssp::MessageType::Hold => ssp::len::HOLD_RESPONSE,
154 ssp::MessageType::DatasetVersion => ssp::len::DATASET_VERSION_RESPONSE,
155 ssp::MessageType::SetBarcodeReaderConfiguration => {
156 ssp::len::SET_BARCODE_READER_CONFIGURATION_RESPONSE
157 }
158 ssp::MessageType::GetBarcodeReaderConfiguration => {
159 ssp::len::GET_BARCODE_READER_CONFIGURATION_RESPONSE
160 }
161 ssp::MessageType::GetBarcodeInhibit => ssp::len::GET_BARCODE_INHIBIT_RESPONSE,
162 ssp::MessageType::SetBarcodeInhibit => ssp::len::SET_BARCODE_INHIBIT_RESPONSE,
163 ssp::MessageType::GetBarcodeData => ssp::len::GET_BARCODE_DATA_RESPONSE,
164 ssp::MessageType::Empty => ssp::len::EMPTY_RESPONSE,
165 ssp::MessageType::PayoutByDenomination => ssp::len::PAYOUT_BY_DENOMINATION_RESPONSE,
166 ssp::MessageType::SmartEmpty => ssp::len::SMART_EMPTY_RESPONSE,
167 ssp::MessageType::ConfigureBezel => ssp::len::CONFIGURE_BEZEL_RESPONSE,
168 ssp::MessageType::PollWithAck => ssp::len::POLL_WITH_ACK_RESPONSE,
169 ssp::MessageType::EventAck => ssp::len::EVENT_ACK_RESPONSE,
170 ssp::MessageType::DisablePayout => ssp::len::DISABLE_PAYOUT_RESPONSE,
171 ssp::MessageType::EnablePayout => ssp::len::ENABLE_PAYOUT_RESPONSE,
172 _ => 1,
174 }
175 }
176
177 pub fn send_default_response(&mut self) -> Result<()> {
179 if let Some(mut res) = Self::default_response(self.msg_type) {
180 res.set_response_status(self.response_status);
181 self.serial_port.write_all(res.as_bytes())?;
182 }
183 Ok(())
184 }
185
186 pub fn serve(&mut self, stop: Arc<AtomicBool>) -> Result<()> {
195 let mut buf = [0u8; ssp::len::MAX_MESSAGE];
196 while !stop.load(Ordering::Relaxed) {
197 if self.serial_port.bytes_to_read().unwrap_or(0) > 0
198 && self
199 .serial_port
200 .read_exact(&mut buf[..=ssp::message::index::LEN])
201 .is_ok()
202 {
203 log::debug!("Received host message");
204 let len = buf[ssp::message::index::LEN] as usize;
205 let rem = len + ssp::message::index::LEN + 2;
206
207 self.serial_port
208 .read_exact(&mut buf[ssp::message::index::LEN..rem])?;
209
210 log::debug!("Receive message bytes: {:x?}", &buf[..rem]);
211
212 let msg_type: ssp::MessageType = if len > ssp::message::index::DATA {
213 buf[ssp::message::index::DATA].into()
214 } else {
215 self.msg_type
216 };
217
218 let seq_id = !ssp::SequenceId::from(buf[ssp::message::index::SEQ_ID]);
219
220 if let Some(mut res) = Self::default_response(msg_type) {
221 res.set_response_status(self.response_status);
222 res.set_data_len(Self::response_data_len(msg_type) as u8);
223 res.set_sequence_id(seq_id);
224 log::debug!("Writing response: {:x?}", res.as_bytes());
225 self.serial_port.write_all(res.as_bytes())?;
226 log::trace!("Successfully wrote response");
227 self.serial_port.flush()?;
228 }
229 }
230 buf.iter_mut().for_each(|b| *b = 0);
231 }
232 Ok(())
233 }
234}