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;
pub(super) async fn read_property_response(
db: &RwLock<ObjectDatabase>,
request: &ConfirmedRequestPdu,
writes: bool,
reinitialize: bool,
registered_port: Option<ObjectIdentifier>,
work_limit: usize,
) -> Apdu {
read_property_response_observed(
db,
None,
DeviceExecution::Endpoint {
writes,
reinitialize,
},
registered_port,
work_limit,
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,
budget: crate::server::ReadPropertyMultipleBudget,
registered_port: Option<ObjectIdentifier>,
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 view = DeviceReadContext::new(&db, DeviceExecution::FullServer)
.with_registered_port(registered_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) async fn read_property_response_observed(
db: &RwLock<ObjectDatabase>,
tables: Option<LiveTables<'_>>,
execution: DeviceExecution,
registered_port: Option<ObjectIdentifier>,
work_limit: usize,
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 lookup_oid =
handlers::resolve_read_target(&db, &decoded.object_identifier, registered_port);
let view = DeviceReadContext::new(&db, execution)
.with_registered_port(registered_port)
.with_work_limit(work_limit);
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(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::Reject { reason } = error {
return Apdu::Reject(RejectPdu {
invoke_id,
reject_reason: RejectReason::from_raw(*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::Protocol { class, code } | Error::Structured { class, code, .. } => (
ErrorClass::from_raw(*class as u16),
ErrorCode::from_raw(*code as u16),
),
_ => (ErrorClass::SERVICES, ErrorCode::OTHER),
}
}
pub(in crate::server) async fn send_unsegmented_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>>,
pending: Option<PendingConfirmedRequest>,
) {
send_response(
network,
response,
ResponseTarget {
source_mac,
source_network,
route,
},
reply_tx,
true,
pending,
)
.await;
}
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>>,
) {
send_response(
network,
response,
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.provenance().is_direct_peer() {
None
} else {
reply_tx
};
if let Some(tx) = reply_tx {
use bacnet_encoding::npdu::{encode_npdu, Npdu};
let npdu = Npdu {
is_network_message: false,
expecting_reply: false,
priority: NetworkPriority::NORMAL,
destination: source_network.cloned(),
source: None,
payload: apdu_bytes.clone(),
..Npdu::default()
};
let mut npdu_buf = BytesMut::with_capacity(2 + apdu_bytes.len());
match encode_npdu(&mut npdu_buf, &npdu) {
Ok(()) => {
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,
}
async fn send_response<T: TransportPort + 'static>(
network: &NetworkLayer<T>,
response: &Apdu,
target: ResponseTarget<'_>,
reply_tx: Option<tokio::sync::oneshot::Sender<Bytes>>,
log_errors: bool,
pending: Option<PendingConfirmedRequest>,
) {
let ResponseTarget {
source_mac,
source_network,
route,
} = target;
let mut buf = BytesMut::new();
encode_apdu(&mut buf, response).expect("valid APDU encoding");
let reply_tx = if route.provenance().is_direct_peer() {
None
} else {
reply_tx
};
if let Some(tx) = reply_tx {
use bacnet_encoding::npdu::{encode_npdu, Npdu};
let apdu_bytes = buf.freeze();
let npdu = Npdu {
is_network_message: false,
expecting_reply: false,
priority: NetworkPriority::NORMAL,
destination: source_network.cloned(),
source: None,
payload: apdu_bytes.clone(),
..Npdu::default()
};
let mut npdu_buf = BytesMut::with_capacity(2 + apdu_bytes.len());
match encode_npdu(&mut npdu_buf, &npdu) {
Ok(()) => {
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");
}
}
Err(error) => {
if log_errors {
warn!(%error, "Failed to encode NPDU for MS/TP reply");
}
if let Err(error) = BACnetServer::<T>::issue_terminal_response(
network,
&apdu_bytes,
source_mac,
source_network,
route,
pending,
)
.await
{
if log_errors {
warn!(%error, "Failed to send response");
}
}
}
}
} else if let Err(error) = BACnetServer::<T>::issue_terminal_response(
network,
&buf,
source_mac,
source_network,
route,
pending,
)
.await
{
if log_errors {
warn!(%error, "Failed to send response");
}
}
}
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;