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