use byteorder::{ReadBytesExt, WriteBytesExt};
use crate::{
DTCExtDataRecordList, DTCExtDataRecordNumber, DTCFormatIdentifier, DTCRecord, DTCSeverityMask,
DTCSeverityRecord, DTCSnapshotRecord, DTCSnapshotRecordList, DTCSnapshotRecordNumber,
DTCStatusMask, DTCStoredDataRecordNumber, Error, FunctionalGroupIdentifier, IterableWireFormat,
SingleValueWireFormat, UserDefDTCSnapshotRecordNumber, WireFormat,
};
type DTCFaultDetectionCounter = u8;
type MemorySelection = u8;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct ReadDTCInfoRequest {
pub dtc_subfunction: ReadDTCInfoSubFunction,
}
impl ReadDTCInfoRequest {
pub(crate) fn new(dtc_subfunction: ReadDTCInfoSubFunction) -> Self {
Self { dtc_subfunction }
}
}
impl WireFormat for ReadDTCInfoRequest {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let dtc_subfunction = ReadDTCInfoSubFunction::decode_single_value(reader)?;
Ok(Some(Self { dtc_subfunction }))
}
fn required_size(&self) -> usize {
self.dtc_subfunction.required_size()
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
self.dtc_subfunction.encode(writer)
}
}
impl SingleValueWireFormat for ReadDTCInfoRequest {}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DTCFaultDetectionCounterRecord {
pub dtc_record: DTCRecord,
pub dtc_fault_detection_counter: DTCFaultDetectionCounter,
}
impl WireFormat for DTCFaultDetectionCounterRecord {
#[allow(clippy::match_same_arms)]
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let dtc_record = match DTCRecord::decode(reader) {
Ok(None) => return Ok(None),
Ok(record) => record,
Err(_) => return Ok(None),
};
let dtc_fault_detection_counter = reader.read_u8()?;
Ok(Some(Self {
dtc_record: dtc_record.unwrap(),
dtc_fault_detection_counter,
}))
}
fn required_size(&self) -> usize {
4
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
self.dtc_record.encode(writer)?;
writer.write_u8(self.dtc_fault_detection_counter)?;
Ok(self.required_size())
}
}
impl IterableWireFormat for DTCFaultDetectionCounterRecord {}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
pub struct UserDefMemoryDTCByStatusMaskRecord {
pub memory_selection: MemorySelection,
pub status_availability_mask: DTCStatusMask,
pub record_data: Vec<(DTCRecord, DTCStatusMask)>,
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
pub struct UserDefMemoryDTCSnapshotRecordByDTCNumRecord<UserPayload> {
pub memory_selection: MemorySelection,
pub dtc_record: DTCRecord,
pub dtc_status_mask: DTCStatusMask,
pub dtc_snapshot_record: Vec<(DTCSnapshotRecordNumber, DTCSnapshotRecord<UserPayload>)>,
}
impl<UserPayload: IterableWireFormat> WireFormat
for UserDefMemoryDTCSnapshotRecordByDTCNumRecord<UserPayload>
{
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let memory_selection = reader.read_u8()?;
let dtc_record = DTCRecord::decode(reader)?.unwrap();
let dtc_status_mask = DTCStatusMask::decode(reader)?.unwrap();
let mut dtc_snapshot_record = Vec::new();
while let Ok(Some(dtc_snapshot_record_number)) = DTCSnapshotRecordNumber::decode(reader) {
let snapshot_record = DTCSnapshotRecord::decode(reader)?.unwrap();
dtc_snapshot_record.push((dtc_snapshot_record_number, snapshot_record));
}
Ok(Some(UserDefMemoryDTCSnapshotRecordByDTCNumRecord {
memory_selection,
dtc_record,
dtc_status_mask,
dtc_snapshot_record,
}))
}
fn required_size(&self) -> usize {
1 + self.dtc_record.required_size()
+ self.dtc_status_mask.required_size()
+ self
.dtc_snapshot_record
.iter()
.fold(0, |acc, (record_number, record)| {
acc + record_number.required_size() + record.required_size()
})
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
writer.write_u8(self.memory_selection)?;
self.dtc_record.encode(writer)?;
self.dtc_status_mask.encode(writer)?;
for (record_number, record) in &self.dtc_snapshot_record {
record_number.encode(writer)?;
record.encode(writer)?;
}
Ok(self.required_size())
}
}
impl<UserPayload: IterableWireFormat> SingleValueWireFormat
for UserDefMemoryDTCSnapshotRecordByDTCNumRecord<UserPayload>
{
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
pub struct WWHOBDDTCByMaskRecord {
pub functional_group_identifier: FunctionalGroupIdentifier,
pub status_availability_mask: DTCStatusAvailabilityMask,
pub severity_availability_mask: DTCSeverityMask,
pub format_identifier: DTCFormatIdentifier,
pub record_data: Vec<(DTCSeverityMask, DTCRecord, DTCStatusMask)>,
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
pub struct WWHOBDDTCWithPermanentStatusRecord {
pub functional_group_identifier: FunctionalGroupIdentifier,
pub status_availability_mask: DTCStatusAvailabilityMask,
pub format_identifier: DTCFormatIdentifier,
pub record_data: Vec<(DTCRecord, DTCStatusMask)>,
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
pub struct DTCByReadinessGroupIdentifierRecord {
pub functional_group_identifier: FunctionalGroupIdentifier,
pub status_availability_mask: DTCStatusAvailabilityMask,
pub format_identifier: DTCFormatIdentifier,
pub readiness_group_identifier: DTCReadinessGroupIdentifier,
pub record_data: Vec<(DTCRecord, DTCStatusMask)>,
}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
pub struct SupportedDTCExtDataRecord {
pub status_availability_mask: DTCStatusAvailabilityMask,
pub ext_data_record_number: Option<DTCExtDataRecordNumber>,
pub dtc_and_status_records: Vec<(DTCRecord, DTCStatusMask)>,
}
type DTCReadinessGroupIdentifier = u8;
#[allow(non_camel_case_types)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ReadDTCInfoSubFunction {
ReportNumberOfDTC_ByStatusMask(DTCStatusMask),
ReportDTC_ByStatusMask(DTCStatusMask),
ReportDTCSnapshotIdentification,
ReportDTCSnapshotRecord_ByDTCNumber(DTCRecord, DTCSnapshotRecordNumber),
ReportDTCStoredData_ByRecordNumber(DTCStoredDataRecordNumber),
ReportDTCExtDataRecord_ByDTCNumber(DTCRecord, DTCExtDataRecordNumber),
ReportNumberOfDTC_BySeverityMaskRecord(DTCSeverityMask, DTCStatusMask),
ReportDTC_BySeverityMaskRecord(DTCSeverityMask, DTCStatusMask),
ReportSeverityInfoOfDTC(DTCRecord),
ReportSupportedDTC,
ReportFirstTestFailedDTC,
ReportFirstConfirmedDTC,
ReportMostRecentTestFailedDTC,
ReportMostRecentConfirmedDTC,
ReportDTCFaultDetectionCounter,
ReportDTCWithPermanentStatus,
ReportDTCExtDataRecord_ByRecordNumber(DTCExtDataRecordNumber),
ReportUserDefMemoryDTC_ByStatusMask(DTCStatusMask),
ReportUserDefMemoryDTCSnapshotRecord_ByDTCNumber(
DTCRecord,
UserDefDTCSnapshotRecordNumber,
MemorySelection,
),
ReportUserDefMemoryDTCExtDataRecord_ByDTCNumber(
DTCRecord,
DTCExtDataRecordNumber,
MemorySelection,
),
ReportSupportedDTCExtDataRecord(DTCExtDataRecordNumber),
ReportWWHOBDDTC_ByMaskRecord(FunctionalGroupIdentifier, DTCStatusMask, DTCSeverityMask),
ReportWWHOBDDTC_WithPermanentStatus(FunctionalGroupIdentifier),
ReportDTCInformation_ByDTCReadinessGroupIdentifier(
FunctionalGroupIdentifier,
DTCReadinessGroupIdentifier,
),
ISOSAEReserved(u8),
}
impl ReadDTCInfoSubFunction {
#[must_use]
pub fn value(&self) -> u8 {
match self {
Self::ReportNumberOfDTC_ByStatusMask(_) => 0x01,
Self::ReportDTC_ByStatusMask(_) => 0x02,
Self::ReportDTCSnapshotIdentification => 0x03,
Self::ReportDTCSnapshotRecord_ByDTCNumber(_, _) => 0x04,
Self::ReportDTCStoredData_ByRecordNumber(_) => 0x05,
Self::ReportDTCExtDataRecord_ByDTCNumber(_, _) => 0x06,
Self::ReportNumberOfDTC_BySeverityMaskRecord(_, _) => 0x07,
Self::ReportDTC_BySeverityMaskRecord(_, _) => 0x08,
Self::ReportSeverityInfoOfDTC(_) => 0x09,
Self::ReportSupportedDTC => 0x0A,
Self::ReportFirstTestFailedDTC => 0x0B,
Self::ReportFirstConfirmedDTC => 0x0C,
Self::ReportMostRecentTestFailedDTC => 0x0D,
Self::ReportMostRecentConfirmedDTC => 0x0E,
Self::ReportDTCFaultDetectionCounter => 0x14,
Self::ReportDTCWithPermanentStatus => 0x15,
Self::ReportDTCExtDataRecord_ByRecordNumber(_) => 0x16,
Self::ReportUserDefMemoryDTC_ByStatusMask(_) => 0x17,
Self::ReportUserDefMemoryDTCSnapshotRecord_ByDTCNumber(_, _, _) => 0x18,
Self::ReportUserDefMemoryDTCExtDataRecord_ByDTCNumber(_, _, _) => 0x19,
Self::ReportSupportedDTCExtDataRecord(_) => 0x1A,
Self::ReportWWHOBDDTC_ByMaskRecord(_, _, _) => 0x42,
Self::ReportWWHOBDDTC_WithPermanentStatus(_) => 0x55,
Self::ReportDTCInformation_ByDTCReadinessGroupIdentifier(_, _) => 0x56,
Self::ISOSAEReserved(value) => *value,
}
}
}
impl WireFormat for ReadDTCInfoSubFunction {
#[allow(clippy::match_same_arms)]
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let report_type = reader.read_u8()?;
let subfunction = match report_type {
0x01 | 0x02 => {
let status = DTCStatusMask::from(reader.read_u8()?);
match report_type {
0x01 => Self::ReportNumberOfDTC_ByStatusMask(status),
0x02 => Self::ReportDTC_ByStatusMask(status),
_ => unreachable!(),
}
}
0x03 => Self::ReportDTCSnapshotIdentification,
0x04 => Self::ReportDTCSnapshotRecord_ByDTCNumber(
DTCRecord::decode_single_value(reader)?,
DTCSnapshotRecordNumber::decode_single_value(reader)?,
),
0x05 => Self::ReportDTCStoredData_ByRecordNumber(
DTCStoredDataRecordNumber::decode_single_value(reader)?,
),
0x06 => Self::ReportDTCExtDataRecord_ByDTCNumber(
DTCRecord::decode_single_value(reader)?,
DTCExtDataRecordNumber::decode_single_value(reader)?,
),
0x07 => Self::ReportNumberOfDTC_BySeverityMaskRecord(
DTCSeverityMask::from(reader.read_u8()?),
DTCStatusMask::from(reader.read_u8()?),
),
0x08 => Self::ReportDTC_BySeverityMaskRecord(
DTCSeverityMask::from(reader.read_u8()?),
DTCStatusMask::from(reader.read_u8()?),
),
0x09 => Self::ReportSeverityInfoOfDTC(DTCRecord::decode_single_value(reader)?),
0x0A => Self::ReportSupportedDTC,
0x0B => Self::ReportFirstTestFailedDTC,
0x0C => Self::ReportFirstConfirmedDTC,
0x0D => Self::ReportMostRecentTestFailedDTC,
0x0E => Self::ReportMostRecentConfirmedDTC,
0x14 => Self::ReportDTCFaultDetectionCounter,
0x15 => Self::ReportDTCWithPermanentStatus,
0x16 => Self::ReportDTCExtDataRecord_ByRecordNumber(
DTCExtDataRecordNumber::decode_single_value(reader)?,
),
0x17 => {
Self::ReportUserDefMemoryDTC_ByStatusMask(DTCStatusMask::from(reader.read_u8()?))
}
0x18 => Self::ReportUserDefMemoryDTCSnapshotRecord_ByDTCNumber(
DTCRecord::decode_single_value(reader)?,
UserDefDTCSnapshotRecordNumber::decode_single_value(reader)?,
reader.read_u8()?,
),
0x19 => Self::ReportUserDefMemoryDTCExtDataRecord_ByDTCNumber(
DTCRecord::decode_single_value(reader)?,
DTCExtDataRecordNumber::decode_single_value(reader)?,
reader.read_u8()?,
),
0x1A => Self::ReportSupportedDTCExtDataRecord(
DTCExtDataRecordNumber::decode_single_value(reader)?,
),
0x42 => Self::ReportWWHOBDDTC_ByMaskRecord(
FunctionalGroupIdentifier::from(reader.read_u8()?),
DTCStatusMask::from(reader.read_u8()?),
DTCSeverityMask::from(reader.read_u8()?),
),
0x43..=0x54 => Self::ISOSAEReserved(report_type),
0x55 => Self::ReportWWHOBDDTC_WithPermanentStatus(FunctionalGroupIdentifier::from(
reader.read_u8()?,
)),
0x56 => Self::ReportDTCInformation_ByDTCReadinessGroupIdentifier(
FunctionalGroupIdentifier::from(reader.read_u8()?),
reader.read_u8()?,
),
0x57..=0x7F => Self::ISOSAEReserved(report_type),
_ => return Err(Error::InvalidDtcSubfunctionType(report_type)),
};
Ok(Some(subfunction))
}
#[allow(clippy::match_same_arms)]
fn required_size(&self) -> usize {
1 + match self {
Self::ReportNumberOfDTC_ByStatusMask(_) => 1,
Self::ReportDTC_ByStatusMask(_) => 1,
Self::ReportDTCSnapshotIdentification => 0,
Self::ReportDTCSnapshotRecord_ByDTCNumber(_, _) => 4,
Self::ReportDTCStoredData_ByRecordNumber(_) => 2,
Self::ReportDTCExtDataRecord_ByDTCNumber(_, _) => 4,
Self::ReportNumberOfDTC_BySeverityMaskRecord(_, _) => 2,
Self::ReportDTC_BySeverityMaskRecord(_, _) => 2,
Self::ReportSeverityInfoOfDTC(_) => 3,
Self::ReportSupportedDTC => 0,
Self::ReportFirstTestFailedDTC => 0,
Self::ReportFirstConfirmedDTC => 0,
Self::ReportMostRecentTestFailedDTC => 0,
Self::ReportMostRecentConfirmedDTC => 0,
Self::ReportDTCFaultDetectionCounter => 0,
Self::ReportDTCWithPermanentStatus => 0,
Self::ReportDTCExtDataRecord_ByRecordNumber(_) => 1,
Self::ReportUserDefMemoryDTC_ByStatusMask(_) => 1,
Self::ReportUserDefMemoryDTCSnapshotRecord_ByDTCNumber(_, _, _) => 5,
Self::ReportUserDefMemoryDTCExtDataRecord_ByDTCNumber(_, _, _) => 5,
Self::ReportSupportedDTCExtDataRecord(_) => 1,
Self::ReportWWHOBDDTC_ByMaskRecord(_, _, _) => 3,
Self::ReportWWHOBDDTC_WithPermanentStatus(_) => 1,
Self::ReportDTCInformation_ByDTCReadinessGroupIdentifier(_, _) => 2,
Self::ISOSAEReserved(_) => 0,
}
}
#[allow(clippy::match_same_arms)]
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
writer.write_u8(self.value())?;
match self {
Self::ReportNumberOfDTC_ByStatusMask(mask) => {
mask.encode(writer)?;
}
Self::ReportDTC_ByStatusMask(mask) => {
mask.encode(writer)?;
}
Self::ReportDTCSnapshotIdentification => {}
Self::ReportDTCSnapshotRecord_ByDTCNumber(mask, record_number) => {
mask.encode(writer)?;
record_number.encode(writer)?;
}
Self::ReportDTCStoredData_ByRecordNumber(record_number) => {
record_number.encode(writer)?;
}
Self::ReportDTCExtDataRecord_ByDTCNumber(mask, record_number) => {
mask.encode(writer)?;
record_number.encode(writer)?;
}
Self::ReportNumberOfDTC_BySeverityMaskRecord(severity, status) => {
writer.write_u8(severity.bits())?;
status.encode(writer)?;
}
Self::ReportDTC_BySeverityMaskRecord(severity, status) => {
writer.write_u8(severity.bits())?;
status.encode(writer)?;
}
Self::ReportSeverityInfoOfDTC(mask) => {
mask.encode(writer)?;
}
Self::ReportSupportedDTC => {}
Self::ReportFirstTestFailedDTC => {}
Self::ReportFirstConfirmedDTC => {}
Self::ReportMostRecentTestFailedDTC => {}
Self::ReportMostRecentConfirmedDTC => {}
Self::ReportDTCFaultDetectionCounter => {}
Self::ReportDTCWithPermanentStatus => {}
Self::ReportDTCExtDataRecord_ByRecordNumber(record_number) => {
record_number.encode(writer)?;
}
Self::ReportUserDefMemoryDTC_ByStatusMask(mask) => {
mask.encode(writer)?;
}
Self::ReportUserDefMemoryDTCSnapshotRecord_ByDTCNumber(mask, number, selection) => {
mask.encode(writer)?;
number.encode(writer)?;
writer.write_u8(*selection)?;
}
Self::ReportUserDefMemoryDTCExtDataRecord_ByDTCNumber(mask, number, selection) => {
mask.encode(writer)?;
number.encode(writer)?;
writer.write_u8(*selection)?;
}
Self::ReportSupportedDTCExtDataRecord(number) => {
number.encode(writer)?;
}
Self::ReportWWHOBDDTC_ByMaskRecord(group, status, severity) => {
writer.write_u8(group.value())?;
status.encode(writer)?;
writer.write_u8(severity.bits())?;
}
Self::ReportWWHOBDDTC_WithPermanentStatus(group) => {
writer.write_u8(group.value())?;
}
Self::ReportDTCInformation_ByDTCReadinessGroupIdentifier(group, readiness) => {
writer.write_u8(group.value())?;
writer.write_u8(*readiness)?;
}
Self::ISOSAEReserved(value) => {
writer.write_u8(*value)?;
}
}
Ok(self.required_size())
}
}
impl SingleValueWireFormat for ReadDTCInfoSubFunction {}
type NumberOfDTCs = u16;
type DTCStatusAvailabilityMask = DTCStatusMask;
type SubFunctionID = u8;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum ReadDTCInfoResponse<UserPayload> {
NumberOfDTCs(SubFunctionID, DTCStatusAvailabilityMask, NumberOfDTCs),
DTCList(
SubFunctionID,
DTCStatusAvailabilityMask,
Vec<(DTCRecord, DTCStatusMask)>,
),
DTCSnapshotList(Vec<(DTCRecord, DTCSnapshotRecordNumber)>),
DTCSnapshotRecordList(DTCSnapshotRecordList<UserPayload>),
DTCExtDataRecordList(DTCExtDataRecordList<UserPayload>),
DTCSeverityRecordList(
SubFunctionID,
DTCStatusAvailabilityMask,
Vec<DTCSeverityRecord>,
),
DTCFaultDetectionCounterRecordList(Vec<DTCFaultDetectionCounterRecord>),
UserDefMemoryDTCByStatusMaskList(UserDefMemoryDTCByStatusMaskRecord),
UserDefMemoryDTCSnapshotRecordByDTCNumberList(
UserDefMemoryDTCSnapshotRecordByDTCNumRecord<UserPayload>,
),
SupportedDTCExtDataRecordList(SupportedDTCExtDataRecord),
WWHOBDDTCByMaskRecordList(WWHOBDDTCByMaskRecord),
WWHOBDDTCWithPermanentStatusList(WWHOBDDTCWithPermanentStatusRecord),
DTCByReadinessGroupIdentifierList(DTCByReadinessGroupIdentifierRecord),
}
impl<UserPayload: IterableWireFormat> WireFormat for ReadDTCInfoResponse<UserPayload> {
#[allow(clippy::too_many_lines)]
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let subfunction_id = reader.read_u8()?;
match subfunction_id {
0x01 | 0x07 => {
let status = DTCStatusAvailabilityMask::from(reader.read_u8()?);
let count = reader.read_u16::<byteorder::BigEndian>()?;
Ok(Some(Self::NumberOfDTCs(subfunction_id, status, count)))
}
0x02 | 0x0A | 0x0B | 0x0C | 0x0D | 0x0E | 0x15 => {
let status = DTCStatusAvailabilityMask::from(reader.read_u8()?);
let mut dtcs: Vec<(DTCRecord, DTCStatusMask)> = Vec::new();
while let Some(record) = DTCRecord::decode(reader)? {
match reader.read_u8() {
Ok(status) => dtcs.push((record, DTCStatusMask::from(status))),
Err(_) => break,
}
}
Ok(Some(Self::DTCList(subfunction_id, status, dtcs)))
}
0x03 => {
let mut dtcs: Vec<(DTCRecord, DTCSnapshotRecordNumber)> = Vec::new();
while let Some(record) = DTCRecord::decode(reader)? {
match DTCSnapshotRecordNumber::decode(reader)? {
Some(number) => dtcs.push((record, number)),
None => break,
}
}
Ok(Some(Self::DTCSnapshotList(dtcs)))
}
0x04 => {
let snapshot_list = DTCSnapshotRecordList::decode(reader)?.unwrap();
Ok(Some(Self::DTCSnapshotRecordList(snapshot_list)))
}
0x06 => {
let ext_data_list = DTCExtDataRecordList::decode(reader)?.unwrap();
Ok(Some(Self::DTCExtDataRecordList(ext_data_list)))
}
0x08 | 0x09 => {
let status = DTCStatusAvailabilityMask::from(reader.read_u8()?);
let mut dtcs = Vec::new();
for dtc_severity_record in DTCSeverityRecord::decode_iterable(reader) {
match dtc_severity_record {
Ok(p) => {
dtcs.push(p);
}
Err(e) => {
return Err(e);
}
}
}
Ok(Some(Self::DTCSeverityRecordList(
subfunction_id,
status,
dtcs,
)))
}
0x14 => {
let mut dtcs = Vec::new();
for dtc_fault_record in DTCFaultDetectionCounterRecord::decode_iterable(reader) {
match dtc_fault_record {
Ok(p) => {
dtcs.push(p);
}
Err(e) => {
return Err(e);
}
}
}
Ok(Some(Self::DTCFaultDetectionCounterRecordList(dtcs)))
}
0x17 => {
let memory_selection = reader.read_u8()?;
let status_availability_mask = DTCStatusMask::decode_single_value(reader)?;
let mut record_data = Vec::new();
while let Ok(Some(record)) = DTCRecord::decode(reader) {
let status = DTCStatusMask::decode(reader)?;
record_data.push((record, status.unwrap()));
}
Ok(Some(Self::UserDefMemoryDTCByStatusMaskList(
UserDefMemoryDTCByStatusMaskRecord {
memory_selection,
status_availability_mask,
record_data,
},
)))
}
0x18 => Ok(Some(Self::UserDefMemoryDTCSnapshotRecordByDTCNumberList(
UserDefMemoryDTCSnapshotRecordByDTCNumRecord::decode(reader)?.unwrap(),
))),
0x1A => {
let status_availability_mask =
DTCStatusAvailabilityMask::decode_single_value(reader)?;
let mut dtc_and_status_records = Vec::new();
let ext_data_record_number = DTCExtDataRecordNumber::decode(reader)?;
if ext_data_record_number.is_some() {
while let Some(dtc_record) = DTCRecord::decode(reader)? {
let dtc_status = DTCStatusMask::decode_single_value(reader)?;
dtc_and_status_records.push((dtc_record, dtc_status));
}
}
Ok(Some(Self::SupportedDTCExtDataRecordList(
SupportedDTCExtDataRecord {
status_availability_mask,
ext_data_record_number,
dtc_and_status_records,
},
)))
}
0x42 => {
let functional_group_identifier =
FunctionalGroupIdentifier::from(reader.read_u8()?);
let status_availability_mask =
DTCStatusAvailabilityMask::decode_single_value(reader)?;
let severity_availability_mask = DTCSeverityMask::from(reader.read_u8()?);
let format_identifier = DTCFormatIdentifier::from(reader.read_u8()?);
if (format_identifier != DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04)
&& (format_identifier != DTCFormatIdentifier::SAE_J1939_73_DTCFormat)
{
return Err(Error::InvalidDtcFormatIdentifier(u8::from(
format_identifier,
)));
}
let mut record_data = Vec::new();
while let Ok(dtc_severity_mask) = reader.read_u8() {
let dtc_severity_mask = DTCSeverityMask::from(dtc_severity_mask);
let dtc_record = DTCRecord::decode_single_value(reader)?;
let dtc_status = DTCStatusMask::decode_single_value(reader)?;
record_data.push((dtc_severity_mask, dtc_record, dtc_status));
}
Ok(Some(Self::WWHOBDDTCByMaskRecordList(
WWHOBDDTCByMaskRecord {
functional_group_identifier,
status_availability_mask,
severity_availability_mask,
format_identifier,
record_data,
},
)))
}
0x55 => {
let functional_group_identifier =
FunctionalGroupIdentifier::from(reader.read_u8()?);
let status_availability_mask = DTCStatusAvailabilityMask::decode(reader)?.unwrap();
let format_identifier = DTCFormatIdentifier::from(reader.read_u8()?);
if !matches!(
format_identifier,
DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04
| DTCFormatIdentifier::SAE_J1939_73_DTCFormat
) {
return Err(Error::InvalidDtcFormatIdentifier(u8::from(
format_identifier,
)));
}
let mut record_data = Vec::new();
while let Ok(Some(dtc_record)) = DTCRecord::decode(reader) {
let dtc_status = DTCStatusMask::decode(reader)?.unwrap();
record_data.push((dtc_record, dtc_status));
}
Ok(Some(Self::WWHOBDDTCWithPermanentStatusList(
WWHOBDDTCWithPermanentStatusRecord {
functional_group_identifier,
status_availability_mask,
format_identifier,
record_data,
},
)))
}
0x56 => {
let functional_group_identifier =
FunctionalGroupIdentifier::from(reader.read_u8()?);
let status_availability_mask =
DTCStatusAvailabilityMask::decode_single_value(reader)?;
let format_identifier = DTCFormatIdentifier::from(reader.read_u8()?);
let readiness_group_identifier =
DTCReadinessGroupIdentifier::from(reader.read_u8()?);
let mut record_data = Vec::new();
while let Ok(Some(dtc_record)) = DTCRecord::decode(reader) {
let dtc_status = DTCStatusMask::decode_single_value(reader)?;
record_data.push((dtc_record, dtc_status));
}
Ok(Some(Self::DTCByReadinessGroupIdentifierList(
DTCByReadinessGroupIdentifierRecord {
functional_group_identifier,
status_availability_mask,
format_identifier,
readiness_group_identifier,
record_data,
},
)))
}
_ => todo!(), }
}
fn required_size(&self) -> usize {
1 + match self {
Self::NumberOfDTCs(_, _, _) => 3,
Self::DTCList(_, _, list) => 1 + list.len() * 4,
Self::DTCSnapshotList(list) => 1 + list.len() * 4,
Self::DTCSnapshotRecordList(list) => list.required_size(),
Self::DTCExtDataRecordList(list) => list.required_size(),
Self::DTCSeverityRecordList(_, _, list) => 1 + list.len() * 6,
Self::DTCFaultDetectionCounterRecordList(list) => list.len() * 4,
Self::UserDefMemoryDTCByStatusMaskList(list) => 2 + list.record_data.len() * 4,
Self::UserDefMemoryDTCSnapshotRecordByDTCNumberList(list) => list.required_size(),
Self::SupportedDTCExtDataRecordList(list) => {
if list.ext_data_record_number.is_some() {
2 + list.dtc_and_status_records.len() * 4
} else {
1
}
}
Self::WWHOBDDTCByMaskRecordList(response_struct) => {
4 + response_struct.record_data.len() * 5
}
Self::WWHOBDDTCWithPermanentStatusList(response_struct) => {
3 + response_struct.record_data.len() * 4
}
Self::DTCByReadinessGroupIdentifierList(response_struct) => {
4 + response_struct.record_data.len() * 4
}
}
}
#[allow(clippy::too_many_lines)]
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
match self {
Self::NumberOfDTCs(id, mask, count) => {
writer.write_u8(*id)?;
writer.write_u8(mask.bits())?;
writer.write_u16::<byteorder::BigEndian>(*count)?;
}
Self::DTCList(id, mask, list) => {
writer.write_u8(*id)?;
writer.write_u8(mask.bits())?;
for (record, status) in list {
record.encode(writer)?;
status.encode(writer)?;
}
}
Self::DTCSnapshotList(list) => {
writer.write_u8(0x03)?;
for (record, number) in list {
record.encode(writer)?;
number.encode(writer)?;
}
}
Self::DTCSnapshotRecordList(list) => {
writer.write_u8(0x04)?;
list.encode(writer)?;
}
Self::DTCExtDataRecordList(list) => {
writer.write_u8(0x06)?;
list.encode(writer)?;
}
Self::DTCFaultDetectionCounterRecordList(list) => {
writer.write_u8(0x14)?;
for fault_detection_counter in list {
fault_detection_counter.encode(writer)?;
}
}
Self::DTCSeverityRecordList(id, status, list) => {
writer.write_u8(*id)?;
status.encode(writer)?;
for dtcs in list {
dtcs.encode(writer)?;
}
}
Self::UserDefMemoryDTCByStatusMaskList(data_record_struct) => {
writer.write_u8(0x17)?;
writer.write_u8(data_record_struct.memory_selection)?;
data_record_struct.status_availability_mask.encode(writer)?;
for (data_record, status) in &data_record_struct.record_data {
data_record.encode(writer)?;
status.encode(writer)?;
}
}
Self::UserDefMemoryDTCSnapshotRecordByDTCNumberList(snapshot_struct) => {
writer.write_u8(0x18)?;
snapshot_struct.encode(writer)?;
}
Self::SupportedDTCExtDataRecordList(response_struct) => {
writer.write_u8(0x1A)?;
response_struct.status_availability_mask.encode(writer)?;
if let Some(record_number) = &response_struct.ext_data_record_number {
record_number.encode(writer)?;
for (record, status) in &response_struct.dtc_and_status_records {
record.encode(writer)?;
status.encode(writer)?;
}
}
}
Self::WWHOBDDTCByMaskRecordList(response_struct) => {
writer.write_u8(0x42)?;
writer.write_u8(response_struct.functional_group_identifier.value())?;
response_struct.status_availability_mask.encode(writer)?;
writer.write_u8(response_struct.severity_availability_mask.into())?;
writer.write_u8(response_struct.format_identifier.into())?;
for (dtc_severity, dtc_record, dtc_status) in &response_struct.record_data {
writer.write_u8((*dtc_severity).into())?;
dtc_record.encode(writer)?;
dtc_status.encode(writer)?;
}
}
Self::WWHOBDDTCWithPermanentStatusList(response_struct) => {
writer.write_u8(0x55)?;
writer.write_u8(response_struct.functional_group_identifier.value())?;
response_struct.status_availability_mask.encode(writer)?;
writer.write_u8(response_struct.format_identifier.into())?;
for (dtc_record, dtc_status) in &response_struct.record_data {
dtc_record.encode(writer)?;
dtc_status.encode(writer)?;
}
}
Self::DTCByReadinessGroupIdentifierList(response_struct) => {
writer.write_u8(0x56)?;
writer.write_u8(response_struct.functional_group_identifier.value())?;
response_struct.status_availability_mask.encode(writer)?;
writer.write_u8(response_struct.format_identifier.into())?;
writer.write_u8(response_struct.readiness_group_identifier)?;
for (dtc_record, dtc_status) in &response_struct.record_data {
dtc_record.encode(writer)?;
dtc_status.encode(writer)?;
}
}
}
Ok(self.required_size())
}
}
impl<UserPayload: IterableWireFormat> SingleValueWireFormat for ReadDTCInfoResponse<UserPayload> {}
#[cfg(test)]
mod response {
use super::*;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TestIdentifier {
Abracadabra = 0xBEEF,
}
impl PartialEq<u16> for TestIdentifier {
fn eq(&self, other: &u16) -> bool {
match self {
TestIdentifier::Abracadabra => *other == 0xBEEF,
}
}
}
impl WireFormat for TestIdentifier {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let mut buf = [0u8; 2];
reader.read_exact(&mut buf)?;
let id = u16::from_be_bytes(buf);
if TestIdentifier::Abracadabra == id {
Ok(Some(TestIdentifier::Abracadabra))
} else {
Err(Error::NoDataAvailable)
}
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
writer.write_u16::<byteorder::BigEndian>(*self as u16)?;
Ok(self.required_size())
}
fn required_size(&self) -> usize {
2
}
}
impl IterableWireFormat for TestIdentifier {}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
enum TestPayload {
Abracadabra(u8),
}
impl WireFormat for TestPayload {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let mut buf = [0u8; 2];
reader.read_exact(&mut buf)?;
let value = u16::from_be_bytes(buf);
if value == TestIdentifier::Abracadabra as u16 {
let mut byte = [0u8; 1];
reader.read_exact(&mut byte)?;
Ok(Some(TestPayload::Abracadabra(byte[0])))
} else {
Err(Error::NoDataAvailable)
}
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
let id_bytes: u16 = match self {
TestPayload::Abracadabra(_) => 0xBEEF,
};
writer.write_all(&id_bytes.to_be_bytes())?;
match self {
TestPayload::Abracadabra(value) => {
writer.write_u8(*value)?;
Ok(self.required_size())
}
}
}
fn required_size(&self) -> usize {
3
}
}
impl IterableWireFormat for TestPayload {}
#[test]
fn dtc_list() {
#[rustfmt::skip]
let bytes = [
0x02, 0x01, 0x01, 0x02, 0x03, (DTCStatusMask::PendingDTC | DTCStatusMask::TestFailed).into(),
0x17, 0x04, 0x03, DTCStatusMask::TestNotCompletedThisOperationCycle.into(),
];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::DTCList(
0x02,
DTCStatusMask::TestFailed,
vec![
(
DTCRecord::new(0x01, 0x02, 0x03),
DTCStatusMask::PendingDTC | DTCStatusMask::TestFailed
),
(
DTCRecord::new(0x17, 0x04, 0x03),
DTCStatusMask::TestNotCompletedThisOperationCycle
)
]
)
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes);
assert_eq!(written, bytes.len());
assert_eq!(written, response.required_size());
}
#[test]
fn severity_list_test() {
let bytes: [u8; 8] = [
0x08, 0x01, DTCSeverityMask::CheckImmediately.into(),
FunctionalGroupIdentifier::EmissionsSystemGroup.into(),
0x01,
0x02,
0x03,
(DTCStatusMask::PendingDTC | DTCStatusMask::TestFailed).into(),
];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::DTCSeverityRecordList(
0x08,
DTCStatusMask::TestFailed,
vec![
(DTCSeverityRecord {
severity: DTCSeverityMask::CheckImmediately,
functional_group_identifier:
FunctionalGroupIdentifier::EmissionsSystemGroup,
dtc_record: DTCRecord::new(0x01, 0x02, 0x03),
dtc_status_mask: (DTCStatusMask::PendingDTC | DTCStatusMask::TestFailed),
})
]
)
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes);
assert_eq!(written, bytes.len());
assert_eq!(written, response.required_size());
}
#[test]
fn severity_empty_list_test() {
let bytes: [u8; 2] = [
0x08, 0x01, ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::DTCSeverityRecordList(0x08, DTCStatusMask::TestFailed, vec![])
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes);
assert_eq!(written, bytes.len());
assert_eq!(written, response.required_size());
}
#[test]
fn fault_detection_test() {
let bytes = [
0x14, 0x01, 0x02, 0x03, 0x04, ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::DTCFaultDetectionCounterRecordList(vec![
DTCFaultDetectionCounterRecord {
dtc_record: DTCRecord::new(0x01, 0x02, 0x03),
dtc_fault_detection_counter: 0x04
}
])
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes);
assert_eq!(written, bytes.len());
assert_eq!(written, response.required_size());
}
#[test]
fn fault_detection_empty_test() {
let bytes = [
0x14, ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::DTCFaultDetectionCounterRecordList(vec![])
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes);
assert_eq!(written, bytes.len());
assert_eq!(written, response.required_size());
}
#[test]
fn user_def_memory_dtc_by_statusmask_empty_list() {
#[rustfmt::skip]
let bytes = [
0x17, 0x15, DTCStatusAvailabilityMask::TestFailed.into(), ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::UserDefMemoryDTCByStatusMaskList(
UserDefMemoryDTCByStatusMaskRecord {
memory_selection: 0x15,
status_availability_mask: DTCStatusAvailabilityMask::TestFailed,
record_data: vec![]
}
)
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn user_def_memory_dtc_by_statusmask_list() {
#[rustfmt::skip]
let bytes = [
0x17, 0x15, DTCStatusAvailabilityMask::TestFailed.into(), 0x12, 0x34, 0x56, DTCStatusMask::TestFailed.into(), 0x12, 0x34, 0x56, DTCStatusMask::TestFailed.into(), ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::UserDefMemoryDTCByStatusMaskList(
UserDefMemoryDTCByStatusMaskRecord {
memory_selection: 0x15,
status_availability_mask: DTCStatusAvailabilityMask::TestFailed,
record_data: vec![
(DTCRecord::new(0x12, 0x34, 0x56), DTCStatusMask::TestFailed),
(DTCRecord::new(0x12, 0x34, 0x56), DTCStatusMask::TestFailed),
]
}
)
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn user_def_memory_dtc_by_dtc_number_empty_list() {
#[rustfmt::skip]
let bytes = [
0x18, 0x01, 0x12, 0x34, 0x56, DTCStatusAvailabilityMask::TestFailed.into(), ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::UserDefMemoryDTCSnapshotRecordByDTCNumberList(
UserDefMemoryDTCSnapshotRecordByDTCNumRecord {
memory_selection: 0x1,
dtc_record: DTCRecord::new(0x12, 0x34, 0x56),
dtc_status_mask: DTCStatusMask::TestFailed,
dtc_snapshot_record: vec![]
}
)
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn user_def_memory_dtc_by_dtc_number_list() {
#[rustfmt::skip]
let bytes = [
0x18, 0x01, 0x12, 0x34, 0x56, DTCStatusAvailabilityMask::TestFailed.into(), 0x13, 0x02, 0xBE, 0xEF, 0x05, 0xBE, 0xEF, 0x05, ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::UserDefMemoryDTCSnapshotRecordByDTCNumberList(
UserDefMemoryDTCSnapshotRecordByDTCNumRecord {
memory_selection: 0x1,
dtc_record: DTCRecord::new(0x12, 0x34, 0x56),
dtc_status_mask: DTCStatusMask::TestFailed,
dtc_snapshot_record: vec![(
DTCSnapshotRecordNumber::new(0x13),
DTCSnapshotRecord {
data: vec![
TestPayload::Abracadabra(0x05),
TestPayload::Abracadabra(0x05)
]
}
)]
}
)
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn supported_dtc_ext_data_record_list() {
#[rustfmt::skip]
let bytes = [
0x1A, DTCStatusAvailabilityMask::TestFailed.into(), DTCExtDataRecordNumber::AllDTCExtDataRecords.value(), 0x15,0x17,0x19 , DTCStatusMask::TestFailedSinceLastClear.into(), 0x15,0x17,0x19 , DTCStatusMask::TestFailedSinceLastClear.into(), ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::SupportedDTCExtDataRecordList(SupportedDTCExtDataRecord {
status_availability_mask: DTCStatusAvailabilityMask::TestFailed,
ext_data_record_number: Some(DTCExtDataRecordNumber::AllDTCExtDataRecords),
dtc_and_status_records: vec![
(
DTCRecord::new(0x15, 0x17, 0x19), DTCStatusMask::TestFailedSinceLastClear
),
(
DTCRecord::new(0x15, 0x17, 0x19), DTCStatusMask::TestFailedSinceLastClear
)
]
})
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn supported_dtc_ext_data_record_empty_list() {
#[rustfmt::skip]
let bytes = [
0x1A, DTCStatusAvailabilityMask::TestFailed.into(), ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::SupportedDTCExtDataRecordList(SupportedDTCExtDataRecord {
status_availability_mask: DTCStatusAvailabilityMask::TestFailed,
ext_data_record_number: None,
dtc_and_status_records: vec![]
})
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn report_wwhobd_dtc_by_mask_record_list() {
#[rustfmt::skip]
let bytes = [
0x42, FunctionalGroupIdentifier::VODBSystem.into(),
DTCStatusAvailabilityMask::TestFailed.into(),
DTCSeverityMask::DTCClass_0.into(),
DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(),
DTCSeverityMask::DTCClass_0.into(),
0x15,0x17,0x19 , DTCStatusAvailabilityMask::TestFailed.into(),
DTCSeverityMask::DTCClass_0.into(),
0x15,0x17,0x19 , DTCStatusAvailabilityMask::TestFailed.into(),
];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::WWHOBDDTCByMaskRecordList(WWHOBDDTCByMaskRecord {
functional_group_identifier: FunctionalGroupIdentifier::VODBSystem,
status_availability_mask: DTCStatusAvailabilityMask::TestFailed,
severity_availability_mask: DTCSeverityMask::DTCClass_0,
format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04,
record_data: vec![
(
DTCSeverityMask::DTCClass_0,
DTCRecord::new(0x15, 0x17, 0x19),
DTCStatusAvailabilityMask::TestFailed
),
(
DTCSeverityMask::DTCClass_0,
DTCRecord::new(0x15, 0x17, 0x19),
DTCStatusAvailabilityMask::TestFailed
)
]
})
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn report_wwhobd_dtc_by_mask_record_empty_list() {
#[rustfmt::skip]
let bytes = [
0x42, FunctionalGroupIdentifier::VODBSystem.into(),
DTCStatusAvailabilityMask::TestFailed.into(),
DTCSeverityMask::all_flags().into(),
DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(),
];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::WWHOBDDTCByMaskRecordList(WWHOBDDTCByMaskRecord {
functional_group_identifier: FunctionalGroupIdentifier::VODBSystem,
status_availability_mask: DTCStatusAvailabilityMask::TestFailed,
severity_availability_mask: DTCSeverityMask::all_flags(),
format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04,
record_data: vec![]
})
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn report_wwhobd_dtc_with_permanent_status_list() {
#[rustfmt::skip]
let bytes = [
0x55, FunctionalGroupIdentifier::VODBSystem.into(),
DTCStatusAvailabilityMask::TestFailed.into(),
DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(),
0x15,0x17,0x19 , DTCStatusMask::TestFailed.into(),
0x51,0x71,0x91 , DTCStatusMask::TestFailed.into(),
];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::WWHOBDDTCWithPermanentStatusList(
WWHOBDDTCWithPermanentStatusRecord {
functional_group_identifier: FunctionalGroupIdentifier::VODBSystem,
status_availability_mask: DTCStatusAvailabilityMask::TestFailed,
format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04,
record_data: vec![
(DTCRecord::new(0x15, 0x17, 0x19), DTCStatusMask::TestFailed),
(DTCRecord::new(0x51, 0x71, 0x91), DTCStatusMask::TestFailed)
]
}
)
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn report_dtc_by_readiness_group_identifier_list() {
#[rustfmt::skip]
let bytes = [
0x56, FunctionalGroupIdentifier::VODBSystem.into(),
DTCStatusAvailabilityMask::TestFailed.into(),
DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(),
0x72, 0x15,0x17,0x19 , DTCStatusAvailabilityMask::TestFailed.into(),
0x51,0x71,0x91 , DTCStatusAvailabilityMask::TestFailed.into(),
];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::DTCByReadinessGroupIdentifierList(
DTCByReadinessGroupIdentifierRecord {
functional_group_identifier: FunctionalGroupIdentifier::VODBSystem,
status_availability_mask: DTCStatusAvailabilityMask::TestFailed,
format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04,
readiness_group_identifier: DTCReadinessGroupIdentifier::from(0x72),
record_data: vec![
(
DTCRecord::new(0x15, 0x17, 0x19),
DTCStatusAvailabilityMask::TestFailed
),
(
DTCRecord::new(0x51, 0x71, 0x91),
DTCStatusAvailabilityMask::TestFailed
)
]
}
)
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
#[test]
fn report_dtc_by_readiness_group_identifier_empty_list() {
#[rustfmt::skip]
let bytes = [
0x56, FunctionalGroupIdentifier::VODBSystem.into(),
DTCStatusAvailabilityMask::TestFailed.into(),
DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04.into(),
0x72, ];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestPayload> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
assert_eq!(
response,
ReadDTCInfoResponse::DTCByReadinessGroupIdentifierList(
DTCByReadinessGroupIdentifierRecord {
functional_group_identifier: FunctionalGroupIdentifier::VODBSystem,
status_availability_mask: DTCStatusAvailabilityMask::TestFailed,
format_identifier: DTCFormatIdentifier::SAE_J2012_DA_DTCFormat_04,
readiness_group_identifier: DTCReadinessGroupIdentifier::from(0x72),
record_data: vec![]
}
)
);
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
}
#[cfg(test)]
mod ext_data {
use super::*;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TestDTCExtDataRecordNumber {
WarmUpCycleCount = 0x04,
FaultDetectionCounter = 0x05,
}
impl WireFormat for TestDTCExtDataRecordNumber {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let id = reader.read_u8();
match id {
Ok(0x04) => Ok(Some(TestDTCExtDataRecordNumber::WarmUpCycleCount)),
Ok(0x05) => Ok(Some(TestDTCExtDataRecordNumber::FaultDetectionCounter)),
Err(_) => Ok(None),
_ => Err(Error::NoDataAvailable),
}
}
fn required_size(&self) -> usize {
1
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
writer.write_u8(*self as u8)?;
Ok(self.required_size())
}
}
impl IterableWireFormat for TestDTCExtDataRecordNumber {}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
enum TestDTCExtData {
WarmUpCycleCount(u16),
FaultDetectionCounter(u8),
}
impl WireFormat for TestDTCExtData {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let id = TestDTCExtDataRecordNumber::decode(reader)?;
match id {
Some(TestDTCExtDataRecordNumber::WarmUpCycleCount) => {
let count = reader.read_u16::<byteorder::BigEndian>()?;
Ok(Some(TestDTCExtData::WarmUpCycleCount(count)))
}
Some(TestDTCExtDataRecordNumber::FaultDetectionCounter) => {
let count = reader.read_u8()?;
Ok(Some(TestDTCExtData::FaultDetectionCounter(count)))
}
None => Ok(None),
}
}
fn required_size(&self) -> usize {
match self {
TestDTCExtData::WarmUpCycleCount(_) => 3,
TestDTCExtData::FaultDetectionCounter(_) => 2,
}
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
match self {
TestDTCExtData::WarmUpCycleCount(count) => {
writer.write_u8(TestDTCExtDataRecordNumber::WarmUpCycleCount as u8)?;
writer.write_u16::<byteorder::BigEndian>(*count)?;
}
TestDTCExtData::FaultDetectionCounter(count) => {
writer.write_u8(TestDTCExtDataRecordNumber::FaultDetectionCounter as u8)?;
writer.write_u8(*count)?;
}
}
Ok(self.required_size())
}
}
impl IterableWireFormat for TestDTCExtData {}
#[test]
fn ext_data_list() {
#[rustfmt::skip]
let bytes = [
0x06, 0x12, 0x34, 0x56, 0x24, 0x04, 0xBE, 0xEF,
0x05, 0x10,
];
let mut reader = &bytes[..];
let response: ReadDTCInfoResponse<TestDTCExtData> =
ReadDTCInfoResponse::decode_single_value(&mut reader).unwrap();
let mut writer = Vec::new();
let written = response.encode(&mut writer).unwrap();
assert_eq!(writer, bytes, "Written: \n{writer:02X?}\n{bytes:02X?}");
assert_eq!(written, bytes.len(), "Written: \n{writer:?}\n{bytes:?}");
assert_eq!(written, response.required_size());
}
}
#[cfg(test)]
mod request {
use super::*;
use crate::DTCStatusMask;
#[test]
fn test_read_dtc_information_request() {
let bytes = [0x01, 0x01];
let mut reader = &bytes[..];
let mut writer = Vec::new();
ReadDTCInfoRequest::new(ReadDTCInfoSubFunction::ReportDTCStoredData_ByRecordNumber(
DTCStoredDataRecordNumber::new(5).unwrap(),
))
.encode(&mut writer)
.unwrap();
let request = ReadDTCInfoRequest::decode(&mut reader).unwrap().unwrap();
assert_eq!(
request,
ReadDTCInfoRequest {
dtc_subfunction: ReadDTCInfoSubFunction::ReportNumberOfDTC_ByStatusMask(
DTCStatusMask::TestFailed
)
}
);
}
#[test]
fn test_read_dtc_information_subfunction() {
let mut writer = Vec::new();
let b = ReadDTCInfoSubFunction::ReportDTCWithPermanentStatus;
b.encode(&mut writer).unwrap();
assert_eq!(writer, vec![0x15]);
for id in 0x01..=0x07 {
let mut writer = Vec::new();
let func = match id {
0x01 => ReadDTCInfoSubFunction::ReportNumberOfDTC_ByStatusMask(
DTCStatusMask::TestFailed,
),
0x02 => ReadDTCInfoSubFunction::ReportDTC_ByStatusMask(
DTCStatusMask::WarningIndicatorRequested,
),
0x03 => ReadDTCInfoSubFunction::ReportDTCSnapshotIdentification,
0x04 => ReadDTCInfoSubFunction::ReportDTCSnapshotRecord_ByDTCNumber(
DTCRecord::new(0x01, 0x02, 0x03),
DTCSnapshotRecordNumber::new(0x04),
),
0x05 => ReadDTCInfoSubFunction::ReportDTCStoredData_ByRecordNumber(
DTCStoredDataRecordNumber::new(0x20).unwrap(),
),
0x06 => ReadDTCInfoSubFunction::ReportDTCExtDataRecord_ByDTCNumber(
DTCRecord::new(0x01, 0x02, 0x03),
DTCExtDataRecordNumber::new(0x04),
),
0x07 => ReadDTCInfoSubFunction::ReportNumberOfDTC_BySeverityMaskRecord(
DTCSeverityMask::DTCClass_4,
DTCStatusMask::TestFailed,
),
_ => unreachable!("Invalid loop value"),
};
let written = func.encode(&mut writer).unwrap();
assert_eq!(written, func.required_size());
}
}
}