Skip to main content

bacnet_objects/access_control/
point.rs

1use alloc::sync::Arc;
2
3use bacnet_encoding::constructed::encode_authentication_factor;
4use bacnet_types::constructed::{BACnetAuthenticationFactor, BACnetAuthenticationPolicy};
5use bacnet_types::enums::AuthorizationMode;
6
7use super::credential_data_input_formats::{is_factor_type, undefined_factor};
8use super::point_authorization::Authorization;
9use super::point_event_time::next_event_time;
10use super::*;
11use crate::clock::ClockReader;
12
13#[path = "point_event_batch.rs"]
14mod event_batch;
15pub use event_batch::CommittedAccessEvent;
16
17#[cfg(test)]
18#[path = "point_event_batch_tests.rs"]
19mod event_batch_tests;
20
21// AccessPointObject (type 33)
22// ---------------------------------------------------------------------------
23
24/// The most authentication policies an Access Point holds while it serves
25/// Authentication_Policy_List and Authentication_Policy_Names, a resource
26/// cap: a count or list past it is VALUE_OUT_OF_RANGE.
27pub const MAX_AUTHENTICATION_POLICIES: u32 = 256;
28
29/// BACnet Access Point object (type 33).
30///
31/// Represents an access point (reader/controller at a door) in an access control system.
32/// The most recent access event is Access_Event; Table 12-36 has no
33/// Present_Value row, so the object serves none (#1064).
34///
35/// Each change of Out_Of_Service is an access event of the point's own
36/// (Clause 12.31.8; #1248): entering out of service records OUT_OF_SERVICE
37/// and the return to service OUT_OF_SERVICE_RELINQUISHED. Recording one
38/// follows Clause 12.31.27.1, which moves Access_Event_Tag on when an event
39/// starts a new access transaction and stamps Access_Event_Time with the
40/// current date and time. The introduction to Clause 12.31 counts an
41/// operator action as an access transaction, so each change of
42/// Out_Of_Service starts a new one: the tag moves on by one (wrapping at the
43/// top of the Unsigned range), and the time comes from the Device clock. A
44/// write that leaves Out_Of_Service as it was, NULL included, records
45/// nothing. No credential or factor belongs to an edge, so each one stores
46/// the no-credential reference in Access_Event_Credential (Clause 12.31.30)
47/// and the UNDEFINED factor in Access_Event_Authentication_Factor (Clause
48/// 12.31.31).
49///
50/// The application's own access events reach a point the server holds
51/// through `BACnetServer::report_access_event_local` (#1132), which refuses
52/// them while Out_Of_Service is TRUE: the point then performs no
53/// authentication or authorization (Clause 12.31.8), so it has no events of
54/// its own to report.
55///
56/// Authentication_Status is the status the application reports for its
57/// authentication process (Clause 12.31.9; #1284), READY until it reports
58/// another. While Out_Of_Service is TRUE no authentication runs, so the
59/// property reads DISABLED; the reported status is kept and served again on
60/// the return to service.
61///
62/// Each event the point stamps itself, an edge or a reported event with no
63/// time, gets an Access_Event_Time strictly after the one served before it,
64/// so every event moves the time (Clause 12.31.29), several events of one
65/// transaction included: the clock's time, or one hundredth past the time
66/// served when the clock isn't later, and with no usable clock the next
67/// sequence number (Clause 21.6). The module `point_event_time` has the
68/// rule.
69///
70/// Access_Event_Time is the Table 13-1 trigger, and each edge moves it, so
71/// each edge sends the SubscribeCOV report, which also carries the changed
72/// OUT_OF_SERVICE flag. That holds for a round trip in one
73/// WritePropertyMultiple too, where Status_Flags ends where it started. The
74/// point runs no intrinsic reporting (no ACCESS_EVENT algorithm;
75/// Event_State stays NORMAL), so an edge raises no event notification.
76///
77/// Active_Authentication_Policy, Number_Of_Authentication_Policies,
78/// Authentication_Policy_List, Authentication_Policy_Names,
79/// Authorization_Mode and Priority_For_Writing hold the settings the
80/// application's authentication and authorization work from (#1307, #1325).
81/// A client picks the policy in effect and the authorization mode; the
82/// application sets the policies, the modes it carries out and the door
83/// command priority. The module `point_authorization` has the rules.
84///
85/// Reliability is CONFIGURATION_ERROR while Active_Authentication_Policy is
86/// zero, which happens when the policies leave none usable in effect
87/// (Clause 12.31.10), or while Authentication_Policy_List holds any invalid
88/// policy (Clause 12.31.12), and NO_FAULT_DETECTED otherwise. While it isn't
89/// NO_FAULT_DETECTED the point performs no authentication and generates no
90/// access events (Clause 12.31.7), so `report_access_event_local` refuses
91/// them. Since it can leave
92/// NO_FAULT_DETECTED, a client may write it while Out_Of_Service is TRUE
93/// to simulate a fault, and only then (Clause 12.31.8): an Enumerated
94/// inside the BACnetReliability production, else INVALID_DATA_TYPE or
95/// VALUE_OUT_OF_RANGE, and WRITE_ACCESS_DENIED in service. Out of service
96/// the value served stays decoupled from the policies: it starts as the
97/// value served at the edge, follows only those writes, and the return to
98/// service serves the derived value again. Status_Flags carries FAULT from
99/// the value served.
100#[derive(Clone)]
101pub struct AccessPointObject {
102    oid: ObjectIdentifier,
103    name: String,
104    description: String,
105    access_event: AccessEvent,
106    access_event_tag: u64,
107    access_event_time: BACnetTimeStamp,
108    access_event_credential: BACnetDeviceObjectReference,
109    /// Access_Event_Authentication_Factor, the UNDEFINED factor until an
110    /// event carries one.
111    access_event_authentication_factor: BACnetAuthenticationFactor,
112    /// The status the application reports; DISABLED is served instead while
113    /// Out_Of_Service is TRUE.
114    authentication_status: AuthenticationStatus,
115    access_doors: Vec<BACnetDeviceObjectReference>,
116    /// The policy, authorization mode and door command priority settings.
117    authorization: Authorization,
118    event_state: EventState,
119    status_flags: StatusFlags,
120    out_of_service: bool,
121    /// Reliability as served: derived from the active policy in service, a
122    /// client's simulation out of service.
123    reliability: Reliability,
124    clock: Option<Arc<dyn ClockReader>>,
125}
126
127impl AccessPointObject {
128    /// Create a new Access Point object.
129    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
130        let oid = ObjectIdentifier::new(ObjectType::ACCESS_POINT, instance)?;
131        Ok(Self {
132            oid,
133            name: name.into(),
134            description: String::new(),
135            access_event: AccessEvent::NONE,
136            access_event_tag: 0,
137            access_event_time: never_updated(),
138            access_event_credential: no_credential(),
139            access_event_authentication_factor: undefined_factor(),
140            authentication_status: AuthenticationStatus::READY,
141            access_doors: Vec::new(),
142            authorization: Authorization::new(),
143            event_state: EventState::NORMAL,
144            status_flags: StatusFlags::empty(),
145            out_of_service: false,
146            reliability: Reliability::NO_FAULT_DETECTED,
147            clock: None,
148        })
149    }
150
151    /// Record the most recent access event: Access_Event, Access_Event_Tag,
152    /// Access_Event_Time, Access_Event_Credential and
153    /// Access_Event_Authentication_Factor, which the application's access
154    /// logic produces. They change together, as Clause 12.31.27.1 has them
155    /// stored for each event; [`AccessEventReport`] documents each field.
156    ///
157    /// Access_Event_Time is a `BACnetTimeStamp` (Clause 12.31.29) and goes
158    /// out in its Clause 21 CHOICE form. A report without one is stamped
159    /// strictly after the time served, from the Device clock or a sequence
160    /// number, as an Out_Of_Service edge is (`point_event_time`). A change of the
161    /// time triggers a SubscribeCOV notification; the other four only ride
162    /// along (Table 13-1). Over the network all five stay read-only.
163    ///
164    /// The credential, when given, names the Access Credential object
165    /// behind the event; without one the point stores the no-credential
166    /// reference, instance 4194303, for an event no credential belongs to or
167    /// one whose credential is unknown or kept back (Clause 12.31.30). Given
168    /// explicitly, that reference carries 4194303 as the object instance
169    /// and, when it names a device, as the device instance too. The factor
170    /// is stored as given, or the UNDEFINED factor without one (Clause
171    /// 12.31.31). Refused with VALUE_OUT_OF_RANGE, with none of the five
172    /// changing: a reference to another object type, one whose device
173    /// identifier isn't a Device (#1285), one with 4194303 in only one of
174    /// the two instances, which is neither a credential nor the
175    /// no-credential reference, a factor whose format type isn't a named
176    /// BACnetAuthenticationFactorType, and an event that is neither a named
177    /// BACnetAccessEvent nor a proprietary one from 512 to 65535 (Clause
178    /// 21). These rows have no network write route, so this setter is the
179    /// only check.
180    ///
181    /// This setter takes an event in or out of service, to set a point up
182    /// before the server holds it. A running server's route,
183    /// `BACnetServer::report_access_event_local`, refuses one while
184    /// Out_Of_Service is TRUE (#1132).
185    pub fn set_access_event(&mut self, report: AccessEventReport) -> Result<(), Error> {
186        let credential = report.credential.unwrap_or_else(no_credential);
187        crate::device_reference::check_device_member(credential.device_identifier)?;
188        if credential.object_identifier.object_type() != ObjectType::ACCESS_CREDENTIAL {
189            return Err(common::value_out_of_range_error());
190        }
191        let empty = |instance: u32| instance == ObjectIdentifier::MAX_INSTANCE;
192        if credential.device_identifier.is_some_and(|device| {
193            empty(device.instance_number()) != empty(credential.object_identifier.instance_number())
194        }) {
195            return Err(common::value_out_of_range_error());
196        }
197        let factor = report
198            .authentication_factor
199            .unwrap_or_else(undefined_factor);
200        if !is_factor_type(factor.format_type) || !access_event_in_range(report.event) {
201            return Err(common::value_out_of_range_error());
202        }
203        self.access_event_time = report
204            .time
205            .unwrap_or_else(|| next_event_time(self.clock.as_deref(), &self.access_event_time));
206        self.access_event = report.event;
207        self.access_event_tag = report.tag;
208        self.access_event_credential = credential;
209        self.access_event_authentication_factor = factor;
210        Ok(())
211    }
212
213    /// Report the status of the point's authentication process
214    /// (Clause 12.31.9), read-only over the network. A value outside the
215    /// closed BACnetAuthenticationStatus production is refused with
216    /// VALUE_OUT_OF_RANGE. While Out_Of_Service is TRUE the status is kept
217    /// but DISABLED is served until the return to service.
218    pub fn set_authentication_status(&mut self, status: AuthenticationStatus) -> Result<(), Error> {
219        if status.to_raw() > AuthenticationStatus::IN_PROGRESS.to_raw() {
220            return Err(common::value_out_of_range_error());
221        }
222        self.authentication_status = status;
223        Ok(())
224    }
225
226    /// Authentication_Status as served: DISABLED while Out_Of_Service is
227    /// TRUE, else the status last reported.
228    pub fn authentication_status(&self) -> AuthenticationStatus {
229        if self.out_of_service {
230            AuthenticationStatus::DISABLED
231        } else {
232            self.authentication_status
233        }
234    }
235
236    /// Set Access_Doors, the Access Door objects whose Present_Value this
237    /// point commands once access is granted (Clause 12.31.32). A point that
238    /// commands no door keeps the array empty. A reference with no Device
239    /// identifier names an object in this device.
240    ///
241    /// Every reference must name an Access Door object, and a device
242    /// identifier, when given, a Device object (#1285); a list breaking
243    /// either rule is refused with VALUE_OUT_OF_RANGE and the doors set
244    /// before are kept. The array is read-only over the network.
245    pub fn set_access_doors(
246        &mut self,
247        doors: impl IntoIterator<Item = impl Into<BACnetDeviceObjectReference>>,
248    ) -> Result<(), Error> {
249        self.access_doors = references_to(ObjectType::ACCESS_DOOR, doors)?;
250        Ok(())
251    }
252
253    /// Set Number_Of_Authentication_Policies, how many authentication
254    /// policies the application defines (Clause 12.31.11), 1 until set. It
255    /// is read-only over the network. Zero is refused with
256    /// VALUE_OUT_OF_RANGE.
257    ///
258    /// Once [`Self::set_authentication_policies`] has set the policy arrays,
259    /// they follow the count, which can then be at most
260    /// [`MAX_AUTHENTICATION_POLICIES`] (VALUE_OUT_OF_RANGE above it): a
261    /// smaller count drops the last policies, and a larger one adds empty
262    /// policies, not enforcing order and with no timeout (Clause 12.31.12.2),
263    /// each with an empty name. An empty policy is invalid, so a grown point
264    /// reads Reliability CONFIGURATION_ERROR until the application fills the
265    /// new policies in. A count below Active_Authentication_Policy, or one
266    /// that leaves it naming an invalid policy, drops it to zero (Clause
267    /// 12.31.10) until a client writes a usable policy.
268    pub fn set_number_of_authentication_policies(&mut self, count: u32) -> Result<(), Error> {
269        self.authorization.set_policies(count)?;
270        self.refresh_reliability();
271        Ok(())
272    }
273
274    /// Set Authentication_Policy_List and Authentication_Policy_Names, the
275    /// policies the point defines, as `(name, policy)` pairs in policy
276    /// order (Clauses 12.31.12 and 12.31.13). The point serves both arrays
277    /// from then on, and Number_Of_Authentication_Policies takes the number
278    /// of pairs, since both arrays are that long (Table 12-36 footnote 1).
279    /// All three are read-only over the network. No pairs at all, or more
280    /// than [`MAX_AUTHENTICATION_POLICIES`], is refused with
281    /// VALUE_OUT_OF_RANGE, and nothing changes.
282    ///
283    /// A policy is stored as given, but only a well-formed one is usable:
284    /// at least one entry, each naming a Credential Data Input object (in a
285    /// Device, when it names a device), with indexes that start at 1 and, in
286    /// list order, either repeat (a second factor that completes the same
287    /// step) or go up by one. A client can't make an invalid policy the
288    /// active one (VALUE_OUT_OF_RANGE), and while the list holds any invalid
289    /// policy Reliability reads CONFIGURATION_ERROR (Clause 12.31.12). When
290    /// the new list leaves Active_Authentication_Policy beyond the count or
291    /// on an invalid policy, it drops to zero (Clause 12.31.10) until a
292    /// client writes a usable policy; the point doesn't pick one itself.
293    pub fn set_authentication_policies(
294        &mut self,
295        policies: impl IntoIterator<Item = (impl Into<String>, BACnetAuthenticationPolicy)>,
296    ) -> Result<(), Error> {
297        self.authorization.set_policy_list(
298            policies
299                .into_iter()
300                .map(|(name, policy)| (name.into(), policy))
301                .collect(),
302        )?;
303        self.refresh_reliability();
304        Ok(())
305    }
306
307    /// Derive Reliability from the policies, in service only: out of
308    /// service a client's simulated value stays served.
309    fn refresh_reliability(&mut self) {
310        if !self.out_of_service {
311            self.reliability = if self.authorization.active_policy() == 0
312                || self.authorization.holds_invalid_policy()
313            {
314                Reliability::CONFIGURATION_ERROR
315            } else {
316                Reliability::NO_FAULT_DETECTED
317            };
318        }
319    }
320
321    /// Whether the point serves Authentication_Policy_List and
322    /// Authentication_Policy_Names.
323    pub(super) fn serves_policy_list(&self) -> bool {
324        self.authorization.serves_policy_list()
325    }
326
327    /// Set the authorization modes the application carries out, the values
328    /// a write of Authorization_Mode can take (Clause 12.31.14). A new point
329    /// takes AUTHORIZE alone, since the point enforces no mode itself: an
330    /// application that acts on Authorization_Mode declares the other modes
331    /// it carries out here. The set must hold AUTHORIZE and the mode in
332    /// effect, and each mode must be a standard one or a proprietary one from
333    /// 64 to 65535; otherwise it is refused with VALUE_OUT_OF_RANGE and the
334    /// set before is kept. A repeated mode counts once.
335    pub fn set_supported_authorization_modes(
336        &mut self,
337        modes: impl IntoIterator<Item = AuthorizationMode>,
338    ) -> Result<(), Error> {
339        self.authorization.set_supported_modes(modes)
340    }
341
342    /// Set Priority_For_Writing, the priority at which the application
343    /// commands the Access_Doors after a grant (Clauses 12.31.32.1 and
344    /// 12.31.33), 16 until set. It is read-only over the network. A
345    /// priority outside 1..=16 is refused with VALUE_OUT_OF_RANGE.
346    pub fn set_priority_for_writing(&mut self, priority: u8) -> Result<(), Error> {
347        self.authorization.set_priority_for_writing(priority)
348    }
349
350    /// Record the access event an Out_Of_Service edge raises: a new
351    /// transaction, stamped after the time served (`point_event_time`), with
352    /// no credential and no factor.
353    fn record_out_of_service_event(&mut self, event: AccessEvent) {
354        self.access_event = event;
355        self.access_event_tag = self.access_event_tag.wrapping_add(1);
356        self.access_event_time = next_event_time(self.clock.as_deref(), &self.access_event_time);
357        self.access_event_credential = no_credential();
358        self.access_event_authentication_factor = undefined_factor();
359    }
360}
361
362/// The Access_Event_Credential of an event without a credential
363/// (Clause 12.31.30): an Access Credential identifier at instance 4194303,
364/// with no device identifier.
365fn no_credential() -> BACnetDeviceObjectReference {
366    ObjectIdentifier::new(
367        ObjectType::ACCESS_CREDENTIAL,
368        ObjectIdentifier::MAX_INSTANCE,
369    )
370    .expect("4194303 is within the instance range")
371    .into()
372}
373
374/// Whether `event` is a named BACnetAccessEvent or a proprietary one
375/// (512..=65535, Clause 21).
376fn access_event_in_range(event: AccessEvent) -> bool {
377    AccessEvent::ALL_NAMED
378        .iter()
379        .any(|&(_, named)| named == event)
380        || (512..=65_535).contains(&event.to_raw())
381}
382
383impl BACnetObject for AccessPointObject {
384    fn object_identifier(&self) -> ObjectIdentifier {
385        self.oid
386    }
387
388    fn object_name(&self) -> &str {
389        &self.name
390    }
391
392    fn read_property(
393        &self,
394        property: PropertyIdentifier,
395        array_index: Option<u32>,
396    ) -> Result<PropertyValue, Error> {
397        if let Some(result) = read_common_properties!(self, property, array_index) {
398            return result;
399        }
400        if let Some(result) = self.authorization.read(property, array_index) {
401            return result;
402        }
403        match property {
404            p if p == PropertyIdentifier::OBJECT_TYPE => {
405                Ok(PropertyValue::Enumerated(ObjectType::ACCESS_POINT.to_raw()))
406            }
407            p if p == PropertyIdentifier::ACCESS_EVENT => {
408                Ok(PropertyValue::Enumerated(self.access_event.to_raw()))
409            }
410            p if p == PropertyIdentifier::ACCESS_EVENT_TAG => {
411                Ok(PropertyValue::Unsigned(self.access_event_tag))
412            }
413            p if p == PropertyIdentifier::ACCESS_EVENT_TIME => {
414                timestamp_value(&self.access_event_time)
415            }
416            p if p == PropertyIdentifier::ACCESS_EVENT_CREDENTIAL => Ok(
417                crate::device_reference::reference_value(&self.access_event_credential),
418            ),
419            p if p == PropertyIdentifier::ACCESS_EVENT_AUTHENTICATION_FACTOR => {
420                let mut buf = BytesMut::new();
421                encode_authentication_factor(&mut buf, &self.access_event_authentication_factor);
422                Ok(PropertyValue::ApplicationData(buf.to_vec()))
423            }
424            p if p == PropertyIdentifier::AUTHENTICATION_STATUS => Ok(PropertyValue::Enumerated(
425                self.authentication_status().to_raw(),
426            )),
427            p if p == PropertyIdentifier::ACCESS_DOORS => common::read_array(
428                crate::device_reference::reference_elements(&self.access_doors),
429                array_index,
430            ),
431            p if p == PropertyIdentifier::EVENT_STATE => {
432                Ok(PropertyValue::Enumerated(self.event_state.to_raw()))
433            }
434            _ => Err(common::unknown_property_error()),
435        }
436    }
437
438    fn write_property(
439        &mut self,
440        property: PropertyIdentifier,
441        array_index: Option<u32>,
442        value: PropertyValue,
443        _priority: Option<u8>,
444    ) -> Result<(), Error> {
445        let was_out_of_service = self.out_of_service;
446        if let Some(result) =
447            common::write_out_of_service(&mut self.out_of_service, property, &value)
448        {
449            match (was_out_of_service, self.out_of_service) {
450                (false, true) => self.record_out_of_service_event(AccessEvent::OUT_OF_SERVICE),
451                (true, false) => {
452                    self.record_out_of_service_event(AccessEvent::OUT_OF_SERVICE_RELINQUISHED);
453                    self.refresh_reliability();
454                }
455                _ => {}
456            }
457            return result;
458        }
459        if let Some(result) = common::write_description(&mut self.description, property, &value) {
460            return result;
461        }
462        if property == PropertyIdentifier::RELIABILITY {
463            if !self.out_of_service {
464                return Err(common::write_access_denied_error());
465            }
466            self.reliability = simulated_reliability(&value)?;
467            return Ok(());
468        }
469        if let Some(result) = self.authorization.write(property, array_index, &value) {
470            self.refresh_reliability();
471            return result;
472        }
473        Err(crate::common::unhandled_write_error(
474            self.property_metadata().as_ref(),
475            property,
476            array_index,
477        ))
478    }
479
480    fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
481        super::metadata_topology::for_access_point_object(self)
482    }
483
484    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
485        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
486    }
487
488    /// Table 13-1 lists Access Point, so it takes SubscribeCOV; its report
489    /// leads with Access_Event, as the point has no Present_Value.
490    fn supports_cov(&self) -> bool {
491        true
492    }
493
494    fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
495        self.clock = clock;
496    }
497
498    fn supports_access_event_batch_internal(&self) -> bool {
499        true
500    }
501
502    fn record_access_event_batch_internal(
503        &mut self,
504        reports: &[AccessEventReport],
505    ) -> Result<Vec<CommittedAccessEvent>, Error> {
506        self.record_event_batch(reports)
507    }
508
509    /// Take an access event the application reports (#1132), through
510    /// [`Self::set_access_event`]. Refused with WRITE_ACCESS_DENIED while
511    /// Out_Of_Service is TRUE (Clause 12.31.8) or Reliability isn't
512    /// NO_FAULT_DETECTED (Clause 12.31.7), since the point performs no
513    /// authentication then, and any other record with
514    /// OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED.
515    fn report_access_input_internal(&mut self, input: AccessControlInput) -> Result<(), Error> {
516        let AccessControlInput::AccessEvent(report) = input else {
517            return Err(common::optional_functionality_not_supported_error());
518        };
519        if self.out_of_service || self.reliability != Reliability::NO_FAULT_DETECTED {
520            return Err(common::write_access_denied_error());
521        }
522        self.set_access_event(report)
523    }
524}
525
526// ---------------------------------------------------------------------------