#[cfg(not(feature = "std"))]
use alloc::string::ToString;
use super::*;
use crate::constructed::log_fields::check_bit_string;
fn encode_status_flags(buf: &mut BytesMut, tag: u8, flags: StatusFlags) {
primitives::encode_ctx_bit_string(buf, tag, 4, &[flags.bits() << 4]);
}
pub fn encode_notification_parameters(
params: &NotificationParameters,
buf: &mut BytesMut,
) -> Result<(), Error> {
let mut encoded = BytesMut::new();
encode_into(params, &mut encoded)?;
if matches!(params, NotificationParameters::ComplexEventType { .. }) {
let (opening, content_start) =
tags::decode_tag(&encoded, 0).map_err(|error| Error::Encoding(error.to_string()))?;
if !opening.is_opening_tag(6) {
return Err(Error::Encoding(
"ComplexEventType missing opening tag [6]".into(),
));
}
let (_, next) = tags::extract_context_value(&encoded, content_start, 6)
.map_err(|error| Error::Encoding(error.to_string()))?;
if next != encoded.len() {
return Err(Error::Encoding(
"ComplexEventType has trailing encoded fields".into(),
));
}
} else {
validate_tlv_sequence(&encoded, "NotificationParameters")
.map_err(|error| Error::Encoding(error.to_string()))?;
}
buf.extend_from_slice(&encoded);
Ok(())
}
fn encode_into(params: &NotificationParameters, buf: &mut BytesMut) -> Result<(), Error> {
match params {
NotificationParameters::ChangeOfBitstring {
referenced_bitstring,
status_flags,
} => {
let (unused_bits, data) = referenced_bitstring;
check_bit_string(*unused_bits, data, "ChangeOfBitstring referenced-bitstring")?;
tags::encode_opening_tag(buf, 0);
primitives::encode_ctx_bit_string(buf, 0, *unused_bits, data);
encode_status_flags(buf, 1, *status_flags);
tags::encode_closing_tag(buf, 0);
}
NotificationParameters::ChangeOfState {
new_state,
status_flags,
} => {
tags::encode_opening_tag(buf, 1);
tags::encode_opening_tag(buf, 0);
encode_property_states(buf, new_state)?;
tags::encode_closing_tag(buf, 0);
encode_status_flags(buf, 1, *status_flags);
tags::encode_closing_tag(buf, 1);
}
NotificationParameters::ChangeOfValue {
new_value,
status_flags,
} => {
tags::encode_opening_tag(buf, 2);
tags::encode_opening_tag(buf, 0);
match new_value {
ChangeOfValueChoice::ChangedBits { unused_bits, data } => {
check_bit_string(*unused_bits, data, "ChangeOfValue changed-bits")?;
primitives::encode_ctx_bit_string(buf, 0, *unused_bits, data);
}
ChangeOfValueChoice::ChangedValue(v) => {
primitives::encode_ctx_real(buf, 1, *v);
}
}
tags::encode_closing_tag(buf, 0);
encode_status_flags(buf, 1, *status_flags);
tags::encode_closing_tag(buf, 2);
}
NotificationParameters::CommandFailure {
command_value,
status_flags,
feedback_value,
} => {
tags::encode_opening_tag(buf, 3);
tags::encode_opening_tag(buf, 0);
buf.extend_from_slice(command_value);
tags::encode_closing_tag(buf, 0);
encode_status_flags(buf, 1, *status_flags);
tags::encode_opening_tag(buf, 2);
buf.extend_from_slice(feedback_value);
tags::encode_closing_tag(buf, 2);
tags::encode_closing_tag(buf, 3);
}
NotificationParameters::FloatingLimit {
reference_value,
status_flags,
setpoint_value,
error_limit,
} => {
tags::encode_opening_tag(buf, 4);
primitives::encode_ctx_real(buf, 0, *reference_value);
encode_status_flags(buf, 1, *status_flags);
primitives::encode_ctx_real(buf, 2, *setpoint_value);
primitives::encode_ctx_real(buf, 3, *error_limit);
tags::encode_closing_tag(buf, 4);
}
NotificationParameters::OutOfRange {
exceeding_value,
status_flags,
deadband,
exceeded_limit,
} => {
tags::encode_opening_tag(buf, 5);
primitives::encode_ctx_real(buf, 0, *exceeding_value);
encode_status_flags(buf, 1, *status_flags);
primitives::encode_ctx_real(buf, 2, *deadband);
primitives::encode_ctx_real(buf, 3, *exceeded_limit);
tags::encode_closing_tag(buf, 5);
}
NotificationParameters::ComplexEventType { property_values } => {
if property_values.len() > MAX_FRAMED_ITEMS {
return Err(Error::Encoding(format!(
"ComplexEventType exceeds {MAX_FRAMED_ITEMS} property values"
)));
}
tags::encode_opening_tag(buf, 6);
for property_value in property_values {
if property_value
.priority
.is_some_and(|priority| !(1..=16).contains(&priority))
{
return Err(Error::Encoding(
"ComplexEventType property priority must be in 1..=16".into(),
));
}
validate_tlv_sequence(&property_value.value, "ComplexEventType property value")
.map_err(|error| Error::Encoding(error.to_string()))?;
encode_bacnet_property_value(property_value, buf);
}
tags::encode_closing_tag(buf, 6);
}
NotificationParameters::ChangeOfLifeSafety {
new_state,
new_mode,
status_flags,
operation_expected,
} => {
tags::encode_opening_tag(buf, 8);
primitives::encode_ctx_enumerated(buf, 0, new_state.to_raw());
primitives::encode_ctx_enumerated(buf, 1, new_mode.to_raw());
encode_status_flags(buf, 2, *status_flags);
primitives::encode_ctx_enumerated(buf, 3, operation_expected.to_raw());
tags::encode_closing_tag(buf, 8);
}
NotificationParameters::Extended {
vendor_id,
extended_event_type,
parameters,
} => {
tags::encode_opening_tag(buf, 9);
primitives::encode_ctx_unsigned(buf, 0, *vendor_id as u64);
primitives::encode_ctx_unsigned(buf, 1, *extended_event_type as u64);
tags::encode_opening_tag(buf, 2);
buf.extend_from_slice(parameters);
tags::encode_closing_tag(buf, 2);
tags::encode_closing_tag(buf, 9);
}
NotificationParameters::BufferReady {
buffer_property,
previous_notification,
current_notification,
} => {
tags::encode_opening_tag(buf, 10);
tags::encode_opening_tag(buf, 0);
primitives::encode_ctx_object_id(buf, 0, &buffer_property.object_identifier);
primitives::encode_ctx_unsigned(buf, 1, buffer_property.property_identifier as u64);
if let Some(idx) = buffer_property.property_array_index {
primitives::encode_ctx_unsigned(buf, 2, idx as u64);
}
if let Some(ref dev) = buffer_property.device_identifier {
primitives::encode_ctx_object_id(buf, 3, dev);
}
tags::encode_closing_tag(buf, 0);
primitives::encode_ctx_unsigned(buf, 1, *previous_notification as u64);
primitives::encode_ctx_unsigned(buf, 2, *current_notification as u64);
tags::encode_closing_tag(buf, 10);
}
NotificationParameters::UnsignedRange {
exceeding_value,
status_flags,
exceeded_limit,
} => {
tags::encode_opening_tag(buf, 11);
primitives::encode_ctx_unsigned(buf, 0, *exceeding_value);
encode_status_flags(buf, 1, *status_flags);
primitives::encode_ctx_unsigned(buf, 2, *exceeded_limit);
tags::encode_closing_tag(buf, 11);
}
NotificationParameters::AccessEvent {
access_event,
status_flags,
access_event_tag,
access_event_time,
access_credential,
authentication_factor,
} => {
if let Some(authentication_factor) = authentication_factor {
structured::validate_authentication_factor(authentication_factor)
.map_err(|error| Error::Encoding(error.to_string()))?;
}
tags::encode_opening_tag(buf, 13);
primitives::encode_ctx_enumerated(buf, 0, access_event.to_raw());
encode_status_flags(buf, 1, *status_flags);
primitives::encode_ctx_unsigned(buf, 2, *access_event_tag as u64);
primitives::encode_timestamp(
buf,
3,
&BACnetTimeStamp::DateTime {
date: access_event_time.0,
time: access_event_time.1,
},
)?;
tags::encode_opening_tag(buf, 4);
if let Some(ref dev) = access_credential.device_identifier {
primitives::encode_ctx_object_id(buf, 0, dev);
}
primitives::encode_ctx_object_id(buf, 1, &access_credential.object_identifier);
tags::encode_closing_tag(buf, 4);
if let Some(authentication_factor) = authentication_factor {
tags::encode_opening_tag(buf, 5);
buf.extend_from_slice(authentication_factor);
tags::encode_closing_tag(buf, 5);
}
tags::encode_closing_tag(buf, 13);
}
NotificationParameters::DoubleOutOfRange {
exceeding_value,
status_flags,
deadband,
exceeded_limit,
} => {
tags::encode_opening_tag(buf, 14);
primitives::encode_ctx_double(buf, 0, *exceeding_value);
encode_status_flags(buf, 1, *status_flags);
primitives::encode_ctx_double(buf, 2, *deadband);
primitives::encode_ctx_double(buf, 3, *exceeded_limit);
tags::encode_closing_tag(buf, 14);
}
NotificationParameters::SignedOutOfRange {
exceeding_value,
status_flags,
deadband,
exceeded_limit,
} => {
tags::encode_opening_tag(buf, 15);
primitives::encode_ctx_signed(buf, 0, *exceeding_value);
encode_status_flags(buf, 1, *status_flags);
primitives::encode_ctx_unsigned(buf, 2, *deadband);
primitives::encode_ctx_signed(buf, 3, *exceeded_limit);
tags::encode_closing_tag(buf, 15);
}
NotificationParameters::UnsignedOutOfRange {
exceeding_value,
status_flags,
deadband,
exceeded_limit,
} => {
tags::encode_opening_tag(buf, 16);
primitives::encode_ctx_unsigned(buf, 0, *exceeding_value);
encode_status_flags(buf, 1, *status_flags);
primitives::encode_ctx_unsigned(buf, 2, *deadband);
primitives::encode_ctx_unsigned(buf, 3, *exceeded_limit);
tags::encode_closing_tag(buf, 16);
}
NotificationParameters::ChangeOfCharacterstring {
changed_value,
status_flags,
alarm_value,
} => {
tags::encode_opening_tag(buf, 17);
primitives::encode_ctx_character_string(buf, 0, changed_value)?;
encode_status_flags(buf, 1, *status_flags);
primitives::encode_ctx_character_string(buf, 2, alarm_value)?;
tags::encode_closing_tag(buf, 17);
}
NotificationParameters::ChangeOfStatusFlags {
present_value,
referenced_flags,
} => {
tags::encode_opening_tag(buf, 18);
if let Some(present_value) = present_value {
tags::encode_opening_tag(buf, 0);
buf.extend_from_slice(present_value);
tags::encode_closing_tag(buf, 0);
}
encode_status_flags(buf, 1, *referenced_flags);
tags::encode_closing_tag(buf, 18);
}
NotificationParameters::ChangeOfReliability {
reliability,
status_flags,
property_values,
} => {
tags::encode_opening_tag(buf, 19);
primitives::encode_ctx_enumerated(buf, 0, reliability.to_raw());
encode_status_flags(buf, 1, *status_flags);
tags::encode_opening_tag(buf, 2);
buf.extend_from_slice(property_values);
tags::encode_closing_tag(buf, 2);
tags::encode_closing_tag(buf, 19);
}
NotificationParameters::ChangeOfDiscreteValue {
new_value,
status_flags,
} => {
tags::encode_opening_tag(buf, 21);
tags::encode_opening_tag(buf, 0);
buf.extend_from_slice(new_value);
tags::encode_closing_tag(buf, 0);
encode_status_flags(buf, 1, *status_flags);
tags::encode_closing_tag(buf, 21);
}
NotificationParameters::ChangeOfTimer {
new_state,
status_flags,
update_time,
last_state_change,
initial_timeout,
expiration_time,
} => {
tags::encode_opening_tag(buf, 22);
primitives::encode_ctx_enumerated(buf, 0, new_state.to_raw());
encode_status_flags(buf, 1, *status_flags);
tags::encode_opening_tag(buf, 2);
primitives::encode_app_date(buf, &update_time.0);
primitives::encode_app_time(buf, &update_time.1);
tags::encode_closing_tag(buf, 2);
if let Some(last_state_change) = last_state_change {
primitives::encode_ctx_enumerated(buf, 3, last_state_change.to_raw());
}
if let Some(initial_timeout) = initial_timeout {
primitives::encode_ctx_unsigned(buf, 4, *initial_timeout as u64);
}
if let Some(expiration_time) = expiration_time {
tags::encode_opening_tag(buf, 5);
primitives::encode_app_date(buf, &expiration_time.0);
primitives::encode_app_time(buf, &expiration_time.1);
tags::encode_closing_tag(buf, 5);
}
tags::encode_closing_tag(buf, 22);
}
}
Ok(())
}