use core::fmt;
use std::fmt::Write;
use std::str;
use ansic::ansi;
use crate::checksums::crc_ihex;
use crate::error::Error;
use crate::Record;
const R: &str = ansi!(reset);
const GREEN: &str = ansi!(green);
const BLUE: &str = ansi!(blue);
const RED: &str = ansi!(red);
const CYAN: &str = ansi!(cyan);
const YELLOW: &str = ansi!(yellow);
const BRIGHT_CYAN: &str = ansi!(br.cyan); const BRIGHT_MAGENTA: &str = ansi!(br.magenta); const BRIGHT_GREEN: &str = ansi!(br.green);
mod char_counts {
pub const SMALLEST_RECORD_EXCLUDING_START_CODE: usize = (1 + 2 + 1 + 1) * 2;
pub const LARGEST_RECORD_EXCLUDING_START_CODE: usize = (1 + 2 + 1 + 255 + 1) * 2;
}
mod payload_sizes {
pub const END_OF_FILE: usize = 0;
pub const EXTENDED_SEGMENT_ADDRESS: usize = 2;
pub const START_SEGMENT_ADDRESS: usize = 4;
pub const EXTENDED_LINEAR_ADDRESS: usize = 2;
pub const START_LINEAR_ADDRESS: usize = 4;
}
#[derive(PartialEq, Eq, Debug, Clone, Hash)]
pub enum IHexRecord {
Data {
offset: u16,
value: Vec<u8>,
},
EndOfFile,
ExtendedSegmentAddress(u16),
StartSegmentAddress(u32),
ExtendedLinearAddress(u16),
StartLinearAddress(u32),
}
impl IHexRecord {
fn record_type(&self) -> u8 {
match self {
IHexRecord::Data { .. } => types::DATA,
IHexRecord::EndOfFile => types::END_OF_FILE,
IHexRecord::ExtendedSegmentAddress(..) => types::EXTENDED_SEGMENT_ADDRESS,
IHexRecord::StartSegmentAddress { .. } => types::START_SEGMENT_ADDRESS,
IHexRecord::ExtendedLinearAddress(..) => types::EXTENDED_LINEAR_ADDRESS,
IHexRecord::StartLinearAddress(..) => types::START_LINEAR_ADDRESS,
}
}
}
impl Record for IHexRecord {
fn to_record_string(&self) -> Result<String, Error> {
match self {
IHexRecord::Data { offset, value } => format_record(self.record_type(), *offset, value),
IHexRecord::EndOfFile => format_record(self.record_type(), 0x0000, []),
IHexRecord::ExtendedSegmentAddress(segment_address) => {
format_record(self.record_type(), 0x0000, segment_address.to_be_bytes())
}
IHexRecord::StartSegmentAddress(address) => {
format_record(self.record_type(), 0x0000, address.to_be_bytes())
}
IHexRecord::ExtendedLinearAddress(linear_address) => {
format_record(self.record_type(), 0x0000, linear_address.to_be_bytes())
}
IHexRecord::StartLinearAddress(address) => {
format_record(self.record_type(), 0x0000, address.to_be_bytes())
}
}
}
fn to_pretty_record_string(&self) -> Result<String, Error> {
let record_string = self.to_record_string()?;
let (type_str, type_txt) = match self {
IHexRecord::Data { .. } => (format!("{GREEN}{}{R}", &record_string[7..9]), " (data)"),
IHexRecord::EndOfFile => (
format!("{BRIGHT_CYAN}{}{R}", &record_string[7..9]),
" (end of file)",
),
IHexRecord::ExtendedSegmentAddress(_) => (
format!("{BLUE}{}{R}", &record_string[7..9]),
" (extended segment address)",
),
IHexRecord::StartSegmentAddress(_) => (
format!("{BRIGHT_GREEN}{}{R}", &record_string[7..9]),
" (start segment address)",
),
IHexRecord::ExtendedLinearAddress(_) => (
format!("{BRIGHT_CYAN}{}{R}", &record_string[7..9]),
" (extended linear address)",
),
IHexRecord::StartLinearAddress(_) => (
format!("{BRIGHT_GREEN}{}{R}", &record_string[7..9]),
" (start linear address)",
),
};
Ok(format!(
"{RED}{}{R}{BRIGHT_MAGENTA}{}{R}{YELLOW}{}{R}{}{}{CYAN}{}{R}{}",
&record_string[..1],
&record_string[1..3],
&record_string[3..7],
type_str,
&record_string[9..record_string.len() - 2],
&record_string[record_string.len() - 2..],
type_txt
))
}
fn from_record_string<S>(record_string: S) -> Result<Self, Error>
where
S: AsRef<str>,
{
let string = record_string.as_ref().trim();
if let Some(':') = string.chars().next() {
} else {
return Err(Error::MissingStartCode);
}
let data_portion = &string[1..];
let data_portion_length = data_portion.chars().count();
if !data_portion
.chars()
.all(|character| character.is_ascii_hexdigit())
{
return Err(Error::ContainsInvalidCharacters);
}
if data_portion_length < char_counts::SMALLEST_RECORD_EXCLUDING_START_CODE {
return Err(Error::RecordTooShort);
} else if data_portion_length > char_counts::LARGEST_RECORD_EXCLUDING_START_CODE {
return Err(Error::RecordTooLong);
} else if (data_portion_length % 2) != 0 {
return Err(Error::RecordNotEvenLength);
}
let mut data_bytes = data_portion
.as_bytes()
.chunks(2)
.map(|chunk| str::from_utf8(chunk).unwrap())
.map(|byte_str| u8::from_str_radix(byte_str, 16).unwrap())
.collect::<Vec<u8>>();
let expected_checksum = data_bytes.pop().unwrap();
let validated_region_bytes = data_bytes.as_slice();
let checksum = crc_ihex(validated_region_bytes);
if checksum != expected_checksum {
return Err(Error::ChecksumMismatch(checksum, expected_checksum));
}
let length = validated_region_bytes[0];
let address = u16::from_be_bytes([validated_region_bytes[1], validated_region_bytes[2]]);
let record_type = validated_region_bytes[3];
let payload_bytes = &validated_region_bytes[4..];
if payload_bytes.len() != (length as usize) {
return Err(Error::PayloadLengthMismatch);
}
match record_type {
types::DATA => {
Ok(IHexRecord::Data {
offset: address,
value: Vec::from(payload_bytes),
})
}
types::END_OF_FILE => {
match payload_bytes.len() {
payload_sizes::END_OF_FILE => Ok(IHexRecord::EndOfFile),
_ => Err(Error::InvalidLengthForType),
}
}
types::EXTENDED_SEGMENT_ADDRESS => {
match payload_bytes.len() {
payload_sizes::EXTENDED_SEGMENT_ADDRESS => {
let address: u16 = u16::from_be_bytes(payload_bytes.try_into().unwrap());
Ok(IHexRecord::ExtendedSegmentAddress(address))
}
_ => Err(Error::InvalidLengthForType),
}
}
types::START_SEGMENT_ADDRESS => match payload_bytes.len() {
payload_sizes::START_SEGMENT_ADDRESS => {
let address: u32 = u32::from_be_bytes(payload_bytes.try_into().unwrap());
Ok(IHexRecord::StartSegmentAddress(address))
}
_ => Err(Error::InvalidLengthForType),
},
types::EXTENDED_LINEAR_ADDRESS => {
match payload_bytes.len() {
payload_sizes::EXTENDED_LINEAR_ADDRESS => {
let ela: u16 = u16::from_be_bytes(payload_bytes.try_into().unwrap());
Ok(IHexRecord::ExtendedLinearAddress(ela))
}
_ => Err(Error::InvalidLengthForType),
}
}
types::START_LINEAR_ADDRESS => {
match payload_bytes.len() {
payload_sizes::START_LINEAR_ADDRESS => {
let sla: u32 = u32::from_be_bytes(payload_bytes.try_into().unwrap());
Ok(IHexRecord::StartLinearAddress(sla))
}
_ => Err(Error::InvalidLengthForType),
}
}
_ => Err(Error::UnsupportedRecordType {
record: string.into(),
record_type,
}),
}
}
}
impl str::FromStr for IHexRecord {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
IHexRecord::from_record_string(input)
}
}
mod types {
pub const DATA: u8 = 0x00;
pub const END_OF_FILE: u8 = 0x01;
pub const EXTENDED_SEGMENT_ADDRESS: u8 = 0x02;
pub const START_SEGMENT_ADDRESS: u8 = 0x03;
pub const EXTENDED_LINEAR_ADDRESS: u8 = 0x04;
pub const START_LINEAR_ADDRESS: u8 = 0x05;
}
impl fmt::Display for IHexRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IHexRecord::Data { offset, value } => {
write!(f, "Data Record: Offset:{}, value: {:X?}", offset, value)
}
IHexRecord::EndOfFile => {
write!(f, "EndOfFile Record")
}
IHexRecord::ExtendedSegmentAddress(address) => {
write!(f, "Extended Segment Address Record: Address: {}", address)
}
IHexRecord::StartSegmentAddress(address) => {
write!(f, "Start Segment Address Record: Address: {}", address)
}
IHexRecord::ExtendedLinearAddress(address) => {
write!(f, "Extended Linear Address Record: Address: {}", address)
}
IHexRecord::StartLinearAddress(address) => {
write!(f, "Start Linear Address Record: Address: {}", address)
}
}
}
}
fn format_record<T>(record_type: u8, address: u16, input: T) -> Result<String, Error>
where
T: AsRef<[u8]>,
{
let data = input.as_ref();
if data.len() > 0xFF {
return Err(Error::DataExceedsMaximumLength(data.len()));
}
let data_length = 1 + 2 + 1 + data.len() + 1;
let mut data_region = Vec::<u8>::with_capacity(data_length);
data_region.push(data.len() as u8);
data_region.push(((address & 0xFF00) >> 8) as u8);
data_region.push((address & 0x00FF) as u8);
data_region.push(record_type);
data_region.extend_from_slice(data);
let checksum = crc_ihex(data_region.as_slice());
data_region.push(checksum);
let result_length = 1 + (2 * data_length);
let mut result = String::with_capacity(result_length);
result.push(':');
data_region.iter().try_fold(result, |mut acc, byte| {
write!(&mut acc, "{:02X}", byte)
.map_err(|_| Error::SynthesisFailed)
.map(|_| acc)
})
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_empty_record() {
let res = IHexRecord::from_record_string("");
assert_eq!(res, Err(Error::MissingStartCode));
}
#[test]
fn test_wrong_start_code() {
let res = IHexRecord::from_record_string(".0011110022");
assert_eq!(res, Err(Error::MissingStartCode));
}
#[test]
fn test_wrong_checksum() {
let res = IHexRecord::from_record_string(":0011110022");
assert_eq!(res, Err(Error::ChecksumMismatch(0xDE, 0x22)));
}
#[test]
fn test_record_from_record_string_rejects_missing_start_code() {
assert_eq!(
IHexRecord::from_record_string("00000001FF"),
Err(Error::MissingStartCode)
);
}
#[test]
fn test_record_from_record_string_rejects_short_records() {
assert_eq!(
IHexRecord::from_record_string(":"),
Err(Error::RecordTooShort)
);
assert_eq!(
IHexRecord::from_record_string(":00"),
Err(Error::RecordTooShort)
);
assert_eq!(
IHexRecord::from_record_string(":00000001F"),
Err(Error::RecordTooShort)
);
}
#[test]
fn test_record_from_record_string_rejects_long_records() {
let longest_valid_data = (0..255).map(|_| 0u8).collect::<Vec<u8>>();
let longest_valid_data_record = IHexRecord::Data {
offset: 0x0010,
value: longest_valid_data,
};
let longest_valid_string = longest_valid_data_record.to_record_string().unwrap();
let shortest_invalid_string = longest_valid_string.clone() + "0";
assert_eq!(longest_valid_string.len(), 521);
assert!(IHexRecord::from_record_string(&longest_valid_string).is_ok());
assert_eq!(shortest_invalid_string.len(), 522);
assert_eq!(
IHexRecord::from_record_string(&shortest_invalid_string),
Err(Error::RecordTooLong)
);
}
#[test]
fn test_record_from_record_string_rejects_odd_length_records() {
assert_eq!(
IHexRecord::from_record_string(":0B0010006164647265737320676170A7D"),
Err(Error::RecordNotEvenLength)
);
assert_eq!(
IHexRecord::from_record_string(":00000001FFF"),
Err(Error::RecordNotEvenLength)
);
assert_eq!(
IHexRecord::from_record_string(":0200000212FEECD"),
Err(Error::RecordNotEvenLength)
);
assert_eq!(
IHexRecord::from_record_string(":04000003123438007BD"),
Err(Error::RecordNotEvenLength)
);
assert_eq!(
IHexRecord::from_record_string(":02000004ABCD823"),
Err(Error::RecordNotEvenLength)
);
assert_eq!(
IHexRecord::from_record_string(":0400000512345678E34"),
Err(Error::RecordNotEvenLength)
);
}
#[test]
fn test_record_from_record_string_rejects_non_hex_characters() {
assert_eq!(
IHexRecord::from_record_string(":000000q1ff"),
Err(Error::ContainsInvalidCharacters)
);
assert_eq!(
IHexRecord::from_record_string(":00000021f*"),
Err(Error::ContainsInvalidCharacters)
);
assert_eq!(
IHexRecord::from_record_string(":^0000001FF"),
Err(Error::ContainsInvalidCharacters)
);
assert_eq!(
IHexRecord::from_record_string(":â„¢0000001FF"),
Err(Error::ContainsInvalidCharacters)
);
}
#[test]
fn test_record_from_record_string_rejects_invalid_checksums() {
assert_eq!(
IHexRecord::from_record_string(":0B0010006164647265737320676170FF"),
Err(Error::ChecksumMismatch(0xA7, 0xFF))
);
assert_eq!(
IHexRecord::from_record_string(":0000000100"),
Err(Error::ChecksumMismatch(0xFF, 0x00))
);
assert_eq!(
IHexRecord::from_record_string(":020000021200EB"),
Err(Error::ChecksumMismatch(0xEA, 0xEB))
);
assert_eq!(
IHexRecord::from_record_string(":04000003000038001C"),
Err(Error::ChecksumMismatch(0xC1, 0x1C))
);
assert_eq!(
IHexRecord::from_record_string(":02000004FFFFFD"),
Err(Error::ChecksumMismatch(0xFC, 0xFD))
);
assert_eq!(
IHexRecord::from_record_string(":04000005000001CD2A"),
Err(Error::ChecksumMismatch(0x29, 0x2A))
);
}
#[test]
fn test_record_from_record_string_rejects_payload_length_mismatches() {
assert_eq!(
IHexRecord::from_record_string(":0C0010006164647265737320676170A6"),
Err(Error::PayloadLengthMismatch)
);
assert_eq!(
IHexRecord::from_record_string(":000010006164647265737320676170B2"),
Err(Error::PayloadLengthMismatch)
);
assert_eq!(
IHexRecord::from_record_string(":01000001FE"),
Err(Error::PayloadLengthMismatch)
);
assert_eq!(
IHexRecord::from_record_string(":0F0000021200DD"),
Err(Error::PayloadLengthMismatch)
);
assert_eq!(
IHexRecord::from_record_string(":0200000300003800C3"),
Err(Error::PayloadLengthMismatch)
);
assert_eq!(
IHexRecord::from_record_string(":01000004FFFFFD"),
Err(Error::PayloadLengthMismatch)
);
assert_eq!(
IHexRecord::from_record_string(":05000005000001CD28"),
Err(Error::PayloadLengthMismatch)
);
}
#[test]
fn test_record_from_record_string_rejects_unsupported_record_types() {
assert_eq!(
IHexRecord::from_record_string(":0B0010066164647265737320676170A1"),
Err(Error::UnsupportedRecordType {
record: ":0B0010066164647265737320676170A1".into(),
record_type: 0x06
})
);
assert_eq!(
IHexRecord::from_record_string(":0B0010FF6164647265737320676170A8"),
Err(Error::UnsupportedRecordType {
record: ":0B0010FF6164647265737320676170A8".into(),
record_type: 0xff
})
);
}
#[test]
fn test_record_from_record_string_rejects_invalid_lengths_for_types() {
assert_eq!(
IHexRecord::from_record_string(":01000001FFFF"),
Err(Error::InvalidLengthForType)
);
assert_eq!(
IHexRecord::from_record_string(":0100000200FD"),
Err(Error::InvalidLengthForType)
);
assert_eq!(
IHexRecord::from_record_string(":03000002FF1200EA"),
Err(Error::InvalidLengthForType)
);
assert_eq!(
IHexRecord::from_record_string(":03000003003800C2"),
Err(Error::InvalidLengthForType)
);
assert_eq!(
IHexRecord::from_record_string(":050000030000003800C0"),
Err(Error::InvalidLengthForType)
);
assert_eq!(
IHexRecord::from_record_string(":01000004FFFC"),
Err(Error::InvalidLengthForType)
);
assert_eq!(
IHexRecord::from_record_string(":03000004FFFFFFFC"),
Err(Error::InvalidLengthForType)
);
assert_eq!(
IHexRecord::from_record_string(":030000050000CD2B"),
Err(Error::InvalidLengthForType)
);
assert_eq!(
IHexRecord::from_record_string(":0500000500000000CD29"),
Err(Error::InvalidLengthForType)
);
}
#[test]
fn test_record_from_record_string_parses_valid_data_records() {
assert_eq!(
IHexRecord::from_record_string(":0B0010006164647265737320676170A7"),
Ok(IHexRecord::Data {
offset: 0x0010,
value: vec![0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x67, 0x61, 0x70,],
})
);
assert_eq!(
IHexRecord::from_record_string(":00FFFE0003"),
Ok(IHexRecord::Data {
offset: 0xFFFE,
value: vec![],
})
);
}
#[test]
fn test_record_from_record_string_parses_valid_eof_record() {
assert_eq!(
IHexRecord::from_record_string(":00000001FF"),
Ok(IHexRecord::EndOfFile)
);
assert_eq!(
IHexRecord::from_record_string(":00000001ff"),
Ok(IHexRecord::EndOfFile)
);
}
#[test]
fn test_record_from_record_string_parses_valid_extended_segment_address() {
assert_eq!(
IHexRecord::from_record_string(":0200000212FEEC"),
Ok(IHexRecord::ExtendedSegmentAddress(0x12FE))
);
assert_eq!(
IHexRecord::from_record_string(":0200000212fEEc"),
Ok(IHexRecord::ExtendedSegmentAddress(0x12FE))
);
}
#[test]
fn test_record_from_record_string_parses_valid_start_segment_address() {
assert_eq!(
IHexRecord::from_record_string(":04000003123438007B"),
IHexRecord::from_record_string(":04000003123438007b")
);
assert_eq!(
IHexRecord::from_record_string(":04000003123438007B"),
Ok(IHexRecord::StartSegmentAddress(0x12343800))
);
}
#[test]
fn test_record_from_record_string_parses_valid_extended_linear_address() {
assert_eq!(
IHexRecord::from_record_string(":02000004ABCD82"),
Ok(IHexRecord::ExtendedLinearAddress(0xABCD))
);
assert_eq!(
IHexRecord::from_record_string(":02000004abcd82"),
Ok(IHexRecord::ExtendedLinearAddress(0xABCD))
);
}
#[test]
fn test_record_from_record_string_parses_valid_start_linear_address() {
assert_eq!(
IHexRecord::from_record_string(":0400000512345678E3"),
Ok(IHexRecord::StartLinearAddress(0x12345678))
);
assert_eq!(
IHexRecord::from_record_string(":0400000512345678e3"),
Ok(IHexRecord::StartLinearAddress(0x12345678))
);
}
#[test]
fn test_to_record_string() {
assert_eq!(
IHexRecord::StartLinearAddress(0x12345678).to_record_string(),
Ok(":0400000512345678E3".into())
);
assert_eq!(
IHexRecord::ExtendedLinearAddress(0xABCD).to_record_string(),
Ok(":02000004ABCD82".into())
);
assert_eq!(
IHexRecord::StartSegmentAddress(0x12343800).to_record_string(),
Ok(":04000003123438007B".into())
);
assert_eq!(
IHexRecord::ExtendedSegmentAddress(0x12FE).to_record_string(),
Ok(":0200000212FEEC".into())
);
assert_eq!(
IHexRecord::EndOfFile.to_record_string(),
Ok(":00000001FF".into())
);
assert_eq!(
IHexRecord::Data {
offset: 0x0010,
value: vec![0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x67, 0x61, 0x70,],
}
.to_record_string(),
Ok(":0B0010006164647265737320676170A7".into())
);
}
#[test]
fn test_pretty_record_string() {
println!(
"{}",
IHexRecord::StartLinearAddress(0x12345678)
.to_pretty_record_string()
.expect("Pretty Record should not fail")
);
println!(
"{}",
IHexRecord::ExtendedLinearAddress(0xABCD)
.to_pretty_record_string()
.expect("Pretty Record should not fail")
);
println!(
"{}",
IHexRecord::StartSegmentAddress(0x12343800)
.to_pretty_record_string()
.expect("Pretty Record should not fail")
);
println!(
"{}",
IHexRecord::ExtendedSegmentAddress(0x12FE)
.to_pretty_record_string()
.expect("Pretty Record should not fail")
);
println!(
"{}",
IHexRecord::EndOfFile
.to_pretty_record_string()
.expect("Pretty Record should not fail")
);
println!(
"{}",
IHexRecord::Data {
offset: 0x0010,
value: vec![0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x67, 0x61, 0x70,],
}
.to_pretty_record_string()
.expect("Pretty Record should not fail")
);
}
}