Skip to main content

bacnet_encoding/primitives/
scalar.rs

1//! Bounded scalar encoding, shared by growable-buffer and sink entrypoints.
2
3use bacnet_types::primitives::{Date, ObjectIdentifier, Time};
4#[cfg(feature = "alloc")]
5use bytes::BytesMut;
6
7use crate::sink::{EncodeSink, SinkFull};
8use crate::tags::{self, TagClass, app_tag};
9
10/// Encode an unsigned integer using the minimum number of big-endian octets.
11#[cfg(feature = "alloc")]
12pub fn encode_unsigned(buf: &mut BytesMut, value: u64) {
13    encode_unsigned_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
14}
15
16/// Encode a signed integer using minimum octets, two's complement, big-endian.
17#[cfg(feature = "alloc")]
18pub fn encode_signed(buf: &mut BytesMut, value: i32) {
19    encode_signed_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
20}
21
22/// Encode an IEEE-754 single-precision float (big-endian, 4 bytes).
23#[cfg(feature = "alloc")]
24pub fn encode_real(buf: &mut BytesMut, value: f32) {
25    encode_real_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
26}
27
28/// Encode an IEEE-754 double-precision float (big-endian, 8 bytes).
29#[cfg(feature = "alloc")]
30pub fn encode_double(buf: &mut BytesMut, value: f64) {
31    encode_double_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
32}
33
34/// Encode an application-tagged Null.
35#[cfg(feature = "alloc")]
36pub fn encode_app_null(buf: &mut BytesMut) {
37    encode_app_null_to_sink(buf).expect("the BytesMut sink cannot return SinkFull");
38}
39
40/// Encode an application-tagged Boolean.
41///
42/// The value is encoded in the tag's L/V/T bits with no content octets.
43#[cfg(feature = "alloc")]
44pub fn encode_app_boolean(buf: &mut BytesMut, value: bool) {
45    encode_app_boolean_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
46}
47
48/// Encode an application-tagged Unsigned.
49#[cfg(feature = "alloc")]
50pub fn encode_app_unsigned(buf: &mut BytesMut, value: u64) {
51    encode_app_unsigned_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
52}
53
54/// Encode an application-tagged Signed.
55#[cfg(feature = "alloc")]
56pub fn encode_app_signed(buf: &mut BytesMut, value: i32) {
57    encode_app_signed_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
58}
59
60/// Encode an application-tagged Real (f32).
61#[cfg(feature = "alloc")]
62pub fn encode_app_real(buf: &mut BytesMut, value: f32) {
63    encode_app_real_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
64}
65
66/// Encode an application-tagged Double (f64).
67#[cfg(feature = "alloc")]
68pub fn encode_app_double(buf: &mut BytesMut, value: f64) {
69    encode_app_double_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
70}
71
72/// Encode an application-tagged Enumerated.
73#[cfg(feature = "alloc")]
74pub fn encode_app_enumerated(buf: &mut BytesMut, value: u32) {
75    encode_app_enumerated_to_sink(buf, value).expect("the BytesMut sink cannot return SinkFull");
76}
77
78/// Encode an application-tagged Date.
79#[cfg(feature = "alloc")]
80pub fn encode_app_date(buf: &mut BytesMut, date: &Date) {
81    encode_app_date_to_sink(buf, date).expect("the BytesMut sink cannot return SinkFull");
82}
83
84/// Encode an application-tagged Time.
85#[cfg(feature = "alloc")]
86pub fn encode_app_time(buf: &mut BytesMut, time: &Time) {
87    encode_app_time_to_sink(buf, time).expect("the BytesMut sink cannot return SinkFull");
88}
89
90/// Encode an application-tagged ObjectIdentifier.
91#[cfg(feature = "alloc")]
92pub fn encode_app_object_id(buf: &mut BytesMut, oid: &ObjectIdentifier) {
93    encode_app_object_id_to_sink(buf, oid).expect("the BytesMut sink cannot return SinkFull");
94}
95
96/// Encode a context-tagged Unsigned.
97#[cfg(feature = "alloc")]
98pub fn encode_ctx_unsigned(buf: &mut BytesMut, tag: u8, value: u64) {
99    encode_ctx_unsigned_to_sink(buf, tag, value).expect("the BytesMut sink cannot return SinkFull");
100}
101
102/// Encode a context-tagged Signed.
103#[cfg(feature = "alloc")]
104pub fn encode_ctx_signed(buf: &mut BytesMut, tag: u8, value: i32) {
105    encode_ctx_signed_to_sink(buf, tag, value).expect("the BytesMut sink cannot return SinkFull");
106}
107
108/// Encode a context-tagged Real (f32).
109#[cfg(feature = "alloc")]
110pub fn encode_ctx_real(buf: &mut BytesMut, tag: u8, value: f32) {
111    encode_ctx_real_to_sink(buf, tag, value).expect("the BytesMut sink cannot return SinkFull");
112}
113
114/// Encode a context-tagged Double (f64).
115#[cfg(feature = "alloc")]
116pub fn encode_ctx_double(buf: &mut BytesMut, tag: u8, value: f64) {
117    encode_ctx_double_to_sink(buf, tag, value).expect("the BytesMut sink cannot return SinkFull");
118}
119
120/// Encode a context-tagged Enumerated.
121#[cfg(feature = "alloc")]
122pub fn encode_ctx_enumerated(buf: &mut BytesMut, tag: u8, value: u32) {
123    encode_ctx_enumerated_to_sink(buf, tag, value)
124        .expect("the BytesMut sink cannot return SinkFull");
125}
126
127/// Encode a context-tagged Boolean.
128///
129/// Context-tagged booleans use a 1-byte content octet (unlike application-tagged).
130#[cfg(feature = "alloc")]
131pub fn encode_ctx_boolean(buf: &mut BytesMut, tag: u8, value: bool) {
132    encode_ctx_boolean_to_sink(buf, tag, value).expect("the BytesMut sink cannot return SinkFull");
133}
134
135/// Encode a context-tagged ObjectIdentifier.
136#[cfg(feature = "alloc")]
137pub fn encode_ctx_object_id(buf: &mut BytesMut, tag: u8, oid: &ObjectIdentifier) {
138    encode_ctx_object_id_to_sink(buf, tag, oid).expect("the BytesMut sink cannot return SinkFull");
139}
140
141/// Encode a context-tagged Date.
142#[cfg(feature = "alloc")]
143pub fn encode_ctx_date(buf: &mut BytesMut, tag: u8, date: &Date) {
144    encode_ctx_date_to_sink(buf, tag, date).expect("the BytesMut sink cannot return SinkFull");
145}
146
147/// Return the number of bytes needed to encode an unsigned value.
148pub fn unsigned_len(value: u64) -> u32 {
149    if value <= 0xFF {
150        1
151    } else if value <= 0xFFFF {
152        2
153    } else if value <= 0xFF_FFFF {
154        3
155    } else if value <= 0xFFFF_FFFF {
156        4
157    } else if value <= 0xFF_FFFF_FFFF {
158        5
159    } else if value <= 0xFFFF_FFFF_FFFF {
160        6
161    } else if value <= 0xFF_FFFF_FFFF_FFFF {
162        7
163    } else {
164        8
165    }
166}
167
168/// Return the number of bytes needed to encode a signed value.
169pub fn signed_len(value: i32) -> u32 {
170    if (-128..=127).contains(&value) {
171        1
172    } else if (-32768..=32767).contains(&value) {
173        2
174    } else if (-8_388_608..=8_388_607).contains(&value) {
175        3
176    } else {
177        4
178    }
179}
180
181/// Append an unsigned integer in the minimum big-endian width without a tag.
182///
183/// Uses one atomic write, with the [scalar sink contract](super).
184pub fn encode_unsigned_to_sink<S: EncodeSink + ?Sized>(
185    sink: &mut S,
186    value: u64,
187) -> Result<usize, SinkFull> {
188    write_scalar(
189        sink,
190        &value.to_be_bytes()[8 - unsigned_len(value) as usize..],
191    )
192}
193
194/// Append a signed integer in the minimum two's-complement width without a tag.
195///
196/// Uses one atomic write, with the [scalar sink contract](super).
197pub fn encode_signed_to_sink<S: EncodeSink + ?Sized>(
198    sink: &mut S,
199    value: i32,
200) -> Result<usize, SinkFull> {
201    write_scalar(sink, &value.to_be_bytes()[4 - signed_len(value) as usize..])
202}
203
204/// Append the four IEEE-754 octets of a Real in big-endian order without a tag.
205///
206/// Uses one atomic write, with the [scalar sink contract](super).
207pub fn encode_real_to_sink<S: EncodeSink + ?Sized>(
208    sink: &mut S,
209    value: f32,
210) -> Result<usize, SinkFull> {
211    write_scalar(sink, &value.to_bits().to_be_bytes())
212}
213
214/// Append the eight IEEE-754 octets of a Double in big-endian order without a tag.
215///
216/// Uses one atomic write, with the [scalar sink contract](super).
217pub fn encode_double_to_sink<S: EncodeSink + ?Sized>(
218    sink: &mut S,
219    value: f64,
220) -> Result<usize, SinkFull> {
221    write_scalar(sink, &value.to_bits().to_be_bytes())
222}
223
224/// Append an application-tagged Null with no contents.
225///
226/// Stages the whole value for one write, with the [scalar sink contract](super).
227pub fn encode_app_null_to_sink<S: EncodeSink + ?Sized>(sink: &mut S) -> Result<usize, SinkFull> {
228    write_tagged(sink, app_tag::NULL, TagClass::Application, 0, &[])
229}
230
231/// Append an application-tagged Boolean with its value in the header and no contents.
232///
233/// Stages the whole value for one write, with the [scalar sink contract](super).
234pub fn encode_app_boolean_to_sink<S: EncodeSink + ?Sized>(
235    sink: &mut S,
236    value: bool,
237) -> Result<usize, SinkFull> {
238    write_tagged(
239        sink,
240        app_tag::BOOLEAN,
241        TagClass::Application,
242        u32::from(value),
243        &[],
244    )
245}
246
247/// Append an application-tagged Unsigned with its minimum-width contents.
248///
249/// Stages the whole value for one write, with the [scalar sink contract](super).
250pub fn encode_app_unsigned_to_sink<S: EncodeSink + ?Sized>(
251    sink: &mut S,
252    value: u64,
253) -> Result<usize, SinkFull> {
254    write_tagged(
255        sink,
256        app_tag::UNSIGNED,
257        TagClass::Application,
258        unsigned_len(value),
259        &value.to_be_bytes()[8 - unsigned_len(value) as usize..],
260    )
261}
262
263/// Append an application-tagged Signed with its minimum-width contents.
264///
265/// Stages the whole value for one write, with the [scalar sink contract](super).
266pub fn encode_app_signed_to_sink<S: EncodeSink + ?Sized>(
267    sink: &mut S,
268    value: i32,
269) -> Result<usize, SinkFull> {
270    write_tagged(
271        sink,
272        app_tag::SIGNED,
273        TagClass::Application,
274        signed_len(value),
275        &value.to_be_bytes()[4 - signed_len(value) as usize..],
276    )
277}
278
279/// Append an application-tagged Real preserving every payload bit.
280///
281/// Stages the whole value for one write, with the [scalar sink contract](super).
282pub fn encode_app_real_to_sink<S: EncodeSink + ?Sized>(
283    sink: &mut S,
284    value: f32,
285) -> Result<usize, SinkFull> {
286    write_tagged(
287        sink,
288        app_tag::REAL,
289        TagClass::Application,
290        4,
291        &value.to_bits().to_be_bytes(),
292    )
293}
294
295/// Append an application-tagged Double preserving every payload bit.
296///
297/// Stages the whole value for one write, with the [scalar sink contract](super).
298pub fn encode_app_double_to_sink<S: EncodeSink + ?Sized>(
299    sink: &mut S,
300    value: f64,
301) -> Result<usize, SinkFull> {
302    write_tagged(
303        sink,
304        app_tag::DOUBLE,
305        TagClass::Application,
306        8,
307        &value.to_bits().to_be_bytes(),
308    )
309}
310
311/// Append an application-tagged Enumerated with its minimum-width contents.
312///
313/// Stages the whole value for one write, with the [scalar sink contract](super).
314pub fn encode_app_enumerated_to_sink<S: EncodeSink + ?Sized>(
315    sink: &mut S,
316    value: u32,
317) -> Result<usize, SinkFull> {
318    write_tagged(
319        sink,
320        app_tag::ENUMERATED,
321        TagClass::Application,
322        unsigned_len(u64::from(value)),
323        &value.to_be_bytes()[4 - unsigned_len(u64::from(value)) as usize..],
324    )
325}
326
327/// Append an application-tagged Date preserving its four fields without validation.
328///
329/// Stages the whole value for one write, with the [scalar sink contract](super).
330pub fn encode_app_date_to_sink<S: EncodeSink + ?Sized>(
331    sink: &mut S,
332    date: &Date,
333) -> Result<usize, SinkFull> {
334    write_tagged(
335        sink,
336        app_tag::DATE,
337        TagClass::Application,
338        4,
339        &date.encode(),
340    )
341}
342
343/// Append an application-tagged Time preserving its four fields without validation.
344///
345/// Stages the whole value for one write, with the [scalar sink contract](super).
346pub fn encode_app_time_to_sink<S: EncodeSink + ?Sized>(
347    sink: &mut S,
348    time: &Time,
349) -> Result<usize, SinkFull> {
350    write_tagged(
351        sink,
352        app_tag::TIME,
353        TagClass::Application,
354        4,
355        &time.encode(),
356    )
357}
358
359/// Append an application-tagged ObjectIdentifier preserving its 10-bit type and 22-bit instance.
360///
361/// Stages the whole value for one write, with the [scalar sink contract](super).
362pub fn encode_app_object_id_to_sink<S: EncodeSink + ?Sized>(
363    sink: &mut S,
364    oid: &ObjectIdentifier,
365) -> Result<usize, SinkFull> {
366    write_tagged(
367        sink,
368        app_tag::OBJECT_IDENTIFIER,
369        TagClass::Application,
370        4,
371        &oid.encode(),
372    )
373}
374
375/// Append a context-tagged Unsigned with its minimum-width contents.
376///
377/// Stages the whole value for one write, with the [scalar sink contract](super).
378/// The tag number keeps its existing unvalidated input behavior.
379///
380/// ```
381/// use bacnet_encoding::primitives::encode_ctx_unsigned_to_sink;
382///
383/// let mut storage = [0xA5; 4];
384/// let mut tail = &mut storage[..];
385/// assert_eq!(encode_ctx_unsigned_to_sink(&mut tail, 15, 256)?, 4);
386/// assert!(tail.is_empty());
387/// assert_eq!(storage, [0xFA, 0x0F, 0x01, 0x00]);
388/// # Ok::<(), bacnet_encoding::sink::SinkFull>(())
389/// ```
390pub fn encode_ctx_unsigned_to_sink<S: EncodeSink + ?Sized>(
391    sink: &mut S,
392    tag: u8,
393    value: u64,
394) -> Result<usize, SinkFull> {
395    write_tagged(
396        sink,
397        tag,
398        TagClass::Context,
399        unsigned_len(value),
400        &value.to_be_bytes()[8 - unsigned_len(value) as usize..],
401    )
402}
403
404/// Append a context-tagged Signed with its minimum-width contents.
405///
406/// Stages the whole value for one write, with the [scalar sink contract](super).
407/// The tag number keeps its existing unvalidated input behavior.
408pub fn encode_ctx_signed_to_sink<S: EncodeSink + ?Sized>(
409    sink: &mut S,
410    tag: u8,
411    value: i32,
412) -> Result<usize, SinkFull> {
413    write_tagged(
414        sink,
415        tag,
416        TagClass::Context,
417        signed_len(value),
418        &value.to_be_bytes()[4 - signed_len(value) as usize..],
419    )
420}
421
422/// Append a context-tagged Real preserving every payload bit.
423///
424/// Stages the whole value for one write, with the [scalar sink contract](super).
425/// The tag number keeps its existing unvalidated input behavior.
426pub fn encode_ctx_real_to_sink<S: EncodeSink + ?Sized>(
427    sink: &mut S,
428    tag: u8,
429    value: f32,
430) -> Result<usize, SinkFull> {
431    write_tagged(
432        sink,
433        tag,
434        TagClass::Context,
435        4,
436        &value.to_bits().to_be_bytes(),
437    )
438}
439
440/// Append a context-tagged Double preserving every payload bit.
441///
442/// Stages the whole value for one write, with the [scalar sink contract](super).
443/// The tag number keeps its existing unvalidated input behavior.
444pub fn encode_ctx_double_to_sink<S: EncodeSink + ?Sized>(
445    sink: &mut S,
446    tag: u8,
447    value: f64,
448) -> Result<usize, SinkFull> {
449    write_tagged(
450        sink,
451        tag,
452        TagClass::Context,
453        8,
454        &value.to_bits().to_be_bytes(),
455    )
456}
457
458/// Append a context-tagged Enumerated with its minimum-width contents.
459///
460/// Stages the whole value for one write, with the [scalar sink contract](super).
461/// The tag number keeps its existing unvalidated input behavior.
462pub fn encode_ctx_enumerated_to_sink<S: EncodeSink + ?Sized>(
463    sink: &mut S,
464    tag: u8,
465    value: u32,
466) -> Result<usize, SinkFull> {
467    write_tagged(
468        sink,
469        tag,
470        TagClass::Context,
471        unsigned_len(u64::from(value)),
472        &value.to_be_bytes()[4 - unsigned_len(u64::from(value)) as usize..],
473    )
474}
475
476/// Append a context-tagged Boolean with one content octet.
477///
478/// Stages the whole value for one write, with the [scalar sink contract](super).
479/// The tag number keeps its existing unvalidated input behavior.
480pub fn encode_ctx_boolean_to_sink<S: EncodeSink + ?Sized>(
481    sink: &mut S,
482    tag: u8,
483    value: bool,
484) -> Result<usize, SinkFull> {
485    write_tagged(sink, tag, TagClass::Context, 1, &[u8::from(value)])
486}
487
488/// Append a context-tagged ObjectIdentifier preserving its 10-bit type and 22-bit instance.
489///
490/// Stages the whole value for one write, with the [scalar sink contract](super).
491/// The tag number keeps its existing unvalidated input behavior.
492pub fn encode_ctx_object_id_to_sink<S: EncodeSink + ?Sized>(
493    sink: &mut S,
494    tag: u8,
495    oid: &ObjectIdentifier,
496) -> Result<usize, SinkFull> {
497    write_tagged(sink, tag, TagClass::Context, 4, &oid.encode())
498}
499
500/// Append a context-tagged Date preserving its four fields without validation.
501///
502/// Stages the whole value for one write, with the [scalar sink contract](super).
503/// The tag number keeps its existing unvalidated input behavior.
504pub fn encode_ctx_date_to_sink<S: EncodeSink + ?Sized>(
505    sink: &mut S,
506    tag: u8,
507    date: &Date,
508) -> Result<usize, SinkFull> {
509    write_tagged(sink, tag, TagClass::Context, 4, &date.encode())
510}
511
512// Scalar contents are at most eight bytes. Their header needs at most
513// three: an extended context tag and an extended one-byte content length.
514fn write_tagged<S: EncodeSink + ?Sized>(
515    sink: &mut S,
516    tag: u8,
517    class: TagClass,
518    length: u32,
519    contents: &[u8],
520) -> Result<usize, SinkFull> {
521    let mut encoded = [0; 11];
522    let header = tags::encode_tag_to_sink(&mut &mut encoded[..], tag, class, length)
523        .expect("a scalar header fits the eleven-byte staging buffer");
524    let used = header + contents.len();
525    encoded[header..used].copy_from_slice(contents);
526    write_scalar(sink, &encoded[..used])
527}
528
529fn write_scalar<S: EncodeSink + ?Sized>(sink: &mut S, encoded: &[u8]) -> Result<usize, SinkFull> {
530    if let Some(available) = sink.remaining_capacity()
531        && available < encoded.len()
532    {
533        return Err(SinkFull {
534            needed: encoded.len(),
535            available,
536        });
537    }
538    sink.write(encoded)?;
539    Ok(encoded.len())
540}