1use std::collections::HashMap;
4use std::sync::atomic::Ordering;
5use std::sync::Arc;
6use std::time::Instant;
7
8use bacnet_encoding::npdu::NpduAddress;
9use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
10use bacnet_types::error::Error;
11use bacnet_types::primitives::ObjectIdentifier;
12use bacnet_types::MacAddr;
13
14use crate::runtime_clock;
15
16mod identity;
17pub use identity::*;
18pub(crate) mod active;
19mod admission;
20pub use admission::MultipleRefusal;
21mod confirmed;
22pub(crate) use confirmed::{BeginRefusal, CovRevisits};
23mod sample;
24pub use sample::CovSample;
25mod observation;
26mod observation_order;
27pub use observation::CovObservation;
28use observation_order::ObservationOwner;
29pub(crate) use observation_order::PreparedCovCompletion;
30pub(crate) mod flags;
31mod lifetime;
32pub(crate) mod multiple_reads;
33pub(crate) mod prepare;
34pub(crate) mod reported;
35pub(crate) mod timed;
36mod timed_capture;
37pub(crate) use timed_capture::TimedWriteCapture;
38pub(crate) mod value_source;
39pub use lifetime::CovTimeRemaining;
40
41mod policy;
42pub use policy::*;
43
44#[cfg(test)]
45mod identity_tests;
46#[cfg(test)]
47mod policy_tests;
48#[cfg(test)]
49mod tests;
50
51const DEFAULT_TIMED_APDU_LENGTH: usize = 1476;
53
54#[derive(Debug, Clone)]
57pub struct CovSubscription {
58 pub subscriber_mac: MacAddr,
60 pub subscriber_network: Option<NpduAddress>,
62 pub subscriber_process_identifier: u32,
64 pub monitored_object_identifier: ObjectIdentifier,
66 pub issue_confirmed_notifications: bool,
69 pub expires_at: Option<Instant>,
73 pub last_notified_observation: Option<CovObservation>,
75 pub monitored_property: Option<PropertyIdentifier>,
77 pub monitored_property_array_index: Option<u32>,
79 pub cov_increment: Option<f32>,
81 pub notification_kind: CovNotificationKind,
83 pub timestamped: bool,
85}
86
87impl CovSubscription {
88 pub fn recipient(&self) -> CovRecipient {
91 CovRecipient::from_endpoint(&self.subscriber_mac, self.subscriber_network.as_ref())
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum CovNotificationKind {
98 Single,
100 Multiple,
102}
103
104#[derive(Debug)]
106pub struct CovSubscriptionTable {
107 subs: HashMap<CovSubscriptionKey, CovSubscriptionSnapshot>,
108 generation: u64,
109 owner: Arc<ObservationOwner>,
110 peer_counts: HashMap<CovRecipient, usize>,
111 peer_indefinite_counts: HashMap<CovRecipient, usize>,
112 policy: CovPolicy,
113 counters: Arc<AtomicCovCounters>,
114 in_flight: Arc<CovInFlightTracker>,
115 revisits: Arc<CovRevisits>,
116 dispatch_turn: usize,
117 timed: timed::TimedStore,
118 #[cfg(test)]
121 live_samples: std::sync::atomic::AtomicUsize,
122}
123
124impl Default for CovSubscriptionTable {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130impl CovSubscriptionTable {
131 pub fn new() -> Self {
133 Self::with_policy(CovPolicy::default(), Arc::new(AtomicCovCounters::default()))
134 }
135
136 pub fn with_policy(policy: CovPolicy, counters: Arc<AtomicCovCounters>) -> Self {
138 let timed = timed::TimedStore::new(DEFAULT_TIMED_APDU_LENGTH, Arc::clone(&counters));
139 Self {
140 subs: HashMap::new(),
141 generation: 0,
142 owner: Arc::new(ObservationOwner::default()),
143 peer_counts: HashMap::new(),
144 peer_indefinite_counts: HashMap::new(),
145 policy: policy.sanitized(),
146 counters,
147 in_flight: Arc::new(CovInFlightTracker::default()),
148 revisits: Arc::default(),
149 dispatch_turn: 0,
150 timed,
151 #[cfg(test)]
152 live_samples: std::sync::atomic::AtomicUsize::new(0),
153 }
154 }
155
156 pub fn with_max_apdu_length(mut self, max_apdu_length: usize) -> Self {
159 self.timed = timed::TimedStore::new(max_apdu_length, Arc::clone(&self.counters));
160 self
161 }
162
163 pub(crate) fn timed(&self) -> &timed::TimedStore {
165 &self.timed
166 }
167
168 pub(crate) fn next_dispatch_turn(&mut self) -> usize {
170 let turn = self.dispatch_turn;
171 self.dispatch_turn = self.dispatch_turn.wrapping_add(1);
172 turn
173 }
174
175 pub fn policy(&self) -> &CovPolicy {
177 &self.policy
178 }
179
180 pub fn counters(&self) -> &Arc<AtomicCovCounters> {
182 &self.counters
183 }
184
185 pub fn in_flight_tracker(&self) -> &Arc<CovInFlightTracker> {
187 &self.in_flight
188 }
189
190 pub fn peer_subscription_count(&self, peer: &CovRecipient) -> usize {
192 self.peer_counts.get(peer).copied().unwrap_or(0)
193 }
194
195 pub fn peer_indefinite_count(&self, peer: &CovRecipient) -> usize {
197 self.peer_indefinite_counts.get(peer).copied().unwrap_or(0)
198 }
199
200 pub fn get_subscription(&self, key: &CovSubscriptionKey) -> Option<&CovSubscriptionSnapshot> {
202 self.subs.get(key)
203 }
204
205 pub(crate) fn multiple_context_references<'a>(
207 &'a self,
208 context: &'a MultipleContextKey,
209 ) -> impl Iterator<Item = &'a CovSubscriptionSnapshot> + 'a {
210 self.subs
211 .iter()
212 .filter(move |(key, _)| key.multiple_context() == Some(context))
213 .map(|(_, sub)| sub)
214 }
215
216 pub fn contains(&self, key: &CovSubscriptionKey) -> bool {
218 self.subs.contains_key(key)
219 }
220
221 fn remove_internal(&mut self, key: &CovSubscriptionKey, was_cancelled: bool) -> bool {
222 if let Some(sub) = self.subs.remove(key) {
223 self.timed.lock().remove(key);
224 self.revisits.forget(key);
225 let peer = sub.recipient();
226 if let Some(count) = self.peer_counts.get_mut(&peer) {
227 *count = count.saturating_sub(1);
228 if *count == 0 {
229 self.peer_counts.remove(&peer);
230 }
231 }
232 if sub.expires_at.is_none() {
233 if let Some(count) = self.peer_indefinite_counts.get_mut(&peer) {
234 *count = count.saturating_sub(1);
235 if *count == 0 {
236 self.peer_indefinite_counts.remove(&peer);
237 }
238 }
239 }
240 if was_cancelled {
241 self.counters
242 .subscriptions_cancelled
243 .fetch_add(1, Ordering::Relaxed);
244 }
245 self.counters
246 .subscriptions_active
247 .store(self.subs.len() as u64, Ordering::Relaxed);
248 true
249 } else {
250 false
251 }
252 }
253
254 pub fn unsubscribe(&mut self, key: &CovSubscriptionKey) -> bool {
256 self.remove_internal(key, true)
257 }
258
259 pub fn unsubscribe_cov_multiple_context(&mut self, context: &MultipleContextKey) {
261 let keys: Vec<_> = self
262 .subs
263 .keys()
264 .filter(|key| key.multiple_context() == Some(context))
265 .cloned()
266 .collect();
267 for key in keys {
268 self.remove_internal(&key, true);
269 }
270 }
271
272 pub fn remove_for_object(&mut self, oid: ObjectIdentifier) {
274 let to_remove: Vec<_> = self
275 .subs
276 .iter()
277 .filter(|(k, _)| k.object() == oid)
278 .map(|(k, _)| k.clone())
279 .collect();
280 for key in to_remove {
281 self.remove_internal(&key, false);
282 }
283 }
284
285 pub fn remove_peer_subscriptions(
288 &mut self,
289 mac: &[u8],
290 network: Option<&NpduAddress>,
291 ) -> usize {
292 let target = SubscriberEndpoint::new(mac, network);
293 let to_remove: Vec<_> = self
294 .subs
295 .iter()
296 .filter(|(_, entry)| entry.endpoint() == target)
297 .map(|(k, _)| k.clone())
298 .collect();
299 let count = to_remove.len();
300 for key in to_remove {
301 self.remove_internal(&key, true);
302 }
303 count
304 }
305
306 pub fn purge_expired(&mut self) -> usize {
308 let now = runtime_clock::now();
309 let mut purged_count = 0;
310 let mut to_remove = Vec::new();
311 for (k, sub) in &self.subs {
312 if sub.expires_at.is_some_and(|exp| exp <= now) {
313 to_remove.push(k.clone());
314 }
315 }
316 for key in to_remove {
317 purged_count += usize::from(self.remove_internal(&key, false));
318 }
319 if purged_count > 0 {
320 self.counters
321 .subscriptions_purged
322 .fetch_add(purged_count as u64, Ordering::Relaxed);
323 self.counters
324 .subscriptions_active
325 .store(self.subs.len() as u64, Ordering::Relaxed);
326 }
327 purged_count
328 }
329
330 #[cfg(test)]
333 pub(crate) fn expire_all_for_test(&mut self) {
334 let now = runtime_clock::now();
335 for entry in self.subs.values_mut() {
336 entry.subscription.expires_at = Some(now);
337 }
338 }
339
340 pub fn subscriptions_for(&mut self, oid: &ObjectIdentifier) -> Vec<&CovSubscriptionSnapshot> {
342 self.purge_expired();
343 self.subs
344 .values()
345 .filter(|sub| sub.monitored_object_identifier == *oid)
346 .collect()
347 }
348
349 pub(crate) fn finest_increments(
355 &self,
356 property: PropertyIdentifier,
357 ) -> HashMap<ObjectIdentifier, f64> {
358 let now = runtime_clock::now();
359 let mut finest = HashMap::new();
360 for sub in self.subs.values() {
361 if sub.monitored_property != Some(property)
362 || sub.expires_at.is_some_and(|expires| expires <= now)
363 {
364 continue;
365 }
366 let Some(increment) = sub
367 .cov_increment
368 .map(f64::from)
369 .filter(|increment| increment.is_finite())
370 else {
371 continue;
372 };
373 let increment = increment.max(0.0);
374 finest
375 .entry(sub.monitored_object_identifier)
376 .and_modify(|finest: &mut f64| *finest = finest.min(increment))
377 .or_insert(increment);
378 }
379 finest
380 }
381
382 pub fn is_current(&self, snapshot: &CovSubscriptionSnapshot) -> bool {
384 self.remaining_lifetime(snapshot, runtime_clock::now())
385 .and_then(CovTimeRemaining::wire_seconds)
386 .is_some()
387 }
388
389 pub fn should_notify(
396 sub: &CovSubscription,
397 current_value: Option<&CovSample>,
398 cov_increment: Option<f64>,
399 ) -> bool {
400 let Some(current) = current_value else {
401 return true;
402 };
403 current.reports(
404 sub.last_notified_observation
405 .as_ref()
406 .map(|observation| observation.sample()),
407 cov_increment,
408 true,
409 )
410 }
411
412 pub fn len(&self) -> usize {
414 self.subs.len()
415 }
416
417 pub fn is_empty(&self) -> bool {
419 self.subs.is_empty()
420 }
421}