Skip to main content

rustbac_core/services/
value_codec.rs

1use crate::encoding::{
2    primitives::{decode_signed, decode_unsigned, encode_signed, encode_unsigned},
3    reader::Reader,
4    tag::{AppTag, Tag},
5    writer::Writer,
6};
7use crate::types::{BitString, DataValue, Date, ObjectId, Time};
8use crate::{DecodeError, EncodeError};
9
10#[cfg(feature = "alloc")]
11extern crate alloc;
12#[cfg(feature = "alloc")]
13use alloc::vec::Vec;
14
15fn u32_len(len: usize) -> Result<u32, EncodeError> {
16    u32::try_from(len).map_err(|_| EncodeError::ValueOutOfRange)
17}
18
19pub fn encode_application_data_value(
20    w: &mut Writer<'_>,
21    value: &DataValue<'_>,
22) -> Result<(), EncodeError> {
23    match value {
24        DataValue::Null => Tag::Application {
25            tag: AppTag::Null,
26            len: 0,
27        }
28        .encode(w),
29        DataValue::Boolean(v) => Tag::Application {
30            tag: AppTag::Boolean,
31            len: if *v { 1 } else { 0 },
32        }
33        .encode(w),
34        DataValue::Unsigned(v) => encode_app_unsigned_like(w, AppTag::UnsignedInt, *v),
35        DataValue::Signed(v) => encode_app_signed_like(w, AppTag::SignedInt, *v),
36        DataValue::Real(v) => {
37            Tag::Application {
38                tag: AppTag::Real,
39                len: 4,
40            }
41            .encode(w)?;
42            w.write_all(&v.to_bits().to_be_bytes())
43        }
44        DataValue::Double(v) => {
45            Tag::Application {
46                tag: AppTag::Double,
47                len: 8,
48            }
49            .encode(w)?;
50            w.write_all(&v.to_bits().to_be_bytes())
51        }
52        DataValue::OctetString(v) => {
53            Tag::Application {
54                tag: AppTag::OctetString,
55                len: u32_len(v.len())?,
56            }
57            .encode(w)?;
58            w.write_all(v)
59        }
60        DataValue::CharacterString(v) => {
61            let bytes = v.as_bytes();
62            Tag::Application {
63                tag: AppTag::CharacterString,
64                len: u32_len(bytes.len().saturating_add(1))?,
65            }
66            .encode(w)?;
67            // BACnet character set 0 = UTF-8/ANSI X3.4 compatible in this baseline.
68            w.write_u8(0)?;
69            w.write_all(bytes)
70        }
71        DataValue::BitString(v) => {
72            if v.unused_bits > 7 {
73                return Err(EncodeError::ValueOutOfRange);
74            }
75            Tag::Application {
76                tag: AppTag::BitString,
77                len: u32_len(v.data.len().saturating_add(1))?,
78            }
79            .encode(w)?;
80            w.write_u8(v.unused_bits)?;
81            w.write_all(v.data)
82        }
83        DataValue::Enumerated(v) => encode_app_unsigned_like(w, AppTag::Enumerated, *v),
84        DataValue::Date(v) => {
85            Tag::Application {
86                tag: AppTag::Date,
87                len: 4,
88            }
89            .encode(w)?;
90            w.write_all(&[v.year_since_1900, v.month, v.day, v.weekday])
91        }
92        DataValue::Time(v) => {
93            Tag::Application {
94                tag: AppTag::Time,
95                len: 4,
96            }
97            .encode(w)?;
98            w.write_all(&[v.hour, v.minute, v.second, v.hundredths])
99        }
100        DataValue::ObjectId(v) => {
101            Tag::Application {
102                tag: AppTag::ObjectId,
103                len: 4,
104            }
105            .encode(w)?;
106            w.write_all(&v.raw().to_be_bytes())
107        }
108        #[cfg(feature = "alloc")]
109        DataValue::Constructed { tag_num, values } => {
110            Tag::Opening { tag_num: *tag_num }.encode(w)?;
111            for child in values {
112                encode_application_data_value(w, child)?;
113            }
114            Tag::Closing { tag_num: *tag_num }.encode(w)
115        }
116    }
117}
118
119pub fn decode_application_data_value<'a>(r: &mut Reader<'a>) -> Result<DataValue<'a>, DecodeError> {
120    let tag = Tag::decode(r)?;
121    decode_application_data_value_from_tag(r, tag)
122}
123
124pub fn decode_application_data_value_from_tag<'a>(
125    r: &mut Reader<'a>,
126    tag: Tag,
127) -> Result<DataValue<'a>, DecodeError> {
128    match tag {
129        Tag::Application {
130            tag: AppTag::Null, ..
131        } => Ok(DataValue::Null),
132        Tag::Application {
133            tag: AppTag::Boolean,
134            len,
135        } => Ok(DataValue::Boolean(len != 0)),
136        Tag::Application {
137            tag: AppTag::UnsignedInt,
138            len,
139        } => Ok(DataValue::Unsigned(decode_unsigned(r, len as usize)?)),
140        Tag::Application {
141            tag: AppTag::SignedInt,
142            len,
143        } => Ok(DataValue::Signed(decode_signed(r, len as usize)?)),
144        Tag::Application {
145            tag: AppTag::Real,
146            len: 4,
147        } => {
148            let b = r.read_exact(4)?;
149            Ok(DataValue::Real(f32::from_bits(u32::from_be_bytes([
150                b[0], b[1], b[2], b[3],
151            ]))))
152        }
153        Tag::Application {
154            tag: AppTag::Double,
155            len: 8,
156        } => {
157            let b = r.read_exact(8)?;
158            Ok(DataValue::Double(f64::from_bits(u64::from_be_bytes([
159                b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
160            ]))))
161        }
162        Tag::Application {
163            tag: AppTag::OctetString,
164            len,
165        } => Ok(DataValue::OctetString(r.read_exact(len as usize)?)),
166        Tag::Application {
167            tag: AppTag::CharacterString,
168            len,
169        } => {
170            if len == 0 {
171                return Err(DecodeError::InvalidLength);
172            }
173            let raw = r.read_exact(len as usize)?;
174            let charset = raw[0];
175            if charset != 0 {
176                return Err(DecodeError::Unsupported);
177            }
178            let s = core::str::from_utf8(&raw[1..]).map_err(|_| DecodeError::InvalidValue)?;
179            Ok(DataValue::CharacterString(s))
180        }
181        Tag::Application {
182            tag: AppTag::BitString,
183            len,
184        } => {
185            if len == 0 {
186                return Err(DecodeError::InvalidLength);
187            }
188            let raw = r.read_exact(len as usize)?;
189            if raw[0] > 7 {
190                return Err(DecodeError::InvalidValue);
191            }
192            Ok(DataValue::BitString(BitString {
193                unused_bits: raw[0],
194                data: &raw[1..],
195            }))
196        }
197        Tag::Application {
198            tag: AppTag::Enumerated,
199            len,
200        } => Ok(DataValue::Enumerated(decode_unsigned(r, len as usize)?)),
201        Tag::Application {
202            tag: AppTag::Date,
203            len: 4,
204        } => {
205            let b = r.read_exact(4)?;
206            Ok(DataValue::Date(Date {
207                year_since_1900: b[0],
208                month: b[1],
209                day: b[2],
210                weekday: b[3],
211            }))
212        }
213        Tag::Application {
214            tag: AppTag::Time,
215            len: 4,
216        } => {
217            let b = r.read_exact(4)?;
218            Ok(DataValue::Time(Time {
219                hour: b[0],
220                minute: b[1],
221                second: b[2],
222                hundredths: b[3],
223            }))
224        }
225        Tag::Application {
226            tag: AppTag::ObjectId,
227            len: 4,
228        } => {
229            let b = r.read_exact(4)?;
230            Ok(DataValue::ObjectId(ObjectId::from_raw(u32::from_be_bytes(
231                [b[0], b[1], b[2], b[3]],
232            ))))
233        }
234        #[cfg(feature = "alloc")]
235        Tag::Opening { tag_num } => {
236            let mut children = Vec::new();
237            loop {
238                let child_tag = Tag::decode(r)?;
239                if child_tag == (Tag::Closing { tag_num }) {
240                    break;
241                }
242                children.push(decode_application_data_value_from_tag(r, child_tag)?);
243            }
244            Ok(DataValue::Constructed {
245                tag_num,
246                values: children,
247            })
248        }
249        _ => Err(DecodeError::Unsupported),
250    }
251}
252
253fn encode_app_unsigned_like(
254    w: &mut Writer<'_>,
255    tag: AppTag,
256    value: u32,
257) -> Result<(), EncodeError> {
258    let mut scratch = [0u8; 4];
259    let mut tw = Writer::new(&mut scratch);
260    let len = encode_unsigned(&mut tw, value)? as u32;
261    Tag::Application { tag, len }.encode(w)?;
262    w.write_all(&scratch[..len as usize])
263}
264
265fn encode_app_signed_like(w: &mut Writer<'_>, tag: AppTag, value: i32) -> Result<(), EncodeError> {
266    let mut scratch = [0u8; 4];
267    let mut tw = Writer::new(&mut scratch);
268    let len = encode_signed(&mut tw, value)? as u32;
269    Tag::Application { tag, len }.encode(w)?;
270    w.write_all(&scratch[..len as usize])
271}
272
273#[cfg(test)]
274#[cfg(feature = "alloc")]
275mod tests {
276    use super::{decode_application_data_value, encode_application_data_value};
277    use crate::encoding::{reader::Reader, writer::Writer};
278    use crate::types::{BitString, DataValue, Date, ObjectId, ObjectType, Time};
279
280    #[test]
281    fn value_codec_roundtrip_supported_types() {
282        let values = [
283            DataValue::Null,
284            DataValue::Boolean(true),
285            DataValue::Unsigned(123),
286            DataValue::Signed(-123),
287            DataValue::Real(12.5),
288            DataValue::Double(42.25),
289            DataValue::OctetString(&[1, 2, 3]),
290            DataValue::CharacterString("hello"),
291            DataValue::BitString(BitString::new(1, &[0b1010_0000])),
292            DataValue::Enumerated(9),
293            DataValue::Date(Date {
294                year_since_1900: 124,
295                month: 2,
296                day: 3,
297                weekday: 6,
298            }),
299            DataValue::Time(Time {
300                hour: 1,
301                minute: 2,
302                second: 3,
303                hundredths: 4,
304            }),
305            DataValue::ObjectId(ObjectId::new(ObjectType::Device, 1)),
306        ];
307
308        for v in values {
309            let mut buf = [0u8; 64];
310            let mut w = Writer::new(&mut buf);
311            encode_application_data_value(&mut w, &v).unwrap();
312            let mut r = Reader::new(w.as_written());
313            let got = decode_application_data_value(&mut r).unwrap();
314            assert_eq!(got, v);
315        }
316    }
317
318    #[test]
319    fn value_codec_roundtrip_constructed() {
320        use alloc::vec;
321
322        let value = DataValue::Constructed {
323            tag_num: 2,
324            values: vec![
325                DataValue::Unsigned(42),
326                DataValue::CharacterString("test"),
327                DataValue::Constructed {
328                    tag_num: 0,
329                    values: vec![DataValue::Boolean(true), DataValue::Real(3.14)],
330                },
331            ],
332        };
333
334        let mut buf = [0u8; 128];
335        let mut w = Writer::new(&mut buf);
336        encode_application_data_value(&mut w, &value).unwrap();
337        let mut r = Reader::new(w.as_written());
338        let got = decode_application_data_value(&mut r).unwrap();
339        assert_eq!(got, value);
340    }
341}