use std::path::Path;
use bacnet_encoding::constructed::{
decode_destination, decode_event_notification_subscription, encode_destination_list,
encode_event_notification_subscription_list,
};
use bacnet_types::constructed::{BACnetDestination, BACnetEventNotificationSubscription};
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use bytes::BytesMut;
use super::MAX_RECIPIENT_LIST_DESTINATIONS;
use crate::durable::file::ObjectFile;
use crate::subscribed_recipients::MAX_SUBSCRIBED_RECIPIENTS;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ForwarderSnapshot {
pub recipient_list: Option<Vec<BACnetDestination>>,
pub subscribed_recipients: Vec<BACnetEventNotificationSubscription>,
}
pub trait NotificationForwarderPersistence: Send + Sync {
fn load(&self, forwarder: ObjectIdentifier) -> Result<Option<ForwarderSnapshot>, Error>;
fn save(&self, forwarder: ObjectIdentifier, snapshot: &ForwarderSnapshot) -> Result<(), Error>;
}
const MAGIC: &[u8; 8] = b"RBNFWD01";
const BODY_HEADER_LEN: usize = 1 + 4;
pub(super) const MAX_FILE_BYTES: u64 = 64 * 1024;
#[derive(Clone, Debug)]
pub struct FileNotificationForwarderPersistence {
file: ObjectFile,
}
impl FileNotificationForwarderPersistence {
pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
Ok(Self {
file: ObjectFile::new(
path.as_ref(),
MAGIC,
MAX_FILE_BYTES,
"Notification Forwarder",
)?,
})
}
pub fn path(&self) -> &Path {
self.file.path()
}
}
impl NotificationForwarderPersistence for FileNotificationForwarderPersistence {
fn load(&self, forwarder: ObjectIdentifier) -> Result<Option<ForwarderSnapshot>, Error> {
let Some(body) = self.file.load(forwarder)? else {
return Ok(None);
};
if body.len() < BODY_HEADER_LEN {
return Err(self.file.corrupt("has no valid header"));
}
let length = u32::from_be_bytes(body[1..BODY_HEADER_LEN].try_into().unwrap());
let split = usize::try_from(length)
.ok()
.and_then(|length| BODY_HEADER_LEN.checked_add(length))
.filter(|split| *split <= body.len())
.ok_or_else(|| self.file.corrupt("has a Recipient_List past its end"))?;
let recipient_list = &body[BODY_HEADER_LEN..split];
let recipient_list = match body[0] {
0 if recipient_list.is_empty() => None,
1 => Some(self.file.decode_capped(
recipient_list,
MAX_RECIPIENT_LIST_DESTINATIONS,
decode_destination,
)?),
_ => return Err(self.file.corrupt("has no valid header")),
};
Ok(Some(ForwarderSnapshot {
recipient_list,
subscribed_recipients: self.file.decode_capped(
&body[split..],
MAX_SUBSCRIBED_RECIPIENTS,
decode_event_notification_subscription,
)?,
}))
}
fn save(&self, forwarder: ObjectIdentifier, snapshot: &ForwarderSnapshot) -> Result<(), Error> {
let mut recipient_list = BytesMut::new();
if let Some(list) = &snapshot.recipient_list {
encode_destination_list(&mut recipient_list, list)?;
}
let mut body = BytesMut::with_capacity(BODY_HEADER_LEN + recipient_list.len() + 1024);
body.extend_from_slice(&[u8::from(snapshot.recipient_list.is_some())]);
let length = u32::try_from(recipient_list.len())
.map_err(|_| Error::OutOfRange("Recipient_List too long to save".into()))?;
body.extend_from_slice(&length.to_be_bytes());
body.extend_from_slice(&recipient_list);
encode_event_notification_subscription_list(&mut body, &snapshot.subscribed_recipients)?;
self.file.save(forwarder, &body)
}
}