Skip to main content

bacnet_encoding/constructed/event_notification/
parameters_encode.rs

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