use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use bacnet_objects::database::ObjectDatabase;
use bacnet_services::wpm::{
WritePropertyAttempt, WritePropertyMultipleCursor, WritePropertyMultipleEvent,
};
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;
#[derive(Default)]
pub(super) struct DecidedAhead {
asked: bool,
decided: Vec<(WritePropertyAttempt, MutationDecision)>,
taken: AtomicUsize,
}
impl DecidedAhead {
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 service_data = &request.req.service_request;
if request.config.mutation_decider().is_none() {
let attempts = DurableTarget::write_property_multiple(service_data);
return DurableTarget::in_object_order(attempts, |_| true);
}
self.asked = true;
let checked = checked_attempts(&*db.read().await, service_data);
if !checked.is_empty() {
let targets = checked
.iter()
.cloned()
.map(MutationTarget::WritePropertyMultiple)
.collect();
let decisions = request.decide(targets).await;
self.decided = checked.into_iter().zip(decisions).collect();
}
let attempts = DurableTarget::write_property_multiple(service_data);
if attempts.is_empty() {
return Vec::new();
}
let mut decided = self.decided.iter();
DurableTarget::in_object_order(attempts, |attempt| {
decided
.by_ref()
.find(|(kept, _)| kept == attempt)
.is_some_and(|(_, decision)| *decision == MutationDecision::Allow)
})
}
pub(super) fn take(&self, attempt: &WritePropertyAttempt) -> Option<MutationDecision> {
if !self.asked {
return None;
}
let next = self.taken.load(Ordering::Relaxed);
match self.decided.get(next) {
Some((decided, decision)) if decided == attempt => {
self.taken.store(next + 1, Ordering::Relaxed);
Some(*decision)
}
_ => Some(MutationDecision::Deny),
}
}
}
fn checked_attempts(db: &ObjectDatabase, service_data: &[u8]) -> Vec<WritePropertyAttempt> {
let mut cursor = WritePropertyMultipleCursor::new(service_data);
let mut checked = Vec::new();
while let Ok(Some(event)) = cursor.next_event() {
let WritePropertyMultipleEvent::WriteAttempt(attempt) = event else {
continue;
};
let reference = &attempt.reference;
let passes = 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 !passes {
break;
}
checked.push(attempt);
}
checked
}