Skip to main content

meterbus_wired_datalink/frame/
mod.rs

1//! Wired M-Bus data-link frame formats.
2//!
3//! This module groups the frame representations used by the crate. The types
4//! are re-exported from the crate root, so users normally import
5//! [`AckFrame`], [`NackFrame`], [`ShortFrame`], [`ControlFrame`], and
6//! [`LongFrame`] directly from `meterbus_wired_datalink`.
7//!
8//! EN 13757-2 uses the FT1.2 frame format for wired M-Bus. This crate
9//! represents five forms: two single-byte acknowledgements, one fixed-length
10//! short format, and two variants of the variable format.
11//!
12//! # Supported formats
13//!
14//! | Frame | First byte | Encoded length | Contents |
15//! | --- | --- | --- | --- |
16//! | [`AckFrame`] | `0xe5` | 1 byte | Positive acknowledgement |
17//! | [`NackFrame`] | `0xa2` | 1 byte | Negative acknowledgement |
18//! | [`ShortFrame`] | `0x10` | 5 bytes | Control and address fields |
19//! | [`ControlFrame`] | `0x68` | 9 bytes | Control, address, and control-information fields |
20//! | [`LongFrame`] | `0x68` | 10 to 261 bytes | Control, address, control information, and 1 to 252 user-data bytes |
21//!
22//! [`ControlFrame`] is the zero-user-data case of the variable wire format.
23//! It has a distinct Rust type because [`LongFrame`] deliberately requires at
24//! least one user-data byte.
25//!
26//! # Wire layouts
27//!
28//! The diagrams below use the field names customary for M-Bus:
29//!
30//! - `L` is the data-length field;
31//! - `C` is the data-link [`Control`] field;
32//! - `A` is the data-link [`Address`] field;
33//! - `CI` is the control-information byte used by the application protocol;
34//! - `UD` is user data; and
35//! - `CS` is the wrapping eight-bit checksum.
36//!
37//! ## Acknowledgements
38//!
39//! ACK and NACK each consist of one fixed byte and contain no
40//! address, control field, or checksum:
41//!
42//! ```text
43//! ACK:  E5
44//! NACK: A2
45//! ```
46//!
47//! ## Short frame
48//!
49//! ```text
50//! +-------+---+---+----+------+
51//! | 0x10  | C | A | CS | 0x16 |
52//! +-------+---+---+----+------+
53//! ```
54//!
55//! `CS` is the wrapping sum of `C` and `A`. Short frames carry the SND-NKE,
56//! REQ-UD1, and REQ-UD2 communication types supported by [`Control`].
57//!
58//! ## Variable-format control frame
59//!
60//! ```text
61//! +------+------+------+------+---+---+----+----+------+
62//! | 0x68 | 0x03 | 0x03 | 0x68 | C | A | CI | CS | 0x16 |
63//! +------+------+------+------+---+---+----+----+------+
64//! ```
65//!
66//! Both length bytes are `3` because the checksum-covered data region contains
67//! `C`, `A`, and `CI`. `CS` is the wrapping sum of those three bytes.
68//!
69//! ## Variable-format long frame
70//!
71//! ```text
72//! +------+------+------+-------+---+---+----+--------+----+------+
73//! | 0x68 |  L   |  L   | 0x68  | C | A | CI | UD ... | CS | 0x16 |
74//! +------+------+------+-------+---+---+----+--------+----+------+
75//! ```
76//!
77//! `L` counts `C`, `A`, `CI`, and every user-data byte. Consequently, this
78//! crate accepts `L` from 4 through 255 and complete long frames from 10
79//! through 261 bytes. `CS` is the wrapping sum of every byte from `C` through
80//! the final user-data byte.
81//!
82//! # Decoding and frame boundaries
83//!
84//! Each frame decoder expects exactly one complete frame. Fixed-size decoders
85//! reject both short and trailing input. [`LongFrame::decode`] derives the
86//! expected total size from the repeated `L` field and likewise rejects any
87//! extra bytes. None of the decoders scans for a start byte or returns an
88//! unconsumed remainder.
89//!
90//! A stream decoder can select an initial candidate from the first byte:
91//!
92//! | First byte | Candidate |
93//! | --- | --- |
94//! | `0xe5` | [`AckFrame`] |
95//! | `0xa2` | [`NackFrame`] |
96//! | `0x10` | [`ShortFrame`] |
97//! | `0x68` | Variable format; inspect `L` to distinguish a control frame from a long frame |
98//!
99//! For a `0x68` prefix, repeated length bytes of `3` describe a
100//! [`ControlFrame`]. Values from `4` through `255` describe a [`LongFrame`].
101//! The surrounding transport remains responsible for buffering the declared
102//! number of bytes before invoking the selected decoder.
103//!
104//! Decoders check lengths, separators, checksums, and control-field
105//! compatibility. All frame types can encode into a caller-provided buffer.
106//! With the `alloc` feature, they can also return an allocated vector.
107//!
108//! # Example
109//!
110//! Decode based on an already-established frame boundary and known format:
111//!
112//! ```
113//! use meterbus_wired_datalink::{
114//!     AckFrame, Address, CommunicationType, Control, ControlFrame, Frame, LongFrame,
115//!     NackFrame, ShortFrame,
116//! };
117//!
118//! # fn main() -> Result<(), meterbus_wired_datalink::ShortFrameError> {
119//! let bytes = [0x10, 0x5b, 0x01, 0x5c, 0x16];
120//! let frame = ShortFrame::decode(&bytes)?;
121//!
122//! assert_eq!(frame.control().communication_type(), CommunicationType::ReqUd2);
123//! assert_eq!(frame.address().value(), 1);
124//! # let _: Frame = AckFrame::new().into();
125//! # let _: Frame = NackFrame::new().into();
126//! # let _: Frame = frame.into();
127//! # let _: Frame = ControlFrame::new(Control::snd_ud2(), Address::new(1), 0).expect("valid control frame").into();
128//! # let _: Frame = LongFrame::new(Control::snd_ud(false), Address::new(1), 0, &[1]).expect("valid long frame").into();
129//! # Ok(())
130//! # }
131//! ```
132//!
133//! The codecs validate individual frames. Message order, timing, retries, and
134//! application data remain the caller's responsibility.
135
136mod ack;
137mod control;
138pub mod field;
139mod long;
140mod nack;
141mod short;
142
143#[cfg(feature = "alloc")]
144use alloc::vec::Vec;
145use core::fmt;
146
147pub use ack::{AckFrame, AckFrameError};
148pub use control::{ControlFrame, ControlFrameError};
149pub use field::{Address, AddressKind, CommunicationType, Control, ControlError, Direction};
150pub use long::{LongFrame, LongFrameError};
151pub use nack::{NackFrame, NackFrameError};
152pub use short::{ShortFrame, ShortFrameError};
153
154/// Any supported wired M-Bus frame.
155///
156/// The long variant remains inline so this type works without an allocator.
157#[derive(Clone, Debug, Eq, PartialEq)]
158#[allow(clippy::large_enum_variant)]
159pub enum Frame {
160    /// A positive acknowledgement.
161    Ack(AckFrame),
162    /// A negative acknowledgement.
163    Nack(NackFrame),
164    /// A fixed-length short frame.
165    Short(ShortFrame),
166    /// A variable-format frame without user data.
167    Control(ControlFrame),
168    /// A variable-format frame with user data.
169    Long(LongFrame),
170}
171
172/// The wire format represented by a [`Frame`].
173#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
174#[non_exhaustive]
175pub enum FrameKind {
176    /// Positive acknowledgement.
177    Ack,
178    /// Negative acknowledgement.
179    Nack,
180    /// Fixed-length short frame.
181    Short,
182    /// Variable-format frame without user data.
183    Control,
184    /// Variable-format frame with user data.
185    Long,
186}
187
188impl Frame {
189    /// Returns the frame's wire format.
190    #[must_use]
191    pub const fn kind(&self) -> FrameKind {
192        match self {
193            Self::Ack(_) => FrameKind::Ack,
194            Self::Nack(_) => FrameKind::Nack,
195            Self::Short(_) => FrameKind::Short,
196            Self::Control(_) => FrameKind::Control,
197            Self::Long(_) => FrameKind::Long,
198        }
199    }
200
201    /// Returns the encoded frame length.
202    #[must_use]
203    pub fn len(&self) -> usize {
204        match self {
205            Self::Ack(_) => AckFrame::LEN,
206            Self::Nack(_) => NackFrame::LEN,
207            Self::Short(_) => ShortFrame::LEN,
208            Self::Control(_) => ControlFrame::LEN,
209            Self::Long(frame) => frame.user_data().len() + 9,
210        }
211    }
212
213    /// Returns whether the encoded frame is empty.
214    #[must_use]
215    pub const fn is_empty(&self) -> bool {
216        false
217    }
218
219    /// Encodes the frame into `output` and returns the encoded portion.
220    ///
221    /// # Errors
222    ///
223    /// Returns [`FrameEncodeError`] when `output` is shorter than [`Self::len`].
224    pub fn encode_into<'a>(&self, output: &'a mut [u8]) -> Result<&'a [u8], FrameEncodeError> {
225        let required = self.len();
226        if output.len() < required {
227            return Err(FrameEncodeError {
228                required,
229                actual: output.len(),
230            });
231        }
232        let encoded = match self {
233            Self::Ack(frame) => frame
234                .encode_into(output)
235                .expect("prechecked ACK output length"),
236            Self::Nack(frame) => frame
237                .encode_into(output)
238                .expect("prechecked NACK output length"),
239            Self::Short(frame) => frame
240                .encode_into(output)
241                .expect("prechecked short-frame output length"),
242            Self::Control(frame) => frame
243                .encode_into(output)
244                .expect("prechecked control-frame output length"),
245            Self::Long(frame) => frame
246                .encode_into(output)
247                .expect("prechecked long-frame output length"),
248        };
249        Ok(encoded)
250    }
251
252    /// Encodes the frame into a newly allocated vector.
253    #[cfg(feature = "alloc")]
254    #[must_use]
255    pub fn encode(&self) -> Vec<u8> {
256        match self {
257            Self::Ack(frame) => frame.encode(),
258            Self::Nack(frame) => frame.encode(),
259            Self::Short(frame) => frame.encode(),
260            Self::Control(frame) => frame.encode(),
261            Self::Long(frame) => frame.encode(),
262        }
263    }
264}
265
266/// Error returned when a buffer cannot hold an encoded [`Frame`].
267#[derive(Clone, Copy, Debug, Eq, PartialEq)]
268#[non_exhaustive]
269pub struct FrameEncodeError {
270    /// Number of bytes required by the frame.
271    pub required: usize,
272    /// Number of bytes available in the output buffer.
273    pub actual: usize,
274}
275
276impl fmt::Display for FrameEncodeError {
277    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
278        write!(
279            formatter,
280            "frame output buffer too small: expected {}, got {}",
281            self.required, self.actual
282        )
283    }
284}
285
286impl core::error::Error for FrameEncodeError {}
287
288impl From<AckFrame> for Frame {
289    fn from(frame: AckFrame) -> Self {
290        Self::Ack(frame)
291    }
292}
293
294impl From<NackFrame> for Frame {
295    fn from(frame: NackFrame) -> Self {
296        Self::Nack(frame)
297    }
298}
299
300impl From<ShortFrame> for Frame {
301    fn from(frame: ShortFrame) -> Self {
302        Self::Short(frame)
303    }
304}
305
306impl From<ControlFrame> for Frame {
307    fn from(frame: ControlFrame) -> Self {
308        Self::Control(frame)
309    }
310}
311
312impl From<LongFrame> for Frame {
313    fn from(frame: LongFrame) -> Self {
314        Self::Long(frame)
315    }
316}
317
318#[cfg(test)]
319mod conversion_coverage {
320    use super::*;
321
322    #[test]
323    fn converts_concrete_frames() {
324        let _: Frame = AckFrame::new().into();
325        let _: Frame = NackFrame::new().into();
326        let _: Frame = ShortFrame::new(Control::snd_nke(), Address::new(1))
327            .expect("valid short frame")
328            .into();
329        let _: Frame = ControlFrame::new(Control::snd_ud2(), Address::new(1), 0)
330            .expect("valid control frame")
331            .into();
332        let _: Frame = LongFrame::new(Control::snd_ud(false), Address::new(1), 0, &[1])
333            .expect("valid long frame")
334            .into();
335    }
336}
337
338#[cfg(test)]
339#[cfg_attr(coverage_nightly, coverage(off))]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn generic_frame_api_covers_every_kind() {
345        let frames = [
346            Frame::from(AckFrame::new()),
347            Frame::from(NackFrame::new()),
348            Frame::from(ShortFrame::new(Control::snd_nke(), Address::new(1)).unwrap()),
349            Frame::from(ControlFrame::new(Control::snd_ud2(), Address::new(1), 0).unwrap()),
350            Frame::from(LongFrame::new(Control::snd_ud(false), Address::new(1), 0, &[1]).unwrap()),
351        ];
352        let kinds = [
353            FrameKind::Ack,
354            FrameKind::Nack,
355            FrameKind::Short,
356            FrameKind::Control,
357            FrameKind::Long,
358        ];
359        for (frame, kind) in frames.iter().zip(kinds) {
360            assert_eq!(frame.kind(), kind);
361            assert!(!frame.is_empty());
362            let mut output = [0; LongFrame::MAX_LEN];
363            assert_eq!(frame.encode_into(&mut output).unwrap().len(), frame.len());
364            assert_eq!(
365                frame.encode_into(&mut output[..frame.len() - 1]),
366                Err(FrameEncodeError {
367                    required: frame.len(),
368                    actual: frame.len() - 1
369                })
370            );
371            #[cfg(feature = "alloc")]
372            assert_eq!(frame.encode().len(), frame.len());
373        }
374    }
375
376    #[test]
377    fn large_frame_types_remain_inline_and_bounded() {
378        assert!(core::mem::size_of::<LongFrame>() <= LongFrame::MAX_LEN);
379        assert!(
380            core::mem::size_of::<Frame>() <= LongFrame::MAX_LEN + core::mem::size_of::<usize>()
381        );
382    }
383
384    #[cfg(feature = "alloc")]
385    #[test]
386    fn formats_frame_encode_errors() {
387        use alloc::string::ToString;
388
389        let error = FrameEncodeError {
390            required: 5,
391            actual: 4,
392        };
393        assert_eq!(
394            error.to_string(),
395            "frame output buffer too small: expected 5, got 4"
396        );
397        assert!(core::error::Error::source(&error).is_none());
398    }
399}