1use bacnet_types::constructed::{BACnetEventParameter, ChangeOfValueCriteria};
41use bacnet_types::error::Error;
42use bytes::BytesMut;
43
44use crate::primitives;
45use crate::tags;
46
47use super::{
48 decode_app_bit_string, decode_ctx_bit_string, decode_ctx_real, decode_ctx_unsigned,
49 decode_dopr_body, decode_property_state, encode_dopr_body, encode_property_state,
50 expect_closing, expect_opening, validate_extended_parameters, validate_tlv_sequence,
51 MAX_FRAMED_ITEMS,
52};
53
54const RESERVED_EVENT_TAGS: [u8; 4] = [6, 7, 12, 19];
58const MODELED_EVENT_TAGS: [u8; 6] = [0, 1, 2, 4, 5, 9];
59
60pub fn encode_event_parameter(
65 buf: &mut BytesMut,
66 value: &BACnetEventParameter,
67) -> Result<(), Error> {
68 let mut encoded = BytesMut::new();
69 encode_event_parameter_into(&mut encoded, value)?;
70 validate_tlv_sequence(&encoded, "BACnetEventParameter")
71 .map_err(|error| Error::Encoding(error.to_string()))?;
72 buf.extend_from_slice(&encoded);
73 Ok(())
74}
75
76fn encode_event_parameter_into(
77 buf: &mut BytesMut,
78 value: &BACnetEventParameter,
79) -> Result<(), Error> {
80 match value {
81 BACnetEventParameter::ChangeOfBitstring {
82 time_delay,
83 bitmask,
84 list_of_values,
85 } => {
86 tags::encode_opening_tag(buf, 0);
87 primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
88 primitives::encode_ctx_bit_string(buf, 1, bitmask.0, &bitmask.1);
89 tags::encode_opening_tag(buf, 2);
90 for (unused_bits, data) in list_of_values {
91 primitives::encode_app_bit_string(buf, *unused_bits, data);
92 }
93 tags::encode_closing_tag(buf, 2);
94 tags::encode_closing_tag(buf, 0);
95 }
96 BACnetEventParameter::ChangeOfState {
97 time_delay,
98 list_of_values,
99 } => {
100 let mut encoded_states = BytesMut::new();
101 for state in list_of_values {
102 encode_property_state(&mut encoded_states, state)?;
103 }
104 tags::encode_opening_tag(buf, 1);
105 primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
106 tags::encode_opening_tag(buf, 1);
107 buf.extend_from_slice(&encoded_states);
108 tags::encode_closing_tag(buf, 1);
109 tags::encode_closing_tag(buf, 1);
110 }
111 BACnetEventParameter::ChangeOfValue {
112 time_delay,
113 criteria,
114 } => {
115 tags::encode_opening_tag(buf, 2);
116 primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
117 tags::encode_opening_tag(buf, 1);
120 match criteria {
121 ChangeOfValueCriteria::Bitmask { unused_bits, data } => {
122 primitives::encode_ctx_bit_string(buf, 0, *unused_bits, data);
123 }
124 ChangeOfValueCriteria::ReferencedPropertyIncrement(increment) => {
125 primitives::encode_ctx_real(buf, 1, *increment);
126 }
127 }
128 tags::encode_closing_tag(buf, 1);
129 tags::encode_closing_tag(buf, 2);
130 }
131 BACnetEventParameter::FloatingLimit {
132 time_delay,
133 setpoint_reference,
134 low_diff_limit,
135 high_diff_limit,
136 deadband,
137 } => {
138 tags::encode_opening_tag(buf, 4);
139 primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
140 tags::encode_opening_tag(buf, 1);
142 encode_dopr_body(buf, setpoint_reference);
143 tags::encode_closing_tag(buf, 1);
144 primitives::encode_ctx_real(buf, 2, *low_diff_limit);
145 primitives::encode_ctx_real(buf, 3, *high_diff_limit);
146 primitives::encode_ctx_real(buf, 4, *deadband);
147 tags::encode_closing_tag(buf, 4);
148 }
149 BACnetEventParameter::OutOfRange {
150 time_delay,
151 low_limit,
152 high_limit,
153 deadband,
154 } => {
155 tags::encode_opening_tag(buf, 5);
156 primitives::encode_ctx_unsigned(buf, 0, *time_delay as u64);
157 primitives::encode_ctx_real(buf, 1, *low_limit);
158 primitives::encode_ctx_real(buf, 2, *high_limit);
159 primitives::encode_ctx_real(buf, 3, *deadband);
160 tags::encode_closing_tag(buf, 5);
161 }
162 BACnetEventParameter::Extended {
163 vendor_id,
164 extended_event_type,
165 parameters,
166 } => {
167 validate_extended_parameters(parameters, "extended")
168 .map_err(|error| Error::Encoding(error.to_string()))?;
169 tags::encode_opening_tag(buf, 9);
170 primitives::encode_ctx_unsigned(buf, 0, *vendor_id as u64);
171 primitives::encode_ctx_unsigned(buf, 1, *extended_event_type as u64);
172 tags::encode_opening_tag(buf, 2);
174 buf.extend_from_slice(parameters);
175 tags::encode_closing_tag(buf, 2);
176 tags::encode_closing_tag(buf, 9);
177 }
178 BACnetEventParameter::Opaque { tag, data } if *tag == u8::MAX => {
179 primitives::encode_app_octet_string(buf, data);
181 }
182 BACnetEventParameter::Opaque { tag: 20, data } if data.is_empty() => {
183 tags::encode_tag(buf, 20, tags::TagClass::Context, 0);
185 }
186 BACnetEventParameter::Opaque { tag, .. }
187 if *tag == 20
188 || MODELED_EVENT_TAGS.contains(tag)
189 || RESERVED_EVENT_TAGS.contains(tag) =>
190 {
191 return Err(Error::Encoding(format!(
192 "BACnetEventParameter: tag [{tag}] cannot use an opaque constructed body"
193 )));
194 }
195 BACnetEventParameter::Opaque { tag, data } => {
196 validate_tlv_sequence(data, "opaque event parameters")
197 .map_err(|error| Error::Encoding(error.to_string()))?;
198 tags::encode_opening_tag(buf, *tag);
201 buf.extend_from_slice(data);
202 tags::encode_closing_tag(buf, *tag);
203 }
204 }
205 Ok(())
206}
207
208pub fn decode_event_parameter(
214 data: &[u8],
215 offset: usize,
216) -> Result<(BACnetEventParameter, usize), Error> {
217 use BACnetEventParameter as EP;
218
219 if data.get(offset..offset.saturating_add(2)) == Some(&[0xFE, 0xFF]) {
223 let body_start = offset + 2;
224 let body_end = data.len().checked_sub(2).ok_or_else(|| {
225 Error::decoding(offset, "BACnetEventParameter: truncated legacy wrapper")
226 })?;
227 if body_end < body_start || data.get(body_end..) != Some(&[0xFF, 0xFF]) {
228 return Err(Error::decoding(
229 offset,
230 "BACnetEventParameter: missing legacy closing tag",
231 ));
232 }
233 return Ok((
234 EP::Opaque {
235 tag: u8::MAX,
236 data: data[body_start..body_end].to_vec(),
237 },
238 data.len(),
239 ));
240 }
241
242 let (tag, pos) = tags::decode_tag(data, offset)?;
243 let what = "BACnetEventParameter";
244
245 if tag.class == tags::TagClass::Application && tag.number == tags::app_tag::OCTET_STRING {
246 let end = pos
247 .checked_add(tag.length as usize)
248 .ok_or_else(|| Error::decoding(pos, "BACnetEventParameter: length overflow"))?;
249 if end > data.len() {
250 return Err(Error::buffer_too_short(end, data.len()));
251 }
252 return Ok((
253 EP::Opaque {
254 tag: u8::MAX,
255 data: data[pos..end].to_vec(),
256 },
257 end,
258 ));
259 }
260
261 if tag.class != tags::TagClass::Context {
262 return Err(Error::decoding(
263 offset,
264 "BACnetEventParameter: expected a context tag",
265 ));
266 }
267
268 if RESERVED_EVENT_TAGS.contains(&tag.number) {
269 return Err(Error::decoding(
270 offset,
271 format!(
272 "BACnetEventParameter: context tag [{}] is omitted/deprecated/reserved in Clause 21",
273 tag.number
274 ),
275 ));
276 }
277
278 if !tag.is_opening && !tag.is_closing {
280 if tag.number == 20 && tag.length == 0 {
281 return Ok((
282 EP::Opaque {
283 tag: 20,
284 data: Vec::new(),
285 },
286 pos,
287 ));
288 }
289 return Err(Error::decoding(
290 offset,
291 "BACnetEventParameter: invalid primitive alternative",
292 ));
293 }
294
295 if !tag.is_opening {
296 return Err(Error::decoding(
297 offset,
298 "BACnetEventParameter: unexpected closing tag",
299 ));
300 }
301 if tag.number == 20 {
302 return Err(Error::decoding(
303 offset,
304 "BACnetEventParameter: none [20] must use primitive NULL form",
305 ));
306 }
307
308 let value = match tag.number {
309 0 => {
310 let mut pos = pos;
311 let (raw, p) = decode_ctx_unsigned(data, pos, 0, what)?;
312 let time_delay = u32::try_from(raw)
313 .map_err(|_| Error::decoding(pos, "change-of-bitstring: time-delay exceeds u32"))?;
314 pos = p;
315 let (bitmask, p) = decode_ctx_bit_string(data, pos, 1, what)?;
316 pos = p;
317 pos = expect_opening(data, pos, 2, what)?;
318 let mut list_of_values = Vec::new();
319 loop {
320 let (peek, _) = tags::decode_tag(data, pos)?;
321 if peek.is_closing_tag(2) {
322 break;
323 }
324 if list_of_values.len() >= MAX_FRAMED_ITEMS {
325 return Err(Error::decoding(
326 pos,
327 "change-of-bitstring: list-of-bitstring-values exceeds limit",
328 ));
329 }
330 let (bits, p) = decode_app_bit_string(data, pos, what)?;
331 list_of_values.push(bits);
332 pos = p;
333 }
334 pos = expect_closing(data, pos, 2, what)?;
335 pos = expect_closing(data, pos, 0, what)?;
336 (
337 EP::ChangeOfBitstring {
338 time_delay,
339 bitmask,
340 list_of_values,
341 },
342 pos,
343 )
344 }
345 1 => {
346 let mut pos = pos;
347 let (raw, p) = decode_ctx_unsigned(data, pos, 0, what)?;
348 let time_delay = u32::try_from(raw)
349 .map_err(|_| Error::decoding(pos, "change-of-state: time-delay exceeds u32"))?;
350 pos = p;
351 pos = expect_opening(data, pos, 1, what)?;
352 let mut list_of_values = Vec::new();
353 loop {
354 let (peek, _) = tags::decode_tag(data, pos)?;
355 if peek.is_closing_tag(1) {
356 break;
357 }
358 if list_of_values.len() >= MAX_FRAMED_ITEMS {
359 return Err(Error::decoding(
360 pos,
361 "change-of-state: list-of-values exceeds limit",
362 ));
363 }
364 let (state, p) = decode_property_state(data, pos)?;
365 list_of_values.push(state);
366 pos = p;
367 }
368 pos = expect_closing(data, pos, 1, what)?;
369 pos = expect_closing(data, pos, 1, what)?;
370 (
371 EP::ChangeOfState {
372 time_delay,
373 list_of_values,
374 },
375 pos,
376 )
377 }
378 2 => {
379 let mut pos = pos;
380 let (raw, p) = decode_ctx_unsigned(data, pos, 0, what)?;
381 let time_delay = u32::try_from(raw)
382 .map_err(|_| Error::decoding(pos, "change-of-value: time-delay exceeds u32"))?;
383 pos = p;
384 pos = expect_opening(data, pos, 1, what)?;
385 let (inner, _) = tags::decode_tag(data, pos)?;
386 let criteria = if inner.is_context(0) {
387 let (bits, p) = decode_ctx_bit_string(data, pos, 0, what)?;
388 pos = p;
389 ChangeOfValueCriteria::Bitmask {
390 unused_bits: bits.0,
391 data: bits.1,
392 }
393 } else if inner.is_context(1) {
394 let (increment, p) = decode_ctx_real(data, pos, 1, what)?;
395 pos = p;
396 ChangeOfValueCriteria::ReferencedPropertyIncrement(increment)
397 } else {
398 return Err(Error::decoding(
399 pos,
400 format!(
401 "change-of-value: unexpected cov-criteria tag [{}]",
402 inner.number
403 ),
404 ));
405 };
406 pos = expect_closing(data, pos, 1, what)?;
407 pos = expect_closing(data, pos, 2, what)?;
408 (
409 EP::ChangeOfValue {
410 time_delay,
411 criteria,
412 },
413 pos,
414 )
415 }
416 4 => {
417 let mut pos = pos;
418 let (raw, p) = decode_ctx_unsigned(data, pos, 0, what)?;
419 let time_delay = u32::try_from(raw)
420 .map_err(|_| Error::decoding(pos, "floating-limit: time-delay exceeds u32"))?;
421 pos = p;
422 pos = expect_opening(data, pos, 1, what)?;
423 let (setpoint_reference, p) = decode_dopr_body(data, pos, what)?;
424 pos = p;
425 pos = expect_closing(data, pos, 1, what)?;
426 let (low_diff_limit, p) = decode_ctx_real(data, pos, 2, what)?;
427 pos = p;
428 let (high_diff_limit, p) = decode_ctx_real(data, pos, 3, what)?;
429 pos = p;
430 let (deadband, p) = decode_ctx_real(data, pos, 4, what)?;
431 pos = p;
432 pos = expect_closing(data, pos, 4, what)?;
433 (
434 EP::FloatingLimit {
435 time_delay,
436 setpoint_reference,
437 low_diff_limit,
438 high_diff_limit,
439 deadband,
440 },
441 pos,
442 )
443 }
444 5 => {
445 let mut pos = pos;
446 let (raw, p) = decode_ctx_unsigned(data, pos, 0, what)?;
447 let time_delay = u32::try_from(raw)
448 .map_err(|_| Error::decoding(pos, "out-of-range: time-delay exceeds u32"))?;
449 pos = p;
450 let (low_limit, p) = decode_ctx_real(data, pos, 1, what)?;
451 pos = p;
452 let (high_limit, p) = decode_ctx_real(data, pos, 2, what)?;
453 pos = p;
454 let (deadband, p) = decode_ctx_real(data, pos, 3, what)?;
455 pos = p;
456 pos = expect_closing(data, pos, 5, what)?;
457 (
458 EP::OutOfRange {
459 time_delay,
460 low_limit,
461 high_limit,
462 deadband,
463 },
464 pos,
465 )
466 }
467 9 => {
468 let mut pos = pos;
469 let (raw, p) = decode_ctx_unsigned(data, pos, 0, what)?;
470 let vendor_id = u16::try_from(raw)
471 .map_err(|_| Error::decoding(pos, "extended: vendor-id exceeds u16"))?;
472 pos = p;
473 let (raw, p) = decode_ctx_unsigned(data, pos, 1, what)?;
474 let extended_event_type = u32::try_from(raw)
475 .map_err(|_| Error::decoding(pos, "extended: extended-event-type exceeds u32"))?;
476 pos = p;
477 pos = expect_opening(data, pos, 2, what)?;
478 let (raw_params, p) = tags::extract_context_value(data, pos, 2)?;
479 validate_extended_parameters(raw_params, "extended")?;
480 pos = p;
481 pos = expect_closing(data, pos, 9, what)?;
482 (
483 EP::Extended {
484 vendor_id,
485 extended_event_type,
486 parameters: raw_params.to_vec(),
487 },
488 pos,
489 )
490 }
491 n => {
492 let (body, p) = tags::extract_context_value(data, pos, n)?;
495 validate_tlv_sequence(body, "opaque event parameters")?;
496 (
497 EP::Opaque {
498 tag: n,
499 data: body.to_vec(),
500 },
501 p,
502 )
503 }
504 };
505 let (value, end) = value;
506 validate_tlv_sequence(&data[offset..end], what)?;
507 Ok((value, end))
508}