Skip to main content

EndpointSession

Struct EndpointSession 

Source
pub struct EndpointSession<T: TransportPort + 'static = AnyTransport> { /* private fields */ }
Expand description

One owned single-device endpoint session.

Owns ONE EndpointIngress, the shared OutboundTransactionCoordinator, role registration, policy-outcome ownership, timers, egress and termination. Start-once/stop-once; Drop aborts without orphaning.

An optional DeviceIdentity is the single source for I-Am + Device readback + role limits when composed via device. Standalone sessions without an identity keep the SessionConfig 480 default untouched.

use bacnet_endpoint::session::{EndpointSession, SessionConfig, SessionRole};
use bacnet_transport::loopback::LoopbackTransport;

let (transport, _peer) = LoopbackTransport::pair(vec![0x01], vec![0x02]);
let mut session =
    EndpointSession::new(transport, SessionRole::Both, SessionConfig::default())?;
session.start().await?;
assert!(session.is_running());
session.stop().await?;

T defaults to AnyTransport, so EndpointSession alone names a session over any built-in transport; EndpointSession<BipTransport> and the like name one over a concrete transport, as the builders return.

Implementations§

Source§

impl<T: TransportPort + 'static> EndpointSession<T>

Source

pub async fn write_audit_recipient( &self, recipient: Option<BACnetRecipient>, ) -> Result<(), Error>

Write the active source profile’s Device Audit recipient as a trusted local operation.

Some(value) validates and changes the actual Device value, atomically admitting its two notifications: to the old and the new recipient, or to the new one and by global broadcast when the old is an Address the session’s number does not name. None is NULL relinquishment: it succeeds unchanged after the same live-owner checks. Equal values are also no-ops. This is runtime mutation, not initial provisioning or route setup.

Available in a running ClientOnly or Both source session. Local calls identify the local Device and have no network invoke ID; they do not invoke the inbound network authorizer. Unavailable routes or precommit resource failures leave state unchanged. Success establishes commit and owned delivery admission, not remote receipt. The session’s sealed state is rechecked after acquiring the database; pre-start, stopping, stopped and sessions without a source owner reject the operation.

Source§

impl<T: TransportPort + 'static> EndpointSession<T>

Source

pub fn with_device_writes(self, authorizer: DeviceWriteAuthorizer) -> Self

Enables authorized writes to the local Device’s Description and installed Audit recipient.

The callback is mandatory and receives the existing redacted mutation context. A refusal or panic denies the request before mutation. It must be fast, nonblocking and side-effect-free; see DeviceWriteAuthorizer. Other objects/properties and WritePropertyMultiple remain unsupported. An authorized NULL relinquishment succeeds without changing Description. Recipient writes require the complete source runtime installed by source selection.

Startup requires a server role and a concrete built-in local Device in the attached database (ObjectDatabase::local_device: the lowest instance when it holds several). Writes reach only that Device; any other Device is out of scope. A composed identity must match it and contain only the services the responder executes. Validation errors precede configuration mutation and transport startup, allowing correction and retry. The Device and identity then advertise exactly those services; the default session remains ReadProperty-only.

§Panics

Panics if startup has already consumed the session configuration.

Source

pub fn with_reinitialize<F>(self, handler: F) -> Self
where F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error> + Send + Sync + 'static,

Enables ReinitializeDevice, carried out by handler with the requester’s ReinitializeContext and the database write-locked.

Startup requirements and the advertised services are those of with_device_writes. The handler follows the rules on ReinitializeHandler. The SimpleACK goes out only after it returns, so register any restart with ReinitializeContext::after_reply instead of restarting inline: the hook runs once the egress has sent the SimpleACK, on the same dispatch task, and never for a refusal. It runs synchronously on the session’s single dispatch task, so a slow or blocking handler stalls the whole session: hand slow work such as backup files to a task. Without a password set by with_reinit_password any peer reaches it, and the Device-write authorizer doesn’t cover this service, so restrict sources through the context. Refuse with Error::protocol; a panic or an Error::reject is answered SERVICES / OTHER and the session keeps serving.

§Panics

Panics if startup has already consumed the session configuration.

Source

pub fn with_reinit_password(self, password: impl Into<String>) -> Self

Sets the password a ReinitializeDevice request must carry.

It takes effect only with with_reinitialize: startup refuses a password without a handler.

§Panics

Panics if startup has already consumed the session configuration.

Source§

impl<T: TransportPort + 'static> EndpointSession<T>

Source

pub fn with_registered_network_port(self, oid: ObjectIdentifier) -> Self

Select one existing built-in Network Port for the owned B/IP or B/IPv6 link, in whichever mode the transport runs: an IPV4 port for B/IP, an IPV6 one for B/IPv6. Startup requires a matching concrete interface, identity entry and database snapshot, and a B/IPv6 transport built with the served Device’s instance (the identity’s, else the database’s Device), which is its VMAC (#2057). It publishes the actual address, UDP port and MAC (a B/IPv6 link’s VMAC) before exposing responder roles.

There is no B/IPv6 endpoint builder, as there is no B/IPv6 server builder: compose the session over a Bip6Transport (the bacnet-transport/ipv6 feature) and declare the port with DeviceIdentity::with_bip6_port.

use std::net::Ipv6Addr;
use bacnet_endpoint::{DeviceIdentity, EndpointSession, SessionConfig, SessionRole};
use bacnet_transport::bip6::Bip6Transport;
use bacnet_types::{enums::ObjectType, primitives::ObjectIdentifier};

let identity = DeviceIdentity::new(1234, 42)?.with_bip6_port(1, 6, Ipv6Addr::LOCALHOST, 0)?;
let port = ObjectIdentifier::new(ObjectType::NETWORK_PORT, 1)?;
// The VMAC comes from the Device instance (Clause H.7.2).
let transport = Bip6Transport::new(Ipv6Addr::LOCALHOST, 0, Some(identity.instance()));
let mut session =
    EndpointSession::new(transport, SessionRole::ServerOnly, SessionConfig::default())?
        .device(identity)
        .with_registered_network_port(port);
session.start().await?; // Port 1 now serves the bound address and VMAC.
session.stop().await?;
Source§

impl<T: TransportPort + 'static> EndpointSession<T>

Source

pub fn new( transport: T, role: SessionRole, config: SessionConfig, ) -> Result<Self, Error>

Creates a session owning transport (not yet started).

Returns Error::Encoding when config.queue_capacity or config.read_work_limit is zero, or config.min_request_interval_ms is more than an hour. Normally built via BipEndpointBuilder, ScEndpointBuilder, or MstpEndpointBuilder instead of directly.

use bacnet_endpoint::session::{EndpointSession, SessionConfig, SessionRole};
use bacnet_transport::loopback::LoopbackTransport;

let (transport, _peer) = LoopbackTransport::pair(vec![0x01], vec![0x02]);
let bad = SessionConfig {
    queue_capacity: 0,
    ..SessionConfig::default()
};
assert!(EndpointSession::new(transport, SessionRole::Both, bad).is_err());
Source

pub fn with_database(self, db: ObjectDatabase) -> Self

Attaches the local object database (before start).

Must be called before start; when the server role is composed without an identity or database, an empty one is used. With a DeviceIdentity, omitting the database requests stock generation after receive-capacity resolution. Explicitly supplied databases, including empty ones, are preserved. Their selected Device must match the resolved identity and declare segmentation NONE, matching the narrow endpoint roles. The session retains ownership even in ClientOnly mode and shares this same database with the responder in Both mode.

§Panics

Panics if startup has already consumed the session configuration.

Source

pub fn with_source_audit_reporter(self, reporter: ObjectIdentifier) -> Self

Select the sole source Audit Reporter in the attached local database.

Activates bounded source READ reporting on direct IPv4 B/IP. The built-in Device must have a typed Audit recipient provision, even at Audit_Level NONE. Device choices use immutable builder route bindings; direct Address choices need no binding. Monitored_Objects must be absent. Both requires an explicit with_device_writes authorizer; ClientOnly permits trusted local changes and ServerOnly is rejected. Standalone BACnetClient source reporting remains unsupported.

start requires a concrete built-in local Device (ObjectDatabase::local_device: the lowest instance when the database holds several) matching the optional identity, and one selected Reporter capability with no conflicting source Reporter. Preflight validation changes no source flags and leaves configuration retryable; post-ingress failure joins terminal cleanup. Successful startup installs the Device recipient mutation owner and source projection without sending traffic. Device/Reporter membership is protected until owned frames quiesce. The private adapter forwards the original object behavior when sealed/released. Repeated calls before start replace the selection.

ⓘ
use bacnet_endpoint::session::source_reporter::SourceReporter;
use std::net::{Ipv4Addr, SocketAddrV4};
use bacnet_endpoint::{bip::BipEndpointBuilder, DeviceIdentity, SessionRole};
use bacnet_objects::{audit::AuditReporterObject, traits::BACnetObject};
use bacnet_types::{constructed::BACnetRecipient, enums::ObjectType, primitives::ObjectIdentifier};
let identity = DeviceIdentity::new(123, 42)?;
let mut db = identity.build_database()?;
let logger = ObjectIdentifier::new(ObjectType::DEVICE, 999)?;
db.get_mut(&identity.device_oid()).unwrap().device_authority_internal().unwrap()
    .provision_audit_recipient(BACnetRecipient::Device(logger))?;
let reporter = AuditReporterObject::new(1, "Source configuration")?;
let reporter_oid = reporter.object_identifier();
db.add(Box::new(reporter))?;
let mut session = BipEndpointBuilder::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::BROADCAST)
    .role(SessionRole::ClientOnly).database(db).device(identity)
    .source_audit_device_binding(logger, SocketAddrV4::new(Ipv4Addr::LOCALHOST, 47808))
    .build_session()?.with_source_audit_reporter(reporter_oid);
session.start().await?; // Startup emits no audit traffic.
session.stop().await?;
§Panics

Panics if startup has already consumed the session configuration.

Source

pub fn device(self, identity: DeviceIdentity) -> Self

Composes the single Device identity (before start).

Truth direction: identity overrides the standalone SessionConfig 480 default for the client role max-APDU; timers/retries stay from SessionConfig. Startup resolves the ceiling against the owned link. An absent database requests stock generation; an explicitly supplied database is preserved and its selected Device must match the resolved identity. Sessions without an identity keep the 480 default.

§Panics

Panics if startup has already consumed the session configuration.

Source

pub async fn start(&mut self) -> Result<(), Error>

Starts ingress, roles and the single dispatch consumer once.

Start-once: a second call returns Error::Encoding without binding again. Takes &mut self so only the owner can start. Source-profile validation runs before lifecycle consumption or ingress startup; its errors leave the session ready for correction and retry. Profile initialization errors after ingress starts cancel and join ingress before returning, leaving the session terminal. Canceling that cleanup leaves a stopping session whose teardown can resume with stop.

Source

pub async fn stop(&mut self) -> Result<SessionExit, Error>

Stops dispatch, roles and ingress once; joins termination.

Returns the dispatch SessionExit. A canceled stop retains its joins and sealed membership protection; a later call finishes teardown. Calling before start() or twice returns Error::Encoding. Takes &mut self so only the owner can stop; cloned role handles observe shutdown and fail closed.

With an attached database, waits for its lock and for queued durable save attempts, including corrections for abandoned staged writes. Release any application-held database guard so shutdown can progress. A stalled backend keeps this call pending; completion does not certify backend success or power-loss durability. The database remains attached after termination.

Source

pub fn client(&self) -> Option<&ClientRoleHandle>

Borrows the client role (None when not composed).

Returns None for ServerOnly sessions, and after stop (handles are released at termination).

Source

pub fn server(&self) -> Option<&ServerRoleHandle>

Borrows the server role (None when not composed).

Returns None for ClientOnly sessions, and after stop.

Source

pub fn cloned_client_handle(&self) -> Option<ClientRoleHandle>

Clones the client role handle with its session binding.

The clone holds only a Weak session token: it works while the session runs and fails closed after stop()/drop. Used to hand the client role to tasks that outlive the borrow.

Source

pub fn cloned_server_handle(&self) -> Option<ServerRoleHandle>

Clones the server role handle with its session binding.

Same Weak-token semantics as cloned_client_handle; see is_session_alive.

Source

pub async fn policy_counters(&self) -> PolicyCountersSnapshot

Samples policy-outcome ownership counters.

Every classifier/policy outcome is counted, never silently dropped.

Source

pub fn active_leases(&self) -> usize

Samples the shared outbound lease count.

Returns usize::MAX only when the coordinator lock is poisoned (internal invariant violation surfaced honestly, never hidden).

Source

pub fn identity(&self) -> Option<&DeviceIdentity>

Borrows the composed identity (narrow admin; no lifecycle).

RB-15 narrow admin borrow: roles hold no transport/session lifecycle; only the session owner exposes identity + counters + coordinator. A role handle attempting stop/transport access fails at compile time (no such method) and at runtime its post-stop calls fail closed.

Source

pub fn bip_local_address(&self) -> Option<SocketAddrV4>

Actual announced B/IP address after successful startup. Absent before publication, while stopping, after stop, or for other links.

Source

pub fn foreign_registration(&self) -> Option<ForeignRegistrationSnapshot>

How the transport’s foreign-device registration is going (#938): whether it holds a live lease, the time left on it, and the last attempt’s result and outcome, a BBMD’s refusal included. It is there for a session BipEndpointBuilder built, in any mode and after stop, and None for any other: take a BipTransport::bvlc_client_status before handing a transport to Self::new.

Source

pub fn mstp_diagnostics(&self) -> Option<MstpDiagnostics>

The MS/TP transport’s counters (#958): a handle to the same MstpDiagnostics the transport updates, readable in any state, after stop included. It is there for a session MstpEndpointBuilder built, and None for any other: take MstpTransport::diagnostics before handing a transport to Self::new.

Source

pub fn session_role(&self) -> SessionRole

Returns the composed session role (narrow admin).

Source

pub fn is_running(&self) -> bool

Returns true while the session dispatch is running (narrow admin).

Source

pub async fn broadcast_i_am(&self) -> Result<(), Error>

Broadcasts an I-Am consistent with the composed identity.

Missing-identity path: errors without sending (no invented device). Present-identity path: encodes encode_iam_apdu and sends one local-broadcast Unconfirmed-Request via the session egress. Field-for-field identical to the server discovery I-Am built from server_config. Transport-dependent: B/IP reaches the local subnet broadcast; SC relays via the hub as a broadcast NPDU. Fails when the session is not running.

Trait Implementations§

Source§

impl<T: TransportPort + 'static> Drop for EndpointSession<T>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

Source§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

Source§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

Source§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more