#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::fmt;
use bacnet_encoding::constructed::{
decode_audit_log_record_at, decode_event_log_record, decode_log_multiple_record,
decode_log_record,
};
use bacnet_types::constructed::{
BACnetAuditLogRecord, BACnetEventLogRecord, BACnetLogMultipleRecord, BACnetLogRecord,
};
use bacnet_types::enums::{ObjectType, PropertyIdentifier};
use bacnet_types::error::Error;
use super::ReadRangeAck;
pub trait LogBufferRecord: Sized {
fn decode_at(data: &[u8], offset: usize) -> Result<(Self, usize), Error>;
}
impl LogBufferRecord for BACnetLogRecord {
fn decode_at(data: &[u8], offset: usize) -> Result<(Self, usize), Error> {
decode_log_record(data, offset)
}
}
impl LogBufferRecord for BACnetEventLogRecord {
fn decode_at(data: &[u8], offset: usize) -> Result<(Self, usize), Error> {
decode_event_log_record(data, offset)
}
}
impl LogBufferRecord for BACnetLogMultipleRecord {
fn decode_at(data: &[u8], offset: usize) -> Result<(Self, usize), Error> {
decode_log_multiple_record(data, offset)
}
}
impl LogBufferRecord for BACnetAuditLogRecord {
fn decode_at(data: &[u8], offset: usize) -> Result<(Self, usize), Error> {
decode_audit_log_record_at(data, offset)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LogRecords {
TrendLog(Vec<BACnetLogRecord>),
EventLog(Vec<BACnetEventLogRecord>),
TrendLogMultiple(Vec<BACnetLogMultipleRecord>),
AuditLog(Vec<BACnetAuditLogRecord>),
}
impl LogRecords {
pub fn empty_for(object_type: ObjectType) -> Option<Self> {
Some(match object_type {
ObjectType::TREND_LOG => Self::TrendLog(Vec::new()),
ObjectType::EVENT_LOG => Self::EventLog(Vec::new()),
ObjectType::TREND_LOG_MULTIPLE => Self::TrendLogMultiple(Vec::new()),
ObjectType::AUDIT_LOG => Self::AuditLog(Vec::new()),
_ => return None,
})
}
pub fn len(&self) -> usize {
match self {
Self::TrendLog(records) => records.len(),
Self::EventLog(records) => records.len(),
Self::TrendLogMultiple(records) => records.len(),
Self::AuditLog(records) => records.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug)]
pub struct LogRecordsError<C> {
pub decoded: C,
pub index: usize,
pub offset: usize,
pub error: Error,
}
impl<C> LogRecordsError<C> {
fn map<D>(self, f: impl FnOnce(C) -> D) -> LogRecordsError<D> {
LogRecordsError {
decoded: f(self.decoded),
index: self.index,
offset: self.offset,
error: self.error,
}
}
}
impl<C> fmt::Display for LogRecordsError<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"log record {} at item-data offset {}: {}",
self.index, self.offset, self.error
)
}
}
impl<C: fmt::Debug> core::error::Error for LogRecordsError<C> {}
impl<C> From<LogRecordsError<C>> for Error {
fn from(error: LogRecordsError<C>) -> Self {
let refused = error.error.to_decode_error(error.offset).within("log record");
match u16::try_from(error.index) {
Ok(index) => refused.at_item(index).into(),
Err(_) => refused.into(),
}
}
}
impl ReadRangeAck {
pub fn records<R: LogBufferRecord>(&self) -> Result<Vec<R>, LogRecordsError<Vec<R>>> {
let data = self.item_data.as_slice();
let expected = usize::try_from(self.item_count).unwrap_or(usize::MAX);
let mut records = Vec::with_capacity(expected.min(data.len() / 2));
let mut offset = 0;
let fault = loop {
if offset == data.len() {
if records.len() == expected {
return Ok(records);
}
break Error::missing(offset, "item data holds fewer records than item-count");
}
if records.len() == expected {
break Error::trailing(offset, "item data holds more records than item-count");
}
match R::decode_at(data, offset) {
Ok((record, next)) if next > offset => {
records.push(record);
offset = next;
}
Ok(_) => break Error::decoding(offset, "log record has no octets"),
Err(error) => break error,
}
};
Err(LogRecordsError {
index: records.len(),
decoded: records,
offset,
error: fault,
})
}
pub fn trend_log_records(
&self,
) -> Result<Vec<BACnetLogRecord>, LogRecordsError<Vec<BACnetLogRecord>>> {
self.records()
}
pub fn event_log_records(
&self,
) -> Result<Vec<BACnetEventLogRecord>, LogRecordsError<Vec<BACnetEventLogRecord>>> {
self.records()
}
pub fn trend_log_multiple_records(
&self,
) -> Result<Vec<BACnetLogMultipleRecord>, LogRecordsError<Vec<BACnetLogMultipleRecord>>> {
self.records()
}
pub fn audit_log_records(
&self,
) -> Result<Vec<BACnetAuditLogRecord>, LogRecordsError<Vec<BACnetAuditLogRecord>>> {
self.records()
}
pub fn log_records(&self) -> Option<Result<LogRecords, LogRecordsError<LogRecords>>> {
if self.property_identifier != PropertyIdentifier::LOG_BUFFER {
return None;
}
Some(match self.object_identifier.object_type() {
ObjectType::TREND_LOG => self
.records()
.map(LogRecords::TrendLog)
.map_err(|e| e.map(LogRecords::TrendLog)),
ObjectType::EVENT_LOG => self
.records()
.map(LogRecords::EventLog)
.map_err(|e| e.map(LogRecords::EventLog)),
ObjectType::TREND_LOG_MULTIPLE => self
.records()
.map(LogRecords::TrendLogMultiple)
.map_err(|e| e.map(LogRecords::TrendLogMultiple)),
ObjectType::AUDIT_LOG => self
.records()
.map(LogRecords::AuditLog)
.map_err(|e| e.map(LogRecords::AuditLog)),
_ => return None,
})
}
}
#[cfg(test)]
#[path = "read_range_records_tests.rs"]
mod tests;