use std::io::Error; use std::path::Path; use std::time::Duration; use serial2::SerialPort; use crate::utils::Hex; pub trait Target { fn read_flash(&mut self, offset: u16, out: &mut [u8]) -> Result<(), Error>; fn write_flash(&mut self, page: u16, data: &[u8; 128]) -> Result<(), Error>; fn erase(&mut self) -> Result<(), Error>; } pub struct PhysicalTarget { port: SerialPort } impl PhysicalTarget { pub fn init(path: impl AsRef, baud: u32) -> Result { let mut port = SerialPort::open(path, baud)?; port.set_read_timeout(Duration::from_millis(100))?; let mut new = Self { port }; new.resync_target()?; let manufacturer = new.read_info(0x00, 0x00).unwrap(); let family = new.read_info(0x00, 0x01).unwrap(); let name = new.read_info(0x00, 0x02).unwrap(); let rev = new.read_info(0x00, 0x03).unwrap(); println!("Detected chip ID: {manufacturer:02X} {family:02X} {name:02X} {rev:02X} (whatever that means)"); Ok(new) } pub fn resync_target(&mut self) -> Result<(), Error> { self.port.discard_buffers()?; self.port.set_dtr(true)?; self.port.set_rts(true)?; std::thread::sleep(Duration::from_millis(10)); self.port.set_dtr(false)?; std::thread::sleep(Duration::from_millis(100)); for i in (0..5).rev() { self.port.write(b"U")?; let mut buf = [0u8; 1]; self.port.read(&mut buf)?; if buf[0] == b'U' { break } if i == 0 { Err(Error::other(format!("Invalid sync response: 0x{:02X}", buf[0])))? } } Ok(()) } pub fn read_info(&mut self, category: u8, idx: u8) -> Result { self.send_command(0x05, 0, &[category, idx])?; let mut resp_hex_digits = Vec::new(); loop { let mut tmp = [0u8; 1]; self.port.read_exact(&mut tmp)?; match tmp[0] { x @ (b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') => resp_hex_digits.push(x), b'.' | b'\r' => {}, b'\n' => break, b'P' => Err(Error::other("Security error"))?, b'X' => Err(Error::other("Checksum error"))?, x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))? } } let mut rx = [0u8; 1]; let resp = resp_hex_digits.as_chunks::<2>().0.iter() .map(u8::hex_decode); if resp.len() != rx.len() { Err(Error::other(format!("Response size mismatch, got {} bytes, expected {} bytes", resp.len(), rx.len())))? } rx.iter_mut() .zip(resp) .for_each(|(dest, src)| *dest = src); Ok(u8::from_le_bytes(rx)) } fn send_command(&mut self, cmd_type: u8, offset: u16, tx: &[u8]) -> Result<(), Error> { if tx.len() >= 256 { Err(Error::other(format!("Payload too large: {} bytes", tx.len())))? } // Why, the ACTUAL FUCK, ARE THEY USING ASCII FOR TRANSFERING BINARIES?! let mut ascii_buf = Vec::with_capacity(1 + 2 + 4 + 2 + tx.len() * 2 + 1); // Mark, Length, Offset, Type, Data, Checksum ascii_buf.push(b':'); ascii_buf.extend_from_slice(&(tx.len() as u8).hex_encode()); ascii_buf.extend_from_slice(&offset.hex_encode()); ascii_buf.extend_from_slice(&cmd_type.hex_encode()); let mut checksum: u8 = 0; checksum = checksum.wrapping_add(cmd_type); checksum = checksum.wrapping_add(tx.len() as u8); checksum = checksum.wrapping_add(offset as u8); checksum = checksum.wrapping_add((offset >> 8) as u8); for val in tx { ascii_buf.extend_from_slice(&val.hex_encode()); checksum = checksum.wrapping_add(*val); } ascii_buf.extend_from_slice(&checksum.wrapping_neg().hex_encode()); self.port.write_all(&ascii_buf)?; let mut echo = vec![0u8; ascii_buf.len()]; self.port.read_exact(&mut echo)?; if echo != ascii_buf { Err(Error::other("Echo response mismatch"))? } Ok(()) } pub fn port(&mut self) -> &mut SerialPort { &mut self.port } } impl Target for PhysicalTarget { fn read_flash(&mut self, offset: u16, out: &mut [u8]) -> Result<(), Error> { self.send_command(0x04, 0, &[ offset.to_be_bytes().as_slice(), offset.saturating_add(out.len() as u16 - 1).to_be_bytes().as_slice(), &[0] // Flash ].concat())?; let mut resp_hex_digits = Vec::with_capacity(out.len() * 2); let mut ignore = false; let mut finish_line = false; loop { let mut tmp = [0u8; 1]; self.port.read_exact(&mut tmp)?; match tmp[0] { x @ (b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') => { if !ignore { resp_hex_digits.push(x); if resp_hex_digits.len() >= resp_hex_digits.capacity() { ignore = true; finish_line = true; } } }, b'=' => { ignore = false; } b'.' | b'\r' => {}, b'\n' => { ignore = true; if finish_line { break } }, b'P' => Err(Error::other("Security error"))?, b'X' => Err(Error::other("Checksum error"))?, x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))? } } let resp = resp_hex_digits.as_chunks::<2>().0.iter() .map(u8::hex_decode); out.iter_mut() .zip(resp) .for_each(|(dest, src)| *dest = src); Ok(()) } fn write_flash(&mut self, page: u16, data: &[u8; 128]) -> Result<(), Error> { self.send_command(0x00, page * 128, data)?; loop { let mut tmp = [0u8; 1]; self.port.read_exact(&mut tmp)?; match tmp[0] { b'.' | b'\r' => {}, b'\n' => break, b'P' => Err(Error::other("Security error"))?, b'X' => Err(Error::other("Checksum error"))?, x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))? } } Ok(()) } fn erase(&mut self) -> Result<(), Error> { self.send_command(0x03, 0, &[0x07])?; let old_timeout = self.port.get_read_timeout()?; self.port.set_read_timeout(Duration::from_secs(15))?; loop { let mut tmp = [0u8; 1]; self.port.read_exact(&mut tmp)?; match tmp[0] { b'.' | b'\r' => {}, b'\n' => break, b'P' => Err(Error::other("Security error"))?, b'X' => Err(Error::other("Checksum error"))?, x => Err(Error::other(format!("Invalid byte in response: {x:02X}")))? } } self.port.set_read_timeout(old_timeout)?; Ok(()) } } impl Drop for PhysicalTarget { fn drop(&mut self) { let _ = self.port.set_dtr(true); let _ = self.port.set_rts(true); std::thread::sleep(Duration::from_millis(100)); let _ = self.port.set_dtr(false); let _ = self.port.set_rts(false); } }