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