use std::borrow::Cow;
use std::sync::Arc;
use std::time::Duration;
use bacnet_types::constructed::{
BACnetDestination, BACnetEventNotificationSubscription, BACnetPortPermission,
};
use bacnet_types::enums::{ObjectType, PropertyIdentifier, Reliability};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
use bytes::BytesMut;
use crate::common::{self, read_common_properties};
use crate::durable::staged::{self, Step};
use crate::durable::{DurableWrites, PendingWrite, SaveWait, StageStep};
use crate::notification_class::recipient_list;
use crate::subscribed_recipients::SubscribedRecipients;
use crate::traits::{BACnetObject, MonotonicClock};
mod metadata;
mod persistence;
mod port_filter;
mod saving;
mod selection;
pub use crate::notification_class::MAX_RECIPIENT_LIST_DESTINATIONS;
pub use persistence::{
FileNotificationForwarderPersistence, ForwarderSnapshot, NotificationForwarderPersistence,
};
pub use saving::ForwarderSaveCounters;
pub use selection::{forwarding_targets, ForwardingInput, ForwardingTargets};
pub struct NotificationForwarderObject {
oid: ObjectIdentifier,
name: String,
description: String,
status_flags: StatusFlags,
reliability: Reliability,
out_of_service: bool,
recipient_list: Vec<BACnetDestination>,
subscribed_recipients: SubscribedRecipients,
process_identifier_filter: Option<u32>,
local_forwarding_only: bool,
port_filter: Option<Vec<BACnetPortPermission>>,
storage: Option<saving::Storage>,
list_writes: u64,
recipient_list_written: bool,
save_counters: ForwarderSaveCounters,
}
impl NotificationForwarderObject {
pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
Ok(Self {
oid: ObjectIdentifier::new(ObjectType::NOTIFICATION_FORWARDER, instance)?,
name: name.into(),
description: String::new(),
status_flags: StatusFlags::empty(),
reliability: Reliability::NO_FAULT_DETECTED,
out_of_service: false,
recipient_list: Vec::new(),
subscribed_recipients: SubscribedRecipients::new(),
process_identifier_filter: None,
local_forwarding_only: false,
port_filter: None,
storage: None,
list_writes: 0,
recipient_list_written: false,
save_counters: ForwarderSaveCounters::default(),
})
}
pub fn with_persistence(
instance: u32,
name: impl Into<String>,
persistence: Arc<dyn NotificationForwarderPersistence>,
) -> Result<Self, Error> {
let mut forwarder = Self::new(instance, name)?;
if let Some(saved) = persistence.load(forwarder.oid)? {
if let Some(list) = saved.recipient_list {
if list.len() > MAX_RECIPIENT_LIST_DESTINATIONS {
return Err(recipient_list::no_space_error());
}
for destination in &list {
recipient_list::check_added(destination)?;
}
forwarder.recipient_list = list;
forwarder.recipient_list_written = true;
}
if !saved.subscribed_recipients.is_empty() {
forwarder
.subscribed_recipients
.write(framed(&saved.subscribed_recipients)?)?;
}
}
forwarder.storage = Some(saving::Storage::new(
forwarder.oid,
persistence,
forwarder.snapshot(),
forwarder.save_counters.clone(),
));
Ok(forwarder)
}
pub fn save_counters(&self) -> ForwarderSaveCounters {
self.save_counters.clone()
}
pub fn set_description(&mut self, description: impl Into<String>) {
self.description = description.into();
}
pub fn process_identifier_filter(&self) -> Option<u32> {
self.process_identifier_filter
}
pub fn set_process_identifier_filter(&mut self, filter: Option<u32>) {
self.process_identifier_filter = filter;
}
pub fn local_forwarding_only(&self) -> bool {
self.local_forwarding_only
}
pub fn set_local_forwarding_only(&mut self, local_only: bool) {
self.local_forwarding_only = local_only;
}
pub fn add_destination(&mut self, destination: BACnetDestination) -> Result<(), Error> {
recipient_list::check_added(&destination)?;
if self.recipient_list_saved() {
tracing::debug!(
forwarder = %self.oid,
"Saved Recipient_List kept over a configured destination"
);
return Ok(());
}
if self.recipient_list.len() >= MAX_RECIPIENT_LIST_DESTINATIONS {
return Err(recipient_list::no_space_error());
}
self.recipient_list.push(destination);
Ok(())
}
pub fn recipient_list(&self) -> &[BACnetDestination] {
&self.recipient_list
}
pub fn subscriptions(&self) -> Vec<BACnetEventNotificationSubscription> {
self.subscribed_recipients.subscriptions()
}
pub fn set_port_filter(&mut self, ports: Option<Vec<BACnetPortPermission>>) {
self.port_filter = ports;
}
pub fn port_filter(&self) -> Option<&[BACnetPortPermission]> {
self.port_filter.as_deref()
}
pub fn recipient_list_saved(&self) -> bool {
self.storage.is_some() && self.recipient_list_written
}
pub fn wait_for_saves(&self) {
if let Some(storage) = &self.storage {
storage.wait_idle();
}
}
fn snapshot(&self) -> ForwarderSnapshot {
ForwarderSnapshot {
recipient_list: self
.recipient_list_written
.then(|| self.recipient_list.clone()),
subscribed_recipients: self.subscribed_recipients.subscriptions(),
}
}
fn next_list(
&self,
earlier: &[Step<saving::NextList>],
property: PropertyIdentifier,
value: PropertyValue,
) -> Result<saving::NextList, Error> {
if property == PropertyIdentifier::RECIPIENT_LIST {
return recipient_list::decode_write(value).map(saving::NextList::RecipientList);
}
let current = earlier
.iter()
.rev()
.find_map(|step| match &step.next {
saving::NextList::SubscribedRecipients(store) => Some(store),
saving::NextList::RecipientList(_) => None,
})
.unwrap_or(&self.subscribed_recipients);
let mut next = current.clone();
next.write(value)?;
Ok(saving::NextList::SubscribedRecipients(next))
}
fn install(&mut self, next: saving::NextList) {
match next {
saving::NextList::RecipientList(list) => {
self.recipient_list = list;
self.recipient_list_written = true;
}
saving::NextList::SubscribedRecipients(store) => self.subscribed_recipients = store,
}
self.list_writes = self.list_writes.wrapping_add(1);
}
fn write_list(
&mut self,
property: PropertyIdentifier,
value: PropertyValue,
) -> Result<(), Error> {
let refused = |_| {
common::protocol_error(
bacnet_types::enums::ErrorClass::DEVICE,
bacnet_types::enums::ErrorCode::OPERATIONAL_PROBLEM,
)
};
if let Some(taken) = self.take_staged(property, &value) {
return taken.map_err(refused);
}
let next = self.next_list(&[], property, value)?;
if self.storage.is_some() {
let mut snapshot = self.snapshot();
saving::apply(&mut snapshot, &next);
self.with_storage(|storage, _| storage.drop_staged());
let storage = self.storage.as_mut().expect("checked above");
storage.save_now(snapshot).map_err(refused)?;
}
self.install(next);
Ok(())
}
fn take_staged(
&mut self,
property: PropertyIdentifier,
value: &PropertyValue,
) -> Option<Result<(), Error>> {
let mut storage = self.storage.take()?;
let taken = storage
.claim(property, value, self.list_writes)
.map(|claimed| claimed.map(|next| self.install(next)));
storage.correct(|| self.snapshot());
self.storage = Some(storage);
taken
}
fn after_time_passed(&mut self, lapsed: bool) -> bool {
let now = self.subscribed_recipients.current_time();
self.with_storage(|storage, served| storage.keep_current(now, lapsed, served));
lapsed
}
fn with_storage<R>(
&mut self,
f: impl FnOnce(&mut saving::Storage, &dyn Fn() -> ForwarderSnapshot) -> R,
) -> Option<R> {
let mut storage = self.storage.take()?;
let served = || self.snapshot();
let result = f(&mut storage, &served);
storage.correct(served);
self.storage = Some(storage);
Some(result)
}
}
fn listed(property: PropertyIdentifier) -> bool {
matches!(
property,
PropertyIdentifier::RECIPIENT_LIST | PropertyIdentifier::SUBSCRIBED_RECIPIENTS
)
}
impl DurableWrites for NotificationForwarderObject {
fn stage_write(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: &PropertyValue,
) -> StageStep {
self.stage_writes(&[PendingWrite {
property,
array_index,
value: value.clone(),
}])
}
fn stage_writes(&mut self, writes: &[PendingWrite]) -> StageStep {
if self.storage.is_none() || !writes.iter().any(|write| listed(write.property)) {
return StageStep::Skip;
}
if let Some(wait) = self.with_storage(|storage, _| storage.busy()).flatten() {
return StageStep::Busy(wait);
}
let steps = staged::steps(writes, |earlier, write| {
if !listed(write.property) {
return Ok(None);
}
if write.array_index.is_some() {
return Err(common::property_is_not_an_array_error());
}
self.next_list(earlier, write.property, write.value.clone())
.map(Some)
});
let served = self.snapshot();
let base = self.list_writes;
let storage = self.storage.as_mut().expect("checked above");
storage.stage(steps, base, served)
}
fn release_staged_write(&mut self, staged: &SaveWait) {
self.with_storage(|storage, _| storage.release(staged));
}
fn has_staged_write(&self) -> bool {
self.storage
.as_ref()
.is_some_and(saving::Storage::is_staged)
}
fn settle_forgotten_writes(&mut self) -> Option<SaveWait> {
self.with_storage(|storage, served| storage.drop_forgotten(served))
}
}
fn framed(subscriptions: &[BACnetEventNotificationSubscription]) -> Result<PropertyValue, Error> {
let mut buf = BytesMut::new();
bacnet_encoding::constructed::encode_event_notification_subscription_list(
&mut buf,
subscriptions,
)?;
Ok(PropertyValue::ApplicationData(buf.to_vec()))
}
fn read_bool(value: &PropertyValue) -> Result<bool, Error> {
match value {
PropertyValue::Boolean(value) => Ok(*value),
_ => Err(common::invalid_data_type_error()),
}
}
impl BACnetObject for NotificationForwarderObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
&self.name
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
if let Some(result) = read_common_properties!(self, property, array_index) {
return result;
}
match property {
PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
ObjectType::NOTIFICATION_FORWARDER.to_raw(),
)),
PropertyIdentifier::RECIPIENT_LIST => {
let mut buf = BytesMut::new();
bacnet_encoding::constructed::encode_destination_list(
&mut buf,
&self.recipient_list,
)?;
Ok(PropertyValue::ApplicationData(buf.to_vec()))
}
PropertyIdentifier::SUBSCRIBED_RECIPIENTS => Ok(self.subscribed_recipients.read()),
PropertyIdentifier::PROCESS_IDENTIFIER_FILTER => Ok(self
.process_identifier_filter
.map_or(PropertyValue::Null, |id| PropertyValue::Unsigned(id.into()))),
PropertyIdentifier::LOCAL_FORWARDING_ONLY => {
Ok(PropertyValue::Boolean(self.local_forwarding_only))
}
PropertyIdentifier::PORT_FILTER => match &self.port_filter {
Some(ports) => port_filter::read(ports, array_index),
None => Err(common::unknown_property_error()),
},
_ => Err(common::unknown_property_error()),
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
if listed(property) && array_index.is_some() {
return Err(common::property_is_not_an_array_error());
}
match property {
PropertyIdentifier::RECIPIENT_LIST | PropertyIdentifier::SUBSCRIBED_RECIPIENTS => {
return self.write_list(property, value);
}
PropertyIdentifier::PROCESS_IDENTIFIER_FILTER if array_index.is_none() => {
self.process_identifier_filter = match value {
PropertyValue::Null => None,
PropertyValue::Unsigned(id) => {
Some(u32::try_from(id).map_err(|_| common::value_out_of_range_error())?)
}
_ => return Err(common::invalid_data_type_error()),
};
return Ok(());
}
PropertyIdentifier::LOCAL_FORWARDING_ONLY if array_index.is_none() => {
self.local_forwarding_only = read_bool(&value)?;
return Ok(());
}
PropertyIdentifier::PORT_FILTER => {
if let Some(ports) = &mut self.port_filter {
return port_filter::write(ports, array_index, value);
}
}
_ => {}
}
if array_index.is_none() {
if let Some(result) =
common::write_out_of_service(&mut self.out_of_service, property, &value)
{
return result;
}
if let Some(result) = common::write_description(&mut self.description, property, &value)
{
return result;
}
}
Err(common::unhandled_write_error(
self.property_metadata().as_ref(),
property,
array_index,
))
}
fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
metadata::for_object(self)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn advance_time_internal(&mut self, elapsed: Duration) -> bool {
let lapsed = self.subscribed_recipients.advance_by(elapsed);
self.after_time_passed(lapsed)
}
fn bind_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
self.subscribed_recipients.bind_monotonic_clock(clock);
self.with_storage(|storage, _| storage.clock_changed());
}
fn advance_monotonic_time_internal(&mut self, now: Duration) -> bool {
let lapsed = self.subscribed_recipients.advance_to(now);
self.after_time_passed(lapsed)
}
fn next_monotonic_deadline_internal(&self) -> Option<Duration> {
self.subscribed_recipients.next_deadline()
}
fn durable_writes_internal(&mut self) -> Option<&mut dyn DurableWrites> {
Some(self)
}
}
#[cfg(test)]
mod tests;