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