1use crate::common::ram_command::{Command, RamCommand};
2use crate::progress::ProgressHandler;
3use crate::utils::Utils;
4use crate::{Error, Result, SifliToolTrait};
5use crc::{Algorithm, Crc};
6use serialport::SerialPort;
7use std::collections::VecDeque;
8use std::fs::File;
9use std::io::{ErrorKind, Read, Seek, Write};
10use std::time::Instant;
11use tempfile::tempfile;
12
13#[derive(Debug)]
15pub struct ReadFlashFile {
16 pub file_path: String,
17 pub address: u32,
18 pub size: u32,
19}
20
21pub struct FlashReader;
23
24impl FlashReader {
25 const START_TRANS_MARKER: &'static [u8] = b"start_trans\r\n";
26 const READ_TIMEOUT_MS: u128 = 10_000;
27 const READ_CHUNK_SIZE: usize = 16 * 1024;
28 const CRC_32_ALGO: Algorithm<u32> = Algorithm {
29 width: 32,
30 poly: 0x04C11DB7,
31 init: 0,
32 refin: true,
33 refout: true,
34 xorout: 0,
35 check: 0,
36 residue: 0,
37 };
38
39 pub fn parse_file_info(file_spec: &str) -> Result<ReadFlashFile> {
41 let Some((file_path, addr_size)) = file_spec.split_once('@') else {
42 return Err(Error::invalid_input(format!(
43 "Invalid format: {}. Expected: filename@address:size",
44 file_spec
45 )));
46 };
47 let Some((addr, size)) = addr_size.split_once(':') else {
48 return Err(Error::invalid_input(format!(
49 "Invalid format: {}. Expected: filename@address:size",
50 file_spec
51 )));
52 };
53
54 let address = Utils::str_to_u32(addr)
55 .map_err(|e| Error::invalid_input(format!("Invalid address '{}': {}", addr, e)))?;
56 let size = Utils::str_to_u32(size)
57 .map_err(|e| Error::invalid_input(format!("Invalid size '{}': {}", size, e)))?;
58
59 Ok(ReadFlashFile {
60 file_path: file_path.to_string(),
61 address,
62 size,
63 })
64 }
65
66 pub fn read_flash_data<T>(
68 tool: &mut T,
69 address: u32,
70 size: u32,
71 output_path: &str,
72 ) -> Result<()>
73 where
74 T: SifliToolTrait + RamCommand,
75 {
76 let progress = tool.progress();
77 let progress_bar =
78 progress.create_bar(size as u64, format!("Reading from 0x{:08X}...", address));
79
80 let mut temp_file = tempfile()?;
81
82 tool.command(Command::Read { address, len: size })?;
84
85 let (expected_crc, actual_crc) = {
86 let port = tool.port();
87
88 Self::wait_for_marker(port, Self::START_TRANS_MARKER, "start_trans marker")?;
89
90 let actual_crc =
91 Self::receive_payload(port, size, &mut temp_file, &progress_bar, address)?;
92
93 let expected_crc = Self::read_crc_value(port)?;
94 Self::expect_ok(port)?;
95
96 (expected_crc, actual_crc)
97 };
98
99 if actual_crc != expected_crc {
100 return Err(Error::CrcMismatch {
101 expected: expected_crc,
102 actual: actual_crc,
103 });
104 }
105
106 progress_bar.finish_with_message("Read complete");
107
108 temp_file.seek(std::io::SeekFrom::Start(0))?;
109 let mut output_file = File::create(output_path)?;
110 std::io::copy(&mut temp_file, &mut output_file)?;
111
112 Ok(())
113 }
114
115 fn wait_for_marker(port: &mut Box<dyn SerialPort>, marker: &[u8], context: &str) -> Result<()> {
116 if marker.is_empty() {
117 return Ok(());
118 }
119
120 let mut window = VecDeque::with_capacity(marker.len());
121 let mut last_activity = Instant::now();
122
123 loop {
124 let mut byte = [0u8; 1];
125 match port.read(&mut byte) {
126 Ok(0) => {
127 if last_activity.elapsed().as_millis() > Self::READ_TIMEOUT_MS {
128 return Err(Error::timeout(format!("waiting for {}", context)));
129 }
130 }
131 Ok(_) => {
132 last_activity = Instant::now();
133 window.push_back(byte[0]);
134 if window.len() > marker.len() {
135 window.pop_front();
136 }
137 if window.len() == marker.len()
138 && window.iter().copied().eq(marker.iter().copied())
139 {
140 return Ok(());
141 }
142 }
143 Err(e) if matches!(e.kind(), ErrorKind::TimedOut | ErrorKind::WouldBlock) => {
144 if last_activity.elapsed().as_millis() > Self::READ_TIMEOUT_MS {
145 return Err(Error::timeout(format!("waiting for {}", context)));
146 }
147 }
148 Err(e) if e.kind() == ErrorKind::Interrupted => {}
149 Err(e) => return Err(e.into()),
150 }
151 }
152 }
153
154 fn receive_payload(
155 port: &mut Box<dyn SerialPort>,
156 size: u32,
157 temp_file: &mut File,
158 progress_bar: &ProgressHandler,
159 address: u32,
160 ) -> Result<u32> {
161 let mut remaining = size as usize;
162 let buffer_len = remaining.clamp(1usize, Self::READ_CHUNK_SIZE);
163 let mut buffer = vec![0u8; buffer_len];
164
165 let crc = Crc::<u32>::new(&Self::CRC_32_ALGO);
166 let mut digest = crc.digest();
167 let mut processed = 0usize;
168
169 while remaining > 0 {
170 let chunk_len = std::cmp::min(buffer.len(), remaining);
171 let chunk = &mut buffer[..chunk_len];
172 let current_address = address.saturating_add(processed as u32);
173 Self::read_exact_with_timeout(
174 port,
175 chunk,
176 Self::READ_TIMEOUT_MS,
177 &format!("reading flash at 0x{:08X}", current_address),
178 )?;
179
180 temp_file.write_all(chunk)?;
181 digest.update(chunk);
182
183 remaining -= chunk_len;
184 processed += chunk_len;
185 progress_bar.inc(chunk_len as u64);
186 }
187
188 Ok(digest.finalize())
189 }
190
191 fn read_crc_value(port: &mut Box<dyn SerialPort>) -> Result<u32> {
192 let line = Self::read_non_empty_line(port, "CRC response")?;
193 let lower = line.to_ascii_lowercase();
194 let prefix = "crc:0x";
195
196 if !lower.starts_with(prefix) {
197 return Err(Error::protocol(format!("unexpected CRC line: {}", line)));
198 }
199
200 let hex_part = &line[prefix.len()..];
201 u32::from_str_radix(hex_part, 16)
202 .map_err(|e| Error::protocol(format!("invalid CRC '{}': {}", line, e)))
203 }
204
205 fn expect_ok(port: &mut Box<dyn SerialPort>) -> Result<()> {
206 let line = Self::read_non_empty_line(port, "OK response")?;
207 if line != "OK" {
208 return Err(Error::protocol(format!("unexpected response: {}", line)));
209 }
210 Ok(())
211 }
212
213 fn read_non_empty_line(port: &mut Box<dyn SerialPort>, context: &str) -> Result<String> {
214 loop {
215 let line = Self::read_line(port, context)?;
216 let trimmed = line.trim().to_string();
217 if trimmed.is_empty() {
218 continue;
219 }
220 return Ok(trimmed);
221 }
222 }
223
224 fn read_line(port: &mut Box<dyn SerialPort>, context: &str) -> Result<String> {
225 let mut buffer = Vec::new();
226 let mut last_activity = Instant::now();
227
228 loop {
229 let mut byte = [0u8; 1];
230 match port.read(&mut byte) {
231 Ok(0) => {
232 if last_activity.elapsed().as_millis() > Self::READ_TIMEOUT_MS {
233 return Err(Error::timeout(format!("waiting for {}", context)));
234 }
235 }
236 Ok(_) => {
237 last_activity = Instant::now();
238 match byte[0] {
239 b'\n' => break,
240 b'\r' => continue,
241 ch => buffer.push(ch),
242 }
243 }
244 Err(e) if matches!(e.kind(), ErrorKind::TimedOut | ErrorKind::WouldBlock) => {
245 if last_activity.elapsed().as_millis() > Self::READ_TIMEOUT_MS {
246 return Err(Error::timeout(format!("waiting for {}", context)));
247 }
248 }
249 Err(e) if e.kind() == ErrorKind::Interrupted => continue,
250 Err(e) => return Err(e.into()),
251 }
252 }
253
254 Ok(String::from_utf8_lossy(&buffer).into_owned())
255 }
256
257 fn read_exact_with_timeout(
258 port: &mut Box<dyn SerialPort>,
259 buf: &mut [u8],
260 timeout_ms: u128,
261 context: &str,
262 ) -> Result<()> {
263 if buf.is_empty() {
264 return Ok(());
265 }
266
267 let mut offset = 0;
268 let mut last_activity = Instant::now();
269
270 while offset < buf.len() {
271 match port.read(&mut buf[offset..]) {
272 Ok(0) => {
273 if last_activity.elapsed().as_millis() > timeout_ms {
274 return Err(Error::timeout(format!("waiting for {}", context)));
275 }
276 }
277 Ok(n) => {
278 offset += n;
279 last_activity = Instant::now();
280 }
281 Err(e) if matches!(e.kind(), ErrorKind::TimedOut | ErrorKind::WouldBlock) => {
282 if last_activity.elapsed().as_millis() > timeout_ms {
283 return Err(Error::timeout(format!("waiting for {}", context)));
284 }
285 }
286 Err(e) if e.kind() == ErrorKind::Interrupted => continue,
287 Err(e) => return Err(e.into()),
288 }
289 }
290
291 Ok(())
292 }
293}