bacnet_encoding/constructed/
log_record.rs1use super::log_fields::{
14 check_bit_string, decode_any_value, decode_failure, decode_integer, decode_log_status,
15 decode_timestamp, encode_any_value, encode_ctx_integer, encode_failure, encode_log_status,
16 encode_timestamp,
17};
18use super::tagged::{contents, decode_ctx_constructed, expect_end};
19use crate::{primitives, tags};
20use bacnet_types::bitstring::status_flags_from_bacnet;
21use bacnet_types::constructed::{BACnetLogRecord, LogDatum};
22use bacnet_types::error::Error;
23use bacnet_types::primitives::StatusFlags;
24use bytes::BytesMut;
25
26const RECORD: &str = "BACnetLogRecord";
27const TIMESTAMP: &str = "BACnetLogRecord timestamp";
28const DATUM: &str = "BACnetLogRecord log-datum";
29const FAILURE_FIELD: &str = "BACnetLogRecord failure";
30
31const LOG_STATUS: u8 = 0;
32const BOOLEAN: u8 = 1;
33const REAL: u8 = 2;
34const ENUMERATED: u8 = 3;
35const UNSIGNED: u8 = 4;
36const INTEGER: u8 = 5;
37const BIT_STRING: u8 = 6;
38const NULL: u8 = 7;
39const FAILURE: u8 = 8;
40const TIME_CHANGE: u8 = 9;
41const ANY: u8 = 10;
42
43const STATUS_FLAGS: u8 = 2;
45
46pub fn encode_log_record(record: &BACnetLogRecord, buf: &mut BytesMut) -> Result<(), Error> {
52 let mut out = BytesMut::new();
53 encode_timestamp(&mut out, &record.date, &record.time);
54 tags::encode_opening_tag(&mut out, 1);
55 match &record.log_datum {
56 LogDatum::LogStatus(status) => encode_log_status(&mut out, LOG_STATUS, *status),
57 LogDatum::BooleanValue(value) => primitives::encode_ctx_boolean(&mut out, BOOLEAN, *value),
58 LogDatum::RealValue(value) => primitives::encode_ctx_real(&mut out, REAL, *value),
59 LogDatum::EnumValue(value) => primitives::encode_ctx_unsigned(&mut out, ENUMERATED, *value),
61 LogDatum::UnsignedValue(value) => {
62 primitives::encode_ctx_unsigned(&mut out, UNSIGNED, *value)
63 }
64 LogDatum::SignedValue(value) => encode_ctx_integer(&mut out, INTEGER, *value),
65 LogDatum::BitstringValue { unused_bits, data } => {
66 check_bit_string(*unused_bits, data, "bitstring-value")?;
67 primitives::encode_ctx_bit_string(&mut out, BIT_STRING, *unused_bits, data);
68 }
69 LogDatum::NullValue => tags::encode_tag(&mut out, NULL, tags::TagClass::Context, 0),
70 LogDatum::Failure {
71 error_class,
72 error_code,
73 } => encode_failure(&mut out, FAILURE, *error_class, *error_code),
74 LogDatum::TimeChange(seconds) => {
75 primitives::encode_ctx_real(&mut out, TIME_CHANGE, *seconds)
76 }
77 LogDatum::AnyValue(bytes) => encode_any_value(&mut out, ANY, bytes)?,
78 }
79 tags::encode_closing_tag(&mut out, 1);
80 if let Some(flags) = record.status_flags {
81 let octet = (flags & StatusFlags::all()).bits() << 4;
84 primitives::encode_ctx_bit_string(&mut out, STATUS_FLAGS, 4, &[octet]);
85 }
86 buf.extend_from_slice(&out);
87 Ok(())
88}
89
90pub fn decode_log_record(data: &[u8], offset: usize) -> Result<(BACnetLogRecord, usize), Error> {
97 let (date, time, datum_start) = decode_timestamp(data, offset, TIMESTAMP)?;
98 let (body, mut end) = decode_ctx_constructed(data, datum_start, 1, DATUM)?;
99 let log_datum = decode_datum(body, datum_start)?;
100 let mut status_flags = None;
101 if end < data.len() {
102 let (tag, start) = tags::decode_tag(data, end)?;
103 if tag.is_context(STATUS_FLAGS) {
104 let (octets, next) = contents(data, start, tag.length)?;
105 status_flags = match octets {
106 [4, bits] if bits & 0x0f == 0 => Some(status_flags_from_bacnet(&[*bits])),
107 _ => {
108 return Err(Error::decoding(
109 end,
110 "BACnetLogRecord status-flags must be a four-bit BitString",
111 ))
112 }
113 };
114 end = next;
115 }
116 }
117 Ok((
118 BACnetLogRecord {
119 date,
120 time,
121 log_datum,
122 status_flags,
123 },
124 end,
125 ))
126}
127
128fn decode_datum(data: &[u8], offset: usize) -> Result<LogDatum, Error> {
129 let (tag, start) = tags::decode_tag(data, 0)?;
130 let (datum, end) = if tag.is_opening_tag(FAILURE) {
131 let (body, end) = tags::extract_context_value(data, start, FAILURE)?;
132 let (error_class, error_code) = decode_failure(body, offset, FAILURE_FIELD)?;
133 let datum = LogDatum::Failure {
134 error_class,
135 error_code,
136 };
137 (datum, end)
138 } else if tag.is_opening_tag(ANY) {
139 let (body, end) = tags::extract_context_value(data, start, ANY)?;
140 (LogDatum::AnyValue(decode_any_value(body)?), end)
141 } else if tag.class == tags::TagClass::Context && !tag.is_opening && !tag.is_closing {
142 let (octets, end) = contents(data, start, tag.length)?;
143 (decode_primitive(tag.number, octets, offset)?, end)
144 } else {
145 return Err(Error::decoding(offset, "log-datum has an unknown tag"));
146 };
147 expect_end(data, end, offset, DATUM)?;
148 Ok(datum)
149}
150
151fn decode_primitive(number: u8, contents: &[u8], offset: usize) -> Result<LogDatum, Error> {
152 Ok(match number {
153 LOG_STATUS => LogDatum::LogStatus(decode_log_status(contents, offset, RECORD)?),
154 BOOLEAN => match contents {
155 [0] => LogDatum::BooleanValue(false),
156 [1] => LogDatum::BooleanValue(true),
157 _ => return Err(Error::decoding(offset, "boolean-value must be one octet")),
158 },
159 REAL => LogDatum::RealValue(primitives::decode_real(contents)?),
160 ENUMERATED => LogDatum::EnumValue(primitives::decode_unsigned(contents)?),
161 UNSIGNED => LogDatum::UnsignedValue(primitives::decode_unsigned(contents)?),
162 INTEGER => LogDatum::SignedValue(decode_integer(contents, offset)?),
163 BIT_STRING => {
164 let (unused_bits, data) = primitives::decode_bit_string(contents)?;
165 LogDatum::BitstringValue { unused_bits, data }
166 }
167 NULL if contents.is_empty() => LogDatum::NullValue,
168 TIME_CHANGE => LogDatum::TimeChange(primitives::decode_real(contents)?),
169 _ => return Err(Error::decoding(offset, "log-datum has an unknown tag")),
170 })
171}