Skip to main content

bacnet_encoding/constructed/
mod.rs

1//! Full ASN.1 framing codecs for constructed Clause-21 types whose property
2//! values are CHOICE/SEQUENCE productions — context-tagged framing per
3//! ASHRAE 135-2020 Clause 20.2.1.5/20.2.1.6.
4//!
5//! These codecs encode to and decode from raw application-layer bytes, the
6//! same bytes carried by [`bacnet_types::primitives::PropertyValue::ApplicationData`].
7//! Objects serve framed properties (e.g. `Event_Parameters`,
8//! `Fault_Parameters`, `Recipient_List`) by encoding with these functions;
9//! the flat application-tagged model in [`crate::primitives::encode_property_value`]
10//! cannot express their wire form.
11//!
12//! Tag-form rules applied here:
13//!
14//! - A CHOICE alternative over a `SEQUENCE` is an opening/closing context tag
15//!   pair around the SEQUENCE's members.
16//! - A CHOICE alternative over a primitive base type is a context-specific
17//!   primitive tag holding the raw contents (e.g. `none [0] NULL`).
18//! - A context-tagged inner `CHOICE` is always explicitly tagged: an
19//!   opening/closing pair around the alternative's own encoding.
20//! - Inner members declared `[n] T` over primitive `T` are context-tagged
21//!   primitives; inner `SEQUENCE OF` / embedded `SEQUENCE` members are
22//!   constructed opening/closing pairs holding application-tagged elements.
23
24use bacnet_types::constructed::{
25    BACnetDeviceObjectPropertyReference, BACnetExtendedPropertyState, BACnetPropertyStates,
26    BACnetProprietaryPropertyState,
27};
28use bacnet_types::error::Error;
29use bytes::BytesMut;
30
31use crate::primitives;
32use crate::tags::{self, TagClass};
33use tagged::{
34    contents, decode_ctx_object_id, decode_ctx_unsigned, decode_optional_ctx, expect_closing,
35    expect_end, expect_opening,
36};
37
38pub mod access_credential;
39pub mod access_rule;
40mod action_list;
41pub mod assigned_landing_calls;
42mod audit_notification;
43mod audit_record;
44pub mod calendar;
45mod channel_value;
46mod color_command;
47pub mod cov_subscription;
48mod event_log_record;
49mod event_notification;
50pub mod event_notification_subscription;
51pub mod event_parameter;
52pub mod fault_parameter;
53mod floor_pairs;
54pub mod landing_call_status;
55pub mod landing_door_status;
56pub mod lift_car_call_list;
57mod lighting_command;
58mod log_fields;
59mod log_multiple_record;
60mod log_record;
61mod members;
62pub mod object_property_reference;
63pub mod port_permission;
64mod property_access_result;
65mod property_value;
66mod read_access;
67pub mod recipient;
68mod scale;
69pub mod schedule;
70mod shed_level;
71pub mod staging;
72pub mod tagged;
73mod value_source;
74
75pub use access_credential::{
76    decode_assigned_access_rights, decode_authentication_factor,
77    decode_authentication_factor_format, decode_credential_authentication_factor,
78    encode_assigned_access_rights, encode_authentication_factor,
79    encode_authentication_factor_format, encode_credential_authentication_factor,
80};
81pub use access_rule::{decode_access_rule, encode_access_rule};
82pub use action_list::{
83    decode_action_command, decode_action_list, encode_action_command, encode_action_list,
84};
85pub use assigned_landing_calls::{decode_assigned_landing_calls, encode_assigned_landing_calls};
86pub use audit_notification::{decode_audit_notification_at, encode_audit_notification};
87pub use audit_record::{
88    decode_audit_log_record, decode_audit_log_record_at, decode_audit_log_record_result_at,
89    encode_audit_log_record, encode_audit_log_record_result,
90};
91pub use calendar::{
92    decode_calendar_entry, decode_calendar_entry_list, decode_date_range, encode_calendar_entry,
93    encode_calendar_entry_list, encode_date_range,
94};
95pub use channel_value::{channel_value_end, is_lighting_command_channel_value};
96pub use color_command::{
97    decode_color_command, decode_color_command_value, decode_xy_color, encode_color_command,
98    encode_xy_color,
99};
100pub use cov_subscription::{
101    decode_cov_multiple_subscription, decode_cov_subscription, encode_cov_multiple_subscription,
102    encode_cov_multiple_subscription_list, encode_cov_subscription, encode_cov_subscription_list,
103};
104pub use event_log_record::{decode_event_log_record, encode_event_log_record};
105pub use event_notification::{
106    decode_event_notification, decode_event_notification_tolerant, decode_notification_parameters,
107    encode_event_notification, encode_notification_parameters,
108};
109pub use event_notification_subscription::{
110    decode_event_notification_subscription, encode_event_notification_subscription,
111    encode_event_notification_subscription_list,
112};
113pub use event_parameter::{decode_event_parameter, encode_event_parameter};
114pub use fault_parameter::{decode_fault_parameters, encode_fault_parameters};
115pub use landing_call_status::{
116    decode_landing_call_status, decode_landing_call_status_list, encode_landing_call_status,
117    encode_landing_call_status_list,
118};
119pub use landing_door_status::{decode_landing_door_status, encode_landing_door_status};
120pub use lift_car_call_list::{decode_lift_car_call_list, encode_lift_car_call_list};
121pub use lighting_command::{
122    decode_lighting_command, decode_lighting_command_value, encode_lighting_command,
123};
124pub use log_multiple_record::{decode_log_multiple_record, encode_log_multiple_record};
125pub use log_record::{decode_log_record, encode_log_record};
126pub use object_property_reference::{
127    decode_object_property_reference, decode_object_property_reference_at,
128    decode_setpoint_reference, decode_setpoint_reference_at, encode_object_property_reference,
129    encode_setpoint_reference,
130};
131pub use port_permission::{decode_port_permission, encode_port_permission};
132pub use property_access_result::{decode_property_access_result, encode_property_access_result};
133pub use property_value::{
134    decode_bacnet_property_value, decode_bacnet_property_value_in_list,
135    decode_bacnet_property_value_in_list_detailed, encode_bacnet_property_value,
136    extract_property_value, PropertyValueBoundary, PropertyValueDecodeError,
137    PropertyValueDecodeFailure, PropertyValueDecodeStage,
138};
139pub use read_access::{
140    decode_property_reference, decode_read_access_specification, encode_property_reference,
141    encode_read_access_specification,
142};
143pub use recipient::{
144    check_decoded_mac_len, check_encoded_mac_len, decode_destination, decode_destination_list,
145    decode_recipient, encode_destination, encode_destination_list, encode_recipient,
146};
147pub use scale::{decode_prescale, decode_scale, encode_prescale, encode_scale};
148pub use schedule::{
149    decode_daily_schedule, decode_exception_schedule, decode_special_event,
150    decode_special_event_period, decode_time_value, decode_weekly_schedule, encode_daily_schedule,
151    encode_exception_schedule, encode_special_event, encode_special_event_period,
152    encode_time_value, encode_weekly_schedule,
153};
154pub use shed_level::{decode_shed_level, encode_shed_level};
155pub use staging::{
156    decode_device_object_reference, decode_stage_limit_value, encode_device_object_reference,
157    encode_stage_limit_value,
158};
159
160pub use value_source::{decode_value_source, encode_value_source};
161
162/// Upper bound on decoded SEQUENCE OF / list lengths, mirroring the socket-
163/// facing posture of `bacnet-services`' `MAX_DECODED_ITEMS`. Prevents memory
164/// exhaustion from malformed framed payloads.
165const MAX_FRAMED_ITEMS: usize = 10_000;
166
167// ---------------------------------------------------------------------------
168// BACnetPropertyStates (Clause 21 CHOICE) — spec-tagged framing
169// ---------------------------------------------------------------------------
170
171/// Encode a [`BACnetPropertyStates`] using the Standard 135-2020 Clause 21 tags.
172///
173/// Returns an error without modifying `buf` when a constructed proprietary
174/// value does not contain a BACnet TLV sequence.
175pub fn encode_property_state(
176    buf: &mut BytesMut,
177    state: &BACnetPropertyStates,
178) -> Result<(), Error> {
179    use BACnetPropertyStates as S;
180    if let S::Other(value) = state {
181        if value.is_constructed() {
182            validate_tlv_sequence(value.data(), "proprietary property-state body")?;
183            let mut framed = BytesMut::new();
184            tags::encode_opening_tag(&mut framed, value.tag());
185            let (_, body_start) = tags::decode_tag(&framed, 0)?;
186            framed.extend_from_slice(value.data());
187            tags::encode_closing_tag(&mut framed, value.tag());
188            let (_, end) = tags::extract_context_value(&framed, body_start, value.tag())?;
189            expect_end(&framed, end, end, "proprietary property-state body")?;
190        }
191    }
192    match state {
193        S::BooleanValue(v) => primitives::encode_ctx_boolean(buf, 0, *v),
194        S::BinaryValue(v) => primitives::encode_ctx_enumerated(buf, 1, *v),
195        S::EventType(v) => primitives::encode_ctx_enumerated(buf, 2, *v),
196        S::Polarity(v) => primitives::encode_ctx_enumerated(buf, 3, *v),
197        S::ProgramChange(v) => primitives::encode_ctx_enumerated(buf, 4, *v),
198        S::ProgramState(v) => primitives::encode_ctx_enumerated(buf, 5, *v),
199        S::ReasonForHalt(v) => primitives::encode_ctx_enumerated(buf, 6, *v),
200        S::Reliability(v) => primitives::encode_ctx_enumerated(buf, 7, *v),
201        S::State(v) => primitives::encode_ctx_enumerated(buf, 8, *v),
202        S::SystemStatus(v) => primitives::encode_ctx_enumerated(buf, 9, *v),
203        S::Units(v) => primitives::encode_ctx_enumerated(buf, 10, *v),
204        S::UnsignedValue(v) => primitives::encode_ctx_unsigned(buf, 11, *v as u64),
205        S::LifeSafetyMode(v) => primitives::encode_ctx_enumerated(buf, 12, *v),
206        S::LifeSafetyState(v) => primitives::encode_ctx_enumerated(buf, 13, *v),
207        S::RestartReason(v) => primitives::encode_ctx_enumerated(buf, 14, *v),
208        S::DoorAlarmState(v) => primitives::encode_ctx_enumerated(buf, 15, *v),
209        S::Action(v) => primitives::encode_ctx_enumerated(buf, 16, *v),
210        S::DoorSecuredStatus(v) => primitives::encode_ctx_enumerated(buf, 17, *v),
211        S::DoorStatus(v) => primitives::encode_ctx_enumerated(buf, 18, *v),
212        S::DoorValue(v) => primitives::encode_ctx_enumerated(buf, 19, *v),
213        S::FileAccessMethod(v) => primitives::encode_ctx_enumerated(buf, 20, *v),
214        S::LockStatus(v) => primitives::encode_ctx_enumerated(buf, 21, *v),
215        S::LifeSafetyOperation(v) => primitives::encode_ctx_enumerated(buf, 22, *v),
216        S::Maintenance(v) => primitives::encode_ctx_enumerated(buf, 23, *v),
217        S::NodeType(v) => primitives::encode_ctx_enumerated(buf, 24, *v),
218        S::NotifyType(v) => primitives::encode_ctx_enumerated(buf, 25, *v),
219        S::ShedState(v) => primitives::encode_ctx_enumerated(buf, 27, *v),
220        S::SilencedState(v) => primitives::encode_ctx_enumerated(buf, 28, *v),
221        S::AccessEvent(v) => primitives::encode_ctx_enumerated(buf, 30, *v),
222        S::ZoneOccupancyState(v) => primitives::encode_ctx_enumerated(buf, 31, *v),
223        S::AccessCredentialDisableReason(v) => primitives::encode_ctx_enumerated(buf, 32, *v),
224        S::AccessCredentialDisable(v) => primitives::encode_ctx_enumerated(buf, 33, *v),
225        S::AuthenticationStatus(v) => primitives::encode_ctx_enumerated(buf, 34, *v),
226        S::BackupState(v) => primitives::encode_ctx_enumerated(buf, 36, *v),
227        S::WriteStatus(v) => primitives::encode_ctx_enumerated(buf, 37, *v),
228        S::LightingInProgress(v) => primitives::encode_ctx_enumerated(buf, 38, *v),
229        S::LightingOperation(v) => primitives::encode_ctx_enumerated(buf, 39, *v),
230        S::LightingTransition(v) => primitives::encode_ctx_enumerated(buf, 40, *v),
231        S::IntegerValue(v) => primitives::encode_ctx_signed(buf, 41, *v),
232        S::BinaryLightingValue(v) => primitives::encode_ctx_enumerated(buf, 42, *v),
233        S::TimerState(v) => primitives::encode_ctx_enumerated(buf, 43, *v),
234        S::TimerTransition(v) => primitives::encode_ctx_enumerated(buf, 44, *v),
235        S::BacnetIpMode(v) => primitives::encode_ctx_enumerated(buf, 45, *v),
236        S::NetworkPortCommand(v) => primitives::encode_ctx_enumerated(buf, 46, *v),
237        S::NetworkType(v) => primitives::encode_ctx_enumerated(buf, 47, *v),
238        S::NetworkNumberQuality(v) => primitives::encode_ctx_enumerated(buf, 48, *v),
239        S::EscalatorOperationDirection(v) => primitives::encode_ctx_enumerated(buf, 49, *v),
240        S::EscalatorFault(v) => primitives::encode_ctx_enumerated(buf, 50, *v),
241        S::EscalatorMode(v) => primitives::encode_ctx_enumerated(buf, 51, *v),
242        S::LiftCarDirection(v) => primitives::encode_ctx_enumerated(buf, 52, *v),
243        S::LiftCarDoorCommand(v) => primitives::encode_ctx_enumerated(buf, 53, *v),
244        S::LiftCarDriveStatus(v) => primitives::encode_ctx_enumerated(buf, 54, *v),
245        S::LiftCarMode(v) => primitives::encode_ctx_enumerated(buf, 55, *v),
246        S::LiftGroupMode(v) => primitives::encode_ctx_enumerated(buf, 56, *v),
247        S::LiftFault(v) => primitives::encode_ctx_enumerated(buf, 57, *v),
248        S::ProtocolLevel(v) => primitives::encode_ctx_enumerated(buf, 58, *v),
249        S::AuditLevel(v) => primitives::encode_ctx_enumerated(buf, 59, *v),
250        S::AuditOperation(v) => primitives::encode_ctx_enumerated(buf, 60, *v),
251        S::ExtendedValue(v) => primitives::encode_ctx_unsigned(buf, 63, v.encoded() as u64),
252        S::Other(v) if v.is_constructed() => {
253            tags::encode_opening_tag(buf, v.tag());
254            buf.extend_from_slice(v.data());
255            tags::encode_closing_tag(buf, v.tag());
256        }
257        S::Other(v) => primitives::encode_ctx_octet_string(buf, v.tag(), v.data()),
258    }
259    Ok(())
260}
261
262/// Decode one [`BACnetPropertyStates`] CHOICE element at `offset`.
263///
264/// Proprietary tags 64 through 254 retain their encoded contents in
265/// [`BACnetPropertyStates::Other`]. Reserved standard tags are rejected.
266pub fn decode_property_state(
267    data: &[u8],
268    offset: usize,
269) -> Result<(BACnetPropertyStates, usize), Error> {
270    use BACnetPropertyStates as S;
271    let (tag, pos) = tags::decode_tag(data, offset)?;
272    if tag.class != TagClass::Context || tag.is_closing {
273        return Err(tagged::misplaced_tag(
274            data,
275            &tag,
276            None,
277            offset,
278            "BACnetPropertyStates: expected a context tag",
279        ));
280    }
281    if tag.is_opening {
282        if !(64..=254).contains(&tag.number) {
283            return Err(Error::invalid_tag(
284                offset,
285                "BACnetPropertyStates: constructed form requires a proprietary tag",
286            ));
287        }
288        let (content, end) = tags::extract_context_value(data, pos, tag.number)?;
289        validate_tlv_sequence(content, "proprietary property-state body")?;
290        return Ok((
291            S::Other(BACnetProprietaryPropertyState::constructed(
292                tag.number,
293                content.to_vec(),
294            )?),
295            end,
296        ));
297    }
298    let (content, end) = contents(data, pos, tag.length)?;
299    let unsigned = || -> Result<u32, Error> {
300        u32::try_from(primitives::decode_unsigned(content)?)
301            .map_err(|_| Error::out_of_range(pos, "BACnetPropertyStates: contents exceed u32"))
302    };
303    let state = match tag.number {
304        0 => {
305            if content.len() != 1 {
306                return Err(Error::decoding(
307                    offset,
308                    format!(
309                        "BACnetPropertyStates boolean-value: expected 1 contents octet, got {}",
310                        content.len()
311                    ),
312                ));
313            }
314            match content[0] {
315                0 => S::BooleanValue(false),
316                1 => S::BooleanValue(true),
317                value => {
318                    return Err(Error::decoding(
319                        pos,
320                        format!("BACnetPropertyStates boolean-value must be 0 or 1, got {value}"),
321                    ));
322                }
323            }
324        }
325        1 => S::BinaryValue(unsigned()?),
326        2 => S::EventType(unsigned()?),
327        3 => S::Polarity(unsigned()?),
328        4 => S::ProgramChange(unsigned()?),
329        5 => S::ProgramState(unsigned()?),
330        6 => S::ReasonForHalt(unsigned()?),
331        7 => S::Reliability(unsigned()?),
332        8 => S::State(unsigned()?),
333        9 => S::SystemStatus(unsigned()?),
334        10 => S::Units(unsigned()?),
335        11 => S::UnsignedValue(unsigned()?),
336        12 => S::LifeSafetyMode(unsigned()?),
337        13 => S::LifeSafetyState(unsigned()?),
338        14 => S::RestartReason(unsigned()?),
339        15 => S::DoorAlarmState(unsigned()?),
340        16 => S::Action(unsigned()?),
341        17 => S::DoorSecuredStatus(unsigned()?),
342        18 => S::DoorStatus(unsigned()?),
343        19 => S::DoorValue(unsigned()?),
344        20 => S::FileAccessMethod(unsigned()?),
345        21 => S::LockStatus(unsigned()?),
346        22 => S::LifeSafetyOperation(unsigned()?),
347        23 => S::Maintenance(unsigned()?),
348        24 => S::NodeType(unsigned()?),
349        25 => S::NotifyType(unsigned()?),
350        27 => S::ShedState(unsigned()?),
351        28 => S::SilencedState(unsigned()?),
352        30 => S::AccessEvent(unsigned()?),
353        31 => S::ZoneOccupancyState(unsigned()?),
354        32 => S::AccessCredentialDisableReason(unsigned()?),
355        33 => S::AccessCredentialDisable(unsigned()?),
356        34 => S::AuthenticationStatus(unsigned()?),
357        36 => S::BackupState(unsigned()?),
358        37 => S::WriteStatus(unsigned()?),
359        38 => S::LightingInProgress(unsigned()?),
360        39 => S::LightingOperation(unsigned()?),
361        40 => S::LightingTransition(unsigned()?),
362        41 => S::IntegerValue(primitives::decode_signed_canonical(content)?),
363        42 => S::BinaryLightingValue(unsigned()?),
364        43 => S::TimerState(unsigned()?),
365        44 => S::TimerTransition(unsigned()?),
366        45 => S::BacnetIpMode(unsigned()?),
367        46 => S::NetworkPortCommand(unsigned()?),
368        47 => S::NetworkType(unsigned()?),
369        48 => S::NetworkNumberQuality(unsigned()?),
370        49 => S::EscalatorOperationDirection(unsigned()?),
371        50 => S::EscalatorFault(unsigned()?),
372        51 => S::EscalatorMode(unsigned()?),
373        52 => S::LiftCarDirection(unsigned()?),
374        53 => S::LiftCarDoorCommand(unsigned()?),
375        54 => S::LiftCarDriveStatus(unsigned()?),
376        55 => S::LiftCarMode(unsigned()?),
377        56 => S::LiftGroupMode(unsigned()?),
378        57 => S::LiftFault(unsigned()?),
379        58 => S::ProtocolLevel(unsigned()?),
380        59 => S::AuditLevel(unsigned()?),
381        60 => S::AuditOperation(unsigned()?),
382        63 => S::ExtendedValue(BACnetExtendedPropertyState::from_encoded(unsigned()?)?),
383        other @ 64..=254 => S::Other(BACnetProprietaryPropertyState::primitive(
384            other,
385            content.to_vec(),
386        )?),
387        reserved => {
388            return Err(Error::invalid_tag(
389                offset,
390                format!("BACnetPropertyStates context tag {reserved} is reserved"),
391            ));
392        }
393    };
394    Ok((state, end))
395}
396
397// ---------------------------------------------------------------------------
398// BACnetDeviceObjectPropertyReference (Clause 21 SEQUENCE)
399// ---------------------------------------------------------------------------
400
401/// Encode the context-tagged members of a `BACnetDeviceObjectPropertyReference`:
402/// `object-identifier [0]`, `property-identifier [1]`, optional
403/// `property-array-index [2]`, optional `device-identifier [3]` — the body
404/// that sits between the enclosing field's opening/closing tags.
405pub(crate) fn encode_dopr_body(buf: &mut BytesMut, r: &BACnetDeviceObjectPropertyReference) {
406    primitives::encode_ctx_object_id(buf, 0, &r.object_identifier);
407    primitives::encode_ctx_unsigned(buf, 1, r.property_identifier as u64);
408    if let Some(index) = r.property_array_index {
409        primitives::encode_ctx_unsigned(buf, 2, index as u64);
410    }
411    if let Some(ref device) = r.device_identifier {
412        primitives::encode_ctx_object_id(buf, 3, device);
413    }
414}
415
416/// Decode the members written by [`encode_dopr_body`], stopping before the
417/// enclosing closing tag.
418pub(crate) fn decode_dopr_body(
419    data: &[u8],
420    offset: usize,
421    what: &str,
422) -> Result<(BACnetDeviceObjectPropertyReference, usize), Error> {
423    let (object_identifier, offset) = decode_ctx_object_id(data, offset, 0, what)?;
424    let (property_identifier, offset) = decode_ctx_unsigned::<u32>(data, offset, 1, what)?;
425    let (property_array_index, offset) =
426        decode_optional_ctx(data, offset, 2, what, decode_ctx_unsigned::<u32>)?;
427    let (device_identifier, offset) =
428        decode_optional_ctx(data, offset, 3, what, decode_ctx_object_id)?;
429    Ok((
430        BACnetDeviceObjectPropertyReference {
431            object_identifier,
432            property_identifier,
433            property_array_index,
434            device_identifier,
435        },
436        offset,
437    ))
438}
439
440/// Encode one bare `BACnetDeviceObjectPropertyReference`, the counterpart of
441/// [`decode_device_object_property_reference`]: its members with no enclosing
442/// frame, so a BACnetLIST of these references is their encodings back to back.
443pub fn encode_device_object_property_reference(
444    buf: &mut BytesMut,
445    r: &BACnetDeviceObjectPropertyReference,
446) {
447    encode_dopr_body(buf, r);
448}
449
450/// Decode one bare `BACnetDeviceObjectPropertyReference` at `offset`; returns
451/// it and the offset past its last member. A BACnetLIST of these references
452/// concatenates its elements with no frame, so walking the list calls this at
453/// each element's start.
454pub fn decode_device_object_property_reference(
455    data: &[u8],
456    offset: usize,
457) -> Result<(BACnetDeviceObjectPropertyReference, usize), Error> {
458    decode_dopr_body(data, offset, "BACnetDeviceObjectPropertyReference")
459}
460
461/// Validate a BACnet TLV sequence without normalizing its encoded values.
462///
463/// This checks matching context tags, the context nesting limit, and
464/// application-value forms while preserving defined CharacterString encodings.
465pub fn validate_tlv_sequence(data: &[u8], what: &str) -> Result<(), Error> {
466    let mut offset = 0;
467    let mut count = 0;
468    while offset < data.len() {
469        if count >= MAX_FRAMED_ITEMS {
470            return Err(Error::overflow(
471                offset,
472                format!("{what}: sequence exceeds item limit"),
473            ));
474        }
475        let (tag, content) = tags::decode_tag(data, offset)?;
476        if tag.is_opening {
477            let (inner, next) = tags::extract_context_value(data, content, tag.number)?;
478            validate_tlv_sequence(inner, what)?;
479            offset = next;
480        } else if tag.class == TagClass::Application {
481            offset = primitives::validate_application_value(data, offset)?;
482        } else {
483            let (_, next) = primitives::decode_application_value(data, offset)?;
484            offset = next;
485        }
486        count += 1;
487    }
488    Ok(())
489}
490
491/// Validate the shared Extended Event/Fault `parameters` production.
492/// Its only context-tagged CHOICE is `reference [0]` over
493/// `BACnetDeviceObjectPropertyReference`; NotificationParameters uses a
494/// different Extended production with `property-value [0]`.
495pub(crate) fn validate_extended_parameters(data: &[u8], what: &str) -> Result<(), Error> {
496    let mut offset = 0;
497    let mut count = 0;
498    while offset < data.len() {
499        if count >= MAX_FRAMED_ITEMS {
500            return Err(Error::overflow(
501                offset,
502                format!("{what}: parameters exceed item limit"),
503            ));
504        }
505        let (tag, _) = tags::decode_tag(data, offset)?;
506        if tag.class == TagClass::Context {
507            let content = expect_opening(data, offset, 0, what)?;
508            let (_, after_reference) = decode_dopr_body(data, content, what)?;
509            offset = expect_closing(data, after_reference, 0, what)?;
510        } else {
511            offset = primitives::validate_application_value(data, offset)?;
512        }
513        count += 1;
514    }
515    Ok(())
516}
517
518#[cfg(test)]
519pub(crate) mod tests;