sftool_lib/
utils.rs

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]; // ELF file magic number
18
19pub 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    /// 文件类型检测
76    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                _ => {} // 如果扩展名无法识别,继续检查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        Err(std::io::Error::new(
96            std::io::ErrorKind::InvalidInput,
97            "Unrecognized file type",
98        ))
99    }
100
101    /// 解析文件信息,支持file@address格式
102    pub fn parse_file_info(file_str: &str) -> Result<Vec<WriteFlashFile>, std::io::Error> {
103        // file@address
104        let parts: Vec<_> = file_str.split('@').collect();
105        // 如果存在@符号,需要先检查文件类型
106        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                    // 对于HEX文件,使用带基地址覆盖的处理函数
115                    return Self::hex_with_base_to_write_flash_files(
116                        Path::new(parts[0]),
117                        Some(addr),
118                    );
119                }
120                FileType::Elf => {
121                    // ELF文件不支持@地址格式
122                    return Err(std::io::Error::new(
123                        std::io::ErrorKind::InvalidInput,
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            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    /// 计算数据的CRC32
154    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    /// 将HEX文件转换为WriteFlashFile
169    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                    // We don't need to do anything special for ExtendedLinearAddress anymore
197                    // Just update the current_base_address for calculating absolute addresses
198                    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                    // Check if we need to start a new segment based on address continuity
204                    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                        // If the new data is not continuous with existing data, start new segment
209                        // Allow for some reasonable gap (e.g., 4KB) to be filled, but beyond that start new segment
210                        let gap_size = if expected_start_address >= current_end_address {
211                            expected_start_address - current_end_address
212                        } else {
213                            // Overlapping or backwards, definitely need new segment
214                            u32::MAX
215                        };
216
217                        // If gap is too large (> 4KB), start new segment
218                        gap_size > 0x1000
219                    } else {
220                        false // No current file, will create one below
221                    };
222
223                    if should_start_new_segment {
224                        // Finalize current segment
225                        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 this is the first data record or start of a new segment
235                    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                        // Fill gaps with 0xFF if they exist
245                        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                        // Write data
253                        temp_file.write_all(&value)?;
254                        current_file_offset += value.len() as u32;
255                    }
256                }
257                ihex::Record::EndOfFile => {
258                    // Finalize the last segment
259                    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 file ends without encountering EndOfFile record, finalize current segment
273        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    /// 将HEX文件转换为WriteFlashFile,支持基地址覆盖
281    /// base_address_override: 如果提供,将用其高8位替换ExtendedLinearAddress中的高8位
282    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                        // 只替换高8位:(原值 & 0x00FF) | ((新地址 >> 16) & 0xFF00)
310                        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                    // We don't need to do anything special for ExtendedLinearAddress anymore
318                    // Just update the current_base_address for calculating absolute addresses
319                    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                    // Check if we need to start a new segment based on address continuity
325                    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                        // If the new data is not continuous with existing data, start new segment
330                        // Allow for some reasonable gap (e.g., 4KB) to be filled, but beyond that start new segment
331                        let gap_size = if expected_start_address >= current_end_address {
332                            expected_start_address - current_end_address
333                        } else {
334                            // Overlapping or backwards, definitely need new segment
335                            u32::MAX
336                        };
337
338                        // If gap is too large (> 4KB), start new segment
339                        gap_size > 0x1000
340                    } else {
341                        false // No current file, will create one below
342                    };
343
344                    if should_start_new_segment {
345                        // Finalize current segment
346                        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 this is the first data record or start of a new segment
356                    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                        // Fill gaps with 0xFF if they exist
366                        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                        // Write data
374                        temp_file.write_all(&value)?;
375                        current_file_offset += value.len() as u32;
376                    }
377                }
378                ihex::Record::EndOfFile => {
379                    // Finalize the last segment
380                    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 file ends without encountering EndOfFile record, finalize current segment
394        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    /// 将ELF文件转换为WriteFlashFile  
402    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; // 扇区大小
407        const FILL_BYTE: u8 = 0xFF; // 填充字节
408
409        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        // 收集所有需要烧录的段
415        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; // 跟踪当前文件中的偏移量
431
432        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            // 计算当前段的对齐基地址
439            let segment_base = vaddr & !(SECTOR_SIZE - 1);
440
441            // 如果超出了当前对齐块,创建新文件
442            if segment_base > current_base + current_offset {
443                current_file.seek(std::io::SeekFrom::Start(0))?;
444                let crc32 = Self::get_file_crc32(&current_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            // 计算相对于当前文件基地址的偏移
455            let relative_offset = vaddr - current_base;
456
457            // 如果当前偏移小于目标偏移,填充间隙
458            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            // 写入数据
465            current_file.write_all(data)?;
466            current_offset += size as u32;
467        }
468
469        // 处理最后一个文件
470        if current_offset > 0 {
471            current_file.seek(std::io::SeekFrom::Start(0))?;
472            let crc32 = Self::get_file_crc32(&current_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    /// 完成一个段的处理,将临时文件转换为WriteFlashFile
484    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    /// 解析读取文件信息 (filename@address:size格式)
500    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    /// 解析擦除地址
543    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    /// 解析擦除区域信息 (address:size格式)
553    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}