use super::*;
fn validate_atomic_read_file_ack(
request: &bacnet_services::file::FileAccessMethod,
ack: &bacnet_services::file::AtomicReadFileAck,
) -> Result<(), Error> {
use bacnet_services::file::{FileAccessMethod, FileReadAckMethod};
match (request, &ack.access) {
(
FileAccessMethod::Stream {
requested_octet_count,
..
},
FileReadAckMethod::Stream { file_data, .. },
) if file_data.len() <= *requested_octet_count as usize => Ok(()),
(FileAccessMethod::Stream { .. }, FileReadAckMethod::Stream { .. }) => Err(
Error::decoding(0, "AtomicReadFile ACK exceeds the requested octet window"),
),
(
FileAccessMethod::Record {
requested_record_count,
..
},
FileReadAckMethod::Record {
returned_record_count,
file_record_data,
..
},
) if returned_record_count <= requested_record_count
&& *returned_record_count as usize == file_record_data.len() =>
{
Ok(())
}
(FileAccessMethod::Record { .. }, FileReadAckMethod::Record { .. }) => Err(
Error::decoding(0, "AtomicReadFile ACK exceeds the requested record window"),
),
_ => Err(Error::decoding(
0,
"AtomicReadFile ACK access method does not match the request",
)),
}
}
impl<T: TransportPort + 'static> BACnetClient<T> {
pub async fn acknowledge_alarm_request(
&self,
destination_mac: &[u8],
request: &bacnet_services::alarm_event::AcknowledgeAlarmRequest,
) -> Result<(), Error> {
let mut buf = BytesMut::new();
request.encode(&mut buf)?;
let _ = self
.confirmed_request(
destination_mac,
ConfirmedServiceChoice::ACKNOWLEDGE_ALARM,
&buf,
)
.await?;
Ok(())
}
pub async fn acknowledge_alarm_request_from_device(
&self,
device_instance: u32,
request: &bacnet_services::alarm_event::AcknowledgeAlarmRequest,
) -> Result<(), Error> {
let (mac, routing) = self.resolve_device(device_instance).await?;
if let Some((network, address)) = routing {
let mut buf = BytesMut::new();
request.encode(&mut buf)?;
self.confirmed_request_routed(
&mac,
network,
&address,
ConfirmedServiceChoice::ACKNOWLEDGE_ALARM,
&buf,
)
.await?;
Ok(())
} else {
self.acknowledge_alarm_request(&mac, request).await
}
}
#[deprecated(
note = "use acknowledge_alarm_request with caller-supplied correlation timestamps"
)]
pub async fn acknowledge_alarm(
&self,
destination_mac: &[u8],
acknowledging_process_identifier: u32,
event_object_identifier: bacnet_types::primitives::ObjectIdentifier,
event_state_acknowledged: bacnet_types::enums::EventState,
acknowledgment_source: &str,
) -> Result<(), Error> {
use bacnet_services::alarm_event::AcknowledgeAlarmRequest;
let request = AcknowledgeAlarmRequest {
acknowledging_process_identifier,
event_object_identifier,
event_state_acknowledged,
timestamp: bacnet_types::primitives::BACnetTimeStamp::SequenceNumber(0),
acknowledgment_source: acknowledgment_source.to_string(),
time_of_acknowledgment: bacnet_types::primitives::BACnetTimeStamp::SequenceNumber(0),
};
self.acknowledge_alarm_request(destination_mac, &request)
.await
}
pub async fn atomic_read_file(
&self,
destination_mac: &[u8],
file_identifier: bacnet_types::primitives::ObjectIdentifier,
access: bacnet_services::file::FileAccessMethod,
) -> Result<Bytes, Error> {
use bacnet_services::file::AtomicReadFileRequest;
let request = AtomicReadFileRequest {
file_identifier,
access,
};
let mut buf = BytesMut::new();
request.encode(&mut buf);
self.confirmed_request(
destination_mac,
ConfirmedServiceChoice::ATOMIC_READ_FILE,
&buf,
)
.await
}
pub async fn atomic_read_file_decoded(
&self,
destination_mac: &[u8],
file_identifier: bacnet_types::primitives::ObjectIdentifier,
access: bacnet_services::file::FileAccessMethod,
) -> Result<bacnet_services::file::AtomicReadFileAck, Error> {
let requested_access = access.clone();
let response = self
.atomic_read_file(destination_mac, file_identifier, access)
.await?;
let ack = bacnet_services::file::AtomicReadFileAck::decode(&response)?;
validate_atomic_read_file_ack(&requested_access, &ack)?;
Ok(ack)
}
pub async fn atomic_read_file_from_device(
&self,
device_instance: u32,
file_identifier: bacnet_types::primitives::ObjectIdentifier,
access: bacnet_services::file::FileAccessMethod,
) -> Result<Bytes, Error> {
let (mac, routing) = self.resolve_device(device_instance).await?;
if let Some((network, address)) = routing {
let request = bacnet_services::file::AtomicReadFileRequest {
file_identifier,
access,
};
let mut buf = BytesMut::new();
request.encode(&mut buf);
self.confirmed_request_routed(
&mac,
network,
&address,
ConfirmedServiceChoice::ATOMIC_READ_FILE,
&buf,
)
.await
} else {
self.atomic_read_file(&mac, file_identifier, access).await
}
}
pub async fn atomic_read_file_decoded_from_device(
&self,
device_instance: u32,
file_identifier: bacnet_types::primitives::ObjectIdentifier,
access: bacnet_services::file::FileAccessMethod,
) -> Result<bacnet_services::file::AtomicReadFileAck, Error> {
let requested_access = access.clone();
let response = self
.atomic_read_file_from_device(device_instance, file_identifier, access)
.await?;
let ack = bacnet_services::file::AtomicReadFileAck::decode(&response)?;
validate_atomic_read_file_ack(&requested_access, &ack)?;
Ok(ack)
}
pub async fn atomic_write_file(
&self,
destination_mac: &[u8],
file_identifier: bacnet_types::primitives::ObjectIdentifier,
access: bacnet_services::file::FileWriteAccessMethod,
) -> Result<Bytes, Error> {
use bacnet_services::file::AtomicWriteFileRequest;
let request = AtomicWriteFileRequest {
file_identifier,
access,
};
let mut buf = BytesMut::new();
request.encode(&mut buf);
self.confirmed_request(
destination_mac,
ConfirmedServiceChoice::ATOMIC_WRITE_FILE,
&buf,
)
.await
}
pub async fn atomic_write_file_from_device(
&self,
device_instance: u32,
file_identifier: bacnet_types::primitives::ObjectIdentifier,
access: bacnet_services::file::FileWriteAccessMethod,
) -> Result<Bytes, Error> {
let (mac, routing) = self.resolve_device(device_instance).await?;
if let Some((network, address)) = routing {
let request = bacnet_services::file::AtomicWriteFileRequest {
file_identifier,
access,
};
let mut buf = BytesMut::new();
request.encode(&mut buf);
self.confirmed_request_routed(
&mac,
network,
&address,
ConfirmedServiceChoice::ATOMIC_WRITE_FILE,
&buf,
)
.await
} else {
self.atomic_write_file(&mac, file_identifier, access).await
}
}
pub async fn add_list_element(
&self,
destination_mac: &[u8],
object_identifier: bacnet_types::primitives::ObjectIdentifier,
property_identifier: bacnet_types::enums::PropertyIdentifier,
property_array_index: Option<u32>,
list_of_elements: Vec<u8>,
) -> Result<(), Error> {
use bacnet_services::list_manipulation::ListElementRequest;
let request = ListElementRequest {
object_identifier,
property_identifier,
property_array_index,
list_of_elements,
};
let mut buf = BytesMut::new();
request.encode(&mut buf)?;
let _ = self
.confirmed_request(
destination_mac,
ConfirmedServiceChoice::ADD_LIST_ELEMENT,
&buf,
)
.await?;
Ok(())
}
pub async fn remove_list_element(
&self,
destination_mac: &[u8],
object_identifier: bacnet_types::primitives::ObjectIdentifier,
property_identifier: bacnet_types::enums::PropertyIdentifier,
property_array_index: Option<u32>,
list_of_elements: Vec<u8>,
) -> Result<(), Error> {
use bacnet_services::list_manipulation::ListElementRequest;
let request = ListElementRequest {
object_identifier,
property_identifier,
property_array_index,
list_of_elements,
};
let mut buf = BytesMut::new();
request.encode(&mut buf)?;
let _ = self
.confirmed_request(
destination_mac,
ConfirmedServiceChoice::REMOVE_LIST_ELEMENT,
&buf,
)
.await?;
Ok(())
}
}
#[cfg(test)]
#[path = "file_list_decoded_tests.rs"]
mod decoded_tests;
#[cfg(test)]
#[path = "routed_file_tests.rs"]
mod routed_tests;