///|
pub(all) enum SRecordType {
SHeader
SData16
SData24
SData32
SCount16
SCount24
STerminate32
STerminate24
STerminate16
} derive(Eq, Debug)
///|
pub struct SRecord {
record_type_value : SRecordType
address_value : UInt64
data_value : Bytes
checksum_value : Byte
line_index_value : Int
} derive(Eq, Debug)
///|
fn srecord_error(
code : FirmwareErrorCode,
message : String,
line_index : Int,
column_index? : Int? = None,
) -> FirmwareError {
let position = match column_index {
Some(column) => SourcePosition::column(line_index, column)
None => SourcePosition::line(line_index)
}
FirmwareError::new(code, message, position)
}
///|
fn srecord_type(
code : Int,
line_index : Int,
) -> Result[SRecordType, FirmwareError] {
match code {
48 => Ok(SHeader)
49 => Ok(SData16)
50 => Ok(SData24)
51 => Ok(SData32)
53 => Ok(SCount16)
54 => Ok(SCount24)
55 => Ok(STerminate32)
56 => Ok(STerminate24)
57 => Ok(STerminate16)
_ =>
Err(
srecord_error(
RecordInvalidType,
"S-record type must be S0, S1, S2, S3, S5, S6, S7, S8 or S9",
line_index,
column_index=Some(1),
),
)
}
}
///|
fn SRecordType::type_digit(self : SRecordType) -> String {
match self {
SHeader => "0"
SData16 => "1"
SData24 => "2"
SData32 => "3"
SCount16 => "5"
SCount24 => "6"
STerminate32 => "7"
STerminate24 => "8"
STerminate16 => "9"
}
}
///|
pub fn SRecordType::address_bytes(self : SRecordType) -> Int {
match self {
SHeader | SData16 | SCount16 | STerminate16 => 2
SData24 | SCount24 | STerminate24 => 3
SData32 | STerminate32 => 4
}
}
///|
pub fn SRecordType::is_data(self : SRecordType) -> Bool {
match self {
SData16 | SData24 | SData32 => true
_ => false
}
}
///|
pub fn SRecordType::is_count(self : SRecordType) -> Bool {
self == SCount16 || self == SCount24
}
///|
pub fn SRecordType::is_termination(self : SRecordType) -> Bool {
self == STerminate16 || self == STerminate24 || self == STerminate32
}
///|
fn address_from_bytes(bytes : Bytes, start : Int, length : Int) -> UInt64 {
let mut value = 0UL
for index = 0; index < length; index = index + 1 {
value = value * 256UL + bytes[start + index].to_uint64()
}
value
}
///|
fn validate_srecord_shape(
record_type : SRecordType,
data_length : Int,
line_index : Int,
) -> Result[Unit, FirmwareError] {
if (record_type.is_count() || record_type.is_termination()) &&
data_length != 0 {
Err(
srecord_error(
RecordLengthMismatch,
"S-record count and termination records cannot contain data bytes",
line_index,
),
)
} else {
Ok(())
}
}
///|
/// Parse and validate one Motorola S-record line.
pub fn parse_srecord(
line : String,
line_index? : Int = 0,
) -> Result[SRecord, FirmwareError] {
if line.length() == 0 {
return Err(srecord_error(RecordEmpty, "S-record is empty", line_index))
}
if line.length() < 2 || line[0].to_int() != 83 {
return Err(
srecord_error(
RecordInvalidPrefix,
"S-record must start with an uppercase S and a type digit",
line_index,
column_index=Some(0),
),
)
}
let record_type = match srecord_type(line[1].to_int(), line_index) {
Ok(value) => value
Err(error) => return Err(error)
}
let bytes = match decode_hex_bytes(line[2:].to_owned(), line_index~) {
Ok(value) => value
Err(error) => return Err(error)
}
if bytes.length() < 2 {
return Err(
srecord_error(
RecordLengthMismatch,
"S-record is shorter than its count and checksum fields",
line_index,
),
)
}
let count = bytes[0].to_int()
if bytes.length() != count + 1 {
return Err(
srecord_error(
RecordLengthMismatch,
"S-record count does not match the encoded record length",
line_index,
column_index=Some(2),
),
)
}
let address_length = record_type.address_bytes()
if count < address_length + 1 {
return Err(
srecord_error(
RecordLengthMismatch,
"S-record count is too small for its address and checksum",
line_index,
),
)
}
if !ones_complement_valid(bytes) {
return Err(
srecord_error(
ChecksumMismatch,
"S-record checksum does not match the record bytes",
line_index,
column_index=Some(line.length() - 2),
),
)
}
let data_length = count - address_length - 1
match validate_srecord_shape(record_type, data_length, line_index) {
Ok(_) => ()
Err(error) => return Err(error)
}
let address = address_from_bytes(bytes, 1, address_length)
let data = Bytes::makei(data_length, index => {
bytes[1 + address_length + index]
})
Ok({
record_type_value: record_type,
address_value: address,
data_value: data,
checksum_value: bytes[bytes.length() - 1],
line_index_value: line_index,
})
}
///|
pub fn SRecord::record_type(self : SRecord) -> SRecordType {
self.record_type_value
}
///|
pub fn SRecord::address(self : SRecord) -> UInt64 {
self.address_value
}
///|
pub fn SRecord::data(self : SRecord) -> Bytes {
Bytes::makei(self.data_value.length(), index => self.data_value[index])
}
///|
pub fn SRecord::byte_count(self : SRecord) -> Int {
self.record_type_value.address_bytes() + self.data_value.length() + 1
}
///|
pub fn SRecord::checksum(self : SRecord) -> Byte {
self.checksum_value
}
///|
pub fn SRecord::line_index(self : SRecord) -> Int {
self.line_index_value
}
///|
/// Encode a validated record using uppercase hexadecimal digits.
pub fn SRecord::encode(self : SRecord) -> String {
let address_length = self.record_type_value.address_bytes()
let count = address_length + self.data_value.length() + 1
let body = Bytes::makei(1 + address_length + self.data_value.length(), index => {
if index == 0 {
count.to_byte()
} else if index <= address_length {
let shift = (address_length - index) * 8
((self.address_value >> shift) & 0xFFUL).to_byte()
} else {
self.data_value[index - address_length - 1]
}
})
let checksum = Bytes::makei(1, _ => ones_complement_checksum(body))
"S" +
self.record_type_value.type_digit() +
encode_hex_bytes(body) +
encode_hex_bytes(checksum)
}