Skip to main content

intelhex/
intelhex.rs

1use std::collections::BTreeMap;
2use std::io::{BufRead, BufReader, BufWriter, Write};
3
4use super::error::{IntelHexError, MemoryError, ParseError};
5
6/// Represents the execution start address in Intel HEX format
7#[derive(Debug)]
8pub enum ExecutionStartAddress {
9    /// Segment address (CS:IP) for 20-bit addressing
10    StartSegmentAddress { cs: u16, ip: u16 },
11    /// Linear address (EIP) for 32-bit addressing
12    StartLinearAddress { eip: u32 },
13}
14
15/// Represents a continuous block of memory data
16#[derive(Debug)]
17pub struct MemoryDataBlock {
18    /// Starting address of the memory block
19    pub address: u32,
20    /// Continuous data bytes stored in this block
21    pub data: Vec<u8>,
22}
23
24/// Main structure for handling Intel HEX format data
25pub struct IntelHex {
26    /// Optional execution start address (if specified in the HEX file)
27    execution_start_address: Option<ExecutionStartAddress>,
28    /// Map of memory addresses to their corresponding data bytes
29    memory_data_cells: BTreeMap<u32, u8>,
30}
31
32enum AddressMode {
33    SegmentAddress(u32),
34    LinearAddress(u32),
35}
36
37impl IntelHex {
38    /// Creates a new empty IntelHex instance
39    ///
40    /// Initializes a new IntelHex object with:
41    /// * An empty memory cells map (no data bytes stored)
42    /// * No execution start address
43    ///
44    /// # Returns
45    ///
46    /// Returns a new empty IntelHex instance
47    ///
48    /// # Examples
49    ///
50    /// ```
51    /// use intelhex::IntelHex;
52    ///
53    /// let intel_hex = IntelHex::new();
54    ///
55    /// // Memory cells map is empty
56    /// assert!(intel_hex.memory_cells().is_empty());
57    ///
58    /// // No execution start address
59    /// assert!(intel_hex.execution_start_address().is_none());
60    /// ```
61    pub fn new() -> IntelHex {
62        Self {
63            execution_start_address: None,
64            memory_data_cells: BTreeMap::new(),
65        }
66    }
67
68    /// Returns a reference to the memory cells map
69    ///
70    /// The memory cells are stored in a BTreeMap where:
71    /// * Keys (u32) are memory addresses
72    /// * Values (u8) are the data bytes stored at those addresses
73    /// * Addresses are automatically sorted in ascending order
74    ///
75    /// # Returns
76    ///
77    /// Returns a reference to the BTreeMap containing all memory cells data
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// use intelhex::IntelHex;
83    ///
84    /// let hex_text = ":10010000214601360121470136007EFE09D2190140\n:00000001FF";
85    /// let intel_hex = IntelHex::from_intel_hex_text(hex_text).unwrap();
86    ///
87    /// // Access memory cells
88    /// let memory = intel_hex.memory_cells();
89    ///
90    /// // Check if an address contains data
91    /// if let Some(&value) = memory.get(&0x100) {
92    ///     println!("Value at 0x100: {:02X}", value);
93    /// }
94    ///
95    /// // Iterate through all memory cells in address order
96    /// for (&addr, &value) in memory.iter() {
97    ///     println!("Address: {:08X}, Value: {:02X}", addr, value);
98    /// }
99    /// ```
100    pub fn memory_cells(&self) -> &BTreeMap<u32, u8> {
101        &self.memory_data_cells
102    }
103
104    /// Returns a reference to the execution start address if it exists
105    ///
106    /// The execution start address can be either a segment address (CS:IP) or
107    /// a linear address (EIP), as defined in the Intel HEX format.
108    ///
109    /// # Returns
110    ///
111    /// * `Some(&ExecutionStartAddress)` - A reference to the execution start address if it was specified
112    /// * `None` - If no execution start address was specified
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// use intelhex::IntelHex;
118    /// use intelhex::ExecutionStartAddress;
119    ///
120    /// let hex_text = ":0400000300100020C9\n:00000001FF";  // Contains a start segment address
121    /// let intel_hex = IntelHex::from_intel_hex_text(hex_text).unwrap();
122    ///
123    /// if let Some(start_addr) = intel_hex.execution_start_address() {
124    ///     match start_addr {
125    ///         ExecutionStartAddress::StartSegmentAddress { cs, ip } => {
126    ///             println!("CS:{:04X}, IP:{:04X}", cs, ip)
127    ///         },
128    ///         ExecutionStartAddress::StartLinearAddress { eip } => {
129    ///             println!("EIP:{:08X}", eip)
130    ///         },
131    ///     }
132    /// }
133    /// ```
134    pub fn execution_start_address(&self) -> Option<&ExecutionStartAddress> {
135        self.execution_start_address.as_ref()
136    }
137
138    /// Creates an IntelHex instance from a reader
139    ///
140    /// # Arguments
141    ///
142    /// * `reader` - Any type that implements the `Read` trait
143    ///
144    /// # Returns
145    ///
146    /// * `Ok(IntelHex)` - A new IntelHex instance containing the parsed data
147    /// * `Err(IntelHexError)` - If there was an error reading or parsing the data
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use std::fs::File;
153    /// use intelhex::IntelHex;
154    ///
155    /// let file = File::open("tests/fixtures/firmware.hex").unwrap();
156    /// let intel_hex = IntelHex::from_reader(file).unwrap();
157    /// ```
158    pub fn from_reader<R>(reader: R) -> Result<IntelHex, IntelHexError>
159    where
160        R: std::io::Read,
161    {
162        let mut intelhex = IntelHex::new();
163        let reader = BufReader::new(reader);
164
165        let mut base_address = AddressMode::LinearAddress(0);
166        for line in reader.lines() {
167            let line = line?;
168            if line.is_empty() {
169                continue;
170            }
171
172            match intelhex.decode_record(&line, &mut base_address) {
173                Ok(true) => break,
174                Err(error) => return Err(error),
175                _ => (),
176            }
177        }
178
179        Ok(intelhex)
180    }
181
182    /// Creates an IntelHex instance from Intel HEX format text
183    ///
184    /// # Arguments
185    ///
186    /// * `hex_text` - A string slice containing Intel HEX formatted data
187    ///
188    /// # Returns
189    ///
190    /// * `Ok(IntelHex)` - A new IntelHex instance containing the parsed data
191    /// * `Err(IntelHexError)` - If the input string is not a valid Intel HEX format
192    ///
193    /// # Examples
194    ///
195    /// ```
196    /// use intelhex::IntelHex;
197    ///
198    /// let hex_text = ":10010000214601360121470136007EFE09D2190140\n:00000001FF";
199    /// let result = IntelHex::from_intel_hex_text(hex_text).unwrap();
200    /// ```
201    pub fn from_intel_hex_text(hex_text: &str) -> Result<IntelHex, IntelHexError> {
202        Self::from_reader(hex_text.as_bytes())
203    }
204
205    /// Decodes a single Intel HEX record line
206    ///
207    /// # Arguments
208    ///
209    /// * `line` - A string slice containing a single Intel HEX record
210    /// * `base_address` - A mutable reference to the current base address
211    ///
212    /// # Returns
213    ///
214    /// * `Ok(bool)` - Returns `true` if this is an End of File record, `false` otherwise
215    /// * `Err(IntelHexError)` - If there was an error decoding the record
216    ///
217    /// # Record Types Supported
218    ///
219    /// * 00 - Data Record
220    /// * 01 - End of File Record
221    /// * 02 - Extended Segment Address Record
222    /// * 03 - Start Segment Address Record
223    /// * 04 - Extended Linear Address Record
224    /// * 05 - Start Linear Address Record
225    fn decode_record(
226        &mut self,
227        line: &str,
228        base_address: &mut AddressMode,
229    ) -> Result<bool, IntelHexError> {
230        let mut line_chars_iter = line.chars();
231
232        // Check for start code
233        if let Some(':') = line_chars_iter.next() {
234        } else {
235            return Err(ParseError::MissingStartCode.into());
236        }
237
238        // Validate hex characters and length
239        let mut hexdigit_length = 0;
240        while let Some(c) = line_chars_iter.next() {
241            hexdigit_length += 1;
242            if !c.is_ascii_hexdigit() {
243                return Err(ParseError::InvalidCharacters.into());
244            }
245        }
246
247        // Validate record length
248        if hexdigit_length < ((1 + 2 + 1 + 1) * 2) {
249            return Err(ParseError::RecordTooShort.into());
250        } else if hexdigit_length > ((1 + 2 + 1 + 255 + 1) * 2) {
251            return Err(ParseError::RecordTooLong.into());
252        } else if hexdigit_length % 2 != 0 {
253            return Err(ParseError::RecordNotEvenLength.into());
254        }
255
256        // Convert hex characters to bytes
257        let mut record_bytes = Vec::new();
258        for i in (1..hexdigit_length).step_by(2) {
259            record_bytes.push(u8::from_str_radix(&line[i..i + 2], 16).unwrap());
260        }
261
262        // Verify checksum
263        let expected_chksum = record_bytes.pop().unwrap();
264        let chksum = (0 as u8).wrapping_sub(
265            record_bytes
266                .iter()
267                .fold(0, |acc: u8, x| acc.wrapping_add(*x)),
268        );
269        if chksum != expected_chksum {
270            return Err(ParseError::ChecksumMismatch(chksum, expected_chksum).into());
271        }
272
273        // Parse record fields
274        let record_length = record_bytes[0] as usize;
275        let record_offset = ((record_bytes[1] as u16) << 8) | (record_bytes[2] as u16);
276        let record_type = record_bytes[3];
277        let record_payload = &record_bytes[4..];
278
279        // Validate payload length
280        if record_payload.len() != record_length {
281            return Err(ParseError::PayloadLengthMismatch.into());
282        }
283
284        // Process record based on type
285        match record_type {
286            0 => {
287                // Data Record
288                for i in 0..record_length {
289                    let address = match base_address {
290                        // Segment addressing mode:
291                        // Physical Address = SBA + ([DRLO + DRI] MOD 65536)
292                        // where:
293                        // - SBA (Segment Base Address) = base
294                        // - DRLO (Data Record Load Offset) = record_offset
295                        // - DRI (Data Record Index) = i
296                        // - MOD 65536 is achieved through u16 wrapping addition
297                        AddressMode::SegmentAddress(base) => base
298                            .wrapping_add(((record_offset as u16).wrapping_add(i as u16)) as u32),
299                        // Linear addressing mode:
300                        // Physical Address = Base Address + DRLO + DRI
301                        // where:
302                        // - Base Address = base (upper 16 bits)
303                        // - DRLO (Data Record Load Offset) = record_offset
304                        // - DRI (Data Record Index) = i
305                        AddressMode::LinearAddress(base) => base
306                            .wrapping_add(record_offset as u32)
307                            .wrapping_add(i as u32),
308                    };
309                    if let None = self.memory_data_cells.get(&address) {
310                        self.memory_data_cells.insert(address, record_payload[i]);
311                    } else {
312                        return Err(MemoryError::AddressOverlap.into());
313                    }
314                }
315            }
316
317            1 => {
318                // End of File Record
319                if record_length != 0 {
320                    return Err(ParseError::InvalidLengthForType.into());
321                } else if record_offset != 0 {
322                    return Err(ParseError::InvalidLoadOffset.into());
323                }
324                return Ok(true);
325            }
326
327            2 => {
328                // Extended Segment Address Record
329                if record_length != 2 {
330                    return Err(ParseError::InvalidLengthForType.into());
331                } else if record_offset != 0 {
332                    return Err(ParseError::InvalidLoadOffset.into());
333                } else {
334                    *base_address = AddressMode::SegmentAddress(
335                        (((record_payload[0] as u32) << 8) | (record_payload[1] as u32)) << 4,
336                    );
337                }
338            }
339
340            3 => {
341                // Start Segment Address Record
342                if record_length != 4 {
343                    return Err(ParseError::InvalidLengthForType.into());
344                } else if record_offset != 0 {
345                    return Err(ParseError::InvalidLoadOffset.into());
346                } else {
347                    let cs: u16 = ((record_payload[0] as u16) << 8) | record_payload[1] as u16;
348                    let ip: u16 = ((record_payload[2] as u16) << 8) | record_payload[3] as u16;
349                    if let None = self.execution_start_address {
350                        self.execution_start_address =
351                            Some(ExecutionStartAddress::StartSegmentAddress { cs, ip });
352                    } else {
353                        return Err(ParseError::MultipleStartAddressRecord.into());
354                    }
355                }
356            }
357
358            4 => {
359                // Extended Linear Address Record
360                if record_length != 2 {
361                    return Err(ParseError::InvalidLengthForType.into());
362                } else if record_offset != 0 {
363                    return Err(ParseError::InvalidLoadOffset.into());
364                } else {
365                    *base_address = AddressMode::LinearAddress(
366                        (((record_payload[0] as u32) << 8) | (record_payload[1] as u32)) << 16,
367                    );
368                }
369            }
370
371            5 => {
372                // Start Linear Address Record
373                if record_length != 4 {
374                    return Err(ParseError::InvalidLengthForType.into());
375                } else if record_offset != 0 {
376                    return Err(ParseError::InvalidLoadOffset.into());
377                } else {
378                    let eip: u32 = ((record_payload[0] as u32) << 24)
379                        | ((record_payload[1] as u32) << 16)
380                        | ((record_payload[2] as u32) << 8)
381                        | record_payload[3] as u32;
382                    if let None = self.execution_start_address {
383                        self.execution_start_address =
384                            Some(ExecutionStartAddress::StartLinearAddress { eip });
385                    } else {
386                        return Err(ParseError::MultipleStartAddressRecord.into());
387                    }
388                }
389            }
390
391            _ => return Err(ParseError::UnsupportedRecordType(record_type).into()),
392        }
393
394        Ok(false)
395    }
396
397    /// Writes Intel HEX format data to a writer
398    ///
399    /// # Arguments
400    ///
401    /// * `writer` - Any type that implements the `Write` trait
402    /// * `data_byte_count` - Number of data bytes per Data Record (1-255).
403    ///                       A value of 16 is recommended for general use.
404    ///
405    /// # Returns
406    ///
407    /// * `Ok(())` - If the data was successfully written
408    /// * `Err(std::io::Error)` - If there was an error writing the data
409    ///
410    /// # Examples
411    ///
412    /// ```no_run
413    /// use intelhex::IntelHex;
414    /// use std::fs::File;
415    ///
416    /// let intel_hex = IntelHex::new();
417    /// let mut file = File::create("output.hex").unwrap();
418    /// intel_hex.to_writer(&mut file, 16).unwrap();  // 16 data bytes per data record
419    /// ```
420    pub fn to_writer<W>(&self, writer: W, data_byte_count: u8) -> Result<(), std::io::Error>
421    where
422        W: std::io::Write,
423    {
424        // Since data_byte_count is u8, it's automatically limited to 0-255
425        // We only need to check the lower bound (0)
426        if data_byte_count == 0 {
427            return Err(std::io::Error::new(
428                std::io::ErrorKind::InvalidInput,
429                "data_byte_count must be between 1 and 255",
430            ));
431        }
432
433        let mut writer = BufWriter::new(writer);
434        let mut is_first = true;
435        let mut base_address: u32 = 0;
436        let mut continuous_bytes: Vec<u8> = Vec::with_capacity(data_byte_count as usize);
437        let mut load_offset = 0;
438        let mut last_addr = 0;
439
440        // Write data records
441        for (addr, val) in &self.memory_data_cells {
442            let mut need_addr_record = false;
443            let mut is_addr_continuous = false;
444
445            if is_first {
446                base_address = *addr & 0xFFFF0000;
447                need_addr_record = true;
448                last_addr = *addr;
449                is_first = false;
450            }
451
452            if *addr - base_address > 0xFFFF {
453                need_addr_record = true;
454            }
455
456            if *addr == (last_addr + 1) {
457                is_addr_continuous = true;
458            }
459
460            if (!is_addr_continuous)
461                || (continuous_bytes.len() >= data_byte_count as usize)
462                || need_addr_record
463            {
464                if !continuous_bytes.is_empty() {
465                    // Data Record
466                    let bytes_length = continuous_bytes.len();
467                    let mut chksum: u8 = (bytes_length as u8)
468                        .wrapping_add((load_offset >> 8) as u8)
469                        .wrapping_add(load_offset as u8);
470
471                    write!(writer, ":{:02X}{:04X}00", bytes_length, load_offset)?;
472
473                    for v in &continuous_bytes {
474                        chksum = chksum.wrapping_add(*v);
475                        write!(writer, "{:02X}", *v)?;
476                    }
477                    chksum = (0 as u8).wrapping_sub(chksum);
478
479                    #[cfg(windows)]
480                    write!(writer, "{:02X}\r\n", chksum)?;
481                    #[cfg(not(windows))]
482                    write!(writer, "{:02X}\n", chksum)?;
483
484                    continuous_bytes.clear();
485                    load_offset = *addr - base_address;
486                }
487            }
488
489            if need_addr_record {
490                base_address = *addr & 0xFFFF0000;
491                load_offset = *addr - base_address;
492
493                // Extended Linear Address Record
494                let mut chksum: u8 = 2 + 4;
495                chksum = chksum.wrapping_add((base_address >> 24) as u8);
496                chksum = chksum.wrapping_add((base_address >> 16) as u8);
497                chksum = (0 as u8).wrapping_sub(chksum);
498
499                #[cfg(windows)]
500                write!(
501                    writer,
502                    ":02000004{:04X}{:02X}\r\n",
503                    base_address >> 16,
504                    chksum
505                )?;
506                #[cfg(not(windows))]
507                write!(
508                    writer,
509                    ":02000004{:04X}{:02X}\n",
510                    base_address >> 16,
511                    chksum
512                )?;
513            }
514
515            continuous_bytes.push(*val);
516            last_addr = *addr;
517        }
518
519        if !continuous_bytes.is_empty() {
520            // Data Record
521            let bytes_length = continuous_bytes.len();
522            let mut chksum: u8 = (bytes_length as u8)
523                .wrapping_add((load_offset >> 8) as u8)
524                .wrapping_add(load_offset as u8);
525
526            write!(writer, ":{:02X}{:04X}00", bytes_length, load_offset)?;
527
528            for v in &continuous_bytes {
529                chksum = chksum.wrapping_add(*v);
530                write!(writer, "{:02X}", *v)?;
531            }
532            chksum = (0 as u8).wrapping_sub(chksum);
533
534            #[cfg(windows)]
535            write!(writer, "{:02X}\r\n", chksum)?;
536            #[cfg(not(windows))]
537            write!(writer, "{:02X}\n", chksum)?;
538        }
539
540        // Write execution start address if present
541        if let Some(execution_start_address) = &self.execution_start_address {
542            match *execution_start_address {
543                ExecutionStartAddress::StartSegmentAddress { cs, ip } => {
544                    // Start Segment Address Record
545                    let mut chksum: u8 = 4 + 3;
546                    chksum = chksum.wrapping_add((cs >> 8) as u8);
547                    chksum = chksum.wrapping_add(cs as u8);
548                    chksum = chksum.wrapping_add((ip >> 8) as u8);
549                    chksum = chksum.wrapping_add(ip as u8);
550                    chksum = (0 as u8).wrapping_sub(chksum);
551
552                    #[cfg(windows)]
553                    write!(writer, ":04000003{:04X}{:04X}{:02X}\r\n", cs, ip, chksum)?;
554                    #[cfg(not(windows))]
555                    write!(writer, ":04000003{:04X}{:04X}{:02X}\n", cs, ip, chksum)?;
556                }
557
558                ExecutionStartAddress::StartLinearAddress { eip } => {
559                    // Start Linear Address Record
560                    let mut chksum: u8 = 4 + 5;
561                    chksum = chksum.wrapping_add((eip >> 24) as u8);
562                    chksum = chksum.wrapping_add((eip >> 16) as u8);
563                    chksum = chksum.wrapping_add((eip >> 8) as u8);
564                    chksum = chksum.wrapping_add(eip as u8);
565                    chksum = (0 as u8).wrapping_sub(chksum);
566
567                    #[cfg(windows)]
568                    write!(writer, ":04000005{:08X}{:02X}\r\n", eip, chksum)?;
569                    #[cfg(not(windows))]
570                    write!(writer, ":04000005{:08X}{:02X}\n", eip, chksum)?;
571                }
572            }
573        }
574
575        // End of File Record
576        #[cfg(windows)]
577        write!(writer, ":00000001FF\r\n")?;
578        #[cfg(not(windows))]
579        write!(writer, ":00000001FF\n")?;
580        writer.flush()?;
581
582        Ok(())
583    }
584
585    /// Converts the IntelHex object to Intel HEX format text
586    ///
587    /// # Returns
588    ///
589    /// * `Ok(String)` - A string containing the Intel HEX formatted data
590    /// * `Err(std::io::Error)` - If there was an error writing the data
591    pub fn to_intel_hex_text(&self) -> Result<String, std::io::Error> {
592        let mut buf: Vec<u8> = Vec::new();
593        self.to_writer(&mut buf, 16)?;
594        Ok(String::from_utf8(buf).unwrap())
595    }
596
597    /// Converts memory data into a collection of continuous data blocks
598    ///
599    /// # Returns
600    ///
601    /// Returns a vector of MemoryDataBlock, each containing:
602    /// * address: Starting address of the data block
603    /// * data: Vector of continuous data bytes
604    pub fn memory_blocks(&self) -> Vec<MemoryDataBlock> {
605        let mut blocks = Vec::new();
606        if self.memory_data_cells.is_empty() {
607            return blocks;
608        }
609
610        let mut current_block = MemoryDataBlock {
611            address: 0,
612            data: Vec::new(),
613        };
614        let mut last_address: Option<u32> = None;
615
616        for (&address, &value) in &self.memory_data_cells {
617            match last_address {
618                None => {
619                    // First data point
620                    current_block.address = address;
621                    current_block.data.push(value);
622                }
623                Some(last_addr) => {
624                    if address == last_addr + 1 {
625                        // Continuous address
626                        current_block.data.push(value);
627                    } else {
628                        // Discontinuous address, create new block
629                        blocks.push(std::mem::replace(
630                            &mut current_block,
631                            MemoryDataBlock {
632                                address,
633                                data: vec![value],
634                            },
635                        ));
636                    }
637                }
638            }
639            last_address = Some(address);
640        }
641
642        // Add the last block
643        if !current_block.data.is_empty() {
644            blocks.push(current_block);
645        }
646
647        blocks
648    }
649
650    /// Adds data from a MemoryDataBlock to the memory cells
651    ///
652    /// # Arguments
653    ///
654    /// * `block` - The MemoryDataBlock containing address and data to add
655    ///
656    /// # Returns
657    ///
658    /// * `Ok(())` - If the data was successfully added
659    /// * `Err(IntelHexError)` - If there was an error adding the data
660    ///
661    /// # Examples
662    ///
663    /// ```
664    /// use intelhex::{IntelHex, MemoryDataBlock};
665    ///
666    /// let mut intel_hex = IntelHex::new();
667    /// let block = MemoryDataBlock {
668    ///     address: 0x1000,
669    ///     data: vec![0x12, 0x34, 0x56],
670    /// };
671    ///
672    /// // Add the memory block
673    /// intel_hex.add_memory_block(&block).unwrap();
674    ///
675    /// // Verify the data was added
676    /// let memory = intel_hex.memory_cells();
677    /// assert_eq!(memory.get(&0x1000), Some(&0x12));
678    /// assert_eq!(memory.get(&0x1001), Some(&0x34));
679    /// assert_eq!(memory.get(&0x1002), Some(&0x56));
680    /// ```
681    pub fn add_memory_block(&mut self, block: &MemoryDataBlock) -> Result<(), IntelHexError> {
682        // Check for address overlap before adding any data
683        for i in 0..block.data.len() {
684            let address = block.address.wrapping_add(i as u32);
685            if self.memory_data_cells.contains_key(&address) {
686                return Err(MemoryError::AddressOverlap.into());
687            }
688        }
689
690        // Add all data bytes to memory cells
691        for (i, &value) in block.data.iter().enumerate() {
692            let address = block.address.wrapping_add(i as u32);
693            self.memory_data_cells.insert(address, value);
694        }
695
696        Ok(())
697    }
698}
699
700#[cfg(test)]
701mod tests {
702    use super::*;
703
704    // Test helper function to create a simple IntelHex instance with test data
705    fn create_test_hex() -> IntelHex {
706        let hex_text = "\
707            :10010000214601360121470136007EFE09D2190140\n\
708            :100110002146017EB7C20001FF5F16002148011988\n\
709            :00000001FF";
710        IntelHex::from_intel_hex_text(hex_text).unwrap()
711    }
712
713    #[test]
714    fn test_new() {
715        // Test creating a new empty IntelHex instance
716        let hex = IntelHex::new();
717        assert!(hex.memory_cells().is_empty());
718        assert!(hex.execution_start_address().is_none());
719    }
720
721    #[test]
722    fn test_basic_parsing() {
723        let hex = create_test_hex();
724        let memory = hex.memory_cells();
725
726        // Verify first data record was parsed correctly
727        assert_eq!(memory.get(&0x0100), Some(&0x21));
728        assert_eq!(memory.get(&0x0101), Some(&0x46));
729
730        // Verify second data record was parsed correctly
731        assert_eq!(memory.get(&0x0110), Some(&0x21));
732        assert_eq!(memory.get(&0x0111), Some(&0x46));
733    }
734
735    #[test]
736    fn test_start_segment_address() {
737        // Test parsing of Start Segment Address Record
738        let hex_text = ":0400000300100020C9\n:00000001FF";
739        let hex = IntelHex::from_intel_hex_text(hex_text).unwrap();
740
741        match hex.execution_start_address().unwrap() {
742            ExecutionStartAddress::StartSegmentAddress { cs, ip } => {
743                assert_eq!(*cs, 0x0010);
744                assert_eq!(*ip, 0x0020);
745            }
746            _ => panic!("Wrong execution start address type"),
747        }
748    }
749
750    #[test]
751    fn test_start_linear_address() {
752        // Test parsing of Start Linear Address Record
753        let hex_text = ":0400000512345678E3\n:00000001FF";
754        let hex = IntelHex::from_intel_hex_text(hex_text).unwrap();
755
756        match hex.execution_start_address().unwrap() {
757            ExecutionStartAddress::StartLinearAddress { eip } => {
758                assert_eq!(*eip, 0x12345678);
759            }
760            _ => panic!("Wrong execution start address type"),
761        }
762    }
763
764    #[test]
765    fn test_extended_segment_address() {
766        // Test parsing with Extended Segment Address Record
767        let hex_text = "\
768            :020000021000EC\n\
769            :10010000214601360121470136007EFE09D2190140\n\
770            :00000001FF";
771        let hex = IntelHex::from_intel_hex_text(hex_text).unwrap();
772        let memory = hex.memory_cells();
773
774        // Base address should be 0x10000 + offset
775        assert_eq!(memory.get(&0x10100), Some(&0x21));
776        assert_eq!(memory.get(&0x10101), Some(&0x46));
777    }
778
779    #[test]
780    fn test_extended_linear_address() {
781        // Test parsing with Extended Linear Address Record
782        let hex_text = "\
783            :02000004FFFFFC\n\
784            :10010000214601360121470136007EFE09D2190140\n\
785            :00000001FF";
786        let hex = IntelHex::from_intel_hex_text(hex_text).unwrap();
787        let memory = hex.memory_cells();
788
789        // Base address should be 0xFFFF0000 + offset
790        assert_eq!(memory.get(&0xFFFF0100), Some(&0x21));
791        assert_eq!(memory.get(&0xFFFF0101), Some(&0x46));
792    }
793
794    #[test]
795    fn test_memory_blocks() {
796        let mut hex = IntelHex::new();
797
798        // Add discontinuous memory blocks
799        let block1 = MemoryDataBlock {
800            address: 0x1000,
801            data: vec![0x11, 0x22, 0x33],
802        };
803        let block2 = MemoryDataBlock {
804            address: 0x2000,
805            data: vec![0x44, 0x55],
806        };
807
808        hex.add_memory_block(&block1).unwrap();
809        hex.add_memory_block(&block2).unwrap();
810
811        let blocks = hex.memory_blocks();
812        assert_eq!(blocks.len(), 2);
813
814        // Verify first block
815        assert_eq!(blocks[0].address, 0x1000);
816        assert_eq!(blocks[0].data, vec![0x11, 0x22, 0x33]);
817
818        // Verify second block
819        assert_eq!(blocks[1].address, 0x2000);
820        assert_eq!(blocks[1].data, vec![0x44, 0x55]);
821    }
822
823    #[test]
824    fn test_error_handling() {
825        // Test invalid start code
826        let result = IntelHex::from_intel_hex_text("10010000214601360121470136007EFE09D2190140");
827        assert!(matches!(
828            result,
829            Err(IntelHexError::Parse(ParseError::MissingStartCode))
830        ));
831
832        // Test invalid checksum
833        let result = IntelHex::from_intel_hex_text(":10010000214601360121470136007EFE09D2190141");
834        assert!(matches!(
835            result,
836            Err(IntelHexError::Parse(ParseError::ChecksumMismatch(_, _)))
837        ));
838
839        // Test address overlap
840        let mut hex = IntelHex::new();
841        let block1 = MemoryDataBlock {
842            address: 0x1000,
843            data: vec![0x11, 0x22],
844        };
845        let block2 = MemoryDataBlock {
846            address: 0x1001,
847            data: vec![0x33, 0x44],
848        };
849
850        hex.add_memory_block(&block1).unwrap();
851        assert!(matches!(
852            hex.add_memory_block(&block2),
853            Err(IntelHexError::Memory(MemoryError::AddressOverlap))
854        ));
855    }
856}