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}