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// ---------------------------------------------------------------------------