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