Skip to main content

meterbus_wired_datalink/frame/
control.rs

1//! Variable-format control frames without user data.
2//!
3//! A control frame carries [`Control`], [`Address`], and a raw
4//! control-information byte. It uses the variable M-Bus frame layout but fixes
5//! both length bytes to `3` because no user data follows.
6//!
7//! ```text
8//! +------+------+------+------+---+---+----+----------+------+
9//! | 0x68 | 0x03 | 0x03 | 0x68 | C | A | CI | checksum | 0x16 |
10//! +------+------+------+------+---+---+----+----------+------+
11//! ```
12//!
13//! The checksum is the wrapping eight-bit sum of `C`, `A`, and `CI`. The
14//! encoded frame is always [`ControlFrame::LEN`] bytes long.
15//!
16//! [`ControlFrame::new`] accepts control values used by variable frames.
17//! [`ControlFrame::decode`] also checks both length bytes, both start bytes,
18//! the stop byte, and the checksum. The crate stores `CI` as a `u8`; its
19//! meaning belongs to the application protocol.
20//!
21//! # Example
22//!
23//! ```
24//! use meterbus_wired_datalink::{Address, Control, ControlFrame};
25//!
26//! # fn main() -> Result<(), meterbus_wired_datalink::ControlFrameError> {
27//! let frame = ControlFrame::new(Control::snd_ud(false), Address::new(254), 0xbd)?;
28//! let mut output = [0_u8; ControlFrame::LEN];
29//! assert_eq!(
30//!     frame.encode_into(&mut output)?,
31//!     [0x68, 0x03, 0x03, 0x68, 0x53, 0xfe, 0xbd, 0x0e, 0x16],
32//! );
33//! # Ok(())
34//! # }
35//! ```
36
37use core::fmt;
38
39#[cfg(feature = "alloc")]
40use alloc::{vec, vec::Vec};
41
42use super::field::{Address, Control, ControlError};
43
44/// A variable-format frame without user data.
45#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
46pub struct ControlFrame {
47    control: Control,
48    address: Address,
49    control_information: u8,
50}
51
52impl ControlFrame {
53    /// Initial and repeated start byte.
54    pub const START: u8 = 0x68;
55    /// Number of checksum-covered bytes.
56    pub const DATA_LEN: u8 = 3;
57    /// Final frame byte.
58    pub const STOP: u8 = 0x16;
59    /// Encoded frame length.
60    pub const LEN: usize = 9;
61
62    /// Creates a validated control frame.
63    pub const fn new(
64        control: Control,
65        address: Address,
66        control_information: u8,
67    ) -> Result<Self, ControlFrameError> {
68        if let Err(error) = control.validate_variable_frame() {
69            return Err(ControlFrameError::Control(error));
70        }
71        Ok(Self {
72            control,
73            address,
74            control_information,
75        })
76    }
77
78    /// Returns the control field.
79    #[must_use]
80    pub const fn control(&self) -> Control {
81        self.control
82    }
83
84    /// Returns the address field.
85    #[must_use]
86    pub const fn address(&self) -> Address {
87        self.address
88    }
89
90    /// Returns the control-information byte.
91    #[must_use]
92    pub const fn control_information(&self) -> u8 {
93        self.control_information
94    }
95
96    /// Decodes exactly one control frame.
97    pub fn decode(bytes: &[u8]) -> Result<Self, ControlFrameError> {
98        if bytes.len() != Self::LEN {
99            return Err(ControlFrameError::InvalidLength {
100                actual: bytes.len(),
101            });
102        }
103        if bytes[0] != Self::START {
104            return Err(ControlFrameError::InvalidStart {
105                index: 0,
106                actual: bytes[0],
107            });
108        }
109        if bytes[1] != Self::DATA_LEN {
110            return Err(ControlFrameError::InvalidDataLength {
111                index: 1,
112                actual: bytes[1],
113            });
114        }
115        if bytes[2] != Self::DATA_LEN {
116            return Err(ControlFrameError::InvalidDataLength {
117                index: 2,
118                actual: bytes[2],
119            });
120        }
121        if bytes[3] != Self::START {
122            return Err(ControlFrameError::InvalidStart {
123                index: 3,
124                actual: bytes[3],
125            });
126        }
127        if bytes[8] != Self::STOP {
128            return Err(ControlFrameError::InvalidStop { actual: bytes[8] });
129        }
130        let expected = Self::checksum(bytes[4], bytes[5], bytes[6]);
131        if bytes[7] != expected {
132            return Err(ControlFrameError::InvalidChecksum {
133                expected,
134                actual: bytes[7],
135            });
136        }
137        Self::new(Control::new(bytes[4]), Address::new(bytes[5]), bytes[6])
138    }
139
140    /// Encodes the frame into `output` and returns the encoded portion.
141    pub fn encode_into<'a>(&self, output: &'a mut [u8]) -> Result<&'a [u8], ControlFrameError> {
142        if output.len() < Self::LEN {
143            return Err(ControlFrameError::OutputTooSmall {
144                actual: output.len(),
145            });
146        }
147        let control = self.control.value();
148        let address = self.address.value();
149        output[..Self::LEN].copy_from_slice(&[
150            Self::START,
151            Self::DATA_LEN,
152            Self::DATA_LEN,
153            Self::START,
154            control,
155            address,
156            self.control_information,
157            Self::checksum(control, address, self.control_information),
158            Self::STOP,
159        ]);
160        Ok(&output[..Self::LEN])
161    }
162
163    /// Encodes the frame into a newly allocated vector.
164    #[cfg(feature = "alloc")]
165    pub fn encode(&self) -> Vec<u8> {
166        let control = self.control.value();
167        let address = self.address.value();
168        vec![
169            Self::START,
170            Self::DATA_LEN,
171            Self::DATA_LEN,
172            Self::START,
173            control,
174            address,
175            self.control_information,
176            Self::checksum(control, address, self.control_information),
177            Self::STOP,
178        ]
179    }
180
181    const fn checksum(control: u8, address: u8, control_information: u8) -> u8 {
182        control
183            .wrapping_add(address)
184            .wrapping_add(control_information)
185    }
186}
187
188/// Error produced while constructing, encoding, or decoding a control frame.
189#[derive(Clone, Copy, Debug, Eq, PartialEq)]
190#[allow(missing_docs)]
191pub enum ControlFrameError {
192    /// The input length was not nine bytes.
193    InvalidLength { actual: usize },
194    /// A start byte was invalid.
195    InvalidStart { index: usize, actual: u8 },
196    /// A length byte did not contain three.
197    InvalidDataLength { index: usize, actual: u8 },
198    /// The stop byte was invalid.
199    InvalidStop { actual: u8 },
200    /// The checksum did not match the frame contents.
201    InvalidChecksum { expected: u8, actual: u8 },
202    /// The control field is invalid for a variable-format frame.
203    Control(ControlError),
204    /// The output buffer is shorter than nine bytes.
205    OutputTooSmall { actual: usize },
206}
207
208impl fmt::Display for ControlFrameError {
209    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
210        match self {
211            Self::InvalidLength { actual } => write!(
212                formatter,
213                "invalid control frame length: expected 9, got {actual}"
214            ),
215            Self::InvalidStart { index, actual } => write!(
216                formatter,
217                "invalid control frame start at {index}: expected 0x68, got 0x{actual:02x}"
218            ),
219            Self::InvalidDataLength { index, actual } => write!(
220                formatter,
221                "invalid control frame data length at {index}: expected 3, got {actual}"
222            ),
223            Self::InvalidStop { actual } => write!(
224                formatter,
225                "invalid control frame stop: expected 0x16, got 0x{actual:02x}"
226            ),
227            Self::InvalidChecksum { expected, actual } => write!(
228                formatter,
229                "invalid control frame checksum: expected 0x{expected:02x}, got 0x{actual:02x}"
230            ),
231            Self::Control(error) => error.fmt(formatter),
232            Self::OutputTooSmall { actual } => write!(
233                formatter,
234                "control frame output buffer too small: expected 9, got {actual}"
235            ),
236        }
237    }
238}
239
240impl core::error::Error for ControlFrameError {
241    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
242        match self {
243            Self::Control(error) => Some(error),
244            _ => None,
245        }
246    }
247}
248
249#[cfg(test)]
250#[cfg_attr(coverage_nightly, coverage(off))]
251mod tests {
252    use super::*;
253    #[cfg(feature = "alloc")]
254    use alloc::string::ToString;
255
256    const FRAME: [u8; 9] = [0x68, 0x03, 0x03, 0x68, 0x53, 0xfe, 0xbd, 0x0e, 0x16];
257
258    #[test]
259    fn encodes_and_decodes_known_frame() {
260        let frame = ControlFrame::new(Control::new(0x53), Address::new(254), 0xbd).unwrap();
261        let mut output = [0; 10];
262        assert_eq!(frame.encode_into(&mut output).unwrap(), FRAME);
263        assert_eq!(ControlFrame::decode(&FRAME), Ok(frame));
264        assert_eq!(frame.control(), Control::new(0x53));
265        assert_eq!(frame.address(), Address::new(254));
266        assert_eq!(frame.control_information(), 0xbd);
267    }
268
269    #[cfg(feature = "alloc")]
270    #[test]
271    fn allocates_encoded_frame() {
272        assert_eq!(ControlFrame::decode(&FRAME).unwrap().encode(), FRAME);
273    }
274
275    #[test]
276    fn validates_control_but_tolerates_reserved_address() {
277        assert_eq!(
278            ControlFrame::new(Control::new(0x40), Address::new(1), 0),
279            Err(ControlFrameError::Control(
280                ControlError::InvalidForVariableFrame { value: 0x40 }
281            ))
282        );
283        assert!(ControlFrame::new(Control::new(0x53), Address::new(252), 0).is_ok());
284        let error =
285            ControlFrameError::Control(ControlError::InvalidForVariableFrame { value: 0x40 });
286        assert!(core::error::Error::source(&error).is_some());
287        assert!(
288            core::error::Error::source(&ControlFrameError::InvalidLength { actual: 0 }).is_none()
289        );
290    }
291
292    #[test]
293    fn rejects_structural_errors() {
294        assert_eq!(
295            ControlFrame::decode(&FRAME[..8]),
296            Err(ControlFrameError::InvalidLength { actual: 8 })
297        );
298        let mut bytes = FRAME;
299        bytes[0] = 0;
300        assert_eq!(
301            ControlFrame::decode(&bytes),
302            Err(ControlFrameError::InvalidStart {
303                index: 0,
304                actual: 0
305            })
306        );
307        bytes = FRAME;
308        bytes[1] = 4;
309        assert_eq!(
310            ControlFrame::decode(&bytes),
311            Err(ControlFrameError::InvalidDataLength {
312                index: 1,
313                actual: 4
314            })
315        );
316        bytes = FRAME;
317        bytes[2] = 4;
318        assert_eq!(
319            ControlFrame::decode(&bytes),
320            Err(ControlFrameError::InvalidDataLength {
321                index: 2,
322                actual: 4
323            })
324        );
325        bytes = FRAME;
326        bytes[3] = 0;
327        assert_eq!(
328            ControlFrame::decode(&bytes),
329            Err(ControlFrameError::InvalidStart {
330                index: 3,
331                actual: 0
332            })
333        );
334        bytes = FRAME;
335        bytes[8] = 0;
336        assert_eq!(
337            ControlFrame::decode(&bytes),
338            Err(ControlFrameError::InvalidStop { actual: 0 })
339        );
340        bytes = FRAME;
341        bytes[7] = 0;
342        assert_eq!(
343            ControlFrame::decode(&bytes),
344            Err(ControlFrameError::InvalidChecksum {
345                expected: 0x0e,
346                actual: 0
347            })
348        );
349    }
350
351    #[test]
352    fn rejects_invalid_decoded_control_and_small_output() {
353        assert_eq!(
354            ControlFrame::decode(&[0x68, 3, 3, 0x68, 0x40, 1, 0, 0x41, 0x16]),
355            Err(ControlFrameError::Control(
356                ControlError::InvalidForVariableFrame { value: 0x40 }
357            ))
358        );
359        let frame = ControlFrame::decode(&FRAME).unwrap();
360        assert_eq!(
361            frame.encode_into(&mut [0; 8]),
362            Err(ControlFrameError::OutputTooSmall { actual: 8 })
363        );
364    }
365
366    #[test]
367    fn wraps_checksum() {
368        let frame = ControlFrame::new(Control::new(0x53), Address::new(255), 255).unwrap();
369        let mut output = [0; 9];
370        assert_eq!(frame.encode_into(&mut output).unwrap()[7], 0x51);
371    }
372
373    #[cfg(feature = "alloc")]
374    #[test]
375    fn formats_errors() {
376        let cases = [
377            (
378                ControlFrameError::InvalidLength { actual: 0 }.to_string(),
379                "invalid control frame length: expected 9, got 0",
380            ),
381            (
382                ControlFrameError::InvalidStart {
383                    index: 3,
384                    actual: 0,
385                }
386                .to_string(),
387                "invalid control frame start at 3: expected 0x68, got 0x00",
388            ),
389            (
390                ControlFrameError::InvalidDataLength {
391                    index: 1,
392                    actual: 4,
393                }
394                .to_string(),
395                "invalid control frame data length at 1: expected 3, got 4",
396            ),
397            (
398                ControlFrameError::InvalidStop { actual: 0 }.to_string(),
399                "invalid control frame stop: expected 0x16, got 0x00",
400            ),
401            (
402                ControlFrameError::InvalidChecksum {
403                    expected: 1,
404                    actual: 2,
405                }
406                .to_string(),
407                "invalid control frame checksum: expected 0x01, got 0x02",
408            ),
409            (
410                ControlFrameError::Control(ControlError::InvalidForVariableFrame { value: 0x40 })
411                    .to_string(),
412                "control value 0x40 is invalid for a variable-format frame",
413            ),
414            (
415                ControlFrameError::OutputTooSmall { actual: 8 }.to_string(),
416                "control frame output buffer too small: expected 9, got 8",
417            ),
418        ];
419        for (actual, expected) in cases {
420            assert_eq!(actual, expected);
421        }
422    }
423}