bacnet-server 0.11.0

BACnet server: APDU dispatch and service handlers
Documentation
use std::sync::{Arc, Mutex as StdMutex};

use bacnet_encoding::apdu::decode_apdu;
use bacnet_encoding::npdu::decode_npdu;
use bacnet_objects::audit::{AuditLogObject, AuditLogPersistence, AuditLogSnapshot};
use bacnet_services::audit::{
    AuditLogQueryAck, AuditLogQueryRequest, BACnetAuditLogQueryParameters,
};
use bacnet_types::primitives::ObjectIdentifier;

use super::*;

#[derive(Default)]
struct MemoryPersistence(StdMutex<Option<AuditLogSnapshot>>);

impl AuditLogPersistence for MemoryPersistence {
    fn load(&self, _expected_object: ObjectIdentifier) -> Result<Option<AuditLogSnapshot>, Error> {
        Ok(self.0.lock().unwrap().clone())
    }

    fn commit(&self, snapshot: &AuditLogSnapshot) -> Result<(), Error> {
        *self.0.lock().unwrap() = Some(snapshot.clone());
        Ok(())
    }
}

#[tokio::test]
async fn audit_log_query_dispatch_returns_a_typed_complex_ack() {
    let audit_log = ObjectIdentifier::new(ObjectType::AUDIT_LOG, 7).unwrap();
    let target = ObjectIdentifier::new(ObjectType::DEVICE, 2).unwrap();
    let mut database = ObjectDatabase::new();
    database
        .add(Box::new(
            AuditLogObject::new(7, "Audit-7", 4, Arc::new(MemoryPersistence::default())).unwrap(),
        ))
        .unwrap();
    let db = Arc::new(RwLock::new(database));

    let request = AuditLogQueryRequest {
        audit_log,
        query_parameters: BACnetAuditLogQueryParameters::ByTarget {
            target_device_identifier: target,
            target_device_address: None,
            target_object_identifier: None,
            target_property_identifier: None,
            target_array_index: None,
            target_priority: None,
            operations: None,
            successful_actions_only: false,
        },
        start_at_sequence_number: None,
        requested_count: 10,
    };
    let mut service_request = BytesMut::new();
    request.try_encode(&mut service_request).unwrap();

    let network = Arc::new(NetworkLayer::new(BipTransport::new(
        Ipv4Addr::LOCALHOST,
        0,
        Ipv4Addr::BROADCAST,
    )));
    let cov_table = Arc::new(RwLock::new(CovSubscriptionTable::new()));
    let seg_ack_senders = Arc::new(Mutex::new(HashMap::new()));
    let seg_send_permits = Arc::new(Semaphore::new(MAX_SEG_SENDERS));
    let cov_in_flight = Arc::new(Semaphore::new(1));
    let server_tsm = Arc::new(Mutex::new(ServerTsm::new()));
    let notification_transactions = NotificationTransactions::new();
    let confirmed_request_tracker = Arc::new(ConfirmedRequestTracker::default());
    let device_bindings = Arc::new(RwLock::new(DeviceBindingTable::new()));
    let comm_state = Arc::new(AtomicU8::new(0));
    let dcc_timer = Arc::new(Mutex::new(None::<JoinHandle<()>>));
    let config = ServerConfig::default();
    let source_mac = MacAddr::from_slice(&[1]);
    let routed_source = NpduAddress {
        network: 100,
        mac_address: MacAddr::from_slice(&[0x0a, 0x0b]),
    };
    let confirmed = ConfirmedRequestPdu {
        segmented: false,
        more_follows: false,
        segmented_response_accepted: false,
        max_segments: None,
        max_apdu_length: 480,
        invoke_id: 0x42,
        sequence_number: None,
        proposed_window_size: None,
        service_choice: ConfirmedServiceChoice::AUDIT_LOG_QUERY,
        service_request: service_request.freeze(),
    };
    let (tx, rx) = oneshot::channel();

    BACnetServer::<BipTransport>::handle_confirmed_request(
        &db,
        &network,
        &cov_table,
        &seg_ack_senders,
        &seg_send_permits,
        &cov_in_flight,
        &server_tsm,
        &notification_transactions,
        &confirmed_request_tracker,
        &device_bindings,
        &comm_state,
        &dcc_timer,
        &config,
        &source_mac,
        Some(routed_source.clone()),
        confirmed,
        Some(tx),
    )
    .await;

    let npdu = decode_npdu(rx.await.expect("query response")).unwrap();
    assert_eq!(npdu.destination, Some(routed_source));
    let Apdu::ComplexAck(ack) = decode_apdu(npdu.payload).unwrap() else {
        panic!("expected AuditLogQuery ComplexAck");
    };
    assert_eq!(ack.invoke_id, 0x42);
    assert_eq!(ack.service_choice, ConfirmedServiceChoice::AUDIT_LOG_QUERY);
    assert_eq!(
        AuditLogQueryAck::decode(&ack.service_ack).unwrap(),
        AuditLogQueryAck {
            audit_log,
            records: Vec::new(),
            no_more_items: true,
        }
    );
}