Skip to main content

bacnet_objects/access_control/
credential_data_input.rs

1use std::sync::Arc;
2
3use bacnet_encoding::constructed::{
4    encode_authentication_factor, encode_authentication_factor_format,
5};
6use bacnet_types::constructed::{BACnetAuthenticationFactor, BACnetAuthenticationFactorFormat};
7
8use super::credential_data_input_formats::{
9    drop_undeclared, is_declared, is_well_formed, undefined_factor, SupportedFormat,
10};
11use super::credential_data_input_out_of_service::Reading;
12use super::*;
13
14// CredentialDataInputObject (type 37)
15// ---------------------------------------------------------------------------
16
17/// BACnet Credential Data Input object (type 37).
18///
19/// Represents a credential reader device (card reader, biometric scanner, etc.).
20/// Its Present_Value is the last factor read, a `BACnetAuthenticationFactor`
21/// (Clause 12.36.4), and Update_Time a `BACnetTimeStamp` (Clause 12.36.11);
22/// both go out in their Clause 21 forms. The enumerated
23/// BACnetAuthenticationStatus belongs to the Access Point's
24/// Authentication_Status (Clause 12.31, Table 12-36), not here.
25///
26/// Supported_Formats and Supported_Format_Classes are BACnetARRAYs of the
27/// same size (Clause 12.36.9.1), so the object keeps them as one list of
28/// pairs, set by [`Self::set_supported_formats`]. Both take an array index.
29/// Present_Value always names one of those formats with its class, or is the
30/// UNDEFINED or ERROR factor (Clause 12.36.4; #1249): the module
31/// `credential_data_input_formats` has the rule and how each route keeps it.
32///
33/// While Out_Of_Service is TRUE a client can simulate Present_Value and
34/// Reliability by writing them, and the reader's own values come back on the
35/// return to service (Table 12-43 footnote 1; #1168). The module
36/// `credential_data_input_out_of_service` has the details.
37pub struct CredentialDataInputObject {
38    oid: ObjectIdentifier,
39    name: String,
40    description: String,
41    /// Present_Value, Update_Time and Reliability as served: the reader's,
42    /// or a client's simulation while Out_Of_Service is TRUE.
43    reading: Reading,
44    /// The reader's own values, put aside while Out_Of_Service is TRUE.
45    device_reading: Option<Reading>,
46    supported_formats: Vec<SupportedFormat>,
47    out_of_service: bool,
48    /// The Device clock, and the sequence numbers that stand in for it.
49    clock: UpdateClock,
50}
51
52impl CredentialDataInputObject {
53    /// Create a new Credential Data Input object.
54    ///
55    /// Until the first read, Present_Value is the UNDEFINED factor (format
56    /// class 0, no value octets) and Update_Time the unspecified date and
57    /// time (Clauses 12.36.4 and 12.36.11). It supports no format until
58    /// [`Self::set_supported_formats`] declares some.
59    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
60        let oid = ObjectIdentifier::new(ObjectType::CREDENTIAL_DATA_INPUT, instance)?;
61        Ok(Self {
62            oid,
63            name: name.into(),
64            description: String::new(),
65            reading: Reading {
66                present_value: undefined_factor(),
67                update_time: never_updated(),
68                reliability: Reliability::NO_FAULT_DETECTED,
69            },
70            device_reading: None,
71            supported_formats: Vec::new(),
72            out_of_service: false,
73            clock: UpdateClock::default(),
74        })
75    }
76
77    /// Record a factor the reader has read: Present_Value takes `factor` and
78    /// Update_Time `update_time`, together, since Clause 12.36.11 moves
79    /// Update_Time on every Present_Value update. Reading the same factor
80    /// again is such an update.
81    ///
82    /// The factor must name a declared format with the class declared beside
83    /// it, or be the UNDEFINED or ERROR factor with class 0 (Clause 12.36.4);
84    /// any other is refused with VALUE_OUT_OF_RANGE and nothing changes.
85    ///
86    /// A change of Update_Time triggers a SubscribeCOV notification
87    /// (Table 13-1). While Out_Of_Service is TRUE a client's simulated values
88    /// keep being served, and these take over on the return to service.
89    pub fn set_present_value(
90        &mut self,
91        factor: BACnetAuthenticationFactor,
92        update_time: BACnetTimeStamp,
93    ) -> Result<(), Error> {
94        if !is_declared(&factor, &self.supported_formats) {
95            return Err(common::value_out_of_range_error());
96        }
97        let reading = self.device_reading_mut();
98        reading.present_value = factor;
99        reading.update_time = update_time;
100        Ok(())
101    }
102
103    /// Declare the formats this reader reads: Supported_Formats takes each
104    /// pair's format and Supported_Format_Classes, at the same position, the
105    /// format class a factor read in it carries (Clauses 12.36.9 and
106    /// 12.36.10). Zero is the class for a format that needs no
107    /// differentiation.
108    ///
109    /// Each format must be a named BACnetAuthenticationFactorType. A CUSTOM
110    /// format must name its vendor and that vendor's format number; any other
111    /// format may carry those members only as zero (Clause 12.36.9). A list
112    /// breaking either rule is refused with VALUE_OUT_OF_RANGE and the
113    /// declared formats are kept.
114    ///
115    /// A Present_Value whose format and class the new list doesn't declare
116    /// goes back to the UNDEFINED factor, with Update_Time stamped from the
117    /// Device clock, or the object's next sequence number when there is no
118    /// usable clock (Clause 12.36.4). Out of service that covers both the
119    /// simulated factor served and the reader's factor put aside.
120    pub fn set_supported_formats(
121        &mut self,
122        formats: impl IntoIterator<Item = (BACnetAuthenticationFactorFormat, u32)>,
123    ) -> Result<(), Error> {
124        let formats: Vec<SupportedFormat> = formats.into_iter().collect();
125        if !formats.iter().all(|(format, _)| is_well_formed(format)) {
126            return Err(common::value_out_of_range_error());
127        }
128        self.supported_formats = formats;
129        drop_undeclared(&mut self.reading, &self.supported_formats, &mut self.clock);
130        if let Some(device) = &mut self.device_reading {
131            drop_undeclared(device, &self.supported_formats, &mut self.clock);
132        }
133        Ok(())
134    }
135
136    /// The reader's own values: the ones put aside while out of service,
137    /// else the ones served.
138    fn device_reading_mut(&mut self) -> &mut Reading {
139        self.device_reading.as_mut().unwrap_or(&mut self.reading)
140    }
141}
142
143impl BACnetObject for CredentialDataInputObject {
144    fn object_identifier(&self) -> ObjectIdentifier {
145        self.oid
146    }
147
148    fn object_name(&self) -> &str {
149        &self.name
150    }
151
152    fn read_property(
153        &self,
154        property: PropertyIdentifier,
155        array_index: Option<u32>,
156    ) -> Result<PropertyValue, Error> {
157        if let Some(result) = crate::common::read_identity_properties!(self, property, array_index)
158        {
159            return result;
160        }
161        match property {
162            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
163                ObjectType::CREDENTIAL_DATA_INPUT.to_raw(),
164            )),
165            // The object runs no intrinsic reporting, so IN_ALARM stays clear.
166            p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
167                StatusFlags::empty(),
168                self.reading.reliability,
169                self.out_of_service,
170                EventState::NORMAL,
171            )),
172            p if p == PropertyIdentifier::OUT_OF_SERVICE => {
173                Ok(PropertyValue::Boolean(self.out_of_service))
174            }
175            p if p == PropertyIdentifier::RELIABILITY => {
176                Ok(PropertyValue::Enumerated(self.reading.reliability.to_raw()))
177            }
178            p if p == PropertyIdentifier::PRESENT_VALUE => {
179                let mut buf = BytesMut::new();
180                encode_authentication_factor(&mut buf, &self.reading.present_value);
181                Ok(PropertyValue::ApplicationData(buf.to_vec()))
182            }
183            p if p == PropertyIdentifier::UPDATE_TIME => timestamp_value(&self.reading.update_time),
184            p if p == PropertyIdentifier::SUPPORTED_FORMATS => common::read_array(
185                self.supported_formats
186                    .iter()
187                    .map(|(format, _)| {
188                        let mut buf = BytesMut::new();
189                        encode_authentication_factor_format(&mut buf, format);
190                        PropertyValue::ApplicationData(buf.to_vec())
191                    })
192                    .collect(),
193                array_index,
194            ),
195            p if p == PropertyIdentifier::SUPPORTED_FORMAT_CLASSES => common::read_array(
196                self.supported_formats
197                    .iter()
198                    .map(|&(_, class)| PropertyValue::Unsigned(class.into()))
199                    .collect(),
200                array_index,
201            ),
202            _ => Err(common::unknown_property_error()),
203        }
204    }
205
206    fn write_property(
207        &mut self,
208        property: PropertyIdentifier,
209        array_index: Option<u32>,
210        value: PropertyValue,
211        _priority: Option<u8>,
212    ) -> Result<(), Error> {
213        // The reader's entry edge is always seen (it starts in service and
214        // only this write moves Out_Of_Service), so there is no fallback.
215        if let Some(result) = common::write_out_of_service_with_restore(
216            &mut self.out_of_service,
217            &mut self.reading,
218            &mut self.device_reading,
219            None,
220            property,
221            &value,
222        ) {
223            return result;
224        }
225        if let Some(result) = common::write_description(&mut self.description, property, &value) {
226            return result;
227        }
228        if let Some(result) = self.reading.write(
229            self.out_of_service,
230            &self.supported_formats,
231            &mut self.clock,
232            property,
233            &value,
234        ) {
235            return result;
236        }
237        Err(common::unhandled_write_error(
238            self.property_metadata().as_ref(),
239            property,
240            array_index,
241        ))
242    }
243
244    fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
245        super::metadata_identity::for_credential_data_input_object(self)
246    }
247
248    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
249        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
250    }
251
252    /// Table 13-1 lists Credential Data Input, so it takes SubscribeCOV.
253    fn supports_cov(&self) -> bool {
254        true
255    }
256
257    fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
258        self.clock.clock = clock;
259    }
260
261    /// The application's Reliability for the reader. Like the other
262    /// Reliability carriers, the reader refuses it with WRITE_ACCESS_DENIED
263    /// while Out_Of_Service is TRUE, so a client's simulated value stays
264    /// served; the return to service brings back the value from before.
265    /// A value outside the BACnetReliability production is refused with
266    /// VALUE_OUT_OF_RANGE.
267    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
268        if self.out_of_service {
269            return Err(common::write_access_denied_error());
270        }
271        self.reading.reliability = super::checked_reliability(reliability)?;
272        Ok(())
273    }
274}
275
276// ---------------------------------------------------------------------------
277// Tests
278// ---------------------------------------------------------------------------