Skip to main content

bacnet_encoding/constructed/
reference_sinks.rs

1//! Private bounded assembly for the five public reference sink encoders.
2
3use bacnet_types::constructed::{
4    BACnetDeviceObjectPropertyReference, BACnetDeviceObjectReference,
5    BACnetObjectPropertyReference, PropertyReference,
6};
7use bacnet_types::primitives::ObjectIdentifier;
8
9use crate::primitives::{encode_ctx_object_id_to_sink, encode_ctx_unsigned_to_sink};
10use crate::sink::{EncodeSink, SinkFull};
11use crate::tags::{encode_closing_tag_to_sink, encode_opening_tag_to_sink};
12
13/// Append one complete `BACnetPropertyReference` in one sink write.
14///
15/// This preserves [`super::encode_property_reference`]'s accepted inputs and
16/// wire bytes. Its bounded assembly stages at most ten encoded bytes. A known
17/// shortage returns the total required and available lengths without calling
18/// `write`; an arbitrary sink refusal is returned unchanged. On failure, earlier
19/// output, the cursor, container length and unused tail remain unchanged.
20/// Earlier successful encoder calls stay committed if a later call fails.
21///
22/// No semantic validation is added. The staging bound is not a whole-stack
23/// bound, and the crate still requires `std` or `alloc` with a bounded sink.
24///
25/// ```
26/// use bacnet_encoding::constructed::encode_property_reference_to_sink;
27/// use bacnet_types::constructed::PropertyReference;
28/// use bacnet_types::enums::PropertyIdentifier;
29///
30/// let reference = PropertyReference {
31///     property_identifier: PropertyIdentifier::PRESENT_VALUE,
32///     property_array_index: None,
33/// };
34/// let mut storage = [0xA5; 2];
35/// let mut tail = &mut storage[..];
36/// assert_eq!(encode_property_reference_to_sink(&mut tail, &reference)?, 2);
37/// assert!(tail.is_empty());
38/// assert_eq!(storage, [0x09, 0x55]);
39/// # Ok::<(), bacnet_encoding::sink::SinkFull>(())
40/// ```
41pub fn encode_property_reference_to_sink<S: EncodeSink + ?Sized>(
42    sink: &mut S,
43    reference: &PropertyReference,
44) -> Result<usize, SinkFull> {
45    stage_and_write::<S, 10>(sink, |tail| {
46        encode_ctx_unsigned_to_sink(tail, 0, u64::from(reference.property_identifier.to_raw()))?;
47        if let Some(index) = reference.property_array_index {
48            encode_ctx_unsigned_to_sink(tail, 1, u64::from(index))?;
49        }
50        Ok(())
51    })
52}
53
54/// Append a complete bare `BACnetObjectPropertyReference` in one sink write.
55///
56/// This retains [`super::encode_object_property_reference`]'s bytes and input
57/// handling with at most fifteen staging bytes. The [reference storage
58/// contract](encode_property_reference_to_sink) applies to the whole sequence,
59/// including its optional index. An absent index and `Some(0)` remain distinct.
60pub fn encode_object_property_reference_to_sink<S: EncodeSink + ?Sized>(
61    sink: &mut S,
62    reference: &BACnetObjectPropertyReference,
63) -> Result<usize, SinkFull> {
64    stage_and_write::<S, 15>(sink, |tail| {
65        stage_object_property(
66            tail,
67            &reference.object_identifier,
68            reference.property_identifier,
69            reference.property_array_index,
70        )
71    })
72}
73
74/// Append a complete context-0 `BACnetSetpointReference` frame in one write.
75///
76/// This preserves [`super::encode_setpoint_reference`]'s bytes and input handling
77/// with at most seventeen staging bytes. The [reference storage
78/// contract](encode_property_reference_to_sink) includes both frame markers.
79/// Callers still represent an absent optional setpoint by emitting nothing;
80/// this function encodes a supplied reference and adds no optional-argument API.
81pub fn encode_setpoint_reference_to_sink<S: EncodeSink + ?Sized>(
82    sink: &mut S,
83    reference: &BACnetObjectPropertyReference,
84) -> Result<usize, SinkFull> {
85    stage_and_write::<S, 17>(sink, |tail| {
86        encode_opening_tag_to_sink(tail, 0)?;
87        stage_object_property(
88            tail,
89            &reference.object_identifier,
90            reference.property_identifier,
91            reference.property_array_index,
92        )?;
93        encode_closing_tag_to_sink(tail, 0)?;
94        Ok(())
95    })
96}
97
98/// Append a complete bare `BACnetDeviceObjectPropertyReference` in one write.
99///
100/// This preserves [`super::encode_device_object_property_reference`]'s bytes
101/// and inputs with at most twenty staging bytes. The optional device remains
102/// context 3 after the object, property and optional index. The [reference
103/// storage contract](encode_property_reference_to_sink) covers all members;
104/// it does not make enclosing variable-length encoders atomic.
105pub fn encode_device_object_property_reference_to_sink<S: EncodeSink + ?Sized>(
106    sink: &mut S,
107    reference: &BACnetDeviceObjectPropertyReference,
108) -> Result<usize, SinkFull> {
109    stage_and_write::<S, 20>(sink, |tail| {
110        stage_object_property(
111            tail,
112            &reference.object_identifier,
113            reference.property_identifier,
114            reference.property_array_index,
115        )?;
116        if let Some(device) = &reference.device_identifier {
117            encode_ctx_object_id_to_sink(tail, 3, device)?;
118        }
119        Ok(())
120    })
121}
122
123/// Append a complete bare `BACnetDeviceObjectReference` in one sink write.
124///
125/// This retains [`super::encode_device_object_reference`]'s bytes and input
126/// handling with at most ten staging bytes. The optional device uses context 0
127/// before the required object in context 1. The [reference storage
128/// contract](encode_property_reference_to_sink) applies to the whole sequence.
129/// The encoder adds no validation of the supplied device identifier's type.
130pub fn encode_device_object_reference_to_sink<S: EncodeSink + ?Sized>(
131    sink: &mut S,
132    reference: &BACnetDeviceObjectReference,
133) -> Result<usize, SinkFull> {
134    stage_and_write::<S, 10>(sink, |tail| {
135        if let Some(device) = &reference.device_identifier {
136            encode_ctx_object_id_to_sink(tail, 0, device)?;
137        }
138        encode_ctx_object_id_to_sink(tail, 1, &reference.object_identifier)?;
139        Ok(())
140    })
141}
142
143fn stage_object_property(
144    tail: &mut &mut [u8],
145    object: &ObjectIdentifier,
146    property: u32,
147    index: Option<u32>,
148) -> Result<(), SinkFull> {
149    encode_ctx_object_id_to_sink(tail, 0, object)?;
150    encode_ctx_unsigned_to_sink(tail, 1, u64::from(property))?;
151    if let Some(index) = index {
152        encode_ctx_unsigned_to_sink(tail, 2, u64::from(index))?;
153    }
154    Ok(())
155}
156
157fn stage_and_write<S: EncodeSink + ?Sized, const N: usize>(
158    sink: &mut S,
159    stage: impl FnOnce(&mut &mut [u8]) -> Result<(), SinkFull>,
160) -> Result<usize, SinkFull> {
161    let mut encoded = [0; N];
162    let mut tail = &mut encoded[..];
163    stage(&mut tail).expect("each reference fits its bounded staging buffer");
164    let used = N - tail.len();
165    if let Some(available) = sink.remaining_capacity()
166        && available < used {
167            return Err(SinkFull {
168                needed: used,
169                available,
170            });
171        }
172    sink.write(&encoded[..used])?;
173    Ok(used)
174}