Skip to main content

bacnet_encoding/constructed/event_notification/
parameters_encode.rs

1use super::*;
2use crate::constructed::log_fields::check_bit_string;
3
4/// Encode a context-tagged `BACnetStatusFlags`: four defined bits, so four unused bits, with
5/// `IN_ALARM` in the content octet's top bit.
6fn encode_status_flags(buf: &mut BytesMut, tag: u8, flags: StatusFlags) {
7    primitives::encode_ctx_bit_string(buf, tag, 4, &[flags.bits() << 4]);
8}
9
10/// Encode notification parameters into the buffer.
11///
12/// Each variant is wrapped in its own opening/closing tag pair
13/// matching the variant's context tag number.
14pub fn encode_notification_parameters(
15    params: &NotificationParameters,
16    buf: &mut BytesMut,
17) -> Result<(), Error> {
18    let mut encoded = BytesMut::new();
19    encode_into(params, &mut encoded)?;
20    if matches!(params, NotificationParameters::ComplexEventType { .. }) {
21        let (opening, content_start) =
22            tags::decode_tag(&encoded, 0).map_err(|error| Error::Encoding(error.to_string()))?;
23        if !opening.is_opening_tag(6) {
24            return Err(Error::Encoding(
25                "ComplexEventType missing opening tag [6]".into(),
26            ));
27        }
28        let (_, next) = tags::extract_context_value(&encoded, content_start, 6)
29            .map_err(|error| Error::Encoding(error.to_string()))?;
30        if next != encoded.len() {
31            return Err(Error::Encoding(
32                "ComplexEventType has trailing encoded fields".into(),
33            ));
34        }
35    } else {
36        validate_tlv_sequence(&encoded, "NotificationParameters")
37            .map_err(|error| Error::Encoding(error.to_string()))?;
38    }
39    buf.extend_from_slice(&encoded);
40    Ok(())
41}
42
43fn encode_into(params: &NotificationParameters, buf: &mut BytesMut) -> Result<(), Error> {
44    match params {
45        NotificationParameters::ChangeOfBitstring {
46            referenced_bitstring,
47            status_flags,
48        } => {
49            let (unused_bits, data) = referenced_bitstring;
50            check_bit_string(*unused_bits, data, "ChangeOfBitstring referenced-bitstring")?;
51            tags::encode_opening_tag(buf, 0);
52            primitives::encode_ctx_bit_string(buf, 0, *unused_bits, data);
53            encode_status_flags(buf, 1, *status_flags);
54            tags::encode_closing_tag(buf, 0);
55        }
56        NotificationParameters::ChangeOfState {
57            new_state,
58            status_flags,
59        } => {
60            tags::encode_opening_tag(buf, 1);
61            // [0] new-state: BACnetPropertyStates — wrapped in opening/closing [0]
62            tags::encode_opening_tag(buf, 0);
63            encode_property_states(buf, new_state)?;
64            tags::encode_closing_tag(buf, 0);
65            // [1] status-flags
66            encode_status_flags(buf, 1, *status_flags);
67            tags::encode_closing_tag(buf, 1);
68        }
69        NotificationParameters::ChangeOfValue {
70            new_value,
71            status_flags,
72        } => {
73            tags::encode_opening_tag(buf, 2);
74            // [0] new-value: CHOICE — wrapped in opening/closing [0]
75            tags::encode_opening_tag(buf, 0);
76            match new_value {
77                ChangeOfValueChoice::ChangedBits { unused_bits, data } => {
78                    check_bit_string(*unused_bits, data, "ChangeOfValue changed-bits")?;
79                    primitives::encode_ctx_bit_string(buf, 0, *unused_bits, data);
80                }
81                ChangeOfValueChoice::ChangedValue(v) => {
82                    primitives::encode_ctx_real(buf, 1, *v);
83                }
84            }
85            tags::encode_closing_tag(buf, 0);
86            // [1] status-flags
87            encode_status_flags(buf, 1, *status_flags);
88            tags::encode_closing_tag(buf, 2);
89        }
90        NotificationParameters::CommandFailure {
91            command_value,
92            status_flags,
93            feedback_value,
94        } => {
95            tags::encode_opening_tag(buf, 3);
96            // [0] command-value — abstract syntax, encoded as raw octet string
97            tags::encode_opening_tag(buf, 0);
98            buf.extend_from_slice(command_value);
99            tags::encode_closing_tag(buf, 0);
100            // [1] status-flags
101            encode_status_flags(buf, 1, *status_flags);
102            // [2] feedback-value — abstract syntax, encoded as raw
103            tags::encode_opening_tag(buf, 2);
104            buf.extend_from_slice(feedback_value);
105            tags::encode_closing_tag(buf, 2);
106            tags::encode_closing_tag(buf, 3);
107        }
108        NotificationParameters::FloatingLimit {
109            reference_value,
110            status_flags,
111            setpoint_value,
112            error_limit,
113        } => {
114            tags::encode_opening_tag(buf, 4);
115            primitives::encode_ctx_real(buf, 0, *reference_value);
116            encode_status_flags(buf, 1, *status_flags);
117            primitives::encode_ctx_real(buf, 2, *setpoint_value);
118            primitives::encode_ctx_real(buf, 3, *error_limit);
119            tags::encode_closing_tag(buf, 4);
120        }
121        NotificationParameters::OutOfRange {
122            exceeding_value,
123            status_flags,
124            deadband,
125            exceeded_limit,
126        } => {
127            tags::encode_opening_tag(buf, 5);
128            primitives::encode_ctx_real(buf, 0, *exceeding_value);
129            encode_status_flags(buf, 1, *status_flags);
130            primitives::encode_ctx_real(buf, 2, *deadband);
131            primitives::encode_ctx_real(buf, 3, *exceeded_limit);
132            tags::encode_closing_tag(buf, 5);
133        }
134        NotificationParameters::ComplexEventType { property_values } => {
135            if property_values.len() > MAX_FRAMED_ITEMS {
136                return Err(Error::Encoding(format!(
137                    "ComplexEventType exceeds {MAX_FRAMED_ITEMS} property values"
138                )));
139            }
140            tags::encode_opening_tag(buf, 6);
141            for property_value in property_values {
142                if property_value
143                    .priority
144                    .is_some_and(|priority| !(1..=16).contains(&priority))
145                {
146                    return Err(Error::Encoding(
147                        "ComplexEventType property priority must be in 1..=16".into(),
148                    ));
149                }
150                validate_tlv_sequence(&property_value.value, "ComplexEventType property value")
151                    .map_err(|error| Error::Encoding(error.to_string()))?;
152                encode_bacnet_property_value(property_value, buf);
153            }
154            tags::encode_closing_tag(buf, 6);
155        }
156        NotificationParameters::ChangeOfLifeSafety {
157            new_state,
158            new_mode,
159            status_flags,
160            operation_expected,
161        } => {
162            tags::encode_opening_tag(buf, 8);
163            primitives::encode_ctx_enumerated(buf, 0, new_state.to_raw());
164            primitives::encode_ctx_enumerated(buf, 1, new_mode.to_raw());
165            encode_status_flags(buf, 2, *status_flags);
166            primitives::encode_ctx_enumerated(buf, 3, operation_expected.to_raw());
167            tags::encode_closing_tag(buf, 8);
168        }
169        NotificationParameters::Extended {
170            vendor_id,
171            extended_event_type,
172            parameters,
173        } => {
174            tags::encode_opening_tag(buf, 9);
175            primitives::encode_ctx_unsigned(buf, 0, *vendor_id as u64);
176            primitives::encode_ctx_unsigned(buf, 1, *extended_event_type as u64);
177            // [2] parameters — raw
178            tags::encode_opening_tag(buf, 2);
179            buf.extend_from_slice(parameters);
180            tags::encode_closing_tag(buf, 2);
181            tags::encode_closing_tag(buf, 9);
182        }
183        NotificationParameters::BufferReady {
184            buffer_property,
185            previous_notification,
186            current_notification,
187        } => {
188            tags::encode_opening_tag(buf, 10);
189            // [0] buffer-property: BACnetDeviceObjectPropertyReference
190            tags::encode_opening_tag(buf, 0);
191            primitives::encode_ctx_object_id(buf, 0, &buffer_property.object_identifier);
192            primitives::encode_ctx_unsigned(buf, 1, buffer_property.property_identifier as u64);
193            if let Some(idx) = buffer_property.property_array_index {
194                primitives::encode_ctx_unsigned(buf, 2, idx as u64);
195            }
196            if let Some(ref dev) = buffer_property.device_identifier {
197                primitives::encode_ctx_object_id(buf, 3, dev);
198            }
199            tags::encode_closing_tag(buf, 0);
200            // [1] previous-notification
201            primitives::encode_ctx_unsigned(buf, 1, *previous_notification as u64);
202            // [2] current-notification
203            primitives::encode_ctx_unsigned(buf, 2, *current_notification as u64);
204            tags::encode_closing_tag(buf, 10);
205        }
206        NotificationParameters::UnsignedRange {
207            exceeding_value,
208            status_flags,
209            exceeded_limit,
210        } => {
211            tags::encode_opening_tag(buf, 11);
212            primitives::encode_ctx_unsigned(buf, 0, *exceeding_value);
213            encode_status_flags(buf, 1, *status_flags);
214            primitives::encode_ctx_unsigned(buf, 2, *exceeded_limit);
215            tags::encode_closing_tag(buf, 11);
216        }
217        NotificationParameters::AccessEvent {
218            access_event,
219            status_flags,
220            access_event_tag,
221            access_event_time,
222            access_credential,
223            authentication_factor,
224        } => {
225            if let Some(authentication_factor) = authentication_factor {
226                structured::validate_authentication_factor(authentication_factor)
227                    .map_err(|error| Error::Encoding(error.to_string()))?;
228            }
229            tags::encode_opening_tag(buf, 13);
230            primitives::encode_ctx_enumerated(buf, 0, access_event.to_raw());
231            encode_status_flags(buf, 1, *status_flags);
232            primitives::encode_ctx_unsigned(buf, 2, *access_event_tag as u64);
233            // [3] access-event-time: BACnetTimeStamp (DateTime)
234            primitives::encode_timestamp(
235                buf,
236                3,
237                &BACnetTimeStamp::DateTime {
238                    date: access_event_time.0,
239                    time: access_event_time.1,
240                },
241            )?;
242            // [4] access-credential: BACnetDeviceObjectReference
243            tags::encode_opening_tag(buf, 4);
244            if let Some(ref dev) = access_credential.device_identifier {
245                primitives::encode_ctx_object_id(buf, 0, dev);
246            }
247            primitives::encode_ctx_object_id(buf, 1, &access_credential.object_identifier);
248            tags::encode_closing_tag(buf, 4);
249            if let Some(authentication_factor) = authentication_factor {
250                // [5] authentication-factor — raw, optional
251                tags::encode_opening_tag(buf, 5);
252                buf.extend_from_slice(authentication_factor);
253                tags::encode_closing_tag(buf, 5);
254            }
255            tags::encode_closing_tag(buf, 13);
256        }
257        NotificationParameters::DoubleOutOfRange {
258            exceeding_value,
259            status_flags,
260            deadband,
261            exceeded_limit,
262        } => {
263            tags::encode_opening_tag(buf, 14);
264            primitives::encode_ctx_double(buf, 0, *exceeding_value);
265            encode_status_flags(buf, 1, *status_flags);
266            primitives::encode_ctx_double(buf, 2, *deadband);
267            primitives::encode_ctx_double(buf, 3, *exceeded_limit);
268            tags::encode_closing_tag(buf, 14);
269        }
270        NotificationParameters::SignedOutOfRange {
271            exceeding_value,
272            status_flags,
273            deadband,
274            exceeded_limit,
275        } => {
276            tags::encode_opening_tag(buf, 15);
277            primitives::encode_ctx_signed(buf, 0, *exceeding_value);
278            encode_status_flags(buf, 1, *status_flags);
279            primitives::encode_ctx_unsigned(buf, 2, *deadband);
280            primitives::encode_ctx_signed(buf, 3, *exceeded_limit);
281            tags::encode_closing_tag(buf, 15);
282        }
283        NotificationParameters::UnsignedOutOfRange {
284            exceeding_value,
285            status_flags,
286            deadband,
287            exceeded_limit,
288        } => {
289            tags::encode_opening_tag(buf, 16);
290            primitives::encode_ctx_unsigned(buf, 0, *exceeding_value);
291            encode_status_flags(buf, 1, *status_flags);
292            primitives::encode_ctx_unsigned(buf, 2, *deadband);
293            primitives::encode_ctx_unsigned(buf, 3, *exceeded_limit);
294            tags::encode_closing_tag(buf, 16);
295        }
296        NotificationParameters::ChangeOfCharacterstring {
297            changed_value,
298            status_flags,
299            alarm_value,
300        } => {
301            tags::encode_opening_tag(buf, 17);
302            primitives::encode_ctx_character_string(buf, 0, changed_value)?;
303            encode_status_flags(buf, 1, *status_flags);
304            primitives::encode_ctx_character_string(buf, 2, alarm_value)?;
305            tags::encode_closing_tag(buf, 17);
306        }
307        NotificationParameters::ChangeOfStatusFlags {
308            present_value,
309            referenced_flags,
310        } => {
311            tags::encode_opening_tag(buf, 18);
312            if let Some(present_value) = present_value {
313                // [0] present-value — abstract syntax, raw, optional
314                tags::encode_opening_tag(buf, 0);
315                buf.extend_from_slice(present_value);
316                tags::encode_closing_tag(buf, 0);
317            }
318            // [1] referenced-flags
319            encode_status_flags(buf, 1, *referenced_flags);
320            tags::encode_closing_tag(buf, 18);
321        }
322        NotificationParameters::ChangeOfReliability {
323            reliability,
324            status_flags,
325            property_values,
326        } => {
327            tags::encode_opening_tag(buf, 19);
328            primitives::encode_ctx_enumerated(buf, 0, reliability.to_raw());
329            encode_status_flags(buf, 1, *status_flags);
330            // [2] property-values — abstract syntax, raw
331            tags::encode_opening_tag(buf, 2);
332            buf.extend_from_slice(property_values);
333            tags::encode_closing_tag(buf, 2);
334            tags::encode_closing_tag(buf, 19);
335        }
336        NotificationParameters::ChangeOfDiscreteValue {
337            new_value,
338            status_flags,
339        } => {
340            tags::encode_opening_tag(buf, 21);
341            // [0] new-value — abstract syntax, raw
342            tags::encode_opening_tag(buf, 0);
343            buf.extend_from_slice(new_value);
344            tags::encode_closing_tag(buf, 0);
345            // [1] status-flags
346            encode_status_flags(buf, 1, *status_flags);
347            tags::encode_closing_tag(buf, 21);
348        }
349        NotificationParameters::ChangeOfTimer {
350            new_state,
351            status_flags,
352            update_time,
353            last_state_change,
354            initial_timeout,
355            expiration_time,
356        } => {
357            tags::encode_opening_tag(buf, 22);
358            primitives::encode_ctx_enumerated(buf, 0, new_state.to_raw());
359            encode_status_flags(buf, 1, *status_flags);
360            // [2] update-time: BACnetDateTime
361            tags::encode_opening_tag(buf, 2);
362            primitives::encode_app_date(buf, &update_time.0);
363            primitives::encode_app_time(buf, &update_time.1);
364            tags::encode_closing_tag(buf, 2);
365            if let Some(last_state_change) = last_state_change {
366                primitives::encode_ctx_enumerated(buf, 3, last_state_change.to_raw());
367            }
368            if let Some(initial_timeout) = initial_timeout {
369                primitives::encode_ctx_unsigned(buf, 4, *initial_timeout as u64);
370            }
371            if let Some(expiration_time) = expiration_time {
372                tags::encode_opening_tag(buf, 5);
373                primitives::encode_app_date(buf, &expiration_time.0);
374                primitives::encode_app_time(buf, &expiration_time.1);
375                tags::encode_closing_tag(buf, 5);
376            }
377            tags::encode_closing_tag(buf, 22);
378        }
379    }
380    Ok(())
381}