Added proper error types

This commit is contained in:
G2-Games 2024-03-11 03:57:47 -05:00
parent 9bdd7ddb39
commit e55736344d
7 changed files with 340 additions and 201 deletions

View file

@ -34,6 +34,7 @@ cbc = "0.1"
ecb = "0.1" ecb = "0.1"
tokio = { version = "1.36", features = ["sync"] } tokio = { version = "1.36", features = ["sync"] }
unicode-jp = { git = "https://github.com/uzabase/unicode-jp-rs.git" } unicode-jp = { git = "https://github.com/uzabase/unicode-jp-rs.git" }
thiserror = "1.0.57"
[target.'cfg(target_family = "wasm")'.dependencies] [target.'cfg(target_family = "wasm")'.dependencies]
gloo = { version = "0.11.0", features = ["futures"] } gloo = { version = "0.11.0", features = ["futures"] }

View file

@ -1,7 +1,5 @@
/*! /// A crate for controlling NetMD and Hi-MD devices.
* This crate is an interface in rust to control NetMD and Hi-MD minidisc devices. ///
* /// To use this library, first you need to get a device from [cross-usb] and then open a [netmd::interface::NetMDInterface]
* Documentation coming soon
*/
pub mod netmd; pub mod netmd;

View file

@ -1,16 +1,16 @@
#![cfg_attr(debug_assertions, allow(dead_code))] #![cfg_attr(debug_assertions, allow(dead_code))]
use nofmt;
use once_cell::sync::Lazy;
use std::error::Error;
use std::time::Duration; use std::time::Duration;
use nofmt;
use once_cell::sync::Lazy;
use thiserror::Error;
// USB stuff // USB stuff
use cross_usb::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient}; use cross_usb::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError};
use cross_usb::{UsbDevice, UsbInterface}; use cross_usb::{UsbDevice, UsbInterface};
use super::utils::cross_sleep; use super::utils::cross_sleep;
const DEFAULT_TIMEOUT: Duration = Duration::new(10000, 0);
const BULK_WRITE_ENDPOINT: u8 = 0x02; const BULK_WRITE_ENDPOINT: u8 = 0x02;
const BULK_READ_ENDPOINT: u8 = 0x81; const BULK_READ_ENDPOINT: u8 = 0x81;
@ -89,13 +89,32 @@ pub enum Status {
} }
/// The ID of a device, including the name /// The ID of a device, including the name
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DeviceId { pub struct DeviceId {
vendor_id: u16, vendor_id: u16,
product_id: u16, product_id: u16,
name: Option<String>, name: Option<String>,
} }
/// A connection to a NetMD device #[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum NetMDError {
#[error("communication timed out")]
Timeout,
#[error("invalid usb result")]
InvalidResult,
#[error("the device is not ready")]
NotReady,
#[error("could not find device")]
UnknownDevice(DeviceId),
#[error("usb connection error")]
UsbError(#[from] UsbError),
}
/// A USB connection to a NetMD device
pub struct NetMD { pub struct NetMD {
usb_interface: UsbInterface, usb_interface: UsbInterface,
model: DeviceId, model: DeviceId,
@ -105,7 +124,7 @@ impl NetMD {
const READ_REPLY_RETRY_INTERVAL: u32 = 10; const READ_REPLY_RETRY_INTERVAL: u32 = 10;
/// Creates a new interface to a NetMD device /// Creates a new interface to a NetMD device
pub async fn new(usb_device: &UsbDevice) -> Result<Self, Box<dyn Error>> { pub async fn new(usb_device: &UsbDevice) -> Result<Self, NetMDError> {
let mut model = DeviceId { let mut model = DeviceId {
vendor_id: usb_device.vendor_id().await, vendor_id: usb_device.vendor_id().await,
product_id: usb_device.product_id().await, product_id: usb_device.product_id().await,
@ -122,7 +141,7 @@ impl NetMD {
} }
match model.name { match model.name {
None => return Err("Could not find device in list".into()), None => return Err(NetMDError::UnknownDevice(model)),
Some(_) => (), Some(_) => (),
} }
@ -151,7 +170,7 @@ impl NetMD {
/// Poll the device to get either the result /// Poll the device to get either the result
/// of the previous command, or the status /// of the previous command, or the status
pub async fn poll(&mut self) -> Result<(u16, [u8; 4]), Box<dyn Error>> { pub async fn poll(&mut self) -> Result<(u16, [u8; 4]), NetMDError> {
// Create an array to store the result of the poll // Create an array to store the result of the poll
let poll_result = match self let poll_result = match self
.usb_interface .usb_interface
@ -173,17 +192,17 @@ impl NetMD {
let poll_result: [u8; 4] = match poll_result.try_into() { let poll_result: [u8; 4] = match poll_result.try_into() {
Ok(val) => val, Ok(val) => val,
Err(_) => return Err("could not convert result".into()), Err(_) => return Err(NetMDError::InvalidResult),
}; };
Ok((length_bytes, poll_result)) Ok((length_bytes, poll_result))
} }
pub async fn send_command(&mut self, command: Vec<u8>) -> Result<(), Box<dyn Error>> { pub async fn send_command(&mut self, command: Vec<u8>) -> Result<(), NetMDError> {
self._send_command(command, false).await self._send_command(command, false).await
} }
pub async fn send_factory_command(&mut self, command: Vec<u8>) -> Result<(), Box<dyn Error>> { pub async fn send_factory_command(&mut self, command: Vec<u8>) -> Result<(), NetMDError> {
self._send_command(command, true).await self._send_command(command, true).await
} }
@ -192,12 +211,12 @@ impl NetMD {
&mut self, &mut self,
command: Vec<u8>, command: Vec<u8>,
use_factory_command: bool, use_factory_command: bool,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), NetMDError> {
// First poll to ensure the device is ready // First poll to ensure the device is ready
match self.poll().await { match self.poll().await {
Ok(buffer) => match buffer.1[2] { Ok(buffer) => match buffer.1[2] {
0 => 0, 0 => 0,
_ => return Err("Device not ready!".into()), _ => return Err(NetMDError::NotReady),
}, },
Err(error) => return Err(error), Err(error) => return Err(error),
}; };
@ -227,14 +246,14 @@ impl NetMD {
pub async fn read_reply( pub async fn read_reply(
&mut self, &mut self,
override_length: Option<i32>, override_length: Option<i32>,
) -> Result<Vec<u8>, Box<dyn Error>> { ) -> Result<Vec<u8>, NetMDError> {
self._read_reply(false, override_length).await self._read_reply(false, override_length).await
} }
pub async fn read_factory_reply( pub async fn read_factory_reply(
&mut self, &mut self,
override_length: Option<i32>, override_length: Option<i32>,
) -> Result<Vec<u8>, Box<dyn Error>> { ) -> Result<Vec<u8>, NetMDError> {
self._read_reply(true, override_length).await self._read_reply(true, override_length).await
} }
@ -243,12 +262,12 @@ impl NetMD {
&mut self, &mut self,
use_factory_command: bool, use_factory_command: bool,
override_length: Option<i32>, override_length: Option<i32>,
) -> Result<Vec<u8>, Box<dyn Error>> { ) -> Result<Vec<u8>, NetMDError> {
let mut length = 0; let mut length = 0;
for attempt in 0..40 { for attempt in 0..40 {
if attempt == 39 { if attempt == 39 {
return Err("Failed to get response length".into()); return Err(NetMDError::Timeout);
} }
length = self.poll().await?.0; length = self.poll().await?.0;
@ -260,7 +279,7 @@ impl NetMD {
// Back off while trying again // Back off while trying again
let sleep_time = Self::READ_REPLY_RETRY_INTERVAL * (u32::pow(2, attempt) - 1); let sleep_time = Self::READ_REPLY_RETRY_INTERVAL * (u32::pow(2, attempt) - 1);
cross_sleep(sleep_time).await; cross_sleep(Duration::from_millis(sleep_time as u64)).await;
} }
if let Some(value) = override_length { if let Some(value) = override_length {
@ -293,7 +312,7 @@ impl NetMD {
&mut self, &mut self,
length: usize, length: usize,
chunksize: usize, chunksize: usize,
) -> Result<Vec<u8>, Box<dyn Error>> { ) -> Result<Vec<u8>, NetMDError> {
let result = self.read_bulk_to_array(length, chunksize).await?; let result = self.read_bulk_to_array(length, chunksize).await?;
Ok(result) Ok(result)
@ -303,7 +322,7 @@ impl NetMD {
&mut self, &mut self,
length: usize, length: usize,
chunksize: usize, chunksize: usize,
) -> Result<Vec<u8>, Box<dyn Error>> { ) -> Result<Vec<u8>, NetMDError> {
let mut final_result: Vec<u8> = Vec::new(); let mut final_result: Vec<u8> = Vec::new();
let mut done = 0; let mut done = 0;
@ -317,7 +336,7 @@ impl NetMD {
.await .await
{ {
Ok(result) => result, Ok(result) => result,
Err(error) => return Err(format!("USB error: {:?}", error).into()), Err(error) => return Err(NetMDError::UsbError(error)),
}; };
final_result.extend_from_slice(&res); final_result.extend_from_slice(&res);
@ -326,7 +345,7 @@ impl NetMD {
Ok(final_result) Ok(final_result)
} }
pub async fn write_bulk(&mut self, data: &[u8]) -> Result<usize, Box<dyn Error>> { pub async fn write_bulk(&mut self, data: &[u8]) -> Result<usize, NetMDError> {
Ok(self Ok(self
.usb_interface .usb_interface
.bulk_out(BULK_WRITE_ENDPOINT, data) .bulk_out(BULK_WRITE_ENDPOINT, data)

View file

@ -2,6 +2,7 @@
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use std::error::Error; use std::error::Error;
use std::time::Duration;
use super::interface::{MDSession, MDTrack, NetMDInterface}; use super::interface::{MDSession, MDTrack, NetMDInterface};
use super::utils::cross_sleep; use super::utils::cross_sleep;
@ -69,7 +70,7 @@ pub async fn prepare_download(interface: &mut NetMDInterface) -> Result<(), Box<
.state .state
.unwrap_or(OperatingStatus::NoDisc), .unwrap_or(OperatingStatus::NoDisc),
) { ) {
cross_sleep(200).await; cross_sleep(Duration::from_millis(200)).await;
} }
let _ = interface.session_key_forget().await; let _ = interface.session_key_forget().await;

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
use crate::netmd::utils; use crate::netmd::utils;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use std::error::Error; use thiserror::Error;
lazy_static! { lazy_static! {
/// %b, w, d, q - explained above (can have endiannes overriden by '>' and '<' operators, f. ex. %>d %<q) /// %b, w, d, q - explained above (can have endiannes overriden by '>' and '<' operators, f. ex. %>d %<q)
@ -27,24 +27,73 @@ pub enum QueryValue {
Array(Vec<u8>), Array(Vec<u8>),
} }
impl QueryValue { #[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub fn to_vec(&self) -> Result<Vec<u8>, Box<dyn Error>> { pub enum ValueError {
match self { #[error("type mismatch: expected {expected}, got {actual}")]
QueryValue::Array(a) => Ok(a.to_vec()), TypeMismatch {
_ => Err("QueryValue type mismatch! Expected Vec<u8>, got i64".into()), expected: String,
actual: String
} }
} }
pub fn to_i64(&self) -> Result<i64, Box<dyn Error>> { impl QueryValue {
pub fn from_array<const S: usize>(value: [u8; S]) -> Self {
Self::Array(value.to_vec())
}
pub fn to_array<const S: usize>(&self) -> Result<[u8; S], ValueError> {
let mut array = [0u8; S];
match self {
QueryValue::Array(a) => {
for (i, byte) in a.iter().take(S).enumerate() {
array[i] = *byte
}
Ok(array)
},
_ => Err(ValueError::TypeMismatch {
expected: String::from("Vec<u8>"),
actual: format!("{:?}", self)
}),
}
}
pub fn to_vec(&self) -> Result<Vec<u8>, ValueError> {
match self {
QueryValue::Array(a) => Ok(a.to_vec()),
_ => Err(ValueError::TypeMismatch {
expected: String::from("Vec<u8>"),
actual: format!("{:?}", self)
}),
}
}
pub fn to_i64(&self) -> Result<i64, ValueError> {
match self { match self {
QueryValue::Number(a) => Ok(*a), QueryValue::Number(a) => Ok(*a),
_ => Err("QueryValue type mismatch! Expected i64, got Vec<u8>".into()), _ => Err(ValueError::TypeMismatch {
expected: String::from("i64"),
actual: format!("{:?}", self)
}),
} }
} }
} }
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum QueryError {
#[error("unrecognized format character: `{0}`")]
UnrecognizedChar(char),
#[error("Format and input mismatch at {index}: expected {expected:#04x}, got {actual:#04x} (format {format_string})")]
InputMismatch {
index: usize,
expected: u8,
actual: u8,
format_string: String,
}
}
/// Formats a query using a standard input to send to the player /// Formats a query using a standard input to send to the player
pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Box<dyn Error>> { pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, QueryError> {
if DEBUG { if DEBUG {
println!("SENT>>> F: {}", format); println!("SENT>>> F: {}", format);
} }
@ -114,7 +163,7 @@ pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Bo
} }
result.push((converted & 0xFF) as u8); result.push((converted & 0xFF) as u8);
} }
_ => return Err(format!("Unrecognized format char {}", character).into()), _ => return Err(QueryError::UnrecognizedChar(character)),
} }
continue; continue;
} }
@ -142,7 +191,7 @@ pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Bo
pub fn scan_query( pub fn scan_query(
query_result: Vec<u8>, query_result: Vec<u8>,
format: String, format: String,
) -> Result<Vec<QueryValue>, Box<dyn Error>> { ) -> Result<Vec<QueryValue>, QueryError> {
let mut result: Vec<QueryValue> = Vec::new(); let mut result: Vec<QueryValue> = Vec::new();
let initial_length = query_result.len(); let initial_length = query_result.len();
@ -224,7 +273,7 @@ pub fn scan_query(
| input_stack.next().unwrap() as i32; | input_stack.next().unwrap() as i32;
result.push(QueryValue::Number(utils::bcd_to_int(v) as i64)); result.push(QueryValue::Number(utils::bcd_to_int(v) as i64));
} }
_ => return Err(format!("Unrecognized format char {}", character).into()), _ => return Err(QueryError::UnrecognizedChar(character)),
} }
continue; continue;
} }
@ -244,7 +293,12 @@ pub fn scan_query(
u8::from_str_radix(&String::from_iter([half.unwrap(), character]), 16).unwrap(); u8::from_str_radix(&String::from_iter([half.unwrap(), character]), 16).unwrap();
if format_value != input_value { if format_value != input_value {
let i = initial_length - input_stack.len() - 1; let i = initial_length - input_stack.len() - 1;
return Err(format!("Format and input mismatch at {i}: expected {format_value:#04x}, got {input_value:#04x} (format {format})").into()); return Err(QueryError::InputMismatch {
index: i,
expected: format_value,
actual: input_value,
format_string: format
});
} }
half = None; half = None;
} }

View file

@ -2,19 +2,19 @@ use crate::netmd::mappings::{ALLOWED_HW_KANA, MAPPINGS_DE, MAPPINGS_HW, MAPPINGS
use diacritics; use diacritics;
use encoding_rs::SHIFT_JIS; use encoding_rs::SHIFT_JIS;
use regex::Regex; use regex::Regex;
use std::{collections::hash_map::HashMap, error::Error, vec::IntoIter}; use std::{collections::hash_map::HashMap, error::Error, vec::IntoIter, time::Duration};
use unicode_normalization::UnicodeNormalization; use unicode_normalization::UnicodeNormalization;
extern crate kana; extern crate kana;
use kana::*; use kana::*;
/// Sleep for a specified number of milliseconds on any platform /// Sleep for a specified number of milliseconds on any platform
pub async fn cross_sleep(millis: u32) { pub async fn cross_sleep(duration: Duration) {
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
std::thread::sleep(std::time::Duration::from_millis(millis as u64)); std::thread::sleep(duration);
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
gloo::timers::future::TimeoutFuture::new(millis).await; gloo::timers::future::TimeoutFuture::new(duration.as_millis()).await;
} }
pub fn bcd_to_int(mut bcd: i32) -> i32 { pub fn bcd_to_int(mut bcd: i32) -> i32 {