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