Skip to main content

bacnet_encoding/constructed/
property_element.rs

1//! Which constructed production a property's value is made of.
2//!
3//! A value read from a device arrives as bare octets. For a primitive value
4//! the application tags say where each element starts and ends, but a list
5//! or array of constructed elements (timestamps, schedule days, references)
6//! carries nothing that marks the boundary between one element and the
7//! next. Splitting it takes the element's datatype, and that depends on the
8//! object type and the property, which the octets don't say.
9//!
10//! [`property_element`] is that knowledge as a table: for each (object type,
11//! property) pair whose datatype it knows, the [`ConstructedElement`] the
12//! value is made of and whether the property holds one of them or a
13//! collection ([`ElementShape`]). Each element names the codec in
14//! [`crate::constructed`] (or, for [`ConstructedElement::ReadAccessResult`],
15//! the ReadPropertyMultiple ACK decoder in `bacnet-services`) that decodes
16//! it, which `split_elements` takes to find where each element ends.
17//!
18//! The table needs no allocator; `split_elements` needs `alloc`.
19
20use bacnet_types::enums::{ObjectType, PropertyIdentifier};
21#[cfg(feature = "alloc")]
22use bacnet_types::error::Error;
23
24/// A constructed production a property's value is made of.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26#[non_exhaustive]
27pub enum ConstructedElement {
28    /// A Tags element, a `BACnetNameValue`
29    /// (`decode_name_value`).
30    NameValue,
31    /// A Recipient_List element, a `BACnetDestination`
32    /// (`decode_destination`).
33    Destination,
34    /// A Port_Filter element, a `BACnetPortPermission`
35    /// (`decode_port_permission`).
36    PortPermission,
37    /// A Group member, a `ReadAccessSpecification`
38    /// (`decode_read_access_specification`).
39    ReadAccessSpecification,
40    /// A Group's Present_Value element: one result of a ReadPropertyMultiple
41    /// ACK, which `bacnet-services` decodes.
42    ReadAccessResult,
43    /// A Command's Action element, a `BACnetActionList`
44    /// (`decode_action_list`).
45    ActionList,
46    /// A `BACnetDeviceObjectReference`
47    /// (`decode_device_object_reference`).
48    DeviceObjectReference,
49    /// A Supported_Formats element, a `BACnetAuthenticationFactorFormat`
50    /// (`decode_authentication_factor_format`).
51    AuthenticationFactorFormat,
52    /// An Authentication_Policy_List element, a `BACnetAuthenticationPolicy`
53    /// (`decode_authentication_policy`).
54    AuthenticationPolicy,
55    /// A Stages element, a `BACnetStageLimitValue`
56    /// (`decode_stage_limit_value`).
57    StageLimitValue,
58    /// An Access Rights rule, a `BACnetAccessRule`
59    /// (`decode_access_rule`).
60    AccessRule,
61    /// A `BACnetDeviceObjectPropertyReference`
62    /// (`decode_device_object_property_reference`).
63    DeviceObjectPropertyReference,
64    /// A `BACnetObjectPropertyReference`, which names no device
65    /// (`decode_object_property_reference_at`).
66    ObjectPropertyReference,
67    /// A Global Group member's result, a `BACnetPropertyAccessResult`
68    /// (`decode_property_access_result`).
69    PropertyAccessResult,
70    /// A `BACnetRecipient`
71    /// (`decode_recipient`).
72    Recipient,
73    /// A Weekly_Schedule day, a `BACnetDailySchedule`
74    /// (`decode_daily_schedule`).
75    DailySchedule,
76    /// An Exception_Schedule element, a `BACnetSpecialEvent`
77    /// (`decode_special_event`).
78    SpecialEvent,
79    /// A Date_List element, a `BACnetCalendarEntry`
80    /// (`decode_calendar_entry`).
81    CalendarEntry,
82    /// A `BACnetDateRange`
83    /// (`decode_date_range`).
84    DateRange,
85    /// A `BACnetTimeStamp`
86    /// (`decode_timestamp_choice`).
87    TimeStamp,
88    /// An Active_COV_Subscriptions element, a `BACnetCOVSubscription`
89    /// (`decode_cov_subscription`).
90    CovSubscription,
91    /// A command source, a `BACnetValueSource`
92    /// (`decode_value_source`).
93    ValueSource,
94    /// An Accumulator's Scale, a `BACnetScale`
95    /// (`decode_scale`).
96    Scale,
97    /// An Accumulator's Prescale, a `BACnetPrescale`
98    /// (`decode_prescale`).
99    Prescale,
100    /// A Device_Address_Binding element: the Device's identifier, then its
101    /// network number and MAC, each application tagged.
102    AddressBinding,
103    /// A Load Control shed level, a `BACnetShedLevel`
104    /// (`decode_shed_level`).
105    ShedLevel,
106    /// A `BACnetAuthenticationFactor`
107    /// (`decode_authentication_factor`).
108    AuthenticationFactor,
109    /// An Event Enrollment's Event_Parameters, a `BACnetEventParameter`
110    /// (`decode_event_parameter`).
111    EventParameter,
112}
113
114impl ConstructedElement {
115    /// Every element, each once.
116    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/// How a property holds its elements.
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
150pub enum ElementShape {
151    /// A BACnetARRAY or BACnetLIST: a whole read is the elements back to
152    /// back, and an indexed read of an array one element.
153    Collection,
154    /// One value: a whole read is the element itself.
155    Single,
156}
157
158/// The element `property` of `object_type` is made of, and how the property
159/// holds it; `None` for a property this table doesn't know, whose value the
160/// application tags alone describe or whose datatype it doesn't cover.
161pub 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        // Every object type that has these properties gives them one datatype.
224        (_, 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/// Where each element of `octets` ends, with `decode` reading the element
235/// that starts at an offset and returning the offset just past it. `None`
236/// when an element doesn't decode or `decode` doesn't move forward, so a
237/// value that isn't wholly made of the elements is never half split. Empty
238/// octets split into no elements.
239#[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        // Elements two octets long, while the octets hold an even count.
317        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        // A decoder that doesn't move forward, or runs past the end.
328        assert_eq!(split_elements(&[1], |_, offset| Ok(offset)), None);
329        assert_eq!(split_elements(&[1], |_, _| Ok(5)), None);
330    }
331}