1use std::collections::VecDeque;
27use std::sync::Arc;
28
29use super::buffer::{apply_change, written_buffer_size, Timestamp};
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 {
121 class: ErrorClass::DEVICE.to_raw() as u32,
122 code: ErrorCode::OPERATIONAL_PROBLEM.to_raw() as u32,
123 }
124}
125
126pub(super) fn writer(
128 oid: ObjectIdentifier,
129 persistence: Arc<dyn AuditLogPersistence>,
130) -> SaveWriter<Arc<AuditLogSnapshot>> {
131 SaveWriter::new(
132 format!("bacnet-al-{}-save", oid.instance_number()),
133 move |snapshot: &Arc<AuditLogSnapshot>| persistence.commit(snapshot),
134 move |snapshot, result| {
135 if let Err(error) = result {
136 tracing::warn!(
137 audit_log = %oid,
138 generation = snapshot.generation,
139 %error,
140 "Failed to commit Audit Log snapshot"
141 );
142 }
143 },
144 )
145}
146
147impl AuditLogObject {
148 pub(super) fn commit_and_apply(&mut self, snapshot: AuditLogSnapshot) -> Result<(), Error> {
150 validate_snapshot(&snapshot)?;
151 let snapshot = Arc::new(snapshot);
152 self.writer.submit(Arc::clone(&snapshot)).take_outcome()?;
153 self.apply_snapshot(snapshot);
154 Ok(())
155 }
156
157 fn apply_snapshot(&mut self, snapshot: Arc<AuditLogSnapshot>) {
158 let snapshot = Arc::try_unwrap(snapshot).unwrap_or_else(|shared| (*shared).clone());
159 self.generation = snapshot.generation;
160 self.buffer_size = snapshot.capacity;
161 self.log_enable = snapshot.log_enable;
162 self.total_record_count = snapshot.total_record_count;
163 self.buffer = snapshot.records.into();
164 self.completed_receipts = snapshot.completed_receipts;
165 }
166
167 pub(super) fn settle_staged(&mut self) {
170 let Some(commit) = self.staged.take() else {
171 return;
172 };
173 commit.released.set();
174 match commit.kind {
175 StagedKind::Batch {
176 token,
177 outcome,
178 changed,
179 } => {
180 let result = commit.ticket.take_outcome().map(|()| {
181 self.apply_snapshot(commit.snapshot);
182 (outcome, changed)
183 });
184 if self.settled.len() == MAX_SETTLED {
185 self.settled.pop_front();
186 }
187 self.settled.push_back(Settled { token, result });
188 }
189 StagedKind::Changes { .. } => self.drop_staged_write(&commit.ticket),
190 }
191 }
192
193 fn drop_staged_write(&mut self, ticket: &SaveTicket) {
199 if let Some(served) = self.served_to_put_back(ticket) {
200 self.writer.submit_coalescing(Arc::new(served));
201 }
202 }
203
204 fn served_to_put_back(&self, ticket: &SaveTicket) -> Option<AuditLogSnapshot> {
209 if ticket.succeeded() == Some(false) {
210 return None;
211 }
212 let generation = self.generation.checked_add(1)?;
213 let mut served = self.current_snapshot();
214 served.generation = generation;
215 Some(served)
216 }
217
218 fn close_staged(&mut self) {
227 let Some(commit) = self
228 .staged
229 .take_if(|commit| matches!(commit.kind, StagedKind::Changes { .. }))
230 else {
231 return;
232 };
233 commit.released.set();
234 if let Some(served) = self.served_to_put_back(&commit.ticket) {
235 self.writer.put_back(Arc::new(served));
236 }
237 }
238
239 fn make_way(&mut self) -> Option<SaveWait> {
245 let commit = self.staged.as_ref()?;
246 let holds = match commit.kind {
247 StagedKind::Batch { .. } => !commit.ticket.is_done(),
248 StagedKind::Changes { .. } => !commit.ticket.outlived(),
249 };
250 if holds {
251 return Some(match commit.kind {
252 StagedKind::Batch { .. } => commit.ticket.wait(),
253 StagedKind::Changes { .. } => SaveWait::new(Arc::clone(&commit.released)),
254 });
255 }
256 self.settle_staged();
257 None
258 }
259
260 fn queue_staged(&mut self, snapshot: Arc<AuditLogSnapshot>, kind: StagedKind) -> SaveWait {
261 let ticket = self.writer.submit(Arc::clone(&snapshot));
262 let saved = ticket.wait();
263 self.staged = Some(StagedCommit {
264 snapshot,
265 ticket,
266 kind,
267 released: Arc::default(),
268 });
269 saved
270 }
271
272 pub(super) fn stage_notification_batch(
277 &mut self,
278 notifications: &[BACnetAuditNotification],
279 apdu_timeout_ms: u32,
280 receipt: Option<CompletedAuditReceipt>,
281 ) -> Result<AuditBatchStage, Error> {
282 if let Some(wait) = self.make_way() {
283 return Ok(AuditBatchStage::Busy(wait));
284 }
285 if !self.log_enable {
286 return Err(Error::Protocol {
287 class: ErrorClass::SERVICES.to_raw() as u32,
288 code: ErrorCode::SERVICE_REQUEST_DENIED.to_raw() as u32,
289 });
290 }
291 if notifications.is_empty() {
292 return Err(Error::OutOfRange(
293 "Audit notification batch must not be empty".into(),
294 ));
295 }
296 if let Some(receipt) = &receipt {
297 if receipt::contains(
298 &self.completed_receipts,
299 receipt.key(),
300 receipt.completed_at_unix_millis(),
301 )? {
302 return Ok(AuditBatchStage::Done(
303 ConfirmedAuditNotificationOutcome::Duplicate,
304 false,
305 ));
306 }
307 }
308 let mut prospective = self.snapshot_for_next_generation()?;
309 let changed =
310 self.apply_notification_batch(&mut prospective, notifications, apdu_timeout_ms)?;
311 let confirmed = receipt.is_some();
312 let outcome = match receipt {
313 Some(receipt) => receipt::insert(&mut prospective.completed_receipts, receipt)?,
314 None => ConfirmedAuditNotificationOutcome::Stored,
315 };
316 if outcome == ConfirmedAuditNotificationOutcome::Duplicate || (!confirmed && !changed) {
319 return Ok(AuditBatchStage::Done(outcome, false));
320 }
321 let changed = changed && self.buffer_size != 0;
322 validate_snapshot(&prospective)?;
323 self.next_token = self.next_token.wrapping_add(1);
324 let token = self.next_token;
325 let saved = self.queue_staged(
326 Arc::new(prospective),
327 StagedKind::Batch {
328 token,
329 outcome,
330 changed,
331 },
332 );
333 Ok(AuditBatchStage::Staged(StagedAuditBatch { token, saved }))
334 }
335
336 pub(super) fn finish_notification_batch(
339 &mut self,
340 staged: StagedAuditBatch,
341 ) -> Result<(ConfirmedAuditNotificationOutcome, bool), Error> {
342 let ours = self.staged.as_ref().is_some_and(|commit| {
343 matches!(commit.kind, StagedKind::Batch { token, .. } if token == staged.token)
344 });
345 if ours {
346 self.settle_staged();
347 }
348 let index = self
349 .settled
350 .iter()
351 .position(|settled| settled.token == staged.token)
352 .ok_or_else(operational_problem)?;
353 self.settled
354 .remove(index)
355 .expect("index found above")
356 .result
357 }
358
359 pub(super) fn commit_change(&mut self, change: StagedChange) -> Result<(), Error> {
366 if let Some(taken) = self.take_step(change) {
367 return taken.map_err(|_| operational_problem());
368 }
369 let timestamp = match change {
370 StagedChange::BufferSize(_) => None,
371 StagedChange::LogEnable(_) | StagedChange::Purge => Some(self.valid_timestamp()?),
372 };
373 let mut prospective = self.snapshot_for_next_generation()?;
374 apply_change(&mut prospective, change, timestamp)?;
375 self.commit_and_apply(prospective)
376 .map_err(|_| operational_problem())
377 }
378
379 fn take_step(&mut self, change: StagedChange) -> Option<Result<(), Error>> {
384 let served = self.generation;
385 let commit = self.staged.as_mut()?;
386 let generation = commit.snapshot.generation;
387 let StagedKind::Changes { steps, taken } = &mut commit.kind else {
388 return None;
389 };
390 let base = if *taken == 0 {
391 generation.checked_sub(1)
392 } else {
393 Some(generation)
394 };
395 if steps.front().map(|step| step.change) != Some(change) || base != Some(served) {
396 return None;
397 }
398 if let Err(error) = commit.ticket.take_outcome() {
399 self.staged.take().expect("matched above").released.set();
400 return Some(Err(error));
401 }
402 let step = steps.pop_front().expect("matched above");
403 *taken += 1;
404 if steps.is_empty() {
405 self.staged.take().expect("matched above").released.set();
406 }
407 self.apply_snapshot(step.state);
408 Some(Ok(()))
409 }
410
411 pub(super) fn settle_finished(&mut self) -> bool {
414 let Some(commit) = &self.staged else {
415 return false;
416 };
417 let finished = commit.ticket.is_done()
418 && match commit.kind {
419 StagedKind::Batch { .. } => true,
420 StagedKind::Changes { .. } => commit.ticket.outlived(),
421 };
422 if finished {
423 self.settle_staged();
424 }
425 finished
426 }
427
428 #[cfg(test)]
430 pub(super) fn wait_for_commits(&self) {
431 self.writer.wait_idle();
432 }
433
434 fn stage_changes(&mut self, changes: &[StagedChange]) -> StageStep {
439 let Ok(mut state) = self.snapshot_for_next_generation() else {
440 return StageStep::Skip;
441 };
442 let timestamp: Option<Timestamp> = self.valid_timestamp().ok();
443 let mut steps = VecDeque::with_capacity(changes.len());
444 let mut made: Option<StagedChange> = None;
447 for &change in changes {
448 let before = made.map(|change| (change, state.clone()));
449 if apply_change(&mut state, change, timestamp).is_err() {
450 break;
451 }
452 if let Some((change, kept)) = before {
453 steps.push_back(Step {
454 change,
455 state: Arc::new(kept),
456 });
457 }
458 made = Some(change);
459 }
460 let Some(last) = made else {
461 return StageStep::Skip;
462 };
463 if validate_snapshot(&state).is_err() {
464 return StageStep::Skip;
465 }
466 let snapshot = Arc::new(state);
467 steps.push_back(Step {
468 change: last,
469 state: Arc::clone(&snapshot),
470 });
471 StageStep::Staged(self.queue_staged(snapshot, StagedKind::Changes { steps, taken: 0 }))
472 }
473}
474
475fn changes_made(
484 mut log_enable: bool,
485 mut buffer_size: u32,
486 writes: &[PendingWrite],
487) -> Vec<StagedChange> {
488 let mut changes = Vec::new();
489 for write in writes {
490 let change = match (write.property, write.array_index, &write.value) {
491 (PropertyIdentifier::LOG_ENABLE, None, PropertyValue::Boolean(on)) => {
492 if *on == log_enable {
493 continue;
494 }
495 log_enable = *on;
496 StagedChange::LogEnable(*on)
497 }
498 (PropertyIdentifier::LOG_ENABLE, None, PropertyValue::Null) => continue,
499 (PropertyIdentifier::BUFFER_SIZE, None, value) => {
500 match written_buffer_size(log_enable, value) {
501 Ok(size) if size == buffer_size => continue,
502 Ok(size) => {
503 buffer_size = size;
504 StagedChange::BufferSize(size)
505 }
506 Err(_) if *value == PropertyValue::Null && !log_enable => continue,
508 Err(_) => break,
509 }
510 }
511 (PropertyIdentifier::LOG_ENABLE | PropertyIdentifier::BUFFER_SIZE, ..) => break,
512 _ => continue,
513 };
514 changes.push(change);
515 }
516 changes
517}
518
519impl DurableWrites for AuditLogObject {
520 fn stage_write(
521 &mut self,
522 property: PropertyIdentifier,
523 array_index: Option<u32>,
524 value: &PropertyValue,
525 ) -> StageStep {
526 self.stage_writes(&[PendingWrite {
527 property,
528 array_index,
529 value: value.clone(),
530 }])
531 }
532
533 fn stage_writes(&mut self, writes: &[PendingWrite]) -> StageStep {
537 if !writes.iter().any(|write| {
538 matches!(
539 write.property,
540 PropertyIdentifier::LOG_ENABLE | PropertyIdentifier::BUFFER_SIZE
541 )
542 }) {
543 return StageStep::Skip;
544 }
545 if let Some(wait) = self.make_way() {
548 return StageStep::Busy(wait);
549 }
550 let changes = changes_made(self.log_enable, self.buffer_size, writes);
551 self.stage_changes(&changes)
552 }
553
554 fn stage_purge(&mut self) -> StageStep {
555 if let Some(wait) = self.make_way() {
556 return StageStep::Busy(wait);
557 }
558 self.stage_changes(&[StagedChange::Purge])
559 }
560
561 fn commit_purge(&mut self) -> Result<(), Error> {
562 self.purge().map(drop)
563 }
564
565 fn release_staged_write(&mut self, staged: &SaveWait) {
566 if self.staged.as_ref().is_some_and(|commit| {
567 matches!(commit.kind, StagedKind::Changes { .. }) && commit.ticket.issued(staged)
568 }) {
569 self.settle_staged();
570 }
571 }
572
573 fn has_staged_write(&self) -> bool {
574 self.staged.is_some()
575 }
576
577 fn settle_forgotten_writes(&mut self) -> Option<SaveWait> {
582 if self.staged.as_ref().is_some_and(|commit| {
583 commit.ticket.is_done() || matches!(commit.kind, StagedKind::Changes { .. })
584 }) {
585 self.settle_staged();
586 }
587 Some(self.writer.idle())
588 }
589}
590
591impl Drop for AuditLogObject {
592 fn drop(&mut self) {
593 self.close_staged();
594 }
595}