1use super::log_fields::{
16 check_bit_string, decode_any_value, decode_failure, decode_integer, decode_log_status,
17 decode_timestamp, encode_any_value, encode_ctx_integer, encode_failure, encode_log_status,
18 encode_timestamp,
19};
20use super::tagged::{contents, decode_ctx_constructed, expect_end};
21use crate::{primitives, tags};
22use bacnet_types::constructed::{BACnetLogMultipleRecord, LogData, LogValue};
23use bacnet_types::error::Error;
24use bytes::BytesMut;
25
26const RECORD: &str = "BACnetLogMultipleRecord";
27const TIMESTAMP: &str = "BACnetLogMultipleRecord timestamp";
28const LOG_DATA: &str = "BACnetLogMultipleRecord log-data";
29const FAILURE_FIELD: &str = "BACnetLogMultipleRecord failure";
30
31const BOOLEAN: u8 = 0;
32const REAL: u8 = 1;
33const ENUMERATED: u8 = 2;
34const UNSIGNED: u8 = 3;
35const INTEGER: u8 = 4;
36const BIT_STRING: u8 = 5;
37const NULL: u8 = 6;
38const FAILURE: u8 = 7;
39const ANY: u8 = 8;
40
41pub fn encode_log_multiple_record(
47 record: &BACnetLogMultipleRecord,
48 buf: &mut BytesMut,
49) -> Result<(), Error> {
50 let mut out = BytesMut::new();
51 encode_timestamp(&mut out, &record.date, &record.time);
52 tags::encode_opening_tag(&mut out, 1);
53 match &record.log_data {
54 LogData::LogStatus(status) => encode_log_status(&mut out, 0, *status),
55 LogData::Values(values) => {
56 tags::encode_opening_tag(&mut out, 1);
57 for value in values {
58 encode_value(value, &mut out)?;
59 }
60 tags::encode_closing_tag(&mut out, 1);
61 }
62 LogData::TimeChange(seconds) => primitives::encode_ctx_real(&mut out, 2, *seconds),
63 }
64 tags::encode_closing_tag(&mut out, 1);
65 buf.extend_from_slice(&out);
66 Ok(())
67}
68
69fn encode_value(value: &LogValue, buf: &mut BytesMut) -> Result<(), Error> {
70 match value {
71 LogValue::BooleanValue(value) => primitives::encode_ctx_boolean(buf, BOOLEAN, *value),
72 LogValue::RealValue(value) => primitives::encode_ctx_real(buf, REAL, *value),
73 LogValue::EnumValue(value) => primitives::encode_ctx_unsigned(buf, ENUMERATED, *value),
74 LogValue::UnsignedValue(value) => primitives::encode_ctx_unsigned(buf, UNSIGNED, *value),
75 LogValue::SignedValue(value) => encode_ctx_integer(buf, INTEGER, *value),
76 LogValue::BitstringValue { unused_bits, data } => {
77 check_bit_string(*unused_bits, data, "bitstring-value")?;
78 primitives::encode_ctx_bit_string(buf, BIT_STRING, *unused_bits, data);
79 }
80 LogValue::NullValue => tags::encode_tag(buf, NULL, tags::TagClass::Context, 0),
81 LogValue::Failure {
82 error_class,
83 error_code,
84 } => encode_failure(buf, FAILURE, *error_class, *error_code),
85 LogValue::AnyValue(bytes) => encode_any_value(buf, ANY, bytes)?,
86 }
87 Ok(())
88}
89
90pub fn decode_log_multiple_record(
93 data: &[u8],
94 offset: usize,
95) -> Result<(BACnetLogMultipleRecord, usize), Error> {
96 let (date, time, data_start) = decode_timestamp(data, offset, TIMESTAMP)?;
97 let (body, end) = decode_ctx_constructed(data, data_start, 1, LOG_DATA)?;
98 let log_data = decode_log_data(body, data_start)?;
99 Ok((
100 BACnetLogMultipleRecord {
101 date,
102 time,
103 log_data,
104 },
105 end,
106 ))
107}
108
109fn decode_log_data(data: &[u8], offset: usize) -> Result<LogData, Error> {
110 let (tag, start) = tags::decode_tag(data, 0)?;
111 let (log_data, end) = if tag.is_context(0) {
112 let (octets, end) = contents(data, start, tag.length)?;
113 (
114 LogData::LogStatus(decode_log_status(octets, offset, RECORD)?),
115 end,
116 )
117 } else if tag.is_opening_tag(1) {
118 let (list, end) = tags::extract_context_value(data, start, 1)?;
119 (LogData::Values(decode_values(list, offset)?), end)
120 } else if tag.is_context(2) {
121 let (octets, end) = contents(data, start, tag.length)?;
122 (LogData::TimeChange(primitives::decode_real(octets)?), end)
123 } else {
124 return Err(Error::decoding(
125 offset,
126 "log-data expected context [0], constructed [1], or context [2]",
127 ));
128 };
129 expect_end(data, end, offset, LOG_DATA)?;
130 Ok(log_data)
131}
132
133fn decode_values(data: &[u8], offset: usize) -> Result<Vec<LogValue>, Error> {
134 let mut values = Vec::new();
135 let mut pos = 0;
136 while pos < data.len() {
137 let (tag, start) = tags::decode_tag(data, pos)?;
138 let (value, end) = if tag.is_opening_tag(FAILURE) {
139 let (body, end) = tags::extract_context_value(data, start, FAILURE)?;
140 let (error_class, error_code) = decode_failure(body, offset, FAILURE_FIELD)?;
141 let value = LogValue::Failure {
142 error_class,
143 error_code,
144 };
145 (value, end)
146 } else if tag.is_opening_tag(ANY) {
147 let (body, end) = tags::extract_context_value(data, start, ANY)?;
148 (LogValue::AnyValue(decode_any_value(body)?), end)
149 } else if tag.class == tags::TagClass::Context && !tag.is_opening && !tag.is_closing {
150 let (octets, end) = contents(data, start, tag.length)?;
151 (decode_primitive(tag.number, octets, offset)?, end)
152 } else {
153 return Err(Error::decoding(offset, "log-data entry has an unknown tag"));
154 };
155 values.push(value);
156 pos = end;
157 }
158 Ok(values)
159}
160
161fn decode_primitive(number: u8, contents: &[u8], offset: usize) -> Result<LogValue, Error> {
162 Ok(match number {
163 BOOLEAN => match contents {
164 [0] => LogValue::BooleanValue(false),
165 [1] => LogValue::BooleanValue(true),
166 _ => return Err(Error::decoding(offset, "boolean-value must be one octet")),
167 },
168 REAL => LogValue::RealValue(primitives::decode_real(contents)?),
169 ENUMERATED => LogValue::EnumValue(primitives::decode_unsigned(contents)?),
170 UNSIGNED => LogValue::UnsignedValue(primitives::decode_unsigned(contents)?),
171 INTEGER => LogValue::SignedValue(decode_integer(contents, offset)?),
172 BIT_STRING => {
173 let (unused_bits, data) = primitives::decode_bit_string(contents)?;
174 LogValue::BitstringValue { unused_bits, data }
175 }
176 NULL if contents.is_empty() => LogValue::NullValue,
177 _ => return Err(Error::decoding(offset, "log-data entry has an unknown tag")),
178 })
179}