1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//! Deciding a WritePropertyMultiple's durable attempts ahead of its handler
//! (#1321).
//!
//! An attempt on an object that saves its state first is staged before the
//! handler runs, so its save can run with the database guard dropped
//! (`durable_writes`). Staging a save for an attempt policy then denies would
//! put a state in storage that no client was told about. So with an
//! authorizer installed, the request asks it about each such attempt first,
//! in wire order among those attempts and after the checks the handler makes
//! on that attempt, and stages only those before the first it denies, or the
//! first whose value doesn't decode, since the request stops there.
//!
//! The handler, reaching one of those attempts under the guard, takes the
//! decision made here instead of asking again. The authorizer is asked once
//! per attempt, and the decision counters count an attempt only when the
//! handler reaches its gate, as before. The price: such an attempt is asked
//! about ahead of earlier attempts the objects don't save first, and one the
//! request never reaches, because an earlier attempt failed, was still put
//! to the authorizer. A request with no such attempt asks nothing ahead and
//! takes no read guard.
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use bacnet_objects::database::ObjectDatabase;
use bacnet_services::wpm::WritePropertyAttempt;
use bacnet_types::enums::PropertyIdentifier;
use tokio::sync::RwLock;
use super::Request;
use crate::handlers;
use crate::mutation::{MutationDecision, MutationPolicy, MutationTarget};
use crate::server::durable_writes::DurableTarget;
/// The decisions made ahead of the handler, in wire order, and how many of
/// them it has taken.
#[derive(Default)]
pub(super) struct DecidedAhead {
decided: Vec<(WritePropertyAttempt, MutationDecision)>,
taken: AtomicUsize,
}
impl DecidedAhead {
/// The attempts of `request` to stage. Without an authorizer to ask,
/// the policy answers alone: a permissive one stages every attempt, as
/// the handler will allow each, and `DenyAll` stages nothing. With one,
/// each attempt is checked and decided in turn under a read guard, which
/// is dropped before anything is staged.
pub(super) async fn targets(
&mut self,
request: &Request<'_>,
db: &Arc<RwLock<ObjectDatabase>>,
) -> Vec<DurableTarget> {
if request.config.mutation_policy == MutationPolicy::DenyAll {
return Vec::new();
}
let attempts = DurableTarget::write_property_multiple(&request.req.service_request);
if attempts.is_empty() {
return Vec::new();
}
if request.config.mutation_authorizer.is_none() {
return DurableTarget::in_object_order(attempts, |_| true);
}
let db = db.read().await;
DurableTarget::in_object_order(attempts, |attempt| self.decide(request, &db, attempt))
}
/// Whether `request` may stage `attempt`, deciding it now and keeping
/// the decision for the handler. An attempt that fails the handler's
/// checks ends the request there, as policy only judges a checked
/// attempt; it is neither decided nor staged.
fn decide(
&mut self,
request: &Request<'_>,
db: &ObjectDatabase,
attempt: &WritePropertyAttempt,
) -> bool {
let reference = &attempt.reference;
let checked = db.get(&reference.object_identifier).is_some_and(|object| {
handlers::gate_and_decode_write(
object,
PropertyIdentifier::from_raw(reference.property_identifier),
reference.property_array_index,
&attempt.value,
)
.is_ok()
});
if !checked {
return false;
}
let decision = request.decide(MutationTarget::WritePropertyMultiple(attempt.clone()));
self.decided.push((attempt.clone(), decision));
matches!(decision, MutationDecision::Allow)
}
/// The decision made ahead for `attempt`, which the handler has
/// reached, if it is the next one kept. The handler reaches attempts in
/// wire order and stops at the first that fails, so the decisions are
/// taken in the order they were made. An attempt with nothing kept is
/// one that objects don't save first; the handler asks for it as usual.
pub(super) fn take(&self, attempt: &WritePropertyAttempt) -> Option<MutationDecision> {
let next = self.taken.load(Ordering::Relaxed);
let (decided, decision) = self.decided.get(next)?;
if decided != attempt {
return None;
}
self.taken.store(next + 1, Ordering::Relaxed);
Some(*decision)
}
}