Skip to main content

meterbus_wired_datalink/frame/
ack.rs

1//! Positive acknowledgement (ACK) frames.
2//!
3//! An ACK is the single byte `0xe5`. It confirms that a slave accepted a
4//! request when the M-Bus exchange calls for an acknowledgement. It contains
5//! no address, control field, or checksum.
6//!
7//! ```text
8//! +------+
9//! | 0xe5 |
10//! +------+
11//! ```
12//!
13//! [`AckFrame::decode`] requires exactly one byte and rejects any other value.
14//! [`AckFrame::encode_into`] requires at least one output byte and returns a
15//! one-byte slice. With the `alloc` feature, [`AckFrame::encode`] returns an
16//! allocated vector.
17//!
18//! # Example
19//!
20//! ```
21//! use meterbus_wired_datalink::AckFrame;
22//!
23//! # fn main() -> Result<(), meterbus_wired_datalink::AckFrameError> {
24//! let frame = AckFrame::decode(&[0xe5])?;
25//! let mut output = [0_u8; AckFrame::LEN];
26//! assert_eq!(frame.encode_into(&mut output)?, [0xe5]);
27//! # Ok(())
28//! # }
29//! ```
30
31use core::fmt;
32
33#[cfg(feature = "alloc")]
34use alloc::{vec, vec::Vec};
35
36/// A positive acknowledgement frame.
37#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
38pub struct AckFrame;
39
40impl AckFrame {
41    /// Wire byte identifying an acknowledgement.
42    pub const BYTE: u8 = 0xe5;
43    /// Encoded frame length.
44    pub const LEN: usize = 1;
45
46    /// Creates an acknowledgement frame.
47    #[must_use]
48    pub const fn new() -> Self {
49        Self
50    }
51
52    /// Decodes exactly one acknowledgement frame.
53    pub fn decode(bytes: &[u8]) -> Result<Self, AckFrameError> {
54        if bytes.len() != Self::LEN {
55            return Err(AckFrameError::InvalidLength {
56                actual: bytes.len(),
57            });
58        }
59        if bytes[0] != Self::BYTE {
60            return Err(AckFrameError::InvalidByte { actual: bytes[0] });
61        }
62        Ok(Self)
63    }
64
65    /// Encodes the frame into `output` and returns the encoded portion.
66    pub fn encode_into<'a>(&self, output: &'a mut [u8]) -> Result<&'a [u8], AckFrameError> {
67        if output.len() < Self::LEN {
68            return Err(AckFrameError::OutputTooSmall {
69                actual: output.len(),
70            });
71        }
72        output[0] = Self::BYTE;
73        Ok(&output[..Self::LEN])
74    }
75
76    /// Encodes the frame into a newly allocated vector.
77    #[cfg(feature = "alloc")]
78    pub fn encode(&self) -> Vec<u8> {
79        vec![Self::BYTE]
80    }
81}
82
83/// Error produced while encoding or decoding an acknowledgement frame.
84#[derive(Clone, Copy, Debug, Eq, PartialEq)]
85#[allow(missing_docs)]
86pub enum AckFrameError {
87    /// The input was not exactly one byte.
88    InvalidLength { actual: usize },
89    /// The single input byte was not `0xe5`.
90    InvalidByte { actual: u8 },
91    /// The output buffer had no room for the frame.
92    OutputTooSmall { actual: usize },
93}
94
95impl fmt::Display for AckFrameError {
96    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            Self::InvalidLength { actual } => write!(
99                formatter,
100                "invalid ACK frame length: expected 1, got {actual}"
101            ),
102            Self::InvalidByte { actual } => write!(
103                formatter,
104                "invalid ACK frame byte: expected 0xe5, got 0x{actual:02x}"
105            ),
106            Self::OutputTooSmall { actual } => write!(
107                formatter,
108                "ACK output buffer too small: expected 1, got {actual}"
109            ),
110        }
111    }
112}
113
114impl core::error::Error for AckFrameError {}
115
116#[cfg(test)]
117#[cfg_attr(coverage_nightly, coverage(off))]
118mod tests {
119    use super::*;
120    #[cfg(feature = "alloc")]
121    use alloc::string::ToString;
122
123    #[test]
124    fn encodes_and_decodes() {
125        let mut output = [0; 2];
126        let encoded = AckFrame::new().encode_into(&mut output).unwrap();
127        assert_eq!(encoded, [0xe5]);
128        assert_eq!(AckFrame::decode(encoded), Ok(AckFrame));
129    }
130
131    #[cfg(feature = "alloc")]
132    #[test]
133    fn allocates_encoded_frame() {
134        assert_eq!(AckFrame.encode(), [0xe5]);
135    }
136
137    #[test]
138    fn rejects_invalid_inputs_and_output() {
139        assert_eq!(
140            AckFrame::decode(&[]),
141            Err(AckFrameError::InvalidLength { actual: 0 })
142        );
143        assert_eq!(
144            AckFrame::decode(&[0xe5, 0]),
145            Err(AckFrameError::InvalidLength { actual: 2 })
146        );
147        assert_eq!(
148            AckFrame::decode(&[0]),
149            Err(AckFrameError::InvalidByte { actual: 0 })
150        );
151        assert_eq!(
152            AckFrame.encode_into(&mut []),
153            Err(AckFrameError::OutputTooSmall { actual: 0 })
154        );
155    }
156
157    #[cfg(feature = "alloc")]
158    #[test]
159    fn formats_errors() {
160        assert_eq!(
161            AckFrameError::InvalidLength { actual: 0 }.to_string(),
162            "invalid ACK frame length: expected 1, got 0"
163        );
164        assert_eq!(
165            AckFrameError::InvalidByte { actual: 0 }.to_string(),
166            "invalid ACK frame byte: expected 0xe5, got 0x00"
167        );
168        assert_eq!(
169            AckFrameError::OutputTooSmall { actual: 0 }.to_string(),
170            "ACK output buffer too small: expected 1, got 0"
171        );
172    }
173}