qusb2snes_client/
lib.rs

1#![warn(clippy::all, clippy::pedantic)]
2//! This crate is currently **experimental**.
3//!
4//! This crate allows interfacing with the websocket server provided by
5//! [Qusb2snes][qusb2snes].
6//!
7//! # Examples
8//!
9//! ```ignore
10//! # // TODO: Come up with a testable example.
11//! let mut client = Client::new().await.unwrap();
12//! println!("{:#?}", client.device_list().await);
13//! ```
14//!
15//! [qusb2snes]: http://usb2snes.com/
16
17pub 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
56// https://github.com/Skarsnik/QUsb2snes/blob/b06e818fd3ed4e5785dab48b22d13ff9160bf204/docs/Procotol.md
57impl Client {
58    /// # Errors
59    ///
60    /// Will return [`Err`] if the underlying [`websockets::WebSocket`] returns an [`Err`].
61    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    /// # Errors
68    ///
69    /// This method can fail if the underlying [`websockets::WebSocket`] has an error,
70    /// or if there is an  issue deserializing the list of devices.
71    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    /// # Errors
83    ///
84    /// This method can fail if the device name to attach to is not serializable as
85    /// a JSON string.
86    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    /// # Errors
94    ///
95    /// This method can fail if there is an issue sending the message to the associated
96    /// [`websockets::WebSocket`], or if there is an issue deserializing the response
97    /// from the websocket.
98    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    /// # Errors
110    ///
111    /// This method can fail if there is an issue sending the message to the associated
112    /// [`websockets::WebSocket`], or if there is an issue deserializing the response
113    /// from the websocket.
114    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    /// # Panics
132    ///
133    /// Basically has no error handling yet.
134    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}