1use bacnet_types::enums::{ObjectType, PropertyIdentifier};
21#[cfg(feature = "alloc")]
22use bacnet_types::error::Error;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26#[non_exhaustive]
27pub enum ConstructedElement {
28 NameValue,
31 Destination,
34 PortPermission,
37 ReadAccessSpecification,
40 ReadAccessResult,
43 ActionList,
46 DeviceObjectReference,
49 AuthenticationFactorFormat,
52 AuthenticationPolicy,
55 StageLimitValue,
58 AccessRule,
61 DeviceObjectPropertyReference,
64 ObjectPropertyReference,
67 PropertyAccessResult,
70 Recipient,
73 DailySchedule,
76 SpecialEvent,
79 CalendarEntry,
82 DateRange,
85 TimeStamp,
88 CovSubscription,
91 ValueSource,
94 Scale,
97 Prescale,
100 AddressBinding,
103 ShedLevel,
106 AuthenticationFactor,
109 EventParameter,
112}
113
114impl ConstructedElement {
115 pub const ALL: [Self; 28] = [
117 Self::NameValue,
118 Self::Destination,
119 Self::PortPermission,
120 Self::ReadAccessSpecification,
121 Self::ReadAccessResult,
122 Self::ActionList,
123 Self::DeviceObjectReference,
124 Self::AuthenticationFactorFormat,
125 Self::AuthenticationPolicy,
126 Self::StageLimitValue,
127 Self::AccessRule,
128 Self::DeviceObjectPropertyReference,
129 Self::ObjectPropertyReference,
130 Self::PropertyAccessResult,
131 Self::Recipient,
132 Self::DailySchedule,
133 Self::SpecialEvent,
134 Self::CalendarEntry,
135 Self::DateRange,
136 Self::TimeStamp,
137 Self::CovSubscription,
138 Self::ValueSource,
139 Self::Scale,
140 Self::Prescale,
141 Self::AddressBinding,
142 Self::ShedLevel,
143 Self::AuthenticationFactor,
144 Self::EventParameter,
145 ];
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
150pub enum ElementShape {
151 Collection,
154 Single,
156}
157
158pub fn property_element(
162 object_type: ObjectType,
163 property: PropertyIdentifier,
164) -> Option<(ConstructedElement, ElementShape)> {
165 type O = ObjectType;
166 type P = PropertyIdentifier;
167 use ConstructedElement as E;
168 use ElementShape::{Collection, Single};
169 Some(match (object_type, property) {
170 (O::NOTIFICATION_CLASS | O::NOTIFICATION_FORWARDER, P::RECIPIENT_LIST) => {
171 (E::Destination, Collection)
172 }
173 (O::NOTIFICATION_FORWARDER, P::PORT_FILTER) => (E::PortPermission, Collection),
174 (O::GROUP, P::LIST_OF_GROUP_MEMBERS) => (E::ReadAccessSpecification, Collection),
175 (O::GROUP, P::PRESENT_VALUE) => (E::ReadAccessResult, Collection),
176 (O::COMMAND, P::ACTION) => (E::ActionList, Collection),
177 (O::ACCESS_DOOR, P::DOOR_MEMBERS)
178 | (O::ACCESS_POINT, P::ACCESS_DOORS)
179 | (O::STAGING, P::TARGET_REFERENCES)
180 | (O::ACCESS_ZONE, P::ENTRY_POINTS | P::EXIT_POINTS)
181 | (O::ACCESS_USER, P::CREDENTIALS | P::MEMBERS | P::MEMBER_OF)
182 | (O::LIFE_SAFETY_POINT | O::LIFE_SAFETY_ZONE, P::MEMBER_OF)
183 | (O::LIFE_SAFETY_ZONE, P::ZONE_MEMBERS)
184 | (O::STRUCTURED_VIEW, P::SUBORDINATE_LIST) => (E::DeviceObjectReference, Collection),
185 (O::ACCESS_RIGHTS, P::ACCOMPANIMENT)
186 | (O::ACCESS_POINT, P::ACCESS_EVENT_CREDENTIAL)
187 | (O::LIFT | O::ESCALATOR, P::ENERGY_METER_REF) => (E::DeviceObjectReference, Single),
188 (O::ACCESS_POINT, P::ACCESS_EVENT_AUTHENTICATION_FACTOR)
189 | (O::CREDENTIAL_DATA_INPUT, P::PRESENT_VALUE) => (E::AuthenticationFactor, Single),
190 (O::ACCESS_POINT, P::ACCESS_EVENT_TIME) => (E::TimeStamp, Single),
191 (
192 O::LOAD_CONTROL,
193 P::REQUESTED_SHED_LEVEL | P::EXPECTED_SHED_LEVEL | P::ACTUAL_SHED_LEVEL,
194 ) => (E::ShedLevel, Single),
195 (O::EVENT_ENROLLMENT, P::EVENT_PARAMETERS) => (E::EventParameter, Single),
196 (O::CREDENTIAL_DATA_INPUT, P::SUPPORTED_FORMATS) => {
197 (E::AuthenticationFactorFormat, Collection)
198 }
199 (O::ACCESS_POINT, P::AUTHENTICATION_POLICY_LIST) => (E::AuthenticationPolicy, Collection),
200 (O::STAGING, P::STAGES) => (E::StageLimitValue, Collection),
201 (O::ACCESS_RIGHTS, P::POSITIVE_ACCESS_RULES | P::NEGATIVE_ACCESS_RULES) => {
202 (E::AccessRule, Collection)
203 }
204 (O::GLOBAL_GROUP, P::GROUP_MEMBERS)
205 | (O::SCHEDULE | O::CHANNEL, P::LIST_OF_OBJECT_PROPERTY_REFERENCES)
206 | (O::TREND_LOG_MULTIPLE, P::LOG_DEVICE_OBJECT_PROPERTY) => {
207 (E::DeviceObjectPropertyReference, Collection)
208 }
209 (O::TREND_LOG, P::LOG_DEVICE_OBJECT_PROPERTY)
210 | (O::AVERAGING | O::EVENT_ENROLLMENT, P::OBJECT_PROPERTY_REFERENCE) => {
211 (E::DeviceObjectPropertyReference, Single)
212 }
213 (O::GLOBAL_GROUP, P::PRESENT_VALUE) => (E::PropertyAccessResult, Collection),
214 (O::DEVICE, P::AUDIT_NOTIFICATION_RECIPIENT) => (E::Recipient, Single),
215 (O::SCHEDULE, P::WEEKLY_SCHEDULE) => (E::DailySchedule, Collection),
216 (O::SCHEDULE, P::EXCEPTION_SCHEDULE) => (E::SpecialEvent, Collection),
217 (O::SCHEDULE, P::EFFECTIVE_PERIOD) => (E::DateRange, Single),
218 (O::CALENDAR, P::DATE_LIST) => (E::CalendarEntry, Collection),
219 (O::DEVICE, P::ACTIVE_COV_SUBSCRIPTIONS) => (E::CovSubscription, Collection),
220 (O::DEVICE, P::DEVICE_ADDRESS_BINDING) => (E::AddressBinding, Collection),
221 (O::ACCUMULATOR, P::SCALE) => (E::Scale, Single),
222 (O::ACCUMULATOR, P::PRESCALE) => (E::Prescale, Single),
223 (_, P::TAGS) => (E::NameValue, Collection),
225 (_, P::EVENT_TIME_STAMPS | P::COMMAND_TIME_ARRAY) => (E::TimeStamp, Collection),
226 (_, P::LAST_COMMAND_TIME | P::LAST_RESTORE_TIME) => (E::TimeStamp, Single),
227 (_, P::VALUE_SOURCE_ARRAY) => (E::ValueSource, Collection),
228 (_, P::VALUE_SOURCE) => (E::ValueSource, Single),
229 (_, P::EVENT_ALGORITHM_INHIBIT_REF) => (E::ObjectPropertyReference, Single),
230 _ => return None,
231 })
232}
233
234#[cfg(feature = "alloc")]
240pub fn split_elements(
241 octets: &[u8],
242 mut decode: impl FnMut(&[u8], usize) -> Result<usize, Error>,
243) -> Option<alloc::vec::Vec<usize>> {
244 let mut ends = alloc::vec::Vec::new();
245 let mut offset = 0;
246 while offset < octets.len() {
247 let end = decode(octets, offset).ok()?;
248 if end <= offset || end > octets.len() {
249 return None;
250 }
251 ends.push(end);
252 offset = end;
253 }
254 Some(ends)
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn the_table_names_the_element_and_its_shape() {
263 type O = ObjectType;
264 type P = PropertyIdentifier;
265 use ConstructedElement as E;
266 for (object_type, property, expected) in [
267 (
268 O::ANALOG_INPUT,
269 P::EVENT_TIME_STAMPS,
270 Some((E::TimeStamp, ElementShape::Collection)),
271 ),
272 (
273 O::ANALOG_INPUT,
274 P::EVENT_ALGORITHM_INHIBIT_REF,
275 Some((E::ObjectPropertyReference, ElementShape::Single)),
276 ),
277 (
278 O::SCHEDULE,
279 P::WEEKLY_SCHEDULE,
280 Some((E::DailySchedule, ElementShape::Collection)),
281 ),
282 (
283 O::TREND_LOG,
284 P::LOG_DEVICE_OBJECT_PROPERTY,
285 Some((E::DeviceObjectPropertyReference, ElementShape::Single)),
286 ),
287 (
288 O::TREND_LOG_MULTIPLE,
289 P::LOG_DEVICE_OBJECT_PROPERTY,
290 Some((E::DeviceObjectPropertyReference, ElementShape::Collection)),
291 ),
292 (O::ANALOG_INPUT, P::PRESENT_VALUE, None),
293 (O::SCHEDULE, P::PRESENT_VALUE, None),
294 ] {
295 assert_eq!(
296 property_element(object_type, property),
297 expected,
298 "{object_type} {property}"
299 );
300 }
301 }
302
303 #[test]
304 fn all_lists_each_element_once() {
305 for (i, element) in ConstructedElement::ALL.iter().enumerate() {
306 assert!(
307 !ConstructedElement::ALL[..i].contains(element),
308 "{element:?}"
309 );
310 }
311 }
312
313 #[cfg(feature = "alloc")]
314 #[test]
315 fn a_split_stops_at_the_first_element_that_does_not_decode() {
316 let pairs = |octets: &[u8], offset: usize| {
318 if offset + 2 <= octets.len() {
319 Ok(offset + 2)
320 } else {
321 Err(Error::decoding(offset, "cut short"))
322 }
323 };
324 assert_eq!(split_elements(&[1, 2, 3, 4], pairs), Some(vec![2, 4]));
325 assert_eq!(split_elements(&[1, 2, 3], pairs), None);
326 assert_eq!(split_elements(&[], pairs), Some(vec![]));
327 assert_eq!(split_elements(&[1], |_, offset| Ok(offset)), None);
329 assert_eq!(split_elements(&[1], |_, _| Ok(5)), None);
330 }
331}