Skip to main content

bacnet_encoding/constructed/
cov_subscription.rs

1//! Encode and decode the constructed values carried by Device
2//! `Active_COV_Subscriptions` and `Active_COV_Multiple_Subscriptions`.
3//!
4//! Each subscription is a bare Clause 21 sequence. A BACnetLIST concatenates
5//! those sequences without adding a list or per-entry wrapper, so each
6//! decoder reads one element at `offset` and returns the offset just past
7//! it: walking a list calls it at each element's start (#1046).
8
9#[cfg(not(feature = "std"))]
10use alloc::vec::Vec;
11
12use bacnet_types::constructed::{
13    BACnetCOVMultipleSubscription, BACnetCOVReference, BACnetCOVSubscription,
14    BACnetCOVSubscriptionSpecification, BACnetRecipientProcess,
15};
16use bacnet_types::enums::PropertyIdentifier;
17use bacnet_types::error::Error;
18use bytes::BytesMut;
19
20use crate::{primitives, tags};
21
22use super::recipient::{check_encoded_recipient, write_recipient};
23use super::tagged::{
24    decode_ctx_boolean, decode_ctx_constructed, decode_ctx_object_id, decode_ctx_real,
25    decode_ctx_unsigned, decode_optional_ctx, expect_closing, expect_opening, next_is_closing,
26};
27use super::{
28    decode_object_property_reference, decode_recipient, encode_object_property_reference,
29    MAX_FRAMED_ITEMS,
30};
31
32/// Encode one bare `BACnetCOVSubscription` sequence. A recipient MAC past
33/// `BACnetAddress::MAX_MAC_LEN` octets is an error, returned before `buf`
34/// changes (#1156).
35pub fn encode_cov_subscription(
36    buf: &mut BytesMut,
37    subscription: &BACnetCOVSubscription,
38) -> Result<(), Error> {
39    check_encoded_recipient(&subscription.recipient.recipient)?;
40    write_cov_subscription(buf, subscription);
41    Ok(())
42}
43
44/// Write a subscription whose recipient has passed the MAC check.
45fn write_cov_subscription(buf: &mut BytesMut, subscription: &BACnetCOVSubscription) {
46    tags::encode_opening_tag(buf, 0);
47    tags::encode_opening_tag(buf, 0);
48    write_recipient(buf, &subscription.recipient.recipient);
49    tags::encode_closing_tag(buf, 0);
50    primitives::encode_ctx_unsigned(buf, 1, subscription.recipient.process_identifier as u64);
51    tags::encode_closing_tag(buf, 0);
52
53    tags::encode_opening_tag(buf, 1);
54    encode_object_property_reference(buf, &subscription.monitored_property_reference);
55    tags::encode_closing_tag(buf, 1);
56
57    primitives::encode_ctx_boolean(buf, 2, subscription.issue_confirmed_notifications);
58    primitives::encode_ctx_unsigned(buf, 3, subscription.time_remaining as u64);
59    if let Some(increment) = subscription.cov_increment {
60        primitives::encode_ctx_real(buf, 4, increment);
61    }
62}
63
64/// Encode a `BACnetLIST of BACnetCOVSubscription` in slice order. Every
65/// recipient is checked before `buf` changes, so a refused list writes nothing.
66pub fn encode_cov_subscription_list(
67    buf: &mut BytesMut,
68    subscriptions: &[BACnetCOVSubscription],
69) -> Result<(), Error> {
70    for subscription in subscriptions {
71        check_encoded_recipient(&subscription.recipient.recipient)?;
72    }
73    for subscription in subscriptions {
74        write_cov_subscription(buf, subscription);
75    }
76    Ok(())
77}
78
79/// Encode one bare `BACnetCOVMultipleSubscription` sequence: `[0]` recipient
80/// process, `[1]` form, `[2]` time remaining, `[3]` maximum notification
81/// delay and `[4]` the nested specifications, each an `[0]` object
82/// identifier plus `[1]` references of `[0]` BACnetPropertyReference,
83/// optional `[1]` REAL increment and `[2]` timestamped flag. A recipient MAC
84/// past `BACnetAddress::MAX_MAC_LEN` octets is an error, returned before `buf`
85/// changes (#1156).
86pub fn encode_cov_multiple_subscription(
87    buf: &mut BytesMut,
88    subscription: &BACnetCOVMultipleSubscription,
89) -> Result<(), Error> {
90    check_encoded_recipient(&subscription.recipient.recipient)?;
91    write_cov_multiple_subscription(buf, subscription);
92    Ok(())
93}
94
95/// Write a Multiple context whose recipient has passed the MAC check.
96fn write_cov_multiple_subscription(
97    buf: &mut BytesMut,
98    subscription: &BACnetCOVMultipleSubscription,
99) {
100    tags::encode_opening_tag(buf, 0);
101    tags::encode_opening_tag(buf, 0);
102    write_recipient(buf, &subscription.recipient.recipient);
103    tags::encode_closing_tag(buf, 0);
104    primitives::encode_ctx_unsigned(buf, 1, subscription.recipient.process_identifier as u64);
105    tags::encode_closing_tag(buf, 0);
106
107    primitives::encode_ctx_boolean(buf, 1, subscription.issue_confirmed_notifications);
108    primitives::encode_ctx_unsigned(buf, 2, subscription.time_remaining as u64);
109    primitives::encode_ctx_unsigned(buf, 3, subscription.max_notification_delay as u64);
110
111    tags::encode_opening_tag(buf, 4);
112    for specification in &subscription.list_of_cov_subscription_specifications {
113        primitives::encode_ctx_object_id(buf, 0, &specification.monitored_object_identifier);
114        tags::encode_opening_tag(buf, 1);
115        for reference in &specification.list_of_cov_references {
116            tags::encode_opening_tag(buf, 0);
117            primitives::encode_ctx_unsigned(buf, 0, reference.property_identifier.to_raw() as u64);
118            if let Some(index) = reference.property_array_index {
119                primitives::encode_ctx_unsigned(buf, 1, index as u64);
120            }
121            tags::encode_closing_tag(buf, 0);
122            if let Some(increment) = reference.cov_increment {
123                primitives::encode_ctx_real(buf, 1, increment);
124            }
125            primitives::encode_ctx_boolean(buf, 2, reference.timestamped);
126        }
127        tags::encode_closing_tag(buf, 1);
128    }
129    tags::encode_closing_tag(buf, 4);
130}
131
132/// Encode a `BACnetLIST of BACnetCOVMultipleSubscription` in slice order.
133/// Every recipient is checked before `buf` changes, so a refused list writes
134/// nothing.
135pub fn encode_cov_multiple_subscription_list(
136    buf: &mut BytesMut,
137    subscriptions: &[BACnetCOVMultipleSubscription],
138) -> Result<(), Error> {
139    for subscription in subscriptions {
140        check_encoded_recipient(&subscription.recipient.recipient)?;
141    }
142    for subscription in subscriptions {
143        write_cov_multiple_subscription(buf, subscription);
144    }
145    Ok(())
146}
147
148/// Decode one bare `BACnetCOVSubscription` at `offset`; returns it and the
149/// offset past its last member.
150///
151/// The members are the `[0]` recipient process, the `[1]` monitored
152/// BACnetObjectPropertyReference (which has no device member), the `[2]`
153/// form, the `[3]` time remaining and the optional `[4]` REAL increment. The
154/// next list element opens with `[0]`, so a `[4]` that follows can only be
155/// this element's increment.
156pub fn decode_cov_subscription(
157    data: &[u8],
158    offset: usize,
159) -> Result<(BACnetCOVSubscription, usize), Error> {
160    let what = "BACnetCOVSubscription";
161    let (recipient, pos) = decode_recipient_process(data, offset, what)?;
162    let (reference, pos) = decode_ctx_constructed(data, pos, 1, what)?;
163    let monitored_property_reference = decode_object_property_reference(reference)?;
164    let (issue_confirmed_notifications, pos) = decode_ctx_boolean(data, pos, 2, what)?;
165    let (time_remaining, pos) = decode_ctx_unsigned::<u32>(data, pos, 3, what)?;
166    let (cov_increment, pos) = decode_optional_ctx(data, pos, 4, what, decode_ctx_real)?;
167    Ok((
168        BACnetCOVSubscription {
169            recipient,
170            monitored_property_reference,
171            issue_confirmed_notifications,
172            time_remaining,
173            cov_increment,
174        },
175        pos,
176    ))
177}
178
179/// Decode one bare `BACnetCOVMultipleSubscription` at `offset`; returns it
180/// and the offset past its closing `[4]` tag, in the layout
181/// [`encode_cov_multiple_subscription`] writes. Each nested list holds at
182/// most `MAX_FRAMED_ITEMS` entries.
183pub fn decode_cov_multiple_subscription(
184    data: &[u8],
185    offset: usize,
186) -> Result<(BACnetCOVMultipleSubscription, usize), Error> {
187    let what = "BACnetCOVMultipleSubscription";
188    let (recipient, pos) = decode_recipient_process(data, offset, what)?;
189    let (issue_confirmed_notifications, pos) = decode_ctx_boolean(data, pos, 1, what)?;
190    let (time_remaining, pos) = decode_ctx_unsigned::<u32>(data, pos, 2, what)?;
191    let (max_notification_delay, pos) = decode_ctx_unsigned::<u32>(data, pos, 3, what)?;
192    let mut pos = expect_opening(data, pos, 4, what)?;
193    let mut specifications = Vec::new();
194    while !is_closing(data, pos, 4)? {
195        if specifications.len() >= MAX_FRAMED_ITEMS {
196            return Err(Error::overflow(
197                pos,
198                format!("{what}: specification count exceeds limit"),
199            ));
200        }
201        let (specification, end) = decode_specification(data, pos, what)?;
202        specifications.push(specification);
203        pos = end;
204    }
205    let pos = expect_closing(data, pos, 4, what)?;
206    Ok((
207        BACnetCOVMultipleSubscription {
208            recipient,
209            issue_confirmed_notifications,
210            time_remaining,
211            max_notification_delay,
212            list_of_cov_subscription_specifications: specifications,
213        },
214        pos,
215    ))
216}
217
218/// The `[0]` monitored object identifier, then the `[1]` list of COV
219/// references.
220fn decode_specification(
221    data: &[u8],
222    offset: usize,
223    what: &str,
224) -> Result<(BACnetCOVSubscriptionSpecification, usize), Error> {
225    let (monitored_object_identifier, pos) = decode_ctx_object_id(data, offset, 0, what)?;
226    let mut pos = expect_opening(data, pos, 1, what)?;
227    let mut references = Vec::new();
228    while !is_closing(data, pos, 1)? {
229        if references.len() >= MAX_FRAMED_ITEMS {
230            return Err(Error::overflow(
231                pos,
232                format!("{what}: COV reference count exceeds limit"),
233            ));
234        }
235        let (reference, end) = decode_cov_reference(data, pos, what)?;
236        references.push(reference);
237        pos = end;
238    }
239    Ok((
240        BACnetCOVSubscriptionSpecification {
241            monitored_object_identifier,
242            list_of_cov_references: references,
243        },
244        expect_closing(data, pos, 1, what)?,
245    ))
246}
247
248/// The `[0]` BACnetPropertyReference (`[0]` identifier and optional `[1]`
249/// index), the optional `[1]` REAL increment and the `[2]` timestamped flag.
250fn decode_cov_reference(
251    data: &[u8],
252    offset: usize,
253    what: &str,
254) -> Result<(BACnetCOVReference, usize), Error> {
255    let pos = expect_opening(data, offset, 0, what)?;
256    let (property_identifier, pos) = decode_ctx_unsigned::<u32>(data, pos, 0, what)?;
257    let (property_array_index, pos) =
258        decode_optional_ctx(data, pos, 1, what, decode_ctx_unsigned::<u32>)?;
259    let pos = expect_closing(data, pos, 0, what)?;
260    let (cov_increment, pos) = decode_optional_ctx(data, pos, 1, what, decode_ctx_real)?;
261    let (timestamped, pos) = decode_ctx_boolean(data, pos, 2, what)?;
262    Ok((
263        BACnetCOVReference {
264            property_identifier: PropertyIdentifier::from_raw(property_identifier),
265            property_array_index,
266            cov_increment,
267            timestamped,
268        },
269        pos,
270    ))
271}
272
273/// The `[0]` BACnetRecipientProcess: the `[0]` recipient CHOICE and the `[1]`
274/// process identifier.
275fn decode_recipient_process(
276    data: &[u8],
277    offset: usize,
278    what: &str,
279) -> Result<(BACnetRecipientProcess, usize), Error> {
280    let pos = expect_opening(data, offset, 0, what)?;
281    let pos = expect_opening(data, pos, 0, what)?;
282    let (recipient, pos) = decode_recipient(data, pos)?;
283    let pos = expect_closing(data, pos, 0, what)?;
284    let (process_identifier, pos) = decode_ctx_unsigned::<u32>(data, pos, 1, what)?;
285    let pos = expect_closing(data, pos, 0, what)?;
286    Ok((
287        BACnetRecipientProcess {
288            recipient,
289            process_identifier,
290        },
291        pos,
292    ))
293}
294
295/// Whether closing tag `tag` starts at `offset`. A list that runs off the end
296/// of the data is truncated, so it fails as a short buffer.
297fn is_closing(data: &[u8], offset: usize, tag: u8) -> Result<bool, Error> {
298    if offset >= data.len() {
299        return Err(Error::buffer_too_short(offset + 1, data.len()));
300    }
301    next_is_closing(data, offset, tag)
302}