Compare commits

...

7 commits

Author SHA1 Message Date
8b193bd16c Fixed track_group_list to be consistent with netmd-js 2024-08-15 01:22:16 -05:00
4fa7b0ec30 Version bump, 0.1.3 2024-08-15 01:09:56 -05:00
ac29c6a0a5 Cleaned up clippy suggestions 2024-08-15 01:07:53 -05:00
c49a86ed75 Fixed documentation example 2024-08-15 00:49:02 -05:00
G2
391d0c8599
WASM track upload (#7)
* Initial working WASM encryption

* Improved encryption implementation
2024-08-15 00:46:12 -05:00
885e7508fd Updated cross-usb to v0.4.0 2024-08-14 20:49:04 -05:00
0a75b6a013 Fixed imports 2024-08-14 13:39:03 -05:00
5 changed files with 180 additions and 56 deletions

View file

@ -1,6 +1,6 @@
[package] [package]
name = "minidisc" name = "minidisc"
version = "0.1.2" version = "0.1.3"
edition = "2021" edition = "2021"
homepage = "https://github.com/G2-Games/minidisc-rs/" homepage = "https://github.com/G2-Games/minidisc-rs/"
repository = "https://github.com/G2-Games/minidisc-rs/" repository = "https://github.com/G2-Games/minidisc-rs/"
@ -37,7 +37,7 @@ nofmt = "1.0.0"
once_cell = "1.18.0" once_cell = "1.18.0"
unicode-normalization = "0.1.22" unicode-normalization = "0.1.22"
regex = "1.10" regex = "1.10"
cross_usb = "0.3" cross_usb = "0.4"
num-derive = "0.4.2" num-derive = "0.4.2"
num-traits = "0.2.14" num-traits = "0.2.14"
rand = "0.8.5" rand = "0.8.5"

View file

@ -6,7 +6,7 @@ use thiserror::Error;
// USB stuff // USB stuff
use cross_usb::prelude::*; use cross_usb::prelude::*;
use cross_usb::usb::{ControlIn, ControlOut, ControlType, Recipient, UsbError}; use cross_usb::usb::{ControlIn, ControlOut, ControlType, Recipient, Error};
use cross_usb::{DeviceInfo, Interface}; use cross_usb::{DeviceInfo, Interface};
use super::utils::cross_sleep; use super::utils::cross_sleep;
@ -114,7 +114,7 @@ pub enum NetMDError {
UnknownDevice(DeviceId), UnknownDevice(DeviceId),
#[error("usb connection error")] #[error("usb connection error")]
UsbError(#[from] UsbError), UsbError(#[from] Error),
} }
/// A low-level USB connection to a NetMD device. /// A low-level USB connection to a NetMD device.

View file

@ -549,7 +549,6 @@ impl NetMDContext {
/// # tokio_test::block_on(async { /// # tokio_test::block_on(async {
/// use minidisc::netmd::DEVICE_IDS_CROSSUSB; /// use minidisc::netmd::DEVICE_IDS_CROSSUSB;
/// use minidisc::netmd::NetMDContext; /// use minidisc::netmd::NetMDContext;
/// use minidisc::netmd::encryption::new_thread_encryptor;
/// use minidisc::netmd::interface::{MDTrack, NetMDInterface}; /// use minidisc::netmd::interface::{MDTrack, NetMDInterface};
/// ///
/// // Get the minidisc device from cross_usb /// // Get the minidisc device from cross_usb
@ -571,7 +570,6 @@ impl NetMDContext {
/// format: minidisc::netmd::interface::WireFormat::LP2, /// format: minidisc::netmd::interface::WireFormat::LP2,
/// full_width_title: None, /// full_width_title: None,
/// data: track_contents, /// data: track_contents,
/// encrypt_packets_iterator: Box::new(new_thread_encryptor),
/// }; /// };
/// ///
/// // Download it to the player! /// // Download it to the player!

View file

@ -9,11 +9,27 @@ use super::interface::DataEncryptorInput;
type DesEcbEnc = ecb::Decryptor<des::Des>; type DesEcbEnc = ecb::Decryptor<des::Des>;
type DesCbcEnc = cbc::Encryptor<des::Des>; type DesCbcEnc = cbc::Encryptor<des::Des>;
pub fn new_thread_encryptor( pub struct Encryptor {
_input: DataEncryptorInput, #[allow(clippy::type_complexity)]
) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> { channel: Option<UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>>,
state: Option<EncryptorState>,
}
struct EncryptorState {
input_data: Vec<u8>,
iv: [u8; 8],
random_key: [u8; 8],
encrypted_random_key: [u8; 8],
default_chunk_size: usize,
current_chunk_size: usize,
offset: usize,
packet_count: usize,
closed: bool,
}
impl Encryptor {
pub fn new_threaded(input: DataEncryptorInput) -> Self {
let (tx, rx) = unbounded_channel::<(Vec<u8>, Vec<u8>, Vec<u8>)>(); let (tx, rx) = unbounded_channel::<(Vec<u8>, Vec<u8>, Vec<u8>)>();
let input = Box::from(_input);
thread::spawn(move || { thread::spawn(move || {
let mut iv = [0u8; 8]; let mut iv = [0u8; 8];
@ -74,5 +90,108 @@ pub fn new_thread_encryptor(
} }
}); });
rx Self {
channel: Some(rx),
state: None
}
}
pub fn new(input: DataEncryptorInput) -> Self {
let iv = [0u8; 8];
// Create the random key
let mut random_key = [0u8; 8];
rand::thread_rng().fill_bytes(&mut random_key);
// Encrypt it with the kek
let mut encrypted_random_key = random_key;
if let Err(x) = DesEcbEnc::new(&input.kek.into())
.decrypt_padded_mut::<NoPadding>(&mut encrypted_random_key)
{
panic!("Cannot create main key {:?}", x)
};
let default_chunk_size = match input.chunk_size {
0 => 0x00100000,
e => e,
};
let packet_count = 0;
let current_chunk_size = 0;
let mut input_data = input.data.clone();
if (input_data.len() % input.frame_size) != 0 {
let padding_remaining = input.frame_size - (input_data.len() % input.frame_size);
input_data.extend(std::iter::repeat(0).take(padding_remaining));
}
let offset: usize = 0;
Encryptor {
channel: None,
state: Some(EncryptorState {
input_data,
iv,
random_key,
encrypted_random_key,
current_chunk_size,
offset,
default_chunk_size,
packet_count,
closed: false,
})
}
}
/// Get the next encrypted value
pub async fn next(&mut self) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
let output;
if let Some(state) = self.state.as_mut() {
if state.closed {
return None
}
if state.packet_count > 0 {
state.current_chunk_size = state.default_chunk_size;
} else {
state.current_chunk_size = state.default_chunk_size - 24;
}
state.current_chunk_size = std::cmp::min(state.current_chunk_size, state.input_data.len() - state.offset);
let this_data_chunk = &mut state.input_data[state.offset..state.offset + state.current_chunk_size];
DesCbcEnc::new(&state.random_key.into(), &state.iv.into())
.encrypt_padded_mut::<NoPadding>(this_data_chunk, state.current_chunk_size)
.unwrap();
output = Some((
state.encrypted_random_key.to_vec(),
state.iv.to_vec(),
this_data_chunk.to_vec(),
));
state.iv.copy_from_slice(&this_data_chunk[this_data_chunk.len() - 8..]);
state.packet_count += 1;
state.offset += state.current_chunk_size;
} else if let Some(channel) = self.channel.as_mut() {
output = channel.recv().await
} else {
unreachable!("If you got here, this is bad!");
}
output
}
/// Call close to return none from subsequent calls
pub fn close(&mut self) {
if let Some(state) = self.state.as_mut() {
state.closed = true;
} else if let Some(channel) = self.channel.as_mut() {
channel.close()
} else {
unreachable!("If you got here, this is bad!");
}
}
} }

View file

@ -15,9 +15,9 @@ use std::collections::HashMap;
use std::error::Error; use std::error::Error;
use std::time::Duration; use std::time::Duration;
use thiserror::Error; use thiserror::Error;
use tokio::sync::mpsc::UnboundedReceiver;
use super::base::NetMD; use super::base::NetMD;
use super::encryption::Encryptor;
use super::utils::{cross_sleep, to_sjis}; use super::utils::{cross_sleep, to_sjis};
/// An action to take on the player /// An action to take on the player
@ -306,7 +306,10 @@ pub struct NetMDInterface {
#[allow(dead_code)] #[allow(dead_code)]
impl NetMDInterface { impl NetMDInterface {
/// The maximum number of times to retry after an interim response
const MAX_INTERIM_READ_ATTEMPTS: u8 = 4; const MAX_INTERIM_READ_ATTEMPTS: u8 = 4;
/// The amount of time to wait after an interim response (in milliseconds)
const INTERIM_RESPONSE_RETRY_INTERVAL: u32 = 100; const INTERIM_RESPONSE_RETRY_INTERVAL: u32 = 100;
/// Get a new interface to a NetMD device /// Get a new interface to a NetMD device
@ -452,7 +455,7 @@ impl NetMDInterface {
/// Send a query to the NetMD player /// Send a query to the NetMD player
async fn send_query( async fn send_query(
&mut self, &mut self,
query: &Vec<u8>, query: &[u8],
test: bool, test: bool,
accept_interim: bool, accept_interim: bool,
) -> Result<Vec<u8>, InterfaceError> { ) -> Result<Vec<u8>, InterfaceError> {
@ -465,7 +468,7 @@ impl NetMDInterface {
async fn send_command( async fn send_command(
&mut self, &mut self,
query: &Vec<u8>, query: &[u8],
test: bool, test: bool,
) -> Result<(), InterfaceError> { ) -> Result<(), InterfaceError> {
let status_byte = match test { let status_byte = match test {
@ -476,7 +479,7 @@ impl NetMDInterface {
let mut new_query = Vec::new(); let mut new_query = Vec::new();
new_query.push(status_byte as u8); new_query.push(status_byte as u8);
new_query.extend_from_slice(&query); new_query.extend_from_slice(query);
self.device.send_command(new_query).await?; self.device.send_command(new_query).await?;
@ -618,8 +621,6 @@ impl NetMDInterface {
pub async fn disc_present(&mut self) -> Result<bool, InterfaceError> { pub async fn disc_present(&mut self) -> Result<bool, InterfaceError> {
let status = self.status().await?; let status = self.status().await?;
println!("{:X?}", status);
Ok(status[4] == 0x40) Ok(status[4] == 0x40)
} }
@ -649,11 +650,9 @@ impl NetMDInterface {
self.change_descriptor_state(&Descriptor::OperatingStatusBlock, &DescriptorAction::Close) self.change_descriptor_state(&Descriptor::OperatingStatusBlock, &DescriptorAction::Close)
.await?; .await?;
if operating_status.len() < 2 { if operating_status.len() < 2 && !operating_status.is_empty() {
if !operating_status.is_empty() {
return Err(InterfaceError::InvalidStatus(StatusError(operating_status[0] as u16))); return Err(InterfaceError::InvalidStatus(StatusError(operating_status[0] as u16)));
} }
}
let operating_status_number = let operating_status_number =
(operating_status[0] as u16) << 8 | operating_status[1] as u16; (operating_status[0] as u16) << 8 | operating_status[1] as u16;
@ -1041,11 +1040,13 @@ impl NetMDInterface {
)); ));
} }
let mut remaining_tracks = Vec::new();
for i in 0..track_count { for i in 0..track_count {
if !track_dict.contains_key(&i) { if !track_dict.contains_key(&i) {
result.insert(0, (None, None, Vec::from([i]))) remaining_tracks.push(i);
} }
} }
result.insert(0, (None, None, remaining_tracks));
Ok(result) Ok(result)
} }
@ -1689,8 +1690,7 @@ impl NetMDInterface {
discformat: u8, discformat: u8,
frames: u32, frames: u32,
pkt_size: u32, pkt_size: u32,
// key, iv, data mut packets: Encryptor,
mut packets: UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>,
hex_session_key: &[u8], hex_session_key: &[u8],
progress_callback: F, progress_callback: F,
) -> Result<(u16, Vec<u8>, Vec<u8>), InterfaceError> ) -> Result<(u16, Vec<u8>, Vec<u8>), InterfaceError>
@ -1729,7 +1729,7 @@ impl NetMDInterface {
let mut written_bytes = 0; let mut written_bytes = 0;
let mut packet_count = 0; let mut packet_count = 0;
while let Some((key, iv, data)) = packets.recv().await { while let Some((key, iv, data)) = packets.next().await {
let binpack = if packet_count == 0 { let binpack = if packet_count == 0 {
let packed_length: Vec<u8> = pkt_size.to_be_bytes().to_vec(); let packed_length: Vec<u8> = pkt_size.to_be_bytes().to_vec();
[vec![0, 0, 0, 0], packed_length, key, iv, data].concat() [vec![0, 0, 0, 0], packed_length, key, iv, data].concat()
@ -1862,10 +1862,6 @@ pub struct MDTrack {
pub data: Vec<u8>, pub data: Vec<u8>,
pub chunk_size: usize, pub chunk_size: usize,
pub full_width_title: Option<String>, pub full_width_title: Option<String>,
#[allow(clippy::type_complexity)]
pub encrypt_packets_iterator:
Box<dyn Fn(DataEncryptorInput) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)>>,
} }
pub struct DataEncryptorInput { pub struct DataEncryptorInput {
@ -1920,8 +1916,19 @@ impl MDTrack {
[0x14, 0xe3, 0x83, 0x4e, 0xe2, 0xd3, 0xcc, 0xa5] [0x14, 0xe3, 0x83, 0x4e, 0xe2, 0xd3, 0xcc, 0xa5]
} }
pub fn get_encrypting_iterator(&mut self) -> UnboundedReceiver<(Vec<u8>, Vec<u8>, Vec<u8>)> { #[cfg(not(target_family = "wasm"))]
(self.encrypt_packets_iterator)(DataEncryptorInput { pub fn get_encrypting_iterator(&mut self) -> Encryptor {
Encryptor::new_threaded(DataEncryptorInput {
kek: self.get_kek(),
frame_size: self.frame_size(),
chunk_size: self.chunk_size(),
data: std::mem::take(&mut self.data),
})
}
#[cfg(target_family = "wasm")]
pub fn get_encrypting_iterator(&mut self) -> Encryptor {
Encryptor::new(DataEncryptorInput {
kek: self.get_kek(), kek: self.get_kek(),
frame_size: self.frame_size(), frame_size: self.frame_size(),
chunk_size: self.chunk_size(), chunk_size: self.chunk_size(),