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