use super::*;
use bacnet_types::enums::EngineeringUnits;
use bacnet_types::error::RemoteError;
pub fn handle_who_has(
db: &ObjectDatabase,
service_data: &[u8],
device_oid: ObjectIdentifier,
) -> Result<Option<IHaveRequest>, Error> {
let request = WhoHasRequest::decode(service_data)?;
let instance = device_oid.instance_number();
if request.range.is_some_and(|range| !range.contains(instance)) {
return Ok(None);
}
match &request.object {
WhoHasObject::Identifier(oid) => {
if let Some(obj) = db.get(oid) {
return Ok(Some(IHaveRequest {
device_identifier: device_oid,
object_identifier: *oid,
object_name: obj.object_name().to_string(),
}));
}
}
WhoHasObject::Name(name) => {
for (oid, obj) in db.iter_objects() {
if obj.object_name() == name {
return Ok(Some(IHaveRequest {
device_identifier: device_oid,
object_identifier: oid,
object_name: name.clone(),
}));
}
}
}
}
Ok(None)
}
pub fn handle_create_object(
db: &mut ObjectDatabase,
service_data: &[u8],
buf: &mut BytesMut,
) -> Result<(), Error> {
handle_create_object_observed(db, service_data, buf, &mut None, None)
.map_err(CreateObjectRefusal::into_error)
}
#[derive(Debug)]
pub(crate) enum CreateObjectRefusal {
Malformed(Error),
Failed(Error),
}
impl CreateObjectRefusal {
pub(crate) fn into_error(self) -> Error {
match self {
Self::Malformed(error) | Self::Failed(error) => error,
}
}
fn of_initial_value(self, position: u32) -> Self {
let named = |error| match error {
Error::Remote(RemoteError::Error { class, code })
| Error::Structured { class, code, .. } => Error::with_detail(
class,
code,
Some(ErrorDetail::FirstFailedElementNumber(position)),
),
other => other,
};
match self {
Self::Malformed(error) => Self::Malformed(named(error)),
Self::Failed(error) => Self::Failed(named(error)),
}
}
}
pub(crate) fn handle_create_object_observed(
db: &mut ObjectDatabase,
service_data: &[u8],
buf: &mut BytesMut,
target: &mut Option<ObjectIdentifier>,
command_origin: Option<&bacnet_objects::command_source::CommandOrigin>,
) -> Result<(), CreateObjectRefusal> {
*target = None;
let request = CreateObjectRequest::decode(service_data)
.map_err(|error| CreateObjectRefusal::Malformed(error.into_request_reject()))?;
if let ObjectSpecifier::Identifier(oid) = request.object_specifier {
*target = Some(oid);
}
let created_oid = create(db, &request, target).map_err(CreateObjectRefusal::Failed)?;
let counts = StateCounts::of(db, created_oid, &request);
for (position, pv) in apply_counts_first(db, created_oid, &request, counts, command_origin) {
if let Err(refusal) = initialize(db, created_oid, pv, counts, command_origin) {
let _ = db.remove(&created_oid);
return Err(refusal.of_initial_value(position));
}
}
bacnet_services::object_mgmt::CreateObjectAck {
object_identifier: created_oid,
}
.encode(buf);
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StateCounts {
None,
Given,
FromStateText,
}
impl StateCounts {
fn of(
db: &ObjectDatabase,
created_oid: ObjectIdentifier,
request: &CreateObjectRequest,
) -> Self {
let object = db.get(&created_oid).expect("created above");
if !object
.creation_only_properties()
.contains(&PropertyIdentifier::NUMBER_OF_STATES)
{
Self::None
} else if request
.list_of_initial_values
.iter()
.any(|pv| pv.property_identifier == PropertyIdentifier::NUMBER_OF_STATES)
{
Self::Given
} else {
Self::FromStateText
}
}
fn gives(self, pv: &bacnet_services::common::BACnetPropertyValue) -> bool {
match self {
Self::None => false,
Self::Given => pv.property_identifier == PropertyIdentifier::NUMBER_OF_STATES,
Self::FromStateText => {
pv.property_identifier == PropertyIdentifier::STATE_TEXT
&& pv.property_array_index.is_none()
}
}
}
}
fn apply_counts_first<'a>(
db: &mut ObjectDatabase,
created_oid: ObjectIdentifier,
request: &'a CreateObjectRequest,
counts: StateCounts,
command_origin: Option<&bacnet_objects::command_source::CommandOrigin>,
) -> Vec<(u32, &'a bacnet_services::common::BACnetPropertyValue)> {
(1u32..)
.zip(&request.list_of_initial_values)
.filter(|(_, pv)| {
!counts.gives(pv) || initialize(db, created_oid, pv, counts, command_origin).is_err()
})
.collect()
}
fn create(
db: &mut ObjectDatabase,
request: &CreateObjectRequest,
target: &mut Option<ObjectIdentifier>,
) -> Result<ObjectIdentifier, Error> {
const MAX_OBJECTS: usize = 10_000;
if db.len() >= MAX_OBJECTS {
return Err(Error::protocol(
ErrorClass::RESOURCES,
ErrorCode::NO_SPACE_FOR_OBJECT,
));
}
let (object_type, instance) = match &request.object_specifier {
ObjectSpecifier::Type(obj_type) => {
let existing: HashSet<u32> = db
.find_by_type(*obj_type)
.iter()
.map(|oid| oid.instance_number())
.collect();
let next =
(1u32..=4_194_303)
.find(|i| !existing.contains(i))
.ok_or(Error::protocol(
ErrorClass::RESOURCES,
ErrorCode::NO_SPACE_FOR_OBJECT,
))?;
(*obj_type, next)
}
ObjectSpecifier::Identifier(oid) => {
if db.get(oid).is_some() {
return Err(Error::protocol(
ErrorClass::OBJECT,
ErrorCode::OBJECT_IDENTIFIER_ALREADY_EXISTS,
));
}
(oid.object_type(), oid.instance_number())
}
};
*target = (object_type.to_raw() <= 1023)
.then(|| ObjectIdentifier::new(object_type, instance).ok())
.flatten();
let name = || default_name(db, object_type, instance);
let object: Box<dyn bacnet_objects::traits::BACnetObject> = if object_type
== ObjectType::ANALOG_INPUT
{
Box::new(bacnet_objects::analog::AnalogInputObject::new(
instance,
name(),
EngineeringUnits::NO_UNITS,
)?)
} else if object_type == ObjectType::ANALOG_OUTPUT {
Box::new(bacnet_objects::analog::AnalogOutputObject::new(
instance,
name(),
EngineeringUnits::NO_UNITS,
)?)
} else if object_type == ObjectType::BINARY_INPUT {
Box::new(bacnet_objects::binary::BinaryInputObject::new(
instance,
name(),
)?)
} else if object_type == ObjectType::BINARY_OUTPUT {
Box::new(bacnet_objects::binary::BinaryOutputObject::new(
instance,
name(),
)?)
} else if object_type == ObjectType::BINARY_VALUE {
let mut object = bacnet_objects::binary::BinaryValueObject::new(instance, name())?;
let mut policy = bacnet_objects::audit::ObjectAuditPolicy::default();
for value in &request.list_of_initial_values {
match value.property_identifier {
PropertyIdentifier::AUDIT_LEVEL => {
policy.level = Some(bacnet_types::enums::AuditLevel::DEFAULT)
}
PropertyIdentifier::AUDITABLE_OPERATIONS => {
policy.operations = Some(bacnet_types::bitstring::AuditOperationFlags::empty())
}
PropertyIdentifier::AUDIT_PRIORITY_FILTER => {
policy.priority_filter =
Some(bacnet_objects::audit::AuditPriorityPolicy::Inherit)
}
_ => {}
}
}
object.set_audit_policy(policy);
Box::new(object)
} else if object_type == ObjectType::MULTI_STATE_INPUT {
Box::new(bacnet_objects::multistate::MultiStateInputObject::new(
instance,
name(),
2,
)?)
} else if object_type == ObjectType::MULTI_STATE_OUTPUT {
Box::new(bacnet_objects::multistate::MultiStateOutputObject::new(
instance,
name(),
2,
)?)
} else if object_type == ObjectType::MULTI_STATE_VALUE {
Box::new(bacnet_objects::multistate::MultiStateValueObject::new(
instance,
name(),
2,
)?)
} else {
return Err(Error::protocol(
ErrorClass::OBJECT,
ErrorCode::UNSUPPORTED_OBJECT_TYPE,
));
};
let created_oid = object.object_identifier();
*target = Some(created_oid);
db.add(object)?;
Ok(created_oid)
}
fn default_name(db: &ObjectDatabase, object_type: ObjectType, instance: u32) -> String {
let base = format!("{object_type}-{instance}");
if db.find_by_name(&base).is_none() {
return base;
}
(2u64..)
.map(|n| format!("{base} ({n})"))
.find(|name| db.find_by_name(name).is_none())
.expect("one candidate past the names held is free")
}
fn initialize(
db: &mut ObjectDatabase,
created_oid: ObjectIdentifier,
pv: &bacnet_services::common::BACnetPropertyValue,
counts: StateCounts,
command_origin: Option<&bacnet_objects::command_source::CommandOrigin>,
) -> Result<(), CreateObjectRefusal> {
use super::write_property::{
WriteTarget, check_and_prepare_name_write, check_write_array_index, commit_attempt,
decode_write_property_value,
};
let property = pv.property_identifier;
let array_index = pv.property_array_index;
let object = db.get(&created_oid).expect("created above");
check_write_array_index(object, property, array_index).map_err(CreateObjectRefusal::Failed)?;
let value = decode_write_property_value(
property,
array_index,
object.is_list_property(property),
&pv.value,
)
.map_err(CreateObjectRefusal::Malformed)?;
let value = crate::local_references::localize(db, created_oid, property, value);
let object = db.get_mut(&created_oid).expect("created above");
if array_index.is_none() && object.creation_only_properties().contains(&property) {
return match object.initialize_property(property, value) {
Ok(()) => Ok(()),
Err(error)
if super::relinquish::is_null_octets(&pv.value)
&& super::relinquish::leaves_unchanged(object, property, None, &error) =>
{
Ok(())
}
Err(error) => Err(CreateObjectRefusal::Failed(error)),
};
}
if property == PropertyIdentifier::OBJECT_NAME {
check_and_prepare_name_write(db, &created_oid, &value)
.map_err(CreateObjectRefusal::Failed)?;
}
let target = WriteTarget {
oid: created_oid,
property,
array_index,
priority: pv.priority,
value: &pv.value,
};
let fixed_count = (counts == StateCounts::Given
&& property == PropertyIdentifier::STATE_TEXT
&& array_index.is_none())
.then(|| state_count(db, created_oid));
commit_attempt(db, None, target, value, None, command_origin)
.map_err(CreateObjectRefusal::Failed)?;
match fixed_count {
Some(count) if state_count(db, created_oid) != count => Err(CreateObjectRefusal::Failed(
Error::protocol(ErrorClass::PROPERTY, ErrorCode::VALUE_OUT_OF_RANGE),
)),
_ => Ok(()),
}
}
fn state_count(db: &ObjectDatabase, created_oid: ObjectIdentifier) -> Option<PropertyValue> {
db.get(&created_oid)?
.read_property(PropertyIdentifier::NUMBER_OF_STATES, None)
.ok()
}
pub fn handle_delete_object(
db: &mut ObjectDatabase,
service_data: &[u8],
) -> Result<Box<dyn bacnet_objects::traits::BACnetObject>, Error> {
let request = DeleteObjectRequest::decode(service_data).map_err(Error::into_request_reject)?;
match request.object_identifier.object_type() {
ObjectType::DEVICE | ObjectType::NETWORK_PORT => {
return Err(Error::protocol(
ErrorClass::OBJECT,
ErrorCode::OBJECT_DELETION_NOT_PERMITTED,
));
}
_ => {}
}
db.remove(&request.object_identifier)?
.ok_or(Error::protocol(
ErrorClass::OBJECT,
ErrorCode::UNKNOWN_OBJECT,
))
}