sftool_lib/
utils.rs

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]; // ELF file magic number
18
19pub 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    /// 文件类型检测
76    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                _ => {} // 如果扩展名无法识别,继续检查MAGIC
83            }
84        }
85
86        // 如果没有可识别的扩展名,则检查文件MAGIC
87        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        // 如果MAGIC也无法识别,返回Unknown
96        Ok(FileType::Unknown)
97    }
98
99    /// 解析文件信息,支持file@address格式
100    pub fn parse_file_info(file_str: &str) -> Result<Vec<WriteFlashFile>> {
101        // file@address
102        let parts: Vec<_> = file_str.split('@').collect();
103        // 如果存在@符号,需要先检查文件类型
104        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                    // 对于HEX文件,使用带基地址覆盖的处理函数
112                    return Self::hex_with_base_to_write_flash_files(
113                        Path::new(parts[0]),
114                        Some(addr),
115                    );
116                }
117                FileType::Elf => {
118                    // ELF文件不支持@地址格式
119                    return Err(Error::invalid_input(
120                        "ELF files do not support @address format",
121                    ));
122                }
123                _ => {
124                    // 对于其他文件类型,使用原来的处理方式
125                    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    /// 解析写入文件信息,直接使用路径与可选地址
149    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    /// 计算数据的CRC32
186    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    /// 将HEX文件转换为WriteFlashFile
201    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                    // We don't need to do anything special for ExtendedLinearAddress anymore
226                    // Just update the current_base_address for calculating absolute addresses
227                    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                    // Check if we need to start a new segment based on address continuity
233                    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                        // If the new data is not continuous with existing data, start new segment
238                        // Allow for some reasonable gap (e.g., 4KB) to be filled, but beyond that start new segment
239                        let gap_size = if expected_start_address >= current_end_address {
240                            expected_start_address - current_end_address
241                        } else {
242                            // Overlapping or backwards, definitely need new segment
243                            u32::MAX
244                        };
245
246                        // If gap is too large (> 4KB), start new segment
247                        gap_size > 0x1000
248                    } else {
249                        false // No current file, will create one below
250                    };
251
252                    if should_start_new_segment {
253                        // Finalize current segment
254                        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 this is the first data record or start of a new segment
264                    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                        // Fill gaps with 0xFF if they exist
274                        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                        // Write data
282                        temp_file.write_all(&value)?;
283                        current_file_offset += value.len() as u32;
284                    }
285                }
286                ihex::Record::EndOfFile => {
287                    // Finalize the last segment
288                    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 file ends without encountering EndOfFile record, finalize current segment
302        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    /// 将HEX文件转换为WriteFlashFile,支持基地址覆盖
310    /// base_address_override: 如果提供,将用其高8位替换ExtendedLinearAddress中的高8位
311    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                        // 只替换高8位:(原值 & 0x00FF) | ((新地址 >> 16) & 0xFF00)
338                        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                    // We don't need to do anything special for ExtendedLinearAddress anymore
346                    // Just update the current_base_address for calculating absolute addresses
347                    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                    // Check if we need to start a new segment based on address continuity
353                    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                        // If the new data is not continuous with existing data, start new segment
358                        // Allow for some reasonable gap (e.g., 4KB) to be filled, but beyond that start new segment
359                        let gap_size = if expected_start_address >= current_end_address {
360                            expected_start_address - current_end_address
361                        } else {
362                            // Overlapping or backwards, definitely need new segment
363                            u32::MAX
364                        };
365
366                        // If gap is too large (> 4KB), start new segment
367                        gap_size > 0x1000
368                    } else {
369                        false // No current file, will create one below
370                    };
371
372                    if should_start_new_segment {
373                        // Finalize current segment
374                        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 this is the first data record or start of a new segment
384                    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                        // Fill gaps with 0xFF if they exist
394                        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                        // Write data
402                        temp_file.write_all(&value)?;
403                        current_file_offset += value.len() as u32;
404                    }
405                }
406                ihex::Record::EndOfFile => {
407                    // Finalize the last segment
408                    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 file ends without encountering EndOfFile record, finalize current segment
422        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    /// 将ELF文件转换为WriteFlashFile  
430    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; // 扇区大小
433        const FILL_BYTE: u8 = 0xFF; // 填充字节
434
435        let file = File::open(elf_file)?;
436        let mmap = unsafe { Mmap::map(&file)? };
437        let elf = goblin::elf::Elf::parse(&mmap[..])?;
438
439        // 收集所有需要烧录的段
440        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; // 跟踪当前文件中的偏移量
456
457        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            // 计算当前段的对齐基地址
464            let segment_base = vaddr & !(SECTOR_SIZE - 1);
465
466            // 如果超出了当前对齐块,创建新文件
467            if segment_base > current_base + current_offset {
468                current_file.seek(std::io::SeekFrom::Start(0))?;
469                let crc32 = Self::get_file_crc32(&current_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            // 计算相对于当前文件基地址的偏移
480            let relative_offset = vaddr - current_base;
481
482            // 如果当前偏移小于目标偏移,填充间隙
483            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            // 写入数据
490            current_file.write_all(data)?;
491            current_offset += size as u32;
492        }
493
494        // 处理最后一个文件
495        if current_offset > 0 {
496            current_file.seek(std::io::SeekFrom::Start(0))?;
497            let crc32 = Self::get_file_crc32(&current_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    /// 完成一个段的处理,将临时文件转换为WriteFlashFile
509    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    /// 解析读取文件信息 (filename@address:size格式)
525    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    /// 解析擦除地址
555    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    /// 解析擦除区域信息 (address:size格式)
561    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}