bacnet_objects/access_control/point.rs
1use std::sync::Arc;
2
3use bacnet_types::enums::AuthorizationMode;
4
5use super::point_authorization::Authorization;
6use super::*;
7use crate::clock::ClockReader;
8
9// AccessPointObject (type 33)
10// ---------------------------------------------------------------------------
11
12/// BACnet Access Point object (type 33).
13///
14/// Represents an access point (reader/controller at a door) in an access control system.
15/// The most recent access event is Access_Event; Table 12-36 has no
16/// Present_Value row, so the object serves none (#1064).
17///
18/// Each change of Out_Of_Service is an access event of the point's own
19/// (Clause 12.31.8; #1248): entering out of service records OUT_OF_SERVICE
20/// and the return to service OUT_OF_SERVICE_RELINQUISHED. Recording one
21/// follows Clause 12.31.27.1, which moves Access_Event_Tag on when an event
22/// starts a new access transaction and stamps Access_Event_Time with the
23/// current date and time. The introduction to Clause 12.31 counts an
24/// operator action as an access transaction, so each change of
25/// Out_Of_Service starts a new one: the tag moves on by one (wrapping at the
26/// top of the Unsigned range), and the time comes from the Device clock. A
27/// write that leaves Out_Of_Service as it was, NULL included, records
28/// nothing. No credential belongs to an edge, so each one stores the
29/// no-credential reference in Access_Event_Credential (Clause 12.31.30).
30/// The point doesn't serve Access_Event_Authentication_Factor.
31///
32/// Authentication_Status is the status the application reports for its
33/// authentication process (Clause 12.31.9; #1284), READY until it reports
34/// another. While Out_Of_Service is TRUE no authentication runs, so the
35/// property reads DISABLED; the reported status is kept and served again on
36/// the return to service.
37///
38/// With no usable Device clock the time takes the BACnetTimeStamp
39/// sequence-number form (Clauses 12.31.29 and 21.6) and holds the new tag:
40/// the tag itself while it fits the form's 1 to 65535, and past that the tag
41/// folded back into that range, so successive edges still differ and none
42/// reads 0, the value of an update time with no update yet.
43///
44/// Access_Event_Time is the Table 13-1 trigger, and each edge moves it, so
45/// each edge sends the SubscribeCOV report, which also carries the changed
46/// OUT_OF_SERVICE flag. That holds for a round trip in one
47/// WritePropertyMultiple too, where Status_Flags ends where it started. The
48/// point runs no intrinsic reporting (no ACCESS_EVENT algorithm;
49/// Event_State stays NORMAL), so an edge raises no event notification.
50///
51/// Active_Authentication_Policy, Number_Of_Authentication_Policies,
52/// Authorization_Mode and Priority_For_Writing hold the settings the
53/// application's authentication and authorization work from (#1307). A
54/// client picks the policy in effect and the authorization mode; the
55/// application sets the policy count, the modes it carries out and the
56/// door command priority. The module `point_authorization` has the rules.
57pub struct AccessPointObject {
58 oid: ObjectIdentifier,
59 name: String,
60 description: String,
61 access_event: AccessEvent,
62 access_event_tag: u64,
63 access_event_time: BACnetTimeStamp,
64 access_event_credential: BACnetDeviceObjectReference,
65 /// The status the application reports; DISABLED is served instead while
66 /// Out_Of_Service is TRUE.
67 authentication_status: AuthenticationStatus,
68 access_doors: Vec<BACnetDeviceObjectReference>,
69 /// The policy, authorization mode and door command priority settings.
70 authorization: Authorization,
71 event_state: EventState,
72 status_flags: StatusFlags,
73 out_of_service: bool,
74 reliability: Reliability,
75 clock: Option<Arc<dyn ClockReader>>,
76}
77
78impl AccessPointObject {
79 /// Create a new Access Point object.
80 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
81 let oid = ObjectIdentifier::new(ObjectType::ACCESS_POINT, instance)?;
82 Ok(Self {
83 oid,
84 name: name.into(),
85 description: String::new(),
86 access_event: AccessEvent::NONE,
87 access_event_tag: 0,
88 access_event_time: never_updated(),
89 access_event_credential: no_credential(),
90 authentication_status: AuthenticationStatus::READY,
91 access_doors: Vec::new(),
92 authorization: Authorization::new(),
93 event_state: EventState::NORMAL,
94 status_flags: StatusFlags::empty(),
95 out_of_service: false,
96 reliability: Reliability::NO_FAULT_DETECTED,
97 clock: None,
98 })
99 }
100
101 /// Record the most recent access event: Access_Event, Access_Event_Tag,
102 /// Access_Event_Time and Access_Event_Credential, which the
103 /// application's access logic produces. They change together, as
104 /// Clause 12.31.27.1 has them stored for each event.
105 ///
106 /// Access_Event_Time is a `BACnetTimeStamp` (Clause 12.31.29) and goes
107 /// out in its Clause 21 CHOICE form. A change of it triggers a SubscribeCOV
108 /// notification; the other three only ride along (Table 13-1). Over the
109 /// network all four stay read-only.
110 ///
111 /// `credential` names the Access Credential object behind the event.
112 /// `None` stores the no-credential reference, instance 4194303, for an
113 /// event no credential belongs to or one whose credential is unknown or
114 /// kept back (Clause 12.31.30). Given explicitly, that reference carries
115 /// 4194303 as the object instance and, when it names a device, as the
116 /// device instance too. Refused with VALUE_OUT_OF_RANGE, with none of the
117 /// four changing: a reference to another object type, one whose device
118 /// identifier isn't a Device (#1285), and one with 4194303 in only one of
119 /// the two instances, which is neither a credential nor the
120 /// no-credential reference. Access_Event_Credential has no network write
121 /// route, so this setter is the only check.
122 ///
123 /// While Out_Of_Service is TRUE the point performs no authentication or
124 /// authorization (Clause 12.31.8), so its access logic has nothing to
125 /// report until the return to service; the point leaves that to the
126 /// application rather than refusing the call.
127 pub fn set_access_event(
128 &mut self,
129 event: AccessEvent,
130 tag: u64,
131 time: BACnetTimeStamp,
132 credential: Option<BACnetDeviceObjectReference>,
133 ) -> Result<(), Error> {
134 let credential = credential.unwrap_or_else(no_credential);
135 crate::device_reference::check_device_member(credential.device_identifier)?;
136 if credential.object_identifier.object_type() != ObjectType::ACCESS_CREDENTIAL {
137 return Err(common::value_out_of_range_error());
138 }
139 let empty = |instance: u32| instance == ObjectIdentifier::MAX_INSTANCE;
140 if credential.device_identifier.is_some_and(|device| {
141 empty(device.instance_number()) != empty(credential.object_identifier.instance_number())
142 }) {
143 return Err(common::value_out_of_range_error());
144 }
145 self.access_event = event;
146 self.access_event_tag = tag;
147 self.access_event_time = time;
148 self.access_event_credential = credential;
149 Ok(())
150 }
151
152 /// Report the status of the point's authentication process
153 /// (Clause 12.31.9), read-only over the network. A value outside the
154 /// closed BACnetAuthenticationStatus production is refused with
155 /// VALUE_OUT_OF_RANGE. While Out_Of_Service is TRUE the status is kept
156 /// but DISABLED is served until the return to service.
157 pub fn set_authentication_status(&mut self, status: AuthenticationStatus) -> Result<(), Error> {
158 if status.to_raw() > AuthenticationStatus::IN_PROGRESS.to_raw() {
159 return Err(common::value_out_of_range_error());
160 }
161 self.authentication_status = status;
162 Ok(())
163 }
164
165 /// Authentication_Status as served: DISABLED while Out_Of_Service is
166 /// TRUE, else the status last reported.
167 pub fn authentication_status(&self) -> AuthenticationStatus {
168 if self.out_of_service {
169 AuthenticationStatus::DISABLED
170 } else {
171 self.authentication_status
172 }
173 }
174
175 /// Set Access_Doors, the Access Door objects whose Present_Value this
176 /// point commands once access is granted (Clause 12.31.32). A point that
177 /// commands no door keeps the array empty. A reference with no Device
178 /// identifier names an object in this device.
179 ///
180 /// Every reference must name an Access Door object, and a device
181 /// identifier, when given, a Device object (#1285); a list breaking
182 /// either rule is refused with VALUE_OUT_OF_RANGE and the doors set
183 /// before are kept. The array is read-only over the network.
184 pub fn set_access_doors(
185 &mut self,
186 doors: impl IntoIterator<Item = impl Into<BACnetDeviceObjectReference>>,
187 ) -> Result<(), Error> {
188 self.access_doors = references_to(ObjectType::ACCESS_DOOR, doors)?;
189 Ok(())
190 }
191
192 /// Set Number_Of_Authentication_Policies, how many authentication
193 /// policies the application defines (Clause 12.31.11), 1 until set. It
194 /// is read-only over the network. Zero is refused with
195 /// VALUE_OUT_OF_RANGE, and so is a count below
196 /// Active_Authentication_Policy: lower the active policy first, with a
197 /// write of that property.
198 pub fn set_number_of_authentication_policies(&mut self, count: u32) -> Result<(), Error> {
199 self.authorization.set_policies(count)
200 }
201
202 /// Set the authorization modes the application carries out, the values
203 /// a write of Authorization_Mode can take (Clause 12.31.14). A new point
204 /// takes AUTHORIZE alone, since the point enforces no mode itself: an
205 /// application that acts on Authorization_Mode declares the other modes
206 /// it carries out here. The set must hold AUTHORIZE and the mode in
207 /// effect, and each mode must be a standard one or a proprietary one from
208 /// 64 to 65535; otherwise it is refused with VALUE_OUT_OF_RANGE and the
209 /// set before is kept. A repeated mode counts once.
210 pub fn set_supported_authorization_modes(
211 &mut self,
212 modes: impl IntoIterator<Item = AuthorizationMode>,
213 ) -> Result<(), Error> {
214 self.authorization.set_supported_modes(modes)
215 }
216
217 /// Set Priority_For_Writing, the priority at which the application
218 /// commands the Access_Doors after a grant (Clauses 12.31.32.1 and
219 /// 12.31.33), 16 until set. It is read-only over the network. A
220 /// priority outside 1..=16 is refused with VALUE_OUT_OF_RANGE.
221 pub fn set_priority_for_writing(&mut self, priority: u8) -> Result<(), Error> {
222 self.authorization.set_priority_for_writing(priority)
223 }
224
225 /// Record the access event an Out_Of_Service edge raises: a new
226 /// transaction, stamped from the Device clock, or with the new tag as a
227 /// sequence number when there is no usable clock, and with no credential.
228 fn record_out_of_service_event(&mut self, event: AccessEvent) {
229 let tag = self.access_event_tag.wrapping_add(1);
230 self.access_event = event;
231 self.access_event_tag = tag;
232 self.access_event_time = update_stamp(self.clock.as_deref(), || tag_sequence(tag));
233 self.access_event_credential = no_credential();
234 }
235}
236
237/// The Access_Event_Credential of an event without a credential
238/// (Clause 12.31.30): an Access Credential identifier at instance 4194303,
239/// with no device identifier.
240fn no_credential() -> BACnetDeviceObjectReference {
241 ObjectIdentifier::new(
242 ObjectType::ACCESS_CREDENTIAL,
243 ObjectIdentifier::MAX_INSTANCE,
244 )
245 .expect("4194303 is within the instance range")
246 .into()
247}
248
249/// `tag` as a sequence number in 1..=65535: the tag itself up to 65535, and
250/// past that folded back into the range, so consecutive tags, the wrap from
251/// the top of the Unsigned range to 0 included, give different numbers.
252fn tag_sequence(tag: u64) -> u16 {
253 const RANGE: u64 = u16::MAX as u64;
254 // In 0..RANGE, so the cast keeps every bit and the sum stays in range.
255 (tag.wrapping_sub(1) % RANGE) as u16 + 1
256}
257
258impl BACnetObject for AccessPointObject {
259 fn object_identifier(&self) -> ObjectIdentifier {
260 self.oid
261 }
262
263 fn object_name(&self) -> &str {
264 &self.name
265 }
266
267 fn read_property(
268 &self,
269 property: PropertyIdentifier,
270 array_index: Option<u32>,
271 ) -> Result<PropertyValue, Error> {
272 if let Some(result) = read_common_properties!(self, property, array_index) {
273 return result;
274 }
275 if let Some(value) = self.authorization.read(property) {
276 return Ok(value);
277 }
278 match property {
279 p if p == PropertyIdentifier::OBJECT_TYPE => {
280 Ok(PropertyValue::Enumerated(ObjectType::ACCESS_POINT.to_raw()))
281 }
282 p if p == PropertyIdentifier::ACCESS_EVENT => {
283 Ok(PropertyValue::Enumerated(self.access_event.to_raw()))
284 }
285 p if p == PropertyIdentifier::ACCESS_EVENT_TAG => {
286 Ok(PropertyValue::Unsigned(self.access_event_tag))
287 }
288 p if p == PropertyIdentifier::ACCESS_EVENT_TIME => {
289 timestamp_value(&self.access_event_time)
290 }
291 p if p == PropertyIdentifier::ACCESS_EVENT_CREDENTIAL => Ok(
292 crate::device_reference::reference_value(&self.access_event_credential),
293 ),
294 p if p == PropertyIdentifier::AUTHENTICATION_STATUS => Ok(PropertyValue::Enumerated(
295 self.authentication_status().to_raw(),
296 )),
297 p if p == PropertyIdentifier::ACCESS_DOORS => common::read_array(
298 crate::device_reference::reference_elements(&self.access_doors),
299 array_index,
300 ),
301 p if p == PropertyIdentifier::EVENT_STATE => {
302 Ok(PropertyValue::Enumerated(self.event_state.to_raw()))
303 }
304 _ => Err(common::unknown_property_error()),
305 }
306 }
307
308 fn write_property(
309 &mut self,
310 property: PropertyIdentifier,
311 array_index: Option<u32>,
312 value: PropertyValue,
313 _priority: Option<u8>,
314 ) -> Result<(), Error> {
315 let was_out_of_service = self.out_of_service;
316 if let Some(result) =
317 common::write_out_of_service(&mut self.out_of_service, property, &value)
318 {
319 match (was_out_of_service, self.out_of_service) {
320 (false, true) => self.record_out_of_service_event(AccessEvent::OUT_OF_SERVICE),
321 (true, false) => {
322 self.record_out_of_service_event(AccessEvent::OUT_OF_SERVICE_RELINQUISHED)
323 }
324 _ => {}
325 }
326 return result;
327 }
328 if let Some(result) = common::write_description(&mut self.description, property, &value) {
329 return result;
330 }
331 if let Some(result) = self.authorization.write(property, array_index, &value) {
332 return result;
333 }
334 Err(crate::common::unhandled_write_error(
335 self.property_metadata().as_ref(),
336 property,
337 array_index,
338 ))
339 }
340
341 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
342 super::metadata_topology::for_access_point_object(self)
343 }
344
345 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
346 crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
347 }
348
349 /// Table 13-1 lists Access Point, so it takes SubscribeCOV; its report
350 /// leads with Access_Event, as the point has no Present_Value.
351 fn supports_cov(&self) -> bool {
352 true
353 }
354
355 fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
356 self.clock = clock;
357 }
358}
359
360// ---------------------------------------------------------------------------