use bacnet_objects::audit::{
AuditBatchStage, CompletedAuditReceipt, ConfirmedAuditNotificationOutcome, StagedAuditBatch,
};
use bacnet_objects::database::ObjectDatabase;
use bacnet_services::audit::AuditNotificationRequest;
use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
pub fn handle_audit_notification(
db: &mut ObjectDatabase,
sink: ObjectIdentifier,
request: &AuditNotificationRequest,
) -> Result<(), Error> {
handle_audit_notification_with_change(db, sink, request).map(|_| ())
}
pub(crate) fn handle_audit_notification_with_change(
db: &mut ObjectDatabase,
sink: ObjectIdentifier,
request: &AuditNotificationRequest,
) -> Result<bool, Error> {
if sink.object_type() != ObjectType::AUDIT_LOG {
return Err(service_request_denied());
}
{
let object = db.get_mut(&sink).ok_or_else(service_request_denied)?;
let storage = object
.audit_log_notification_sink_internal()
.ok_or_else(service_request_denied)?;
if !storage.notification_logging_enabled() {
return Err(service_request_denied());
}
}
let apdu_timeout_ms = configured_apdu_timeout(db)?;
let object = db
.get_mut(&sink)
.expect("sink existence was checked before Device timeout lookup");
let storage = object
.audit_log_notification_sink_internal()
.expect("sink capability was checked before Device timeout lookup");
storage.store_notifications_with_change(&request.notifications, apdu_timeout_ms)
}
pub fn handle_confirmed_audit_notification(
db: &mut ObjectDatabase,
sink: ObjectIdentifier,
request: &AuditNotificationRequest,
) -> Result<(), Error> {
handle_audit_notification(db, sink, request)
}
pub(crate) fn has_completed_confirmed_audit_receipt(
db: &mut ObjectDatabase,
sink: ObjectIdentifier,
key: &[u8],
now_unix_millis: u64,
) -> Result<bool, Error> {
if sink.object_type() != ObjectType::AUDIT_LOG {
return Err(service_request_denied());
}
let object = db.get_mut(&sink).ok_or_else(service_request_denied)?;
let storage = object
.audit_log_notification_sink_internal()
.ok_or_else(service_request_denied)?;
storage.has_completed_confirmed_receipt(key, now_unix_millis)
}
pub(crate) fn stage_audit_notification(
db: &mut ObjectDatabase,
sink: ObjectIdentifier,
request: &AuditNotificationRequest,
receipt: Option<CompletedAuditReceipt>,
) -> Result<AuditBatchStage, Error> {
if sink.object_type() != ObjectType::AUDIT_LOG {
return Err(service_request_denied());
}
{
let object = db.get_mut(&sink).ok_or_else(service_request_denied)?;
let storage = object
.audit_log_notification_sink_internal()
.ok_or_else(service_request_denied)?;
if !storage.notification_logging_enabled() {
return Err(service_request_denied());
}
}
let apdu_timeout_ms = configured_apdu_timeout(db)?;
let storage = db
.get_mut(&sink)
.and_then(|object| object.audit_log_notification_sink_internal())
.expect("sink capability was checked before Device timeout lookup");
storage.stage_notification_batch(&request.notifications, apdu_timeout_ms, receipt)
}
pub(crate) fn finish_audit_notification(
db: &mut ObjectDatabase,
sink: ObjectIdentifier,
staged: StagedAuditBatch,
) -> Result<(ConfirmedAuditNotificationOutcome, bool), Error> {
db.get_mut(&sink)
.and_then(|object| object.audit_log_notification_sink_internal())
.ok_or_else(service_request_denied)?
.finish_notification_batch(staged)
.map_err(|_| operational_problem())
}
fn configured_apdu_timeout(db: &ObjectDatabase) -> Result<u32, Error> {
let Some(device) = db.selected_device().and_then(|oid| db.get(&oid)) else {
return Err(operational_problem());
};
let Ok(PropertyValue::Unsigned(timeout)) =
device.read_property(PropertyIdentifier::APDU_TIMEOUT, None)
else {
return Err(operational_problem());
};
u32::try_from(timeout)
.ok()
.filter(|timeout| *timeout != 0)
.ok_or_else(operational_problem)
}
fn service_request_denied() -> Error {
Error::protocol(ErrorClass::SERVICES, ErrorCode::SERVICE_REQUEST_DENIED)
}
fn operational_problem() -> Error {
Error::protocol(ErrorClass::DEVICE, ErrorCode::OPERATIONAL_PROBLEM)
}