use std::sync::Arc;
use bacnet_objects::command::CommandRun;
use bacnet_types::enums::{ErrorClass, ErrorCode};
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use tracing::debug;
use super::{RunHost, TakenRuns};
pub(crate) const MAX_RUN_DEPTH: usize = 8;
fn inherited_chain(parent: &CommandRun) -> Arc<[ObjectIdentifier]> {
parent
.chain
.iter()
.copied()
.chain(std::iter::once(parent.source))
.collect()
}
fn may_start(chain: &[ObjectIdentifier], source: ObjectIdentifier) -> bool {
chain.len() <= MAX_RUN_DEPTH && !chain.contains(&source)
}
pub(crate) async fn admit<H: RunHost>(
host: &H,
parent: &CommandRun,
mut runs: TakenRuns,
start: impl FnOnce(TakenRuns),
) -> Result<(), Error> {
if runs.is_empty() {
return Ok(());
}
let chain = inherited_chain(parent);
let refused = runs.split_off(|run| !may_start(&chain, run.source));
for run in runs.iter_mut() {
run.chain = Arc::clone(&chain);
}
start(runs);
if refused.is_empty() {
return Ok(());
}
for run in refused.iter() {
debug!(
source = %parent.source,
target = %run.source,
depth = chain.len(),
"a run would start its own object again or nest too deep; ending it as failed"
);
}
end_refused(host, refused).await;
Err(Error::protocol(ErrorClass::OBJECT, ErrorCode::BUSY))
}
async fn end_refused<H: RunHost>(host: &H, refused: TakenRuns) {
let mut db = host.database().write().await;
let ended = refused.end(&mut db);
for source in &ended {
host.committed(&db, *source).await;
}
drop(db);
for source in &ended {
host.report(*source).await;
}
}
#[cfg(test)]
mod tests {
use super::*;
use bacnet_objects::command::RunPlan;
use bacnet_types::enums::ObjectType;
fn ch(instance: u32) -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::CHANNEL, instance).unwrap()
}
fn run(source: ObjectIdentifier, chain: &[ObjectIdentifier]) -> CommandRun {
CommandRun {
source,
generation: 1,
plan: RunPlan::Actions(Vec::new()),
chain: chain.into(),
}
}
#[test]
fn a_run_chain_refuses_its_own_objects_and_more_than_eight_ancestors() {
let top = run(ch(1), &[]);
let chain = inherited_chain(&top);
assert_eq!(chain.as_ref(), [ch(1)]);
assert!(may_start(&chain, ch(2)));
assert!(!may_start(&chain, ch(1)));
let chain = inherited_chain(&run(ch(2), &chain));
assert_eq!(chain.as_ref(), [ch(1), ch(2)]);
assert!(!may_start(&chain, ch(1)));
assert!(may_start(&chain, ch(3)));
let eight: Vec<_> = (1..=8).map(ch).collect();
assert!(may_start(&eight, ch(9)));
let nine: Vec<_> = (1..=9).map(ch).collect();
assert!(!may_start(&nine, ch(10)));
}
}