Skip to main content

bacnet_server/server/
local_access_inputs.rs

1//! The application's route to the inputs of the access-control objects the
2//! server holds (#1132): an Access Point's access events, a Credential Data
3//! Input's reads and an Access Door's hardware state.
4//!
5//! Each takes a typed record whose values change together, as one local
6//! write through `write_local_as`: the object takes the record under the
7//! database guard, then the event pass and the COV fanout run as they do
8//! after any other local write, and the Tokio-runtime rule of #1367 holds.
9//! The records and their rules are in `bacnet_objects::access_control`.
10
11use super::*;
12use bacnet_objects::access_control::{
13    AccessControlInput, AccessEventReport, CredentialReadReport, DoorStateReport,
14};
15
16impl<T: TransportPort + 'static> BACnetServer<T> {
17    /// Report an access event at an Access Point: Access_Event,
18    /// Access_Event_Tag, Access_Event_Time, Access_Event_Credential and
19    /// Access_Event_Authentication_Factor change together (Clause
20    /// 12.31.27.1), checked as `AccessPointObject::set_access_event` checks
21    /// them (VALUE_OUT_OF_RANGE for a credential reference, factor or event
22    /// it refuses; an event is a named BACnetAccessEvent or a proprietary
23    /// one from 512 to 65535). A report without a time is stamped from the
24    /// Device clock.
25    ///
26    /// Access_Event_Time is the point's Table 13-1 trigger, so each event
27    /// with a new time sends the SubscribeCOV report, carrying all five
28    /// values. A time the point stamps itself is strictly after the one it
29    /// served, so several events of one transaction each move the time and
30    /// each report. While Out_Of_Service is TRUE (Clause 12.31.8), or
31    /// Reliability isn't NO_FAULT_DETECTED (Clause 12.31.7), the point
32    /// performs no authentication, so an event is refused with PROPERTY /
33    /// WRITE_ACCESS_DENIED and nothing changes. An unknown object fails
34    /// with OBJECT / UNKNOWN_OBJECT and any object other than an Access
35    /// Point with OBJECT / OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED. The Python
36    /// binding exposes this as `BACnetServer.report_access_event_local`.
37    ///
38    /// Like [`write_local`](Self::write_local), it must be awaited inside a
39    /// Tokio runtime, failing before anything is written outside one, and a
40    /// caller dropped once the event has committed skips none of the work
41    /// it owes (#1367).
42    pub async fn report_access_event_local(
43        &self,
44        oid: &ObjectIdentifier,
45        report: AccessEventReport,
46    ) -> Result<(), Error> {
47        self.report_access_input(oid, AccessControlInput::AccessEvent(report))
48            .await
49    }
50
51    /// Report a factor a Credential Data Input's reader has read:
52    /// Present_Value and Update_Time change together (Clause 12.36.11). The
53    /// factor must name one of the reader's declared formats with its class,
54    /// or be the UNDEFINED or ERROR factor with class 0, else
55    /// VALUE_OUT_OF_RANGE. A report without a time is stamped from the
56    /// Device clock, or the object's next sequence number without a usable
57    /// clock, so reading the same card again still moves Update_Time.
58    ///
59    /// Update_Time is the reader's Table 13-1 trigger, so each read with a
60    /// new time sends the SubscribeCOV report. While Out_Of_Service is TRUE
61    /// a client may be simulating the reader, so the read replaces the
62    /// reader's values put aside, as `set_present_value` does (#1168): the
63    /// served values, and with them the COV report, stay as they are, and
64    /// the return to service serves the latest read. The other errors are
65    /// those of [`Self::report_access_event_local`], for a Credential Data
66    /// Input. The Python binding exposes this as
67    /// `BACnetServer.report_credential_read_local`.
68    ///
69    /// The Tokio-runtime rule of [`write_local`](Self::write_local) applies.
70    pub async fn report_credential_read_local(
71        &self,
72        oid: &ObjectIdentifier,
73        report: CredentialReadReport,
74    ) -> Result<(), Error> {
75        self.report_access_input(oid, AccessControlInput::CredentialRead(report))
76            .await
77    }
78
79    /// Report an Access Door's hardware state: whichever of Door_Status,
80    /// Lock_Status and Door_Alarm_State the report gives, together. Each
81    /// must fall in its production, and the alarm state be NORMAL or a
82    /// member of Alarm_Values or Fault_Values and no member of
83    /// Masked_Alarm_Values, else VALUE_OUT_OF_RANGE and nothing changes.
84    ///
85    /// Door_Alarm_State is the door's Table 13-1 trigger, so a change of it
86    /// sends the SubscribeCOV report, and the event pass after the write
87    /// runs the door's CHANGE_OF_STATE and FAULT_STATE algorithms on it at
88    /// once. Secured_Status follows the new values on its next read. While
89    /// Out_Of_Service is TRUE a client may be simulating the door, so the
90    /// report replaces the device values put aside, as the door's setters do
91    /// (#1131): the served values stay, so no COV report or event follows,
92    /// and the return to service serves the latest values reported. The
93    /// other errors are those of
94    /// [`Self::report_access_event_local`], for an Access Door. The Python
95    /// binding exposes this as `BACnetServer.report_door_state_local`.
96    ///
97    /// The Tokio-runtime rule of [`write_local`](Self::write_local) applies.
98    pub async fn report_door_state_local(
99        &self,
100        oid: &ObjectIdentifier,
101        report: DoorStateReport,
102    ) -> Result<(), Error> {
103        self.report_access_input(oid, AccessControlInput::DoorState(report))
104            .await
105    }
106
107    /// One access-control input, as a local write.
108    async fn report_access_input(
109        &self,
110        oid: &ObjectIdentifier,
111        input: AccessControlInput,
112    ) -> Result<(), Error> {
113        self.write_local_as(
114            oid,
115            LocalWrite::ApplicationAccessInput(&input),
116            PropertyValue::Null,
117            None,
118        )
119        .await
120    }
121}