use alloc::sync::Arc;
use bacnet_types::bip_port::{BbmdChanges, BbmdTables, IpMulticast};
use bacnet_types::enums::PropertyIdentifier as P;
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use super::NetworkPortObject;
use super::bip_mode::LiveMode;
use crate::common;
#[derive(Clone, Default)]
pub(super) struct Multicast {
view: Option<Arc<dyn IpMulticast>>,
pending: Option<[u8; 4]>,
}
impl Multicast {
pub(super) fn lent(view: Option<Arc<dyn IpMulticast>>) -> Self {
Self {
view,
pending: None,
}
}
pub(super) fn is_pending(&self) -> bool {
self.pending.is_some()
}
pub(super) fn clear_pending(&mut self) {
self.pending = None;
}
}
impl NetworkPortObject {
pub(super) fn serves_multicast(&self) -> bool {
self.multicast.view.is_some()
&& self
.ip
.as_ref()
.is_some_and(super::ip_config::IpConfig::is_ipv4)
}
pub(super) fn group_bbmd(&self) -> Option<&Arc<dyn BbmdTables>> {
match &self.mode {
LiveMode::Bbmd(tables) if tables.changes_multicast_group() => Some(tables),
_ => None,
}
}
pub(super) fn activates_multicast(&self) -> bool {
let through_bbmd = self.group_bbmd().is_some_and(|tables| tables.can_change());
self.activation
&& (through_bbmd
|| self
.multicast
.view
.as_ref()
.is_some_and(|view| view.can_change()))
}
pub(super) fn read_multicast(&self, property: P) -> Option<Result<PropertyValue, Error>> {
if property != P::BACNET_IP_MULTICAST_ADDRESS {
return None;
}
let view = self
.multicast
.view
.as_ref()
.filter(|_| self.serves_multicast());
Some(match view {
Some(view) => {
let group = self
.pending_group()
.or(self.multicast.pending)
.unwrap_or_else(|| view.group());
Ok(PropertyValue::OctetString(group.to_vec()))
}
None => Err(common::unknown_property_error()),
})
}
pub(super) fn write_multicast(
&mut self,
property: P,
array_index: Option<u32>,
value: &PropertyValue,
) -> Option<Result<(), Error>> {
if property != P::BACNET_IP_MULTICAST_ADDRESS || !self.serves_multicast() {
return None;
}
if !self.activates_multicast() {
return Some(Err(common::write_access_denied_error()));
}
if array_index.is_some() {
return Some(Err(common::property_is_not_an_array_error()));
}
let PropertyValue::OctetString(octets) = value else {
return Some(Err(common::invalid_data_type_error()));
};
let Ok(group) = <[u8; 4]>::try_from(octets.as_slice()) else {
return Some(Err(common::value_out_of_range_error()));
};
if let Some(tables) = self.group_bbmd() {
let mut changes = BbmdChanges::default();
changes.multicast_group = Some(group);
return Some(
tables
.check_value(&changes)
.map(|()| self.set_pending_group(group)),
);
}
let view = self.multicast.view.as_ref()?;
Some(
view.check(group)
.map(|()| self.multicast.pending = Some(group)),
)
}
pub(super) fn check_multicast(&self) -> Result<(), Error> {
match (self.multicast.pending, &self.multicast.view) {
(None, _) => Ok(()),
(Some(group), Some(view)) => view.check(group),
(Some(_), None) => Err(common::write_access_denied_error()),
}
}
pub(super) fn activate_multicast(&mut self) -> Result<(), Error> {
let Some(group) = self.multicast.pending else {
return Ok(());
};
let view = self
.multicast
.view
.as_ref()
.ok_or_else(common::write_access_denied_error)?;
view.activate(group)?;
self.multicast.pending = None;
Ok(())
}
}