rustmod_core/pdu/
exception.rs1use 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 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}