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
14mod identity;
15pub use identity::*;
16pub(crate) mod active;
17mod admission;
18pub use admission::MultipleRefusal;
19mod confirmed;
20pub(crate) use confirmed::{BeginRefusal, CovRevisits};
21mod sample;
22pub use sample::CovSample;
23mod observation;
24mod observation_order;
25pub use observation::CovObservation;
26use observation_order::ObservationOwner;
27pub(crate) use observation_order::PreparedCovCompletion;
28pub(crate) mod flags;
29mod lifetime;
30pub(crate) mod multiple_reads;
31pub(crate) mod prepare;
32pub(crate) mod reported;
33pub(crate) mod timed;
34mod timed_capture;
35pub(crate) use timed_capture::TimedWriteCapture;
36pub(crate) mod value_source;
37pub use lifetime::CovTimeRemaining;
38
39mod policy;
40pub use policy::*;
41
42#[cfg(test)]
43mod identity_tests;
44#[cfg(test)]
45mod policy_tests;
46#[cfg(test)]
47mod tests;
48
49const DEFAULT_TIMED_APDU_LENGTH: usize = 1476;
51
52#[derive(Debug, Clone)]
55pub struct CovSubscription {
56 pub subscriber_mac: MacAddr,
58 pub subscriber_network: Option<NpduAddress>,
60 pub subscriber_process_identifier: u32,
62 pub monitored_object_identifier: ObjectIdentifier,
64 pub issue_confirmed_notifications: bool,
67 pub expires_at: Option<Instant>,
69 pub last_notified_observation: Option<CovObservation>,
71 pub monitored_property: Option<PropertyIdentifier>,
73 pub monitored_property_array_index: Option<u32>,
75 pub cov_increment: Option<f32>,
77 pub notification_kind: CovNotificationKind,
79 pub timestamped: bool,
81}
82
83impl CovSubscription {
84 pub fn recipient(&self) -> CovRecipient {
87 CovRecipient::from_endpoint(&self.subscriber_mac, self.subscriber_network.as_ref())
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum CovNotificationKind {
94 Single,
96 Multiple,
98}
99
100#[derive(Debug)]
102pub struct CovSubscriptionTable {
103 subs: HashMap<CovSubscriptionKey, CovSubscriptionSnapshot>,
104 generation: u64,
105 owner: Arc<ObservationOwner>,
106 peer_counts: HashMap<CovRecipient, usize>,
107 peer_indefinite_counts: HashMap<CovRecipient, usize>,
108 policy: CovPolicy,
109 counters: Arc<AtomicCovCounters>,
110 in_flight: Arc<CovInFlightTracker>,
111 revisits: Arc<CovRevisits>,
112 dispatch_turn: usize,
113 timed: timed::TimedStore,
114 #[cfg(test)]
117 live_samples: std::sync::atomic::AtomicUsize,
118}
119
120impl Default for CovSubscriptionTable {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126impl CovSubscriptionTable {
127 pub fn new() -> Self {
129 Self::with_policy(CovPolicy::default(), Arc::new(AtomicCovCounters::default()))
130 }
131
132 pub fn with_policy(policy: CovPolicy, counters: Arc<AtomicCovCounters>) -> Self {
134 let timed = timed::TimedStore::new(DEFAULT_TIMED_APDU_LENGTH, Arc::clone(&counters));
135 Self {
136 subs: HashMap::new(),
137 generation: 0,
138 owner: Arc::new(ObservationOwner::default()),
139 peer_counts: HashMap::new(),
140 peer_indefinite_counts: HashMap::new(),
141 policy: policy.sanitized(),
142 counters,
143 in_flight: Arc::new(CovInFlightTracker::default()),
144 revisits: Arc::default(),
145 dispatch_turn: 0,
146 timed,
147 #[cfg(test)]
148 live_samples: std::sync::atomic::AtomicUsize::new(0),
149 }
150 }
151
152 pub fn with_max_apdu_length(mut self, max_apdu_length: usize) -> Self {
155 self.timed = timed::TimedStore::new(max_apdu_length, Arc::clone(&self.counters));
156 self
157 }
158
159 pub(crate) fn timed(&self) -> &timed::TimedStore {
161 &self.timed
162 }
163
164 pub(crate) fn next_dispatch_turn(&mut self) -> usize {
166 let turn = self.dispatch_turn;
167 self.dispatch_turn = self.dispatch_turn.wrapping_add(1);
168 turn
169 }
170
171 pub fn policy(&self) -> &CovPolicy {
173 &self.policy
174 }
175
176 pub fn counters(&self) -> &Arc<AtomicCovCounters> {
178 &self.counters
179 }
180
181 pub fn in_flight_tracker(&self) -> &Arc<CovInFlightTracker> {
183 &self.in_flight
184 }
185
186 pub fn peer_subscription_count(&self, peer: &CovRecipient) -> usize {
188 self.peer_counts.get(peer).copied().unwrap_or(0)
189 }
190
191 pub fn peer_indefinite_count(&self, peer: &CovRecipient) -> usize {
193 self.peer_indefinite_counts.get(peer).copied().unwrap_or(0)
194 }
195
196 pub fn get_subscription(&self, key: &CovSubscriptionKey) -> Option<&CovSubscriptionSnapshot> {
198 self.subs.get(key)
199 }
200
201 pub(crate) fn multiple_context_references<'a>(
203 &'a self,
204 context: &'a MultipleContextKey,
205 ) -> impl Iterator<Item = &'a CovSubscriptionSnapshot> + 'a {
206 self.subs
207 .iter()
208 .filter(move |(key, _)| key.multiple_context() == Some(context))
209 .map(|(_, sub)| sub)
210 }
211
212 pub fn contains(&self, key: &CovSubscriptionKey) -> bool {
214 self.subs.contains_key(key)
215 }
216
217 fn remove_internal(&mut self, key: &CovSubscriptionKey, was_cancelled: bool) -> bool {
218 if let Some(sub) = self.subs.remove(key) {
219 self.timed.lock().remove(key);
220 self.revisits.forget(key);
221 let peer = sub.recipient();
222 if let Some(count) = self.peer_counts.get_mut(&peer) {
223 *count = count.saturating_sub(1);
224 if *count == 0 {
225 self.peer_counts.remove(&peer);
226 }
227 }
228 if sub.expires_at.is_none() {
229 if let Some(count) = self.peer_indefinite_counts.get_mut(&peer) {
230 *count = count.saturating_sub(1);
231 if *count == 0 {
232 self.peer_indefinite_counts.remove(&peer);
233 }
234 }
235 }
236 if was_cancelled {
237 self.counters
238 .subscriptions_cancelled
239 .fetch_add(1, Ordering::Relaxed);
240 }
241 self.counters
242 .subscriptions_active
243 .store(self.subs.len() as u64, Ordering::Relaxed);
244 true
245 } else {
246 false
247 }
248 }
249
250 pub fn unsubscribe(&mut self, key: &CovSubscriptionKey) -> bool {
252 self.remove_internal(key, true)
253 }
254
255 pub fn unsubscribe_cov_multiple_context(&mut self, context: &MultipleContextKey) {
257 let keys: Vec<_> = self
258 .subs
259 .keys()
260 .filter(|key| key.multiple_context() == Some(context))
261 .cloned()
262 .collect();
263 for key in keys {
264 self.remove_internal(&key, true);
265 }
266 }
267
268 pub fn remove_for_object(&mut self, oid: ObjectIdentifier) {
270 let to_remove: Vec<_> = self
271 .subs
272 .iter()
273 .filter(|(k, _)| k.object() == oid)
274 .map(|(k, _)| k.clone())
275 .collect();
276 for key in to_remove {
277 self.remove_internal(&key, false);
278 }
279 }
280
281 pub fn remove_peer_subscriptions(
284 &mut self,
285 mac: &[u8],
286 network: Option<&NpduAddress>,
287 ) -> usize {
288 let target = SubscriberEndpoint::new(mac, network);
289 let to_remove: Vec<_> = self
290 .subs
291 .iter()
292 .filter(|(_, entry)| entry.endpoint() == target)
293 .map(|(k, _)| k.clone())
294 .collect();
295 let count = to_remove.len();
296 for key in to_remove {
297 self.remove_internal(&key, true);
298 }
299 count
300 }
301
302 pub fn purge_expired(&mut self) -> usize {
304 let now = Instant::now();
305 let mut purged_count = 0;
306 let mut to_remove = Vec::new();
307 for (k, sub) in &self.subs {
308 if sub.expires_at.is_some_and(|exp| exp <= now) {
309 to_remove.push(k.clone());
310 }
311 }
312 for key in to_remove {
313 purged_count += usize::from(self.remove_internal(&key, false));
314 }
315 if purged_count > 0 {
316 self.counters
317 .subscriptions_purged
318 .fetch_add(purged_count as u64, Ordering::Relaxed);
319 self.counters
320 .subscriptions_active
321 .store(self.subs.len() as u64, Ordering::Relaxed);
322 }
323 purged_count
324 }
325
326 #[cfg(test)]
329 pub(crate) fn expire_all_for_test(&mut self) {
330 let now = Instant::now();
331 for entry in self.subs.values_mut() {
332 entry.subscription.expires_at = Some(now);
333 }
334 }
335
336 pub fn subscriptions_for(&mut self, oid: &ObjectIdentifier) -> Vec<&CovSubscriptionSnapshot> {
338 self.purge_expired();
339 self.subs
340 .values()
341 .filter(|sub| sub.monitored_object_identifier == *oid)
342 .collect()
343 }
344
345 pub fn is_current(&self, snapshot: &CovSubscriptionSnapshot) -> bool {
347 self.remaining_lifetime(snapshot, Instant::now())
348 .and_then(CovTimeRemaining::wire_seconds)
349 .is_some()
350 }
351
352 pub fn should_notify(
359 sub: &CovSubscription,
360 current_value: Option<&CovSample>,
361 cov_increment: Option<f64>,
362 ) -> bool {
363 let Some(current) = current_value else {
364 return true;
365 };
366 current.reports(
367 sub.last_notified_observation
368 .as_ref()
369 .map(|observation| observation.sample()),
370 cov_increment,
371 true,
372 )
373 }
374
375 pub fn len(&self) -> usize {
377 self.subs.len()
378 }
379
380 pub fn is_empty(&self) -> bool {
382 self.subs.is_empty()
383 }
384}