rusty_modbus_codec/response/
device_id.rs1use rusty_modbus_types::{DeviceIdCode, MeiType};
4
5use crate::error::DecodeError;
6
7fn valid_conformity_level(level: u8) -> bool {
8 matches!(level, 0x01 | 0x02 | 0x03 | 0x81 | 0x82 | 0x83)
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct DeviceIdObjectEntry<'buf> {
14 pub id: u8,
16 pub value: &'buf [u8],
18}
19
20#[derive(Debug)]
25pub struct ReadDeviceIdentificationResponse<'buf> {
26 pub device_id_code: DeviceIdCode,
28 pub conformity_level: u8,
30 pub more_follows: bool,
32 pub next_object_id: u8,
34 pub num_objects: u8,
36 pub object_data: &'buf [u8],
38}
39
40impl<'buf> ReadDeviceIdentificationResponse<'buf> {
41 pub fn decode(data: &'buf [u8]) -> Result<Self, DecodeError> {
47 if data.len() < 6 {
48 return Err(DecodeError::Truncated {
49 expected: 6,
50 actual: data.len(),
51 });
52 }
53 if data[0] != MeiType::ReadDeviceIdentification.code() {
54 return Err(DecodeError::UnknownMeiType(data[0]));
55 }
56 let device_id_code =
57 DeviceIdCode::from_raw(data[1]).ok_or(DecodeError::InvalidDeviceIdCode(data[1]))?;
58 let conformity_level = data[2];
59 if !valid_conformity_level(conformity_level) {
60 return Err(DecodeError::InvalidDeviceIdConformityLevel(
61 conformity_level,
62 ));
63 }
64 let more_follows = match data[3] {
65 0x00 => false,
66 0xFF => true,
67 value => return Err(DecodeError::InvalidDeviceIdMoreFollows(value)),
68 };
69 let next_object_id = data[4];
70 if !more_follows && next_object_id != 0 {
71 return Err(DecodeError::InvalidDeviceIdNextObjectId(next_object_id));
72 }
73 let num_objects = data[5];
74 if device_id_code == DeviceIdCode::Individual && num_objects != 1 {
75 return Err(DecodeError::InvalidDeviceIdObjectCount(num_objects));
76 }
77
78 let object_data = &data[6..];
80 let mut offset = 0;
81 for _ in 0..num_objects {
82 if offset + 2 > object_data.len() {
83 return Err(DecodeError::Truncated {
84 expected: 6 + offset + 2,
85 actual: data.len(),
86 });
87 }
88 let obj_len = object_data[offset + 1] as usize;
89 offset += 2 + obj_len;
90 if offset > object_data.len() {
91 return Err(DecodeError::Truncated {
92 expected: 6 + offset,
93 actual: data.len(),
94 });
95 }
96 }
97 if offset != object_data.len() {
98 return Err(DecodeError::LengthMismatch {
99 expected: 6 + offset,
100 actual: data.len(),
101 });
102 }
103
104 Ok(Self {
105 device_id_code,
106 conformity_level,
107 more_follows,
108 next_object_id,
109 num_objects,
110 object_data,
111 })
112 }
113
114 #[must_use]
116 pub fn objects(&self) -> DeviceIdObjectIter<'buf> {
117 DeviceIdObjectIter {
118 data: self.object_data,
119 remaining: self.num_objects,
120 }
121 }
122}
123
124pub struct DeviceIdObjectIter<'buf> {
126 data: &'buf [u8],
127 remaining: u8,
128}
129
130impl<'buf> Iterator for DeviceIdObjectIter<'buf> {
131 type Item = DeviceIdObjectEntry<'buf>;
132
133 fn next(&mut self) -> Option<Self::Item> {
134 if self.remaining == 0 || self.data.len() < 2 {
135 return None;
136 }
137 let id = self.data[0];
138 let len = self.data[1] as usize;
139 if self.data.len() < 2 + len {
140 self.remaining = 0;
141 self.data = &[];
142 return None;
143 }
144 let value = &self.data[2..2 + len];
145 self.data = &self.data[2 + len..];
146 self.remaining -= 1;
147 Some(DeviceIdObjectEntry { id, value })
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 extern crate alloc;
154 use alloc::vec::Vec;
155
156 use super::*;
157
158 #[test]
159 fn decode_single_object_response() {
160 let data = [
161 0x0E, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x05, b'h', b'e', b'l', b'l', b'o',
162 ];
163 let resp = ReadDeviceIdentificationResponse::decode(&data).unwrap();
164 assert_eq!(resp.device_id_code, DeviceIdCode::BasicStream);
165 assert!(!resp.more_follows);
166 assert_eq!(resp.num_objects, 1);
167 let objs: Vec<_> = resp.objects().collect();
168 assert_eq!(objs[0].id, 0x00);
169 assert_eq!(objs[0].value, b"hello");
170 }
171
172 #[test]
173 fn decode_multiple_objects() {
174 let data = [
175 0x0E, 0x01, 0x01, 0x00, 0x00, 0x03, 0x00, 0x04, b't', b'e', b's', b't', 0x01, 0x03,
176 b'X', b'Y', b'Z', 0x02, 0x05, b'1', b'.', b'0', b'.', b'0',
177 ];
178 let resp = ReadDeviceIdentificationResponse::decode(&data).unwrap();
179 let objs: Vec<_> = resp.objects().collect();
180 assert_eq!(objs.len(), 3);
181 assert_eq!(objs[0].value, b"test");
182 assert_eq!(objs[1].value, b"XYZ");
183 assert_eq!(objs[2].value, b"1.0.0");
184 }
185
186 #[test]
187 fn decode_more_follows() {
188 let data = [0x0E, 0x01, 0x01, 0xFF, 0x02, 0x01, 0x00, 0x02, b'O', b'K'];
189 let resp = ReadDeviceIdentificationResponse::decode(&data).unwrap();
190 assert!(resp.more_follows);
191 assert_eq!(resp.next_object_id, 0x02);
192 }
193
194 #[test]
195 fn decode_truncated_header() {
196 let data = [0x0E, 0x01, 0x01];
197 assert!(matches!(
198 ReadDeviceIdentificationResponse::decode(&data),
199 Err(DecodeError::Truncated { .. })
200 ));
201 }
202
203 #[test]
204 fn decode_truncated_object() {
205 let data = [0x0E, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x05, b'h', b'i'];
206 assert!(matches!(
207 ReadDeviceIdentificationResponse::decode(&data),
208 Err(DecodeError::Truncated { .. })
209 ));
210 }
211
212 #[test]
213 fn decode_rejects_invalid_conformity_level() {
214 let data = [0x0E, 0x01, 0x04, 0x00, 0x00, 0x00];
215 assert!(matches!(
216 ReadDeviceIdentificationResponse::decode(&data),
217 Err(DecodeError::InvalidDeviceIdConformityLevel(0x04))
218 ));
219 }
220
221 #[test]
222 fn decode_rejects_invalid_more_follows_value() {
223 let data = [0x0E, 0x01, 0x01, 0x01, 0x00, 0x00];
224 assert!(matches!(
225 ReadDeviceIdentificationResponse::decode(&data),
226 Err(DecodeError::InvalidDeviceIdMoreFollows(0x01))
227 ));
228 }
229
230 #[test]
231 fn decode_rejects_next_object_id_without_more_follows() {
232 let data = [0x0E, 0x01, 0x01, 0x00, 0x02, 0x00];
233 assert!(matches!(
234 ReadDeviceIdentificationResponse::decode(&data),
235 Err(DecodeError::InvalidDeviceIdNextObjectId(0x02))
236 ));
237 }
238
239 #[test]
240 fn decode_rejects_individual_response_without_one_object() {
241 let data = [0x0E, 0x04, 0x81, 0x00, 0x00, 0x00];
242 assert!(matches!(
243 ReadDeviceIdentificationResponse::decode(&data),
244 Err(DecodeError::InvalidDeviceIdObjectCount(0))
245 ));
246 }
247
248 #[test]
249 fn object_iterator_stops_on_malformed_manual_response() {
250 let resp = ReadDeviceIdentificationResponse {
251 device_id_code: DeviceIdCode::BasicStream,
252 conformity_level: 0x01,
253 more_follows: false,
254 next_object_id: 0,
255 num_objects: 1,
256 object_data: &[0x00, 0x05, b'h', b'i'],
257 };
258
259 assert!(resp.objects().next().is_none());
260 }
261}