Skip to main content

bacnet_objects/channel/
arrays.rs

1//! Writes of a Channel's three arrays: List_Of_Object_Property_References,
2//! Execution_Delay and Control_Groups (Clauses 12.53.11, 12.53.12 and
3//! 12.53.15).
4//!
5//! Each takes a whole-array write, a write of one element at a one-based
6//! index, and a write of index 0, which resizes it. A write leaves the array
7//! unchanged when it fails.
8//!
9//! The member list and the delays always have the same size (Table 12-62,
10//! footnote 1). The clause names the writes that change a size: index 0 of
11//! either array grows the other with it, the list with empty references and
12//! the delays with zeros (Clauses 12.53.11.2 and 12.53.12.1). Here an
13//! index-0 write that shrinks one shrinks the other from the end too, and a
14//! whole write of the member list, which says how many members there are,
15//! carries the delays to its size the same way. A whole write of
16//! Execution_Delay only supplies one delay per member: one of any other
17//! length is refused with VALUE_OUT_OF_RANGE, as
18//! `ChannelObject::set_execution_delay` refuses it, so writing delays never
19//! adds or drops a member.
20//!
21//! A member may name another device (Clause 12.53.11, #1264); the server
22//! writes it there. The object can't tell which Device holds it, so the
23//! bundled server drops a Device identifier naming its own Device before the
24//! value gets here (#1136), and its runner treats a member still naming this
25//! Device as local. A reference whose Device instance is 4194303 is an empty
26//! one, not a remote one. A member whose device identifier isn't a Device
27//! object at all is refused with PROPERTY / VALUE_OUT_OF_RANGE (#1285), so the
28//! server's localizing, which compares the whole identifier, leaves it to that
29//! check. The members are decoded and checked by the shared helpers in
30//! `device_reference.rs` (#1313).
31
32use bacnet_types::constructed::BACnetDeviceObjectPropertyReference;
33use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType};
34use bacnet_types::error::Error;
35use bacnet_types::primitives::PropertyValue;
36
37use super::ChannelObject;
38use crate::{common, device_reference};
39
40/// Resource cap on members, and so on Execution_Delay elements: the bound a
41/// Schedule puts on its reference list.
42pub const MAX_CHANNEL_MEMBERS: usize = 1024;
43
44/// Resource cap on Control_Groups elements, the most groups one Channel
45/// joins at once (Clause 12.53.15 leaves it to the implementation).
46pub const MAX_CONTROL_GROUPS: usize = 64;
47
48/// The member a resize adds: the shared unset reference, object instance
49/// 4194303, which marks the reference empty (Clause 12.53.11.1).
50pub(super) fn empty_reference() -> BACnetDeviceObjectPropertyReference {
51    device_reference::unset_reference(ObjectType::ANALOG_INPUT)
52}
53
54/// Whether `member` is an empty reference, one the Channel skips: its object
55/// or Device instance is 4194303 (Clause 12.53.11.1), the shared
56/// [`BACnetDeviceObjectPropertyReference::is_unset`] rule.
57pub(super) fn is_empty(member: &BACnetDeviceObjectPropertyReference) -> bool {
58    member.is_unset()
59}
60
61fn no_space_error() -> Error {
62    common::protocol_error(ErrorClass::RESOURCES, ErrorCode::NO_SPACE_TO_WRITE_PROPERTY)
63}
64
65/// The new size an index-0 write asks for, up to `cap`.
66fn new_size(value: PropertyValue, cap: usize) -> Result<usize, Error> {
67    let PropertyValue::Unsigned(size) = value else {
68        return Err(common::invalid_data_type_error());
69    };
70    usize::try_from(size)
71        .ok()
72        .filter(|size| *size <= cap)
73        .ok_or_else(no_space_error)
74}
75
76/// The zero-based slot of one-based `index` in an array of `len` elements.
77fn slot(index: u32, len: usize) -> Result<usize, Error> {
78    usize::try_from(index - 1)
79        .ok()
80        .filter(|slot| *slot < len)
81        .ok_or_else(common::invalid_array_index_error)
82}
83
84/// The members a whole write holds, each checked in order: a member whose
85/// device identifier isn't a Device object is refused, empty or not.
86fn decode_members(
87    value: &PropertyValue,
88) -> Result<Vec<BACnetDeviceObjectPropertyReference>, Error> {
89    let members = device_reference::decode_references(value)?;
90    if members.len() > MAX_CHANNEL_MEMBERS {
91        return Err(no_space_error());
92    }
93    device_reference::check_device_members(&members)?;
94    Ok(members)
95}
96
97/// The elements a whole-array write of Unsigned values holds: a list, or the
98/// single value a one-element array decodes to.
99fn unsigned_elements(value: PropertyValue) -> Result<Vec<u32>, Error> {
100    let elements = match value {
101        PropertyValue::List(elements) => elements,
102        single => vec![single],
103    };
104    elements.into_iter().map(unsigned_element).collect()
105}
106
107/// One element of Execution_Delay or Control_Groups. Both are kept as 32-bit
108/// numbers: Control_Groups is Unsigned32, and a delay past 2^32-1
109/// milliseconds (about 49 days) is VALUE_OUT_OF_RANGE.
110fn unsigned_element(value: PropertyValue) -> Result<u32, Error> {
111    let PropertyValue::Unsigned(raw) = value else {
112        return Err(common::invalid_data_type_error());
113    };
114    u32::try_from(raw).map_err(|_| common::value_out_of_range_error())
115}
116
117impl ChannelObject {
118    /// Make both parallel arrays, and the datatypes learned for the members,
119    /// `size` long. The members a resize keeps keep what was learned for
120    /// them.
121    fn resize_members(&mut self, size: usize) {
122        self.members.resize_with(size, empty_reference);
123        self.learned.resize(size, None);
124        self.execution_delay.resize(size, 0);
125    }
126
127    /// Write List_Of_Object_Property_References. A member replaced, by an
128    /// element write or a whole one, loses the datatype learned for it.
129    pub(super) fn write_members(
130        &mut self,
131        array_index: Option<u32>,
132        value: PropertyValue,
133    ) -> Result<(), Error> {
134        match array_index {
135            None => {
136                let members = decode_members(&value)?;
137                let size = members.len();
138                self.members = members;
139                self.learned.clear();
140                self.resize_members(size);
141            }
142            Some(0) => {
143                let size = new_size(value, MAX_CHANNEL_MEMBERS)?;
144                self.resize_members(size);
145            }
146            Some(index) => {
147                let at = slot(index, self.members.len())?;
148                let member: BACnetDeviceObjectPropertyReference =
149                    device_reference::decode_reference(&value)?;
150                device_reference::check_device_member(member.device_identifier)?;
151                self.members[at] = member;
152                self.learned[at] = None;
153            }
154        }
155        Ok(())
156    }
157
158    pub(super) fn write_execution_delay(
159        &mut self,
160        array_index: Option<u32>,
161        value: PropertyValue,
162    ) -> Result<(), Error> {
163        match array_index {
164            None => {
165                let delays = unsigned_elements(value)?;
166                if delays.len() != self.members.len() {
167                    return Err(common::value_out_of_range_error());
168                }
169                self.execution_delay = delays;
170            }
171            Some(0) => {
172                let size = new_size(value, MAX_CHANNEL_MEMBERS)?;
173                self.resize_members(size);
174            }
175            Some(index) => {
176                let at = slot(index, self.execution_delay.len())?;
177                self.execution_delay[at] = unsigned_element(value)?;
178            }
179        }
180        Ok(())
181    }
182
183    pub(super) fn write_control_groups(
184        &mut self,
185        array_index: Option<u32>,
186        value: PropertyValue,
187    ) -> Result<(), Error> {
188        match array_index {
189            None => {
190                let groups = unsigned_elements(value)?;
191                check_group_count(groups.len())?;
192                self.control_groups = groups;
193            }
194            Some(0) => {
195                let size = new_size(value, MAX_CONTROL_GROUPS)?;
196                check_group_count(size)?;
197                self.control_groups.resize(size, 0);
198            }
199            Some(index) => {
200                let at = slot(index, self.control_groups.len())?;
201                self.control_groups[at] = unsigned_element(value)?;
202            }
203        }
204        Ok(())
205    }
206}
207
208/// Control_Groups holds at least one entry (Clause 12.53.15) and at most
209/// [`MAX_CONTROL_GROUPS`].
210pub(super) fn check_group_count(count: usize) -> Result<(), Error> {
211    match count {
212        0 => Err(common::value_out_of_range_error()),
213        count if count > MAX_CONTROL_GROUPS => Err(no_space_error()),
214        _ => Ok(()),
215    }
216}