Skip to main content

m_bus_application_layer/
data_record.rs

1use super::{
2    data_information::{
3        Data, DataFieldCoding, DataInformation, DataInformationBlock, DataType, SpecialFunctions,
4    },
5    value_information::{ValueInformation, ValueInformationBlock},
6    variable_user_data::DataRecordError,
7    LongTplHeader,
8};
9#[cfg_attr(feature = "serde", derive(serde::Serialize))]
10#[derive(Debug, PartialEq, Clone)]
11#[cfg_attr(feature = "defmt", derive(defmt::Format))]
12pub struct RawDataRecordHeader<'a> {
13    pub data_information_block: DataInformationBlock<'a>,
14    pub value_information_block: Option<ValueInformationBlock<'a>>,
15}
16#[cfg_attr(feature = "serde", derive(serde::Serialize))]
17#[derive(Debug, PartialEq, Clone)]
18#[cfg_attr(feature = "defmt", derive(defmt::Format))]
19pub struct ProcessedDataRecordHeader<'a> {
20    pub data_information: Option<DataInformation>,
21    pub value_information: Option<ValueInformation<'a>>,
22}
23#[cfg_attr(feature = "serde", derive(serde::Serialize))]
24#[derive(Debug, PartialEq, Clone)]
25#[cfg_attr(feature = "defmt", derive(defmt::Format))]
26pub struct DataRecord<'a> {
27    pub data_record_header: DataRecordHeader<'a>,
28    pub data: Data<'a>,
29    /// Raw bytes encompassing this data record
30    #[cfg_attr(
31        feature = "serde",
32        serde(serialize_with = "m_bus_core::serde_hex::serialize")
33    )]
34    pub raw_bytes: &'a [u8],
35}
36
37impl<'a> DataRecord<'a> {
38    /// Returns the parsed value carried by this record.
39    #[must_use]
40    pub fn value(&self) -> Option<&DataType<'_>> {
41        self.data.value.as_ref()
42    }
43
44    /// Returns the processed data information (DIF and DIFE fields).
45    #[must_use]
46    pub fn data_information(&self) -> Option<&DataInformation> {
47        self.data_record_header
48            .processed_data_record_header
49            .data_information
50            .as_ref()
51    }
52
53    /// Returns the processed value information (VIF and VIFE fields).
54    #[must_use]
55    pub fn value_information(&self) -> Option<&ValueInformation<'a>> {
56        self.data_record_header
57            .processed_data_record_header
58            .value_information
59            .as_ref()
60    }
61
62    /// Returns all raw bytes consumed by this record.
63    #[must_use]
64    pub fn raw_bytes(&self) -> &[u8] {
65        self.raw_bytes
66    }
67
68    #[must_use]
69    pub fn get_size(&self) -> usize {
70        self.raw_bytes.len()
71    }
72
73    #[cfg(feature = "std")]
74    #[must_use]
75    pub fn data_record_header_hex(&self) -> String {
76        let start = 0;
77        let end = self.data_record_header.get_size();
78        self.raw_bytes
79            .get(start..end)
80            .unwrap_or(&[])
81            .iter()
82            .map(|b| format!("{:02X}", b))
83            .collect::<Vec<_>>()
84            .join(" ")
85    }
86
87    #[cfg(feature = "std")]
88    #[must_use]
89    pub fn data_hex(&self) -> String {
90        let start = self.data_record_header.get_size();
91        let end = self.get_size();
92        self.raw_bytes
93            .get(start..end)
94            .unwrap_or(&[])
95            .iter()
96            .map(|b| format!("{:02X}", b))
97            .collect::<Vec<_>>()
98            .join(" ")
99    }
100}
101
102impl<'a> DataRecord<'a> {
103    /// Parses the record at the start of `data` and sets `consumed` to how far
104    /// a record stream must advance: the record's length or, after an error,
105    /// its declared length if the header is readable, otherwise all of `data`.
106    /// Reporting this here lets callers return the record without reading it
107    /// again, so it can be built directly in their result.
108    pub(crate) fn parse(
109        data: &'a [u8],
110        fixed_data_header: Option<&'a LongTplHeader>,
111        consumed: &mut usize,
112    ) -> Result<Self, DataRecordError> {
113        *consumed = data.len();
114        // This runs for every record, so keep large values out of extra stack
115        // slots: both headers are borrowed from their results instead of moved
116        // out with `?`, and the record is assembled only once everything parsed.
117        let raw = RawDataRecordHeader::try_from(data);
118        let raw_data_record_header = match &raw {
119            Ok(header) => header,
120            Err(error) => return Err(*error),
121        };
122        let processed = ProcessedDataRecordHeader::try_from(raw_data_record_header);
123        let processed_data_record_header = match &processed {
124            Ok(header) => header,
125            Err(error) => return Err(*error),
126        };
127        let header_size = raw_data_record_header.get_size();
128        let rest = data
129            .get(header_size..)
130            .ok_or(DataRecordError::InsufficientData)?;
131        // Without a DIB coding the rest of the record is manufacturer specific;
132        // decoding it through the same call avoids merging two `Data` values.
133        let data_field_coding = processed_data_record_header
134            .data_information
135            .as_ref()
136            .map_or(
137                DataFieldCoding::SpecialFunctions(SpecialFunctions::ManufacturerSpecific),
138                |data_info| data_info.data_field_coding,
139            );
140        let data_out = match data_field_coding.parse(rest, fixed_data_header) {
141            Ok(data_out) => data_out,
142            Err(error) => {
143                // A record whose contents fail to decode must not cost us the
144                // records behind it: step over it by its declared length.
145                if let Some(size) = data_field_coding
146                    .data_size(rest)
147                    .and_then(|size| size.checked_add(header_size))
148                    .filter(|&size| size != 0 && size <= data.len())
149                {
150                    *consumed = size;
151                }
152                return Err(error);
153            }
154        };
155        // A data field longer than the input is truncated to the input.
156        let raw_bytes = data
157            .get(..header_size + data_out.get_size())
158            .unwrap_or(data);
159        *consumed = raw_bytes.len();
160
161        Ok(Self::assemble(
162            raw_data_record_header,
163            processed_data_record_header,
164            data_out,
165            raw_bytes,
166        ))
167    }
168
169    // Out of line: cloning the optional header fields needs temporaries that
170    // should not widen the frame of `parse`, which is live during the deepest
171    // calls of a decode.
172    #[inline(never)]
173    fn assemble(
174        raw_data_record_header: &RawDataRecordHeader<'a>,
175        processed_data_record_header: &ProcessedDataRecordHeader<'a>,
176        data: Data<'a>,
177        raw_bytes: &'a [u8],
178    ) -> Self {
179        DataRecord {
180            data_record_header: DataRecordHeader {
181                raw_data_record_header: raw_data_record_header.clone(),
182                processed_data_record_header: processed_data_record_header.clone(),
183            },
184            data,
185            raw_bytes,
186        }
187    }
188}
189
190#[cfg_attr(feature = "serde", derive(serde::Serialize))]
191#[derive(Debug, PartialEq, Clone)]
192#[cfg_attr(feature = "defmt", derive(defmt::Format))]
193pub struct DataRecordHeader<'a> {
194    pub raw_data_record_header: RawDataRecordHeader<'a>,
195    pub processed_data_record_header: ProcessedDataRecordHeader<'a>,
196}
197
198impl DataRecordHeader<'_> {
199    #[must_use]
200    pub fn get_size(&self) -> usize {
201        self.raw_data_record_header.get_size()
202    }
203}
204
205impl RawDataRecordHeader<'_> {
206    pub(crate) fn get_size(&self) -> usize {
207        let s = self.data_information_block.get_size();
208        if let Some(x) = &self.value_information_block {
209            s + x.get_size()
210        } else {
211            s
212        }
213    }
214}
215
216impl<'a> TryFrom<&'a [u8]> for RawDataRecordHeader<'a> {
217    type Error = DataRecordError;
218    // Out of line: the temporaries of the block parsers are dead before the
219    // deepest calls of a decode, so they should not widen the caller's frame.
220    #[inline(never)]
221    fn try_from(data: &[u8]) -> Result<RawDataRecordHeader<'_>, DataRecordError> {
222        let difb = DataInformationBlock::try_from(data)?;
223        let offset = difb.get_size();
224
225        let mut vifb = None;
226
227        if !difb.data_information_field.is_special_function() {
228            vifb = Some(ValueInformationBlock::try_from(
229                data.get(offset..)
230                    .ok_or(DataRecordError::InsufficientData)?,
231            )?);
232        }
233
234        Ok(RawDataRecordHeader {
235            data_information_block: difb,
236            value_information_block: vifb,
237        })
238    }
239}
240
241impl<'a> TryFrom<&RawDataRecordHeader<'a>> for ProcessedDataRecordHeader<'a> {
242    type Error = DataRecordError;
243    fn try_from(raw_data_record_header: &RawDataRecordHeader<'a>) -> Result<Self, DataRecordError> {
244        let value_information = match &raw_data_record_header.value_information_block {
245            Some(x) => Some(ValueInformation::try_from(x)?),
246            None if raw_data_record_header
247                .data_information_block
248                .data_information_field
249                .is_special_function() =>
250            {
251                None
252            }
253            None => {
254                return Ok(Self {
255                    data_information: None,
256                    value_information: None,
257                })
258            }
259        };
260
261        // Decode at one call site and adjust the coding in place so the
262        // processed header does not need a second DataInformation value.
263        let mut data_information =
264            DataInformation::try_from(&raw_data_record_header.data_information_block);
265        let d = match &mut data_information {
266            Ok(d) => d,
267            Err(error) => return Err((*error).into()),
268        };
269        // The DIF alone does not always identify the data field coding.
270        d.data_field_coding = raw_data_record_header
271            .value_information_block
272            .as_ref()
273            .map_or(d.data_field_coding, |vib| {
274                date_time_coding(vib.value_information.data, d.data_field_coding)
275            });
276
277        Ok(Self {
278            data_information: data_information.ok(),
279            value_information,
280        })
281    }
282}
283
284/// The current VIF tables produce Date and DateTime only from primary VIFs
285/// 6Ch and 6Dh. Their orthogonal VIFEs cannot change the data-field coding, so
286/// there is no need to decode the lazy label iterator for every record.
287fn date_time_coding(vif: u8, coding: DataFieldCoding) -> DataFieldCoding {
288    match vif & 0x7F {
289        0x6C => DataFieldCoding::DateTypeG,
290        // VIF 6Dh with six data bytes is type I; four bytes is type F.
291        0x6D if coding == DataFieldCoding::Integer48Bit => DataFieldCoding::DateTimeTypeI,
292        0x6D => DataFieldCoding::DateTimeTypeF,
293        _ => coding,
294    }
295}
296
297impl<'a> TryFrom<&'a [u8]> for DataRecordHeader<'a> {
298    type Error = DataRecordError;
299    fn try_from(data: &'a [u8]) -> Result<Self, DataRecordError> {
300        let raw_data_record_header = RawDataRecordHeader::try_from(data)?;
301        let processed_data_record_header =
302            ProcessedDataRecordHeader::try_from(&raw_data_record_header)?;
303        Ok(Self {
304            raw_data_record_header,
305            processed_data_record_header,
306        })
307    }
308}
309
310impl<'a> TryFrom<(&'a [u8], &'a LongTplHeader)> for DataRecord<'a> {
311    type Error = DataRecordError;
312    fn try_from(
313        (data, fixed_data_header): (&'a [u8], &'a LongTplHeader),
314    ) -> Result<Self, Self::Error> {
315        Self::parse(data, Some(fixed_data_header), &mut 0)
316    }
317}
318
319impl<'a> TryFrom<&'a [u8]> for DataRecord<'a> {
320    type Error = DataRecordError;
321    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
322        Self::parse(data, None, &mut 0)
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329    use crate::value_information::ValueLabel;
330
331    #[test]
332    fn date_time_overrides_match_all_single_extension_vifs() {
333        // Compare the single-pass classification with the original label
334        // precedence for all VIF/VIFE bytes and the relevant DIF widths.
335        for dif in [0x02, 0x04, 0x06, 0x09] {
336            for vif in 0..=u8::MAX {
337                // 0x7D is reserved and the existing VIF decoder rejects it
338                // with an unreachable!(), rather than a parse error.
339                if vif == 0x7d {
340                    continue;
341                }
342                for vife in 0..=0x7f {
343                    let bytes = [dif, vif, vife];
344                    let Ok(raw) = RawDataRecordHeader::try_from(bytes.as_slice()) else {
345                        continue;
346                    };
347                    let Ok(actual) = ProcessedDataRecordHeader::try_from(&raw) else {
348                        continue;
349                    };
350                    let Some(value) = actual.value_information.as_ref() else {
351                        continue;
352                    };
353                    let original = DataInformation::try_from(&raw.data_information_block)
354                        .unwrap()
355                        .data_field_coding;
356                    let expected = if value.has_label(ValueLabel::Date) {
357                        DataFieldCoding::DateTypeG
358                    } else if value.has_label(ValueLabel::DateTime) {
359                        if original == DataFieldCoding::Integer48Bit {
360                            DataFieldCoding::DateTimeTypeI
361                        } else {
362                            DataFieldCoding::DateTimeTypeF
363                        }
364                    } else if value.has_label(ValueLabel::Time) {
365                        DataFieldCoding::DateTimeTypeJ
366                    } else if value.has_label(ValueLabel::DateTimeWithSeconds) {
367                        DataFieldCoding::DateTimeTypeI
368                    } else {
369                        original
370                    };
371                    assert_eq!(
372                        actual.data_information.unwrap().data_field_coding,
373                        expected,
374                        "DIF={dif:02x} VIF={vif:02x} VIFE={vife:02x}",
375                    );
376                }
377            }
378        }
379    }
380
381    #[test]
382    fn test_parse_raw_data_record() {
383        let data = &[0x03, 0x13, 0x15, 0x31, 0x00];
384        let _result = DataRecordHeader::try_from(data.as_slice());
385    }
386    #[test]
387    #[cfg(feature = "std")]
388    fn test_manufacturer_specific_block() {
389        let data = [0x0F, 0x01, 0x02, 0x03, 0x04];
390        let result = DataRecord::try_from(data.as_slice());
391        println!("{:?}", result);
392    }
393}