#[cfg(not(feature = "std"))]
use alloc::{string::ToString, vec::Vec};
use bacnet_types::constructed::FaultParameters;
use bacnet_types::enums::LifeSafetyState;
use bacnet_types::error::Error;
use bytes::BytesMut;
use crate::primitives;
use crate::tags::{self, TagClass};
use super::tagged::{
contents, decode_app_character_string, decode_app_enumerated, decode_ctx_constructed,
decode_ctx_unsigned, expect_closing, expect_opening, next_is_closing,
};
use super::{
decode_dopr_body, decode_property_state, encode_dopr_body, encode_property_state,
validate_extended_parameters, MAX_FRAMED_ITEMS,
};
pub fn encode_fault_parameters(buf: &mut BytesMut, value: &FaultParameters) -> Result<(), Error> {
let mut encoded = BytesMut::new();
encode_fault_parameters_into(&mut encoded, value)?;
super::validate_tlv_sequence(&encoded, "FaultParameters")
.map_err(|error| Error::Encoding(error.to_string()))?;
buf.extend_from_slice(&encoded);
Ok(())
}
fn encode_fault_parameters_into(buf: &mut BytesMut, value: &FaultParameters) -> Result<(), Error> {
use FaultParameters as F;
match value {
F::FaultNone => {
tags::encode_tag(buf, 0, TagClass::Context, 0);
}
F::FaultCharacterString { fault_values } => {
tags::encode_opening_tag(buf, 1);
tags::encode_opening_tag(buf, 0);
for s in fault_values {
primitives::encode_app_character_string(buf, s)?;
}
tags::encode_closing_tag(buf, 0);
tags::encode_closing_tag(buf, 1);
}
F::FaultExtended {
vendor_id,
extended_fault_type,
parameters,
} => {
validate_extended_parameters(parameters, "fault-extended")
.map_err(|error| Error::Encoding(error.to_string()))?;
tags::encode_opening_tag(buf, 2);
primitives::encode_ctx_unsigned(buf, 0, *vendor_id as u64);
primitives::encode_ctx_unsigned(buf, 1, *extended_fault_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, 2);
}
F::FaultLifeSafety {
fault_values,
mode_property_reference,
} => {
tags::encode_opening_tag(buf, 3);
tags::encode_opening_tag(buf, 0);
for v in fault_values {
primitives::encode_app_enumerated(buf, v.to_raw());
}
tags::encode_closing_tag(buf, 0);
tags::encode_opening_tag(buf, 1);
encode_dopr_body(buf, mode_property_reference);
tags::encode_closing_tag(buf, 1);
tags::encode_closing_tag(buf, 3);
}
F::FaultState { fault_values } => {
let mut encoded_states = BytesMut::new();
for state in fault_values {
encode_property_state(&mut encoded_states, state)?;
}
tags::encode_opening_tag(buf, 4);
tags::encode_opening_tag(buf, 0);
buf.extend_from_slice(&encoded_states);
tags::encode_closing_tag(buf, 0);
tags::encode_closing_tag(buf, 4);
}
F::FaultStatusFlags { reference } => {
tags::encode_opening_tag(buf, 5);
tags::encode_opening_tag(buf, 0);
encode_dopr_body(buf, reference);
tags::encode_closing_tag(buf, 0);
tags::encode_closing_tag(buf, 5);
}
F::FaultOutOfRange {
min_normal,
max_normal,
} => {
tags::encode_opening_tag(buf, 6);
tags::encode_opening_tag(buf, 0);
primitives::encode_app_double(buf, *min_normal);
tags::encode_closing_tag(buf, 0);
tags::encode_opening_tag(buf, 1);
primitives::encode_app_double(buf, *max_normal);
tags::encode_closing_tag(buf, 1);
tags::encode_closing_tag(buf, 6);
}
F::FaultListed { reference } => {
tags::encode_opening_tag(buf, 7);
tags::encode_opening_tag(buf, 0);
encode_dopr_body(buf, reference);
tags::encode_closing_tag(buf, 0);
tags::encode_closing_tag(buf, 7);
}
}
Ok(())
}
fn decode_fault_normal_value(data: &[u8], offset: usize) -> Result<(f64, usize), Error> {
let (t, pos) = tags::decode_tag(data, offset)?;
if t.class != TagClass::Application || t.is_opening || t.is_closing {
return Err(Error::decoding(
offset,
"fault-out-of-range: min/max-normal-value must be application-tagged",
));
}
let what = "fault-out-of-range min/max-normal-value";
if t.number == tags::app_tag::BOOLEAN {
return Err(Error::decoding(
offset,
format!("{what}: BOOLEAN is not a valid alternative"),
));
}
let (content, end) = contents(data, pos, t.length)?;
let value = match t.number {
tags::app_tag::REAL => primitives::decode_real(content)? as f64,
tags::app_tag::UNSIGNED => primitives::decode_unsigned(content)? as f64,
tags::app_tag::DOUBLE => primitives::decode_double(content)?,
tags::app_tag::SIGNED => primitives::decode_signed(content)? as f64,
other => {
return Err(Error::decoding(
offset,
format!("{what}: unexpected application tag {other}"),
))
}
};
Ok((value, end))
}
pub fn decode_fault_parameters(
data: &[u8],
offset: usize,
) -> Result<(FaultParameters, usize), Error> {
use FaultParameters as F;
let (tag, pos) = tags::decode_tag(data, offset)?;
let what = "BACnetFaultParameter";
if tag.is_context(0) {
if tag.length != 0 {
return Err(Error::decoding(
offset,
"BACnetFaultParameter: none [0] NULL must have no contents",
));
}
return Ok((F::FaultNone, pos));
}
if !tag.is_opening {
return Err(Error::decoding(
offset,
"BACnetFaultParameter: expected an opening context tag or [0] NULL",
));
}
let value = match tag.number {
1 => {
let mut pos = expect_opening(data, pos, 0, what)?;
let mut fault_values = Vec::new();
while !next_is_closing(data, pos, 0)? {
if fault_values.len() >= MAX_FRAMED_ITEMS {
return Err(Error::overflow(
pos,
"fault-characterstring: list-of-fault-values exceeds limit",
));
}
let (s, p) = decode_app_character_string(data, pos, what)?;
fault_values.push(s);
pos = p;
}
pos = expect_closing(data, pos, 0, what)?;
pos = expect_closing(data, pos, 1, what)?;
(F::FaultCharacterString { fault_values }, pos)
}
2 => {
let what = "BACnetFaultParameter fault-extended";
let (vendor_id, pos) = decode_ctx_unsigned::<u16>(data, pos, 0, what)?;
let (extended_fault_type, pos) = decode_ctx_unsigned::<u32>(data, pos, 1, what)?;
let (raw_params, pos) = decode_ctx_constructed(data, pos, 2, what)?;
validate_extended_parameters(raw_params, "fault-extended")?;
let pos = expect_closing(data, pos, 2, what)?;
(
F::FaultExtended {
vendor_id,
extended_fault_type,
parameters: raw_params.to_vec(),
},
pos,
)
}
3 => {
let mut pos = expect_opening(data, pos, 0, what)?;
let mut fault_values = Vec::new();
while !next_is_closing(data, pos, 0)? {
if fault_values.len() >= MAX_FRAMED_ITEMS {
return Err(Error::overflow(
pos,
"fault-life-safety: list-of-fault-values exceeds limit",
));
}
let (v, p) = decode_app_enumerated::<u32>(data, pos, what)?;
fault_values.push(LifeSafetyState::from_raw(v));
pos = p;
}
pos = expect_closing(data, pos, 0, what)?;
pos = expect_opening(data, pos, 1, what)?;
let (mode_property_reference, p) = decode_dopr_body(data, pos, what)?;
pos = p;
pos = expect_closing(data, pos, 1, what)?;
pos = expect_closing(data, pos, 3, what)?;
(
F::FaultLifeSafety {
fault_values,
mode_property_reference,
},
pos,
)
}
4 => {
let mut pos = expect_opening(data, pos, 0, what)?;
let mut fault_values = Vec::new();
while !next_is_closing(data, pos, 0)? {
if fault_values.len() >= MAX_FRAMED_ITEMS {
return Err(Error::overflow(
pos,
"fault-state: list-of-fault-values exceeds limit",
));
}
let (state, p) = decode_property_state(data, pos)?;
fault_values.push(state);
pos = p;
}
pos = expect_closing(data, pos, 0, what)?;
pos = expect_closing(data, pos, 4, what)?;
(F::FaultState { fault_values }, pos)
}
5 => {
let mut pos = expect_opening(data, pos, 0, what)?;
let (reference, p) = decode_dopr_body(data, pos, what)?;
pos = p;
pos = expect_closing(data, pos, 0, what)?;
pos = expect_closing(data, pos, 5, what)?;
(F::FaultStatusFlags { reference }, pos)
}
6 => {
let mut pos = expect_opening(data, pos, 0, what)?;
let (min_normal, p) = decode_fault_normal_value(data, pos)?;
pos = p;
pos = expect_closing(data, pos, 0, what)?;
pos = expect_opening(data, pos, 1, what)?;
let (max_normal, p) = decode_fault_normal_value(data, pos)?;
pos = p;
pos = expect_closing(data, pos, 1, what)?;
pos = expect_closing(data, pos, 6, what)?;
(
F::FaultOutOfRange {
min_normal,
max_normal,
},
pos,
)
}
7 => {
let mut pos = expect_opening(data, pos, 0, what)?;
let (reference, p) = decode_dopr_body(data, pos, what)?;
pos = p;
pos = expect_closing(data, pos, 0, what)?;
pos = expect_closing(data, pos, 7, what)?;
(F::FaultListed { reference }, pos)
}
n => {
return Err(Error::decoding(
offset,
format!("BACnetFaultParameter: unknown CHOICE tag [{n}]"),
));
}
};
let (value, end) = value;
super::validate_tlv_sequence(&data[offset..end], what)?;
Ok((value, end))
}