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}