#![warn(clippy::all, clippy::pedantic)]
pub mod results;
pub mod request;
pub mod offsets;
use thiserror::Error;
use tracing::trace;
use websockets::{
Frame,
WebSocket,
};
pub use results::{
Result,
ResultData,
};
pub use request::Request;
pub struct Client {
websocket: WebSocket,
}
#[derive(Error, Debug)]
pub enum Qusb2snesError {
#[error("websocket error: {source}")]
SocketError {
#[from]
source: websockets::WebSocketError,
},
#[error("unable to deserialize message: {source}")]
MessageError{
#[from]
source: serde_json::error::Error,
},
#[error("unhandled message frame: {msg}")]
FrameError {
msg: String,
},
}
impl Client {
pub async fn new() -> std::result::Result<Self, Qusb2snesError> {
let websocket = WebSocket::connect("ws://localhost:8080").await?;
Ok(Client { websocket })
}
pub async fn device_list(&mut self) -> std::result::Result<Vec<String>, Qusb2snesError> {
let request_string = serde_json::to_string(&Request::device_list())?;
self.send_text(&request_string).await?;
if let ResultData::Text(result) = self.receive_until_fin().await? {
return Ok(result);
};
Err(Qusb2snesError::FrameError { msg: "Unexpected response retrieving device list.".into() })
}
pub async fn attach(&mut self, device: &str) -> std::result::Result<(), Qusb2snesError> {
let request_string = serde_json::to_string(&Request::attach(device))?;
self.send_text(&request_string).await?;
Ok(())
}
pub async fn info(&mut self) -> std::result::Result<Vec<String>, Qusb2snesError> {
let request_string = serde_json::to_string(&Request::info())?;
self.send_text(&request_string).await?;
if let ResultData::Text(result) = self.receive_until_fin().await? {
return Ok(result);
}
Err(Qusb2snesError::FrameError { msg: "Unexpected response retrieving device info.".into() })
}
pub async fn get_address(&mut self, offset: usize, length: usize) -> std::result::Result<Vec<u8>, Qusb2snesError> {
let mut mem = vec![];
for (start, len) in chunked_range(offset, length) {
let request_string = serde_json::to_string(&Request::get_address(start, len))?;
self.send_text(&request_string).await?;
if let ResultData::Binary(res) = self.receive_until_fin().await? {
mem.extend_from_slice(&res);
} else {
return Err(Qusb2snesError::FrameError { msg: "Unable to decode response".into() });
};
}
Ok(mem)
}
async fn send_text(&mut self, request_string: &str) -> std::result::Result<(), Qusb2snesError> {
trace!("Request: {:?}", request_string);
match self.websocket.send_text(request_string.into()).await.err() {
Some(e) => Err(e.into()),
None => Ok(()),
}
}
async fn receive_until_fin(&mut self) -> std::result::Result<ResultData, Qusb2snesError> {
let mut text_buf = vec![];
let mut binary_buf = vec![];
loop {
let response = self.websocket.receive().await?;
println!("Received: {:#?}", response);
match response {
Frame::Text { payload, fin, continuation: _ } => {
if let Result { results: ResultData::Text(res) } = serde_json::from_str::<Result>(&payload)?{
text_buf.extend_from_slice(&res);
} else {
return Err(Qusb2snesError::FrameError { msg: "Unable to handle text frame".into() });
};
if fin {
return Ok(ResultData::Text(text_buf));
}
}
Frame::Binary { payload, fin, continuation: _ } => {
binary_buf.extend_from_slice(&payload);
if fin {
return Ok(ResultData::Binary(binary_buf));
}
}
Frame::Close { payload: _ } => return Err(Qusb2snesError::FrameError { msg: "Websocket closed".into() }),
_ => {}
}
}
}
}
fn chunked_range(start: usize, length: usize) -> Vec<(usize, usize)> {
let mut chunks = vec![];
let page_size = 1_024;
if length > page_size {
let mut total_bytes = 0;
while total_bytes < length {
let current_page_size = std::cmp::min(page_size, length - total_bytes);
chunks.push((start + total_bytes, current_page_size));
total_bytes += current_page_size;
}
} else {
chunks.push((start, length));
}
chunks
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions_sorted::assert_eq;
#[test]
fn range_chunks() {
assert_eq!(
chunked_range(0, 10),
vec![(0, 10)],
);
assert_eq!(
chunked_range(0, 1024),
vec![(0, 1024)],
);
assert_eq!(
chunked_range(0, 2000),
vec![(0, 1024), (1024, 976)],
);
assert_eq!(
chunked_range(0xF5_0000, 0x2000),
vec![
(0xF5_0000, 1024),
(0xF5_0400, 1024),
(0xF5_0800, 1024),
(0xF5_0C00, 1024),
(0xF5_1000, 1024),
(0xF5_1400, 1024),
(0xF5_1800, 1024),
(0xF5_1C00, 1024),
],
);
assert_eq!(
chunked_range(0xF5_0000, 0x1FFF),
vec![
(0xF5_0000, 1024),
(0xF5_0400, 1024),
(0xF5_0800, 1024),
(0xF5_0C00, 1024),
(0xF5_1000, 1024),
(0xF5_1400, 1024),
(0xF5_1800, 1024),
(0xF5_1C00, 1023),
],
);
}
}