use super::{tracking::decode_claim, CommandOrigin};
use crate::common;
use crate::property_metadata::{
PropertyConformance, PropertyMetadata, PropertyPresenceCondition, PropertyWriteCapability,
};
use bacnet_encoding::constructed::encode_value_source;
use bacnet_types::{
constructed::BACnetValueSource, enums::PropertyIdentifier as P, error::Error,
primitives::PropertyValue,
};
use bytes::BytesMut;
#[derive(Debug, Clone, Default)]
pub(crate) struct SingleValueSource {
tracked: Option<Tracked>,
}
#[derive(Debug, Clone)]
struct Tracked {
source: BACnetValueSource,
owner: Option<CommandOrigin>,
}
impl SingleValueSource {
pub(crate) fn set_enabled(&mut self, enabled: bool) {
if !enabled {
self.tracked = None;
} else if self.tracked.is_none() {
self.tracked = Some(Tracked {
source: BACnetValueSource::None,
owner: None,
});
}
}
pub(crate) fn is_enabled(&self) -> bool {
self.tracked.is_some()
}
pub(crate) fn metadata(&self) -> Option<PropertyMetadata> {
self.tracked.as_ref().map(|_| {
PropertyMetadata::new(
P::VALUE_SOURCE,
PropertyConformance::Optional,
Some(PropertyPresenceCondition::ValueSourceTracking),
PropertyWriteCapability::WhenCommandOwner,
)
})
}
pub(crate) fn admit(&self, origin: &CommandOrigin) -> Result<(), Error> {
match self.tracked {
Some(_) => origin.validate(),
None => Ok(()),
}
}
pub(crate) fn record(&mut self, origin: &CommandOrigin) {
if let Some(tracked) = &mut self.tracked {
tracked.source = origin.published_source();
tracked.owner = Some(origin.clone());
}
}
pub(crate) fn unsourced(&self) -> Result<(), Error> {
match self.tracked {
Some(_) => Err(common::write_access_denied_error()),
None => Ok(()),
}
}
pub(crate) fn forget(&mut self) {
if let Some(tracked) = &mut self.tracked {
tracked.source = BACnetValueSource::None;
tracked.owner = None;
}
}
pub(crate) fn correct(
&mut self,
index: Option<u32>,
value: PropertyValue,
origin: &CommandOrigin,
) -> Result<(), Error> {
let Some(tracked) = &mut self.tracked else {
return Err(common::unknown_property_error());
};
if index.is_some() {
return Err(common::property_is_not_an_array_error());
}
origin.validate()?;
if !tracked
.owner
.as_ref()
.is_some_and(|owner| owner.permits_correction_by(origin))
{
return Err(common::write_access_denied_error());
}
tracked.source = decode_claim(value)?;
Ok(())
}
pub(crate) fn read(
&self,
property: P,
index: Option<u32>,
) -> Option<Result<PropertyValue, Error>> {
if property != P::VALUE_SOURCE {
return None;
}
Some(match &self.tracked {
None => Err(common::unknown_property_error()),
Some(_) if index.is_some() => Err(common::property_is_not_an_array_error()),
Some(tracked) => {
let mut bytes = BytesMut::new();
encode_value_source(&mut bytes, &tracked.source)
.map(|()| PropertyValue::ApplicationData(bytes.to_vec()))
}
})
}
}
#[cfg(test)]
#[path = "single_tests.rs"]
mod tests;