use std::collections::BTreeMap;
use std::fs::File;
use std::io::{ErrorKind, Read, Seek};
use color_eyre::eyre::{Report, Result, eyre};
use log::debug;
use super::FirmwareStorage;
pub struct IntelHexFirmwareFile
{
segments: BTreeMap<u32, Box<[u8]>>,
firmware_image: Box<[u8]>,
}
struct IntelHexRecord
{
pub byte_count: u8,
pub address: u16,
pub record_type: IntelHexRecordType,
pub data: [u8; 255],
}
#[repr(u8)]
#[non_exhaustive]
enum IntelHexRecordType
{
Data = 0x00,
EndOfFile = 0x01,
ExtendedSegmentAddress = 0x02,
StartSegmentAddress = 0x03,
ExtendedLinearAddress = 0x04,
StartLinearAddress = 0x05,
}
struct IntelHexSegment
{
base_address: u32,
data: Box<[u8]>,
}
impl TryFrom<File> for IntelHexFirmwareFile
{
type Error = Report;
fn try_from(mut file: File) -> Result<Self>
{
debug!("Loading file as Intel HEX firmware binary");
let mut records = Vec::new();
let mut buf = [0];
let mut eof = false;
while !eof {
records.push(IntelHexRecord::try_from(&mut file)?);
while !eof {
match file.read(&mut buf) {
Ok(0) => eof = true,
Ok(_) => {
if !matches!(buf[0], b'\r' | b'\n') {
file.seek_relative(-1)?;
break;
}
},
Err(ref error) if error.kind() == ErrorKind::Interrupted => {},
Err(error) => return Err(error.into()),
}
}
}
debug!("Read {} records", records.len());
let segments = IntelHexSegment::from_records(&records)?
.into_iter()
.map(|segment| (segment.base_address, segment.data))
.collect();
let mut result = Self {
segments,
firmware_image: Box::default(),
};
result.build_firmware_image();
Ok(result)
}
}
impl IntelHexFirmwareFile
{
fn build_firmware_image(&mut self)
{
let load_address = self.load_address().unwrap_or(0);
debug!("Firmware image loads at 0x{load_address:08x}");
let total_length = self
.segments
.iter()
.map(|(&base_address, segment)| base_address..base_address + segment.len() as u32)
.reduce(|a, b| a.start..b.end)
.map(|range| range.len())
.unwrap_or(0);
debug!("Firmware is {total_length} bytes long flattened");
let mut firmware_image = vec![0xffu8; total_length].into_boxed_slice();
for (base_address, segment) in &self.segments {
let begin = (base_address - load_address) as usize;
let range = begin..begin + segment.len();
firmware_image[range].copy_from_slice(segment);
}
self.firmware_image = firmware_image;
}
}
impl IntelHexSegment
{
pub fn from_records(records: &[IntelHexRecord]) -> Result<Box<[Self]>>
{
let mut base_address = 0;
let mut begin_address = 0;
let mut end_address = 0;
let mut segment_data = Vec::new();
let mut segments = Vec::new();
for (idx, record) in records.iter().enumerate() {
match record.record_type {
IntelHexRecordType::Data => {
let address = base_address + record.address as u32;
let length = record.byte_count as usize;
if end_address != address {
if end_address != begin_address {
segments.push(Self {
base_address: begin_address,
data: segment_data.into_boxed_slice(),
});
}
begin_address = address;
end_address = address;
segment_data = Vec::new();
}
segment_data.extend_from_slice(&record.data[0..length]);
end_address += length as u32;
},
IntelHexRecordType::EndOfFile => {
if idx + 1 != records.len() {
return Err(eyre!("Premature EOF record found, invalid Intel HEX file"));
}
if end_address != begin_address {
segments.push(Self {
base_address: begin_address,
data: segment_data.into_boxed_slice(),
});
segment_data = Vec::new();
}
},
IntelHexRecordType::ExtendedLinearAddress => {
let bytes = record.data[0..2].try_into()?;
let address_high = u16::from_be_bytes(bytes);
base_address = (address_high as u32) << 16;
},
IntelHexRecordType::StartLinearAddress => {
let bytes = record.data[0..4].try_into()?;
base_address = u32::from_be_bytes(bytes);
},
_ => todo!(),
}
}
debug!("Recovered {} segments from data stream", segments.len());
Ok(segments.into_boxed_slice())
}
}
impl FirmwareStorage for IntelHexFirmwareFile
{
fn load_address(&self) -> Option<u32>
{
self.segments.first_key_value().map(|(&address, _)| address)
}
fn firmware_data(&self) -> &[u8]
{
&self.firmware_image
}
}
impl TryFrom<&mut File> for IntelHexRecord
{
type Error = Report;
fn try_from(file: &mut File) -> Result<Self>
{
let mut data = [0];
loop {
file.read_exact(&mut data)?;
if data[0] == b':' {
break;
}
}
let mut actual_checksum = 0u8;
let mut data = [0; 2];
file.read_exact(&mut data)?;
let byte_count = u8::from_str_radix(str::from_utf8(&data)?, 16)?;
actual_checksum = actual_checksum.wrapping_add(byte_count);
let mut data = [0; 4];
file.read_exact(&mut data)?;
let address = u16::from_str_radix(str::from_utf8(&data)?, 16)?;
actual_checksum = actual_checksum
.wrapping_add((address >> 8) as u8)
.wrapping_add(address as u8);
let mut data = [0; 2];
file.read_exact(&mut data)?;
let record_type = u8::from_str_radix(str::from_utf8(&data)?, 16)?;
actual_checksum = actual_checksum.wrapping_add(record_type);
let len = byte_count as usize;
let mut bytes = vec![0; len * 2];
file.read_exact(&mut bytes[0..(len * 2)])?;
for idx in 0..len {
let begin = idx * 2;
let end = begin + 2;
bytes[idx] = u8::from_str_radix(str::from_utf8(&bytes[begin..end])?, 16)?;
actual_checksum = actual_checksum.wrapping_add(bytes[idx]);
}
let mut data = [0; 2];
file.read_exact(&mut data)?;
let expected_checksum = u8::from_str_radix(str::from_utf8(&data)?, 16)?;
if expected_checksum != (!actual_checksum).wrapping_add(1) {
return Err(eyre!("Checksum invalid for ihex record"));
}
let mut data = [0xff; 255];
data[0..len].copy_from_slice(&bytes[0..len]);
let record_type = IntelHexRecordType::try_from(record_type)?;
record_type.validate_byte_count(byte_count)?;
Ok(Self {
byte_count,
address,
record_type,
data,
})
}
}
impl TryFrom<u8> for IntelHexRecordType
{
type Error = Report;
fn try_from(value: u8) -> Result<Self>
{
match value {
0 => Ok(Self::Data),
1 => Ok(Self::EndOfFile),
2 => Ok(Self::ExtendedSegmentAddress),
3 => Ok(Self::StartSegmentAddress),
4 => Ok(Self::ExtendedLinearAddress),
5 => Ok(Self::StartLinearAddress),
_ => Err(eyre!("Invalid record type {value} found")),
}
}
}
impl IntelHexRecordType
{
pub fn validate_byte_count(&self, byte_count: u8) -> Result<()>
{
match self {
Self::EndOfFile => {
if byte_count == 0 {
Ok(())
} else {
Err(eyre!("Invalid EOF, expected 0 bytes in record, got {byte_count}"))
}
},
Self::ExtendedSegmentAddress | Self::ExtendedLinearAddress => {
if byte_count == 2 {
Ok(())
} else {
Err(eyre!("Invalid extended address record, expected 2 bytes, got {byte_count}"))
}
},
Self::StartSegmentAddress | Self::StartLinearAddress => {
if byte_count == 4 {
Ok(())
} else {
Err(eyre!("Invalid extended address record, expected 4 bytes, got {byte_count}"))
}
},
_ => Ok(()),
}
}
}