1#![warn(clippy::all, clippy::pedantic)]
2pub mod results;
18pub mod request;
19pub mod offsets;
20
21use thiserror::Error;
22use tracing::trace;
23use websockets::{
24 Frame,
25 WebSocket,
26};
27
28pub use results::{
29 Result,
30 ResultData,
31};
32pub use request::Request;
33
34pub struct Client {
35 websocket: WebSocket,
36}
37
38#[derive(Error, Debug)]
39pub enum Qusb2snesError {
40 #[error("websocket error: {source}")]
41 SocketError {
42 #[from]
43 source: websockets::WebSocketError,
44 },
45 #[error("unable to deserialize message: {source}")]
46 MessageError{
47 #[from]
48 source: serde_json::error::Error,
49 },
50 #[error("unhandled message frame: {msg}")]
51 FrameError {
52 msg: String,
53 },
54}
55
56impl Client {
58 pub async fn new() -> std::result::Result<Self, Qusb2snesError> {
62 let websocket = WebSocket::connect("ws://localhost:8080").await?;
63
64 Ok(Client { websocket })
65 }
66
67 pub async fn device_list(&mut self) -> std::result::Result<Vec<String>, Qusb2snesError> {
72 let request_string = serde_json::to_string(&Request::device_list())?;
73 self.send_text(&request_string).await?;
74
75 if let ResultData::Text(result) = self.receive_until_fin().await? {
76 return Ok(result);
77 };
78
79 Err(Qusb2snesError::FrameError { msg: "Unexpected response retrieving device list.".into() })
80 }
81
82 pub async fn attach(&mut self, device: &str) -> std::result::Result<(), Qusb2snesError> {
87 let request_string = serde_json::to_string(&Request::attach(device))?;
88 self.send_text(&request_string).await?;
89
90 Ok(())
91 }
92
93 pub async fn info(&mut self) -> std::result::Result<Vec<String>, Qusb2snesError> {
99 let request_string = serde_json::to_string(&Request::info())?;
100 self.send_text(&request_string).await?;
101
102 if let ResultData::Text(result) = self.receive_until_fin().await? {
103 return Ok(result);
104 }
105
106 Err(Qusb2snesError::FrameError { msg: "Unexpected response retrieving device info.".into() })
107 }
108
109 pub async fn get_address(&mut self, offset: usize, length: usize) -> std::result::Result<Vec<u8>, Qusb2snesError> {
115 let mut mem = vec![];
116
117 for (start, len) in chunked_range(offset, length) {
118 let request_string = serde_json::to_string(&Request::get_address(start, len))?;
119 self.send_text(&request_string).await?;
120
121 if let ResultData::Binary(res) = self.receive_until_fin().await? {
122 mem.extend_from_slice(&res);
123 } else {
124 return Err(Qusb2snesError::FrameError { msg: "Unable to decode response".into() });
125 };
126 }
127
128 Ok(mem)
129 }
130
131 async fn send_text(&mut self, request_string: &str) -> std::result::Result<(), Qusb2snesError> {
135 trace!("Request: {:?}", request_string);
136 match self.websocket.send_text(request_string.into()).await.err() {
137 Some(e) => Err(e.into()),
138 None => Ok(()),
139 }
140 }
141
142 async fn receive_until_fin(&mut self) -> std::result::Result<ResultData, Qusb2snesError> {
143 let mut text_buf = vec![];
144 let mut binary_buf = vec![];
145
146 loop {
147 let response = self.websocket.receive().await?;
148 println!("Received: {:#?}", response);
149
150 match response {
151 Frame::Text { payload, fin, continuation: _ } => {
152 if let Result { results: ResultData::Text(res) } = serde_json::from_str::<Result>(&payload)?{
153 text_buf.extend_from_slice(&res);
154 } else {
155 return Err(Qusb2snesError::FrameError { msg: "Unable to handle text frame".into() });
156 };
157
158 if fin {
159 return Ok(ResultData::Text(text_buf));
160 }
161 }
162 Frame::Binary { payload, fin, continuation: _ } => {
163 binary_buf.extend_from_slice(&payload);
164 if fin {
165 return Ok(ResultData::Binary(binary_buf));
166 }
167 }
168 Frame::Close { payload: _ } => return Err(Qusb2snesError::FrameError { msg: "Websocket closed".into() }),
169 _ => {}
170 }
171 }
172 }
173}
174
175fn chunked_range(start: usize, length: usize) -> Vec<(usize, usize)> {
176 let mut chunks = vec![];
177 let page_size = 1_024;
178
179 if length > page_size {
180 let mut total_bytes = 0;
181 while total_bytes < length {
182 let current_page_size = std::cmp::min(page_size, length - total_bytes);
183 chunks.push((start + total_bytes, current_page_size));
184 total_bytes += current_page_size;
185 }
186 } else {
187 chunks.push((start, length));
188 }
189
190 chunks
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use pretty_assertions_sorted::assert_eq;
197
198 #[test]
199 fn range_chunks() {
200 assert_eq!(
201 chunked_range(0, 10),
202 vec![(0, 10)],
203 );
204
205 assert_eq!(
206 chunked_range(0, 1024),
207 vec![(0, 1024)],
208 );
209
210 assert_eq!(
211 chunked_range(0, 2000),
212 vec![(0, 1024), (1024, 976)],
213 );
214
215 assert_eq!(
216 chunked_range(0xF5_0000, 0x2000),
217 vec![
218 (0xF5_0000, 1024),
219 (0xF5_0400, 1024),
220 (0xF5_0800, 1024),
221 (0xF5_0C00, 1024),
222 (0xF5_1000, 1024),
223 (0xF5_1400, 1024),
224 (0xF5_1800, 1024),
225 (0xF5_1C00, 1024),
226 ],
227 );
228
229 assert_eq!(
230 chunked_range(0xF5_0000, 0x1FFF),
231 vec![
232 (0xF5_0000, 1024),
233 (0xF5_0400, 1024),
234 (0xF5_0800, 1024),
235 (0xF5_0C00, 1024),
236 (0xF5_1000, 1024),
237 (0xF5_1400, 1024),
238 (0xF5_1800, 1024),
239 (0xF5_1C00, 1023),
240 ],
241 );
242 }
243}