1use std::collections::VecDeque;
4
5use bacnet_encoding::constructed::{
6 encode_event_log_record, encode_log_multiple_record, encode_log_record,
7};
8use bacnet_types::bitstring::LogStatus;
9use bacnet_types::constructed::{
10 BACnetEventLogRecord, BACnetLogMultipleRecord, BACnetLogRecord, EventLogDatum, LogData,
11 LogDatum,
12};
13use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier as P};
14use bacnet_types::error::Error;
15use bacnet_types::primitives::{Date, Time};
16use bytes::BytesMut;
17
18use crate::property_metadata::{
19 PropertyConformance::RequiredRead, PropertyMetadata, PropertyWriteCapability::ReadOnly,
20};
21
22pub(crate) const BUFFER_SIZE_METADATA: PropertyMetadata =
25 PropertyMetadata::new(P::BUFFER_SIZE, RequiredRead, None, ReadOnly);
26pub(crate) const LOG_BUFFER_METADATA: PropertyMetadata =
27 PropertyMetadata::new(P::LOG_BUFFER, RequiredRead, None, ReadOnly);
28pub(crate) const TOTAL_RECORD_COUNT_METADATA: PropertyMetadata =
29 PropertyMetadata::new(P::TOTAL_RECORD_COUNT, RequiredRead, None, ReadOnly);
30
31pub(crate) fn log_buffer_read_denied() -> Error {
38 Error::Protocol {
39 class: ErrorClass::PROPERTY.to_raw() as u32,
40 code: ErrorCode::READ_ACCESS_DENIED.to_raw() as u32,
41 }
42}
43
44pub trait LogBufferRecords {
54 fn record_count(&self) -> usize;
56
57 fn encode_record(&self, index: usize, buf: &mut BytesMut);
64
65 fn record_identity(&self, index: usize) -> LogRecordIdentity;
68
69 fn record_position(&self, sequence_number: u64) -> Option<usize> {
75 (0..self.record_count())
76 .find(|&index| self.record_identity(index).sequence_number() == sequence_number)
77 }
78
79 fn timestamp_order(&self) -> TimestampOrder {
85 let mut order = TimestampOrder::Ascending;
86 let mut previous = None;
87 for index in 0..self.record_count() {
88 let Some(key) = self.record_identity(index).timestamp_key() else {
89 return TimestampOrder::Unkeyed;
90 };
91 if previous.is_some_and(|previous| key < previous) {
92 order = TimestampOrder::Unordered;
93 }
94 previous = Some(key);
95 }
96 order
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
103pub struct TimestampKey {
104 year: u16,
105 month: u8,
106 day: u8,
107 hour: u8,
108 minute: u8,
109 second: u8,
110 hundredths: u8,
111}
112
113impl TimestampKey {
114 pub fn of(date: Date, time: Time) -> Option<Self> {
119 let year = date.actual_year()?;
120 let valid = (1..=12).contains(&date.month)
121 && (1..=31).contains(&date.day)
122 && (1..=7).contains(&date.day_of_week)
123 && time.hour <= 23
124 && time.minute <= 59
125 && time.second <= 59
126 && time.hundredths <= 99;
127 valid.then_some(Self {
128 year,
129 month: date.month,
130 day: date.day,
131 hour: time.hour,
132 minute: time.minute,
133 second: time.second,
134 hundredths: time.hundredths,
135 })
136 }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum TimestampOrder {
143 Unkeyed,
147 Ascending,
150 Unordered,
153}
154
155pub const ANY_VALUE_MAX_OCTETS: usize = 256;
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct LogRecordIdentity {
170 sequence_number: u64,
171 date: Date,
172 time: Time,
173}
174
175impl LogRecordIdentity {
176 pub fn new(sequence_number: u64, date: Date, time: Time) -> Option<Self> {
178 (sequence_number != 0).then_some(Self {
179 sequence_number,
180 date,
181 time,
182 })
183 }
184
185 pub fn sequence_number(&self) -> u64 {
187 self.sequence_number
188 }
189
190 pub fn date(&self) -> Date {
192 self.date
193 }
194
195 pub fn time(&self) -> Time {
197 self.time
198 }
199
200 pub fn timestamp_key(&self) -> Option<TimestampKey> {
203 TimestampKey::of(self.date, self.time)
204 }
205}
206
207pub(crate) trait ResidentLogRecord: Clone {
211 fn timestamp(&self) -> (Date, Time);
213 fn log_status(date: Date, time: Time, status: LogStatus) -> Self;
215 fn encode(&self, buf: &mut BytesMut) -> Result<(), Error>;
217}
218
219impl ResidentLogRecord for BACnetLogRecord {
220 fn timestamp(&self) -> (Date, Time) {
221 (self.date, self.time)
222 }
223
224 fn log_status(date: Date, time: Time, status: LogStatus) -> Self {
225 Self {
226 date,
227 time,
228 log_datum: LogDatum::LogStatus(status),
229 status_flags: None,
230 }
231 }
232
233 fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
234 encode_log_record(self, buf)
235 }
236}
237
238impl ResidentLogRecord for BACnetEventLogRecord {
239 fn timestamp(&self) -> (Date, Time) {
240 (self.date, self.time)
241 }
242
243 fn log_status(date: Date, time: Time, status: LogStatus) -> Self {
244 Self {
245 date,
246 time,
247 log_datum: EventLogDatum::LogStatus(status),
248 }
249 }
250
251 fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
252 encode_event_log_record(self, buf)
253 }
254}
255
256impl ResidentLogRecord for BACnetLogMultipleRecord {
257 fn timestamp(&self) -> (Date, Time) {
258 (self.date, self.time)
259 }
260
261 fn log_status(date: Date, time: Time, status: LogStatus) -> Self {
262 Self {
263 date,
264 time,
265 log_data: LogData::LogStatus(status),
266 }
267 }
268
269 fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
270 encode_log_multiple_record(self, buf)
271 }
272}
273
274#[derive(Clone)]
284pub(crate) struct LogRecordBuffer<R = BACnetLogRecord> {
285 capacity: u32,
286 records: VecDeque<R>,
287 total_record_count: u32,
288 unkeyed: usize,
289 descents: usize,
290}
291
292#[derive(Debug, Clone, Copy, PartialEq, Eq)]
293pub(crate) enum OrdinaryAdmission {
294 IgnoredDisabled,
295 Inserted,
296 CountOnly,
297 StopBeforeFull,
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301pub(crate) enum ForcedAdmission {
302 Inserted,
303 CountOnly,
304}
305
306impl<R: ResidentLogRecord> LogRecordBuffer<R> {
307 pub(crate) fn new(capacity: u32) -> Self {
308 Self {
309 capacity,
310 records: VecDeque::new(),
311 total_record_count: 0,
312 unkeyed: 0,
313 descents: 0,
314 }
315 }
316
317 pub(crate) fn admit_ordinary(
318 &mut self,
319 record: R,
320 enabled: bool,
321 stop_when_full: bool,
322 ) -> OrdinaryAdmission {
323 if !enabled {
324 return OrdinaryAdmission::IgnoredDisabled;
325 }
326 if stop_when_full && self.next_record_would_fill() {
327 return OrdinaryAdmission::StopBeforeFull;
328 }
329
330 match self.insert_counted(record) {
331 ForcedAdmission::Inserted => OrdinaryAdmission::Inserted,
332 ForcedAdmission::CountOnly => OrdinaryAdmission::CountOnly,
333 }
334 }
335
336 pub(crate) fn insert_forced(&mut self, record: R) -> ForcedAdmission {
337 self.insert_counted(record)
338 }
339
340 pub(crate) fn is_full(&self) -> bool {
341 self.records.len() >= self.capacity as usize
342 }
343
344 pub(crate) fn next_record_would_fill_positive_capacity(&self) -> bool {
345 self.capacity > 0 && self.next_record_would_fill()
346 }
347
348 pub(crate) fn records(&self) -> &VecDeque<R> {
349 &self.records
350 }
351
352 pub(crate) fn total_record_count(&self) -> u32 {
353 self.total_record_count
354 }
355
356 pub(crate) fn capacity(&self) -> u32 {
357 self.capacity
358 }
359
360 pub(crate) fn clear(&mut self) {
361 self.records.clear();
362 self.unkeyed = 0;
363 self.descents = 0;
364 }
365
366 pub(crate) fn identities(&self) -> Vec<LogRecordIdentity> {
367 (0..self.records.len())
368 .map(|index| self.record_identity(index))
369 .collect()
370 }
371
372 pub(crate) fn restore(
385 &mut self,
386 total_record_count: u32,
387 records: VecDeque<R>,
388 stops_when_full: bool,
389 ) -> Result<(), Error> {
390 let held = records.len();
391 if held > self.capacity as usize {
392 return Err(Error::OutOfRange(format!(
393 "{held} log records exceed Buffer_Size {}",
394 self.capacity
395 )));
396 }
397 if total_record_count == 0 && held > 0 {
398 return Err(Error::OutOfRange(format!(
399 "{held} log records restored with a Total_Record_Count of 0"
400 )));
401 }
402 if stops_when_full && held > 0 && held >= self.capacity as usize {
403 return Err(Error::OutOfRange(
404 "a log with Stop_When_Full and Enable TRUE can't hold a full buffer".into(),
405 ));
406 }
407 let mut scratch = BytesMut::new();
408 for record in &records {
409 record.encode(&mut scratch)?;
410 scratch.clear();
411 }
412
413 self.clear();
414 for record in records {
415 self.push_back(record);
416 }
417 self.total_record_count = total_record_count;
418 Ok(())
419 }
420
421 fn next_record_would_fill(&self) -> bool {
422 self.capacity == 0 || self.records.len().saturating_add(1) >= self.capacity as usize
423 }
424
425 fn insert_counted(&mut self, record: R) -> ForcedAdmission {
426 self.total_record_count = next_sequence(self.total_record_count);
427 if self.capacity == 0 {
428 return ForcedAdmission::CountOnly;
429 }
430 if self.is_full() {
431 self.pop_front();
432 }
433 self.push_back(record);
434 ForcedAdmission::Inserted
435 }
436
437 fn push_back(&mut self, record: R) {
439 if self
440 .records
441 .back()
442 .is_some_and(|last| descends(last, &record))
443 {
444 self.descents += 1;
445 }
446 if timestamp_key(&record).is_none() {
447 self.unkeyed += 1;
448 }
449 self.records.push_back(record);
450 }
451
452 fn pop_front(&mut self) {
455 let Some(oldest) = self.records.pop_front() else {
456 return;
457 };
458 if timestamp_key(&oldest).is_none() {
459 self.unkeyed -= 1;
460 }
461 if self
462 .records
463 .front()
464 .is_some_and(|next| descends(&oldest, next))
465 {
466 self.descents -= 1;
467 }
468 }
469}
470
471impl<R: ResidentLogRecord> LogBufferRecords for LogRecordBuffer<R> {
472 fn record_count(&self) -> usize {
473 self.records.len()
474 }
475
476 fn encode_record(&self, index: usize, buf: &mut BytesMut) {
477 self.records[index]
481 .encode(buf)
482 .expect("every resident log record encodes");
483 }
484
485 fn record_identity(&self, index: usize) -> LogRecordIdentity {
486 let (date, time) = self.records[index].timestamp();
487 let newer = self.records.len() - 1 - index;
488 LogRecordIdentity {
489 sequence_number: u64::from(sequence_before(self.total_record_count, newer)),
490 date,
491 time,
492 }
493 }
494
495 fn record_position(&self, sequence_number: u64) -> Option<usize> {
496 let sequence_number = u32::try_from(sequence_number)
497 .ok()
498 .filter(|&number| number != 0)?;
499 let newer = sequence_distance(sequence_number, self.total_record_count);
500 let newer = usize::try_from(newer)
501 .ok()
502 .filter(|&newer| newer < self.records.len())?;
503 Some(self.records.len() - 1 - newer)
504 }
505
506 fn timestamp_order(&self) -> TimestampOrder {
507 if self.unkeyed > 0 {
508 TimestampOrder::Unkeyed
509 } else if self.descents > 0 {
510 TimestampOrder::Unordered
511 } else {
512 TimestampOrder::Ascending
513 }
514 }
515}
516
517fn timestamp_key<R: ResidentLogRecord>(record: &R) -> Option<TimestampKey> {
518 let (date, time) = record.timestamp();
519 TimestampKey::of(date, time)
520}
521
522fn descends<R: ResidentLogRecord>(earlier: &R, later: &R) -> bool {
527 matches!(
528 (timestamp_key(earlier), timestamp_key(later)),
529 (Some(earlier), Some(later)) if later < earlier
530 )
531}
532
533const SEQUENCE_CYCLE: u64 = u32::MAX as u64;
536
537fn next_sequence(sequence_number: u32) -> u32 {
538 if sequence_number == u32::MAX {
539 1
540 } else {
541 sequence_number + 1
542 }
543}
544
545fn sequence_before(sequence_number: u32, steps: usize) -> u32 {
547 let steps = steps as u64 % SEQUENCE_CYCLE;
548 let offset = u64::from(sequence_number).saturating_sub(1);
549 ((offset + SEQUENCE_CYCLE - steps) % SEQUENCE_CYCLE) as u32 + 1
550}
551
552fn sequence_distance(from: u32, to: u32) -> u64 {
554 (u64::from(to) + SEQUENCE_CYCLE - u64::from(from)) % SEQUENCE_CYCLE
555}
556
557#[cfg(test)]
558#[path = "log_buffer_lookup_tests.rs"]
559mod lookup_tests;
560
561#[cfg(test)]
562mod tests {
563 use super::*;
564 use bacnet_types::constructed::{BACnetLogRecord, LogDatum};
565 use bacnet_types::primitives::{Date, Time};
566
567 fn record(hour: u8) -> BACnetLogRecord {
568 BACnetLogRecord {
569 date: Date {
570 year: 126,
571 month: 8,
572 day: 31,
573 day_of_week: 1,
574 },
575 time: Time {
576 hour,
577 minute: 0,
578 second: 0,
579 hundredths: 0,
580 },
581 log_datum: LogDatum::UnsignedValue(hour as u64),
582 status_flags: None,
583 }
584 }
585
586 #[test]
587 fn log_buffer_assigns_one_and_wraps_max_without_zero() {
588 let mut buffer = LogRecordBuffer::new(2);
589 assert_eq!(buffer.total_record_count(), 0);
590 assert!(LogRecordIdentity::new(0, record(0).date, record(0).time).is_none());
591
592 assert_eq!(
593 buffer.admit_ordinary(record(1), true, false),
594 OrdinaryAdmission::Inserted
595 );
596 assert_eq!(buffer.identities()[0].sequence_number(), 1);
597
598 buffer.clear();
599 buffer.restore(u32::MAX, VecDeque::new(), false).unwrap();
600 assert_eq!(
601 buffer.admit_ordinary(record(2), true, false),
602 OrdinaryAdmission::Inserted
603 );
604 assert_eq!(buffer.total_record_count(), 1);
605 assert_eq!(buffer.identities()[0].sequence_number(), 1);
606 }
607
608 #[test]
609 fn log_buffer_rejections_do_not_consume_sequence() {
610 let mut buffer = LogRecordBuffer::new(1);
611 assert_eq!(
612 buffer.admit_ordinary(record(1), false, false),
613 OrdinaryAdmission::IgnoredDisabled
614 );
615 assert_eq!(buffer.total_record_count(), 0);
616
617 assert_eq!(
618 buffer.admit_ordinary(record(2), true, true),
619 OrdinaryAdmission::StopBeforeFull
620 );
621 assert_eq!(buffer.total_record_count(), 0);
622 assert!(buffer.identities().is_empty());
623 }
624
625 #[test]
626 fn log_buffer_zero_capacity_counts_without_retaining() {
627 let mut ring = LogRecordBuffer::new(0);
628 assert_eq!(
629 ring.admit_ordinary(record(1), true, false),
630 OrdinaryAdmission::CountOnly
631 );
632 assert_eq!(
633 ring.admit_ordinary(record(2), true, false),
634 OrdinaryAdmission::CountOnly
635 );
636 assert!(ring.records().is_empty());
637 assert_eq!(ring.total_record_count(), 2);
638
639 let mut stop_when_full = LogRecordBuffer::new(0);
640 assert_eq!(
641 stop_when_full.admit_ordinary(record(1), true, true),
642 OrdinaryAdmission::StopBeforeFull
643 );
644 assert!(stop_when_full.records().is_empty());
645 assert_eq!(stop_when_full.total_record_count(), 0);
646 }
647
648 #[test]
649 fn log_buffer_fifo_eviction_keeps_survivor_identity_and_alignment() {
650 let mut buffer = LogRecordBuffer::new(2);
651 assert_eq!(
652 buffer.admit_ordinary(record(1), true, false),
653 OrdinaryAdmission::Inserted
654 );
655 assert_eq!(
656 buffer.admit_ordinary(record(2), true, false),
657 OrdinaryAdmission::Inserted
658 );
659 let survivor = buffer.identities()[1];
660 assert_eq!(
661 buffer.admit_ordinary(record(3), true, false),
662 OrdinaryAdmission::Inserted
663 );
664
665 let identities = buffer.identities();
666 assert_eq!(
667 identities,
668 vec![
669 survivor,
670 LogRecordIdentity::new(3, record(3).date, record(3).time).unwrap()
671 ]
672 );
673 assert_eq!(identities[0].sequence_number(), 2);
674 assert_ne!(identities[0].sequence_number(), 1);
675 for (record, identity) in buffer.records().iter().zip(&identities) {
676 assert_eq!(identity.date(), record.date);
677 assert_eq!(identity.time(), record.time);
678 }
679 }
680
681 #[test]
682 fn log_buffer_wrap_and_eviction_keep_modular_fifo_alignment() {
683 let mut buffer = LogRecordBuffer::new(3);
684 buffer
685 .restore(u32::MAX - 1, VecDeque::new(), false)
686 .unwrap();
687 assert_eq!(
688 buffer.admit_ordinary(record(1), true, false),
689 OrdinaryAdmission::Inserted
690 );
691 assert_eq!(
692 buffer.admit_ordinary(record(2), true, false),
693 OrdinaryAdmission::Inserted
694 );
695 assert_eq!(
696 buffer.admit_ordinary(record(3), true, false),
697 OrdinaryAdmission::Inserted
698 );
699 assert_eq!(
700 buffer
701 .identities()
702 .iter()
703 .map(LogRecordIdentity::sequence_number)
704 .collect::<Vec<_>>(),
705 vec![u64::from(u32::MAX), 1, 2]
706 );
707
708 assert_eq!(
709 buffer.admit_ordinary(record(4), true, false),
710 OrdinaryAdmission::Inserted
711 );
712 assert_eq!(
713 buffer
714 .identities()
715 .iter()
716 .map(LogRecordIdentity::sequence_number)
717 .collect::<Vec<_>>(),
718 vec![1, 2, 3]
719 );
720 assert_eq!(
721 buffer
722 .records()
723 .iter()
724 .map(|record| record.time.hour)
725 .collect::<Vec<_>>(),
726 vec![2, 3, 4]
727 );
728 }
729
730 #[test]
731 fn log_buffer_clear_preserves_counter_and_constructor_resets_it() {
732 let mut buffer = LogRecordBuffer::new(2);
733 assert_eq!(
734 buffer.admit_ordinary(record(1), true, false),
735 OrdinaryAdmission::Inserted
736 );
737 assert_eq!(
738 buffer.admit_ordinary(record(2), true, false),
739 OrdinaryAdmission::Inserted
740 );
741 buffer.clear();
742
743 assert!(buffer.records().is_empty());
744 assert!(buffer.identities().is_empty());
745 assert_eq!(buffer.total_record_count(), 2);
746 assert_eq!(
747 buffer.admit_ordinary(record(3), true, false),
748 OrdinaryAdmission::Inserted
749 );
750 assert_eq!(buffer.identities()[0].sequence_number(), 3);
751 assert_eq!(
752 LogRecordBuffer::<BACnetLogRecord>::new(2).total_record_count(),
753 0
754 );
755 }
756
757 #[test]
758 fn log_buffer_duplicate_timestamps_keep_distinct_sequences() {
759 let mut buffer = LogRecordBuffer::new(2);
760 let duplicate = record(4);
761 assert_eq!(
762 buffer.admit_ordinary(duplicate.clone(), true, false),
763 OrdinaryAdmission::Inserted
764 );
765 assert_eq!(
766 buffer.admit_ordinary(duplicate, true, false),
767 OrdinaryAdmission::Inserted
768 );
769
770 let identities = buffer.identities();
771 assert_eq!(identities[0].date(), identities[1].date());
772 assert_eq!(identities[0].time(), identities[1].time());
773 assert_eq!(identities[0].sequence_number(), 1);
774 assert_eq!(identities[1].sequence_number(), 2);
775 }
776
777 #[test]
778 fn log_buffer_clone_restore_preserves_payloads_and_derived_identities() {
779 let mut buffer = LogRecordBuffer::new(2);
780 assert_eq!(
781 buffer.admit_ordinary(record(1), true, false),
782 OrdinaryAdmission::Inserted
783 );
784 assert_eq!(
785 buffer.admit_ordinary(record(2), true, false),
786 OrdinaryAdmission::Inserted
787 );
788 let records = buffer.records().clone();
789 let identities = buffer.identities();
790 let total = buffer.total_record_count();
791
792 let snapshot = buffer.clone();
793 buffer.clear();
794 buffer = snapshot;
795
796 assert_eq!(buffer.records(), &records);
797 assert_eq!(buffer.identities(), identities);
798 assert_eq!(buffer.total_record_count(), total);
799 }
800}