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 const HEX_SEGMENT_GAP_LIMIT: u32 = 0x1000;
22 const DEFAULT_HEX_SECTOR_SIZE: u32 = 0x1000;
23 const HEX_GAP_FILL_BYTE: u8 = 0xFF;
24
25 pub fn str_to_u32(s: &str) -> Result<u32> {
26 let s = s.trim();
27
28 let (num_str, multiplier) = match s.chars().last() {
29 Some('k') | Some('K') => (&s[..s.len() - 1], 1_000u32),
30 Some('m') | Some('M') => (&s[..s.len() - 1], 1_000_000u32),
31 Some('g') | Some('G') => (&s[..s.len() - 1], 1_000_000_000u32),
32 _ => (s, 1),
33 };
34
35 let unsigned: u32 = if let Some(hex) = num_str.strip_prefix("0x") {
36 u32::from_str_radix(hex, 16)?
37 } else if let Some(bin) = num_str.strip_prefix("0b") {
38 u32::from_str_radix(bin, 2)?
39 } else if let Some(oct) = num_str.strip_prefix("0o") {
40 u32::from_str_radix(oct, 8)?
41 } else {
42 num_str.parse()?
43 };
44
45 Ok(unsigned * multiplier)
46 }
47
48 pub(crate) fn get_file_crc32(file: &File) -> Result<u32> {
49 const CRC_32_ALGO: Algorithm<u32> = Algorithm {
50 width: 32,
51 poly: 0x04C11DB7,
52 init: 0,
53 refin: true,
54 refout: true,
55 xorout: 0,
56 check: 0x2DFD2D88,
57 residue: 0,
58 };
59
60 const CRC: crc::Crc<u32> = crc::Crc::<u32>::new(&CRC_32_ALGO);
61 let mut reader = BufReader::new(file);
62
63 let mut digest = CRC.digest();
64
65 let mut buffer = [0u8; 4 * 1024];
66 loop {
67 let n = reader.read(&mut buffer)?;
68 if n == 0 {
69 break;
70 }
71 digest.update(&buffer[..n]);
72 }
73
74 let checksum = digest.finalize();
75 reader.seek(SeekFrom::Start(0))?;
76 Ok(checksum)
77 }
78
79 pub fn detect_file_type(path: &Path) -> Result<FileType> {
81 if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
82 match ext.to_lowercase().as_str() {
83 "bin" => return Ok(FileType::Bin),
84 "hex" => return Ok(FileType::Hex),
85 "elf" | "axf" => return Ok(FileType::Elf),
86 _ => {} }
88 }
89
90 let mut file = File::open(path)?;
92 let mut magic = [0u8; 4];
93 file.read_exact(&mut magic)?;
94
95 if magic == ELF_MAGIC {
96 return Ok(FileType::Elf);
97 }
98
99 Ok(FileType::Unknown)
101 }
102
103 pub fn parse_file_info(file_str: &str) -> Result<Vec<WriteFlashFile>> {
105 let parts: Vec<_> = file_str.split('@').collect();
107 if parts.len() == 2 {
109 let addr = Self::str_to_u32(parts[1])?;
110
111 let file_type = Self::detect_file_type(Path::new(parts[0]))?;
112
113 match file_type {
114 FileType::Hex => {
115 return Self::hex_with_base_to_write_flash_files(
117 Path::new(parts[0]),
118 Some(addr),
119 );
120 }
121 FileType::Elf => {
122 return Err(Error::invalid_input(
124 "ELF files do not support @address format",
125 ));
126 }
127 _ => {
128 let file = std::fs::File::open(parts[0])?;
130 let crc32 = Self::get_file_crc32(&file)?;
131
132 return Ok(vec![WriteFlashFile {
133 address: addr,
134 file,
135 crc32,
136 }]);
137 }
138 }
139 }
140
141 let file_type = Self::detect_file_type(Path::new(parts[0]))?;
142
143 match file_type {
144 FileType::Hex => Self::hex_to_write_flash_files(Path::new(parts[0])),
145 FileType::Elf => Self::elf_to_write_flash_files(Path::new(parts[0])),
146 _ => Err(Error::invalid_input(
147 "For binary files, please use the <file@address> format",
148 )),
149 }
150 }
151
152 pub fn parse_write_file(path: &str, address: Option<u32>) -> Result<Vec<WriteFlashFile>> {
154 let file_path = Path::new(path);
155 match address {
156 Some(addr) => {
157 let file_type = Self::detect_file_type(file_path)?;
158 match file_type {
159 FileType::Hex => {
160 Self::hex_with_base_to_write_flash_files(file_path, Some(addr))
161 }
162 FileType::Elf => Err(Error::invalid_input(
163 "ELF files do not support @address format",
164 )),
165 _ => {
166 let file = std::fs::File::open(file_path)?;
167 let crc32 = Self::get_file_crc32(&file)?;
168 Ok(vec![WriteFlashFile {
169 address: addr,
170 file,
171 crc32,
172 }])
173 }
174 }
175 }
176 None => {
177 let file_type = Self::detect_file_type(file_path)?;
178 match file_type {
179 FileType::Hex => Self::hex_to_write_flash_files(file_path),
180 FileType::Elf => Self::elf_to_write_flash_files(file_path),
181 _ => Err(Error::invalid_input(
182 "For binary files, please use the <file@address> format",
183 )),
184 }
185 }
186 }
187 }
188
189 pub fn calculate_crc32(data: &[u8]) -> u32 {
191 const CRC_32_ALGO: Algorithm<u32> = Algorithm {
192 width: 32,
193 poly: 0x04C11DB7,
194 init: 0,
195 refin: true,
196 refout: true,
197 xorout: 0,
198 check: 0,
199 residue: 0,
200 };
201 crc::Crc::<u32>::new(&CRC_32_ALGO).checksum(data)
202 }
203
204 pub fn hex_to_write_flash_files(hex_file: &Path) -> Result<Vec<WriteFlashFile>> {
206 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
207
208 let file = std::fs::File::open(hex_file)?;
209 let reader = std::io::BufReader::new(file);
210
211 let mut current_base_address = 0u32;
212 let mut current_temp_file: Option<File> = None;
213 let mut current_segment_start = 0u32;
214 let mut current_file_offset = 0u32;
215
216 for line in reader.lines() {
217 let line = line?;
218 let line = line.trim_end_matches('\r');
219 if line.is_empty() {
220 continue;
221 }
222
223 let ihex_record = ihex::Record::from_record_string(line)?;
224
225 match ihex_record {
226 ihex::Record::ExtendedLinearAddress(addr) => {
227 let new_base_address = (addr as u32) << 16;
228
229 current_base_address = new_base_address;
232 }
233 ihex::Record::Data { offset, value } => {
234 let absolute_address = current_base_address + offset as u32;
235
236 let should_start_new_segment = if current_temp_file.is_some() {
238 Self::should_start_new_hex_segment(
239 current_segment_start,
240 current_file_offset,
241 absolute_address,
242 )
243 } else {
244 false };
246
247 if should_start_new_segment {
248 if let Some(temp_file) = current_temp_file.take() {
250 Self::finalize_segment(
251 temp_file,
252 current_segment_start,
253 &mut write_flash_files,
254 )?;
255 }
256 }
257
258 if current_temp_file.is_none() {
260 current_temp_file = Some(tempfile()?);
261 current_segment_start = absolute_address;
262 current_file_offset = 0;
263 }
264
265 if let Some(ref mut temp_file) = current_temp_file {
266 let expected_file_offset = absolute_address - current_segment_start;
267
268 if expected_file_offset > current_file_offset {
270 let gap_size = expected_file_offset - current_file_offset;
271 let fill_data = vec![Self::HEX_GAP_FILL_BYTE; gap_size as usize];
272 temp_file.write_all(&fill_data)?;
273 current_file_offset = expected_file_offset;
274 }
275
276 temp_file.write_all(&value)?;
278 current_file_offset += value.len() as u32;
279 }
280 }
281 ihex::Record::EndOfFile => {
282 if let Some(temp_file) = current_temp_file.take() {
284 Self::finalize_segment(
285 temp_file,
286 current_segment_start,
287 &mut write_flash_files,
288 )?;
289 }
290 break;
291 }
292 _ => {}
293 }
294 }
295
296 if let Some(temp_file) = current_temp_file.take() {
298 Self::finalize_segment(temp_file, current_segment_start, &mut write_flash_files)?;
299 }
300
301 Ok(write_flash_files)
302 }
303
304 pub fn hex_with_base_to_write_flash_files(
307 hex_file: &Path,
308 base_address_override: Option<u32>,
309 ) -> Result<Vec<WriteFlashFile>> {
310 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
311
312 let file = std::fs::File::open(hex_file)?;
313 let reader = std::io::BufReader::new(file);
314
315 let mut current_base_address = 0u32;
316 let mut current_temp_file: Option<File> = None;
317 let mut current_segment_start = 0u32;
318 let mut current_file_offset = 0u32;
319
320 for line in reader.lines() {
321 let line = line?;
322 let line = line.trim_end_matches('\r');
323 if line.is_empty() {
324 continue;
325 }
326
327 let ihex_record = ihex::Record::from_record_string(line)?;
328
329 match ihex_record {
330 ihex::Record::ExtendedLinearAddress(addr) => {
331 let new_base_address = if let Some(override_addr) = base_address_override {
332 let modified_addr =
334 (addr & 0x00FF) | ((override_addr >> 16) as u16 & 0xFF00);
335 (modified_addr as u32) << 16
336 } else {
337 (addr as u32) << 16
338 };
339
340 current_base_address = new_base_address;
343 }
344 ihex::Record::Data { offset, value } => {
345 let absolute_address = current_base_address + offset as u32;
346
347 let should_start_new_segment = if current_temp_file.is_some() {
349 Self::should_start_new_hex_segment(
350 current_segment_start,
351 current_file_offset,
352 absolute_address,
353 )
354 } else {
355 false };
357
358 if should_start_new_segment {
359 if let Some(temp_file) = current_temp_file.take() {
361 Self::finalize_segment(
362 temp_file,
363 current_segment_start,
364 &mut write_flash_files,
365 )?;
366 }
367 }
368
369 if current_temp_file.is_none() {
371 current_temp_file = Some(tempfile()?);
372 current_segment_start = absolute_address;
373 current_file_offset = 0;
374 }
375
376 if let Some(ref mut temp_file) = current_temp_file {
377 let expected_file_offset = absolute_address - current_segment_start;
378
379 if expected_file_offset > current_file_offset {
381 let gap_size = expected_file_offset - current_file_offset;
382 let fill_data = vec![Self::HEX_GAP_FILL_BYTE; gap_size as usize];
383 temp_file.write_all(&fill_data)?;
384 current_file_offset = expected_file_offset;
385 }
386
387 temp_file.write_all(&value)?;
389 current_file_offset += value.len() as u32;
390 }
391 }
392 ihex::Record::EndOfFile => {
393 if let Some(temp_file) = current_temp_file.take() {
395 Self::finalize_segment(
396 temp_file,
397 current_segment_start,
398 &mut write_flash_files,
399 )?;
400 }
401 break;
402 }
403 _ => {}
404 }
405 }
406
407 if let Some(temp_file) = current_temp_file.take() {
409 Self::finalize_segment(temp_file, current_segment_start, &mut write_flash_files)?;
410 }
411
412 Ok(write_flash_files)
413 }
414
415 pub fn elf_to_write_flash_files(elf_file: &Path) -> Result<Vec<WriteFlashFile>> {
417 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
418 const SECTOR_SIZE: u32 = 0x1000; const FILL_BYTE: u8 = 0xFF; let file = File::open(elf_file)?;
422 let mmap = unsafe { Mmap::map(&file)? };
423 let elf = goblin::elf::Elf::parse(&mmap[..])?;
424
425 let mut load_segments: Vec<_> = elf
427 .program_headers
428 .iter()
429 .filter(|ph| {
430 ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_paddr < 0x2000_0000
431 })
432 .collect();
433 load_segments.sort_by_key(|ph| ph.p_paddr);
434
435 if load_segments.is_empty() {
436 return Ok(write_flash_files);
437 }
438
439 let mut current_file = tempfile()?;
440 let mut current_base = (load_segments[0].p_paddr as u32) & !(SECTOR_SIZE - 1);
441 let mut current_offset = 0; for ph in load_segments.iter() {
444 let vaddr = ph.p_paddr as u32;
445 let offset = ph.p_offset as usize;
446 let size = ph.p_filesz as usize;
447 let data = &mmap[offset..offset + size];
448
449 let segment_base = vaddr & !(SECTOR_SIZE - 1);
451
452 if segment_base > current_base + current_offset {
454 current_file.seek(std::io::SeekFrom::Start(0))?;
455 let crc32 = Self::get_file_crc32(¤t_file)?;
456 write_flash_files.push(WriteFlashFile {
457 address: current_base,
458 file: std::mem::replace(&mut current_file, tempfile()?),
459 crc32,
460 });
461 current_base = segment_base;
462 current_offset = 0;
463 }
464
465 let relative_offset = vaddr - current_base;
467
468 if current_offset < relative_offset {
470 let padding = relative_offset - current_offset;
471 current_file.write_all(&vec![FILL_BYTE; padding as usize])?;
472 current_offset = relative_offset;
473 }
474
475 current_file.write_all(data)?;
477 current_offset += size as u32;
478 }
479
480 if current_offset > 0 {
482 current_file.seek(std::io::SeekFrom::Start(0))?;
483 let crc32 = Self::get_file_crc32(¤t_file)?;
484 write_flash_files.push(WriteFlashFile {
485 address: current_base,
486 file: current_file,
487 crc32,
488 });
489 }
490
491 Ok(write_flash_files)
492 }
493
494 fn finalize_segment(
496 mut temp_file: File,
497 address: u32,
498 write_flash_files: &mut Vec<WriteFlashFile>,
499 ) -> Result<()> {
500 temp_file.seek(std::io::SeekFrom::Start(0))?;
501 let crc32 = Self::get_file_crc32(&temp_file)?;
502 write_flash_files.push(WriteFlashFile {
503 address,
504 file: temp_file,
505 crc32,
506 });
507 Ok(())
508 }
509
510 fn should_start_new_hex_segment(
515 current_segment_start: u32,
516 current_file_offset: u32,
517 next_address: u32,
518 ) -> bool {
519 let current_end_address = current_segment_start.saturating_add(current_file_offset);
520 if next_address < current_end_address {
521 return true;
522 }
523
524 let gap_size = next_address - current_end_address;
525 if gap_size <= Self::HEX_SEGMENT_GAP_LIMIT {
526 return false;
527 }
528
529 next_address.is_multiple_of(Self::DEFAULT_HEX_SECTOR_SIZE)
530 }
531
532 pub fn parse_read_file_info(file_spec: &str) -> Result<crate::ReadFlashFile> {
534 let Some((file_path, addr_size)) = file_spec.split_once('@') else {
535 return Err(Error::invalid_input(format!(
536 "Invalid format: {}. Expected: filename@address:size",
537 file_spec
538 )));
539 };
540
541 let Some((address_str, size_str)) = addr_size.split_once(':') else {
542 return Err(Error::invalid_input(format!(
543 "Invalid address:size format: {}. Expected: address:size",
544 addr_size
545 )));
546 };
547
548 let address = Self::str_to_u32(address_str).map_err(|e| {
549 Error::invalid_input(format!("Invalid address '{}': {}", address_str, e))
550 })?;
551
552 let size = Self::str_to_u32(size_str)
553 .map_err(|e| Error::invalid_input(format!("Invalid size '{}': {}", size_str, e)))?;
554
555 Ok(crate::ReadFlashFile {
556 file_path: file_path.to_string(),
557 address,
558 size,
559 })
560 }
561
562 pub fn parse_erase_address(address_str: &str) -> Result<u32> {
564 Self::str_to_u32(address_str)
565 .map_err(|e| Error::invalid_input(format!("Invalid address '{}': {}", address_str, e)))
566 }
567
568 pub fn parse_erase_region(region_spec: &str) -> Result<crate::EraseRegionFile> {
570 let Some((address_str, size_str)) = region_spec.split_once(':') else {
571 return Err(Error::invalid_input(format!(
572 "Invalid region format: {}. Expected: address:size",
573 region_spec
574 )));
575 };
576
577 let address = Self::str_to_u32(address_str).map_err(|e| {
578 Error::invalid_input(format!("Invalid address '{}': {}", address_str, e))
579 })?;
580
581 let size = Self::str_to_u32(size_str)
582 .map_err(|e| Error::invalid_input(format!("Invalid size '{}': {}", size_str, e)))?;
583
584 Ok(crate::EraseRegionFile { address, size })
585 }
586}