bacnet-endpoint 0.15.0

BACnet endpoint that owns one transport and runs client and server roles through it
Documentation
//! Resolve Device declarations while the session still owns an unstarted link.
use crate::session::EndpointSession;
use bacnet_transport::port::TransportPort;
use bacnet_types::error::Error;
use std::sync::Arc;
use tokio::sync::RwLock;

impl<T: TransportPort + 'static> EndpointSession<T> {
    pub(super) async fn prepare_identity(&mut self) -> Result<(), Error> {
        let Some(identity) = self.identity.as_ref() else {
            return Ok(());
        };
        let identity = identity.resolve(self.local_receive_capacity)?;
        // Validate before replacing configuration or constructing an object.
        if let Some(database) = &self.database {
            let database = database.read().await;
            if let Some(oid) = database.selected_device() {
                identity.validate_device(database.get(&oid).expect("selected under guard"))?;
            }
        }
        if self.database.is_none() {
            self.database = Some(Arc::new(RwLock::new(identity.build_database()?)));
        }
        self.client_config.max_apdu_length = identity.max_apdu_length();
        self.config.max_apdu_length = identity.max_apdu_length();
        self.identity = Some(identity);
        Ok(())
    }
}

/// Keep generated declarations local to a successful preflight. In particular,
/// failure or cancellation must not turn a later identity correction into a
/// mismatch against a stock Device generated by the abandoned attempt.
struct IdentityPreparation<'a, T: TransportPort + 'static> {
    session: &'a mut EndpointSession<T>,
    original: Option<bacnet_objects::DeviceIdentity>,
    had_database: bool,
    client_capacity: u16,
    session_capacity: u16,
    committed: bool,
}

impl<T: TransportPort + 'static> Drop for IdentityPreparation<'_, T> {
    fn drop(&mut self) {
        if !self.committed {
            self.session.identity = self.original.take();
            self.session.client_config.max_apdu_length = self.client_capacity;
            self.session.config.max_apdu_length = self.session_capacity;
            if !self.had_database {
                self.session.database = None;
            }
        }
    }
}

impl<T: TransportPort + 'static> EndpointSession<T> {
    pub(super) async fn prepare_start(
        &mut self,
    ) -> Result<
        (
            Option<bacnet_types::primitives::ObjectIdentifier>,
            Option<crate::source_audit::recipient::SourceRoutes>,
        ),
        Error,
    > {
        let mut preparation = IdentityPreparation {
            original: self.identity.clone(),
            had_database: self.database.is_some(),
            client_capacity: self.client_config.max_apdu_length,
            session_capacity: self.config.max_apdu_length,
            session: self,
            committed: false,
        };
        preparation.session.validate_segmentation().await?;
        preparation.session.prepare_identity().await?;
        let device = preparation.session.validate_device_execution()?;
        let routes = preparation.session.prepare_source_audit_reporter()?;
        preparation.session.prepare_registered_port().await?;
        preparation.session.commit_device_profile(device);
        preparation.committed = true;
        Ok((device, routes))
    }
}