///|
/// Errors raised while encoding or decoding diagnostic trouble codes.
pub suberror DtcCodecError {
InvalidCode
InvalidPayload
InvalidRecordLength
CapacityExceeded
} derive(Debug)
///|
/// A normalized three-byte diagnostic trouble code.
pub struct DtcRecord {
code : UInt
status : Byte
occurrence : UInt
snapshot : Array[Byte]
}
///|
/// A parsed ReadDTCInformation response.
pub struct DtcReport {
subfunction : Byte
status_availability_mask : Byte
records : Array[DtcRecord]
}
///|
/// Construct a report for a positive ReadDTCInformation response.
pub fn new_dtc_report(
subfunction : Byte,
status_availability_mask : Byte,
records : Array[DtcRecord],
) -> DtcReport {
{ subfunction, status_availability_mask, records: records.copy() }
}
///|
/// Create a DTC record with an optional occurrence counter and snapshot.
pub fn dtc_record(
code : UInt,
status : Byte,
occurrence? : UInt = 1,
snapshot? : Array[Byte] = [],
) -> DtcRecord raise DtcCodecError {
if code > 0xFFFFFF {
raise InvalidCode
}
{ code, status, occurrence, snapshot: snapshot.copy() }
}
///|
/// Encode the four-byte DTC record used in a UDS response.
pub fn encode_dtc_record(record : DtcRecord) -> Array[Byte] {
[
(record.code >> 16).to_byte(),
(record.code >> 8).to_byte(),
record.code.to_byte(),
record.status,
]
}
///|
/// Parse a positive ReadDTCInformation response.
pub fn parse_dtc_report(payload : Array[Byte]) -> DtcReport raise DtcCodecError {
if payload.length() < 3 || payload[0] != 0x59 {
raise InvalidPayload
}
if (payload.length() - 3) % 4 != 0 {
raise InvalidRecordLength
}
let records : Array[DtcRecord] = []
let mut index = 3
while index < payload.length() {
let code : UInt = (payload[index].to_uint() << 16) |
(payload[index + 1].to_uint() << 8) |
payload[index + 2].to_uint()
records.push(
dtc_record(code, payload[index + 3]) catch {
_ => raise InvalidCode
},
)
index += 4
}
{ subfunction: payload[1], status_availability_mask: payload[2], records }
}
///|
/// Encode a report while preserving record order.
pub fn encode_dtc_report(report : DtcReport) -> Array[Byte] {
let result : Array[Byte] = [
0x59,
report.subfunction,
report.status_availability_mask,
]
for record in report.records {
for byte in encode_dtc_record(record) {
result.push(byte)
}
}
result
}
///|
/// A bounded DTC store suitable for an ECU simulator or gateway cache.
pub struct DtcStore {
capacity : Int
records : Array[DtcRecord]
mut updates : UInt
}
///|
pub fn new_dtc_store(capacity : Int) -> DtcStore raise DtcCodecError {
if capacity < 0 {
raise CapacityExceeded
}
{ capacity, records: [], updates: 0 }
}
///|
/// Add a code or update the status and occurrence count of an existing code.
pub fn DtcStore::upsert(
self : DtcStore,
record : DtcRecord,
) -> Unit raise DtcCodecError {
match self.find_index(record.code) {
Some(index) => {
let previous = self.records[index]
self.records[index] = {
code: record.code,
status: record.status,
occurrence: previous.occurrence + record.occurrence,
snapshot: record.snapshot.copy(),
}
}
None => {
if self.capacity > 0 && self.records.length() >= self.capacity {
raise CapacityExceeded
}
self.records.push(record)
}
}
self.updates += 1
}
///|
/// Remove one DTC, returning whether it existed.
pub fn DtcStore::remove(self : DtcStore, code : UInt) -> Bool {
match self.find_index(code) {
Some(index) => {
ignore(self.records.remove(index))
self.updates += 1
true
}
None => false
}
}
///|
/// Clear all records whose code matches a 24-bit mask.
pub fn DtcStore::clear_mask(self : DtcStore, code : UInt, mask : UInt) -> Int {
let mut removed = 0
let mut index = 0
while index < self.records.length() {
if (self.records[index].code & mask) == (code & mask) {
ignore(self.records.remove(index))
removed += 1
} else {
index += 1
}
}
if removed > 0 {
self.updates += 1
}
removed
}
///|
/// Query records whose status contains every bit in `status_mask`.
pub fn DtcStore::query_status(
self : DtcStore,
status_mask : Byte,
) -> Array[DtcRecord] {
let result : Array[DtcRecord] = []
for record in self.records {
if (record.status & status_mask) == status_mask {
result.push(record)
}
}
result
}
///|
pub fn DtcStore::find(self : DtcStore, code : UInt) -> DtcRecord? {
match self.find_index(code) {
Some(index) => Some(self.records[index])
None => None
}
}
///|
pub fn DtcStore::snapshot(self : DtcStore) -> Array[DtcRecord] {
self.records.copy()
}
///|
pub fn DtcStore::length(self : DtcStore) -> Int {
self.records.length()
}
///|
pub fn DtcStore::updates(self : DtcStore) -> UInt {
self.updates
}
///|
pub fn DtcRecord::code(self : DtcRecord) -> UInt {
self.code
}
///|
pub fn DtcRecord::status(self : DtcRecord) -> Byte {
self.status
}
///|
pub fn DtcRecord::occurrence(self : DtcRecord) -> UInt {
self.occurrence
}
///|
pub fn DtcRecord::snapshot(self : DtcRecord) -> Array[Byte] {
self.snapshot.copy()
}
///|
pub fn DtcReport::subfunction(self : DtcReport) -> Byte {
self.subfunction
}
///|
pub fn DtcReport::status_availability_mask(self : DtcReport) -> Byte {
self.status_availability_mask
}
///|
pub fn DtcReport::records(self : DtcReport) -> Array[DtcRecord] {
self.records.copy()
}
///|
fn DtcStore::find_index(self : DtcStore, code : UInt) -> Int? {
for index in 0..