Skip to main content

meterbus_wired_datalink/frame/
nack.rs

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