rfid_silion_compat/
client.rs1use core::fmt;
2
3use crate::codes::StatusCode;
4use crate::error::ProtocolError;
5use crate::frame::{ReaderFrame, build_host_frame, parse_reader_frame};
6use crate::transport::ReaderTransport;
7
8#[derive(Debug)]
10pub enum ClientError<TE> {
11 Transport(TE),
13 Protocol(ProtocolError),
15 ReaderStatus {
17 status_raw: u16,
19 status: Option<StatusCode>,
21 },
22 UnexpectedResponseCommand {
24 expected: u8,
26 actual: u8,
28 },
29}
30
31impl<TE: fmt::Display> fmt::Display for ClientError<TE> {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 Self::Transport(e) => write!(f, "transport error: {e}"),
35 Self::Protocol(e) => write!(f, "protocol error: {e}"),
36 Self::ReaderStatus { status_raw, status } => {
37 write!(f, "reader returned status 0x{status_raw:04X} ({status:?})")
38 }
39 Self::UnexpectedResponseCommand { expected, actual } => {
40 write!(
41 f,
42 "unexpected response command: expected 0x{expected:02X}, got 0x{actual:02X}"
43 )
44 }
45 }
46 }
47}
48
49impl<TE: fmt::Debug + fmt::Display> std::error::Error for ClientError<TE> {}
50
51pub struct ReaderClient<T: ReaderTransport> {
53 transport: T,
54}
55
56impl<T: ReaderTransport> ReaderClient<T> {
57 pub fn new(transport: T) -> Self {
59 Self { transport }
60 }
61
62 pub fn into_inner(self) -> T {
64 self.transport
65 }
66
67 pub fn transport_mut(&mut self) -> &mut T {
69 &mut self.transport
70 }
71
72 pub async fn write_frame(&mut self, data: &[u8]) -> Result<(), ClientError<T::Error>> {
74 self.transport
75 .write_all(data)
76 .await
77 .map_err(ClientError::Transport)
78 }
79
80 pub async fn read_frame(&mut self) -> Result<ReaderFrame, ClientError<T::Error>> {
82 let mut prefix = [0u8; 5];
83 self.transport
84 .read_exact(&mut prefix)
85 .await
86 .map_err(ClientError::Transport)?;
87
88 let data_len = prefix[1] as usize;
89 let mut suffix = vec![0u8; data_len + 2];
90 self.transport
91 .read_exact(&mut suffix)
92 .await
93 .map_err(ClientError::Transport)?;
94
95 let mut packet = Vec::with_capacity(prefix.len() + suffix.len());
96 packet.extend_from_slice(&prefix);
97 packet.extend_from_slice(&suffix);
98
99 parse_reader_frame(&packet).map_err(ClientError::Protocol)
100 }
101
102 pub async fn transact_frame(
104 &mut self,
105 request: &[u8],
106 ) -> Result<ReaderFrame, ClientError<T::Error>> {
107 self.transport
108 .write_all(request)
109 .await
110 .map_err(ClientError::Transport)?;
111 self.read_frame().await
112 }
113
114 pub async fn transact(
116 &mut self,
117 command: u8,
118 data: &[u8],
119 ) -> Result<ReaderFrame, ClientError<T::Error>> {
120 let request = build_host_frame(command, data).map_err(ClientError::Protocol)?;
121 let response = self.transact_frame(&request).await?;
122
123 if response.command != command {
124 return Err(ClientError::UnexpectedResponseCommand {
125 expected: command,
126 actual: response.command,
127 });
128 }
129
130 if response.status_raw != StatusCode::Success as u16 {
131 return Err(ClientError::ReaderStatus {
132 status_raw: response.status_raw,
133 status: response.status,
134 });
135 }
136
137 Ok(response)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::ReaderClient;
144 use crate::client::ClientError;
145 use crate::codes::CommandCode;
146 use crate::test_support::{MockInteraction, MockTransport};
147
148 #[test]
149 fn transact_success() {
150 let transport = MockTransport::scripted(vec![MockInteraction {
151 request_command: CommandCode::GetCurrentRegion as u8,
152 response_status: 0x0000,
153 response_data: vec![0x01],
154 }]);
155
156 let mut client = ReaderClient::new(transport);
157 let frame =
158 futures::executor::block_on(client.transact(CommandCode::GetCurrentRegion as u8, &[]))
159 .expect("transact should succeed");
160 assert_eq!(frame.command, CommandCode::GetCurrentRegion as u8);
161 assert_eq!(frame.data, vec![0x01]);
162 }
163
164 #[test]
165 fn transact_reader_status_error() {
166 let transport = MockTransport::scripted(vec![MockInteraction {
167 request_command: CommandCode::GetCurrentRegion as u8,
168 response_status: 0x010B,
169 response_data: vec![],
170 }]);
171
172 let mut client = ReaderClient::new(transport);
173 let err =
174 futures::executor::block_on(client.transact(CommandCode::GetCurrentRegion as u8, &[]))
175 .expect_err("transact should fail with reader status");
176
177 match err {
178 ClientError::ReaderStatus { status_raw, .. } => assert_eq!(status_raw, 0x010B),
179 other => panic!("unexpected error: {other:?}"),
180 }
181 }
182}