use std::sync::atomic::{AtomicU64, Ordering};
use bacnet_encoding::npdu::NpduAddress;
use bacnet_services::cov::{SubscribeCOVPropertyRequest, SubscribeCOVRequest};
use bacnet_services::cov_multiple::SubscribeCOVPropertyMultipleRequest;
use bacnet_services::file::AtomicWriteFileRequest;
use bacnet_services::list_manipulation::ListElementRequest;
use bacnet_services::object_mgmt::{CreateObjectRequest, DeleteObjectRequest};
use bacnet_services::wpm::WritePropertyAttempt;
use bacnet_services::write_property::WritePropertyRequest;
use bacnet_transport::port::TransportProvenance;
use bacnet_types::MacAddr;
use bacnet_types::enums::{ConfirmedServiceChoice, UnconfirmedServiceChoice};
use bacnet_types::primitives::ObjectIdentifier;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MutationTrust {
Unverified,
VerifiedChannel,
VerifiedRelay,
}
impl MutationTrust {
pub fn from_provenance(provenance: &TransportProvenance) -> Self {
if provenance.is_direct_peer() || provenance.is_hub_channel() {
Self::VerifiedChannel
} else if provenance.is_relayed_origin() {
Self::VerifiedRelay
} else {
Self::Unverified
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum MutationPolicy {
#[default]
Permissive,
DenyAll,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MutationTarget {
WriteProperty(WritePropertyRequest),
WritePropertyMultiple(WritePropertyAttempt),
CreateObject(CreateObjectRequest),
DeleteObject(DeleteObjectRequest),
AddListElement(ListElementRequest),
RemoveListElement(ListElementRequest),
AtomicWriteFile(AtomicWriteFileRequest),
SubscribeCov(SubscribeCOVRequest),
SubscribeCovProperty(SubscribeCOVPropertyRequest),
SubscribeCovPropertyMultiple(SubscribeCOVPropertyMultipleRequest),
WriteGroup(WriteGroupTarget),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WriteGroupTarget {
pub channel: ObjectIdentifier,
pub group_number: u32,
pub channel_number: u16,
pub priority: u8,
pub value: Vec<u8>,
pub inhibit_delay: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MutationService {
Confirmed(ConfirmedServiceChoice),
Unconfirmed(UnconfirmedServiceChoice),
}
impl From<ConfirmedServiceChoice> for MutationService {
fn from(service: ConfirmedServiceChoice) -> Self {
Self::Confirmed(service)
}
}
impl MutationTarget {
pub fn kind(&self) -> &'static str {
match self {
Self::WriteProperty(_) => "write-property",
Self::WritePropertyMultiple(_) => "write-property-multiple",
Self::CreateObject(_) => "create-object",
Self::DeleteObject(_) => "delete-object",
Self::AddListElement(_) => "add-list-element",
Self::RemoveListElement(_) => "remove-list-element",
Self::AtomicWriteFile(_) => "atomic-write-file",
Self::SubscribeCov(_) => "subscribe-cov",
Self::SubscribeCovProperty(_) => "subscribe-cov-property",
Self::SubscribeCovPropertyMultiple(_) => "subscribe-cov-property-multiple",
Self::WriteGroup(_) => "write-group",
}
}
}
#[derive(Clone, PartialEq)]
pub struct MutationAuthorizationContext {
pub source_mac: MacAddr,
pub source_network: Option<NpduAddress>,
pub provenance: TransportProvenance,
pub trust: MutationTrust,
pub invoke_id: Option<u8>,
pub service_choice: MutationService,
pub target: MutationTarget,
}
impl MutationAuthorizationContext {
pub fn principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
crate::policy::originating_principal(&self.provenance, self.source_network.as_ref())
}
pub fn forwarding_peer_principal(&self) -> Option<bacnet_transport::port::ScPrincipal> {
self.provenance.principal()
}
crate::policy::certificate_accessors!();
pub fn direct_sc_identity(&self) -> Option<bacnet_transport::port::DirectScIdentity> {
self.provenance.direct_sc_identity()
}
}
impl std::fmt::Debug for MutationAuthorizationContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MutationAuthorizationContext")
.field("source_mac_len", &self.source_mac.len())
.field(
"source_network",
&self
.source_network
.as_ref()
.map(|source| (source.network, source.mac_address.len())),
)
.field("provenance", &self.provenance)
.field("principal", &self.principal().is_some())
.field("trust", &self.trust)
.field("invoke_id", &self.invoke_id)
.field("service_choice", &self.service_choice)
.field("target_kind", &self.target.kind())
.finish_non_exhaustive()
}
}
pub type DeviceWriteAuthorizer =
std::sync::Arc<dyn Fn(&MutationAuthorizationContext) -> bool + Send + Sync>;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct MutationServiceCounters {
pub allow_total: u64,
pub deny_total: u64,
pub policy_deny_total: u64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct MutationDecisionCounters {
pub write_property: MutationServiceCounters,
pub write_property_multiple: MutationServiceCounters,
pub create_object: MutationServiceCounters,
pub delete_object: MutationServiceCounters,
pub add_list_element: MutationServiceCounters,
pub remove_list_element: MutationServiceCounters,
pub atomic_write_file: MutationServiceCounters,
pub subscribe_cov: MutationServiceCounters,
pub subscribe_cov_property: MutationServiceCounters,
pub subscribe_cov_property_multiple: MutationServiceCounters,
pub write_group: MutationServiceCounters,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum MutationDecision {
Allow,
Deny,
PolicyDeny,
}
#[derive(Default)]
pub(crate) struct MutationDecisions([[AtomicU64; 3]; 11]);
impl MutationDecisions {
pub(crate) fn snapshot(&self) -> MutationDecisionCounters {
let [
write_property,
write_property_multiple,
create_object,
delete_object,
add_list_element,
remove_list_element,
atomic_write_file,
subscribe_cov,
subscribe_cov_property,
subscribe_cov_property_multiple,
write_group,
] = self.0.each_ref().map(|row| {
let [allow_total, deny_total, policy_deny_total] =
row.each_ref().map(|n| n.load(Ordering::Relaxed));
MutationServiceCounters {
allow_total,
deny_total,
policy_deny_total,
}
});
MutationDecisionCounters {
write_property,
write_property_multiple,
create_object,
delete_object,
add_list_element,
remove_list_element,
atomic_write_file,
subscribe_cov,
subscribe_cov_property,
subscribe_cov_property_multiple,
write_group,
}
}
pub(crate) fn record(&self, service: impl Into<MutationService>, decision: MutationDecision) {
let service = match service.into() {
MutationService::Confirmed(service) => service,
MutationService::Unconfirmed(UnconfirmedServiceChoice::WRITE_GROUP) => {
return self.increment(10, decision);
}
MutationService::Unconfirmed(_) => return,
};
let index = match service {
ConfirmedServiceChoice::WRITE_PROPERTY => 0,
ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE => 1,
ConfirmedServiceChoice::CREATE_OBJECT => 2,
ConfirmedServiceChoice::DELETE_OBJECT => 3,
ConfirmedServiceChoice::ADD_LIST_ELEMENT => 4,
ConfirmedServiceChoice::REMOVE_LIST_ELEMENT => 5,
ConfirmedServiceChoice::ATOMIC_WRITE_FILE => 6,
ConfirmedServiceChoice::SUBSCRIBE_COV => 7,
ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY => 8,
ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY_MULTIPLE => 9,
_ => return,
};
self.increment(index, decision);
}
fn increment(&self, index: usize, decision: MutationDecision) {
let increment = |column: usize| {
let _ = self.0[index][column].try_update(Ordering::Relaxed, Ordering::Relaxed, |n| {
Some(n.saturating_add(1))
});
};
match decision {
MutationDecision::Allow => increment(0),
MutationDecision::Deny => increment(1),
MutationDecision::PolicyDeny => {
increment(1);
increment(2);
}
}
}
}
#[cfg(test)]
#[path = "mutation_counter_tests.rs"]
mod counter_tests;