Skip to main content

bacnet_objects/access_control/
point_event_batch.rs

1//! Native atomic event staging for one completed local authorization attempt.
2
3use super::*;
4use crate::access_control::point_event_time::{next_event_time_from_frame, stamped_time_advances};
5use crate::clock::ClockFrame;
6
7/// One installed access event, with the state needed to report it after commit.
8///
9/// These snapshots preserve this attempt's order; they do not impose an ordering
10/// on notification delivery between independent transactions.
11pub struct CommittedAccessEvent {
12    /// The installed report, with an actual timestamp. Absent credential/factor
13    /// fields retain the existing no-credential/UNDEFINED representation.
14    pub report: AccessEventReport,
15    /// COV-readable intermediate state, independent of later point mutations.
16    pub snapshot: Box<dyn BACnetObject>,
17    /// Coherent Device-clock observation made while staging this event. An
18    /// absent/invalid clock does not acquire a later timestamp during capture.
19    pub clock_frame: Option<ClockFrame>,
20}
21
22impl AccessPointObject {
23    pub(super) fn record_event_batch(
24        &mut self,
25        reports: &[AccessEventReport],
26    ) -> Result<Vec<CommittedAccessEvent>, Error> {
27        if self.out_of_service || self.reliability != Reliability::NO_FAULT_DETECTED {
28            return Err(common::write_access_denied_error());
29        }
30        if reports.len() > 4 {
31            return Err(common::value_out_of_range_error());
32        }
33        let mut staged = self.clone();
34        let mut stamps = vec![self.access_event_time.clone()];
35        let mut committed = Vec::with_capacity(reports.len());
36        for input in reports {
37            let mut report = input.clone();
38            let frame = staged
39                .clock
40                .as_deref()
41                .and_then(ClockReader::read_clock)
42                .filter(|frame| frame.is_valid_actual_datetime());
43            if report.time.is_none() {
44                let next = next_event_time_from_frame(frame, &staged.access_event_time);
45                if !stamped_time_advances(&staged.access_event_time, &next) {
46                    return Err(common::value_out_of_range_error());
47                }
48                report.time = Some(next);
49            }
50            // Reuse the single-report reference/factor/event validation on the
51            // detached state; a later failure cannot install a successful prefix.
52            staged.set_access_event(report.clone())?;
53            if stamps.contains(&staged.access_event_time) {
54                return Err(common::value_out_of_range_error());
55            }
56            stamps.push(staged.access_event_time.clone());
57            committed.push(CommittedAccessEvent {
58                report,
59                snapshot: Box::new(staged.clone()),
60                clock_frame: frame,
61            });
62        }
63        *self = staged;
64        Ok(committed)
65    }
66}