deployer remote connection done

This commit is contained in:
Mateusz Faderewski 2023-03-08 16:40:47 +01:00
parent 441633d655
commit eadb7f9f92
6 changed files with 558 additions and 229 deletions

44
sw/deployer/Cargo.lock generated
View File

@ -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",
]

View File

@ -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(())
}

View File

@ -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<DataType> for u32 {
fn from(value: DataType) -> Self {
match value {
DataType::Command => 1,
DataType::Response => 2,
DataType::Packet => 3,
}
}
}
impl TryFrom<u32> for DataType {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
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<u8>,
pub data: &'a [u8],
}
pub struct Response {
@ -23,32 +51,20 @@ pub struct Packet {
pub data: Vec<u8>,
}
enum DataType {
Response,
Packet,
}
pub trait Link {
fn execute_command(&mut self, command: &Command) -> Result<Vec<u8>, 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<Vec<u8>, Error>;
fn receive_packet(&mut self) -> Result<Option<Packet>, Error>;
data_type: DataType,
packets: &mut VecDeque<Packet>,
) -> Result<Option<Response>, Error>;
}
pub struct SerialLink {
struct SerialBackend {
serial: Box<dyn serialport::SerialPort>,
packets: VecDeque<Packet>,
}
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<u8> = 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<Packet>,
) -> Result<Option<Response>, 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 new_serial_backend(port: &str) -> Result<SerialBackend, Error> {
let mut backend = SerialBackend {
serial: serialport::new(port, 115_200)
.timeout(Duration::from_secs(10))
.open()?,
};
backend.reset()?;
Ok(backend)
}
struct TcpBackend {
stream: TcpStream,
reader: BufReader<TcpStream>,
writer: BufWriter<TcpStream>,
}
impl TcpBackend {
fn bytes_to_read(&mut self) -> Result<usize, Error> {
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 Backend for TcpBackend {
fn send_command(&mut self, command: &Command) -> Result<(), Error> {
self.serial_send_command(command, COMMAND_TIMEOUT)
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 receive_response(&mut self, timeout: Duration) -> Result<Response, Error> {
if let Some(response) = self.serial_process_incoming_data(DataType::Response, timeout)? {
return Ok(response);
fn process_incoming_data(
&mut self,
data_type: DataType,
packets: &mut VecDeque<Packet>,
) -> Result<Option<Response>, 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,
}));
}
Err(Error::new("Command response timeout"))
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)
}
}
impl Link for SerialLink {
fn execute_command(&mut self, command: &Command) -> Result<Vec<u8>, Error> {
self.execute_command_raw(command, COMMAND_TIMEOUT, false, false)
fn new_tcp_backend(address: &str) -> Result<TcpBackend, Error> {
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,
})
}
fn execute_command_raw(
pub struct Link {
backend: Box<dyn Backend>,
packets: VecDeque<Packet>,
}
impl Link {
pub fn execute_command(&mut self, command: &Command) -> Result<Vec<u8>, 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<Vec<u8>, 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<Option<Packet>, Error> {
fn receive_response(&mut self) -> Result<Response, Error> {
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<Option<Packet>, 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<Link, Error> {
Ok(Link {
backend: Box::new(new_serial_backend(port)?),
packets: VecDeque::new(),
})
}
pub fn new_remote(address: &str) -> Result<Link, Error> {
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<Vec<LocalDevice>, Error> {
const SC64_VID: u16 = 0x0403;
const SC64_PID: u16 = 0x6014;
@ -206,13 +381,109 @@ pub fn list_local_devices() -> Result<Vec<LocalDevice>, Error> {
return Ok(serial_devices);
}
pub fn new_local(port: &str) -> Result<Box<dyn Link>, 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<Packet> = 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));
}
}
}

View File

@ -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<dyn Link>,
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<Vec<u8>, 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<u8> = 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(&params[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<Local>) -> 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<u32, Error> {
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<FpgaDebugData, Error> {
self.link
.execute_command(&Command {
let data = self.link.execute_command(&Command {
id: b'?',
args: [0, 0],
data: vec![],
})?
.try_into()
data: &[],
})?;
Ok(data.try_into()?)
}
fn command_stack_usage_get(&mut self) -> Result<McuStackUsage, Error> {
self.link
.execute_command(&Command {
let data = self.link.execute_command(&Command {
id: b'%',
args: [0, 0],
data: vec![],
})?
.try_into()
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<String>) -> Result<SC64, Error> {
} 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<SC64, Error> {
let _ = remote;
Err(Error::new("Remote connection not implemented yet"))
pub fn new_remote(address: String) -> Result<SC64, Error> {
let mut sc64 = SC64 {
link: link::new_remote(&address)?,
};
sc64.check_device()?;
Ok(sc64)
}
pub fn new_server(port: Option<String>, bind: String) -> Result<(), Error> {
let _ = (port, bind);
Err(Error::new("SC64 server not implemented yet"))
pub fn run_server(
port: Option<String>,
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)
}

View File

@ -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<Packet> 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<Vec<u8>> 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<Vec<u8>> 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<Vec<u8>> for FpgaDebugData {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
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<Vec<u8>> for McuStackUsage {
type Error = Error;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
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()),
})
}
}

View File

@ -1,33 +1,6 @@
use super::Error;
use chrono::{DateTime, Datelike, Local, NaiveDateTime, TimeZone, Timelike};
pub fn u16_from_vec(data: &[u8]) -> Result<u16, Error> {
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<u32, Error> {
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<DateTime<Local>, Error> {
pub fn convert_to_datetime(data: &[u8; 8]) -> Result<DateTime<Local>, 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<DateTime<Local>, Error> {
Ok(Local.from_local_datetime(native).unwrap())
}
pub fn vec_from_datetime(datetime: DateTime<Local>) -> Result<Vec<u8>, Error> {
pub fn convert_from_datetime(datetime: DateTime<Local>) -> [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<Local>) -> Result<Vec<u8>, 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]),
]
}