From 6283014489a20fdca5da098d0445239fc6d5a426 Mon Sep 17 00:00:00 2001 From: HalfSweet Date: Tue, 3 Mar 2026 17:28:18 +0800 Subject: [PATCH] fix(hex): avoid splitting on non-sector-aligned large gaps Keep HEX data in one segment when a later block starts off 4KB boundary. For large gaps, only split at aligned sector starts; otherwise fill gap bytes with 0xFF. Also add regression tests for both hex parsing paths --- sftool-lib/src/utils.rs | 72 +++++++++++++++++++--------------- sftool-lib/tests/utils_test.rs | 54 +++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 32 deletions(-) diff --git a/sftool-lib/src/utils.rs b/sftool-lib/src/utils.rs index dc223f2..9fe1d2f 100644 --- a/sftool-lib/src/utils.rs +++ b/sftool-lib/src/utils.rs @@ -18,6 +18,10 @@ pub const ELF_MAGIC: &[u8] = &[0x7F, 0x45, 0x4C, 0x46]; // ELF file magic number pub struct Utils; impl Utils { + const HEX_SEGMENT_GAP_LIMIT: u32 = 0x1000; + const DEFAULT_HEX_SECTOR_SIZE: u32 = 0x1000; + const HEX_GAP_FILL_BYTE: u8 = 0xFF; + pub fn str_to_u32(s: &str) -> Result { let s = s.trim(); @@ -230,21 +234,12 @@ impl Utils { let absolute_address = current_base_address + offset as u32; // Check if we need to start a new segment based on address continuity - let should_start_new_segment = if let Some(ref _temp_file) = current_temp_file { - let current_end_address = current_segment_start + current_file_offset; - let expected_start_address = absolute_address; - - // If the new data is not continuous with existing data, start new segment - // Allow for some reasonable gap (e.g., 4KB) to be filled, but beyond that start new segment - let gap_size = if expected_start_address >= current_end_address { - expected_start_address - current_end_address - } else { - // Overlapping or backwards, definitely need new segment - u32::MAX - }; - - // If gap is too large (> 4KB), start new segment - gap_size > 0x1000 + let should_start_new_segment = if current_temp_file.is_some() { + Self::should_start_new_hex_segment( + current_segment_start, + current_file_offset, + absolute_address, + ) } else { false // No current file, will create one below }; @@ -273,7 +268,7 @@ impl Utils { // Fill gaps with 0xFF if they exist if expected_file_offset > current_file_offset { let gap_size = expected_file_offset - current_file_offset; - let fill_data = vec![0xFF; gap_size as usize]; + let fill_data = vec![Self::HEX_GAP_FILL_BYTE; gap_size as usize]; temp_file.write_all(&fill_data)?; current_file_offset = expected_file_offset; } @@ -350,21 +345,12 @@ impl Utils { let absolute_address = current_base_address + offset as u32; // Check if we need to start a new segment based on address continuity - let should_start_new_segment = if let Some(ref _temp_file) = current_temp_file { - let current_end_address = current_segment_start + current_file_offset; - let expected_start_address = absolute_address; - - // If the new data is not continuous with existing data, start new segment - // Allow for some reasonable gap (e.g., 4KB) to be filled, but beyond that start new segment - let gap_size = if expected_start_address >= current_end_address { - expected_start_address - current_end_address - } else { - // Overlapping or backwards, definitely need new segment - u32::MAX - }; - - // If gap is too large (> 4KB), start new segment - gap_size > 0x1000 + let should_start_new_segment = if current_temp_file.is_some() { + Self::should_start_new_hex_segment( + current_segment_start, + current_file_offset, + absolute_address, + ) } else { false // No current file, will create one below }; @@ -393,7 +379,7 @@ impl Utils { // Fill gaps with 0xFF if they exist if expected_file_offset > current_file_offset { let gap_size = expected_file_offset - current_file_offset; - let fill_data = vec![0xFF; gap_size as usize]; + let fill_data = vec![Self::HEX_GAP_FILL_BYTE; gap_size as usize]; temp_file.write_all(&fill_data)?; current_file_offset = expected_file_offset; } @@ -521,6 +507,28 @@ impl Utils { Ok(()) } + /// HEX段分段策略: + /// - 地址回退/重叠:分段 + /// - 间隙 <= 4KB:不分段(以0xFF填充) + /// - 间隙 > 4KB:只有下一段起始地址为sector对齐时才分段 + fn should_start_new_hex_segment( + current_segment_start: u32, + current_file_offset: u32, + next_address: u32, + ) -> bool { + let current_end_address = current_segment_start.saturating_add(current_file_offset); + if next_address < current_end_address { + return true; + } + + let gap_size = next_address - current_end_address; + if gap_size <= Self::HEX_SEGMENT_GAP_LIMIT { + return false; + } + + next_address.is_multiple_of(Self::DEFAULT_HEX_SECTOR_SIZE) + } + /// 解析读取文件信息 (filename@address:size格式) pub fn parse_read_file_info(file_spec: &str) -> Result { let Some((file_path, addr_size)) = file_spec.split_once('@') else { diff --git a/sftool-lib/tests/utils_test.rs b/sftool-lib/tests/utils_test.rs index 65a77d1..24c05ab 100644 --- a/sftool-lib/tests/utils_test.rs +++ b/sftool-lib/tests/utils_test.rs @@ -425,6 +425,33 @@ fn test_hex_non_continuous_segments_not_merged() { assert_eq!(file_size_1, 4); } +#[test] +fn test_hex_non_aligned_large_gap_segments_are_merged() { + // Test that a large-gap second segment is still merged when it is not sector aligned + // 0x1201FFF0 is not aligned to 4KB sector boundary. + let hex_content = ":020000041201E7\n:0400000001020304F2\n:04FFF0001122334463\n:00000001FF\n"; + + let mut temp_hex = NamedTempFile::new().unwrap(); + temp_hex.write_all(hex_content.as_bytes()).unwrap(); + + let result = Utils::hex_to_write_flash_files(temp_hex.path()).unwrap(); + + // Should stay as a single segment because second block is not 4KB-aligned + assert_eq!(result.len(), 1); + assert_eq!(result[0].address, 0x12010000); + + let file_size = result[0].file.metadata().unwrap().len() as usize; + assert_eq!(file_size, 0xFFF4); + + let mut file_data = Vec::new(); + let mut file = &result[0].file; + file.read_to_end(&mut file_data).unwrap(); + + assert_eq!(&file_data[0..4], &[0x01, 0x02, 0x03, 0x04]); + assert!(file_data[4..0xFFF0].iter().all(|&b| b == 0xFF)); + assert_eq!(&file_data[0xFFF0..0xFFF4], &[0x11, 0x22, 0x33, 0x44]); +} + #[test] fn test_hex_with_base_continuous_segments_merging() { // Test continuous segment merging with base address override @@ -459,6 +486,33 @@ fn test_hex_with_base_continuous_segments_merging() { ); } +#[test] +fn test_hex_with_base_non_aligned_large_gap_segments_are_merged() { + // Test the same behavior via hex_with_base_to_write_flash_files path. + let hex_content = ":020000040801F1\n:0400000001020304F2\n:04FFF0001122334463\n:00000001FF\n"; + + let mut temp_hex = NamedTempFile::new().unwrap(); + temp_hex.write_all(hex_content.as_bytes()).unwrap(); + + // 0x0801 will be replaced to 0x1201 when override is 0x12000000 + let result = + Utils::hex_with_base_to_write_flash_files(temp_hex.path(), Some(0x12000000)).unwrap(); + + assert_eq!(result.len(), 1); + assert_eq!(result[0].address, 0x12010000); + + let file_size = result[0].file.metadata().unwrap().len() as usize; + assert_eq!(file_size, 0xFFF4); + + let mut file_data = Vec::new(); + let mut file = &result[0].file; + file.read_to_end(&mut file_data).unwrap(); + + assert_eq!(&file_data[0..4], &[0x01, 0x02, 0x03, 0x04]); + assert!(file_data[4..0xFFF0].iter().all(|&b| b == 0xFF)); + assert_eq!(&file_data[0xFFF0..0xFFF4], &[0x11, 0x22, 0x33, 0x44]); +} + #[test] fn test_hex_continuous_with_gaps_still_merged() { // Test that segments are merged even when there are internal gaps (filled with 0xFF)