1use alloc::collections::VecDeque;
27use alloc::sync::Arc;
28
29use super::buffer::{Timestamp, apply_change, written_buffer_size};
30use super::*;
31use crate::durable::{
32 DurableWrites, Event, PendingWrite, SaveTicket, SaveWait, SaveWriter, StageStep,
33};
34
35const MAX_SETTLED: usize = 16;
40
41#[derive(Debug)]
43pub enum AuditBatchStage {
44 Done(ConfirmedAuditNotificationOutcome, bool),
49 Staged(StagedAuditBatch),
53 Busy(SaveWait),
56}
57
58#[derive(Debug)]
60pub struct StagedAuditBatch {
61 token: u64,
62 saved: SaveWait,
63}
64
65impl StagedAuditBatch {
66 pub fn saved(&self) -> SaveWait {
68 self.saved.clone()
69 }
70}
71
72pub(super) enum StagedKind {
73 Batch {
74 token: u64,
75 outcome: ConfirmedAuditNotificationOutcome,
76 changed: bool,
77 },
78 Changes { steps: VecDeque<Step>, taken: usize },
82}
83
84pub(super) struct Step {
87 change: StagedChange,
88 state: Arc<AuditLogSnapshot>,
89}
90
91#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub(super) enum StagedChange {
95 LogEnable(bool),
97 BufferSize(u32),
99 Purge,
101}
102
103pub(super) struct StagedCommit {
105 snapshot: Arc<AuditLogSnapshot>,
106 ticket: SaveTicket,
107 kind: StagedKind,
108 released: Arc<Event>,
111}
112
113pub(super) struct Settled {
115 token: u64,
116 result: Result<(ConfirmedAuditNotificationOutcome, bool), Error>,
117}
118
119pub(super) fn operational_problem() -> Error {
120 Error::protocol(ErrorClass::DEVICE, ErrorCode::OPERATIONAL_PROBLEM)
121}
122
123pub(super) fn writer(
125 oid: ObjectIdentifier,
126 persistence: Arc<dyn AuditLogPersistence>,
127) -> SaveWriter<Arc<AuditLogSnapshot>> {
128 SaveWriter::new(
129 format!("bacnet-al-{}-save", oid.instance_number()),
130 move |snapshot: &Arc<AuditLogSnapshot>| persistence.commit(snapshot),
131 move |snapshot, result| {
132 if let Err(error) = result {
133 log_warn!(
134 audit_log = %oid,
135 generation = snapshot.generation,
136 %error,
137 "Failed to commit Audit Log snapshot"
138 );
139 }
140 },
141 )
142}
143
144impl AuditLogObject {
145 pub(super) fn commit_and_apply(&mut self, snapshot: AuditLogSnapshot) -> Result<(), Error> {
147 validate_snapshot(&snapshot)?;
148 let snapshot = Arc::new(snapshot);
149 self.writer.submit(Arc::clone(&snapshot)).take_outcome()?;
150 self.apply_snapshot(snapshot);
151 Ok(())
152 }
153
154 fn apply_snapshot(&mut self, snapshot: Arc<AuditLogSnapshot>) {
155 let snapshot = Arc::try_unwrap(snapshot).unwrap_or_else(|shared| (*shared).clone());
156 self.generation = snapshot.generation;
157 self.buffer_size = snapshot.capacity;
158 self.log_enable = snapshot.log_enable;
159 self.total_record_count = snapshot.total_record_count;
160 self.buffer = snapshot.records.into();
161 self.completed_receipts = snapshot.completed_receipts;
162 }
163
164 pub(super) fn settle_staged(&mut self) {
167 let Some(commit) = self.staged.take() else {
168 return;
169 };
170 commit.released.set();
171 match commit.kind {
172 StagedKind::Batch {
173 token,
174 outcome,
175 changed,
176 } => {
177 let result = commit.ticket.take_outcome().map(|()| {
178 self.apply_snapshot(commit.snapshot);
179 (outcome, changed)
180 });
181 if self.settled.len() == MAX_SETTLED {
182 self.settled.pop_front();
183 }
184 self.settled.push_back(Settled { token, result });
185 }
186 StagedKind::Changes { .. } => self.drop_staged_write(&commit.ticket),
187 }
188 }
189
190 fn drop_staged_write(&mut self, ticket: &SaveTicket) {
196 if let Some(served) = self.served_to_put_back(ticket) {
197 self.writer.submit_coalescing(Arc::new(served));
198 }
199 }
200
201 fn served_to_put_back(&self, ticket: &SaveTicket) -> Option<AuditLogSnapshot> {
206 if ticket.succeeded() == Some(false) {
207 return None;
208 }
209 let generation = self.generation.checked_add(1)?;
210 let mut served = self.current_snapshot();
211 served.generation = generation;
212 Some(served)
213 }
214
215 fn close_staged(&mut self) {
224 let Some(commit) = self
225 .staged
226 .take_if(|commit| matches!(commit.kind, StagedKind::Changes { .. }))
227 else {
228 return;
229 };
230 commit.released.set();
231 if let Some(served) = self.served_to_put_back(&commit.ticket) {
232 self.writer.put_back(Arc::new(served));
233 }
234 }
235
236 fn make_way(&mut self) -> Option<SaveWait> {
242 let commit = self.staged.as_ref()?;
243 let holds = match commit.kind {
244 StagedKind::Batch { .. } => !commit.ticket.is_done(),
245 StagedKind::Changes { .. } => !commit.ticket.outlived(),
246 };
247 if holds {
248 return Some(match commit.kind {
249 StagedKind::Batch { .. } => commit.ticket.wait(),
250 StagedKind::Changes { .. } => SaveWait::new(Arc::clone(&commit.released)),
251 });
252 }
253 self.settle_staged();
254 None
255 }
256
257 fn queue_staged(&mut self, snapshot: Arc<AuditLogSnapshot>, kind: StagedKind) -> SaveWait {
258 let ticket = self.writer.submit(Arc::clone(&snapshot));
259 let saved = ticket.wait();
260 self.staged = Some(StagedCommit {
261 snapshot,
262 ticket,
263 kind,
264 released: Arc::default(),
265 });
266 saved
267 }
268
269 pub(super) fn stage_notification_batch(
274 &mut self,
275 notifications: &[BACnetAuditNotification],
276 apdu_timeout_ms: u32,
277 receipt: Option<CompletedAuditReceipt>,
278 ) -> Result<AuditBatchStage, Error> {
279 if let Some(wait) = self.make_way() {
280 return Ok(AuditBatchStage::Busy(wait));
281 }
282 if !self.log_enable {
283 return Err(Error::protocol(
284 ErrorClass::SERVICES,
285 ErrorCode::SERVICE_REQUEST_DENIED,
286 ));
287 }
288 if notifications.is_empty() {
289 return Err(Error::OutOfRange(
290 "Audit notification batch must not be empty".into(),
291 ));
292 }
293 if let Some(receipt) = &receipt
294 && receipt::contains(
295 &self.completed_receipts,
296 receipt.key(),
297 receipt.completed_at_unix_millis(),
298 )?
299 {
300 return Ok(AuditBatchStage::Done(
301 ConfirmedAuditNotificationOutcome::Duplicate,
302 false,
303 ));
304 }
305 let mut prospective = self.snapshot_for_next_generation()?;
306 let changed =
307 self.apply_notification_batch(&mut prospective, notifications, apdu_timeout_ms)?;
308 let confirmed = receipt.is_some();
309 let outcome = match receipt {
310 Some(receipt) => receipt::insert(&mut prospective.completed_receipts, receipt)?,
311 None => ConfirmedAuditNotificationOutcome::Stored,
312 };
313 if outcome == ConfirmedAuditNotificationOutcome::Duplicate || (!confirmed && !changed) {
316 return Ok(AuditBatchStage::Done(outcome, false));
317 }
318 let changed = changed && self.buffer_size != 0;
319 validate_snapshot(&prospective)?;
320 self.next_token = self.next_token.wrapping_add(1);
321 let token = self.next_token;
322 let saved = self.queue_staged(
323 Arc::new(prospective),
324 StagedKind::Batch {
325 token,
326 outcome,
327 changed,
328 },
329 );
330 Ok(AuditBatchStage::Staged(StagedAuditBatch { token, saved }))
331 }
332
333 pub(super) fn finish_notification_batch(
336 &mut self,
337 staged: StagedAuditBatch,
338 ) -> Result<(ConfirmedAuditNotificationOutcome, bool), Error> {
339 let ours = self.staged.as_ref().is_some_and(|commit| {
340 matches!(commit.kind, StagedKind::Batch { token, .. } if token == staged.token)
341 });
342 if ours {
343 self.settle_staged();
344 }
345 let index = self
346 .settled
347 .iter()
348 .position(|settled| settled.token == staged.token)
349 .ok_or_else(operational_problem)?;
350 self.settled
351 .remove(index)
352 .expect("index found above")
353 .result
354 }
355
356 pub(super) fn commit_change(&mut self, change: StagedChange) -> Result<(), Error> {
363 if let Some(taken) = self.take_step(change) {
364 return taken.map_err(|_| operational_problem());
365 }
366 let timestamp = match change {
367 StagedChange::BufferSize(_) => None,
368 StagedChange::LogEnable(_) | StagedChange::Purge => Some(self.valid_timestamp()?),
369 };
370 let mut prospective = self.snapshot_for_next_generation()?;
371 apply_change(&mut prospective, change, timestamp)?;
372 self.commit_and_apply(prospective)
373 .map_err(|_| operational_problem())
374 }
375
376 fn take_step(&mut self, change: StagedChange) -> Option<Result<(), Error>> {
381 let served = self.generation;
382 let commit = self.staged.as_mut()?;
383 let generation = commit.snapshot.generation;
384 let StagedKind::Changes { steps, taken } = &mut commit.kind else {
385 return None;
386 };
387 let base = if *taken == 0 {
388 generation.checked_sub(1)
389 } else {
390 Some(generation)
391 };
392 if steps.front().map(|step| step.change) != Some(change) || base != Some(served) {
393 return None;
394 }
395 if let Err(error) = commit.ticket.take_outcome() {
396 self.staged.take().expect("matched above").released.set();
397 return Some(Err(error));
398 }
399 let step = steps.pop_front().expect("matched above");
400 *taken += 1;
401 if steps.is_empty() {
402 self.staged.take().expect("matched above").released.set();
403 }
404 self.apply_snapshot(step.state);
405 Some(Ok(()))
406 }
407
408 pub(super) fn settle_finished(&mut self) -> bool {
411 let Some(commit) = &self.staged else {
412 return false;
413 };
414 let finished = commit.ticket.is_done()
415 && match commit.kind {
416 StagedKind::Batch { .. } => true,
417 StagedKind::Changes { .. } => commit.ticket.outlived(),
418 };
419 if finished {
420 self.settle_staged();
421 }
422 finished
423 }
424
425 #[cfg(all(test, feature = "std"))]
427 pub(super) fn wait_for_commits(&self) {
428 self.writer.wait_idle();
429 }
430
431 fn stage_changes(&mut self, changes: &[StagedChange]) -> StageStep {
436 let Ok(mut state) = self.snapshot_for_next_generation() else {
437 return StageStep::Skip;
438 };
439 let timestamp: Option<Timestamp> = self.valid_timestamp().ok();
440 let mut steps = VecDeque::with_capacity(changes.len());
441 let mut made: Option<StagedChange> = None;
444 for &change in changes {
445 let before = made.map(|change| (change, state.clone()));
446 if apply_change(&mut state, change, timestamp).is_err() {
447 break;
448 }
449 if let Some((change, kept)) = before {
450 steps.push_back(Step {
451 change,
452 state: Arc::new(kept),
453 });
454 }
455 made = Some(change);
456 }
457 let Some(last) = made else {
458 return StageStep::Skip;
459 };
460 if validate_snapshot(&state).is_err() {
461 return StageStep::Skip;
462 }
463 let snapshot = Arc::new(state);
464 steps.push_back(Step {
465 change: last,
466 state: Arc::clone(&snapshot),
467 });
468 StageStep::Staged(self.queue_staged(snapshot, StagedKind::Changes { steps, taken: 0 }))
469 }
470}
471
472fn changes_made(
481 mut log_enable: bool,
482 mut buffer_size: u32,
483 writes: &[PendingWrite],
484) -> Vec<StagedChange> {
485 let mut changes = Vec::new();
486 for write in writes {
487 let change = match (write.property, write.array_index, &write.value) {
488 (PropertyIdentifier::LOG_ENABLE, None, PropertyValue::Boolean(on)) => {
489 if *on == log_enable {
490 continue;
491 }
492 log_enable = *on;
493 StagedChange::LogEnable(*on)
494 }
495 (PropertyIdentifier::LOG_ENABLE, None, PropertyValue::Null) => continue,
496 (PropertyIdentifier::BUFFER_SIZE, None, value) => {
497 match written_buffer_size(log_enable, value) {
498 Ok(size) if size == buffer_size => continue,
499 Ok(size) => {
500 buffer_size = size;
501 StagedChange::BufferSize(size)
502 }
503 Err(_) if *value == PropertyValue::Null && !log_enable => continue,
505 Err(_) => break,
506 }
507 }
508 (PropertyIdentifier::LOG_ENABLE | PropertyIdentifier::BUFFER_SIZE, ..) => break,
509 _ => continue,
510 };
511 changes.push(change);
512 }
513 changes
514}
515
516impl DurableWrites for AuditLogObject {
517 fn stage_write(
518 &mut self,
519 property: PropertyIdentifier,
520 array_index: Option<u32>,
521 value: &PropertyValue,
522 ) -> StageStep {
523 self.stage_writes(&[PendingWrite {
524 property,
525 array_index,
526 value: value.clone(),
527 }])
528 }
529
530 fn stage_writes(&mut self, writes: &[PendingWrite]) -> StageStep {
534 if !writes.iter().any(|write| {
535 matches!(
536 write.property,
537 PropertyIdentifier::LOG_ENABLE | PropertyIdentifier::BUFFER_SIZE
538 )
539 }) {
540 return StageStep::Skip;
541 }
542 if let Some(wait) = self.make_way() {
545 return StageStep::Busy(wait);
546 }
547 let changes = changes_made(self.log_enable, self.buffer_size, writes);
548 self.stage_changes(&changes)
549 }
550
551 fn stage_purge(&mut self) -> StageStep {
552 if let Some(wait) = self.make_way() {
553 return StageStep::Busy(wait);
554 }
555 self.stage_changes(&[StagedChange::Purge])
556 }
557
558 fn commit_purge(&mut self) -> Result<(), Error> {
559 self.purge().map(drop)
560 }
561
562 fn release_staged_write(&mut self, staged: &SaveWait) {
563 if self.staged.as_ref().is_some_and(|commit| {
564 matches!(commit.kind, StagedKind::Changes { .. }) && commit.ticket.issued(staged)
565 }) {
566 self.settle_staged();
567 }
568 }
569
570 fn has_staged_write(&self) -> bool {
571 self.staged.is_some()
572 }
573
574 fn settle_forgotten_writes(&mut self) -> Option<SaveWait> {
579 if self.staged.as_ref().is_some_and(|commit| {
580 commit.ticket.is_done() || matches!(commit.kind, StagedKind::Changes { .. })
581 }) {
582 self.settle_staged();
583 }
584 Some(self.writer.idle())
585 }
586}
587
588impl Drop for AuditLogObject {
589 fn drop(&mut self) {
590 self.close_staged();
591 }
592}