use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use bacnet_encoding::constructed::{
decode_event_notification_subscription, encode_event_notification_subscription_list,
};
use bacnet_types::constructed::{BACnetEventNotificationSubscription, BACnetRecipient};
use bacnet_types::enums::{ErrorClass, ErrorCode};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use bytes::BytesMut;
use crate::common;
use crate::traits::MonotonicClock;
pub const MAX_SUBSCRIBED_RECIPIENTS: usize = 32;
pub const MAX_SUBSCRIPTION_MINUTES: u32 = 1440;
const NANOS_PER_MINUTE: u128 = 60_000_000_000;
#[derive(Clone)]
struct Entry {
recipient: BACnetRecipient,
process_identifier: u32,
issue_confirmed_notifications: bool,
expires_at: Duration,
}
impl Entry {
fn names_the_same(&self, other: &Entry) -> bool {
self.recipient == other.recipient && self.process_identifier == other.process_identifier
}
fn is_live(&self, now: Duration) -> bool {
self.expires_at > now
}
fn served(&self, now: Duration) -> BACnetEventNotificationSubscription {
let minutes = self
.expires_at
.saturating_sub(now)
.as_nanos()
.div_ceil(NANOS_PER_MINUTE);
BACnetEventNotificationSubscription {
recipient: self.recipient.clone(),
process_identifier: self.process_identifier,
issue_confirmed_notifications: self.issue_confirmed_notifications,
time_remaining: u32::try_from(minutes).unwrap_or(u32::MAX),
}
}
}
#[derive(Clone, Default)]
pub struct SubscribedRecipients {
entries: Vec<Entry>,
monotonic_clock: Option<Arc<MonotonicClock>>,
logical_now: Duration,
}
impl fmt::Debug for SubscribedRecipients {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SubscribedRecipients")
.field("subscriptions", &self.subscriptions())
.finish_non_exhaustive()
}
}
impl SubscribedRecipients {
pub fn new() -> Self {
Self::default()
}
fn now(&self) -> Duration {
self.monotonic_clock
.as_ref()
.map_or(self.logical_now, |clock| clock())
}
pub(crate) fn current_time(&self) -> Duration {
self.now()
}
pub fn subscriptions(&self) -> Vec<BACnetEventNotificationSubscription> {
let now = self.now();
self.entries
.iter()
.filter(|entry| entry.is_live(now))
.map(|entry| entry.served(now))
.collect()
}
pub fn read(&self) -> PropertyValue {
let mut buf = BytesMut::new();
encode_event_notification_subscription_list(&mut buf, &self.subscriptions())
.expect("stored recipients fit BACnetAddress::MAX_MAC_LEN");
PropertyValue::ApplicationData(buf.to_vec())
}
pub fn write(&mut self, value: PropertyValue) -> Result<(), Error> {
let PropertyValue::ApplicationData(bytes) = value else {
return Err(common::invalid_data_type_error());
};
let now = self.now();
let mut written: Vec<Entry> = Vec::new();
let mut offset = 0;
let mut index = 0;
while offset < bytes.len() {
let (subscription, next) = decode_event_notification_subscription(&bytes, offset)
.map_err(|_| common::invalid_data_type_error())?;
offset = next;
if !(1..=MAX_SUBSCRIPTION_MINUTES).contains(&subscription.time_remaining) {
return Err(common::at_list_element(
common::value_out_of_range_error(),
index,
));
}
let entry = self.entry_for(subscription, now);
match written.iter().position(|held| held.names_the_same(&entry)) {
Some(held) => written[held] = entry,
None if written.len() == MAX_SUBSCRIBED_RECIPIENTS => {
return Err(common::at_list_element(
common::protocol_error(
ErrorClass::RESOURCES,
ErrorCode::NO_SPACE_TO_WRITE_PROPERTY,
),
index,
));
}
None => written.push(entry),
}
index += 1;
}
self.entries = written;
Ok(())
}
fn entry_for(&self, subscription: BACnetEventNotificationSubscription, now: Duration) -> Entry {
let lifetime = Duration::from_secs(u64::from(subscription.time_remaining) * 60);
let expires_at = self
.entries
.iter()
.find(|entry| {
if !entry.is_live(now) {
return false;
}
let served = entry.served(now);
served.recipient == subscription.recipient
&& served.process_identifier == subscription.process_identifier
&& served.issue_confirmed_notifications
== subscription.issue_confirmed_notifications
&& (served.time_remaining..=served.time_remaining.saturating_add(1))
.contains(&subscription.time_remaining)
})
.map_or_else(|| now.saturating_add(lifetime), |entry| entry.expires_at);
Entry {
recipient: subscription.recipient,
process_identifier: subscription.process_identifier,
issue_confirmed_notifications: subscription.issue_confirmed_notifications,
expires_at,
}
}
pub fn bind_monotonic_clock(&mut self, clock: Option<Arc<MonotonicClock>>) {
let before = self.now();
self.monotonic_clock = clock;
let after = self.now();
for entry in &mut self.entries {
let left = entry.expires_at.saturating_sub(before);
entry.expires_at = after.saturating_add(left);
}
}
pub fn advance_to(&mut self, now: Duration) -> bool {
let held = self.entries.len();
self.entries.retain(|entry| entry.is_live(now));
self.entries.len() != held
}
pub fn advance_by(&mut self, elapsed: Duration) -> bool {
self.logical_now = self.logical_now.saturating_add(elapsed);
self.advance_to(self.now())
}
pub fn next_deadline(&self) -> Option<Duration> {
self.entries.iter().map(|entry| entry.expires_at).min()
}
}
#[cfg(test)]
#[path = "subscribed_recipients_tests.rs"]
mod tests;