use super::*;
fn check_and_prepare_name_write(
db: &ObjectDatabase,
oid: &ObjectIdentifier,
value: &PropertyValue,
) -> Result<(), Error> {
if let PropertyValue::CharacterString(new_name) = value {
db.check_name_available(oid, new_name)?;
}
Ok(())
}
pub(crate) enum WritePropertyMultipleOutcome {
Success {
committed_oids: Vec<ObjectIdentifier>,
},
Error {
error: Error,
first_failed_write_attempt: BACnetObjectPropertyReference,
committed_oids: Vec<ObjectIdentifier>,
},
Reject {
reason: RejectReason,
},
}
pub fn handle_write_property_multiple(
db: &mut ObjectDatabase,
service_data: &[u8],
) -> Result<Vec<ObjectIdentifier>, Error> {
let mut snapshots = crate::life_safety_cov::LifeSafetyCovSnapshots::default();
match handle_write_property_multiple_detailed(db, service_data, &mut snapshots) {
WritePropertyMultipleOutcome::Success { committed_oids } => Ok(committed_oids),
WritePropertyMultipleOutcome::Error { error, .. } => Err(error),
WritePropertyMultipleOutcome::Reject { reason } => Err(Error::Reject {
reason: reason.to_raw(),
}),
}
}
pub(crate) fn handle_write_property_multiple_detailed(
db: &mut ObjectDatabase,
service_data: &[u8],
snapshots: &mut crate::life_safety_cov::LifeSafetyCovSnapshots,
) -> WritePropertyMultipleOutcome {
let mut cursor = WritePropertyMultipleCursor::new(service_data);
let mut committed_oids = Vec::new();
loop {
let event = match cursor.next_event() {
Ok(Some(event)) => event,
Ok(None) => {
return WritePropertyMultipleOutcome::Success { committed_oids };
}
Err(cursor_error) if committed_oids.is_empty() => {
return WritePropertyMultipleOutcome::Reject {
reason: cursor_error.reject_reason,
};
}
Err(cursor_error) => {
return WritePropertyMultipleOutcome::Error {
error: protocol_error(ErrorClass::SERVICES, ErrorCode::INVALID_TAG),
first_failed_write_attempt: cursor_error
.first_failed_write_attempt
.unwrap_or_else(wpm_undecodable_coordinate),
committed_oids,
};
}
};
let WritePropertyMultipleEvent::WriteAttempt(attempt) = event else {
continue;
};
let reference = attempt.reference;
let oid = reference.object_identifier;
let property = PropertyIdentifier::from_raw(reference.property_identifier);
let Some(object) = db.get(&oid) else {
return semantic_failure(
protocol_error(ErrorClass::OBJECT, ErrorCode::UNKNOWN_OBJECT),
reference,
committed_oids,
);
};
if reference.property_array_index.is_some() && !object.is_array_property(property) {
return semantic_failure(
protocol_error(ErrorClass::PROPERTY, ErrorCode::PROPERTY_IS_NOT_AN_ARRAY),
reference,
committed_oids,
);
}
let value = match decode_write_property_value(
property,
reference.property_array_index,
&attempt.value,
) {
Ok(value) => value,
Err(error) => return semantic_failure(error, reference, committed_oids),
};
if property == PropertyIdentifier::OBJECT_NAME {
if let Err(error) = check_and_prepare_name_write(db, &oid, &value) {
return semantic_failure(error, reference, committed_oids);
}
}
snapshots.capture_before_write(db, oid);
let write = db
.get_mut(&oid)
.expect("existence checked above")
.write_property(
property,
reference.property_array_index,
value,
attempt.priority,
);
if let Err(error) = write {
return semantic_failure(error, reference, committed_oids);
}
if property == PropertyIdentifier::OBJECT_NAME {
db.update_name_index(&oid);
}
if !committed_oids.contains(&oid) {
committed_oids.push(oid);
}
}
}
fn semantic_failure(
error: Error,
first_failed_write_attempt: BACnetObjectPropertyReference,
committed_oids: Vec<ObjectIdentifier>,
) -> WritePropertyMultipleOutcome {
WritePropertyMultipleOutcome::Error {
error,
first_failed_write_attempt,
committed_oids,
}
}
fn protocol_error(class: ErrorClass, code: ErrorCode) -> Error {
Error::Protocol {
class: class.to_raw() as u32,
code: code.to_raw() as u32,
}
}
fn wpm_undecodable_coordinate() -> BACnetObjectPropertyReference {
BACnetObjectPropertyReference {
object_identifier: ObjectIdentifier::new(
ObjectType::DEVICE,
ObjectIdentifier::MAX_INSTANCE,
)
.expect("the wildcard instance is valid service vocabulary"),
property_identifier: PropertyIdentifier::ALL.to_raw(),
property_array_index: None,
}
}
fn invalid_data_encoding_error() -> Error {
protocol_error(ErrorClass::PROPERTY, ErrorCode::INVALID_DATA_ENCODING)
}
pub(crate) fn decode_write_property_value(
property: PropertyIdentifier,
array_index: Option<u32>,
bytes: &[u8],
) -> Result<PropertyValue, Error> {
if property == PropertyIdentifier::EVENT_PARAMETERS && bytes.starts_with(&[0xfe, 0xff]) {
use bacnet_types::constructed::BACnetEventParameter;
return match bacnet_encoding::constructed::decode_event_parameter(bytes, 0) {
Ok((BACnetEventParameter::Opaque { tag, data }, consumed))
if tag == u8::MAX && consumed == bytes.len() =>
{
Ok(PropertyValue::OctetString(data))
}
_ => Err(invalid_data_encoding_error()),
};
}
if property == PropertyIdentifier::RECIPIENT_LIST {
return Ok(PropertyValue::ApplicationData(bytes.to_vec()));
}
if array_index != Some(0) && property == PropertyIdentifier::STAGES {
return decode_structured_array(bytes, array_index, |data, offset| {
bacnet_encoding::constructed::decode_stage_limit_value(data, offset).map(|(_, end)| end)
});
}
if array_index != Some(0) && property == PropertyIdentifier::TARGET_REFERENCES {
return decode_structured_array(bytes, array_index, |data, offset| {
bacnet_encoding::constructed::decode_device_object_reference(data, offset)
.map(|(_, end)| end)
});
}
let mut values = Vec::new();
let mut offset = 0;
while offset < bytes.len() {
let (value, new_offset) =
bacnet_encoding::primitives::decode_application_value(bytes, offset)
.map_err(|_| invalid_data_encoding_error())?;
values.push(value);
offset = new_offset;
}
match values.len() {
0 if property == PropertyIdentifier::FAULT_SIGNALS => Ok(PropertyValue::List(values)),
0 => Err(invalid_data_encoding_error()),
1 => Ok(values.pop().expect("one element present")),
_ => Ok(PropertyValue::List(values)),
}
}
fn decode_structured_array<F>(
bytes: &[u8],
array_index: Option<u32>,
mut decode: F,
) -> Result<PropertyValue, Error>
where
F: FnMut(&[u8], usize) -> Result<usize, Error>,
{
let mut values = Vec::new();
let mut offset = 0;
while offset < bytes.len() {
if values.len() >= 10_000 {
return Err(invalid_data_encoding_error());
}
let start = offset;
offset = decode(bytes, offset).map_err(|_| invalid_data_encoding_error())?;
if offset <= start || offset > bytes.len() {
return Err(invalid_data_encoding_error());
}
values.push(PropertyValue::ApplicationData(
bytes[start..offset].to_vec(),
));
}
if array_index.is_some() {
if values.len() == 1 {
return Ok(values.pop().unwrap());
}
return Err(invalid_data_encoding_error());
}
Ok(PropertyValue::List(values))
}
pub fn handle_write_property(
db: &mut ObjectDatabase,
service_data: &[u8],
) -> Result<ObjectIdentifier, Error> {
let request = WritePropertyRequest::decode(service_data)?;
let oid = request.object_identifier;
if db.get(&oid).is_none() {
return Err(protocol_error(
ErrorClass::OBJECT,
ErrorCode::UNKNOWN_OBJECT,
));
}
if request.property_array_index.is_some()
&& !db
.get(&oid)
.expect("existence checked above")
.is_array_property(request.property_identifier)
{
return Err(protocol_error(
ErrorClass::PROPERTY,
ErrorCode::PROPERTY_IS_NOT_AN_ARRAY,
));
}
let value = decode_write_property_value(
request.property_identifier,
request.property_array_index,
&request.property_value,
)?;
if request.property_identifier == PropertyIdentifier::OBJECT_NAME {
check_and_prepare_name_write(db, &oid, &value)?;
}
db.get_mut(&oid)
.expect("existence checked above")
.write_property(
request.property_identifier,
request.property_array_index,
value,
request.priority,
)?;
if request.property_identifier == PropertyIdentifier::OBJECT_NAME {
db.update_name_index(&oid);
}
Ok(oid)
}