Skip to main content

bacnet_objects/access_control/
point.rs

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