use super::*;
use crate::cov::active::{LiveCovSelection, LiveDeviceCov};
use crate::device_view::{DeviceExecution, DeviceReadContext};
use bacnet_services::read_property::ReadPropertyRequest;
use bacnet_types::error::{ErrorDetail, RemoteError};
pub(super) async fn read_property_response(
db: &RwLock<ObjectDatabase>,
request: &ConfirmedRequestPdu,
writes: bool,
reinitialize: bool,
registered_port: Option<ObjectIdentifier>,
work_limit: usize,
identity: Option<&bacnet_objects::DeviceIdentity>,
) -> Apdu {
read_property_response_observed(
db,
None,
ReadProfile {
execution: DeviceExecution::Endpoint {
writes,
reinitialize,
},
registered_port,
work_limit,
identity,
},
request,
|_, _, _, _| {},
)
.await
}
#[derive(Clone, Copy)]
pub(in crate::server) struct LiveTables<'a> {
pub(in crate::server) cov: &'a RwLock<CovSubscriptionTable>,
pub(in crate::server) bindings: &'a RwLock<DeviceBindingTable>,
}
pub(in crate::server) async fn active_cov_snapshot(
db: &ObjectDatabase,
tables: LiveTables<'_>,
selection: LiveCovSelection,
) -> LiveDeviceCov {
let live = if selection.reads_cov() {
let entries = {
let table = tables.cov.read().await;
table.live_cov_entries(selection, runtime_clock::now())
};
LiveDeviceCov::project(db, selection, entries)
} else {
LiveDeviceCov::default()
};
let bindings = address_bindings(tables.bindings, selection).await;
live.with_address_bindings(selection, bindings)
}
pub(in crate::server) async fn address_bindings(
table: &RwLock<DeviceBindingTable>,
selection: LiveCovSelection,
) -> Option<PropertyValue> {
if !selection.address_bindings {
return None;
}
let table = table.read().await;
Some(table.address_binding_list(runtime_clock::now()))
}
pub(super) async fn read_property_multiple_observed(
db: &RwLock<ObjectDatabase>,
tables: LiveTables<'_>,
service_request: &[u8],
service_ack: &mut BytesMut,
config: &ServerConfig,
mut completed: impl FnMut(
&ObjectDatabase,
ObjectIdentifier,
PropertyIdentifier,
Option<u32>,
Option<(ErrorClass, ErrorCode)>,
),
) -> Result<(), handlers::ReadFailure> {
let db = db.read().await;
let request = bacnet_services::rpm::ReadPropertyMultipleRequest::decode(service_request)
.map_err(|error| handlers::ReadFailure::Service(error.into_request_reject()))?;
let budget = config.read_property_multiple_budget;
let view = DeviceReadContext::new(&db, DeviceExecution::FullServer)
.with_identity(Some(&config.device))
.with_registered_port(config.registered_network_port);
let plan = handlers::RpmPlan::new(&db, &request, budget.max_result_elements, Some(&view))?;
let live = match plan.live_cov(&db) {
Some(selection) => Some(active_cov_snapshot(&db, tables, selection).await),
None => None,
};
let view = view.with_live(live.as_ref());
plan.read_observed(
&db,
Some(&view),
service_ack,
budget.max_service_ack_bytes,
|oid, property, index, result| completed(&db, oid, property, index, result),
)
}
pub(super) struct ReadProfile<'a> {
pub(super) execution: DeviceExecution,
pub(super) registered_port: Option<ObjectIdentifier>,
pub(super) work_limit: usize,
pub(super) identity: Option<&'a bacnet_objects::DeviceIdentity>,
}
pub(super) async fn read_property_response_observed(
db: &RwLock<ObjectDatabase>,
tables: Option<LiveTables<'_>>,
profile: ReadProfile<'_>,
request: &ConfirmedRequestPdu,
mut completed: impl FnMut(
&ObjectDatabase,
ObjectIdentifier,
&ReadPropertyRequest,
&Result<(), Error>,
),
) -> Apdu {
let mut service_ack = BytesMut::with_capacity(512);
let db = db.read().await;
let result = match ReadPropertyRequest::decode(&request.service_request)
.map_err(Error::into_request_reject)
{
Ok(decoded) => {
let view = DeviceReadContext::new(&db, profile.execution)
.with_identity(profile.identity)
.with_registered_port(profile.registered_port)
.with_work_limit(profile.work_limit);
let lookup_oid = handlers::resolve_read_target(
&db,
&decoded.object_identifier,
profile.registered_port,
view.device_oid(),
);
match handlers::plan_read_property(
&db,
Some(&view),
lookup_oid,
decoded.property_identifier,
decoded.property_array_index,
) {
Ok(plan) => {
let live = match (tables, plan.live_cov(&db)) {
(Some(tables), Some(selection)) => {
Some(Box::pin(active_cov_snapshot(&db, tables, selection)).await)
}
_ => None,
};
let view = view.with_live(live.as_ref());
handlers::read_property_request_observed(
&db,
Some(&view),
&decoded,
plan,
&mut service_ack,
|oid, request, result| completed(&db, oid, request, result),
)
.map_err(handlers::ReadFailure::Service)
}
Err(failure) => Err(failure),
}
}
Err(error) => Err(handlers::ReadFailure::Service(error)),
};
match result {
Ok(()) => Apdu::ComplexAck(ComplexAck {
segmented: false,
more_follows: false,
invoke_id: request.invoke_id,
sequence_number: None,
proposed_window_size: None,
service_choice: request.service_choice,
service_ack: service_ack.freeze(),
}),
Err(handlers::ReadFailure::Service(error)) => {
error_apdu_from_error(request.invoke_id, request.service_choice, &error)
}
Err(handlers::ReadFailure::Work | handlers::ReadFailure::Bytes) => Apdu::Abort(AbortPdu {
sent_by_server: true,
invoke_id: request.invoke_id,
abort_reason: AbortReason::OUT_OF_RESOURCES,
}),
}
}
pub(in crate::server) fn error_apdu_from_error(
invoke_id: u8,
service_choice: ConfirmedServiceChoice,
error: &Error,
) -> Apdu {
if let Error::Remote(RemoteError::Reject { reason }) = error {
return Apdu::Reject(RejectPdu {
invoke_id,
reject_reason: *reason,
});
}
let (error_class, error_code) = error_fields(error);
let detail = match error {
Error::Structured { detail, .. } => Some(detail.as_ref()),
_ => None,
};
let first_failed_element_number = match detail {
Some(ErrorDetail::FirstFailedElementNumber(number)) => *number,
_ => 0,
};
if service_choice == ConfirmedServiceChoice::ADD_LIST_ELEMENT
|| service_choice == ConfirmedServiceChoice::REMOVE_LIST_ELEMENT
{
return Apdu::Error(
bacnet_services::list_manipulation::ChangeListError {
error_class,
error_code,
first_failed_element_number,
}
.to_error_pdu(invoke_id, service_choice),
);
}
if service_choice == ConfirmedServiceChoice::CREATE_OBJECT {
return Apdu::Error(
bacnet_services::object_mgmt::CreateObjectError {
error_class,
error_code,
first_failed_element_number,
}
.to_error_pdu(invoke_id),
);
}
if service_choice == ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY_MULTIPLE {
let first_failed_subscription = match detail {
Some(ErrorDetail::FirstFailedSubscription(reference)) => Some(reference.clone()),
_ => None,
};
return Apdu::Error(
bacnet_services::cov_multiple::SubscribeCOVPropertyMultipleError {
error_class,
error_code,
first_failed_subscription,
}
.to_error_pdu(invoke_id),
);
}
Apdu::Error(ErrorPdu {
invoke_id,
service_choice,
error_class,
error_code,
error_data: Bytes::new(),
})
}
pub(in crate::server) fn error_fields(error: &Error) -> (ErrorClass, ErrorCode) {
match error {
Error::Remote(RemoteError::Error { class, code })
| Error::Structured { class, code, .. } => (*class, *code),
_ => (ErrorClass::SERVICES, ErrorCode::OTHER),
}
}
pub(in crate::server) fn send_unsegmented_response<'a, T: TransportPort + 'static>(
network: &'a NetworkLayer<T>,
apdu: &'a [u8],
target: ResponseTarget<'a>,
reply_tx: Option<tokio::sync::oneshot::Sender<Bytes>>,
pending: Option<PendingConfirmedRequest>,
) -> impl std::future::Future<Output = bool> + Send + 'a {
send_response(network, apdu, target, reply_tx, true, pending)
}
pub(in crate::server) fn encoded_apdu(response: &Apdu) -> BytesMut {
let body = match response {
Apdu::ComplexAck(ack) => ack.service_ack.len(),
_ => 0,
};
let mut buf = BytesMut::with_capacity(8 + body);
encode_apdu(&mut buf, response).expect("valid APDU encoding");
buf
}
pub(in crate::server) async fn send_overload_response<T: TransportPort + 'static>(
network: &NetworkLayer<T>,
response: &Apdu,
source_mac: &[u8],
source_network: Option<&NpduAddress>,
route: &bacnet_network::response_route::ResponseRoute,
reply_tx: Option<tokio::sync::oneshot::Sender<Bytes>>,
) {
let apdu = encoded_apdu(response);
send_response(
network,
&apdu,
ResponseTarget {
source_mac,
source_network,
route,
},
reply_tx,
false,
None,
)
.await;
}
pub(in crate::server) async fn send_replay_bytes<T: TransportPort + 'static>(
network: &NetworkLayer<T>,
apdu_bytes: &Bytes,
source_mac: &[u8],
source_network: Option<&NpduAddress>,
route: &bacnet_network::response_route::ResponseRoute,
reply_tx: Option<tokio::sync::oneshot::Sender<Bytes>>,
) {
send_raw_response(
network,
apdu_bytes,
source_mac,
source_network,
route,
reply_tx,
)
.await;
}
async fn send_raw_response<T: TransportPort + 'static>(
network: &NetworkLayer<T>,
apdu_bytes: &Bytes,
source_mac: &[u8],
source_network: Option<&NpduAddress>,
route: &bacnet_network::response_route::ResponseRoute,
reply_tx: Option<tokio::sync::oneshot::Sender<Bytes>>,
) {
let reply_tx = if route.requires_direct_response() {
None
} else {
reply_tx
};
if let Some(tx) = reply_tx {
match network.encode_response_for_handoff(
bacnet_network::layer::IssuedApdu {
apdu: apdu_bytes,
next_hop: source_mac,
destination: source_network,
expecting_reply: false,
priority: NetworkPriority::Normal,
},
route,
) {
Ok(npdu_buf) => {
let _ = tx.send(npdu_buf.freeze());
}
Err(error) => {
warn!(%error, "Failed to encode NPDU for MS/TP replay");
if let Err(error) = BACnetServer::<T>::send_confirmed_response_apdu(
network,
apdu_bytes,
source_mac,
source_network,
route,
)
.await
{
warn!(%error, "Failed to send replay");
}
}
}
} else if let Err(error) = BACnetServer::<T>::send_confirmed_response_apdu(
network,
apdu_bytes,
source_mac,
source_network,
route,
)
.await
{
warn!(%error, "Failed to send replay");
}
}
#[derive(Clone, Copy)]
pub(in crate::server) struct ResponseTarget<'a> {
pub(in crate::server) source_mac: &'a [u8],
pub(in crate::server) source_network: Option<&'a NpduAddress>,
pub(in crate::server) route: &'a bacnet_network::response_route::ResponseRoute,
}
#[allow(
clippy::manual_async_fn,
reason = "an async fn would hold its moved arguments twice (#1713)"
)]
fn send_response<'a, T: TransportPort + 'static>(
network: &'a NetworkLayer<T>,
apdu: &'a [u8],
target: ResponseTarget<'a>,
reply_tx: Option<tokio::sync::oneshot::Sender<Bytes>>,
log_errors: bool,
pending: Option<PendingConfirmedRequest>,
) -> impl std::future::Future<Output = bool> + Send + 'a {
async move {
let sent = match hand_off_reply(network, apdu, target, reply_tx, log_errors, pending) {
Ok(handed_off) => return handed_off,
Err(pending) => BACnetServer::<T>::issue_terminal_response(
network,
apdu,
target.source_mac,
target.source_network,
target.route,
pending,
),
}
.await;
if let Err(error) = &sent
&& log_errors
{
warn!(%error, "Failed to send response");
}
sent.is_ok()
}
}
fn hand_off_reply<T: TransportPort + 'static>(
network: &NetworkLayer<T>,
apdu: &[u8],
target: ResponseTarget<'_>,
reply_tx: Option<tokio::sync::oneshot::Sender<Bytes>>,
log_errors: bool,
pending: Option<PendingConfirmedRequest>,
) -> Result<bool, Option<PendingConfirmedRequest>> {
let ResponseTarget {
source_mac,
source_network,
route,
} = target;
let Some(tx) = reply_tx.filter(|_| !route.requires_direct_response()) else {
return Err(pending);
};
match network.encode_response_for_handoff(
bacnet_network::layer::IssuedApdu {
apdu,
next_hop: source_mac,
destination: source_network,
expecting_reply: false,
priority: NetworkPriority::Normal,
},
route,
) {
Ok(npdu_buf) => {
let handed_off = tx.send(npdu_buf.freeze()).is_ok();
drop(pending);
if !handed_off && log_errors {
warn!("MS/TP reply receiver closed before response handoff");
}
Ok(handed_off)
}
Err(error) => {
if log_errors {
warn!(%error, "Failed to encode NPDU for MS/TP reply");
}
Err(pending)
}
}
}
impl<T: TransportPort + 'static> BACnetServer<T> {
pub(in crate::server) fn error_apdu_from_error(
invoke_id: u8,
service_choice: ConfirmedServiceChoice,
error: &Error,
) -> Apdu {
error_apdu_from_error(invoke_id, service_choice, error)
}
}
#[cfg(test)]
#[path = "../alarm_summary_tests.rs"]
mod alarm_summary_tests;
#[cfg(test)]
#[path = "typed_error_reply_tests.rs"]
mod typed_error_reply_tests;
#[cfg(test)]
mod encoded_apdu_tests {
use super::*;
#[test]
fn encoded_apdu_is_the_encoder_output_for_every_reply_kind() {
let replies = [
Apdu::SimpleAck(SimpleAck {
invoke_id: 7,
service_choice: ConfirmedServiceChoice::WRITE_PROPERTY,
}),
Apdu::ComplexAck(ComplexAck {
segmented: false,
more_follows: false,
invoke_id: 7,
sequence_number: None,
proposed_window_size: None,
service_choice: ConfirmedServiceChoice::READ_PROPERTY,
service_ack: Bytes::from(vec![0x5a; 1400]),
}),
error_apdu_from_error(
7,
ConfirmedServiceChoice::READ_PROPERTY,
&Error::protocol(ErrorClass::OBJECT, ErrorCode::UNKNOWN_OBJECT),
),
Apdu::Reject(RejectPdu {
invoke_id: 7,
reject_reason: RejectReason::UNRECOGNIZED_SERVICE,
}),
Apdu::Abort(AbortPdu {
sent_by_server: true,
invoke_id: 7,
abort_reason: AbortReason::OUT_OF_RESOURCES,
}),
];
for reply in &replies {
let mut expected = BytesMut::new();
encode_apdu(&mut expected, reply).unwrap();
assert_eq!(encoded_apdu(reply), expected, "{reply:?}");
}
}
}