Skip to main content

bacnet_encoding/constructed/
event_log_record.rs

1//! Event Log log records (Clause 12.27.13), framed as Clause 21 gives
2//! BACnetEventLogRecord.
3//!
4//! A record is two constructed fields. Context tag 0 wraps the timestamp, an
5//! application Date then an application Time. Context tag 1 wraps the log
6//! datum, one of three alternatives: a primitive context 0 holding the
7//! three-bit log status, a constructed context 1 holding a notification's
8//! request parameters, or a primitive context 2 holding the clock change as
9//! a REAL.
10
11use super::log_fields::{decode_log_status, decode_timestamp, encode_log_status, encode_timestamp};
12use super::tagged::{contents, decode_ctx_constructed, expect_end};
13use super::{decode_event_notification_tolerant, encode_event_notification};
14use crate::{primitives, tags};
15use bacnet_types::constructed::{BACnetEventLogRecord, EventLogDatum};
16use bacnet_types::error::Error;
17use bytes::BytesMut;
18
19const RECORD: &str = "BACnetEventLogRecord";
20const TIMESTAMP: &str = "BACnetEventLogRecord timestamp";
21const DATUM: &str = "BACnetEventLogRecord log-datum";
22
23const LOG_STATUS: u8 = 0;
24const NOTIFICATION: u8 = 1;
25const TIME_CHANGE: u8 = 2;
26
27/// Encode one Event Log record.
28///
29/// Fails, leaving `buf` unchanged, for a notification that would not encode,
30/// such as one whose raw event values aren't a well-formed run of tagged
31/// fields.
32pub fn encode_event_log_record(
33    record: &BACnetEventLogRecord,
34    buf: &mut BytesMut,
35) -> Result<(), Error> {
36    let mut out = BytesMut::new();
37    encode_timestamp(&mut out, &record.date, &record.time);
38    tags::encode_opening_tag(&mut out, 1);
39    match &record.log_datum {
40        EventLogDatum::LogStatus(status) => encode_log_status(&mut out, LOG_STATUS, *status),
41        EventLogDatum::Notification(notification) => {
42            tags::encode_opening_tag(&mut out, NOTIFICATION);
43            encode_event_notification(notification, &mut out)?;
44            tags::encode_closing_tag(&mut out, NOTIFICATION);
45        }
46        EventLogDatum::TimeChange(seconds) => {
47            primitives::encode_ctx_real(&mut out, TIME_CHANGE, *seconds)
48        }
49    }
50    tags::encode_closing_tag(&mut out, 1);
51    buf.extend_from_slice(&out);
52    Ok(())
53}
54
55/// Decode one Event Log record starting at `offset`, returning it and the
56/// offset just past it.
57///
58/// A notification decodes in full, so a record whose notification isn't a
59/// valid request fails as a whole. The one exception is a message text whose
60/// characters don't decode, such as one in a character set this stack doesn't
61/// support: the notification reads with no message text, as a received
62/// notification does at the client ([`decode_event_notification_tolerant`]).
63pub fn decode_event_log_record(
64    data: &[u8],
65    offset: usize,
66) -> Result<(BACnetEventLogRecord, usize), Error> {
67    let (date, time, datum_start) = decode_timestamp(data, offset, TIMESTAMP)?;
68    let (body, end) = decode_ctx_constructed(data, datum_start, 1, DATUM)?;
69    let log_datum = decode_datum(body, datum_start)?;
70    Ok((
71        BACnetEventLogRecord {
72            date,
73            time,
74            log_datum,
75        },
76        end,
77    ))
78}
79
80fn decode_datum(data: &[u8], offset: usize) -> Result<EventLogDatum, Error> {
81    let (tag, start) = tags::decode_tag(data, 0)?;
82    let (datum, end) = if tag.is_context(LOG_STATUS) {
83        let (octets, end) = contents(data, start, tag.length)?;
84        let status = decode_log_status(octets, offset, RECORD)?;
85        (EventLogDatum::LogStatus(status), end)
86    } else if tag.is_opening_tag(NOTIFICATION) {
87        let (parameters, end) = tags::extract_context_value(data, start, NOTIFICATION)?;
88        let notification = decode_event_notification_tolerant(parameters)?;
89        (EventLogDatum::Notification(notification), end)
90    } else if tag.is_context(TIME_CHANGE) {
91        let (octets, end) = contents(data, start, tag.length)?;
92        (
93            EventLogDatum::TimeChange(primitives::decode_real(octets)?),
94            end,
95        )
96    } else {
97        return Err(Error::decoding(
98            offset,
99            "BACnetEventLogRecord log-datum expected context [0], constructed [1], or context [2]",
100        ));
101    };
102    expect_end(data, end, offset, DATUM)?;
103    Ok(datum)
104}