///|
/// The wire format or origin of an image.
pub(all) enum Format {
IntelHex
SRecord
RawBinary
Unknown
} derive(Eq, Debug)
///|
/// Machine-readable failure categories; messages add context, not identity.
pub(all) enum ErrorCode {
InvalidDigit
InvalidLength
InvalidRecord
UnsupportedRecord
ChecksumMismatch
MissingTerminator
AfterTerminator
DuplicateRecord
CountMismatch
AddressOverflow
InvalidRange
AddressConflict
GapRequiresFill
ResourceLimit
InvalidOption
UnknownFormat
EntryConflict
Io
} derive(Eq, Debug)
///|
/// Diagnostic locations are one-based; zero means not tied to a text line.
pub(all) struct Diagnostic {
code : ErrorCode
format : Format
line : Int
column : Int
record_type : Int?
address : Int64?
end_address : Int64?
message : String
} derive(Eq, Debug)
///|
/// Library failures always carry a structured diagnostic.
pub(all) suberror FirmwareError {
FirmwareError(Diagnostic)
} derive(Debug)
///|
/// Create a diagnostic outside a parser; parser code supplies source fields.
pub fn diagnostic(
code : ErrorCode,
message : String,
address? : Int64,
end_address? : Int64,
) -> Diagnostic {
{
code,
message,
format: Unknown,
line: 0,
column: 0,
record_type: None,
address,
end_address,
}
}
///|
/// Human-readable name independent of enum debugging syntax.
pub fn Format::label(self : Format) -> String {
match self {
IntelHex => "Intel HEX"
SRecord => "Motorola S-Record"
RawBinary => "Raw binary"
Unknown => "Unknown"
}
}
///|
/// Stable fixed-width display for firmware addresses and range endpoints.
pub fn hex_address(address : Int64) -> String {
"0x" + address.to_string(radix=16).to_upper().pad_start(8, '0')
}
///|
/// Render diagnostic text without losing structured address information.
pub fn Diagnostic::render(self : Diagnostic) -> String {
let out = StringBuilder()
if self.line > 0 {
out.write_string("line \{self.line}")
if self.column > 0 {
out.write_string(", column \{self.column}")
}
out.write_string(": ")
}
out.write_string(self.message)
match self.address {
Some(start) => {
out.write_string(" at " + hex_address(start))
match self.end_address {
Some(end) => out.write_string(".." + hex_address(end))
None => ()
}
}
None => ()
}
out.to_string()
}
///|
/// Strict structural validation or a small documented set of tolerances.
pub(all) enum ParseMode {
Strict
Permissive
} derive(Eq, Debug)
///|
/// Reject any overlap, accept byte-identical overlap, or replace existing data.
pub(all) enum OverlapPolicy {
Reject
AllowIdentical
Overwrite
} derive(Eq, Debug)
///|
/// Resource limits apply before parsing or allocating output buffers.
pub(all) struct ParseOptions {
mode : ParseMode
overlap : OverlapPolicy
max_line_length : Int
max_records : Int
max_payload : Int
max_text_length : Int
max_warnings : Int
} derive(Eq, Debug)
///|
/// Conservative defaults, independent of the largest address in the input.
pub fn ParseOptions::default() -> ParseOptions {
{
mode: Strict,
overlap: Reject,
max_line_length: 1024,
max_records: 1000000,
max_payload: 64 * 1024 * 1024,
max_text_length: 256 * 1024 * 1024,
max_warnings: 1024,
}
}
///|
/// Check options once before processing untrusted records.
pub fn ParseOptions::validate(self : ParseOptions) -> Unit raise FirmwareError {
if self.max_line_length < 11 ||
self.max_line_length > 65536 ||
self.max_records < 1 ||
self.max_records > 10000000 ||
self.max_payload < 0 ||
self.max_payload > 64 * 1024 * 1024 ||
self.max_text_length < 0 ||
self.max_text_length > 256 * 1024 * 1024 ||
self.max_warnings < 0 ||
self.max_warnings > 65536 {
raise FirmwareError(
diagnostic(InvalidOption, "parse limits outside supported bounds"),
)
}
}