Skip to main content

rustmod_core/pdu/
exception.rs

1use crate::encoding::{Reader, Writer};
2use crate::{DecodeError, EncodeError};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5#[cfg_attr(feature = "defmt", derive(defmt::Format))]
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7pub enum ExceptionCode {
8    IllegalFunction,
9    IllegalDataAddress,
10    IllegalDataValue,
11    ServerDeviceFailure,
12    Acknowledge,
13    ServerDeviceBusy,
14    MemoryParityError,
15    GatewayPathUnavailable,
16    GatewayTargetFailedToRespond,
17    Unknown(u8),
18}
19
20impl ExceptionCode {
21    pub const fn from_u8(value: u8) -> Self {
22        match value {
23            0x01 => Self::IllegalFunction,
24            0x02 => Self::IllegalDataAddress,
25            0x03 => Self::IllegalDataValue,
26            0x04 => Self::ServerDeviceFailure,
27            0x05 => Self::Acknowledge,
28            0x06 => Self::ServerDeviceBusy,
29            0x08 => Self::MemoryParityError,
30            0x0A => Self::GatewayPathUnavailable,
31            0x0B => Self::GatewayTargetFailedToRespond,
32            other => Self::Unknown(other),
33        }
34    }
35
36    pub const fn as_u8(self) -> u8 {
37        match self {
38            Self::IllegalFunction => 0x01,
39            Self::IllegalDataAddress => 0x02,
40            Self::IllegalDataValue => 0x03,
41            Self::ServerDeviceFailure => 0x04,
42            Self::Acknowledge => 0x05,
43            Self::ServerDeviceBusy => 0x06,
44            Self::MemoryParityError => 0x08,
45            Self::GatewayPathUnavailable => 0x0A,
46            Self::GatewayTargetFailedToRespond => 0x0B,
47            Self::Unknown(raw) => raw,
48        }
49    }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[cfg_attr(feature = "defmt", derive(defmt::Format))]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct ExceptionResponse {
56    /// Raw function code without the exception bit (bit 7).
57    pub function_code: u8,
58    pub exception_code: ExceptionCode,
59}
60
61impl ExceptionResponse {
62    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
63        w.write_u8(self.function_code | 0x80)?;
64        w.write_u8(self.exception_code.as_u8())?;
65        Ok(())
66    }
67
68    pub fn decode(function_byte: u8, r: &mut Reader<'_>) -> Result<Self, DecodeError> {
69        if (function_byte & 0x80) == 0 {
70            return Err(DecodeError::InvalidFunctionCode);
71        }
72        let exception = r.read_u8()?;
73        Ok(Self {
74            function_code: function_byte & 0x7F,
75            exception_code: ExceptionCode::from_u8(exception),
76        })
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::{ExceptionCode, ExceptionResponse};
83    use crate::encoding::{Reader, Writer};
84
85    #[test]
86    fn roundtrip_exception_response() {
87        let mut buf = [0u8; 2];
88        let mut w = Writer::new(&mut buf);
89        let resp = ExceptionResponse {
90            function_code: 0x03,
91            exception_code: ExceptionCode::ServerDeviceBusy,
92        };
93        resp.encode(&mut w).unwrap();
94        assert_eq!(w.as_written(), &[0x83, 0x06]);
95
96        let mut r = Reader::new(w.as_written());
97        let fc = r.read_u8().unwrap();
98        let decoded = ExceptionResponse::decode(fc, &mut r).unwrap();
99        assert_eq!(decoded, resp);
100    }
101
102    #[test]
103    fn preserves_unknown_exception_codes() {
104        let mut r = Reader::new(&[0x11]);
105        let decoded = ExceptionResponse::decode(0x83, &mut r).unwrap();
106        assert_eq!(decoded.exception_code, ExceptionCode::Unknown(0x11));
107    }
108}