1use super::*;
2use bacnet_types::enums::PropertyIdentifier;
3use std::sync::Mutex as StdMutex;
4use tokio::sync::oneshot;
5use tokio::time::{Instant as TokioInstant, sleep_until};
6
7const DEFAULT_COV_RENEWAL_MARGIN: Duration = Duration::from_secs(30);
8const DEFAULT_COV_RENEWAL_EVENT_CAPACITY: usize = 16;
9
10#[derive(Debug, Clone)]
12#[non_exhaustive]
13pub struct ManagedCOVSubscriptionOptions {
14 pub renewal_margin: Duration,
20 pub event_channel_capacity: usize,
24 pub renewal_gate: Option<Arc<tokio::sync::Semaphore>>,
40}
41
42impl Default for ManagedCOVSubscriptionOptions {
43 fn default() -> Self {
44 Self {
45 renewal_margin: DEFAULT_COV_RENEWAL_MARGIN,
46 event_channel_capacity: DEFAULT_COV_RENEWAL_EVENT_CAPACITY,
47 renewal_gate: None,
48 }
49 }
50}
51
52impl ManagedCOVSubscriptionOptions {
53 pub fn with_renewal_margin(mut self, margin: Duration) -> Self {
55 self.renewal_margin = margin;
56 self
57 }
58
59 pub fn with_event_channel_capacity(mut self, capacity: usize) -> Self {
61 self.event_channel_capacity = capacity;
62 self
63 }
64
65 pub fn with_renewal_gate(mut self, gate: Arc<tokio::sync::Semaphore>) -> Self {
68 self.renewal_gate = Some(gate);
69 self
70 }
71
72 fn validate(&self, lifetime: u32) -> Result<(), Error> {
73 if !(1..=MAX_COV_CHANNEL_CAPACITY).contains(&self.event_channel_capacity) {
74 return Err(Error::Encoding(format!(
75 "invalid managed COV subscription event channel capacity {}; expected 1..={}",
76 self.event_channel_capacity, MAX_COV_CHANNEL_CAPACITY
77 )));
78 }
79 if self.renewal_margin >= Duration::from_secs(u64::from(lifetime)) {
80 return Err(Error::Encoding(
81 "managed COV subscription renewal margin must be shorter than lifetime".into(),
82 ));
83 }
84 Ok(())
85 }
86
87 fn renewal_delay(&self, time_remaining: u32) -> Duration {
88 Duration::from_secs(u64::from(time_remaining)).saturating_sub(self.renewal_margin)
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94#[non_exhaustive]
95pub enum ManagedCOVSubscriptionEvent {
96 NotificationObserved {
98 time_remaining: u32,
100 renew_after: Duration,
102 },
103 ImpendingExpiry {
105 time_remaining: u32,
107 },
108 Renewed {
110 requested_lifetime: u32,
112 renew_after: Duration,
114 },
115 RenewalFailed {
117 error: String,
119 },
120}
121
122pub struct ManagedCOVSubscription {
124 events_tx: broadcast::Sender<ManagedCOVSubscriptionEvent>,
125 last_event: Arc<StdMutex<Option<ManagedCOVSubscriptionEvent>>>,
126 stop_tx: Option<oneshot::Sender<()>>,
127 task: Option<JoinHandle<()>>,
128}
129
130impl ManagedCOVSubscription {
131 pub fn events(&self) -> broadcast::Receiver<ManagedCOVSubscriptionEvent> {
133 self.events_tx.subscribe()
134 }
135
136 pub fn last_event(&self) -> Option<ManagedCOVSubscriptionEvent> {
138 self.last_event.lock().ok().and_then(|event| event.clone())
139 }
140
141 pub async fn stop(mut self) {
143 if let Some(stop_tx) = self.stop_tx.take() {
144 let _ = stop_tx.send(());
145 }
146 if let Some(task) = self.task.take() {
147 let _ = task.await;
148 }
149 }
150
151 pub fn is_finished(&self) -> bool {
153 match &self.task {
154 Some(task) => task.is_finished(),
155 None => true,
156 }
157 }
158}
159
160impl Drop for ManagedCOVSubscription {
161 fn drop(&mut self) {
162 if let Some(stop_tx) = self.stop_tx.take() {
163 let _ = stop_tx.send(());
164 }
165 if let Some(task) = self.task.take() {
166 task.abort();
167 }
168 }
169}
170
171#[derive(Clone, Copy)]
173enum ManagedRequest {
174 Object {
176 subscriber_process_identifier: u32,
177 monitored_object_identifier: bacnet_types::primitives::ObjectIdentifier,
178 confirmed: bool,
179 lifetime: u32,
180 },
181 Property(CovPropertySubscription),
183}
184
185impl ManagedRequest {
186 fn lifetime(self) -> u32 {
187 match self {
188 Self::Object { lifetime, .. } => lifetime,
189 Self::Property(subscription) => subscription.lifetime.get(),
190 }
191 }
192
193 fn property(self) -> Option<PropertyIdentifier> {
195 match self {
196 Self::Object { .. } => None,
197 Self::Property(subscription) => Some(subscription.monitored_property_identifier),
198 }
199 }
200
201 fn identity(self) -> (u32, bacnet_types::primitives::ObjectIdentifier) {
202 match self {
203 Self::Object {
204 subscriber_process_identifier,
205 monitored_object_identifier,
206 ..
207 } => (subscriber_process_identifier, monitored_object_identifier),
208 Self::Property(subscription) => (
209 subscription.subscriber_process_identifier,
210 subscription.monitored_object_identifier,
211 ),
212 }
213 }
214
215 async fn send<T: TransportPort + 'static>(
216 self,
217 client: &BACnetClient<T>,
218 target: &CovTarget,
219 ) -> Result<(), Error> {
220 let target = target.confirmed();
221 match self {
222 Self::Object {
223 subscriber_process_identifier,
224 monitored_object_identifier,
225 confirmed,
226 lifetime,
227 } => {
228 let request = BACnetClient::<T>::subscribe_cov_request(
229 subscriber_process_identifier,
230 monitored_object_identifier,
231 Some(confirmed),
232 Some(lifetime),
233 );
234 client.send_subscribe_cov_request(target, request).await
235 }
236 Self::Property(subscription) => {
237 client
238 .send_subscribe_cov_property_request(target, subscription.into_request())
239 .await
240 }
241 }
242 }
243}
244
245impl<T: TransportPort + 'static> BACnetClient<T> {
246 pub async fn manage_cov_subscription(
261 self: Arc<Self>,
262 target: CovTarget,
263 subscriber_process_identifier: u32,
264 monitored_object_identifier: bacnet_types::primitives::ObjectIdentifier,
265 confirmed: bool,
266 lifetime: u32,
267 options: ManagedCOVSubscriptionOptions,
268 ) -> Result<ManagedCOVSubscription, Error> {
269 if lifetime == 0 {
270 return Err(Error::Encoding(
271 "managed COV subscriptions require a finite non-zero lifetime".into(),
272 ));
273 }
274 let request = ManagedRequest::Object {
275 subscriber_process_identifier,
276 monitored_object_identifier,
277 confirmed,
278 lifetime,
279 };
280 self.manage(target, request, options).await
281 }
282
283 pub async fn manage_cov_property_subscription(
290 self: Arc<Self>,
291 target: CovTarget,
292 subscription: CovPropertySubscription,
293 options: ManagedCOVSubscriptionOptions,
294 ) -> Result<ManagedCOVSubscription, Error> {
295 self.manage(target, ManagedRequest::Property(subscription), options)
296 .await
297 }
298
299 async fn manage(
300 self: Arc<Self>,
301 target: CovTarget,
302 request: ManagedRequest,
303 options: ManagedCOVSubscriptionOptions,
304 ) -> Result<ManagedCOVSubscription, Error> {
305 let lifetime = request.lifetime();
306 options.validate(lifetime)?;
307 let (subscriber_process_identifier, monitored_object_identifier) = request.identity();
308
309 let mut notifications = self.cov_notifications();
310
311 request.send(&self, &target).await?;
312
313 let (events_tx, _) = broadcast::channel(options.event_channel_capacity);
314 let events_tx_for_task = events_tx.clone();
315 let last_event = Arc::new(StdMutex::new(None));
316 let last_event_for_task = Arc::clone(&last_event);
317 let (stop_tx, mut stop_rx) = oneshot::channel();
318 let client = Arc::clone(&self);
319 let mut renew_at = TokioInstant::now() + options.renewal_delay(lifetime);
320
321 let task = tokio::spawn(async move {
322 loop {
323 let renew_sleep = sleep_until(renew_at);
324 tokio::pin!(renew_sleep);
325 tokio::select! {
326 _ = &mut stop_rx => break,
327 _ = &mut renew_sleep => {
328 let renewal = async {
332 let _permit = match &options.renewal_gate {
333 Some(gate) => gate.acquire().await.ok(),
334 None => None,
335 };
336 request.send(&client, &target).await
337 };
338 tokio::pin!(renewal);
339 tokio::select! {
340 _ = &mut stop_rx => break,
341 result = &mut renewal => match result {
342 Ok(()) => {
343 let renew_after = options.renewal_delay(lifetime);
344 publish_managed_cov_event(
345 &events_tx_for_task,
346 &last_event_for_task,
347 ManagedCOVSubscriptionEvent::Renewed {
348 requested_lifetime: lifetime,
349 renew_after,
350 },
351 );
352 renew_at = TokioInstant::now() + renew_after;
353 }
354 Err(error) => {
355 publish_managed_cov_event(
356 &events_tx_for_task,
357 &last_event_for_task,
358 ManagedCOVSubscriptionEvent::RenewalFailed {
359 error: error.to_string(),
360 },
361 );
362 break;
363 }
364 },
365 }
366 }
367 received = notifications.recv() => {
368 let received = match received {
369 Ok(received) => received,
370 Err(broadcast::error::RecvError::Lagged(_)) => continue,
371 Err(broadcast::error::RecvError::Closed) => break,
372 };
373 if !matches_managed_cov_notification(
374 &received,
375 &target,
376 client.network.local_network_number().get(),
377 subscriber_process_identifier,
378 monitored_object_identifier,
379 request.property(),
380 ) {
381 continue;
382 }
383
384 let time_remaining = received.notification.time_remaining;
385 let renew_after = options.renewal_delay(time_remaining);
386 publish_managed_cov_event(
387 &events_tx_for_task,
388 &last_event_for_task,
389 ManagedCOVSubscriptionEvent::NotificationObserved {
390 time_remaining,
391 renew_after,
392 },
393 );
394
395 let observed_renew_at = TokioInstant::now() + renew_after;
396 if renew_after.is_zero() {
397 publish_managed_cov_event(
398 &events_tx_for_task,
399 &last_event_for_task,
400 ManagedCOVSubscriptionEvent::ImpendingExpiry {
401 time_remaining,
402 },
403 );
404 renew_at = observed_renew_at;
405 } else if observed_renew_at < renew_at {
406 renew_at = observed_renew_at;
407 }
408 }
409 }
410 }
411 });
412
413 Ok(ManagedCOVSubscription {
414 events_tx,
415 last_event,
416 stop_tx: Some(stop_tx),
417 task: Some(task),
418 })
419 }
420}
421
422fn publish_managed_cov_event(
423 events_tx: &broadcast::Sender<ManagedCOVSubscriptionEvent>,
424 last_event: &StdMutex<Option<ManagedCOVSubscriptionEvent>>,
425 event: ManagedCOVSubscriptionEvent,
426) {
427 if let Ok(mut last_event) = last_event.lock() {
428 *last_event = Some(event.clone());
429 }
430 let _ = events_tx.send(event);
431}
432
433fn matches_managed_cov_notification(
439 received: &ReceivedCOVNotification,
440 target: &CovTarget,
441 local_network: Option<u16>,
442 subscriber_process_identifier: u32,
443 monitored_object_identifier: bacnet_types::primitives::ObjectIdentifier,
444 property: Option<PropertyIdentifier>,
445) -> bool {
446 received.notification.subscriber_process_identifier == subscriber_process_identifier
447 && received.notification.monitored_object_identifier == monitored_object_identifier
448 && target.sent(received, local_network)
449 && property.is_none_or(|property| {
450 received
451 .notification
452 .list_of_values
453 .iter()
454 .any(|value| value.property_identifier == property)
455 })
456}