Skip to main content

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    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    /// 文件类型检测
80    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                _ => {} // 如果扩展名无法识别,继续检查MAGIC
87            }
88        }
89
90        // 如果没有可识别的扩展名,则检查文件MAGIC
91        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        // 如果MAGIC也无法识别,返回Unknown
100        Ok(FileType::Unknown)
101    }
102
103    /// 解析文件信息,支持file@address格式
104    pub fn parse_file_info(file_str: &str) -> Result<Vec<WriteFlashFile>> {
105        // file@address
106        let parts: Vec<_> = file_str.split('@').collect();
107        // 如果存在@符号,需要先检查文件类型
108        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                    // 对于HEX文件,使用带基地址覆盖的处理函数
116                    return Self::hex_with_base_to_write_flash_files(
117                        Path::new(parts[0]),
118                        Some(addr),
119                    );
120                }
121                FileType::Elf => {
122                    // ELF文件不支持@地址格式
123                    return Err(Error::invalid_input(
124                        "ELF files do not support @address format",
125                    ));
126                }
127                _ => {
128                    // 对于其他文件类型,使用原来的处理方式
129                    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    /// 解析写入文件信息,直接使用路径与可选地址
153    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    /// 计算数据的CRC32
190    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    /// 将HEX文件转换为WriteFlashFile
205    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                    // We don't need to do anything special for ExtendedLinearAddress anymore
230                    // Just update the current_base_address for calculating absolute addresses
231                    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                    // Check if we need to start a new segment based on address continuity
237                    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 // No current file, will create one below
245                    };
246
247                    if should_start_new_segment {
248                        // Finalize current segment
249                        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 this is the first data record or start of a new segment
259                    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                        // Fill gaps with 0xFF if they exist
269                        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                        // Write data
277                        temp_file.write_all(&value)?;
278                        current_file_offset += value.len() as u32;
279                    }
280                }
281                ihex::Record::EndOfFile => {
282                    // Finalize the last segment
283                    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 file ends without encountering EndOfFile record, finalize current segment
297        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    /// 将HEX文件转换为WriteFlashFile,支持基地址覆盖
305    /// base_address_override: 如果提供,将用其高8位替换ExtendedLinearAddress中的高8位
306    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                        // 只替换高8位:(原值 & 0x00FF) | ((新地址 >> 16) & 0xFF00)
333                        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                    // We don't need to do anything special for ExtendedLinearAddress anymore
341                    // Just update the current_base_address for calculating absolute addresses
342                    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                    // Check if we need to start a new segment based on address continuity
348                    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 // No current file, will create one below
356                    };
357
358                    if should_start_new_segment {
359                        // Finalize current segment
360                        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 this is the first data record or start of a new segment
370                    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                        // Fill gaps with 0xFF if they exist
380                        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                        // Write data
388                        temp_file.write_all(&value)?;
389                        current_file_offset += value.len() as u32;
390                    }
391                }
392                ihex::Record::EndOfFile => {
393                    // Finalize the last segment
394                    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 file ends without encountering EndOfFile record, finalize current segment
408        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    /// 将ELF文件转换为WriteFlashFile  
416    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; // 扇区大小
419        const FILL_BYTE: u8 = 0xFF; // 填充字节
420
421        let file = File::open(elf_file)?;
422        let mmap = unsafe { Mmap::map(&file)? };
423        let elf = goblin::elf::Elf::parse(&mmap[..])?;
424
425        // 收集所有需要烧录的段
426        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; // 跟踪当前文件中的偏移量
442
443        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            // 计算当前段的对齐基地址
450            let segment_base = vaddr & !(SECTOR_SIZE - 1);
451
452            // 如果超出了当前对齐块,创建新文件
453            if segment_base > current_base + current_offset {
454                current_file.seek(std::io::SeekFrom::Start(0))?;
455                let crc32 = Self::get_file_crc32(&current_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            // 计算相对于当前文件基地址的偏移
466            let relative_offset = vaddr - current_base;
467
468            // 如果当前偏移小于目标偏移,填充间隙
469            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            // 写入数据
476            current_file.write_all(data)?;
477            current_offset += size as u32;
478        }
479
480        // 处理最后一个文件
481        if current_offset > 0 {
482            current_file.seek(std::io::SeekFrom::Start(0))?;
483            let crc32 = Self::get_file_crc32(&current_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    /// 完成一个段的处理,将临时文件转换为WriteFlashFile
495    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    /// HEX段分段策略:
511    /// - 地址回退/重叠:分段
512    /// - 间隙 <= 4KB:不分段(以0xFF填充)
513    /// - 间隙 > 4KB:只有下一段起始地址为sector对齐时才分段
514    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    /// 解析读取文件信息 (filename@address:size格式)
533    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    /// 解析擦除地址
563    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    /// 解析擦除区域信息 (address:size格式)
569    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}