Skip to main content

bacnet_encoding/constructed/
scale.rs

1//! Clause 21 codecs for an Accumulator's Scale and Prescale (Clause 12.61,
2//! Table 12-79).
3//!
4//! - `BACnetScale` is a CHOICE of two primitive context tags: `[0]` a REAL
5//!   for a float scale, `[1]` an INTEGER for a power-of-ten scale. A value is
6//!   that one tag, with no frame around it.
7//! - `BACnetPrescale` is a SEQUENCE of two required primitive context tags:
8//!   `[0]` the multiplier and `[1]` the modulo divide, both Unsigned.
9
10use bacnet_types::constructed::{BACnetPrescale, BACnetScale};
11use bacnet_types::error::Error;
12use bytes::BytesMut;
13
14use super::tagged::{decode_ctx_primitive, decode_ctx_real, decode_ctx_unsigned, misplaced_tag};
15use crate::{primitives, tags};
16
17const SCALE: &str = "BACnetScale";
18const PRESCALE: &str = "BACnetPrescale";
19
20/// Encode one `BACnetScale` CHOICE, appending to `buf`.
21pub fn encode_scale(buf: &mut BytesMut, scale: &BACnetScale) {
22    match *scale {
23        BACnetScale::FloatScale(factor) => primitives::encode_ctx_real(buf, 0, factor),
24        BACnetScale::IntegerScale(power) => primitives::encode_ctx_signed(buf, 1, power),
25    }
26}
27
28/// Decode one `BACnetScale` CHOICE at `offset`.
29///
30/// Returns the scale and the offset just past it; a caller holding exactly
31/// one value must check that offset reaches the end of its data. No tag at
32/// all is [`DecodingKind::Missing`], and any tag but a primitive context
33/// `[0]` or `[1]` the kind [`misplaced_tag`] gives (InvalidTag, or Missing for
34/// a closing tag that ends the enclosing frame). A REAL that isn't four
35/// octets or an INTEGER of no octets or more than four is
36/// [`DecodingKind::InvalidEncoding`], and truncated contents
37/// [`Error::BufferTooShort`].
38///
39/// [`DecodingKind::Missing`]: bacnet_types::error::DecodingKind::Missing
40/// [`DecodingKind::InvalidEncoding`]: bacnet_types::error::DecodingKind::InvalidEncoding
41pub fn decode_scale(data: &[u8], offset: usize) -> Result<(BACnetScale, usize), Error> {
42    let (tag, _) = tags::decode_tag(data, offset)?;
43    if tag.is_context(0) {
44        let (factor, end) = decode_ctx_real(data, offset, 0, SCALE)?;
45        return Ok((BACnetScale::FloatScale(factor), end));
46    }
47    if tag.is_context(1) {
48        let (octets, end) = decode_ctx_primitive(data, offset, 1, SCALE)?;
49        let power = primitives::decode_signed(octets).map_err(|_| {
50            Error::decoding(
51                offset,
52                format!("{SCALE}: integer-scale [1] needs 1 to 4 octets"),
53            )
54        })?;
55        return Ok((BACnetScale::IntegerScale(power), end));
56    }
57    Err(misplaced_tag(
58        data,
59        &tag,
60        None,
61        offset,
62        format!("{SCALE}: expected float-scale [0] or integer-scale [1]"),
63    ))
64}
65
66/// Encode one `BACnetPrescale` SEQUENCE, appending to `buf`.
67pub fn encode_prescale(buf: &mut BytesMut, prescale: &BACnetPrescale) {
68    primitives::encode_ctx_unsigned(buf, 0, u64::from(prescale.multiplier));
69    primitives::encode_ctx_unsigned(buf, 1, u64::from(prescale.modulo_divide));
70}
71
72/// Decode one `BACnetPrescale` SEQUENCE at `offset`; returns it and the
73/// offset past its `[1]` member. A member left out (the data ends, or `[1]`
74/// comes first) is [`DecodingKind::Missing`], another tag where a member is
75/// due [`DecodingKind::InvalidTag`], an Unsigned of no octets
76/// [`DecodingKind::InvalidEncoding`], and a value past unsigned32, the width
77/// the object keeps, [`DecodingKind::OutOfRange`].
78///
79/// [`DecodingKind::Missing`]: bacnet_types::error::DecodingKind::Missing
80/// [`DecodingKind::InvalidTag`]: bacnet_types::error::DecodingKind::InvalidTag
81/// [`DecodingKind::InvalidEncoding`]: bacnet_types::error::DecodingKind::InvalidEncoding
82/// [`DecodingKind::OutOfRange`]: bacnet_types::error::DecodingKind::OutOfRange
83pub fn decode_prescale(data: &[u8], offset: usize) -> Result<(BACnetPrescale, usize), Error> {
84    let (multiplier, pos) = decode_ctx_unsigned::<u32>(data, offset, 0, PRESCALE)?;
85    let (modulo_divide, end) = decode_ctx_unsigned::<u32>(data, pos, 1, PRESCALE)?;
86    Ok((
87        BACnetPrescale {
88            multiplier,
89            modulo_divide,
90        },
91        end,
92    ))
93}