1use super::event_recipient_route::ConfirmedRecipientRoute;
2use super::notification_transactions::{
3 AuditFailureContext, AuditFailureTicket, NotificationReservation, NotificationReserveError,
4};
5use super::*;
6use crate::handlers::{WriteCommitObserver, WriteTarget};
7use bacnet_objects::audit::AuditReporterStatus;
8use bacnet_services::audit::AuditNotificationRequest;
9use bacnet_types::constructed::{
10 AuditPropertyReference, BACnetAddress, BACnetAuditNotification, BACnetRecipient,
11};
12use bacnet_types::enums::{AuditLevel, AuditOperation};
13
14const DELIVERY_TIMEOUT: Duration = Duration::from_secs(3);
15
16#[path = "audit_reporter_read.rs"]
17mod read;
18
19#[path = "audit_reporter_dcc.rs"]
20mod dcc;
21
22#[path = "audit_reporter_write.rs"]
23mod write;
24use write::WriteSelection;
25
26#[path = "audit_policy_precommit.rs"]
27mod policy_precommit;
28
29#[derive(Debug, Clone)]
138pub struct AuditReportersConfig {
139 pub reporters: Vec<ObjectIdentifier>,
142}
143
144impl AuditReportersConfig {
145 pub(super) fn canonicalize(&mut self) -> Result<(), Error> {
146 if self.reporters.is_empty()
147 || self.reporters.len() > 64
148 || self.reporters.iter().any(|oid| {
149 oid.object_type() != ObjectType::AUDIT_REPORTER
150 || oid.instance_number() == ObjectIdentifier::MAX_INSTANCE
151 })
152 {
153 return Err(Error::Encoding(
154 "target Audit requires 1..=64 concrete Reporter identifiers".into(),
155 ));
156 }
157 self.reporters.sort_by_key(|oid| oid.instance_number());
158 if self.reporters.windows(2).any(|pair| pair[0] == pair[1]) {
159 return Err(Error::Encoding(
160 "duplicate target Audit Reporter identifier".into(),
161 ));
162 }
163 Ok(())
164 }
165}
166
167impl<T: TransportPort + 'static> ServerBuilder<T> {
168 pub fn audit_reporters(mut self, profile: AuditReportersConfig) -> Self {
170 self.config.audit_reporters = Some(profile);
171 self
172 }
173}
174
175impl BipServerBuilder {
176 pub fn audit_reporters(mut self, profile: AuditReportersConfig) -> Self {
178 self.config.audit_reporters = Some(profile);
179 self
180 }
181}
182
183#[doc(hidden)]
186pub fn valid_bip_audit_address(address: &BACnetAddress) -> bool {
187 if address.network_number != 0 || address.mac_address.len() != 6 {
188 return false;
189 }
190 let mac = address.mac_address.as_slice();
191 let ip = std::net::Ipv4Addr::new(mac[0], mac[1], mac[2], mac[3]);
192 !ip.is_unspecified()
193 && !ip.is_multicast()
194 && !ip.is_broadcast()
195 && mac[0] != 0
196 && mac[0] < 240
197 && (mac[4] != 0 || mac[5] != 0)
198}
199
200fn recipient(db: &ObjectDatabase, device: ObjectIdentifier) -> Option<BACnetRecipient> {
201 let PropertyValue::ApplicationData(value) = db
202 .get(&device)?
203 .read_property(PropertyIdentifier::AUDIT_NOTIFICATION_RECIPIENT, None)
204 .ok()?
205 else {
206 return None;
207 };
208 bacnet_encoding::constructed::decode_recipient(&value, 0)
209 .ok()
210 .map(|(value, _)| value)
211}
212
213#[derive(Clone, Copy)]
216pub(super) struct RequestSource<'s> {
217 pub(super) mac: &'s [u8],
218 pub(super) network: Option<&'s NpduAddress>,
219 pub(super) invoke_id: u8,
220 pub(super) local_network: Option<u16>,
224}
225
226pub(super) struct WriteAudit<'a, T: TransportPort> {
227 config: &'a ServerConfig,
228 network: &'a Arc<NetworkLayer<T>>,
229 transactions: &'a Arc<NotificationTransactions>,
230 source: BACnetRecipient,
231 invoke_id: Option<u8>,
232 pending: Option<PendingWrite>,
233}
234
235struct PendingWrite {
236 delay: Option<bacnet_objects::audit::AuditSendDelay>,
237 selection: Option<WriteSelection>,
238 route: Option<Arc<ConfirmedRecipientRoute>>,
239 failure: Option<AuditFailureTicket<Arc<ConfirmedRecipientRoute>>>,
240 completion: bacnet_objects::audit::AuditDeliveryToken,
241 status: Arc<AuditReporterStatus>,
242 confirmed: bool,
243 notification: BACnetAuditNotification,
244}
245
246impl<'a, T: TransportPort + 'static> WriteAudit<'a, T> {
247 pub(super) fn write_source(&self) -> bacnet_objects::device::AuditWriteSource {
248 bacnet_objects::device::AuditWriteSource {
249 device: self.source.clone(),
250 invoke_id: self.invoke_id.expect("network write source"),
251 }
252 }
253
254 pub(super) fn local(
255 config: &'a ServerConfig,
256 network: &'a Arc<NetworkLayer<T>>,
257 transactions: &'a Arc<NotificationTransactions>,
258 db: &ObjectDatabase,
259 ) -> Option<Self> {
260 Some(Self {
261 config,
262 network,
263 transactions,
264 source: BACnetRecipient::Device(db.local_device().identifier()?),
265 invoke_id: None,
266 pending: None,
267 })
268 }
269
270 pub(super) async fn new(
271 config: &'a ServerConfig,
272 network: &'a Arc<NetworkLayer<T>>,
273 transactions: &'a Arc<NotificationTransactions>,
274 bindings: &Arc<RwLock<DeviceBindingTable>>,
275 request: RequestSource<'_>,
276 ) -> Self {
277 let RequestSource {
278 mac,
279 network: source_network,
280 invoke_id,
281 local_network,
282 } = request;
283 let source = Self::requester(
284 config,
285 transactions,
286 bindings,
287 mac,
288 source_network,
289 local_network,
290 )
291 .await;
292 Self::requested_by(config, network, transactions, source, Some(invoke_id))
293 }
294
295 pub(super) fn requested_by(
298 config: &'a ServerConfig,
299 network: &'a Arc<NetworkLayer<T>>,
300 transactions: &'a Arc<NotificationTransactions>,
301 source: BACnetRecipient,
302 invoke_id: Option<u8>,
303 ) -> Self {
304 Self {
305 config,
306 network,
307 transactions,
308 source,
309 invoke_id,
310 pending: None,
311 }
312 }
313
314 pub(super) async fn requester(
320 config: &ServerConfig,
321 transactions: &Arc<NotificationTransactions>,
322 bindings: &Arc<RwLock<DeviceBindingTable>>,
323 source_mac: &[u8],
324 source_network: Option<&NpduAddress>,
325 local_network: Option<u16>,
326 ) -> BACnetRecipient {
327 let known_source = if config.audit_reporters.is_some() {
330 bindings
331 .read()
332 .await
333 .source_device(source_mac, source_network, local_network, |mac| {
334 transactions
335 .audit_routes
336 .get()
337 .is_some_and(|routes| routes.is_group(mac))
338 })
339 } else {
340 None
341 };
342 known_source
343 .map(BACnetRecipient::Device)
344 .unwrap_or_else(|| {
345 BACnetRecipient::Address(BACnetAddress {
346 network_number: source_network.map_or(0, |source| source.network),
347 mac_address: source_network.map_or_else(
348 || MacAddr::from_slice(source_mac),
349 |source| source.mac_address.clone(),
350 ),
351 })
352 })
353 }
354}
355
356impl<T: TransportPort + 'static> WriteAudit<'_, T> {
357 pub(super) fn select(
358 &self,
359 target: Option<ObjectIdentifier>,
360 kind: ObjectType,
361 ) -> Option<bacnet_objects::audit::SelectedAuditReporter> {
362 self.transactions
363 .audit_association
364 .get()?
365 .select(target, kind, None)
366 }
367 pub(super) fn select_write(
371 &self,
372 db: &ObjectDatabase,
373 target: ObjectIdentifier,
374 ) -> Option<(bacnet_objects::audit::SelectedAuditReporter, bool)> {
375 let mandatory = db
376 .get(&target)
377 .and_then(|object| object.audit_reporter_internal())
378 .is_some_and(|reporter| reporter.configuration_internal().enabled());
379 let association = self.transactions.audit_association.get()?;
380 let selected = if mandatory {
381 association.select_change(target, None)
382 } else {
383 association.select(Some(target), target.object_type(), None)
384 }?;
385 Some((selected, mandatory))
386 }
387 pub(super) fn file_completed(
390 &mut self,
391 db: &mut ObjectDatabase,
392 target: ObjectIdentifier,
393 result: &Result<(), Error>,
394 ) {
395 self.pending = None;
396 let Some((selected_reporter, mandatory)) = self.select_write(db, target) else {
397 return;
398 };
399 let reporter = &selected_reporter.configuration;
400 let device = db.local_device().identifier();
401 let route = device
402 .and_then(|device| recipient(db, device))
403 .and_then(|value| self.transactions.audit_routes.get()?.resolve(&value));
404 let status = Arc::clone(&selected_reporter.status);
405 status.set_configured(device.is_some() && route.is_some());
406 let Some(device) = device else { return };
407 let policy = db
408 .get(&target)
409 .map(|object| object.audit_object_policy_internal())
410 .unwrap_or_default()
411 .effective_internal(reporter);
412 let selected = if mandatory {
413 true
414 } else {
415 policy.reports(AuditOperation::WRITE, None, None)
416 };
417 if !selected {
418 return;
419 }
420 self.pending = Some(PendingWrite {
421 delay: (!mandatory)
422 .then_some(reporter.maximum_send_delay)
423 .flatten(),
424 selection: None,
425 failure: self.failure_ticket(&status, reporter.confirmed, device, route.clone()),
426 route,
427 completion: status.begin_delivery(),
428 status,
429 confirmed: reporter.confirmed,
430 notification: BACnetAuditNotification {
431 source_timestamp: None,
432 target_timestamp: None,
433 source_device: self.source.clone(),
434 source_object: None,
435 operation: AuditOperation::WRITE,
436 source_comment: None,
437 target_comment: None,
438 invoke_id: self.invoke_id,
439 source_user_id: None,
440 source_user_role: None,
441 target_device: BACnetRecipient::Device(device),
442 target_object: Some(target),
443 target_property: None,
444 target_priority: None,
445 target_value: None,
446 current_value: None,
447 result: None,
448 },
449 });
450 match result {
451 Ok(()) => self.committed(db),
452 Err(error) => self.failed(db, error),
453 }
454 }
455
456 pub(super) fn before_list(
460 &mut self,
461 db: &ObjectDatabase,
462 request: &bacnet_services::list_manipulation::ListElementRequest,
463 current: Option<&PropertyValue>,
464 ) {
465 self.pending = None;
466 let Some((selected_reporter, mandatory)) = self.select_write(db, request.object_identifier)
467 else {
468 return;
469 };
470 let reporter = &selected_reporter.configuration;
471 let device = db.local_device().identifier();
472 let route = device
473 .and_then(|device| recipient(db, device))
474 .and_then(|value| self.transactions.audit_routes.get()?.resolve(&value));
475 let status = Arc::clone(&selected_reporter.status);
476 status.set_configured(device.is_some() && route.is_some());
477 let Some(device) = device else { return };
478 let policy = db
479 .get(&request.object_identifier)
480 .map(|object| object.audit_object_policy_internal())
481 .unwrap_or_default()
482 .effective_internal(reporter);
483 let selected = if mandatory {
484 true
485 } else {
486 policy.reports(
487 AuditOperation::WRITE,
488 Some(request.property_identifier),
489 None,
490 )
491 };
492 if !selected {
493 return;
494 }
495 self.pending = Some(PendingWrite {
496 delay: (!mandatory)
497 .then_some(reporter.maximum_send_delay)
498 .flatten(),
499 selection: None,
500 failure: self.failure_ticket(&status, reporter.confirmed, device, route.clone()),
501 route,
502 completion: status.begin_delivery(),
503 status,
504 confirmed: reporter.confirmed,
505 notification: BACnetAuditNotification {
506 source_timestamp: None,
507 target_timestamp: None,
508 source_device: self.source.clone(),
509 source_object: None,
510 operation: AuditOperation::WRITE,
511 source_comment: None,
512 target_comment: None,
513 invoke_id: self.invoke_id,
514 source_user_id: None,
515 source_user_role: None,
516 target_device: BACnetRecipient::Device(device),
517 target_object: Some(request.object_identifier),
518 target_property: Some(AuditPropertyReference {
519 property_identifier: request.property_identifier,
520 property_array_index: request.property_array_index.map(u64::from),
521 }),
522 target_priority: None,
523 target_value: (request.list_of_elements.len() <= 32
524 && bacnet_encoding::constructed::validate_tlv_sequence(
525 &request.list_of_elements,
526 "list delta",
527 )
528 .is_ok())
529 .then(|| request.list_of_elements.clone()),
530 current_value: current.and_then(small_value).filter(|bytes| {
531 bacnet_encoding::constructed::validate_tlv_sequence(bytes, "list pre-image")
532 .is_ok()
533 }),
534 result: None,
535 },
536 });
537 }
538
539 pub(super) fn before_lifecycle(
542 &mut self,
543 db: &ObjectDatabase,
544 operation: AuditOperation,
545 target: Option<ObjectIdentifier>,
546 kind: ObjectType,
547 use_object_policy: bool,
548 ) {
549 self.pending = None;
550 let Some(selected_reporter) = self.select(target, kind) else {
551 return;
552 };
553 let reporter = &selected_reporter.configuration;
554 let device = db.local_device().identifier();
555 let route = device
556 .and_then(|device| recipient(db, device))
557 .and_then(|value| self.transactions.audit_routes.get()?.resolve(&value));
558 let status = Arc::clone(&selected_reporter.status);
559 status.set_configured(device.is_some() && route.is_some());
560 let Some(device) = device else { return };
561 let selected = target.map_or_else(
562 || reporter.monitors_unassigned(kind),
563 |oid| reporter.monitors(oid),
564 );
565 let policy = target
566 .filter(|_| use_object_policy)
567 .and_then(|oid| db.get(&oid))
568 .map(|object| object.audit_object_policy_internal())
569 .unwrap_or_default()
570 .effective_internal(reporter);
571 if !selected || !policy.reports(operation, None, None) {
572 return;
573 }
574 self.pending = Some(PendingWrite {
575 delay: reporter.maximum_send_delay,
576 selection: None,
577 failure: self.failure_ticket(&status, reporter.confirmed, device, route.clone()),
578 route,
579 completion: status.begin_delivery(),
580 status,
581 confirmed: reporter.confirmed,
582 notification: BACnetAuditNotification {
583 source_timestamp: None,
584 target_timestamp: None,
585 source_device: self.source.clone(),
586 source_object: None,
587 operation,
588 source_comment: None,
589 target_comment: None,
590 invoke_id: self.invoke_id,
591 source_user_id: None,
592 source_user_role: None,
593 target_device: BACnetRecipient::Device(device),
594 target_object: target,
595 target_property: None,
596 target_priority: None,
597 target_value: None,
598 current_value: None,
599 result: None,
600 },
601 });
602 }
603
604 pub(super) fn lifecycle_completed(
605 &mut self,
606 db: &mut ObjectDatabase,
607 result: &Result<(), Error>,
608 ) {
609 match result {
610 Ok(()) => self.committed(db),
611 Err(error) => self.failed(db, error),
612 }
613 }
614
615 fn complete(&mut self, db: &mut ObjectDatabase, result: Option<(ErrorClass, ErrorCode)>) {
616 let Some(mut pending) = self.pending.take() else {
617 return;
618 };
619 if pending
620 .selection
621 .as_ref()
622 .is_some_and(|selection| !selection.selected(db, result.is_none()))
623 {
624 return;
625 }
626 if pending.route.is_none() || pending.failure.is_none() {
627 return;
628 }
629 pending.notification.result = result;
632 pending.notification.target_timestamp =
633 Some(super::event_timestamp::sample_event_timestamp(db).timestamp);
634 self.admit(pending);
635 }
636
637 fn admit(&self, pending: PendingWrite) {
638 if let Some(delay) = pending.delay.filter(|delay| delay.seconds() != 0) {
639 let completion = DeliveryCompletion {
640 status: Arc::clone(&pending.status),
641 epoch: pending.completion,
642 finished: false,
643 };
644 if let Some(queue) = self
645 .transactions
646 .audit_batch
647 .get()
648 .and_then(std::sync::Weak::upgrade)
649 {
650 let mut record = BytesMut::new();
651 if bacnet_encoding::constructed::encode_audit_notification(
652 &pending.notification,
653 &mut record,
654 )
655 .is_err()
656 {
657 return;
658 }
659 let queued = super::audit_batch_queue::QueuedAudit::new(
660 completion,
661 record,
662 pending
663 .notification
664 .target_timestamp
665 .clone()
666 .expect("completed record"),
667 pending.failure.clone().expect("captured ordinary record"),
668 delay,
669 );
670 if queue.enqueue(queued).is_err() {
671 self.resource_drop(&pending);
672 }
673 return;
674 }
675 }
676 let Some(route) = pending.route.clone() else {
677 return;
678 };
679 let completion = DeliveryCompletion {
680 status: Arc::clone(&pending.status),
681 epoch: pending.completion,
682 finished: false,
683 };
684 let Some(mut bytes) = encode_notification(
687 &pending.notification,
688 pending.confirmed,
689 self.config.max_apdu_length,
690 0,
691 ) else {
692 return;
693 };
694 let permit = match self.transactions.try_admit_audit() {
695 Ok(permit) => permit,
696 Err(tokio::sync::TryAcquireError::NoPermits) => {
697 self.resource_drop(&pending);
698 return;
699 }
700 Err(tokio::sync::TryAcquireError::Closed) => return,
701 };
702 let confirmed = pending.confirmed;
703 let reserved = if confirmed {
704 match self.transactions.reserve(
705 route.canonical_peer.clone(),
706 ConfirmedServiceChoice::CONFIRMED_AUDIT_NOTIFICATION,
707 ) {
708 Ok(reserved) => Some(reserved),
709 Err(NotificationReserveError::Coordinator(
710 bacnet_endpoint_core::coordinator::ReserveError::Exhausted,
711 )) => {
712 self.resource_drop(&pending);
713 return;
714 }
715 Err(_) => return,
716 }
717 } else {
718 None
719 };
720 if let Some((operation, _)) = &reserved {
721 let Some(encoded) = encode_notification(
722 &pending.notification,
723 confirmed,
724 self.config.max_apdu_length,
725 operation.invoke_id(),
726 ) else {
727 return;
728 };
729 bytes = encoded;
730 }
731 let network = Arc::clone(self.network);
732 let deadline = tokio::time::Instant::now() + DELIVERY_TIMEOUT;
734 let context_pin = pending.failure;
735 self.transactions.spawn(async move {
736 let _permit = permit;
737 let _context_pin = context_pin;
738 let delivered = deliver(&network, &route, &bytes, reserved, deadline).await;
739 completion.finish(delivered);
740 });
741 }
742}
743
744#[path = "audit_delivery.rs"]
745mod delivery;
746pub(super) use delivery::{
747 deliver, deliver_global_broadcast, deliver_observed, encode_notification, DeliveryCompletion,
748};
749
750pub(super) fn small_value(value: &PropertyValue) -> Option<Vec<u8>> {
753 fn append(value: &PropertyValue, out: &mut BytesMut, remaining: &mut usize) -> Option<()> {
754 if *remaining == 0 || out.len() > 32 {
755 return None;
756 }
757 *remaining -= 1;
758 match value {
759 PropertyValue::List(values) => {
760 if values.len() > 32 {
761 return None;
762 }
763 for value in values {
764 append(value, out, remaining)?;
765 }
766 }
767 PropertyValue::CharacterString(value) if value.len() > 32 => return None,
768 PropertyValue::OctetString(value) | PropertyValue::ApplicationData(value)
769 if value.len() > 32 =>
770 {
771 return None
772 }
773 PropertyValue::BitString { data, .. } if data.len() > 32 => return None,
774 _ => encode_property_value(out, value).ok()?,
775 }
776 (out.len() <= 32).then_some(())
777 }
778 let mut bytes = BytesMut::new();
779 append(value, &mut bytes, &mut 64)?;
780 Some(bytes.to_vec())
781}
782
783#[path = "audit_reporter_failure.rs"]
784mod failure;
785pub(super) use failure::record_resource_drop;