use byteorder::{ReadBytesExt, WriteBytesExt};
use crate::{
DTCRecord, DTCStatusMask, Error, IterableWireFormat, SingleValueWireFormat, WireFormat,
};
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
pub struct DTCSnapshotRecordList<UserPayload> {
pub dtc_record: DTCRecord,
pub status_mask: DTCStatusMask,
pub snapshot_data: Vec<(DTCSnapshotRecordNumber, DTCSnapshotRecord<UserPayload>)>,
}
impl<Identifier: IterableWireFormat> WireFormat for DTCSnapshotRecordList<Identifier> {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let dtc_record = DTCRecord::decode(reader)?;
if dtc_record.is_none() {
return Ok(None);
}
let status_mask = DTCStatusMask::decode(reader)?;
let mut snapshot_data = Vec::new();
loop {
let record_number = match DTCSnapshotRecordNumber::decode(reader) {
Ok(Some(record_number)) => record_number,
Ok(None) => break,
Err(e) => return Err(e),
};
let record = match DTCSnapshotRecord::decode(reader) {
Ok(Some(record)) => record,
Ok(None) => break,
Err(e) => return Err(e),
};
snapshot_data.push((record_number, record));
}
Ok(Some(Self {
dtc_record: dtc_record.unwrap(),
status_mask: status_mask.unwrap(),
snapshot_data,
}))
}
fn required_size(&self) -> usize {
self.dtc_record.required_size()
+ self.status_mask.required_size()
+ self
.snapshot_data
.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> {
self.dtc_record.encode(writer)?;
self.status_mask.encode(writer)?;
for (record_number, record) in &self.snapshot_data {
record_number.encode(writer)?;
record.encode(writer)?;
}
Ok(self.required_size())
}
}
impl<UserPayload: IterableWireFormat> SingleValueWireFormat for DTCSnapshotRecordList<UserPayload> {}
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
pub struct DTCSnapshotRecord<UserPayload> {
pub data: Vec<UserPayload>,
}
impl<UserPayload: IterableWireFormat> DTCSnapshotRecord<UserPayload> {
#[must_use]
pub fn new(data: Vec<UserPayload>) -> Self {
Self { data }
}
#[allow(clippy::cast_possible_truncation)]
#[must_use]
pub fn number_of_dids(&self) -> u8 {
if self.data.len() > 0xFF {
0
} else {
self.data.len() as u8
}
}
}
impl<UserPayload: IterableWireFormat> WireFormat for DTCSnapshotRecord<UserPayload> {
#[allow(clippy::cast_possible_truncation)]
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let number_of_dids = reader.read_u8()?;
let mut data = Vec::new();
for payload in UserPayload::decode_iterable(reader) {
match payload {
Ok(did) => {
data.push(did);
if number_of_dids != 0 && data.len() == number_of_dids as usize {
break;
}
}
Err(e) => {
return Err(e);
}
}
}
if number_of_dids != 0x00 && number_of_dids != data.len() as u8 {
return Err(Error::IncorrectMessageLengthOrInvalidFormat);
}
Ok(Some(Self { data }))
}
fn required_size(&self) -> usize {
1 + self
.data
.iter()
.map(WireFormat::required_size)
.sum::<usize>()
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
writer.write_u8(self.number_of_dids())?;
let mut payload_written = 0;
for payload in &self.data {
payload_written += payload.encode(writer)?;
}
Ok(1 + payload_written)
}
}
pub type UserDefDTCSnapshotRecordNumber = DTCSnapshotRecordNumber;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DTCSnapshotRecordNumber {
Reserved(u8),
Number(u8),
All,
}
impl DTCSnapshotRecordNumber {
#[must_use]
pub fn new(record_number: u8) -> Self {
match record_number {
0x00 | 0xF0 => Self::Reserved(record_number),
0xFF => Self::All,
_ => Self::Number(record_number),
}
}
#[must_use]
#[allow(clippy::match_same_arms)]
pub fn value(&self) -> u8 {
match self {
DTCSnapshotRecordNumber::Reserved(value) => *value,
DTCSnapshotRecordNumber::Number(value) => *value,
DTCSnapshotRecordNumber::All => 0xFF,
}
}
}
impl PartialEq<u8> for DTCSnapshotRecordNumber {
fn eq(&self, other: &u8) -> bool {
self.value() == *other
}
}
impl WireFormat for DTCSnapshotRecordNumber {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let Ok(record_number) = reader.read_u8() else {
return Ok(None);
};
Ok(Some(Self::new(record_number)))
}
fn required_size(&self) -> usize {
1
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
writer.write_u8(self.value())?;
Ok(1)
}
}
impl SingleValueWireFormat for DTCSnapshotRecordNumber {}
#[cfg(test)]
mod snapshot {
pub enum ProtocolPayload {
Did4711([u8; 5]),
Did8711([u8; 5]),
Did8712(u8, u8, u16),
}
macro_rules! value_map {
($(($e:ident, $v:literal)),* $(,)?) => {
pub fn value(&self) -> u16 {
match self {
$(ProtocolPayload::$e(..) => $v,)*
}
}
}
}
impl ProtocolPayload {
#[rustfmt::skip]
value_map![
(Did4711, 0x4711),
(Did8711, 0x8711),
(Did8712, 0x8712),
];
}
impl WireFormat for ProtocolPayload {
fn decode<T: std::io::Read>(reader: &mut T) -> Result<Option<Self>, Error> {
let mut identifier_data: [u8; 2] = [0; 2];
match reader.read(&mut identifier_data)? {
0 => return Ok(None),
1 => return Err(Error::IncorrectMessageLengthOrInvalidFormat),
2 => (),
_ => unreachable!("Impossible to read more than 2 bytes into 2 byte array"),
}
let identifier = u16::from_be_bytes(identifier_data);
match identifier {
0x4711 => {
let mut did_4711 = [0u8; 5];
match reader.read(&mut did_4711)? {
0 => return Ok(None),
1 => return Err(Error::IncorrectMessageLengthOrInvalidFormat),
5 => (),
_ => unreachable!("Impossible to read more than 5 bytes into 5 byte array"),
}
Ok(Some(Self::Did4711(did_4711)))
}
0x8711 => {
let mut did_8711 = [0u8; 5];
match reader.read(&mut did_8711)? {
0 => return Ok(None),
1 => return Err(Error::IncorrectMessageLengthOrInvalidFormat),
5 => (),
_ => unreachable!("Impossible to read more than 5 bytes into 5 byte array"),
}
Ok(Some(Self::Did8711(did_8711)))
}
_ => Err(Error::IncorrectMessageLengthOrInvalidFormat),
}
}
#[allow(clippy::match_same_arms)]
fn required_size(&self) -> usize {
2 + match self {
ProtocolPayload::Did4711(_) => 5,
ProtocolPayload::Did8711(_) => 5,
ProtocolPayload::Did8712(..) => 4,
}
}
fn encode<T: std::io::Write>(&self, writer: &mut T) -> Result<usize, Error> {
writer.write_u16::<byteorder::BigEndian>(self.value())?;
let mut written = 2;
match self {
ProtocolPayload::Did8711(data) | ProtocolPayload::Did4711(data) => {
writer.write_all(data)?;
written += data.len();
}
ProtocolPayload::Did8712(..) => {
writer.write_u32::<byteorder::BigEndian>(78)?;
written += 4;
}
}
Ok(written)
}
}
impl IterableWireFormat for ProtocolPayload {}
use super::*;
#[test]
fn snapshot_record() {
let record = DTCSnapshotRecordNumber::new(0x01);
let mut writer = Vec::new();
let written_number = record.encode(&mut writer).unwrap();
assert_eq!(record.required_size(), 1);
assert_eq!(written_number, 1);
}
#[test]
fn test_value() {
let did = ProtocolPayload::Did8712(1, 2, 3);
assert_eq!(did.value(), 0x8712);
match did {
ProtocolPayload::Did8712(a, b, c) => {
assert_eq!(a, 1);
assert_eq!(b, 2);
assert_eq!(c, 3);
}
_ => panic!("Expected Did8712"),
}
}
#[test]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::match_wildcard_for_single_variants)]
fn snapshot_list() {
#[rustfmt::skip]
let bytes:[u8; 29] = [
0x12, 0x34, 0x56, 0x24,
0x01,
0x02,
0x47, 0x11,
0xA6, 0x66, 0x07, 0x50, 0x20,
0x87, 0x11,
0x00, 0x00, 0x00, 0x00, 0x09,
0x02,
0x01,
0x47, 0x11,
0xA6, 0x66, 0x07, 0x50, 0x20,
];
let resp = DTCSnapshotRecordList::decode_single_value(&mut bytes.as_slice()).unwrap();
assert_eq!(resp.dtc_record, DTCRecord::from(0x0012_3456));
let mut number: u8 = 1;
resp.snapshot_data
.iter()
.for_each(|(record_number, record)| {
assert_eq!(*record_number, number);
number += 1;
assert_eq!(record.number_of_dids(), record.data.len() as u8);
for payload in &record.data {
match payload {
ProtocolPayload::Did4711(data) => {
assert_eq!(data, &[0xA6, 0x66, 0x07, 0x50, 0x20]);
}
ProtocolPayload::Did8711(data) => {
assert_eq!(data, &[0x00, 0x00, 0x00, 0x00, 0x09]);
}
_ => panic!("Unexpected payload in bagging area"),
}
let mut writer = Vec::new();
let written = payload.encode(&mut writer).unwrap();
assert_eq!(written, payload.required_size());
}
});
let mut writer = Vec::new();
let written = resp.encode(&mut writer).unwrap();
assert_eq!(written, resp.required_size());
assert_eq!(
written,
bytes.len(),
"Written bytes: \n{writer:?}\n{bytes:?}"
);
assert_eq!(writer, bytes);
}
}