use bacnet_types::enums::{ConfirmedServiceChoice, ErrorClass, ErrorCode};
use bacnet_types::error::Error;
use crate::constructed::tagged::{
decode_app_enumerated, decode_app_unsigned, decode_ctx_constructed, decode_ctx_object_id,
decode_ctx_unsigned, expect_end,
};
use crate::constructed::{decode_object_property_reference, decode_property_reference};
use crate::tags;
type Members = fn(&[u8], usize, &str) -> Result<usize, Error>;
pub(super) fn decode_formal_body(
service: ConfirmedServiceChoice,
data: &[u8],
) -> Result<Option<(ErrorClass, ErrorCode)>, Error> {
let (what, members): (&str, Members) = match service {
s if s == ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE => {
("formal WPM Error", first_failed_write_attempt)
}
s if s == ConfirmedServiceChoice::ADD_LIST_ELEMENT
|| s == ConfirmedServiceChoice::REMOVE_LIST_ELEMENT =>
{
("ChangeList-Error", first_failed_element_number)
}
s if s == ConfirmedServiceChoice::CREATE_OBJECT => {
("CreateObject-Error", first_failed_element_number)
}
s if s == ConfirmedServiceChoice::CONFIRMED_PRIVATE_TRANSFER => {
("ConfirmedPrivateTransfer-Error", private_transfer_members)
}
s if s == ConfirmedServiceChoice::VT_CLOSE => ("VTClose-Error", vt_session_identifiers),
s if s == ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY_MULTIPLE => {
return subscribe_cov_property_multiple(data);
}
_ => return Ok(None),
};
if !opens_with(data, 0) {
return Ok(None);
}
let (pair, offset) = error_type(data, 0, 0, what)?;
let end = members(data, offset, what)?;
expect_end(data, end, end, what)?;
Ok(Some(pair))
}
fn opens_with(data: &[u8], number: u8) -> bool {
tags::decode_tag(data, 0).is_ok_and(|(tag, _)| tag.is_opening_tag(number))
}
fn subscribe_cov_property_multiple(data: &[u8]) -> Result<Option<(ErrorClass, ErrorCode)>, Error> {
const WHAT: &str = "SubscribeCOVPropertyMultiple-Error";
if opens_with(data, 0) {
let (pair, end) = error_type(data, 0, 0, WHAT)?;
expect_end(data, end, end, WHAT)?;
return Ok(Some(pair));
}
if !opens_with(data, 1) {
return Ok(None);
}
let (subscription, end) = decode_ctx_constructed(data, 0, 1, WHAT)?;
expect_end(data, end, end, WHAT)?;
let (_, offset) = decode_ctx_object_id(subscription, 0, 0, WHAT)?;
let (reference, after_reference) = decode_ctx_constructed(subscription, offset, 1, WHAT)?;
let (_, reference_end) = decode_property_reference(reference, 0)?;
expect_end(reference, reference_end, offset, WHAT)?;
let (pair, end) = error_type(subscription, after_reference, 2, WHAT)?;
expect_end(subscription, end, 0, WHAT)?;
Ok(Some(pair))
}
fn first_failed_write_attempt(data: &[u8], offset: usize, what: &str) -> Result<usize, Error> {
let (reference, end) = decode_ctx_constructed(data, offset, 1, what)?;
decode_object_property_reference(reference)?;
Ok(end)
}
fn first_failed_element_number(data: &[u8], offset: usize, what: &str) -> Result<usize, Error> {
let (_, end) = decode_ctx_unsigned::<u32>(data, offset, 1, what)?;
Ok(end)
}
fn private_transfer_members(data: &[u8], offset: usize, what: &str) -> Result<usize, Error> {
let (_, offset) = decode_ctx_unsigned::<u32>(data, offset, 1, what)?;
let (_, offset) = decode_ctx_unsigned::<u32>(data, offset, 2, what)?;
if offset == data.len() {
return Ok(offset);
}
let (_, end) = decode_ctx_constructed(data, offset, 3, what)?;
Ok(end)
}
fn vt_session_identifiers(data: &[u8], offset: usize, what: &str) -> Result<usize, Error> {
if offset == data.len() {
return Ok(offset);
}
let (sessions, end) = decode_ctx_constructed(data, offset, 1, what)?;
let mut position = 0;
while position < sessions.len() {
let (session, next) = decode_app_unsigned::<u64>(sessions, position, what)?;
if u8::try_from(session).is_err() {
return Err(Error::out_of_range(
position,
format!("{what}: session identifier {session} exceeds u8"),
));
}
position = next;
}
Ok(end)
}
fn error_type(
data: &[u8],
offset: usize,
number: u8,
what: &str,
) -> Result<((ErrorClass, ErrorCode), usize), Error> {
let (error_body, end) = decode_ctx_constructed(data, offset, number, what)?;
let (class, next) = decode_app_enumerated(error_body, 0, what)?;
let (code, body_end) = decode_app_enumerated(error_body, next, what)?;
expect_end(error_body, body_end, offset, what)?;
Ok((
(ErrorClass::from_raw(class), ErrorCode::from_raw(code)),
end,
))
}