use std::collections::VecDeque;
use super::*;
use bacnet_encoding::constructed::encode_audit_log_record;
use bacnet_objects::log_buffer::{LogBufferRecords, TimestampKey, TimestampOrder};
use bacnet_types::constructed::BACnetAuditLogRecordResult;
pub(crate) enum RangeItems<'a> {
Values(Vec<PropertyValue>),
AuditRecords(&'a VecDeque<BACnetAuditLogRecordResult>),
LogRecords(&'a dyn LogBufferRecords),
}
impl RangeItems<'_> {
pub(super) fn len(&self) -> usize {
match self {
Self::Values(items) => items.len(),
Self::AuditRecords(records) => records.len(),
Self::LogRecords(records) => records.record_count(),
}
}
pub(super) fn encode_with<F>(
&self,
index: usize,
buf: &mut BytesMut,
encode_value: &mut F,
) -> Result<(), Error>
where
F: FnMut(&mut BytesMut, &PropertyValue) -> Result<(), Error>,
{
match self {
Self::Values(items) => encode_value(buf, &items[index]),
Self::AuditRecords(records) => encode_audit_log_record(&records[index].record, buf),
Self::LogRecords(records) => {
records.encode_record(index, buf);
Ok(())
}
}
}
}
pub(crate) enum LogIdentities<'a> {
Buffer(&'a dyn LogBufferRecords),
Audit(&'a VecDeque<BACnetAuditLogRecordResult>),
Listed(Vec<LogRecordIdentity>),
}
impl LogIdentities<'_> {
pub(super) fn len(&self) -> usize {
match self {
Self::Buffer(records) => records.record_count(),
Self::Audit(records) => records.len(),
Self::Listed(identities) => identities.len(),
}
}
pub(super) fn sequence_number(&self, index: usize) -> u64 {
match self {
Self::Buffer(records) => records.record_identity(index).sequence_number(),
Self::Audit(records) => records[index].sequence_number,
Self::Listed(identities) => identities[index].sequence_number(),
}
}
pub(super) fn position(&self, sequence_number: u64) -> Result<Option<usize>, Error> {
match self {
Self::Buffer(records) => Ok(records.record_position(sequence_number)),
Self::Audit(records) => {
let computed = records.front().and_then(|oldest| {
let offset = sequence_offset(oldest.sequence_number, sequence_number)?;
usize::try_from(offset).ok()
});
if let Some(index) = computed.filter(|&index| {
records
.get(index)
.is_some_and(|record| record.sequence_number == sequence_number)
}) {
return Ok(Some(index));
}
if records.iter().any(|record| record.sequence_number == 0) {
return Err(super::list_item_not_numbered());
}
Ok(records
.iter()
.position(|record| record.sequence_number == sequence_number))
}
Self::Listed(identities) => Ok(identities
.iter()
.position(|identity| identity.sequence_number() == sequence_number)),
}
}
pub(super) fn time_anchor(
&self,
reference: TimestampKey,
count: i32,
) -> Result<Option<usize>, Error> {
match self {
Self::Buffer(records) => {
let len = records.record_count();
let key = |index| records.record_identity(index).timestamp_key();
Ok(match records.timestamp_order() {
TimestampOrder::Unkeyed => return Err(super::list_item_not_timestamped()),
TimestampOrder::Ascending => bisect_time_anchor(len, key, reference, count),
TimestampOrder::Unordered if count > 0 => {
(0..len).find(|&index| key(index).is_some_and(|key| key > reference))
}
TimestampOrder::Unordered => (0..len)
.rev()
.find(|&index| key(index).is_some_and(|key| key < reference)),
})
}
Self::Audit(records) => scan_time_anchor(
records.len(),
|index| {
let record = &records[index];
let (date, time) = record.record.timestamp;
LogRecordIdentity::new(record.sequence_number, date, time)
.and_then(|identity| identity.timestamp_key())
},
reference,
count,
),
Self::Listed(identities) => scan_time_anchor(
identities.len(),
|index| identities[index].timestamp_key(),
reference,
count,
),
}
}
}
fn sequence_offset(from: u64, to: u64) -> Option<u64> {
if from == 0 || to == 0 {
return None;
}
Some(if to >= from {
to - from
} else {
(u64::MAX - from) + to
})
}
fn scan_time_anchor(
len: usize,
key: impl Fn(usize) -> Option<TimestampKey>,
reference: TimestampKey,
count: i32,
) -> Result<Option<usize>, Error> {
let mut first_after = None;
let mut last_before = None;
for index in 0..len {
let key = key(index).ok_or_else(super::list_item_not_timestamped)?;
if first_after.is_none() && key > reference {
first_after = Some(index);
}
if key < reference {
last_before = Some(index);
}
}
Ok(if count > 0 { first_after } else { last_before })
}
fn bisect_time_anchor(
len: usize,
key: impl Fn(usize) -> Option<TimestampKey>,
reference: TimestampKey,
count: i32,
) -> Option<usize> {
let reference = Some(reference);
if count > 0 {
let after = partition_point(len, |index| key(index) <= reference);
(after < len).then_some(after)
} else {
partition_point(len, |index| key(index) < reference).checked_sub(1)
}
}
fn partition_point(len: usize, before: impl Fn(usize) -> bool) -> usize {
let (mut low, mut high) = (0, len);
while low < high {
let middle = low + (high - low) / 2;
if before(middle) {
low = middle + 1;
} else {
high = middle;
}
}
low
}
pub(super) type LogBuffer<'a> = (RangeItems<'a>, LogIdentities<'a>);
pub(super) fn log_buffer<'a>(
object: &'a dyn BACnetObject,
request: &ReadRangeRequest,
) -> Result<Option<LogBuffer<'a>>, Error> {
if request.property_identifier != PropertyIdentifier::LOG_BUFFER {
return Ok(None);
}
let resolved = if let Some(storage) = object.audit_log_storage_internal() {
let records = storage.retained_records();
(
RangeItems::AuditRecords(records),
LogIdentities::Audit(records),
)
} else if let Some(records) = object.log_buffer_internal() {
(
RangeItems::LogRecords(records),
LogIdentities::Buffer(records),
)
} else {
return Ok(None);
};
if request.property_array_index.is_some() {
return Err(Error::protocol(
ErrorClass::PROPERTY,
ErrorCode::PROPERTY_IS_NOT_AN_ARRAY,
));
}
Ok(Some(resolved))
}
#[cfg(test)]
mod tests {
use super::sequence_offset;
#[test]
fn audit_offsets_count_on_from_the_oldest_and_round_the_wrap() {
assert_eq!(sequence_offset(5, 5), Some(0));
assert_eq!(sequence_offset(5, 9), Some(4));
assert_eq!(sequence_offset(u64::MAX - 1, u64::MAX), Some(1));
assert_eq!(sequence_offset(u64::MAX, 1), Some(1));
assert_eq!(sequence_offset(u64::MAX - 1, 2), Some(3));
assert_eq!(sequence_offset(9, 5), Some(u64::MAX - 4));
assert_eq!(sequence_offset(0, 5), None);
assert_eq!(sequence_offset(5, 0), None);
}
}