use super::*;
pub fn handle_read_property(
db: &ObjectDatabase,
service_data: &[u8],
buf: &mut BytesMut,
) -> Result<(), Error> {
let request = ReadPropertyRequest::decode(service_data)?;
let lookup_oid = resolve_device_wildcard(db, &request.object_identifier);
let object = db.get(&lookup_oid).ok_or(Error::Protocol {
class: ErrorClass::OBJECT.to_raw() as u32,
code: ErrorCode::UNKNOWN_OBJECT.to_raw() as u32,
})?;
if request.property_array_index.is_some()
&& !object.is_array_property(request.property_identifier)
{
return Err(Error::Protocol {
class: ErrorClass::PROPERTY.to_raw() as u32,
code: ErrorCode::PROPERTY_IS_NOT_AN_ARRAY.to_raw() as u32,
});
}
let value = object.read_property(request.property_identifier, request.property_array_index)?;
let mut value_buf = BytesMut::new();
encode_property_value(&mut value_buf, &value)?;
let ack = ReadPropertyACK {
object_identifier: lookup_oid,
property_identifier: request.property_identifier,
property_array_index: request.property_array_index,
property_value: value_buf.to_vec(),
};
ack.encode(buf);
Ok(())
}
fn resolve_device_wildcard(db: &ObjectDatabase, oid: &ObjectIdentifier) -> ObjectIdentifier {
if oid.object_type() == ObjectType::DEVICE && oid.instance_number() == 4194303 {
for candidate in db.list_objects() {
if candidate.object_type() == ObjectType::DEVICE {
return candidate;
}
}
}
*oid
}
fn expand_property_reference(
object: &dyn bacnet_objects::traits::BACnetObject,
property_identifier: PropertyIdentifier,
) -> Vec<PropertyIdentifier> {
use bacnet_objects::property_metadata::PropertyConformance;
let metadata = object.property_metadata();
if !metadata.is_empty() {
return match property_identifier {
PropertyIdentifier::ALL => metadata
.iter()
.filter_map(|row| {
(row.property_identifier != PropertyIdentifier::PROPERTY_LIST)
.then_some(row.property_identifier)
})
.collect(),
PropertyIdentifier::REQUIRED => metadata
.iter()
.filter_map(|row| {
(row.property_identifier != PropertyIdentifier::PROPERTY_LIST
&& row.conformance.is_required())
.then_some(row.property_identifier)
})
.collect(),
PropertyIdentifier::OPTIONAL => metadata
.iter()
.filter_map(|row| {
(row.conformance == PropertyConformance::Optional)
.then_some(row.property_identifier)
})
.collect(),
other => vec![other],
};
}
match property_identifier {
PropertyIdentifier::ALL => object.property_list().to_vec(),
PropertyIdentifier::REQUIRED => object.required_properties().to_vec(),
PropertyIdentifier::OPTIONAL => {
let required: std::collections::HashSet<PropertyIdentifier> =
object.required_properties().iter().copied().collect();
object
.property_list()
.iter()
.copied()
.filter(|property| !required.contains(property))
.collect()
}
other => vec![other],
}
}
pub fn handle_read_property_multiple(
db: &ObjectDatabase,
service_data: &[u8],
buf: &mut BytesMut,
) -> Result<(), Error> {
let request = ReadPropertyMultipleRequest::decode(service_data)?;
let mut results = Vec::new();
for spec in &request.list_of_read_access_specs {
let mut elements = Vec::new();
let lookup_oid = resolve_device_wildcard(db, &spec.object_identifier);
match db.get(&lookup_oid) {
Some(object) => {
for prop_ref in &spec.list_of_property_references {
let prop_ids = expand_property_reference(object, prop_ref.property_identifier);
for prop_id in prop_ids {
let array_index = if prop_ref.property_identifier == prop_id {
prop_ref.property_array_index
} else {
None
};
if array_index.is_some() && !object.is_array_property(prop_id) {
elements.push(ReadResultElement {
property_identifier: prop_id,
property_array_index: array_index,
property_value: None,
error: Some((
ErrorClass::PROPERTY,
ErrorCode::PROPERTY_IS_NOT_AN_ARRAY,
)),
});
continue;
}
match object.read_property(prop_id, array_index) {
Ok(value) => {
let mut value_buf = BytesMut::new();
match encode_property_value(&mut value_buf, &value) {
Ok(()) => {
elements.push(ReadResultElement {
property_identifier: prop_id,
property_array_index: array_index,
property_value: Some(value_buf.to_vec()),
error: None,
});
}
Err(_) => {
elements.push(ReadResultElement {
property_identifier: prop_id,
property_array_index: array_index,
property_value: None,
error: Some((ErrorClass::PROPERTY, ErrorCode::OTHER)),
});
}
}
}
Err(e) => {
let (err_class, err_code) = match &e {
Error::Protocol { class, code } => (
ErrorClass::from_raw(*class as u16),
ErrorCode::from_raw(*code as u16),
),
_ => (ErrorClass::PROPERTY, ErrorCode::UNKNOWN_PROPERTY),
};
elements.push(ReadResultElement {
property_identifier: prop_id,
property_array_index: array_index,
property_value: None,
error: Some((err_class, err_code)),
});
}
}
}
}
}
None => {
for prop_ref in &spec.list_of_property_references {
elements.push(ReadResultElement {
property_identifier: prop_ref.property_identifier,
property_array_index: prop_ref.property_array_index,
property_value: None,
error: Some((ErrorClass::OBJECT, ErrorCode::UNKNOWN_OBJECT)),
});
}
}
}
results.push(ReadAccessResult {
object_identifier: spec.object_identifier,
list_of_results: elements,
});
}
let ack = ReadPropertyMultipleACK {
list_of_read_access_results: results,
};
ack.encode(buf);
Ok(())
}