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 parse_write_file(path: &str, address: Option<u32>) -> Result<Vec<WriteFlashFile>> {
150 let file_path = Path::new(path);
151 match address {
152 Some(addr) => {
153 let file_type = Self::detect_file_type(file_path)?;
154 match file_type {
155 FileType::Hex => {
156 Self::hex_with_base_to_write_flash_files(file_path, Some(addr))
157 }
158 FileType::Elf => Err(Error::invalid_input(
159 "ELF files do not support @address format",
160 )),
161 _ => {
162 let file = std::fs::File::open(file_path)?;
163 let crc32 = Self::get_file_crc32(&file)?;
164 Ok(vec![WriteFlashFile {
165 address: addr,
166 file,
167 crc32,
168 }])
169 }
170 }
171 }
172 None => {
173 let file_type = Self::detect_file_type(file_path)?;
174 match file_type {
175 FileType::Hex => Self::hex_to_write_flash_files(file_path),
176 FileType::Elf => Self::elf_to_write_flash_files(file_path),
177 _ => Err(Error::invalid_input(
178 "For binary files, please use the <file@address> format",
179 )),
180 }
181 }
182 }
183 }
184
185 pub fn calculate_crc32(data: &[u8]) -> u32 {
187 const CRC_32_ALGO: Algorithm<u32> = Algorithm {
188 width: 32,
189 poly: 0x04C11DB7,
190 init: 0,
191 refin: true,
192 refout: true,
193 xorout: 0,
194 check: 0,
195 residue: 0,
196 };
197 crc::Crc::<u32>::new(&CRC_32_ALGO).checksum(data)
198 }
199
200 pub fn hex_to_write_flash_files(hex_file: &Path) -> Result<Vec<WriteFlashFile>> {
202 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
203
204 let file = std::fs::File::open(hex_file)?;
205 let reader = std::io::BufReader::new(file);
206
207 let mut current_base_address = 0u32;
208 let mut current_temp_file: Option<File> = None;
209 let mut current_segment_start = 0u32;
210 let mut current_file_offset = 0u32;
211
212 for line in reader.lines() {
213 let line = line?;
214 let line = line.trim_end_matches('\r');
215 if line.is_empty() {
216 continue;
217 }
218
219 let ihex_record = ihex::Record::from_record_string(line)?;
220
221 match ihex_record {
222 ihex::Record::ExtendedLinearAddress(addr) => {
223 let new_base_address = (addr as u32) << 16;
224
225 current_base_address = new_base_address;
228 }
229 ihex::Record::Data { offset, value } => {
230 let absolute_address = current_base_address + offset as u32;
231
232 let should_start_new_segment = if let Some(ref _temp_file) = current_temp_file {
234 let current_end_address = current_segment_start + current_file_offset;
235 let expected_start_address = absolute_address;
236
237 let gap_size = if expected_start_address >= current_end_address {
240 expected_start_address - current_end_address
241 } else {
242 u32::MAX
244 };
245
246 gap_size > 0x1000
248 } else {
249 false };
251
252 if should_start_new_segment {
253 if let Some(temp_file) = current_temp_file.take() {
255 Self::finalize_segment(
256 temp_file,
257 current_segment_start,
258 &mut write_flash_files,
259 )?;
260 }
261 }
262
263 if current_temp_file.is_none() {
265 current_temp_file = Some(tempfile()?);
266 current_segment_start = absolute_address;
267 current_file_offset = 0;
268 }
269
270 if let Some(ref mut temp_file) = current_temp_file {
271 let expected_file_offset = absolute_address - current_segment_start;
272
273 if expected_file_offset > current_file_offset {
275 let gap_size = expected_file_offset - current_file_offset;
276 let fill_data = vec![0xFF; gap_size as usize];
277 temp_file.write_all(&fill_data)?;
278 current_file_offset = expected_file_offset;
279 }
280
281 temp_file.write_all(&value)?;
283 current_file_offset += value.len() as u32;
284 }
285 }
286 ihex::Record::EndOfFile => {
287 if let Some(temp_file) = current_temp_file.take() {
289 Self::finalize_segment(
290 temp_file,
291 current_segment_start,
292 &mut write_flash_files,
293 )?;
294 }
295 break;
296 }
297 _ => {}
298 }
299 }
300
301 if let Some(temp_file) = current_temp_file.take() {
303 Self::finalize_segment(temp_file, current_segment_start, &mut write_flash_files)?;
304 }
305
306 Ok(write_flash_files)
307 }
308
309 pub fn hex_with_base_to_write_flash_files(
312 hex_file: &Path,
313 base_address_override: Option<u32>,
314 ) -> Result<Vec<WriteFlashFile>> {
315 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
316
317 let file = std::fs::File::open(hex_file)?;
318 let reader = std::io::BufReader::new(file);
319
320 let mut current_base_address = 0u32;
321 let mut current_temp_file: Option<File> = None;
322 let mut current_segment_start = 0u32;
323 let mut current_file_offset = 0u32;
324
325 for line in reader.lines() {
326 let line = line?;
327 let line = line.trim_end_matches('\r');
328 if line.is_empty() {
329 continue;
330 }
331
332 let ihex_record = ihex::Record::from_record_string(line)?;
333
334 match ihex_record {
335 ihex::Record::ExtendedLinearAddress(addr) => {
336 let new_base_address = if let Some(override_addr) = base_address_override {
337 let modified_addr =
339 (addr & 0x00FF) | ((override_addr >> 16) as u16 & 0xFF00);
340 (modified_addr as u32) << 16
341 } else {
342 (addr as u32) << 16
343 };
344
345 current_base_address = new_base_address;
348 }
349 ihex::Record::Data { offset, value } => {
350 let absolute_address = current_base_address + offset as u32;
351
352 let should_start_new_segment = if let Some(ref _temp_file) = current_temp_file {
354 let current_end_address = current_segment_start + current_file_offset;
355 let expected_start_address = absolute_address;
356
357 let gap_size = if expected_start_address >= current_end_address {
360 expected_start_address - current_end_address
361 } else {
362 u32::MAX
364 };
365
366 gap_size > 0x1000
368 } else {
369 false };
371
372 if should_start_new_segment {
373 if let Some(temp_file) = current_temp_file.take() {
375 Self::finalize_segment(
376 temp_file,
377 current_segment_start,
378 &mut write_flash_files,
379 )?;
380 }
381 }
382
383 if current_temp_file.is_none() {
385 current_temp_file = Some(tempfile()?);
386 current_segment_start = absolute_address;
387 current_file_offset = 0;
388 }
389
390 if let Some(ref mut temp_file) = current_temp_file {
391 let expected_file_offset = absolute_address - current_segment_start;
392
393 if expected_file_offset > current_file_offset {
395 let gap_size = expected_file_offset - current_file_offset;
396 let fill_data = vec![0xFF; gap_size as usize];
397 temp_file.write_all(&fill_data)?;
398 current_file_offset = expected_file_offset;
399 }
400
401 temp_file.write_all(&value)?;
403 current_file_offset += value.len() as u32;
404 }
405 }
406 ihex::Record::EndOfFile => {
407 if let Some(temp_file) = current_temp_file.take() {
409 Self::finalize_segment(
410 temp_file,
411 current_segment_start,
412 &mut write_flash_files,
413 )?;
414 }
415 break;
416 }
417 _ => {}
418 }
419 }
420
421 if let Some(temp_file) = current_temp_file.take() {
423 Self::finalize_segment(temp_file, current_segment_start, &mut write_flash_files)?;
424 }
425
426 Ok(write_flash_files)
427 }
428
429 pub fn elf_to_write_flash_files(elf_file: &Path) -> Result<Vec<WriteFlashFile>> {
431 let mut write_flash_files: Vec<WriteFlashFile> = Vec::new();
432 const SECTOR_SIZE: u32 = 0x1000; const FILL_BYTE: u8 = 0xFF; let file = File::open(elf_file)?;
436 let mmap = unsafe { Mmap::map(&file)? };
437 let elf = goblin::elf::Elf::parse(&mmap[..])?;
438
439 let mut load_segments: Vec<_> = elf
441 .program_headers
442 .iter()
443 .filter(|ph| {
444 ph.p_type == goblin::elf::program_header::PT_LOAD && ph.p_paddr < 0x2000_0000
445 })
446 .collect();
447 load_segments.sort_by_key(|ph| ph.p_paddr);
448
449 if load_segments.is_empty() {
450 return Ok(write_flash_files);
451 }
452
453 let mut current_file = tempfile()?;
454 let mut current_base = (load_segments[0].p_paddr as u32) & !(SECTOR_SIZE - 1);
455 let mut current_offset = 0; for ph in load_segments.iter() {
458 let vaddr = ph.p_paddr as u32;
459 let offset = ph.p_offset as usize;
460 let size = ph.p_filesz as usize;
461 let data = &mmap[offset..offset + size];
462
463 let segment_base = vaddr & !(SECTOR_SIZE - 1);
465
466 if segment_base > current_base + current_offset {
468 current_file.seek(std::io::SeekFrom::Start(0))?;
469 let crc32 = Self::get_file_crc32(¤t_file)?;
470 write_flash_files.push(WriteFlashFile {
471 address: current_base,
472 file: std::mem::replace(&mut current_file, tempfile()?),
473 crc32,
474 });
475 current_base = segment_base;
476 current_offset = 0;
477 }
478
479 let relative_offset = vaddr - current_base;
481
482 if current_offset < relative_offset {
484 let padding = relative_offset - current_offset;
485 current_file.write_all(&vec![FILL_BYTE; padding as usize])?;
486 current_offset = relative_offset;
487 }
488
489 current_file.write_all(data)?;
491 current_offset += size as u32;
492 }
493
494 if current_offset > 0 {
496 current_file.seek(std::io::SeekFrom::Start(0))?;
497 let crc32 = Self::get_file_crc32(¤t_file)?;
498 write_flash_files.push(WriteFlashFile {
499 address: current_base,
500 file: current_file,
501 crc32,
502 });
503 }
504
505 Ok(write_flash_files)
506 }
507
508 fn finalize_segment(
510 mut temp_file: File,
511 address: u32,
512 write_flash_files: &mut Vec<WriteFlashFile>,
513 ) -> Result<()> {
514 temp_file.seek(std::io::SeekFrom::Start(0))?;
515 let crc32 = Self::get_file_crc32(&temp_file)?;
516 write_flash_files.push(WriteFlashFile {
517 address,
518 file: temp_file,
519 crc32,
520 });
521 Ok(())
522 }
523
524 pub fn parse_read_file_info(file_spec: &str) -> Result<crate::ReadFlashFile> {
526 let Some((file_path, addr_size)) = file_spec.split_once('@') else {
527 return Err(Error::invalid_input(format!(
528 "Invalid format: {}. Expected: filename@address:size",
529 file_spec
530 )));
531 };
532
533 let Some((address_str, size_str)) = addr_size.split_once(':') else {
534 return Err(Error::invalid_input(format!(
535 "Invalid address:size format: {}. Expected: address:size",
536 addr_size
537 )));
538 };
539
540 let address = Self::str_to_u32(address_str).map_err(|e| {
541 Error::invalid_input(format!("Invalid address '{}': {}", address_str, e))
542 })?;
543
544 let size = Self::str_to_u32(size_str)
545 .map_err(|e| Error::invalid_input(format!("Invalid size '{}': {}", size_str, e)))?;
546
547 Ok(crate::ReadFlashFile {
548 file_path: file_path.to_string(),
549 address,
550 size,
551 })
552 }
553
554 pub fn parse_erase_address(address_str: &str) -> Result<u32> {
556 Self::str_to_u32(address_str)
557 .map_err(|e| Error::invalid_input(format!("Invalid address '{}': {}", address_str, e)))
558 }
559
560 pub fn parse_erase_region(region_spec: &str) -> Result<crate::EraseRegionFile> {
562 let Some((address_str, size_str)) = region_spec.split_once(':') else {
563 return Err(Error::invalid_input(format!(
564 "Invalid region format: {}. Expected: address:size",
565 region_spec
566 )));
567 };
568
569 let address = Self::str_to_u32(address_str).map_err(|e| {
570 Error::invalid_input(format!("Invalid address '{}': {}", address_str, e))
571 })?;
572
573 let size = Self::str_to_u32(size_str)
574 .map_err(|e| Error::invalid_input(format!("Invalid size '{}': {}", size_str, e)))?;
575
576 Ok(crate::EraseRegionFile { address, size })
577 }
578}