bacnet_objects/notification_forwarder/
persistence.rs1use std::path::Path;
13
14use bacnet_encoding::constructed::{
15 decode_destination, decode_event_notification_subscription, encode_destination_list,
16 encode_event_notification_subscription_list,
17};
18use bacnet_types::constructed::{BACnetDestination, BACnetEventNotificationSubscription};
19use bacnet_types::error::Error;
20use bacnet_types::primitives::ObjectIdentifier;
21use bytes::BytesMut;
22
23use super::MAX_RECIPIENT_LIST_DESTINATIONS;
24use crate::durable::file::ObjectFile;
25use crate::subscribed_recipients::MAX_SUBSCRIBED_RECIPIENTS;
26
27#[derive(Clone, Debug, Default, PartialEq)]
29pub struct ForwarderSnapshot {
30 pub recipient_list: Option<Vec<BACnetDestination>>,
34 pub subscribed_recipients: Vec<BACnetEventNotificationSubscription>,
37}
38
39pub trait NotificationForwarderPersistence: Send + Sync {
46 fn load(&self, forwarder: ObjectIdentifier) -> Result<Option<ForwarderSnapshot>, Error>;
49
50 fn save(&self, forwarder: ObjectIdentifier, snapshot: &ForwarderSnapshot) -> Result<(), Error>;
52}
53
54const MAGIC: &[u8; 8] = b"RBNFWD01";
55const BODY_HEADER_LEN: usize = 1 + 4;
59pub(super) const MAX_FILE_BYTES: u64 = 64 * 1024;
63
64#[derive(Clone, Debug)]
76pub struct FileNotificationForwarderPersistence {
77 file: ObjectFile,
78}
79
80impl FileNotificationForwarderPersistence {
81 pub fn new(path: impl AsRef<Path>) -> Result<Self, Error> {
83 Ok(Self {
84 file: ObjectFile::new(
85 path.as_ref(),
86 MAGIC,
87 MAX_FILE_BYTES,
88 "Notification Forwarder",
89 )?,
90 })
91 }
92
93 pub fn path(&self) -> &Path {
95 self.file.path()
96 }
97}
98
99impl NotificationForwarderPersistence for FileNotificationForwarderPersistence {
100 fn load(&self, forwarder: ObjectIdentifier) -> Result<Option<ForwarderSnapshot>, Error> {
101 let Some(body) = self.file.load(forwarder)? else {
102 return Ok(None);
103 };
104 if body.len() < BODY_HEADER_LEN {
105 return Err(self.file.corrupt("has no valid header"));
106 }
107 let length = u32::from_be_bytes(body[1..BODY_HEADER_LEN].try_into().unwrap());
108 let split = usize::try_from(length)
109 .ok()
110 .and_then(|length| BODY_HEADER_LEN.checked_add(length))
111 .filter(|split| *split <= body.len())
112 .ok_or_else(|| self.file.corrupt("has a Recipient_List past its end"))?;
113 let recipient_list = &body[BODY_HEADER_LEN..split];
114 let recipient_list = match body[0] {
115 0 if recipient_list.is_empty() => None,
116 1 => Some(self.file.decode_capped(
117 recipient_list,
118 MAX_RECIPIENT_LIST_DESTINATIONS,
119 decode_destination,
120 )?),
121 _ => return Err(self.file.corrupt("has no valid header")),
122 };
123 Ok(Some(ForwarderSnapshot {
124 recipient_list,
125 subscribed_recipients: self.file.decode_capped(
126 &body[split..],
127 MAX_SUBSCRIBED_RECIPIENTS,
128 decode_event_notification_subscription,
129 )?,
130 }))
131 }
132
133 fn save(&self, forwarder: ObjectIdentifier, snapshot: &ForwarderSnapshot) -> Result<(), Error> {
134 let mut recipient_list = BytesMut::new();
135 if let Some(list) = &snapshot.recipient_list {
136 encode_destination_list(&mut recipient_list, list)?;
137 }
138 let mut body = BytesMut::with_capacity(BODY_HEADER_LEN + recipient_list.len() + 1024);
139 body.extend_from_slice(&[u8::from(snapshot.recipient_list.is_some())]);
140 let length = u32::try_from(recipient_list.len())
141 .map_err(|_| Error::OutOfRange("Recipient_List too long to save".into()))?;
142 body.extend_from_slice(&length.to_be_bytes());
143 body.extend_from_slice(&recipient_list);
144 encode_event_notification_subscription_list(&mut body, &snapshot.subscribed_recipients)?;
145 self.file.save(forwarder, &body)
146 }
147}