1use bacnet_encoding::primitives;
4use bacnet_encoding::tags;
5use bacnet_types::enums::{EventState, EventType};
6use bacnet_types::error::Error;
7use bacnet_types::primitives::ObjectIdentifier;
8use bytes::BytesMut;
9
10use crate::common::MAX_DECODED_ITEMS;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct PriorityFilter {
19 pub min_priority: u8,
20 pub max_priority: u8,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct RecipientProcess {
29 pub device: Option<ObjectIdentifier>,
31 pub process_identifier: u32,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct GetEnrollmentSummaryRequest {
38 pub acknowledgment_filter: u32,
40 pub enrollment_filter: Option<RecipientProcess>,
42 pub event_state_filter: Option<EventState>,
44 pub event_type_filter: Option<EventType>,
46 pub priority_filter: Option<PriorityFilter>,
48 pub notification_class_filter: Option<u16>,
50}
51
52impl GetEnrollmentSummaryRequest {
53 pub fn encode(&self, buf: &mut BytesMut) {
54 primitives::encode_ctx_enumerated(buf, 0, self.acknowledgment_filter);
56 if let Some(ref ef) = self.enrollment_filter {
58 tags::encode_opening_tag(buf, 1);
59 if let Some(ref device) = ef.device {
61 tags::encode_opening_tag(buf, 0);
62 primitives::encode_ctx_object_id(buf, 0, device);
63 tags::encode_closing_tag(buf, 0);
64 }
65 primitives::encode_ctx_unsigned(buf, 1, ef.process_identifier as u64);
67 tags::encode_closing_tag(buf, 1);
68 }
69 if let Some(es) = self.event_state_filter {
71 primitives::encode_ctx_enumerated(buf, 2, es.to_raw());
72 }
73 if let Some(et) = self.event_type_filter {
75 primitives::encode_ctx_enumerated(buf, 3, et.to_raw());
76 }
77 if let Some(pf) = self.priority_filter {
79 tags::encode_opening_tag(buf, 4);
80 primitives::encode_ctx_unsigned(buf, 0, pf.min_priority as u64);
81 primitives::encode_ctx_unsigned(buf, 1, pf.max_priority as u64);
82 tags::encode_closing_tag(buf, 4);
83 }
84 if let Some(nc) = self.notification_class_filter {
86 primitives::encode_ctx_unsigned(buf, 5, nc as u64);
87 }
88 }
89
90 pub fn decode(data: &[u8]) -> Result<Self, Error> {
91 let mut offset = 0;
92
93 let (tag, pos) = tags::decode_tag(data, offset)?;
95 let end = pos + tag.length as usize;
96 if end > data.len() {
97 return Err(Error::decoding(
98 pos,
99 "EnrollmentSummary truncated at acknowledgmentFilter",
100 ));
101 }
102 let acknowledgment_filter = primitives::decode_unsigned(&data[pos..end])? as u32;
103 offset = end;
104
105 let mut enrollment_filter = None;
107 if offset < data.len() {
108 let (tag, tag_end) = tags::decode_tag(data, offset)?;
109 if tag.is_opening_tag(1) {
110 let mut device = None;
112 let mut process_id = 0u32;
113 let (content, new_offset) = tags::extract_context_value(data, tag_end, 1)?;
114 let mut inner_offset = 0;
115 while inner_offset < content.len() {
116 let (inner_tag, inner_pos) = tags::decode_tag(content, inner_offset)?;
117 if inner_tag.is_opening_tag(0) {
118 let (recipient_content, recipient_end) =
120 tags::extract_context_value(content, inner_pos, 0)?;
121 if !recipient_content.is_empty() {
122 let (dev_tag, dev_pos) = tags::decode_tag(recipient_content, 0)?;
123 if dev_tag.is_context(0) {
124 let dev_end = dev_pos + dev_tag.length as usize;
125 if dev_end <= recipient_content.len() {
126 device = Some(ObjectIdentifier::decode(
127 &recipient_content[dev_pos..dev_end],
128 )?);
129 }
130 }
131 }
132 inner_offset = recipient_end;
133 } else if inner_tag.is_context(1) {
134 let inner_end = inner_pos + inner_tag.length as usize;
135 if inner_end <= content.len() {
136 process_id =
137 primitives::decode_unsigned(&content[inner_pos..inner_end])? as u32;
138 }
139 inner_offset = inner_end;
140 } else {
141 inner_offset = inner_pos + inner_tag.length as usize;
142 }
143 }
144 enrollment_filter = Some(RecipientProcess {
145 device,
146 process_identifier: process_id,
147 });
148 offset = new_offset;
149 }
150 }
151
152 let mut event_state_filter = None;
154 let (opt_data, new_offset) = tags::decode_optional_context(data, offset, 2)?;
155 if let Some(content) = opt_data {
156 event_state_filter = Some(EventState::from_raw(
157 primitives::decode_unsigned(content)? as u32
158 ));
159 offset = new_offset;
160 }
161
162 let mut event_type_filter = None;
164 let (opt_data, new_offset) = tags::decode_optional_context(data, offset, 3)?;
165 if let Some(content) = opt_data {
166 event_type_filter = Some(EventType::from_raw(
167 primitives::decode_unsigned(content)? as u32
168 ));
169 offset = new_offset;
170 }
171
172 let mut priority_filter = None;
174 if offset < data.len() {
175 let (tag, tag_end) = tags::decode_tag(data, offset)?;
176 if tag.is_opening_tag(4) {
177 let (inner_tag, inner_pos) = tags::decode_tag(data, tag_end)?;
179 let inner_end = inner_pos + inner_tag.length as usize;
180 if inner_end > data.len() {
181 return Err(Error::decoding(
182 inner_pos,
183 "EnrollmentSummary truncated at minPriority",
184 ));
185 }
186 let min_priority = primitives::decode_unsigned(&data[inner_pos..inner_end])? as u8;
187
188 let (inner_tag, inner_pos) = tags::decode_tag(data, inner_end)?;
190 let inner_end = inner_pos + inner_tag.length as usize;
191 if inner_end > data.len() {
192 return Err(Error::decoding(
193 inner_pos,
194 "EnrollmentSummary truncated at maxPriority",
195 ));
196 }
197 let max_priority = primitives::decode_unsigned(&data[inner_pos..inner_end])? as u8;
198
199 let (close_tag, close_end) = tags::decode_tag(data, inner_end)?;
201 if !close_tag.is_closing_tag(4) {
202 return Err(Error::decoding(
203 inner_end,
204 "EnrollmentSummary expected closing tag 4",
205 ));
206 }
207 priority_filter = Some(PriorityFilter {
208 min_priority,
209 max_priority,
210 });
211 offset = close_end;
212 }
213 }
214
215 let mut notification_class_filter = None;
217 if offset < data.len() {
218 let (opt_data, _new_offset) = tags::decode_optional_context(data, offset, 5)?;
219 if let Some(content) = opt_data {
220 notification_class_filter = Some(primitives::decode_unsigned(content)? as u16);
221 }
222 }
223
224 Ok(Self {
225 acknowledgment_filter,
226 enrollment_filter,
227 event_state_filter,
228 event_type_filter,
229 priority_filter,
230 notification_class_filter,
231 })
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct EnrollmentSummaryEntry {
242 pub object_identifier: ObjectIdentifier,
243 pub event_type: EventType,
244 pub event_state: EventState,
245 pub priority: u8,
246 pub notification_class: u16,
247}
248
249#[derive(Debug, Clone, PartialEq, Eq)]
251pub struct GetEnrollmentSummaryAck {
252 pub entries: Vec<EnrollmentSummaryEntry>,
253}
254
255impl GetEnrollmentSummaryAck {
256 pub fn encode(&self, buf: &mut BytesMut) {
257 for entry in &self.entries {
258 primitives::encode_app_object_id(buf, &entry.object_identifier);
259 primitives::encode_app_enumerated(buf, entry.event_type.to_raw());
260 primitives::encode_app_enumerated(buf, entry.event_state.to_raw());
261 primitives::encode_app_unsigned(buf, entry.priority as u64);
262 primitives::encode_app_unsigned(buf, entry.notification_class as u64);
263 }
264 }
265
266 pub fn decode(data: &[u8]) -> Result<Self, Error> {
267 let mut entries = Vec::new();
268 let mut offset = 0;
269
270 while offset < data.len() {
271 if entries.len() >= MAX_DECODED_ITEMS {
272 return Err(Error::decoding(
273 offset,
274 "EnrollmentSummaryAck too many entries",
275 ));
276 }
277
278 let (tag, pos) = tags::decode_tag(data, offset)?;
280 let end = pos + tag.length as usize;
281 if end > data.len() {
282 return Err(Error::decoding(
283 pos,
284 "EnrollmentSummaryAck truncated at object-id",
285 ));
286 }
287 let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
288 offset = end;
289
290 let (tag, pos) = tags::decode_tag(data, offset)?;
292 let end = pos + tag.length as usize;
293 if end > data.len() {
294 return Err(Error::decoding(
295 pos,
296 "EnrollmentSummaryAck truncated at eventType",
297 ));
298 }
299 let event_type =
300 EventType::from_raw(primitives::decode_unsigned(&data[pos..end])? as u32);
301 offset = end;
302
303 let (tag, pos) = tags::decode_tag(data, offset)?;
305 let end = pos + tag.length as usize;
306 if end > data.len() {
307 return Err(Error::decoding(
308 pos,
309 "EnrollmentSummaryAck truncated at eventState",
310 ));
311 }
312 let event_state =
313 EventState::from_raw(primitives::decode_unsigned(&data[pos..end])? as u32);
314 offset = end;
315
316 let (tag, pos) = tags::decode_tag(data, offset)?;
318 let end = pos + tag.length as usize;
319 if end > data.len() {
320 return Err(Error::decoding(
321 pos,
322 "EnrollmentSummaryAck truncated at priority",
323 ));
324 }
325 let priority = primitives::decode_unsigned(&data[pos..end])? as u8;
326 offset = end;
327
328 let (tag, pos) = tags::decode_tag(data, offset)?;
330 let end = pos + tag.length as usize;
331 if end > data.len() {
332 return Err(Error::decoding(
333 pos,
334 "EnrollmentSummaryAck truncated at notificationClass",
335 ));
336 }
337 let notification_class = primitives::decode_unsigned(&data[pos..end])? as u16;
338 offset = end;
339
340 entries.push(EnrollmentSummaryEntry {
341 object_identifier,
342 event_type,
343 event_state,
344 priority,
345 notification_class,
346 });
347 }
348
349 Ok(Self { entries })
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use bacnet_types::enums::ObjectType;
357
358 #[test]
359 fn request_round_trip() {
360 let req = GetEnrollmentSummaryRequest {
361 acknowledgment_filter: 0, enrollment_filter: None,
363 event_state_filter: Some(EventState::OFFNORMAL),
364 event_type_filter: None,
365 priority_filter: Some(PriorityFilter {
366 min_priority: 1,
367 max_priority: 10,
368 }),
369 notification_class_filter: Some(5),
370 };
371 let mut buf = BytesMut::new();
372 req.encode(&mut buf);
373 let decoded = GetEnrollmentSummaryRequest::decode(&buf).unwrap();
374 assert_eq!(req, decoded);
375 }
376
377 #[test]
378 fn request_minimal_round_trip() {
379 let req = GetEnrollmentSummaryRequest {
380 acknowledgment_filter: 2, enrollment_filter: None,
382 event_state_filter: None,
383 event_type_filter: None,
384 priority_filter: None,
385 notification_class_filter: None,
386 };
387 let mut buf = BytesMut::new();
388 req.encode(&mut buf);
389 let decoded = GetEnrollmentSummaryRequest::decode(&buf).unwrap();
390 assert_eq!(req, decoded);
391 }
392
393 #[test]
394 fn ack_round_trip() {
395 let ack = GetEnrollmentSummaryAck {
396 entries: vec![
397 EnrollmentSummaryEntry {
398 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
399 event_type: EventType::OUT_OF_RANGE,
400 event_state: EventState::HIGH_LIMIT,
401 priority: 3,
402 notification_class: 10,
403 },
404 EnrollmentSummaryEntry {
405 object_identifier: ObjectIdentifier::new(ObjectType::BINARY_INPUT, 5).unwrap(),
406 event_type: EventType::CHANGE_OF_STATE,
407 event_state: EventState::NORMAL,
408 priority: 7,
409 notification_class: 20,
410 },
411 ],
412 };
413 let mut buf = BytesMut::new();
414 ack.encode(&mut buf);
415 let decoded = GetEnrollmentSummaryAck::decode(&buf).unwrap();
416 assert_eq!(ack, decoded);
417 }
418
419 #[test]
420 fn ack_empty_round_trip() {
421 let ack = GetEnrollmentSummaryAck { entries: vec![] };
422 let mut buf = BytesMut::new();
423 ack.encode(&mut buf);
424 let decoded = GetEnrollmentSummaryAck::decode(&buf).unwrap();
425 assert_eq!(ack, decoded);
426 }
427
428 #[test]
433 fn test_decode_request_empty_input() {
434 assert!(GetEnrollmentSummaryRequest::decode(&[]).is_err());
435 }
436
437 #[test]
438 fn test_decode_request_truncated_1_byte() {
439 let req = GetEnrollmentSummaryRequest {
440 acknowledgment_filter: 0,
441 enrollment_filter: None,
442 event_state_filter: Some(EventState::FAULT),
443 event_type_filter: None,
444 priority_filter: None,
445 notification_class_filter: None,
446 };
447 let mut buf = BytesMut::new();
448 req.encode(&mut buf);
449 assert!(GetEnrollmentSummaryRequest::decode(&buf[..1]).is_err());
450 }
451
452 #[test]
453 fn test_decode_request_invalid_tag() {
454 assert!(GetEnrollmentSummaryRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
455 }
456
457 #[test]
458 fn test_decode_ack_truncated_1_byte() {
459 let ack = GetEnrollmentSummaryAck {
460 entries: vec![EnrollmentSummaryEntry {
461 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
462 event_type: EventType::OUT_OF_RANGE,
463 event_state: EventState::HIGH_LIMIT,
464 priority: 3,
465 notification_class: 10,
466 }],
467 };
468 let mut buf = BytesMut::new();
469 ack.encode(&mut buf);
470 assert!(GetEnrollmentSummaryAck::decode(&buf[..1]).is_err());
471 }
472
473 #[test]
474 fn test_decode_ack_truncated_half() {
475 let ack = GetEnrollmentSummaryAck {
476 entries: vec![EnrollmentSummaryEntry {
477 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap(),
478 event_type: EventType::OUT_OF_RANGE,
479 event_state: EventState::HIGH_LIMIT,
480 priority: 3,
481 notification_class: 10,
482 }],
483 };
484 let mut buf = BytesMut::new();
485 ack.encode(&mut buf);
486 let half = buf.len() / 2;
487 assert!(GetEnrollmentSummaryAck::decode(&buf[..half]).is_err());
488 }
489
490 #[test]
491 fn test_decode_ack_invalid_tag() {
492 assert!(GetEnrollmentSummaryAck::decode(&[0xFF, 0xFF, 0xFF]).is_err());
493 }
494}