use crate::common::ram_command::{Command, RamCommand};
use crate::common::serial_io::{SerialIo, for_tool};
use crate::progress::{ProgressHandle, ProgressOperation, ProgressStatus};
use crate::utils::Utils;
use crate::{Error, Result, SifliToolTrait};
use crc::{Algorithm, Crc};
use std::fs::File;
use std::io::{Seek, Write};
use std::time::Duration;
use tempfile::tempfile;
#[derive(Debug)]
pub struct ReadFlashFile {
pub file_path: String,
pub address: u32,
pub size: u32,
}
pub struct FlashReader;
impl FlashReader {
const START_TRANS_MARKER: &'static [u8] = b"start_trans\r\n";
const READ_TIMEOUT_MS: u128 = 10_000;
const READ_CHUNK_SIZE: usize = 16 * 1024;
const CRC_32_ALGO: Algorithm<u32> = Algorithm {
width: 32,
poly: 0x04C11DB7,
init: 0,
refin: true,
refout: true,
xorout: 0,
check: 0,
residue: 0,
};
pub fn parse_file_info(file_spec: &str) -> Result<ReadFlashFile> {
let Some((file_path, addr_size)) = file_spec.split_once('@') else {
return Err(Error::invalid_input(format!(
"Invalid format: {}. Expected: filename@address:size",
file_spec
)));
};
let Some((addr, size)) = addr_size.split_once(':') else {
return Err(Error::invalid_input(format!(
"Invalid format: {}. Expected: filename@address:size",
file_spec
)));
};
let address = Utils::str_to_u32(addr)
.map_err(|e| Error::invalid_input(format!("Invalid address '{}': {}", addr, e)))?;
let size = Utils::str_to_u32(size)
.map_err(|e| Error::invalid_input(format!("Invalid size '{}': {}", size, e)))?;
Ok(ReadFlashFile {
file_path: file_path.to_string(),
address,
size,
})
}
pub fn read_flash_data<T>(
tool: &mut T,
address: u32,
size: u32,
output_path: &str,
) -> Result<()>
where
T: SifliToolTrait + RamCommand,
{
tool.check_cancelled()?;
let progress = tool.progress();
let progress_bar =
progress.create_bar(size as u64, ProgressOperation::ReadFlash { address, size });
let mut temp_file = tempfile()?;
tool.command(Command::Read { address, len: size })?;
let (expected_crc, actual_crc) = {
let mut io = for_tool(tool);
Self::wait_for_marker(&mut io, Self::START_TRANS_MARKER, "start_trans marker")?;
let actual_crc =
Self::receive_payload(&mut io, size, &mut temp_file, &progress_bar, address)?;
let expected_crc = Self::read_crc_value(&mut io)?;
Self::expect_ok(&mut io)?;
(expected_crc, actual_crc)
};
if actual_crc != expected_crc {
return Err(Error::CrcMismatch {
expected: expected_crc,
actual: actual_crc,
});
}
progress_bar.finish(ProgressStatus::Success);
temp_file.seek(std::io::SeekFrom::Start(0))?;
let mut output_file = File::create(output_path)?;
std::io::copy(&mut temp_file, &mut output_file)?;
Ok(())
}
fn wait_for_marker(io: &mut SerialIo<'_>, marker: &[u8], context: &str) -> Result<()> {
io.wait_for_pattern(
marker,
Duration::from_millis(Self::READ_TIMEOUT_MS as u64),
context,
)?;
Ok(())
}
fn receive_payload(
io: &mut SerialIo<'_>,
size: u32,
temp_file: &mut File,
progress_bar: &ProgressHandle,
address: u32,
) -> Result<u32> {
let mut remaining = size as usize;
let buffer_len = remaining.clamp(1usize, Self::READ_CHUNK_SIZE);
let mut buffer = vec![0u8; buffer_len];
let crc = Crc::<u32>::new(&Self::CRC_32_ALGO);
let mut digest = crc.digest();
let mut processed = 0usize;
while remaining > 0 {
io.check_cancelled()?;
let chunk_len = std::cmp::min(buffer.len(), remaining);
let chunk = &mut buffer[..chunk_len];
let current_address = address.saturating_add(processed as u32);
io.read_exact_with_timeout(
chunk,
Duration::from_millis(Self::READ_TIMEOUT_MS as u64),
&format!("reading flash at 0x{:08X}", current_address),
)?;
temp_file.write_all(chunk)?;
digest.update(chunk);
remaining -= chunk_len;
processed += chunk_len;
progress_bar.inc(chunk_len as u64);
}
Ok(digest.finalize())
}
fn read_crc_value(io: &mut SerialIo<'_>) -> Result<u32> {
let line = Self::read_non_empty_line(io, "CRC response")?;
let lower = line.to_ascii_lowercase();
let prefix = "crc:0x";
if !lower.starts_with(prefix) {
return Err(Error::protocol(format!("unexpected CRC line: {}", line)));
}
let hex_part = &line[prefix.len()..];
u32::from_str_radix(hex_part, 16)
.map_err(|e| Error::protocol(format!("invalid CRC '{}': {}", line, e)))
}
fn expect_ok(io: &mut SerialIo<'_>) -> Result<()> {
let line = Self::read_non_empty_line(io, "OK response")?;
if line != "OK" {
return Err(Error::protocol(format!("unexpected response: {}", line)));
}
Ok(())
}
fn read_non_empty_line(io: &mut SerialIo<'_>, context: &str) -> Result<String> {
loop {
let line = Self::read_line(io, context)?;
let trimmed = line.trim().to_string();
if trimmed.is_empty() {
continue;
}
return Ok(trimmed);
}
}
fn read_line(io: &mut SerialIo<'_>, context: &str) -> Result<String> {
io.read_line_with_timeout(Duration::from_millis(Self::READ_TIMEOUT_MS as u64), context)
}
}