use bacnet_encoding::constructed::{
decode_daily_schedule, decode_date_range, decode_special_event,
};
use bacnet_encoding::tags::{self, app_tag, TagClass};
use bacnet_types::constructed::{
BACnetCalendarEntry, BACnetSpecialEvent, BACnetTimeValue, SpecialEventPeriod,
};
use bacnet_types::enums::{ErrorClass, ErrorCode};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use super::{evaluation, unspecified_date, ScheduleObject};
use crate::common;
pub(crate) const MAX_EXCEPTIONS: usize = 1024;
pub(super) fn no_space_error() -> Error {
common::protocol_error(ErrorClass::RESOURCES, ErrorCode::NO_SPACE_TO_WRITE_PROPERTY)
}
fn empty_special_event() -> BACnetSpecialEvent {
BACnetSpecialEvent {
period: SpecialEventPeriod::CalendarEntry(BACnetCalendarEntry::Date(unspecified_date())),
list_of_time_values: Vec::new(),
event_priority: 16,
}
}
fn decode_days(value: PropertyValue) -> Result<Vec<Vec<BACnetTimeValue>>, Error> {
common::decode_elements(&value, |tag| tag.is_opening_tag(0), decode_daily_schedule)
}
fn decode_events(value: PropertyValue) -> Result<Vec<BACnetSpecialEvent>, Error> {
common::decode_elements(
&value,
|tag| tag.is_opening_tag(0) || tag.is_context(1),
decode_special_event,
)
}
fn single<T>(mut elements: Vec<T>) -> Result<T, Error> {
match elements.pop() {
Some(element) if elements.is_empty() => Ok(element),
_ => Err(common::invalid_data_encoding_error()),
}
}
fn slot(index: u32, len: usize) -> Result<usize, Error> {
usize::try_from(index - 1)
.ok()
.filter(|slot| *slot < len)
.ok_or_else(common::invalid_array_index_error)
}
impl ScheduleObject {
pub(super) fn write_weekly_schedule(
&mut self,
array_index: Option<u32>,
value: PropertyValue,
) -> Result<(), Error> {
match array_index {
None => {
let days: [Vec<BACnetTimeValue>; 7] = decode_days(value)?
.try_into()
.map_err(|_| common::value_out_of_range_error())?;
for day in &days {
evaluation::check_time_values(day)?;
}
self.weekly_schedule = days;
self.contents_changed();
Ok(())
}
Some(0) => Err(common::write_access_denied_error()),
Some(index) => {
let day = slot(index, self.weekly_schedule.len())?;
self.set_weekly_schedule(day, single(decode_days(value)?)?)
}
}
}
pub(super) fn write_exception_schedule(
&mut self,
array_index: Option<u32>,
value: PropertyValue,
) -> Result<(), Error> {
match array_index {
None => {
let events = decode_events(value)?;
if events.len() > MAX_EXCEPTIONS {
return Err(no_space_error());
}
for event in &events {
evaluation::check_special_event(event)?;
}
self.exception_schedule = events;
}
Some(0) => {
let PropertyValue::Unsigned(size) = value else {
return Err(common::invalid_data_type_error());
};
let size = usize::try_from(size)
.ok()
.filter(|size| *size <= MAX_EXCEPTIONS)
.ok_or_else(no_space_error)?;
self.exception_schedule
.resize_with(size, empty_special_event);
}
Some(index) => {
let slot = slot(index, self.exception_schedule.len())?;
let event = single(decode_events(value)?)?;
evaluation::check_special_event(&event)?;
self.exception_schedule[slot] = event;
}
}
self.contents_changed();
Ok(())
}
pub(super) fn write_effective_period(&mut self, value: PropertyValue) -> Result<(), Error> {
let PropertyValue::ApplicationData(bytes) = value else {
return Err(common::invalid_data_type_error());
};
match tags::decode_tag(&bytes, 0) {
Ok((tag, _)) if tag.class == TagClass::Application && tag.number == app_tag::DATE => {}
Ok(_) => return Err(common::invalid_data_type_error()),
Err(_) => return Err(common::invalid_data_encoding_error()),
}
let (period, end) =
decode_date_range(&bytes, 0).map_err(|_| common::invalid_data_encoding_error())?;
if end != bytes.len() {
return Err(common::invalid_data_encoding_error());
}
self.set_effective_period(period)
}
}