Skip to main content

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