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