1#[cfg(not(feature = "std"))]
2use alloc::string::ToString;
3
4use super::*;
5use crate::constructed::log_fields::check_bit_string;
6
7fn encode_status_flags(buf: &mut BytesMut, tag: u8, flags: StatusFlags) {
10 primitives::encode_ctx_bit_string(buf, tag, 4, &[flags.bits() << 4]);
11}
12
13pub 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 tags::encode_opening_tag(buf, 0);
66 encode_property_states(buf, new_state)?;
67 tags::encode_closing_tag(buf, 0);
68 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 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 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 tags::encode_opening_tag(buf, 0);
101 buf.extend_from_slice(command_value);
102 tags::encode_closing_tag(buf, 0);
103 encode_status_flags(buf, 1, *status_flags);
105 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 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 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 primitives::encode_ctx_unsigned(buf, 1, *previous_notification as u64);
205 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 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 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 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 tags::encode_opening_tag(buf, 0);
318 buf.extend_from_slice(present_value);
319 tags::encode_closing_tag(buf, 0);
320 }
321 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 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 tags::encode_opening_tag(buf, 0);
346 buf.extend_from_slice(new_value);
347 tags::encode_closing_tag(buf, 0);
348 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 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}