diff --git a/sw/deployer/Cargo.lock b/sw/deployer/Cargo.lock index 20722e6..4939c32 100644 --- a/sw/deployer/Cargo.lock +++ b/sw/deployer/Cargo.lock @@ -281,9 +281,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" +checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" dependencies = [ "cc", "cxxbridge-flags", @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" +checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" dependencies = [ "cc", "codespan-reporting", @@ -308,15 +308,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" +checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" [[package]] name = "cxxbridge-macro" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" +checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" dependencies = [ "proc-macro2", "quote", @@ -538,9 +538,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" +checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" dependencies = [ "libc", "windows-sys", @@ -867,9 +867,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", @@ -877,9 +877,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.2" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -916,9 +916,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.8" +version = "0.36.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ "bitflags", "errno", @@ -961,9 +961,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "serialport" @@ -1067,9 +1067,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-width" @@ -1254,9 +1254,9 @@ checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "zune-inflate" -version = "0.2.50" +version = "0.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589245df6230839c305984dcc0a8385cc72af1fd223f360ffd5d65efa4216d40" +checksum = "a01728b79fb9b7e28a8c11f715e1cd8dc2cda7416a007d66cac55cebb3a8ac6b" dependencies = [ "simd-adler32", ] diff --git a/sw/deployer/src/main.rs b/sw/deployer/src/main.rs index 4e79392..ecd4a72 100644 --- a/sw/deployer/src/main.rs +++ b/sw/deployer/src/main.rs @@ -7,9 +7,10 @@ use clap::{Args, Parser, Subcommand, ValueEnum}; use clap_num::maybe_hex_range; use colored::Colorize; use panic_message::panic_message; +use sc64::ServerEvent; use std::{ fs::File, - io::{stdin, stdout, BufReader, Read, Write}, + io::{stdin, stdout, Read, Write}, path::PathBuf, sync::{ atomic::{AtomicBool, Ordering}, @@ -307,8 +308,7 @@ fn handle_list_command() -> Result<(), sc64::Error> { fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<(), sc64::Error> { let mut sc64 = init_sc64(connection, true)?; - let (rom_file_unbuffered, rom_name, rom_length) = open_file(&args.rom)?; - let mut rom_file = BufReader::new(rom_file_unbuffered); + let (mut rom_file, rom_name, rom_length) = open_file(&args.rom)?; sc64.reset_state()?; @@ -532,7 +532,7 @@ fn handle_firmware_command( let metadata = sc64::firmware::verify(&firmware)?; println!("{}", "Firmware metadata:".bold()); - println!("{}", format!("{}", metadata).blue().to_string()); + println!("{}", format!("{}", metadata).cyan().to_string()); Ok(()) } @@ -551,7 +551,7 @@ fn handle_firmware_command( let metadata = sc64::firmware::verify(&firmware)?; println!("{}", "Firmware metadata:".bold()); - println!("{}", format!("{}", metadata).blue().to_string()); + println!("{}", format!("{}", metadata).cyan().to_string()); backup_file.write_all(&firmware)?; @@ -568,12 +568,17 @@ fn handle_firmware_command( let metadata = sc64::firmware::verify(&firmware)?; println!("{}", "Firmware metadata:".bold()); - println!("{}", format!("{}", metadata).blue().to_string()); + println!("{}", format!("{}", metadata).cyan().to_string()); println!("{}", "Firmware file verification was successful".green()); let answer = prompt(format!("{}", "Continue with update process? [y/N] ".bold())); if answer.to_ascii_lowercase() != "y" { - panic!("Firmware update process aborted"); + println!("{}", "Firmware update process aborted".red()); + return Ok(()); } + println!( + "{}", + "Do not unplug SC64 from the computer, doing so might brick your device".yellow() + ); sc64.reset_state()?; @@ -594,10 +599,26 @@ fn handle_server_command(connection: Connection, args: &ServerArgs) -> Result<() None }; - let _server = sc64::new_server(port, args.address.clone())?; - - let exit = setup_exit_flag(); - while !exit.load(Ordering::Relaxed) {} + sc64::run_server(port, args.address.clone(), |event| match event { + ServerEvent::StartedListening(address) => println!( + "{}: Started listening on address {}", + "[Server]".bold(), + address + ), + ServerEvent::NewConnection(peer) => { + println!("{}: New connection from {}", "[Server]".bold(), peer); + } + ServerEvent::Disconnected(peer) => { + println!("{}: Client {} disconnected", "[Server]".bold(), peer); + } + ServerEvent::Err(error) => { + println!( + "{}: Client disconnected with error: {}", + "[Server]".bold(), + error + ); + } + })?; Ok(()) } diff --git a/sw/deployer/src/sc64/link.rs b/sw/deployer/src/sc64/link.rs index 642e108..136eba2 100644 --- a/sw/deployer/src/sc64/link.rs +++ b/sw/deployer/src/sc64/link.rs @@ -1,15 +1,43 @@ -use super::{error::Error, utils}; -use std::{collections::VecDeque, time::Duration}; +use super::error::Error; +use std::{ + collections::VecDeque, + io::{BufRead, BufReader, BufWriter, ErrorKind, Read, Write}, + net::{TcpListener, TcpStream}, + time::Duration, +}; -pub struct LocalDevice { - pub port: String, - pub serial_number: String, +enum DataType { + Command, + Response, + Packet, } -pub struct Command { +impl From for u32 { + fn from(value: DataType) -> Self { + match value { + DataType::Command => 1, + DataType::Response => 2, + DataType::Packet => 3, + } + } +} + +impl TryFrom for DataType { + type Error = Error; + fn try_from(value: u32) -> Result { + Ok(match value { + 1 => Self::Command, + 2 => Self::Response, + 3 => Self::Packet, + _ => return Err(Error::new("Unknown data type")), + }) + } +} + +pub struct Command<'a> { pub id: u8, pub args: [u32; 2], - pub data: Vec, + pub data: &'a [u8], } pub struct Response { @@ -23,32 +51,20 @@ pub struct Packet { pub data: Vec, } -enum DataType { - Response, - Packet, -} - -pub trait Link { - fn execute_command(&mut self, command: &Command) -> Result, Error>; - fn execute_command_raw( +trait Backend { + fn send_command(&mut self, command: &Command) -> Result<(), Error>; + fn process_incoming_data( &mut self, - command: &Command, - timeout: Duration, - no_response: bool, - ignore_error: bool, - ) -> Result, Error>; - fn receive_packet(&mut self) -> Result, Error>; + data_type: DataType, + packets: &mut VecDeque, + ) -> Result, Error>; } -pub struct SerialLink { +struct SerialBackend { serial: Box, - packets: VecDeque, } -const COMMAND_TIMEOUT: Duration = Duration::from_secs(5); -const PACKET_TIMEOUT: Duration = Duration::from_secs(5); - -impl SerialLink { +impl SerialBackend { fn reset(&mut self) -> Result<(), Error> { const WAIT_DURATION: Duration = Duration::from_millis(10); const RETRY_COUNT: i32 = 100; @@ -78,37 +94,33 @@ impl SerialLink { Ok(()) } +} - fn serial_send_command(&mut self, command: &Command, timeout: Duration) -> Result<(), Error> { - let mut header: Vec = Vec::new(); - header.append(&mut b"CMD".to_vec()); - header.append(&mut [command.id].to_vec()); - header.append(&mut command.args[0].to_be_bytes().to_vec()); - header.append(&mut command.args[1].to_be_bytes().to_vec()); +impl Backend for SerialBackend { + fn send_command(&mut self, command: &Command) -> Result<(), Error> { + self.serial.write_all(b"CMD")?; + self.serial.write_all(&command.id.to_be_bytes())?; + self.serial.write_all(&command.args[0].to_be_bytes())?; + self.serial.write_all(&command.args[1].to_be_bytes())?; - self.serial.set_timeout(timeout)?; - self.serial.write_all(&header)?; self.serial.write_all(&command.data)?; + self.serial.flush()?; Ok(()) } - fn serial_process_incoming_data( + fn process_incoming_data( &mut self, data_type: DataType, - timeout: Duration, + packets: &mut VecDeque, ) -> Result, Error> { - const HEADER_SIZE: u32 = 8; + let mut buffer = [0u8; 4]; - let mut buffer = [0u8; HEADER_SIZE as usize]; - - self.serial.set_timeout(timeout)?; - - while matches!(data_type, DataType::Response) || self.serial.bytes_to_read()? >= HEADER_SIZE + while matches!(data_type, DataType::Response) + || self.serial.bytes_to_read()? as usize >= buffer.len() { self.serial.read_exact(&mut buffer)?; - let (packet_token, error) = (match &buffer[0..3] { b"CMP" => Ok((false, false)), b"PKT" => Ok((true, false)), @@ -116,15 +128,17 @@ impl SerialLink { _ => Err(Error::new("Unknown response token")), })?; let id = buffer[3]; - let length = utils::u32_from_vec(&buffer[4..8])? as usize; + + self.serial.read_exact(&mut buffer)?; + let length = u32::from_be_bytes(buffer) as usize; let mut data = vec![0u8; length]; self.serial.read_exact(&mut data)?; if packet_token { - self.packets.push_back(Packet { id, data }); + packets.push_back(Packet { id, data }); if matches!(data_type, DataType::Packet) { - return Ok(None); + break; } } else { return Ok(Some(Response { id, error, data })); @@ -133,36 +147,158 @@ impl SerialLink { Ok(None) } +} - fn send_command(&mut self, command: &Command) -> Result<(), Error> { - self.serial_send_command(command, COMMAND_TIMEOUT) - } +fn new_serial_backend(port: &str) -> Result { + let mut backend = SerialBackend { + serial: serialport::new(port, 115_200) + .timeout(Duration::from_secs(10)) + .open()?, + }; + backend.reset()?; + Ok(backend) +} - fn receive_response(&mut self, timeout: Duration) -> Result { - if let Some(response) = self.serial_process_incoming_data(DataType::Response, timeout)? { - return Ok(response); - } - Err(Error::new("Command response timeout")) +struct TcpBackend { + stream: TcpStream, + reader: BufReader, + writer: BufWriter, +} + +impl TcpBackend { + fn bytes_to_read(&mut self) -> Result { + self.stream.set_nonblocking(true)?; + let result = self.reader.fill_buf(); + let length = match result { + Ok(buffer) => buffer.len(), + Err(error) => { + if error.kind() == ErrorKind::WouldBlock { + 0 + } else { + return Err(error.into()); + } + } + }; + self.stream.set_nonblocking(false)?; + return Ok(length); } } -impl Link for SerialLink { - fn execute_command(&mut self, command: &Command) -> Result, Error> { - self.execute_command_raw(command, COMMAND_TIMEOUT, false, false) +impl Backend for TcpBackend { + fn send_command(&mut self, command: &Command) -> Result<(), Error> { + let payload_data_type: u32 = DataType::Command.into(); + self.writer.write_all(&payload_data_type.to_be_bytes())?; + + self.writer.write_all(&command.id.to_be_bytes())?; + self.writer.write_all(&command.args[0].to_be_bytes())?; + self.writer.write_all(&command.args[1].to_be_bytes())?; + + let command_data_length = command.data.len() as u32; + self.writer.write_all(&command_data_length.to_be_bytes())?; + self.writer.write_all(&command.data)?; + + self.writer.flush()?; + + Ok(()) } - fn execute_command_raw( + fn process_incoming_data( + &mut self, + data_type: DataType, + packets: &mut VecDeque, + ) -> Result, Error> { + let mut buffer = [0u8; 4]; + + while matches!(data_type, DataType::Response) || self.bytes_to_read()? >= 4 { + self.reader.read_exact(&mut buffer)?; + let payload_data_type: DataType = u32::from_be_bytes(buffer).try_into()?; + + match payload_data_type { + DataType::Response => { + let mut response_info = vec![0u8; 2]; + self.reader.read_exact(&mut response_info)?; + + self.reader.read_exact(&mut buffer)?; + let response_data_length = u32::from_be_bytes(buffer) as usize; + + let mut data = vec![0u8; response_data_length]; + self.reader.read_exact(&mut data)?; + + return Ok(Some(Response { + id: response_info[0], + error: response_info[1] != 0, + data, + })); + } + DataType::Packet => { + let mut packet_info = vec![0u8; 1]; + self.reader.read_exact(&mut packet_info)?; + + self.reader.read_exact(&mut buffer)?; + let packet_data_length = u32::from_be_bytes(buffer) as usize; + + let mut data = vec![0u8; packet_data_length]; + self.reader.read_exact(&mut data)?; + + packets.push_back(Packet { + id: packet_info[0], + data, + }); + if matches!(data_type, DataType::Packet) { + break; + } + } + _ => return Err(Error::new("Unexpected payload data type received")), + }; + } + + Ok(None) + } +} + +fn new_tcp_backend(address: &str) -> Result { + let stream = match TcpStream::connect(address) { + Ok(stream) => { + stream.set_write_timeout(Some(Duration::from_secs(10)))?; + stream.set_read_timeout(Some(Duration::from_secs(10)))?; + stream + } + Err(error) => { + return Err(Error::new( + format!("Couldn't connect to [{address}]: {error}").as_str(), + )) + } + }; + let reader = BufReader::new(stream.try_clone()?); + let writer = BufWriter::new(stream.try_clone()?); + Ok(TcpBackend { + stream, + reader, + writer, + }) +} + +pub struct Link { + backend: Box, + packets: VecDeque, +} + +impl Link { + pub fn execute_command(&mut self, command: &Command) -> Result, Error> { + self.execute_command_raw(command, false, false) + } + + pub fn execute_command_raw( &mut self, command: &Command, - timeout: Duration, no_response: bool, ignore_error: bool, ) -> Result, Error> { - self.send_command(command)?; + self.backend.send_command(command)?; if no_response { return Ok(vec![]); } - let response = self.receive_response(timeout)?; + let response = self.receive_response()?; if command.id != response.id { return Err(Error::new("Command response ID didn't match")); } @@ -172,14 +308,53 @@ impl Link for SerialLink { Ok(response.data) } - fn receive_packet(&mut self) -> Result, Error> { + fn receive_response(&mut self) -> Result { + match self + .backend + .process_incoming_data(DataType::Response, &mut self.packets) + { + Ok(response) => match response { + Some(response) => Ok(response), + None => Err(Error::new("No response was received")), + }, + Err(error) => Err(Error::new( + format!("Command response error: {error}").as_str(), + )), + } + } + + pub fn receive_packet(&mut self) -> Result, Error> { if self.packets.len() == 0 { - self.serial_process_incoming_data(DataType::Packet, PACKET_TIMEOUT)?; + let response = self + .backend + .process_incoming_data(DataType::Packet, &mut self.packets)?; + if response.is_some() { + return Err(Error::new("Unexpected command response in data stream")); + } } Ok(self.packets.pop_front()) } } +pub fn new_local(port: &str) -> Result { + Ok(Link { + backend: Box::new(new_serial_backend(port)?), + packets: VecDeque::new(), + }) +} + +pub fn new_remote(address: &str) -> Result { + Ok(Link { + backend: Box::new(new_tcp_backend(address)?), + packets: VecDeque::new(), + }) +} + +pub struct LocalDevice { + pub port: String, + pub serial_number: String, +} + pub fn list_local_devices() -> Result, Error> { const SC64_VID: u16 = 0x0403; const SC64_PID: u16 = 0x6014; @@ -206,13 +381,109 @@ pub fn list_local_devices() -> Result, Error> { return Ok(serial_devices); } -pub fn new_local(port: &str) -> Result, Error> { - let mut link = SerialLink { - serial: serialport::new(port, 115_200).open()?, - packets: VecDeque::new(), - }; - - link.reset()?; - - Ok(Box::new(link)) +pub enum ServerEvent { + StartedListening(String), + NewConnection(String), + Disconnected(String), + Err(String), +} + +pub fn run_server( + port: &str, + address: String, + event_callback: fn(ServerEvent), +) -> Result<(), Error> { + let listener = TcpListener::bind(address)?; + + event_callback(ServerEvent::StartedListening( + listener.local_addr()?.to_string(), + )); + + for stream in listener.incoming() { + match stream { + Ok(mut stream) => match server_accept_connection(port, event_callback, &mut stream) { + Ok(()) => {} + Err(error) => event_callback(ServerEvent::Err(error.to_string())), + }, + Err(error) => return Err(error.into()), + } + } + + Ok(()) +} + +fn server_accept_connection( + port: &str, + event_callback: fn(ServerEvent), + stream: &mut TcpStream, +) -> Result<(), Error> { + stream.set_nonblocking(true)?; + let peer = stream.peer_addr()?.to_string(); + + let mut serial_backend = new_serial_backend(port)?; + serial_backend.reset()?; + + let mut packets: VecDeque = VecDeque::new(); + + let mut buffer = [0u8; 4]; + + event_callback(ServerEvent::NewConnection(peer.clone())); + + loop { + match stream.read_exact(&mut buffer) { + Ok(()) => { + let data_type: DataType = u32::from_be_bytes(buffer).try_into()?; + + if !matches!(data_type, DataType::Command) { + return Err(Error::new("Received data type wasn't a command data type")); + } + + let mut id_buffer = [0u8; 1]; + let mut args = [0u32; 2]; + + stream.read_exact(&mut id_buffer)?; + stream.read_exact(&mut buffer)?; + args[0] = u32::from_be_bytes(buffer); + stream.read_exact(&mut buffer)?; + args[1] = u32::from_be_bytes(buffer); + + stream.read_exact(&mut buffer)?; + let command_data_length = u32::from_be_bytes(buffer) as usize; + let mut data = vec![0u8; command_data_length]; + stream.read_exact(&mut data)?; + + serial_backend.send_command(&Command { + id: id_buffer[0], + args, + data: &data, + })?; + + continue; + } + Err(error) => { + if error.kind() != ErrorKind::WouldBlock { + event_callback(ServerEvent::Disconnected(peer.clone())); + return Ok(()); + } + } + } + if let Some(response) = + serial_backend.process_incoming_data(DataType::Packet, &mut packets)? + { + stream.write_all(&u32::to_be_bytes(DataType::Response.into()))?; + stream.write_all(&[response.id])?; + stream.write_all(&[response.error as u8])?; + stream.write_all(&(response.data.len() as u32).to_be_bytes())?; + stream.write_all(&response.data)?; + stream.flush()?; + } else if let Some(packet) = packets.pop_front() { + stream.write_all(&u32::to_be_bytes(DataType::Packet.into()))?; + stream.write_all(&[packet.id])?; + stream.write_all(&(packet.data.len() as u32).to_be_bytes())?; + stream.write_all(&packet.data)?; + stream.flush()?; + } else { + std::thread::sleep(Duration::from_millis(1)); + } + } } diff --git a/sw/deployer/src/sc64/mod.rs b/sw/deployer/src/sc64/mod.rs index c14da27..6f39c60 100644 --- a/sw/deployer/src/sc64/mod.rs +++ b/sw/deployer/src/sc64/mod.rs @@ -7,7 +7,7 @@ mod utils; pub use self::{ error::Error, - link::list_local_devices, + link::{list_local_devices, ServerEvent}, types::{ BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode, DebugPacket, DiskPacket, FpgaDebugData, McuStackUsage, SaveType, Switch, TvType, @@ -20,7 +20,7 @@ use self::{ types::{ get_config, get_setting, Config, ConfigId, FirmwareStatus, Setting, SettingId, UpdateStatus, }, - utils::{args_from_vec, datetime_from_vec, u32_from_vec, vec_from_datetime}, + utils::{convert_from_datetime, convert_to_datetime}, }; use chrono::{DateTime, Local}; use std::{ @@ -30,7 +30,7 @@ use std::{ }; pub struct SC64 { - link: Box, + link: Link, } pub struct DeviceState { @@ -86,11 +86,8 @@ const FLASHRAM_LENGTH: usize = 128 * 1024; const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000; const FIRMWARE_ADDRESS: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory -const FIRMWARE_COMMAND_TIMEOUT: Duration = Duration::from_secs(30); const FIRMWARE_UPDATE_TIMEOUT: Duration = Duration::from_secs(90); -const USB_WRITE_COMMAND_TIMEOUT: Duration = Duration::from_secs(5); - const ISV_BUFFER_LENGTH: usize = 64 * 1024; pub const MEMORY_LENGTH: usize = 0x0500_2980; @@ -98,23 +95,33 @@ pub const MEMORY_LENGTH: usize = 0x0500_2980; const MEMORY_CHUNK_LENGTH: usize = 1 * 1024 * 1024; impl SC64 { - fn command_identifier_get(&mut self) -> Result, Error> { - let identifier = self.link.execute_command(&Command { + fn command_identifier_get(&mut self) -> Result<[u8; 4], Error> { + let data = self.link.execute_command(&Command { id: b'v', args: [0, 0], - data: vec![], + data: &[], })?; - Ok(identifier) + if data.len() != 4 { + return Err(Error::new( + "Invalid data length received for identifier get command", + )); + } + Ok(data[0..4].try_into().unwrap()) } fn command_version_get(&mut self) -> Result<(u16, u16), Error> { - let version = self.link.execute_command(&Command { + let data = self.link.execute_command(&Command { id: b'V', args: [0, 0], - data: vec![], + data: &[], })?; - let major = utils::u16_from_vec(&version[0..2])?; - let minor = utils::u16_from_vec(&version[2..4])?; + if data.len() != 4 { + return Err(Error::new( + "Invalid data length received for version get command", + )); + } + let major = u16::from_be_bytes(data[0..2].try_into().unwrap()); + let minor = u16::from_be_bytes(data[2..4].try_into().unwrap()); Ok((major, minor)) } @@ -122,7 +129,7 @@ impl SC64 { self.link.execute_command(&Command { id: b'R', args: [0, 0], - data: vec![], + data: &[], })?; Ok(()) } @@ -133,13 +140,14 @@ impl SC64 { seed: u8, checksum: &[u8; 6], ) -> Result<(), Error> { - let mut params: Vec = vec![]; - params.append(&mut [(disable as u8) << 0, seed].to_vec()); - params.append(&mut checksum.to_vec()); + let args = [ + u32::from_be_bytes([(disable as u8) << 0, seed, checksum[0], checksum[1]]), + u32::from_be_bytes([checksum[2], checksum[3], checksum[4], checksum[5]]), + ]; self.link.execute_command(&Command { id: b'B', - args: args_from_vec(¶ms[0..8])?, - data: vec![], + args, + data: &[], })?; Ok(()) } @@ -148,9 +156,14 @@ impl SC64 { let data = self.link.execute_command(&Command { id: b'c', args: [config_id.into(), 0], - data: vec![], + data: &[], })?; - let value = u32_from_vec(&data[0..4])?; + if data.len() != 4 { + return Err(Error::new( + "Invalid data length received for config get command", + )); + } + let value = u32::from_be_bytes(data[0..4].try_into().unwrap()); Ok((config_id, value).try_into()?) } @@ -158,7 +171,7 @@ impl SC64 { self.link.execute_command(&Command { id: b'C', args: config.into(), - data: vec![], + data: &[], })?; Ok(()) } @@ -167,9 +180,14 @@ impl SC64 { let data = self.link.execute_command(&Command { id: b'a', args: [setting_id.into(), 0], - data: vec![], + data: &[], })?; - let value = u32_from_vec(&data[0..4])?; + if data.len() != 4 { + return Err(Error::new( + "Invalid data length received for setting get command", + )); + } + let value = u32::from_be_bytes(data[0..4].try_into().unwrap()); Ok((setting_id, value).try_into()?) } @@ -177,7 +195,7 @@ impl SC64 { self.link.execute_command(&Command { id: b'A', args: setting.into(), - data: vec![], + data: &[], })?; Ok(()) } @@ -186,16 +204,21 @@ impl SC64 { let data = self.link.execute_command(&Command { id: b't', args: [0, 0], - data: vec![], + data: &[], })?; - Ok(datetime_from_vec(&data[0..8])?) + if data.len() != 8 { + return Err(Error::new( + "Invalid data length received for time get command", + )); + } + Ok(convert_to_datetime(&data[0..8].try_into().unwrap())?) } fn command_time_set(&mut self, datetime: DateTime) -> Result<(), Error> { self.link.execute_command(&Command { id: b'T', - args: args_from_vec(&vec_from_datetime(datetime)?[0..8])?, - data: vec![], + args: convert_from_datetime(datetime), + data: &[], })?; Ok(()) } @@ -204,8 +227,13 @@ impl SC64 { let data = self.link.execute_command(&Command { id: b'm', args: [address, length as u32], - data: vec![], + data: &[], })?; + if data.len() != length { + return Err(Error::new( + "Invalid data length received for memory read command", + )); + } Ok(data) } @@ -213,7 +241,7 @@ impl SC64 { self.link.execute_command(&Command { id: b'M', args: [address, data.len() as u32], - data: data.to_vec(), + data, })?; Ok(()) } @@ -223,9 +251,8 @@ impl SC64 { &Command { id: b'U', args: [datatype as u32, data.len() as u32], - data: data.to_vec(), + data, }, - USB_WRITE_COMMAND_TIMEOUT, true, false, )?; @@ -236,25 +263,31 @@ impl SC64 { self.link.execute_command(&Command { id: b'D', args: [error as u32, 0], - data: vec![], + data: &[], })?; Ok(()) } fn command_flash_wait_busy(&mut self, wait: bool) -> Result { - let erase_block_size = self.link.execute_command(&Command { + let data = self.link.execute_command(&Command { id: b'p', args: [wait as u32, 0], - data: vec![], + data: &[], })?; - Ok(utils::u32_from_vec(&erase_block_size[0..4])?) + if data.len() != 4 { + return Err(Error::new( + "Invalid data length received for flash wait busy command", + )); + } + let erase_block_size = u32::from_be_bytes(data[0..4].try_into().unwrap()); + Ok(erase_block_size) } fn command_flash_erase_block(&mut self, address: u32) -> Result<(), Error> { self.link.execute_command(&Command { id: b'P', args: [address, 0], - data: vec![], + data: &[], })?; Ok(()) } @@ -264,15 +297,19 @@ impl SC64 { &Command { id: b'f', args: [address, 0], - data: vec![], + data: &[], }, - FIRMWARE_COMMAND_TIMEOUT, false, true, )?; - let status = FirmwareStatus::try_from(utils::u32_from_vec(&data[0..4])?)?; - let length = utils::u32_from_vec(&data[4..8])?; - Ok((status, length)) + if data.len() != 8 { + return Err(Error::new( + "Invalid data length received for firmware backup command", + )); + } + let status = u32::from_be_bytes(data[0..4].try_into().unwrap()); + let length = u32::from_be_bytes(data[4..8].try_into().unwrap()); + Ok((status.try_into()?, length)) } fn command_firmware_update( @@ -284,33 +321,35 @@ impl SC64 { &Command { id: b'F', args: [address, length as u32], - data: vec![], + data: &[], }, - FIRMWARE_COMMAND_TIMEOUT, false, true, )?; - Ok(FirmwareStatus::try_from(utils::u32_from_vec(&data[0..4])?)?) + if data.len() != 4 { + return Err(Error::new( + "Invalid data length received for firmware update command", + )); + } + Ok(u32::from_be_bytes(data[0..4].try_into().unwrap()).try_into()?) } fn command_debug_get(&mut self) -> Result { - self.link - .execute_command(&Command { - id: b'?', - args: [0, 0], - data: vec![], - })? - .try_into() + let data = self.link.execute_command(&Command { + id: b'?', + args: [0, 0], + data: &[], + })?; + Ok(data.try_into()?) } fn command_stack_usage_get(&mut self) -> Result { - self.link - .execute_command(&Command { - id: b'%', - args: [0, 0], - data: vec![], - })? - .try_into() + let data = self.link.execute_command(&Command { + id: b'%', + args: [0, 0], + data: &[], + })?; + Ok(data.try_into()?) } } @@ -564,6 +603,16 @@ impl SC64 { self.command_usb_write(debug_packet.datatype, &debug_packet.data) } + pub fn check_device(&mut self) -> Result<(), Error> { + let identifier = self.command_identifier_get().map_err(|e| { + Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str()) + })?; + if &identifier != SC64_V2_IDENTIFIER { + return Err(Error::new("Unknown identifier received, not a SC64 device")); + } + Ok(()) + } + pub fn check_firmware_version(&mut self) -> Result<(u16, u16), Error> { let (major, minor) = self .command_version_get() @@ -626,8 +675,9 @@ impl SC64 { if timeout.elapsed() > FIRMWARE_UPDATE_TIMEOUT { return Err(Error::new( format!( - "Firmware update timeout, SC64 did not finish update in {} seconds", - FIRMWARE_UPDATE_TIMEOUT.as_secs() + "Firmware update timeout, SC64 did not finish update in {} seconds, last step: {}", + FIRMWARE_UPDATE_TIMEOUT.as_secs(), + last_update_status ) .as_str(), )); @@ -706,28 +756,30 @@ pub fn new_local(port: Option) -> Result { } else { list_local_devices()?[0].port.clone() }; - let mut sc64 = SC64 { link: link::new_local(&port)?, }; - - let identifier = sc64 - .command_identifier_get() - .map_err(|_| Error::new("Couldn't get SC64 device identifier"))?; - - if identifier != SC64_V2_IDENTIFIER { - return Err(Error::new("Unknown identifier received, not a SC64 device")); - } - + sc64.check_device()?; Ok(sc64) } -pub fn new_remote(remote: String) -> Result { - let _ = remote; - Err(Error::new("Remote connection not implemented yet")) +pub fn new_remote(address: String) -> Result { + let mut sc64 = SC64 { + link: link::new_remote(&address)?, + }; + sc64.check_device()?; + Ok(sc64) } -pub fn new_server(port: Option, bind: String) -> Result<(), Error> { - let _ = (port, bind); - Err(Error::new("SC64 server not implemented yet")) +pub fn run_server( + port: Option, + address: String, + event_callback: fn(ServerEvent), +) -> Result<(), Error> { + let port = if let Some(port) = port { + port + } else { + list_local_devices()?[0].port.clone() + }; + link::run_server(&port, address, event_callback) } diff --git a/sw/deployer/src/sc64/types.rs b/sw/deployer/src/sc64/types.rs index 158e87d..0877aa3 100644 --- a/sw/deployer/src/sc64/types.rs +++ b/sw/deployer/src/sc64/types.rs @@ -1,4 +1,4 @@ -use super::{link::Packet, utils::u32_from_vec, Error}; +use super::{link::Packet, Error}; use encoding_rs::EUC_JP; use std::fmt::Display; @@ -591,7 +591,16 @@ impl TryFrom for DataPacket { b'U' => Self::Debug(value.data.try_into()?), b'D' => Self::Disk(value.data.try_into()?), b'I' => Self::IsViewer(EUC_JP.decode(&value.data).0.into()), - b'F' => Self::UpdateStatus(u32_from_vec(&value.data[0..4])?.try_into()?), + b'F' => { + if value.data.len() != 4 { + return Err(Error::new( + "Incorrect data length for update status data packet", + )); + } + Self::UpdateStatus( + u32::from_be_bytes(value.data[0..4].try_into().unwrap()).try_into()?, + ) + } _ => return Err(Error::new("Unknown data packet code")), }) } @@ -608,7 +617,7 @@ impl TryFrom> for DebugPacket { if value.len() < 4 { return Err(Error::new("Couldn't extract header from debug packet")); } - let header = u32_from_vec(&value[0..4])?; + let header = u32::from_be_bytes(value[0..4].try_into().unwrap()); let datatype = ((header >> 24) & 0xFF) as u8; let length = (header & 0x00FFFFFF) as usize; let data = value[4..].to_vec(); @@ -630,9 +639,9 @@ impl TryFrom> for DiskPacket { if value.len() < 12 { return Err(Error::new("Couldn't extract block info from disk packet")); } - let command = u32_from_vec(&value[0..4])?; - let address = u32_from_vec(&value[4..8])?; - let track_head_block = u32_from_vec(&value[8..12])?; + let command = u32::from_be_bytes(value[0..4].try_into().unwrap()); + let address = u32::from_be_bytes(value[4..8].try_into().unwrap()); + let track_head_block = u32::from_be_bytes(value[8..12].try_into().unwrap()); let disk_block = DiskBlock { address, track: (track_head_block >> 2) & 0xFFF, @@ -678,7 +687,7 @@ impl Display for FirmwareStatus { FirmwareStatus::Ok => "OK", FirmwareStatus::ErrToken => "Invalid firmware header", FirmwareStatus::ErrChecksum => "Invalid chunk checksum", - FirmwareStatus::ErrSize => "Invalid firmware size", + FirmwareStatus::ErrSize => "Invalid chunk size", FirmwareStatus::ErrUnknownChunk => "Unknown chunk in firmware", FirmwareStatus::ErrRead => "Firmware read error", FirmwareStatus::ErrAddress => "Invalid address or length provided", @@ -747,11 +756,11 @@ pub struct FpgaDebugData { impl TryFrom> for FpgaDebugData { type Error = Error; fn try_from(value: Vec) -> Result { - if value.len() < 8 { + if value.len() != 8 { return Err(Error::new("Invalid data length for FPGA debug data")); } Ok(FpgaDebugData { - last_pi_address: u32_from_vec(&value[0..4])?, + last_pi_address: u32::from_be_bytes(value[0..4].try_into().unwrap()), read_fifo_wait: (value[7] & (1 << 0)) != 0, read_fifo_failure: (value[7] & (1 << 1)) != 0, write_fifo_wait: (value[7] & (1 << 2)) != 0, @@ -789,14 +798,14 @@ pub struct McuStackUsage { impl TryFrom> for McuStackUsage { type Error = Error; fn try_from(value: Vec) -> Result { - if value.len() < 16 { + if value.len() != 16 { return Err(Error::new("Invalid data length for MCU stack usage")); } Ok(McuStackUsage { - cic: u32_from_vec(&value[0..4])?, - rtc: u32_from_vec(&value[4..8])?, - led: u32_from_vec(&value[8..12])?, - gvr: u32_from_vec(&value[12..16])?, + cic: u32::from_be_bytes(value[0..4].try_into().unwrap()), + rtc: u32::from_be_bytes(value[4..8].try_into().unwrap()), + led: u32::from_be_bytes(value[8..12].try_into().unwrap()), + gvr: u32::from_be_bytes(value[12..16].try_into().unwrap()), }) } } diff --git a/sw/deployer/src/sc64/utils.rs b/sw/deployer/src/sc64/utils.rs index d3f152b..5e2c36f 100644 --- a/sw/deployer/src/sc64/utils.rs +++ b/sw/deployer/src/sc64/utils.rs @@ -1,33 +1,6 @@ use super::Error; use chrono::{DateTime, Datelike, Local, NaiveDateTime, TimeZone, Timelike}; -pub fn u16_from_vec(data: &[u8]) -> Result { - if data.len() != 2 { - return Err(Error::new("Invalid slice length provided to u16_from_vec")); - } - let bytes = data[0..2] - .try_into() - .map_err(|_| Error::new("Couldn't convert from bytes to u16"))?; - Ok(u16::from_be_bytes(bytes)) -} - -pub fn u32_from_vec(data: &[u8]) -> Result { - if data.len() != 4 { - return Err(Error::new("Invalid slice length provided to u32_from_vec")); - } - let bytes = data[0..4] - .try_into() - .map_err(|_| Error::new("Couldn't convert from bytes to u32"))?; - Ok(u32::from_be_bytes(bytes)) -} - -pub fn args_from_vec(data: &[u8]) -> Result<[u32; 2], Error> { - if data.len() != 8 { - return Err(Error::new("Invalid slice length provided to args_from_vec")); - } - Ok([u32_from_vec(&data[0..4])?, u32_from_vec(&data[4..8])?]) -} - pub fn u8_from_bcd(value: u8) -> u8 { (((value & 0xF0) >> 4) * 10) + (value & 0x0F) } @@ -36,7 +9,7 @@ pub fn bcd_from_u8(value: u8) -> u8 { (((value / 10) & 0x0F) << 4) | ((value % 10) & 0x0F) } -pub fn datetime_from_vec(data: &[u8]) -> Result, Error> { +pub fn convert_to_datetime(data: &[u8; 8]) -> Result, Error> { let hour = u8_from_bcd(data[1]); let minute = u8_from_bcd(data[2]); let second = u8_from_bcd(data[3]); @@ -51,7 +24,7 @@ pub fn datetime_from_vec(data: &[u8]) -> Result, Error> { Ok(Local.from_local_datetime(native).unwrap()) } -pub fn vec_from_datetime(datetime: DateTime) -> Result, Error> { +pub fn convert_from_datetime(datetime: DateTime) -> [u32; 2] { let weekday = bcd_from_u8((datetime.weekday() as u8) + 1); let hour = bcd_from_u8(datetime.hour() as u8); let minute = bcd_from_u8(datetime.minute() as u8); @@ -59,5 +32,8 @@ pub fn vec_from_datetime(datetime: DateTime) -> Result, Error> { let year = bcd_from_u8((datetime.year() - 2000) as u8); let month = bcd_from_u8(datetime.month() as u8); let day = bcd_from_u8(datetime.day() as u8); - Ok(vec![weekday, hour, minute, second, 0, year, month, day]) + [ + u32::from_be_bytes([weekday, hour, minute, second]), + u32::from_be_bytes([0, year, month, day]), + ] }