use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use onerom_database::{RomEntry, RomType};
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Command {
Ping = 0x0000_0000,
ReadRom = 0x0000_0001,
GetRawData = 0x0000_0002,
Unknown = 0xFFFF_FFFF,
}
impl From<u32> for Command {
fn from(value: u32) -> Self {
match value {
0x0000_0000 => Command::Ping,
0x0000_0001 => Command::ReadRom,
_ => Command::Unknown,
}
}
}
impl From<Command> for u32 {
fn from(cmd: Command) -> Self {
cmd as u32
}
}
impl Command {
pub fn size() -> usize {
core::mem::size_of::<Self>()
}
pub fn as_bytes(&self) -> [u8; 4] {
(*self as u32).to_le_bytes()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Response {
Pong = 0x0000_0000,
RomEntry = 0x0000_0001,
RomNotRecognised = 0x0000_0002,
Error = 0x8000_0000,
NoRom = 0x8000_0001,
Unknown = 0xFFFF_FFFF,
}
impl Response {
pub const fn size() -> usize {
core::mem::size_of::<Self>()
}
pub fn to_bytes(&self, buf: &mut [u8]) {
let value = *self as u32;
buf[..4].copy_from_slice(&value.to_le_bytes());
}
}
impl From<u32> for Response {
fn from(value: u32) -> Self {
match value {
0x0000_0000 => Response::Pong,
0x0000_0001 => Response::RomEntry,
0x0000_0002 => Response::RomNotRecognised,
0x8000_0000 => Response::Error,
0x8000_0001 => Response::NoRom,
_ => Response::Unknown,
}
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct GetRawData {
rom_type: RomType,
}
impl GetRawData {
const fn binary_size() -> usize {
RomType::binary_size()
}
pub fn from_buffer(buf: &[u8]) -> Result<Self, Error> {
let rom_type_size = Self::binary_size();
if buf.len() < rom_type_size {
return Err(Error::BufferTooSmall);
}
let rom_type = RomType::from_bytes(&buf[0..rom_type_size])?;
Ok(Self { rom_type })
}
pub fn to_buffer(&self) -> Result<Vec<u8>, Error> {
let mut pos = 0;
let size = Self::binary_size() + Command::size();
let mut buf = vec![0; size];
if size < Command::size() {
return Err(Error::BufferTooSmall);
}
let rsp_u32 = Command::GetRawData as u32;
buf[pos..pos + 4].copy_from_slice(&rsp_u32.to_le_bytes());
pos += 4;
let rom_type_size = RomType::binary_size();
if size < pos + rom_type_size {
return Err(Error::BufferTooSmall);
}
self.rom_type.to_bytes(&mut buf[pos..pos + rom_type_size])?;
Ok(buf)
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct LabRomType {
#[serde(rename = "ROM Type")]
rom_type: RomType,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct RomRawData {
#[serde(rename = "ROM Type")]
rom_type: RomType,
#[serde(rename = "Checksum")]
checksum: u32,
#[serde(rename = "SHA1 Digest")]
sha1: [u8; 20],
}
impl RomRawData {
const fn binary_size() -> usize {
RomType::binary_size() + 4 + 20
}
pub fn from_buffer(buf: &[u8]) -> Result<Self, Error> {
let mut pos = 0;
let rom_type_size = RomType::binary_size();
if buf.len() < pos + rom_type_size {
return Err(Error::BufferTooSmall);
}
let rom_type = RomType::from_bytes(&buf[pos..pos + rom_type_size])?;
pos += rom_type_size;
if buf.len() < pos + 4 {
return Err(Error::BufferTooSmall);
}
let checksum = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]);
pos += 4;
if buf.len() < pos + 20 {
return Err(Error::BufferTooSmall);
}
let mut sha1 = [0u8; 20];
sha1.copy_from_slice(&buf[pos..pos + 20]);
Ok(Self {
rom_type,
checksum,
sha1,
})
}
pub fn to_buffer(&self) -> Result<Vec<u8>, Error> {
let mut pos = 0;
let size = Self::binary_size();
let mut buf = vec![0; size];
let rom_type_size = RomType::binary_size();
if size < pos + rom_type_size {
return Err(Error::BufferTooSmall);
}
self.rom_type.to_bytes(&mut buf[pos..pos + rom_type_size])?;
pos += rom_type_size;
if size < pos + 4 {
return Err(Error::BufferTooSmall);
}
buf[pos..pos + 4].copy_from_slice(&self.checksum.to_le_bytes());
pos += 4;
if size < pos + 20 {
return Err(Error::BufferTooSmall);
}
buf[pos..pos + 20].copy_from_slice(&self.sha1);
Ok(buf)
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct LabRomEntry {
#[serde(rename = "Name")]
name: String,
#[serde(rename = "Part Number")]
part_number: String,
#[serde(rename = "Checksum")]
checksum: u32,
#[serde(rename = "SHA1 Digest")]
sha1: [u8; 20],
}
impl LabRomEntry {
pub fn from_buffer(buf: &[u8]) -> Result<Self, Error> {
let mut pos = 0;
if buf.len() < 4 {
return Err(Error::BufferTooSmall);
}
let rsp_u32 = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
let response: Response = rsp_u32.into();
pos += 4;
match response {
Response::RomEntry => (), Response::NoRom => Err(Error::NoRom)?,
Response::RomNotRecognised => Err(Error::RomNotRecognised)?,
_ => {
warn!("Unexpected response code for RomMetadata: {rsp_u32:#010X} {response:?}");
Err(Error::InvalidResponse)?
}
}
let name_end = buf[pos..].iter().position(|&b| b == 0).ok_or_else(|| {
warn!("Name string not null-terminated");
Error::InvalidData
})?;
let name = String::from_utf8(buf[pos..pos + name_end].to_vec()).map_err(|_| {
warn!("Invalid UTF-8 in name");
Error::InvalidData
})?;
pos += name_end + 1;
let part_end = buf[pos..].iter().position(|&b| b == 0).ok_or_else(|| {
warn!("Part number string not null-terminated");
Error::InvalidData
})?;
let part_number = String::from_utf8(buf[pos..pos + part_end].to_vec()).map_err(|_| {
warn!("Invalid UTF-8 in part number");
Error::InvalidData
})?;
pos += part_end + 1;
if buf.len() < pos + 4 {
warn!("Buffer too short for checksum");
return Err(Error::BufferTooSmall);
}
let checksum = u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]);
pos += 4;
if buf.len() < pos + 20 {
warn!("Buffer too short for SHA1 Digest");
return Err(Error::BufferTooSmall);
}
let mut sha1 = [0u8; 20];
sha1.copy_from_slice(&buf[pos..pos + 20]);
Ok(LabRomEntry {
name,
part_number,
checksum,
sha1,
})
}
fn buf_size(&self) -> usize {
Response::size() + self.name.len() + 1 + self.part_number.len() + 1 + 4 + 20
}
pub fn to_buffer(&self) -> Result<Vec<u8>, Error> {
let mut pos = 0;
let size = self.buf_size();
let mut buf = vec![0; size];
if size < 4 {
return Err(Error::BufferTooSmall);
}
let rsp_u32 = Response::RomEntry as u32;
buf[pos..pos + 4].copy_from_slice(&rsp_u32.to_le_bytes());
pos += 4;
let name_bytes = self.name.as_bytes();
if size < pos + name_bytes.len() + 1 {
return Err(Error::BufferTooSmall);
}
buf[pos..pos + name_bytes.len()].copy_from_slice(name_bytes);
pos += name_bytes.len();
buf[pos] = 0; pos += 1;
let part_bytes = self.part_number.as_bytes();
if size < pos + part_bytes.len() + 1 {
return Err(Error::BufferTooSmall);
}
buf[pos..pos + part_bytes.len()].copy_from_slice(part_bytes);
pos += part_bytes.len();
buf[pos] = 0; pos += 1;
if size < pos + 4 {
return Err(Error::BufferTooSmall);
}
buf[pos..pos + 4].copy_from_slice(&self.checksum.to_le_bytes());
pos += 4;
if size < pos + 20 {
return Err(Error::BufferTooSmall);
}
buf[pos..pos + 20].copy_from_slice(&self.sha1);
Ok(buf)
}
}
impl From<RomEntry> for LabRomEntry {
fn from(entry: RomEntry) -> Self {
LabRomEntry {
name: entry.name().to_string(),
part_number: entry.part().to_string(),
checksum: entry.sum(),
sha1: *entry.sha1(),
}
}
}