1use crate::{Error, Result, WriteFlashFile};
2use crc::Algorithm;
3use memmap2::Mmap;
4use std::fs::File;
5use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
6use std::path::Path;
7use tempfile::tempfile;
8
9#[derive(Debug, PartialEq, Eq, Clone)]
10pub enum FileType {
11 Bin,
12 Hex,
13 Elf,
14 Unknown,
15}
16
17pub const ELF_MAGIC: &[u8] = &[0x7F, 0x45, 0x4C, 0x46]; pub struct Utils;
20impl Utils {
21 pub fn str_to_u32(s: &str) -> Result<u32> {
22 let s = s.trim();
23
24 let (num_str, multiplier) = match s.chars().last() {
25 Some('k') | Some('K') => (&s[..s.len() - 1], 1_000u32),
26 Some('m') | Some('M') => (&s[..s.len() - 1], 1_000_000u32),
27 Some('g') | Some('G') => (&s[..s.len() - 1], 1_000_000_000u32),
28 _ => (s, 1),
29 };
30
31 let unsigned: u32 = if let Some(hex) = num_str.strip_prefix("0x") {
32 u32::from_str_radix(hex, 16)?
33 } else if let Some(bin) = num_str.strip_prefix("0b") {
34 u32::from_str_radix(bin, 2)?
35 } else if let Some(oct) = num_str.strip_prefix("0o") {
36 u32::from_str_radix(oct, 8)?
37 } else {
38 num_str.parse()?
39 };
40
41 Ok(unsigned * multiplier)
42 }
43
44 pub(crate) fn get_file_crc32(file: &File) -> Result<u32> {
45 const CRC_32_ALGO: Algorithm<u32> = Algorithm {
46 width: 32,
47 poly: 0x04C11DB7,
48 init: 0,
49 refin: true,
50 refout: true,
51 xorout: 0,
52 check: 0x2DFD2D88,
53 residue: 0,
54 };
55
56 const CRC: crc::Crc<u32> = crc::Crc::<u32>::new(&CRC_32_ALGO);
57 let mut reader = BufReader::new(file);
58
59 let mut digest = CRC.digest();
60
61 let mut buffer = [0u8; 4 * 1024];
62 loop {
63 let n = reader.read(&mut buffer)?;
64 if n == 0 {
65 break;
66 }
67 digest.update(&buffer[..n]);
68 }
69
70 let checksum = digest.finalize();
71 reader.seek(SeekFrom::Start(0))?;
72 Ok(checksum)
73 }
74
75 pub fn detect_file_type(path: &Path) -> Result<FileType> {
77 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
78 match ext.to_lowercase().as_str() {
79 "bin" => return Ok(FileType::Bin),
80 "hex" => return Ok(FileType::Hex),
81 "elf" | "axf" => return Ok(FileType::Elf),
82 _ => {} }
84 }
85
86 let mut file = File::open(path)?;
88 let mut magic = [0u8; 4];
89 file.read_exact(&mut magic)?;
90
91 if magic == ELF_MAGIC {
92 return Ok(FileType::Elf);
93 }
94
95 Ok(FileType::Unknown)
97 }
98
99 pub fn parse_file_info(file_str: &str) -> Result<Vec<WriteFlashFile>> {
101 let parts: Vec<_> = file_str.split('@').collect();
103 if parts.len() == 2 {
105 let addr = Self::str_to_u32(parts[1])?;
106
107 let file_type = Self::detect_file_type(Path::new(parts[0]))?;
108
109 match file_type {
110 FileType::Hex => {
111 return Self::hex_with_base_to_write_flash_files(
113 Path::new(parts[0]),
114 Some(addr),
115 );
116 }
117 FileType::Elf => {
118 return Err(Error::invalid_input(
120 "ELF files do not support @address format",
121 ));
122 }
123 _ => {
124 let file = std::fs::File::open(parts[0])?;
126 let crc32 = Self::get_file_crc32(&file)?;
127
128 return Ok(vec![WriteFlashFile {
129 address: addr,
130 file,
131 crc32,
132 }]);
133 }
134 }
135 }
136
137 let file_type = Self::detect_file_type(Path::new(parts[0]))?;
138
139 match file_type {
140 FileType::Hex => Self::hex_to_write_flash_files(Path::new(parts[0])),
141 FileType::Elf => Self::elf_to_write_flash_files(Path::new(parts[0])),
142 _ => Err(Error::invalid_input(
143 "For binary files, please use the <file@address> format",
144 )),
145 }
146 }
147
148 pub fn calculate_crc32(data: &[u8]) -> u32 {
150 const CRC_32_ALGO: Algorithm<u32> = Algorithm {
151 width: 32,
152 poly: 0x04C11DB7,
153 init: 0,
154 refin: true,
155 refout: true,
156 xorout: 0,
157 check: 0,
158 residue: 0,
159 };
160 crc::Crc::<u32>::new(&CRC_32_ALGO).checksum(data)
161 }
162
163 pub fn hex_to_write_flash_files(hex_file: &Path) -> Result<Vec<WriteFlashFile>> {
165 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
166
167 let file = std::fs::File::open(hex_file)?;
168 let reader = std::io::BufReader::new(file);
169
170 let mut current_base_address = 0u32;
171 let mut current_temp_file: Option<File> = None;
172 let mut current_segment_start = 0u32;
173 let mut current_file_offset = 0u32;
174
175 for line in reader.lines() {
176 let line = line?;
177 let line = line.trim_end_matches('\r');
178 if line.is_empty() {
179 continue;
180 }
181
182 let ihex_record = ihex::Record::from_record_string(line)?;
183
184 match ihex_record {
185 ihex::Record::ExtendedLinearAddress(addr) => {
186 let new_base_address = (addr as u32) << 16;
187
188 current_base_address = new_base_address;
191 }
192 ihex::Record::Data { offset, value } => {
193 let absolute_address = current_base_address + offset as u32;
194
195 let should_start_new_segment = if let Some(ref _temp_file) = current_temp_file {
197 let current_end_address = current_segment_start + current_file_offset;
198 let expected_start_address = absolute_address;
199
200 let gap_size = if expected_start_address >= current_end_address {
203 expected_start_address - current_end_address
204 } else {
205 u32::MAX
207 };
208
209 gap_size > 0x1000
211 } else {
212 false };
214
215 if should_start_new_segment {
216 if let Some(temp_file) = current_temp_file.take() {
218 Self::finalize_segment(
219 temp_file,
220 current_segment_start,
221 &mut write_flash_files,
222 )?;
223 }
224 }
225
226 if current_temp_file.is_none() {
228 current_temp_file = Some(tempfile()?);
229 current_segment_start = absolute_address;
230 current_file_offset = 0;
231 }
232
233 if let Some(ref mut temp_file) = current_temp_file {
234 let expected_file_offset = absolute_address - current_segment_start;
235
236 if expected_file_offset > current_file_offset {
238 let gap_size = expected_file_offset - current_file_offset;
239 let fill_data = vec![0xFF; gap_size as usize];
240 temp_file.write_all(&fill_data)?;
241 current_file_offset = expected_file_offset;
242 }
243
244 temp_file.write_all(&value)?;
246 current_file_offset += value.len() as u32;
247 }
248 }
249 ihex::Record::EndOfFile => {
250 if let Some(temp_file) = current_temp_file.take() {
252 Self::finalize_segment(
253 temp_file,
254 current_segment_start,
255 &mut write_flash_files,
256 )?;
257 }
258 break;
259 }
260 _ => {}
261 }
262 }
263
264 if let Some(temp_file) = current_temp_file.take() {
266 Self::finalize_segment(temp_file, current_segment_start, &mut write_flash_files)?;
267 }
268
269 Ok(write_flash_files)
270 }
271
272 pub fn hex_with_base_to_write_flash_files(
275 hex_file: &Path,
276 base_address_override: Option<u32>,
277 ) -> Result<Vec<WriteFlashFile>> {
278 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
279
280 let file = std::fs::File::open(hex_file)?;
281 let reader = std::io::BufReader::new(file);
282
283 let mut current_base_address = 0u32;
284 let mut current_temp_file: Option<File> = None;
285 let mut current_segment_start = 0u32;
286 let mut current_file_offset = 0u32;
287
288 for line in reader.lines() {
289 let line = line?;
290 let line = line.trim_end_matches('\r');
291 if line.is_empty() {
292 continue;
293 }
294
295 let ihex_record = ihex::Record::from_record_string(line)?;
296
297 match ihex_record {
298 ihex::Record::ExtendedLinearAddress(addr) => {
299 let new_base_address = if let Some(override_addr) = base_address_override {
300 let modified_addr =
302 (addr & 0x00FF) | ((override_addr >> 16) as u16 & 0xFF00);
303 (modified_addr as u32) << 16
304 } else {
305 (addr as u32) << 16
306 };
307
308 current_base_address = new_base_address;
311 }
312 ihex::Record::Data { offset, value } => {
313 let absolute_address = current_base_address + offset as u32;
314
315 let should_start_new_segment = if let Some(ref _temp_file) = current_temp_file {
317 let current_end_address = current_segment_start + current_file_offset;
318 let expected_start_address = absolute_address;
319
320 let gap_size = if expected_start_address >= current_end_address {
323 expected_start_address - current_end_address
324 } else {
325 u32::MAX
327 };
328
329 gap_size > 0x1000
331 } else {
332 false };
334
335 if should_start_new_segment {
336 if let Some(temp_file) = current_temp_file.take() {
338 Self::finalize_segment(
339 temp_file,
340 current_segment_start,
341 &mut write_flash_files,
342 )?;
343 }
344 }
345
346 if current_temp_file.is_none() {
348 current_temp_file = Some(tempfile()?);
349 current_segment_start = absolute_address;
350 current_file_offset = 0;
351 }
352
353 if let Some(ref mut temp_file) = current_temp_file {
354 let expected_file_offset = absolute_address - current_segment_start;
355
356 if expected_file_offset > current_file_offset {
358 let gap_size = expected_file_offset - current_file_offset;
359 let fill_data = vec![0xFF; gap_size as usize];
360 temp_file.write_all(&fill_data)?;
361 current_file_offset = expected_file_offset;
362 }
363
364 temp_file.write_all(&value)?;
366 current_file_offset += value.len() as u32;
367 }
368 }
369 ihex::Record::EndOfFile => {
370 if let Some(temp_file) = current_temp_file.take() {
372 Self::finalize_segment(
373 temp_file,
374 current_segment_start,
375 &mut write_flash_files,
376 )?;
377 }
378 break;
379 }
380 _ => {}
381 }
382 }
383
384 if let Some(temp_file) = current_temp_file.take() {
386 Self::finalize_segment(temp_file, current_segment_start, &mut write_flash_files)?;
387 }
388
389 Ok(write_flash_files)
390 }
391
392 pub fn elf_to_write_flash_files(elf_file: &Path) -> Result<Vec<WriteFlashFile>> {
394 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
395 const SECTOR_SIZE: u32 = 0x1000; const FILL_BYTE: u8 = 0xFF; let file = File::open(elf_file)?;
399 let mmap = unsafe { Mmap::map(&file)? };
400 let elf = goblin::elf::Elf::parse(&mmap[..])?;
401
402 let mut load_segments: Vec<_> = elf
404 .program_headers
405 .iter()
406 .filter(|ph| {
407 ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_paddr < 0x2000_0000
408 })
409 .collect();
410 load_segments.sort_by_key(|ph| ph.p_paddr);
411
412 if load_segments.is_empty() {
413 return Ok(write_flash_files);
414 }
415
416 let mut current_file = tempfile()?;
417 let mut current_base = (load_segments[0].p_paddr as u32) & !(SECTOR_SIZE - 1);
418 let mut current_offset = 0; for ph in load_segments.iter() {
421 let vaddr = ph.p_paddr as u32;
422 let offset = ph.p_offset as usize;
423 let size = ph.p_filesz as usize;
424 let data = &mmap[offset..offset + size];
425
426 let segment_base = vaddr & !(SECTOR_SIZE - 1);
428
429 if segment_base > current_base + current_offset {
431 current_file.seek(std::io::SeekFrom::Start(0))?;
432 let crc32 = Self::get_file_crc32(¤t_file)?;
433 write_flash_files.push(WriteFlashFile {
434 address: current_base,
435 file: std::mem::replace(&mut current_file, tempfile()?),
436 crc32,
437 });
438 current_base = segment_base;
439 current_offset = 0;
440 }
441
442 let relative_offset = vaddr - current_base;
444
445 if current_offset < relative_offset {
447 let padding = relative_offset - current_offset;
448 current_file.write_all(&vec![FILL_BYTE; padding as usize])?;
449 current_offset = relative_offset;
450 }
451
452 current_file.write_all(data)?;
454 current_offset += size as u32;
455 }
456
457 if current_offset > 0 {
459 current_file.seek(std::io::SeekFrom::Start(0))?;
460 let crc32 = Self::get_file_crc32(¤t_file)?;
461 write_flash_files.push(WriteFlashFile {
462 address: current_base,
463 file: current_file,
464 crc32,
465 });
466 }
467
468 Ok(write_flash_files)
469 }
470
471 fn finalize_segment(
473 mut temp_file: File,
474 address: u32,
475 write_flash_files: &mut Vec<WriteFlashFile>,
476 ) -> Result<()> {
477 temp_file.seek(std::io::SeekFrom::Start(0))?;
478 let crc32 = Self::get_file_crc32(&temp_file)?;
479 write_flash_files.push(WriteFlashFile {
480 address,
481 file: temp_file,
482 crc32,
483 });
484 Ok(())
485 }
486
487 pub fn parse_read_file_info(file_spec: &str) -> Result<crate::ReadFlashFile> {
489 let Some((file_path, addr_size)) = file_spec.split_once('@') else {
490 return Err(Error::invalid_input(format!(
491 "Invalid format: {}. Expected: filename@address:size",
492 file_spec
493 )));
494 };
495
496 let Some((address_str, size_str)) = addr_size.split_once(':') else {
497 return Err(Error::invalid_input(format!(
498 "Invalid address:size format: {}. Expected: address:size",
499 addr_size
500 )));
501 };
502
503 let address = Self::str_to_u32(address_str).map_err(|e| {
504 Error::invalid_input(format!("Invalid address '{}': {}", address_str, e))
505 })?;
506
507 let size = Self::str_to_u32(size_str)
508 .map_err(|e| Error::invalid_input(format!("Invalid size '{}': {}", size_str, e)))?;
509
510 Ok(crate::ReadFlashFile {
511 file_path: file_path.to_string(),
512 address,
513 size,
514 })
515 }
516
517 pub fn parse_erase_address(address_str: &str) -> Result<u32> {
519 Self::str_to_u32(address_str)
520 .map_err(|e| Error::invalid_input(format!("Invalid address '{}': {}", address_str, e)))
521 }
522
523 pub fn parse_erase_region(region_spec: &str) -> Result<crate::EraseRegionFile> {
525 let Some((address_str, size_str)) = region_spec.split_once(':') else {
526 return Err(Error::invalid_input(format!(
527 "Invalid region format: {}. Expected: address:size",
528 region_spec
529 )));
530 };
531
532 let address = Self::str_to_u32(address_str).map_err(|e| {
533 Error::invalid_input(format!("Invalid address '{}': {}", address_str, e))
534 })?;
535
536 let size = Self::str_to_u32(size_str)
537 .map_err(|e| Error::invalid_input(format!("Invalid size '{}': {}", size_str, e)))?;
538
539 Ok(crate::EraseRegionFile { address, size })
540 }
541}