1use crate::WriteFlashFile;
2use crc::Algorithm;
3use memmap2::Mmap;
4use std::fs::File;
5use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
6use std::num::ParseIntError;
7use std::path::Path;
8use tempfile::tempfile;
9
10#[derive(Debug, PartialEq, Eq, Clone)]
11pub enum FileType {
12 Bin,
13 Hex,
14 Elf,
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, ParseIntError> {
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, std::io::Error> {
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, std::io::Error> {
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 Err(std::io::Error::new(
96 std::io::ErrorKind::InvalidInput,
97 "Unrecognized file type",
98 ))
99 }
100
101 pub fn parse_file_info(file_str: &str) -> Result<Vec<WriteFlashFile>, std::io::Error> {
103 let parts: Vec<_> = file_str.split('@').collect();
105 if parts.len() == 2 {
107 let addr = Self::str_to_u32(parts[1])
108 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
109
110 let file_type = Self::detect_file_type(Path::new(parts[0]))?;
111
112 match file_type {
113 FileType::Hex => {
114 return Self::hex_with_base_to_write_flash_files(
116 Path::new(parts[0]),
117 Some(addr),
118 );
119 }
120 FileType::Elf => {
121 return Err(std::io::Error::new(
123 std::io::ErrorKind::InvalidInput,
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 FileType::Bin => Err(std::io::Error::new(
147 std::io::ErrorKind::InvalidInput,
148 "For binary files, please use the <file@address> format",
149 )),
150 }
151 }
152
153 pub fn calculate_crc32(data: &[u8]) -> u32 {
155 const CRC_32_ALGO: Algorithm<u32> = Algorithm {
156 width: 32,
157 poly: 0x04C11DB7,
158 init: 0,
159 refin: true,
160 refout: true,
161 xorout: 0,
162 check: 0,
163 residue: 0,
164 };
165 crc::Crc::<u32>::new(&CRC_32_ALGO).checksum(data)
166 }
167
168 pub fn hex_to_write_flash_files(
170 hex_file: &Path,
171 ) -> Result<Vec<WriteFlashFile>, std::io::Error> {
172 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
173
174 let file = std::fs::File::open(hex_file)?;
175 let reader = std::io::BufReader::new(file);
176
177 let mut current_base_address = 0u32;
178 let mut current_temp_file: Option<File> = None;
179 let mut current_segment_start = 0u32;
180 let mut current_file_offset = 0u32;
181
182 for line in reader.lines() {
183 let line = line?;
184 let line = line.trim_end_matches('\r');
185 if line.is_empty() {
186 continue;
187 }
188
189 let ihex_record = ihex::Record::from_record_string(&line)
190 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
191
192 match ihex_record {
193 ihex::Record::ExtendedLinearAddress(addr) => {
194 let new_base_address = (addr as u32) << 16;
195
196 current_base_address = new_base_address;
199 }
200 ihex::Record::Data { offset, value } => {
201 let absolute_address = current_base_address + offset as u32;
202
203 let should_start_new_segment = if let Some(ref _temp_file) = current_temp_file {
205 let current_end_address = current_segment_start + current_file_offset;
206 let expected_start_address = absolute_address;
207
208 let gap_size = if expected_start_address >= current_end_address {
211 expected_start_address - current_end_address
212 } else {
213 u32::MAX
215 };
216
217 gap_size > 0x1000
219 } else {
220 false };
222
223 if should_start_new_segment {
224 if let Some(temp_file) = current_temp_file.take() {
226 Self::finalize_segment(
227 temp_file,
228 current_segment_start,
229 &mut write_flash_files,
230 )?;
231 }
232 }
233
234 if current_temp_file.is_none() {
236 current_temp_file = Some(tempfile()?);
237 current_segment_start = absolute_address;
238 current_file_offset = 0;
239 }
240
241 if let Some(ref mut temp_file) = current_temp_file {
242 let expected_file_offset = absolute_address - current_segment_start;
243
244 if expected_file_offset > current_file_offset {
246 let gap_size = expected_file_offset - current_file_offset;
247 let fill_data = vec![0xFF; gap_size as usize];
248 temp_file.write_all(&fill_data)?;
249 current_file_offset = expected_file_offset;
250 }
251
252 temp_file.write_all(&value)?;
254 current_file_offset += value.len() as u32;
255 }
256 }
257 ihex::Record::EndOfFile => {
258 if let Some(temp_file) = current_temp_file.take() {
260 Self::finalize_segment(
261 temp_file,
262 current_segment_start,
263 &mut write_flash_files,
264 )?;
265 }
266 break;
267 }
268 _ => {}
269 }
270 }
271
272 if let Some(temp_file) = current_temp_file.take() {
274 Self::finalize_segment(temp_file, current_segment_start, &mut write_flash_files)?;
275 }
276
277 Ok(write_flash_files)
278 }
279
280 pub fn hex_with_base_to_write_flash_files(
283 hex_file: &Path,
284 base_address_override: Option<u32>,
285 ) -> Result<Vec<WriteFlashFile>, std::io::Error> {
286 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
287
288 let file = std::fs::File::open(hex_file)?;
289 let reader = std::io::BufReader::new(file);
290
291 let mut current_base_address = 0u32;
292 let mut current_temp_file: Option<File> = None;
293 let mut current_segment_start = 0u32;
294 let mut current_file_offset = 0u32;
295
296 for line in reader.lines() {
297 let line = line?;
298 let line = line.trim_end_matches('\r');
299 if line.is_empty() {
300 continue;
301 }
302
303 let ihex_record = ihex::Record::from_record_string(&line)
304 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
305
306 match ihex_record {
307 ihex::Record::ExtendedLinearAddress(addr) => {
308 let new_base_address = if let Some(override_addr) = base_address_override {
309 let modified_addr =
311 (addr & 0x00FF) | ((override_addr >> 16) as u16 & 0xFF00);
312 (modified_addr as u32) << 16
313 } else {
314 (addr as u32) << 16
315 };
316
317 current_base_address = new_base_address;
320 }
321 ihex::Record::Data { offset, value } => {
322 let absolute_address = current_base_address + offset as u32;
323
324 let should_start_new_segment = if let Some(ref _temp_file) = current_temp_file {
326 let current_end_address = current_segment_start + current_file_offset;
327 let expected_start_address = absolute_address;
328
329 let gap_size = if expected_start_address >= current_end_address {
332 expected_start_address - current_end_address
333 } else {
334 u32::MAX
336 };
337
338 gap_size > 0x1000
340 } else {
341 false };
343
344 if should_start_new_segment {
345 if let Some(temp_file) = current_temp_file.take() {
347 Self::finalize_segment(
348 temp_file,
349 current_segment_start,
350 &mut write_flash_files,
351 )?;
352 }
353 }
354
355 if current_temp_file.is_none() {
357 current_temp_file = Some(tempfile()?);
358 current_segment_start = absolute_address;
359 current_file_offset = 0;
360 }
361
362 if let Some(ref mut temp_file) = current_temp_file {
363 let expected_file_offset = absolute_address - current_segment_start;
364
365 if expected_file_offset > current_file_offset {
367 let gap_size = expected_file_offset - current_file_offset;
368 let fill_data = vec![0xFF; gap_size as usize];
369 temp_file.write_all(&fill_data)?;
370 current_file_offset = expected_file_offset;
371 }
372
373 temp_file.write_all(&value)?;
375 current_file_offset += value.len() as u32;
376 }
377 }
378 ihex::Record::EndOfFile => {
379 if let Some(temp_file) = current_temp_file.take() {
381 Self::finalize_segment(
382 temp_file,
383 current_segment_start,
384 &mut write_flash_files,
385 )?;
386 }
387 break;
388 }
389 _ => {}
390 }
391 }
392
393 if let Some(temp_file) = current_temp_file.take() {
395 Self::finalize_segment(temp_file, current_segment_start, &mut write_flash_files)?;
396 }
397
398 Ok(write_flash_files)
399 }
400
401 pub fn elf_to_write_flash_files(
403 elf_file: &Path,
404 ) -> Result<Vec<WriteFlashFile>, std::io::Error> {
405 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
406 const SECTOR_SIZE: u32 = 0x1000; const FILL_BYTE: u8 = 0xFF; let file = File::open(elf_file)?;
410 let mmap = unsafe { Mmap::map(&file)? };
411 let elf = goblin::elf::Elf::parse(&mmap[..])
412 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
413
414 let mut load_segments: Vec<_> = elf
416 .program_headers
417 .iter()
418 .filter(|ph| {
419 ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_paddr < 0x2000_0000
420 })
421 .collect();
422 load_segments.sort_by_key(|ph| ph.p_paddr);
423
424 if load_segments.is_empty() {
425 return Ok(write_flash_files);
426 }
427
428 let mut current_file = tempfile()?;
429 let mut current_base = (load_segments[0].p_paddr as u32) & !(SECTOR_SIZE - 1);
430 let mut current_offset = 0; for ph in load_segments.iter() {
433 let vaddr = ph.p_paddr as u32;
434 let offset = ph.p_offset as usize;
435 let size = ph.p_filesz as usize;
436 let data = &mmap[offset..offset + size];
437
438 let segment_base = vaddr & !(SECTOR_SIZE - 1);
440
441 if segment_base > current_base + current_offset {
443 current_file.seek(std::io::SeekFrom::Start(0))?;
444 let crc32 = Self::get_file_crc32(¤t_file)?;
445 write_flash_files.push(WriteFlashFile {
446 address: current_base,
447 file: std::mem::replace(&mut current_file, tempfile()?),
448 crc32,
449 });
450 current_base = segment_base;
451 current_offset = 0;
452 }
453
454 let relative_offset = vaddr - current_base;
456
457 if current_offset < relative_offset {
459 let padding = relative_offset - current_offset;
460 current_file.write_all(&vec![FILL_BYTE; padding as usize])?;
461 current_offset = relative_offset;
462 }
463
464 current_file.write_all(data)?;
466 current_offset += size as u32;
467 }
468
469 if current_offset > 0 {
471 current_file.seek(std::io::SeekFrom::Start(0))?;
472 let crc32 = Self::get_file_crc32(¤t_file)?;
473 write_flash_files.push(WriteFlashFile {
474 address: current_base,
475 file: current_file,
476 crc32,
477 });
478 }
479
480 Ok(write_flash_files)
481 }
482
483 fn finalize_segment(
485 mut temp_file: File,
486 address: u32,
487 write_flash_files: &mut Vec<WriteFlashFile>,
488 ) -> Result<(), std::io::Error> {
489 temp_file.seek(std::io::SeekFrom::Start(0))?;
490 let crc32 = Self::get_file_crc32(&temp_file)?;
491 write_flash_files.push(WriteFlashFile {
492 address,
493 file: temp_file,
494 crc32,
495 });
496 Ok(())
497 }
498
499 pub fn parse_read_file_info(file_spec: &str) -> Result<crate::ReadFlashFile, std::io::Error> {
501 let Some((file_path, addr_size)) = file_spec.split_once('@') else {
502 return Err(std::io::Error::new(
503 std::io::ErrorKind::InvalidInput,
504 format!(
505 "Invalid format: {}. Expected: filename@address:size",
506 file_spec
507 ),
508 ));
509 };
510
511 let Some((address_str, size_str)) = addr_size.split_once(':') else {
512 return Err(std::io::Error::new(
513 std::io::ErrorKind::InvalidInput,
514 format!(
515 "Invalid address:size format: {}. Expected: address:size",
516 addr_size
517 ),
518 ));
519 };
520
521 let address = Self::str_to_u32(address_str).map_err(|e| {
522 std::io::Error::new(
523 std::io::ErrorKind::InvalidInput,
524 format!("Invalid address '{}': {}", address_str, e),
525 )
526 })?;
527
528 let size = Self::str_to_u32(size_str).map_err(|e| {
529 std::io::Error::new(
530 std::io::ErrorKind::InvalidInput,
531 format!("Invalid size '{}': {}", size_str, e),
532 )
533 })?;
534
535 Ok(crate::ReadFlashFile {
536 file_path: file_path.to_string(),
537 address,
538 size,
539 })
540 }
541
542 pub fn parse_erase_address(address_str: &str) -> Result<u32, std::io::Error> {
544 Self::str_to_u32(address_str).map_err(|e| {
545 std::io::Error::new(
546 std::io::ErrorKind::InvalidInput,
547 format!("Invalid address '{}': {}", address_str, e),
548 )
549 })
550 }
551
552 pub fn parse_erase_region(region_spec: &str) -> Result<crate::EraseRegionFile, std::io::Error> {
554 let Some((address_str, size_str)) = region_spec.split_once(':') else {
555 return Err(std::io::Error::new(
556 std::io::ErrorKind::InvalidInput,
557 format!(
558 "Invalid region format: {}. Expected: address:size",
559 region_spec
560 ),
561 ));
562 };
563
564 let address = Self::str_to_u32(address_str).map_err(|e| {
565 std::io::Error::new(
566 std::io::ErrorKind::InvalidInput,
567 format!("Invalid address '{}': {}", address_str, e),
568 )
569 })?;
570
571 let size = Self::str_to_u32(size_str).map_err(|e| {
572 std::io::Error::new(
573 std::io::ErrorKind::InvalidInput,
574 format!("Invalid size '{}': {}", size_str, e),
575 )
576 })?;
577
578 Ok(crate::EraseRegionFile { address, size })
579 }
580}