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>
impl<T: TransportPort + 'static> EndpointSession<T>
Sourcepub async fn write_audit_recipient(
&self,
recipient: Option<BACnetRecipient>,
) -> Result<(), Error>
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>
impl<T: TransportPort + 'static> EndpointSession<T>
Sourcepub fn with_device_writes(self, authorizer: DeviceWriteAuthorizer) -> Self
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.
Sourcepub fn with_reinitialize<F>(self, handler: F) -> Selfwhere
F: Fn(&ReinitializeContext, &mut ObjectDatabase) -> Result<(), Error> + Send + Sync + 'static,
pub fn with_reinitialize<F>(self, handler: F) -> Selfwhere
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.
Sourcepub fn with_reinit_password(self, password: impl Into<String>) -> Self
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>
impl<T: TransportPort + 'static> EndpointSession<T>
Sourcepub fn with_registered_network_port(self, oid: ObjectIdentifier) -> Self
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>
impl<T: TransportPort + 'static> EndpointSession<T>
Sourcepub fn new(
transport: T,
role: SessionRole,
config: SessionConfig,
) -> Result<Self, Error>
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());Sourcepub fn with_database(self, db: ObjectDatabase) -> Self
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.
Sourcepub fn with_source_audit_reporter(self, reporter: ObjectIdentifier) -> Self
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.
Sourcepub fn device(self, identity: DeviceIdentity) -> Self
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.
Sourcepub async fn start(&mut self) -> Result<(), Error>
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.
Sourcepub async fn stop(&mut self) -> Result<SessionExit, Error>
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.
Sourcepub fn client(&self) -> Option<&ClientRoleHandle>
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).
Sourcepub fn server(&self) -> Option<&ServerRoleHandle>
pub fn server(&self) -> Option<&ServerRoleHandle>
Borrows the server role (None when not composed).
Returns None for ClientOnly sessions,
and after stop.
Sourcepub fn cloned_client_handle(&self) -> Option<ClientRoleHandle>
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.
Sourcepub fn cloned_server_handle(&self) -> Option<ServerRoleHandle>
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.
Sourcepub async fn policy_counters(&self) -> PolicyCountersSnapshot
pub async fn policy_counters(&self) -> PolicyCountersSnapshot
Samples policy-outcome ownership counters.
Every classifier/policy outcome is counted, never silently dropped.
Sourcepub fn active_leases(&self) -> usize
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).
Sourcepub fn identity(&self) -> Option<&DeviceIdentity>
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.
Sourcepub fn bip_local_address(&self) -> Option<SocketAddrV4>
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.
Sourcepub fn foreign_registration(&self) -> Option<ForeignRegistrationSnapshot>
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.
Sourcepub fn mstp_diagnostics(&self) -> Option<MstpDiagnostics>
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.
Sourcepub fn session_role(&self) -> SessionRole
pub fn session_role(&self) -> SessionRole
Returns the composed session role (narrow admin).
Sourcepub fn is_running(&self) -> bool
pub fn is_running(&self) -> bool
Returns true while the session dispatch is running (narrow admin).
Sourcepub async fn broadcast_i_am(&self) -> Result<(), Error>
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.