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. A
89    /// running server's route, `BACnetServer::report_credential_read_local`,
90    /// stores a read the same way (#1132).
91    pub fn set_present_value(
92        &mut self,
93        factor: BACnetAuthenticationFactor,
94        update_time: BACnetTimeStamp,
95    ) -> Result<(), Error> {
96        if !is_declared(&factor, &self.supported_formats) {
97            return Err(common::value_out_of_range_error());
98        }
99        let reading = self.device_reading_mut();
100        reading.present_value = factor;
101        reading.update_time = update_time;
102        Ok(())
103    }
104
105    /// Declare the formats this reader reads: Supported_Formats takes each
106    /// pair's format and Supported_Format_Classes, at the same position, the
107    /// format class a factor read in it carries (Clauses 12.36.9 and
108    /// 12.36.10). Zero is the class for a format that needs no
109    /// differentiation.
110    ///
111    /// Each format must be a named BACnetAuthenticationFactorType. A CUSTOM
112    /// format must name its vendor and that vendor's format number; any other
113    /// format may carry those members only as zero (Clause 12.36.9). A list
114    /// breaking either rule is refused with VALUE_OUT_OF_RANGE and the
115    /// declared formats are kept.
116    ///
117    /// A Present_Value whose format and class the new list doesn't declare
118    /// goes back to the UNDEFINED factor, with Update_Time stamped from the
119    /// Device clock, or the object's next sequence number when there is no
120    /// usable clock (Clause 12.36.4). Out of service that covers both the
121    /// simulated factor served and the reader's factor put aside.
122    pub fn set_supported_formats(
123        &mut self,
124        formats: impl IntoIterator<Item = (BACnetAuthenticationFactorFormat, u32)>,
125    ) -> Result<(), Error> {
126        let formats: Vec<SupportedFormat> = formats.into_iter().collect();
127        if !formats.iter().all(|(format, _)| is_well_formed(format)) {
128            return Err(common::value_out_of_range_error());
129        }
130        self.supported_formats = formats;
131        drop_undeclared(&mut self.reading, &self.supported_formats, &mut self.clock);
132        if let Some(device) = &mut self.device_reading {
133            drop_undeclared(device, &self.supported_formats, &mut self.clock);
134        }
135        Ok(())
136    }
137
138    /// The reader's own values: the ones put aside while out of service,
139    /// else the ones served.
140    fn device_reading_mut(&mut self) -> &mut Reading {
141        self.device_reading.as_mut().unwrap_or(&mut self.reading)
142    }
143}
144
145impl BACnetObject for CredentialDataInputObject {
146    fn object_identifier(&self) -> ObjectIdentifier {
147        self.oid
148    }
149
150    fn object_name(&self) -> &str {
151        &self.name
152    }
153
154    fn read_property(
155        &self,
156        property: PropertyIdentifier,
157        array_index: Option<u32>,
158    ) -> Result<PropertyValue, Error> {
159        if let Some(result) = crate::common::read_identity_properties!(self, property, array_index)
160        {
161            return result;
162        }
163        match property {
164            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
165                ObjectType::CREDENTIAL_DATA_INPUT.to_raw(),
166            )),
167            // The object runs no intrinsic reporting, so IN_ALARM stays clear.
168            p if p == PropertyIdentifier::STATUS_FLAGS => Ok(common::compute_status_flags(
169                StatusFlags::empty(),
170                self.reading.reliability,
171                self.out_of_service,
172                EventState::NORMAL,
173            )),
174            p if p == PropertyIdentifier::OUT_OF_SERVICE => {
175                Ok(PropertyValue::Boolean(self.out_of_service))
176            }
177            p if p == PropertyIdentifier::RELIABILITY => {
178                Ok(PropertyValue::Enumerated(self.reading.reliability.to_raw()))
179            }
180            p if p == PropertyIdentifier::PRESENT_VALUE => {
181                let mut buf = BytesMut::new();
182                encode_authentication_factor(&mut buf, &self.reading.present_value);
183                Ok(PropertyValue::ApplicationData(buf.to_vec()))
184            }
185            p if p == PropertyIdentifier::UPDATE_TIME => timestamp_value(&self.reading.update_time),
186            p if p == PropertyIdentifier::SUPPORTED_FORMATS => common::read_array(
187                self.supported_formats
188                    .iter()
189                    .map(|(format, _)| {
190                        let mut buf = BytesMut::new();
191                        encode_authentication_factor_format(&mut buf, format);
192                        PropertyValue::ApplicationData(buf.to_vec())
193                    })
194                    .collect(),
195                array_index,
196            ),
197            p if p == PropertyIdentifier::SUPPORTED_FORMAT_CLASSES => common::read_array(
198                self.supported_formats
199                    .iter()
200                    .map(|&(_, class)| PropertyValue::Unsigned(class.into()))
201                    .collect(),
202                array_index,
203            ),
204            _ => Err(common::unknown_property_error()),
205        }
206    }
207
208    fn write_property(
209        &mut self,
210        property: PropertyIdentifier,
211        array_index: Option<u32>,
212        value: PropertyValue,
213        _priority: Option<u8>,
214    ) -> Result<(), Error> {
215        // The reader's entry edge is always seen (it starts in service and
216        // only this write moves Out_Of_Service), so there is no fallback.
217        if let Some(result) = common::write_out_of_service_with_restore(
218            &mut self.out_of_service,
219            &mut self.reading,
220            &mut self.device_reading,
221            None,
222            property,
223            &value,
224        ) {
225            return result;
226        }
227        if let Some(result) = common::write_description(&mut self.description, property, &value) {
228            return result;
229        }
230        if let Some(result) = self.reading.write(
231            self.out_of_service,
232            &self.supported_formats,
233            &mut self.clock,
234            property,
235            &value,
236        ) {
237            return result;
238        }
239        Err(common::unhandled_write_error(
240            self.property_metadata().as_ref(),
241            property,
242            array_index,
243        ))
244    }
245
246    fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
247        super::metadata_identity::for_credential_data_input_object(self)
248    }
249
250    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
251        crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
252    }
253
254    /// Table 13-1 lists Credential Data Input, so it takes SubscribeCOV.
255    fn supports_cov(&self) -> bool {
256        true
257    }
258
259    fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
260        self.clock.clock = clock;
261    }
262
263    /// Take a read the application reports (#1132): Present_Value and
264    /// Update_Time together, the time stamped from the Device clock, or the
265    /// object's next sequence number, when the report has none. Checked and
266    /// stored as [`Self::set_present_value`] stores a read: while
267    /// Out_Of_Service is TRUE it replaces the reader's values put aside, and
268    /// a client's simulated ones stay served. Any other record is
269    /// OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED.
270    fn report_access_input_internal(&mut self, input: AccessControlInput) -> Result<(), Error> {
271        let AccessControlInput::CredentialRead(report) = input else {
272            return Err(common::optional_functionality_not_supported_error());
273        };
274        if !is_declared(&report.factor, &self.supported_formats) {
275            return Err(common::value_out_of_range_error());
276        }
277        let update_time = report.update_time.unwrap_or_else(|| self.clock.stamp());
278        self.set_present_value(report.factor, update_time)
279    }
280
281    /// The application's Reliability for the reader. Like the other
282    /// Reliability carriers, the reader refuses it with WRITE_ACCESS_DENIED
283    /// while Out_Of_Service is TRUE, so a client's simulated value stays
284    /// served; the return to service brings back the value from before.
285    /// A value outside the BACnetReliability production is refused with
286    /// VALUE_OUT_OF_RANGE.
287    fn set_reliability_internal(&mut self, reliability: Reliability) -> Result<(), Error> {
288        if self.out_of_service {
289            return Err(common::write_access_denied_error());
290        }
291        self.reading.reliability = super::checked_reliability(reliability)?;
292        Ok(())
293    }
294}
295
296// ---------------------------------------------------------------------------
297// Tests
298// ---------------------------------------------------------------------------