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"
tokio = { version = "1.36", features = ["sync"] }
unicode-jp = { git = "https://github.com/uzabase/unicode-jp-rs.git" }
thiserror = "1.0.57"
[target.'cfg(target_family = "wasm")'.dependencies]
gloo = { version = "0.11.0", features = ["futures"] }

View file

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

View file

@ -1,16 +1,16 @@
#![cfg_attr(debug_assertions, allow(dead_code))]
use nofmt;
use once_cell::sync::Lazy;
use std::error::Error;
use std::time::Duration;
use nofmt;
use once_cell::sync::Lazy;
use thiserror::Error;
// 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 super::utils::cross_sleep;
const DEFAULT_TIMEOUT: Duration = Duration::new(10000, 0);
const BULK_WRITE_ENDPOINT: u8 = 0x02;
const BULK_READ_ENDPOINT: u8 = 0x81;
@ -89,13 +89,32 @@ pub enum Status {
}
/// The ID of a device, including the name
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct DeviceId {
vendor_id: u16,
product_id: u16,
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 {
usb_interface: UsbInterface,
model: DeviceId,
@ -105,7 +124,7 @@ impl NetMD {
const READ_REPLY_RETRY_INTERVAL: u32 = 10;
/// 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 {
vendor_id: usb_device.vendor_id().await,
product_id: usb_device.product_id().await,
@ -122,7 +141,7 @@ impl NetMD {
}
match model.name {
None => return Err("Could not find device in list".into()),
None => return Err(NetMDError::UnknownDevice(model)),
Some(_) => (),
}
@ -151,7 +170,7 @@ impl NetMD {
/// Poll the device to get either the result
/// 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
let poll_result = match self
.usb_interface
@ -173,17 +192,17 @@ impl NetMD {
let poll_result: [u8; 4] = match poll_result.try_into() {
Ok(val) => val,
Err(_) => return Err("could not convert result".into()),
Err(_) => return Err(NetMDError::InvalidResult),
};
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
}
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
}
@ -192,12 +211,12 @@ impl NetMD {
&mut self,
command: Vec<u8>,
use_factory_command: bool,
) -> Result<(), Box<dyn Error>> {
) -> Result<(), NetMDError> {
// First poll to ensure the device is ready
match self.poll().await {
Ok(buffer) => match buffer.1[2] {
0 => 0,
_ => return Err("Device not ready!".into()),
_ => return Err(NetMDError::NotReady),
},
Err(error) => return Err(error),
};
@ -227,14 +246,14 @@ impl NetMD {
pub async fn read_reply(
&mut self,
override_length: Option<i32>,
) -> Result<Vec<u8>, Box<dyn Error>> {
) -> Result<Vec<u8>, NetMDError> {
self._read_reply(false, override_length).await
}
pub async fn read_factory_reply(
&mut self,
override_length: Option<i32>,
) -> Result<Vec<u8>, Box<dyn Error>> {
) -> Result<Vec<u8>, NetMDError> {
self._read_reply(true, override_length).await
}
@ -243,12 +262,12 @@ impl NetMD {
&mut self,
use_factory_command: bool,
override_length: Option<i32>,
) -> Result<Vec<u8>, Box<dyn Error>> {
) -> Result<Vec<u8>, NetMDError> {
let mut length = 0;
for attempt in 0..40 {
if attempt == 39 {
return Err("Failed to get response length".into());
return Err(NetMDError::Timeout);
}
length = self.poll().await?.0;
@ -260,7 +279,7 @@ impl NetMD {
// Back off while trying again
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 {
@ -293,7 +312,7 @@ impl NetMD {
&mut self,
length: usize,
chunksize: usize,
) -> Result<Vec<u8>, Box<dyn Error>> {
) -> Result<Vec<u8>, NetMDError> {
let result = self.read_bulk_to_array(length, chunksize).await?;
Ok(result)
@ -303,7 +322,7 @@ impl NetMD {
&mut self,
length: usize,
chunksize: usize,
) -> Result<Vec<u8>, Box<dyn Error>> {
) -> Result<Vec<u8>, NetMDError> {
let mut final_result: Vec<u8> = Vec::new();
let mut done = 0;
@ -317,7 +336,7 @@ impl NetMD {
.await
{
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);
@ -326,7 +345,7 @@ impl NetMD {
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
.usb_interface
.bulk_out(BULK_WRITE_ENDPOINT, data)

View file

@ -2,6 +2,7 @@
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::error::Error;
use std::time::Duration;
use super::interface::{MDSession, MDTrack, NetMDInterface};
use super::utils::cross_sleep;
@ -69,7 +70,7 @@ pub async fn prepare_download(interface: &mut NetMDInterface) -> Result<(), Box<
.state
.unwrap_or(OperatingStatus::NoDisc),
) {
cross_sleep(200).await;
cross_sleep(Duration::from_millis(200)).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 lazy_static::lazy_static;
use std::collections::hash_map::HashMap;
use std::error::Error;
use thiserror::Error;
lazy_static! {
/// %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>),
}
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum ValueError {
#[error("type mismatch: expected {expected}, got {actual}")]
TypeMismatch {
expected: String,
actual: String
}
}
impl QueryValue {
pub fn to_vec(&self) -> Result<Vec<u8>, Box<dyn Error>> {
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) => Ok(a.to_vec()),
_ => Err("QueryValue type mismatch! Expected Vec<u8>, got i64".into()),
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_i64(&self) -> Result<i64, Box<dyn Error>> {
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 {
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
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 {
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);
}
_ => return Err(format!("Unrecognized format char {}", character).into()),
_ => return Err(QueryError::UnrecognizedChar(character)),
}
continue;
}
@ -142,7 +191,7 @@ pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Bo
pub fn scan_query(
query_result: Vec<u8>,
format: String,
) -> Result<Vec<QueryValue>, Box<dyn Error>> {
) -> Result<Vec<QueryValue>, QueryError> {
let mut result: Vec<QueryValue> = Vec::new();
let initial_length = query_result.len();
@ -224,7 +273,7 @@ pub fn scan_query(
| input_stack.next().unwrap() as i32;
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;
}
@ -244,7 +293,12 @@ pub fn scan_query(
u8::from_str_radix(&String::from_iter([half.unwrap(), character]), 16).unwrap();
if format_value != input_value {
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;
}

View file

@ -2,19 +2,19 @@ use crate::netmd::mappings::{ALLOWED_HW_KANA, MAPPINGS_DE, MAPPINGS_HW, MAPPINGS
use diacritics;
use encoding_rs::SHIFT_JIS;
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;
extern crate kana;
use kana::*;
/// 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"))]
std::thread::sleep(std::time::Duration::from_millis(millis as u64));
std::thread::sleep(duration);
#[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 {