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 {
52 fn record_count(&self) -> usize;
54
55 fn encode_record(&self, index: usize, buf: &mut BytesMut);
62}
63
64pub const ANY_VALUE_MAX_OCTETS: usize = 256;
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct LogRecordIdentity {
79 sequence_number: u64,
80 date: Date,
81 time: Time,
82}
83
84impl LogRecordIdentity {
85 pub fn new(sequence_number: u64, date: Date, time: Time) -> Option<Self> {
87 (sequence_number != 0).then_some(Self {
88 sequence_number,
89 date,
90 time,
91 })
92 }
93
94 pub fn sequence_number(&self) -> u64 {
96 self.sequence_number
97 }
98
99 pub fn date(&self) -> Date {
101 self.date
102 }
103
104 pub fn time(&self) -> Time {
106 self.time
107 }
108}
109
110pub(crate) trait ResidentLogRecord: Clone {
114 fn timestamp(&self) -> (Date, Time);
116 fn log_status(date: Date, time: Time, status: LogStatus) -> Self;
118 fn encode(&self, buf: &mut BytesMut) -> Result<(), Error>;
120}
121
122impl ResidentLogRecord for BACnetLogRecord {
123 fn timestamp(&self) -> (Date, Time) {
124 (self.date, self.time)
125 }
126
127 fn log_status(date: Date, time: Time, status: LogStatus) -> Self {
128 Self {
129 date,
130 time,
131 log_datum: LogDatum::LogStatus(status),
132 status_flags: None,
133 }
134 }
135
136 fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
137 encode_log_record(self, buf)
138 }
139}
140
141impl ResidentLogRecord for BACnetEventLogRecord {
142 fn timestamp(&self) -> (Date, Time) {
143 (self.date, self.time)
144 }
145
146 fn log_status(date: Date, time: Time, status: LogStatus) -> Self {
147 Self {
148 date,
149 time,
150 log_datum: EventLogDatum::LogStatus(status),
151 }
152 }
153
154 fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
155 encode_event_log_record(self, buf)
156 }
157}
158
159impl ResidentLogRecord for BACnetLogMultipleRecord {
160 fn timestamp(&self) -> (Date, Time) {
161 (self.date, self.time)
162 }
163
164 fn log_status(date: Date, time: Time, status: LogStatus) -> Self {
165 Self {
166 date,
167 time,
168 log_data: LogData::LogStatus(status),
169 }
170 }
171
172 fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
173 encode_log_multiple_record(self, buf)
174 }
175}
176
177#[derive(Clone)]
178pub(crate) struct LogRecordBuffer<R = BACnetLogRecord> {
179 capacity: u32,
180 records: VecDeque<R>,
181 total_record_count: u32,
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185pub(crate) enum OrdinaryAdmission {
186 IgnoredDisabled,
187 Inserted,
188 CountOnly,
189 StopBeforeFull,
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub(crate) enum ForcedAdmission {
194 Inserted,
195 CountOnly,
196}
197
198impl<R: ResidentLogRecord> LogRecordBuffer<R> {
199 pub(crate) fn new(capacity: u32) -> Self {
200 Self {
201 capacity,
202 records: VecDeque::new(),
203 total_record_count: 0,
204 }
205 }
206
207 pub(crate) fn admit_ordinary(
208 &mut self,
209 record: R,
210 enabled: bool,
211 stop_when_full: bool,
212 ) -> OrdinaryAdmission {
213 if !enabled {
214 return OrdinaryAdmission::IgnoredDisabled;
215 }
216 if stop_when_full && self.next_record_would_fill() {
217 return OrdinaryAdmission::StopBeforeFull;
218 }
219
220 match self.insert_counted(record) {
221 ForcedAdmission::Inserted => OrdinaryAdmission::Inserted,
222 ForcedAdmission::CountOnly => OrdinaryAdmission::CountOnly,
223 }
224 }
225
226 pub(crate) fn insert_forced(&mut self, record: R) -> ForcedAdmission {
227 self.insert_counted(record)
228 }
229
230 pub(crate) fn is_full(&self) -> bool {
231 self.records.len() >= self.capacity as usize
232 }
233
234 pub(crate) fn next_record_would_fill_positive_capacity(&self) -> bool {
235 self.capacity > 0 && self.next_record_would_fill()
236 }
237
238 pub(crate) fn records(&self) -> &VecDeque<R> {
239 &self.records
240 }
241
242 pub(crate) fn total_record_count(&self) -> u32 {
243 self.total_record_count
244 }
245
246 pub(crate) fn capacity(&self) -> u32 {
247 self.capacity
248 }
249
250 pub(crate) fn clear(&mut self) {
251 self.records.clear();
252 }
253
254 pub(crate) fn identities(&self) -> Vec<LogRecordIdentity> {
255 if self.records.is_empty() {
256 return Vec::new();
257 }
258
259 debug_assert_ne!(self.total_record_count, 0);
260 let mut sequence_number = self.total_record_count;
261 for _ in 1..self.records.len() {
262 sequence_number = previous_sequence(sequence_number);
263 }
264
265 self.records
266 .iter()
267 .map(|record| {
268 let (date, time) = record.timestamp();
269 let identity = LogRecordIdentity {
270 sequence_number: u64::from(sequence_number),
271 date,
272 time,
273 };
274 sequence_number = next_sequence(sequence_number);
275 identity
276 })
277 .collect()
278 }
279
280 fn next_record_would_fill(&self) -> bool {
281 self.capacity == 0 || self.records.len().saturating_add(1) >= self.capacity as usize
282 }
283
284 fn insert_counted(&mut self, record: R) -> ForcedAdmission {
285 self.total_record_count = next_sequence(self.total_record_count);
286 if self.capacity == 0 {
287 return ForcedAdmission::CountOnly;
288 }
289 if self.is_full() {
290 self.records.pop_front();
291 }
292 self.records.push_back(record);
293 ForcedAdmission::Inserted
294 }
295
296 #[cfg(test)]
297 pub(crate) fn set_total_record_count_for_test(&mut self, total_record_count: u32) {
298 debug_assert!(self.records.is_empty());
299 self.total_record_count = total_record_count;
300 }
301}
302
303impl<R: ResidentLogRecord> LogBufferRecords for LogRecordBuffer<R> {
304 fn record_count(&self) -> usize {
305 self.records.len()
306 }
307
308 fn encode_record(&self, index: usize, buf: &mut BytesMut) {
309 self.records[index]
313 .encode(buf)
314 .expect("every resident log record encodes");
315 }
316}
317
318fn next_sequence(sequence_number: u32) -> u32 {
319 if sequence_number == u32::MAX {
320 1
321 } else {
322 sequence_number + 1
323 }
324}
325
326fn previous_sequence(sequence_number: u32) -> u32 {
327 if sequence_number == 1 {
328 u32::MAX
329 } else {
330 sequence_number - 1
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337 use bacnet_types::constructed::{BACnetLogRecord, LogDatum};
338 use bacnet_types::primitives::{Date, Time};
339
340 fn record(hour: u8) -> BACnetLogRecord {
341 BACnetLogRecord {
342 date: Date {
343 year: 126,
344 month: 8,
345 day: 31,
346 day_of_week: 1,
347 },
348 time: Time {
349 hour,
350 minute: 0,
351 second: 0,
352 hundredths: 0,
353 },
354 log_datum: LogDatum::UnsignedValue(hour as u64),
355 status_flags: None,
356 }
357 }
358
359 #[test]
360 fn log_buffer_assigns_one_and_wraps_max_without_zero() {
361 let mut buffer = LogRecordBuffer::new(2);
362 assert_eq!(buffer.total_record_count(), 0);
363 assert!(LogRecordIdentity::new(0, record(0).date, record(0).time).is_none());
364
365 assert_eq!(
366 buffer.admit_ordinary(record(1), true, false),
367 OrdinaryAdmission::Inserted
368 );
369 assert_eq!(buffer.identities()[0].sequence_number(), 1);
370
371 buffer.clear();
372 buffer.set_total_record_count_for_test(u32::MAX);
373 assert_eq!(
374 buffer.admit_ordinary(record(2), true, false),
375 OrdinaryAdmission::Inserted
376 );
377 assert_eq!(buffer.total_record_count(), 1);
378 assert_eq!(buffer.identities()[0].sequence_number(), 1);
379 }
380
381 #[test]
382 fn log_buffer_rejections_do_not_consume_sequence() {
383 let mut buffer = LogRecordBuffer::new(1);
384 assert_eq!(
385 buffer.admit_ordinary(record(1), false, false),
386 OrdinaryAdmission::IgnoredDisabled
387 );
388 assert_eq!(buffer.total_record_count(), 0);
389
390 assert_eq!(
391 buffer.admit_ordinary(record(2), true, true),
392 OrdinaryAdmission::StopBeforeFull
393 );
394 assert_eq!(buffer.total_record_count(), 0);
395 assert!(buffer.identities().is_empty());
396 }
397
398 #[test]
399 fn log_buffer_zero_capacity_counts_without_retaining() {
400 let mut ring = LogRecordBuffer::new(0);
401 assert_eq!(
402 ring.admit_ordinary(record(1), true, false),
403 OrdinaryAdmission::CountOnly
404 );
405 assert_eq!(
406 ring.admit_ordinary(record(2), true, false),
407 OrdinaryAdmission::CountOnly
408 );
409 assert!(ring.records().is_empty());
410 assert_eq!(ring.total_record_count(), 2);
411
412 let mut stop_when_full = LogRecordBuffer::new(0);
413 assert_eq!(
414 stop_when_full.admit_ordinary(record(1), true, true),
415 OrdinaryAdmission::StopBeforeFull
416 );
417 assert!(stop_when_full.records().is_empty());
418 assert_eq!(stop_when_full.total_record_count(), 0);
419 }
420
421 #[test]
422 fn log_buffer_fifo_eviction_keeps_survivor_identity_and_alignment() {
423 let mut buffer = LogRecordBuffer::new(2);
424 assert_eq!(
425 buffer.admit_ordinary(record(1), true, false),
426 OrdinaryAdmission::Inserted
427 );
428 assert_eq!(
429 buffer.admit_ordinary(record(2), true, false),
430 OrdinaryAdmission::Inserted
431 );
432 let survivor = buffer.identities()[1];
433 assert_eq!(
434 buffer.admit_ordinary(record(3), true, false),
435 OrdinaryAdmission::Inserted
436 );
437
438 let identities = buffer.identities();
439 assert_eq!(
440 identities,
441 vec![
442 survivor,
443 LogRecordIdentity::new(3, record(3).date, record(3).time).unwrap()
444 ]
445 );
446 assert_eq!(identities[0].sequence_number(), 2);
447 assert_ne!(identities[0].sequence_number(), 1);
448 for (record, identity) in buffer.records().iter().zip(&identities) {
449 assert_eq!(identity.date(), record.date);
450 assert_eq!(identity.time(), record.time);
451 }
452 }
453
454 #[test]
455 fn log_buffer_wrap_and_eviction_keep_modular_fifo_alignment() {
456 let mut buffer = LogRecordBuffer::new(3);
457 buffer.set_total_record_count_for_test(u32::MAX - 1);
458 assert_eq!(
459 buffer.admit_ordinary(record(1), true, false),
460 OrdinaryAdmission::Inserted
461 );
462 assert_eq!(
463 buffer.admit_ordinary(record(2), true, false),
464 OrdinaryAdmission::Inserted
465 );
466 assert_eq!(
467 buffer.admit_ordinary(record(3), true, false),
468 OrdinaryAdmission::Inserted
469 );
470 assert_eq!(
471 buffer
472 .identities()
473 .iter()
474 .map(LogRecordIdentity::sequence_number)
475 .collect::<Vec<_>>(),
476 vec![u64::from(u32::MAX), 1, 2]
477 );
478
479 assert_eq!(
480 buffer.admit_ordinary(record(4), true, false),
481 OrdinaryAdmission::Inserted
482 );
483 assert_eq!(
484 buffer
485 .identities()
486 .iter()
487 .map(LogRecordIdentity::sequence_number)
488 .collect::<Vec<_>>(),
489 vec![1, 2, 3]
490 );
491 assert_eq!(
492 buffer
493 .records()
494 .iter()
495 .map(|record| record.time.hour)
496 .collect::<Vec<_>>(),
497 vec![2, 3, 4]
498 );
499 }
500
501 #[test]
502 fn log_buffer_clear_preserves_counter_and_constructor_resets_it() {
503 let mut buffer = LogRecordBuffer::new(2);
504 assert_eq!(
505 buffer.admit_ordinary(record(1), true, false),
506 OrdinaryAdmission::Inserted
507 );
508 assert_eq!(
509 buffer.admit_ordinary(record(2), true, false),
510 OrdinaryAdmission::Inserted
511 );
512 buffer.clear();
513
514 assert!(buffer.records().is_empty());
515 assert!(buffer.identities().is_empty());
516 assert_eq!(buffer.total_record_count(), 2);
517 assert_eq!(
518 buffer.admit_ordinary(record(3), true, false),
519 OrdinaryAdmission::Inserted
520 );
521 assert_eq!(buffer.identities()[0].sequence_number(), 3);
522 assert_eq!(
523 LogRecordBuffer::<BACnetLogRecord>::new(2).total_record_count(),
524 0
525 );
526 }
527
528 #[test]
529 fn log_buffer_duplicate_timestamps_keep_distinct_sequences() {
530 let mut buffer = LogRecordBuffer::new(2);
531 let duplicate = record(4);
532 assert_eq!(
533 buffer.admit_ordinary(duplicate.clone(), true, false),
534 OrdinaryAdmission::Inserted
535 );
536 assert_eq!(
537 buffer.admit_ordinary(duplicate, true, false),
538 OrdinaryAdmission::Inserted
539 );
540
541 let identities = buffer.identities();
542 assert_eq!(identities[0].date(), identities[1].date());
543 assert_eq!(identities[0].time(), identities[1].time());
544 assert_eq!(identities[0].sequence_number(), 1);
545 assert_eq!(identities[1].sequence_number(), 2);
546 }
547
548 #[test]
549 fn log_buffer_clone_restore_preserves_payloads_and_derived_identities() {
550 let mut buffer = LogRecordBuffer::new(2);
551 assert_eq!(
552 buffer.admit_ordinary(record(1), true, false),
553 OrdinaryAdmission::Inserted
554 );
555 assert_eq!(
556 buffer.admit_ordinary(record(2), true, false),
557 OrdinaryAdmission::Inserted
558 );
559 let records = buffer.records().clone();
560 let identities = buffer.identities();
561 let total = buffer.total_record_count();
562
563 let snapshot = buffer.clone();
564 buffer.clear();
565 buffer = snapshot;
566
567 assert_eq!(buffer.records(), &records);
568 assert_eq!(buffer.identities(), identities);
569 assert_eq!(buffer.total_record_count(), total);
570 }
571}