///|
/// A validated S-record. S5/S6 use the address field as a record count.
pub struct Record {
kind : Int
address : Int64
data : Bytes
} derive(Eq, Debug)
///|
/// S4 is reserved and is never treated as a data record.
pub fn address_width(kind : Int) -> Int raise @model.FirmwareError {
match kind {
0 | 1 | 5 | 9 => 2
2 | 6 | 8 => 3
3 | 7 => 4
_ =>
raise @model.FirmwareError(
@model.diagnostic(
UnsupportedRecord,
"unsupported S-record type S\{kind}",
),
)
}
}
///|
/// Check address width, count capacity and no-data control records.
pub fn Record::new(
kind : Int,
address : Int64,
data : Bytes,
) -> Record raise @model.FirmwareError {
let width = address_width(kind)
ignore(@codec.address_bytes(address, width))
if data.length() > 254 - width {
raise @model.FirmwareError(
@model.diagnostic(InvalidLength, "S-record count exceeds 255"),
)
}
if kind >= 5 && !data.is_empty() {
raise @model.FirmwareError(
@model.diagnostic(
InvalidLength,
"count and termination records cannot contain data",
),
)
}
if kind == 0 && address != 0L {
raise @model.FirmwareError(
@model.diagnostic(InvalidRecord, "S0 header address must be zero"),
)
}
if kind >= 1 && kind <= 3 {
ignore(@model.data_range(address, data.length()))
}
{ kind, address, data, }
}
///|
/// Parse a single line with mandatory count and independent SREC checksum checks.
pub fn parse_line(
text : String,
line? : Int = 1,
) -> Record raise @model.FirmwareError {
parse_at(text, { format: SRecord, line, column_offset: 0, record_type: None, })
}
///|
fn parse_at(
text : String,
initial : @codec.Location,
) -> Record raise @model.FirmwareError {
if text.length() > 514 {
raise initial.error(ResourceLimit, 1, "S-record exceeds 514 characters")
}
if text.length() < 2 || text[0] != 83 {
raise initial.error(InvalidRecord, 1, "expected S-record prefix")
}
let kind = text[1].to_int() - 48
let location = { ..initial, record_type: Some(kind), }
let width = address_width(kind) catch {
e => raise @codec.locate_error(e, location)
}
let bytes = @codec.decode_hex(text, 2, location)
if bytes.is_empty() {
raise location.error(InvalidLength, 3, "missing S-record count")
}
let count = bytes[0].to_int()
if count != bytes.length() - 1 {
raise location.error(
InvalidLength,
3,
"expected \{count} bytes after count but found \{bytes.length() - 1}",
)
}
if count < width + 1 {
raise location.error(
InvalidLength,
3,
"count is too small for address and checksum",
)
}
if !verify_checksum(bytes) {
raise location.error(
ChecksumMismatch,
text.length() - 1,
"checksum mismatch",
)
}
let address = @codec.read_address(bytes, 1, width)
let data = bytes[1 + width:bytes.length() - 1].to_owned()
Record::new(kind, address, data) catch {
e => raise @codec.locate_error(e, location)
}
}
///|
/// Encode count and address in big-endian order with a one's complement checksum.
pub fn Record::encode(
self : Record,
uppercase? : Bool = true,
) -> String raise @model.FirmwareError {
let width = address_width(self.kind)
let body = Bytes::from_array([(self.data.length() + width + 1).to_byte()]) +
@codec.address_bytes(self.address, width) +
self.data
"S\{self.kind}" +
@codec.encode_hex(body, uppercase~) +
@codec.encode_hex(Bytes::from_array([compute_checksum(body)]), uppercase~)
}