Skip to main content

meterbus_wired_datalink/frame/
short.rs

1//! Fixed-length short frames.
2//!
3//! Masters use short frames for SND-NKE, REQ-UD1, and REQ-UD2 messages. A
4//! short frame carries a [`Control`] field and an [`Address`], but no
5//! control-information byte or user data.
6//!
7//! ```text
8//! +------+---+---+----------+------+
9//! | 0x10 | C | A | checksum | 0x16 |
10//! +------+---+---+----------+------+
11//! ```
12//!
13//! The checksum is the wrapping eight-bit sum of `C` and `A`. The encoded frame
14//! is always [`ShortFrame::LEN`] bytes long.
15//!
16//! [`ShortFrame::new`] rejects control values that do not belong in a short
17//! frame. [`ShortFrame::decode`] additionally checks the exact length, start
18//! and stop bytes, and checksum. Reserved and special addresses remain valid
19//! values; the caller decides whether they are suitable for the request.
20//!
21//! # Example
22//!
23//! ```
24//! use meterbus_wired_datalink::{Address, Control, ShortFrame};
25//!
26//! # fn main() -> Result<(), meterbus_wired_datalink::ShortFrameError> {
27//! let frame = ShortFrame::new(Control::req_ud2(false), Address::new(1))?;
28//! let mut output = [0_u8; ShortFrame::LEN];
29//! assert_eq!(
30//!     frame.encode_into(&mut output)?,
31//!     [0x10, 0x5b, 0x01, 0x5c, 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 fixed-length short frame.
45#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
46pub struct ShortFrame {
47    control: Control,
48    address: Address,
49}
50
51impl ShortFrame {
52    /// Initial frame byte.
53    pub const START: u8 = 0x10;
54    /// Final frame byte.
55    pub const STOP: u8 = 0x16;
56    /// Encoded frame length.
57    pub const LEN: usize = 5;
58
59    /// Creates a validated short frame.
60    pub const fn new(control: Control, address: Address) -> Result<Self, ShortFrameError> {
61        if let Err(error) = control.validate_short_frame() {
62            return Err(ShortFrameError::Control(error));
63        }
64        Ok(Self { control, address })
65    }
66
67    /// Returns the control field.
68    #[must_use]
69    pub const fn control(&self) -> Control {
70        self.control
71    }
72
73    /// Returns the address field.
74    #[must_use]
75    pub const fn address(&self) -> Address {
76        self.address
77    }
78
79    /// Decodes exactly one short frame.
80    pub fn decode(bytes: &[u8]) -> Result<Self, ShortFrameError> {
81        if bytes.len() != Self::LEN {
82            return Err(ShortFrameError::InvalidLength {
83                actual: bytes.len(),
84            });
85        }
86        if bytes[0] != Self::START {
87            return Err(ShortFrameError::InvalidStart { actual: bytes[0] });
88        }
89        if bytes[4] != Self::STOP {
90            return Err(ShortFrameError::InvalidStop { actual: bytes[4] });
91        }
92        let expected = Self::checksum(bytes[1], bytes[2]);
93        if bytes[3] != expected {
94            return Err(ShortFrameError::InvalidChecksum {
95                expected,
96                actual: bytes[3],
97            });
98        }
99        Self::new(Control::new(bytes[1]), Address::new(bytes[2]))
100    }
101
102    /// Encodes the frame into `output` and returns the encoded portion.
103    pub fn encode_into<'a>(&self, output: &'a mut [u8]) -> Result<&'a [u8], ShortFrameError> {
104        if output.len() < Self::LEN {
105            return Err(ShortFrameError::OutputTooSmall {
106                actual: output.len(),
107            });
108        }
109        let control = self.control.value();
110        let address = self.address.value();
111        output[..Self::LEN].copy_from_slice(&[
112            Self::START,
113            control,
114            address,
115            Self::checksum(control, address),
116            Self::STOP,
117        ]);
118        Ok(&output[..Self::LEN])
119    }
120
121    /// Encodes the frame into a newly allocated vector.
122    #[cfg(feature = "alloc")]
123    pub fn encode(&self) -> Vec<u8> {
124        let control = self.control.value();
125        let address = self.address.value();
126        vec![
127            Self::START,
128            control,
129            address,
130            Self::checksum(control, address),
131            Self::STOP,
132        ]
133    }
134
135    const fn checksum(control: u8, address: u8) -> u8 {
136        control.wrapping_add(address)
137    }
138}
139
140/// Error produced while constructing, encoding, or decoding a short frame.
141#[derive(Clone, Copy, Debug, Eq, PartialEq)]
142#[allow(missing_docs)]
143pub enum ShortFrameError {
144    /// The input length was not five bytes.
145    InvalidLength { actual: usize },
146    /// The initial start byte was invalid.
147    InvalidStart { actual: u8 },
148    /// The final stop byte was invalid.
149    InvalidStop { actual: u8 },
150    /// The checksum did not match the frame contents.
151    InvalidChecksum { expected: u8, actual: u8 },
152    /// The control field is invalid for a short frame.
153    Control(ControlError),
154    /// The output buffer is shorter than five bytes.
155    OutputTooSmall { actual: usize },
156}
157
158impl fmt::Display for ShortFrameError {
159    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
160        match self {
161            Self::InvalidLength { actual } => write!(
162                formatter,
163                "invalid short frame length: expected 5, got {actual}"
164            ),
165            Self::InvalidStart { actual } => write!(
166                formatter,
167                "invalid short frame start: expected 0x10, got 0x{actual:02x}"
168            ),
169            Self::InvalidStop { actual } => write!(
170                formatter,
171                "invalid short frame stop: expected 0x16, got 0x{actual:02x}"
172            ),
173            Self::InvalidChecksum { expected, actual } => write!(
174                formatter,
175                "invalid short frame checksum: expected 0x{expected:02x}, got 0x{actual:02x}"
176            ),
177            Self::Control(error) => error.fmt(formatter),
178            Self::OutputTooSmall { actual } => write!(
179                formatter,
180                "short frame output buffer too small: expected 5, got {actual}"
181            ),
182        }
183    }
184}
185
186impl core::error::Error for ShortFrameError {
187    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
188        match self {
189            Self::Control(error) => Some(error),
190            _ => None,
191        }
192    }
193}
194
195#[cfg(test)]
196#[cfg_attr(coverage_nightly, coverage(off))]
197mod tests {
198    use super::*;
199    #[cfg(feature = "alloc")]
200    use alloc::string::ToString;
201
202    const FRAME: [u8; 5] = [0x10, 0x40, 0x01, 0x41, 0x16];
203
204    #[test]
205    fn encodes_and_decodes_specification_frame() {
206        let frame = ShortFrame::new(Control::new(0x40), Address::new(1)).unwrap();
207        let mut output = [0; 6];
208        assert_eq!(frame.encode_into(&mut output).unwrap(), FRAME);
209        assert_eq!(ShortFrame::decode(&FRAME), Ok(frame));
210        assert_eq!(frame.control(), Control::new(0x40));
211        assert_eq!(frame.address(), Address::new(1));
212    }
213
214    #[cfg(feature = "alloc")]
215    #[test]
216    fn allocates_encoded_frame() {
217        let frame = ShortFrame::decode(&FRAME).unwrap();
218        assert_eq!(frame.encode(), FRAME);
219    }
220
221    #[test]
222    fn validates_constructor_fields() {
223        assert_eq!(
224            ShortFrame::new(Control::new(0x53), Address::new(1)),
225            Err(ShortFrameError::Control(
226                ControlError::InvalidForShortFrame { value: 0x53 }
227            ))
228        );
229        assert!(ShortFrame::new(Control::new(0x40), Address::new(252)).is_ok());
230        let error = ShortFrameError::Control(ControlError::InvalidForShortFrame { value: 0x53 });
231        assert!(core::error::Error::source(&error).is_some());
232        assert!(
233            core::error::Error::source(&ShortFrameError::InvalidLength { actual: 0 }).is_none()
234        );
235    }
236
237    #[test]
238    fn rejects_each_structural_error() {
239        assert_eq!(
240            ShortFrame::decode(&FRAME[..4]),
241            Err(ShortFrameError::InvalidLength { actual: 4 })
242        );
243        assert_eq!(
244            ShortFrame::decode(&[FRAME.as_slice(), &[0]].concat()),
245            Err(ShortFrameError::InvalidLength { actual: 6 })
246        );
247        let mut bytes = FRAME;
248        bytes[0] = 0;
249        assert_eq!(
250            ShortFrame::decode(&bytes),
251            Err(ShortFrameError::InvalidStart { actual: 0 })
252        );
253        bytes = FRAME;
254        bytes[4] = 0;
255        assert_eq!(
256            ShortFrame::decode(&bytes),
257            Err(ShortFrameError::InvalidStop { actual: 0 })
258        );
259        bytes = FRAME;
260        bytes[3] = 0;
261        assert_eq!(
262            ShortFrame::decode(&bytes),
263            Err(ShortFrameError::InvalidChecksum {
264                expected: 0x41,
265                actual: 0
266            })
267        );
268    }
269
270    #[test]
271    fn rejects_semantically_invalid_decoded_fields() {
272        assert_eq!(
273            ShortFrame::decode(&[0x10, 0x53, 1, 0x54, 0x16]),
274            Err(ShortFrameError::Control(
275                ControlError::InvalidForShortFrame { value: 0x53 }
276            ))
277        );
278        assert_eq!(
279            ShortFrame::decode(&[0x10, 0x40, 252, 0x3c, 0x16])
280                .unwrap()
281                .address(),
282            Address::new(252)
283        );
284    }
285
286    #[test]
287    fn wraps_checksum_and_rejects_small_output() {
288        let frame = ShortFrame::new(Control::new(0x7b), Address::new(255)).unwrap();
289        let mut output = [0; 5];
290        assert_eq!(
291            frame.encode_into(&mut output).unwrap(),
292            [0x10, 0x7b, 0xff, 0x7a, 0x16]
293        );
294        assert_eq!(
295            frame.encode_into(&mut [0; 4]),
296            Err(ShortFrameError::OutputTooSmall { actual: 4 })
297        );
298    }
299
300    #[cfg(feature = "alloc")]
301    #[test]
302    fn formats_errors() {
303        assert_eq!(
304            ShortFrameError::InvalidLength { actual: 0 }.to_string(),
305            "invalid short frame length: expected 5, got 0"
306        );
307        assert_eq!(
308            ShortFrameError::InvalidStart { actual: 0 }.to_string(),
309            "invalid short frame start: expected 0x10, got 0x00"
310        );
311        assert_eq!(
312            ShortFrameError::InvalidStop { actual: 0 }.to_string(),
313            "invalid short frame stop: expected 0x16, got 0x00"
314        );
315        assert_eq!(
316            ShortFrameError::InvalidChecksum {
317                expected: 1,
318                actual: 2
319            }
320            .to_string(),
321            "invalid short frame checksum: expected 0x01, got 0x02"
322        );
323        assert_eq!(
324            ShortFrameError::Control(ControlError::InvalidForShortFrame { value: 0x53 })
325                .to_string(),
326            "control value 0x53 is invalid for a short frame"
327        );
328        assert_eq!(
329            ShortFrameError::OutputTooSmall { actual: 4 }.to_string(),
330            "short frame output buffer too small: expected 5, got 4"
331        );
332    }
333}