Skip to main content

bacnet_encoding/constructed/event_notification/
parameters_encode.rs

1#[cfg(not(feature = "std"))]
2use alloc::string::ToString;
3
4use super::*;
5use crate::constructed::log_fields::check_bit_string;
6
7/// Encode a context-tagged `BACnetStatusFlags`: four defined bits, so four unused bits, with
8/// `IN_ALARM` in the content octet's top bit.
9fn encode_status_flags(buf: &mut BytesMut, tag: u8, flags: StatusFlags) {
10    primitives::encode_ctx_bit_string(buf, tag, 4, &[flags.bits() << 4]);
11}
12
13/// Encode notification parameters into the buffer.
14///
15/// Each variant is wrapped in its own opening/closing tag pair
16/// matching the variant's context tag number.
17pub fn encode_notification_parameters(
18    params: &NotificationParameters,
19    buf: &mut BytesMut,
20) -> Result<(), Error> {
21    let mut encoded = BytesMut::new();
22    encode_into(params, &mut encoded)?;
23    if matches!(params, NotificationParameters::ComplexEventType { .. }) {
24        let (opening, content_start) =
25            tags::decode_tag(&encoded, 0).map_err(|error| Error::Encoding(error.to_string()))?;
26        if !opening.is_opening_tag(6) {
27            return Err(Error::Encoding(
28                "ComplexEventType missing opening tag [6]".into(),
29            ));
30        }
31        let (_, next) = tags::extract_context_value(&encoded, content_start, 6)
32            .map_err(|error| Error::Encoding(error.to_string()))?;
33        if next != encoded.len() {
34            return Err(Error::Encoding(
35                "ComplexEventType has trailing encoded fields".into(),
36            ));
37        }
38    } else {
39        validate_tlv_sequence(&encoded, "NotificationParameters")
40            .map_err(|error| Error::Encoding(error.to_string()))?;
41    }
42    buf.extend_from_slice(&encoded);
43    Ok(())
44}
45
46fn encode_into(params: &NotificationParameters, buf: &mut BytesMut) -> Result<(), Error> {
47    match params {
48        NotificationParameters::ChangeOfBitstring {
49            referenced_bitstring,
50            status_flags,
51        } => {
52            let (unused_bits, data) = referenced_bitstring;
53            check_bit_string(*unused_bits, data, "ChangeOfBitstring referenced-bitstring")?;
54            tags::encode_opening_tag(buf, 0);
55            primitives::encode_ctx_bit_string(buf, 0, *unused_bits, data);
56            encode_status_flags(buf, 1, *status_flags);
57            tags::encode_closing_tag(buf, 0);
58        }
59        NotificationParameters::ChangeOfState {
60            new_state,
61            status_flags,
62        } => {
63            tags::encode_opening_tag(buf, 1);
64            // [0] new-state: BACnetPropertyStates — wrapped in opening/closing [0]
65            tags::encode_opening_tag(buf, 0);
66            encode_property_states(buf, new_state)?;
67            tags::encode_closing_tag(buf, 0);
68            // [1] status-flags
69            encode_status_flags(buf, 1, *status_flags);
70            tags::encode_closing_tag(buf, 1);
71        }
72        NotificationParameters::ChangeOfValue {
73            new_value,
74            status_flags,
75        } => {
76            tags::encode_opening_tag(buf, 2);
77            // [0] new-value: CHOICE — wrapped in opening/closing [0]
78            tags::encode_opening_tag(buf, 0);
79            match new_value {
80                ChangeOfValueChoice::ChangedBits { unused_bits, data } => {
81                    check_bit_string(*unused_bits, data, "ChangeOfValue changed-bits")?;
82                    primitives::encode_ctx_bit_string(buf, 0, *unused_bits, data);
83                }
84                ChangeOfValueChoice::ChangedValue(v) => {
85                    primitives::encode_ctx_real(buf, 1, *v);
86                }
87            }
88            tags::encode_closing_tag(buf, 0);
89            // [1] status-flags
90            encode_status_flags(buf, 1, *status_flags);
91            tags::encode_closing_tag(buf, 2);
92        }
93        NotificationParameters::CommandFailure {
94            command_value,
95            status_flags,
96            feedback_value,
97        } => {
98            tags::encode_opening_tag(buf, 3);
99            // [0] command-value — abstract syntax, encoded as raw octet string
100            tags::encode_opening_tag(buf, 0);
101            buf.extend_from_slice(command_value);
102            tags::encode_closing_tag(buf, 0);
103            // [1] status-flags
104            encode_status_flags(buf, 1, *status_flags);
105            // [2] feedback-value — abstract syntax, encoded as raw
106            tags::encode_opening_tag(buf, 2);
107            buf.extend_from_slice(feedback_value);
108            tags::encode_closing_tag(buf, 2);
109            tags::encode_closing_tag(buf, 3);
110        }
111        NotificationParameters::FloatingLimit {
112            reference_value,
113            status_flags,
114            setpoint_value,
115            error_limit,
116        } => {
117            tags::encode_opening_tag(buf, 4);
118            primitives::encode_ctx_real(buf, 0, *reference_value);
119            encode_status_flags(buf, 1, *status_flags);
120            primitives::encode_ctx_real(buf, 2, *setpoint_value);
121            primitives::encode_ctx_real(buf, 3, *error_limit);
122            tags::encode_closing_tag(buf, 4);
123        }
124        NotificationParameters::OutOfRange {
125            exceeding_value,
126            status_flags,
127            deadband,
128            exceeded_limit,
129        } => {
130            tags::encode_opening_tag(buf, 5);
131            primitives::encode_ctx_real(buf, 0, *exceeding_value);
132            encode_status_flags(buf, 1, *status_flags);
133            primitives::encode_ctx_real(buf, 2, *deadband);
134            primitives::encode_ctx_real(buf, 3, *exceeded_limit);
135            tags::encode_closing_tag(buf, 5);
136        }
137        NotificationParameters::ComplexEventType { property_values } => {
138            if property_values.len() > MAX_FRAMED_ITEMS {
139                return Err(Error::Encoding(format!(
140                    "ComplexEventType exceeds {MAX_FRAMED_ITEMS} property values"
141                )));
142            }
143            tags::encode_opening_tag(buf, 6);
144            for property_value in property_values {
145                if property_value
146                    .priority
147                    .is_some_and(|priority| !(1..=16).contains(&priority))
148                {
149                    return Err(Error::Encoding(
150                        "ComplexEventType property priority must be in 1..=16".into(),
151                    ));
152                }
153                validate_tlv_sequence(&property_value.value, "ComplexEventType property value")
154                    .map_err(|error| Error::Encoding(error.to_string()))?;
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| Error::Encoding(error.to_string()))?;
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}