Skip to main content

ScClientBuilder

Struct ScClientBuilder 

Source
pub struct ScClientBuilder { /* private fields */ }
Available on crate feature sc-tls only.
Expand description

SC-specific client builder.

Created by BACnetClient::sc_builder(). Requires the sc-tls feature.

Implementations§

Source§

impl ScClientBuilder

Source

pub async fn build( self, ) -> Result<BACnetClient<ScTransport<TlsWebSocket>>, Error>

Connect to the hub and start the client.

NPDU admission and reconnect configuration are validated before TLS lookup or dialing, and again when the transport starts. An error still consumes this builder and drops its inputs; this does not promise generic endpoint rollback.

The client’s transport() lends the hub connection-state watch, hub-connector events and link status, accepted Hub certificate observation and NPDU drop counts:

use bacnet_client::client::BACnetClient;
use bacnet_transport::sc::{ScConnectionState, ScEvent, ScReconnectConfig};
use bacnet_transport::sc_tls::ScNodeTlsConfig;

async fn watch_hub(tls: ScNodeTlsConfig) -> Result<(), bacnet_types::error::Error> {
    let client = BACnetClient::sc_builder()
        .hub_url("wss://hub.example.com/bacnet")
        .failover_hub_url("wss://failover.example.com/bacnet")
        .reconnect(ScReconnectConfig::default())
        .tls_config(tls)
        .vmac([0x02, 0, 0, 0, 0, 0x01])
        .device_uuid([0x42; 16])
        .build()
        .await?;
    // Owned receivers: they can live in their own tasks.
    let mut state = client.transport().connection_state_changes();
    tokio::spawn(async move {
        while state.changed().await.is_ok() {
            let _connected = *state.borrow_and_update() == ScConnectionState::Connected;
        }
    });
    let mut events = client.transport().connection_events();
    tokio::spawn(async move {
        use tokio::sync::broadcast::error::RecvError;
        loop {
            match events.recv().await {
                Ok(event) => {
                    let _failover = matches!(event, ScEvent::FailoverEngaged);
                }
                // Fell behind: the oldest events were dropped, not waited for.
                Err(RecvError::Lagged(_)) => continue,
                Err(RecvError::Closed) => break,
            }
        }
    });
    // Active hub, hub VMAC and heartbeat timing: poll when redrawing.
    let _status = client.transport().connection_status();
    // Owned historical TLS data, present only for an accepted active Hub.
    let _peer = client.transport().hub_peer_info().await;
    // A snapshot: poll it as often as needed.
    let _full_queue_drops = client.transport().npdu_drop_counts().full_drops;
    Ok(())
}
Source

pub async fn build_with_events( self, ) -> Result<(BACnetClient<ScTransport<TlsWebSocket>>, Receiver<ScEvent>), Error>

Connect to the hub and start the client, as build does, and also return a hub-connector event receiver subscribed before the client started.

The first ScEvent::Connected goes out while build runs, before a caller of transport().connection_events() can subscribe. This receiver is already subscribed then, so it holds that event, followed by every later one, with the same lag rules as connection_events().

use bacnet_client::client::BACnetClient;
use bacnet_transport::sc::ScEvent;
use bacnet_transport::sc_tls::ScNodeTlsConfig;

async fn first_event(tls: ScNodeTlsConfig) -> Result<(), bacnet_types::error::Error> {
    let (client, mut events) = BACnetClient::sc_builder()
        .hub_url("wss://hub.example.com/bacnet")
        .tls_config(tls)
        .vmac([0x02, 0, 0, 0, 0, 0x01])
        .device_uuid([0x42; 16])
        .build_with_events()
        .await?;
    let first = events.recv().await;
    assert!(matches!(first, Ok(ScEvent::Connected { .. })));
    drop(client);
    Ok(())
}
Source

pub async fn build_any(self) -> Result<BACnetClient<AnyTransport>, Error>

Connect to the hub and start a transport-erased BACnet/SC client.

Shares Self::build’s validation, TLS policy, identity, heartbeat and optional reconnect configuration. Identity, NPDU admission, reconnect and client options are checked before dialing; heartbeat timing is checked when the transport starts. The client’s type is the default, over AnyTransport.

use bacnet_client::client::BACnetClient;
use bacnet_transport::sc::ScReconnectConfig;
use bacnet_transport::sc_tls::ScNodeTlsConfig;
async fn connect(tls: ScNodeTlsConfig) -> Result<BACnetClient, bacnet_types::error::Error> {
    BACnetClient::sc_builder()
        .hub_url("wss://hub.example.com/bacnet")
        .tls_config(tls)
        .vmac([2, 0, 0, 0, 0, 1])
        .device_uuid([0x42; 16])
        .reconnect(ScReconnectConfig::default())
        .build_any().await
}
Source

pub async fn build_into<T>(self) -> Result<BACnetClient<T>, Error>
where T: TransportPort + From<ScTransport<TlsWebSocket>> + 'static,

Connect to the hub and start a BACnet/SC client over any transport type an ScTransport converts into (#2071). Self::build_any is this with AnyTransport.

Source

pub async fn build_any_with_events( self, ) -> Result<(BACnetClient<AnyTransport>, Receiver<ScEvent>), Error>

Connect to the hub and start a transport-erased BACnet/SC client, as build_any does, and also return a hub-connector event receiver subscribed before the client started (#1909).

As with build_with_events, the receiver holds the first ScEvent::Connected, which a later connection_events() call misses.

use bacnet_client::client::BACnetClient;
use bacnet_transport::sc::ScEvent;
use bacnet_transport::sc_tls::ScNodeTlsConfig;

async fn first_event(tls: ScNodeTlsConfig) -> Result<(), bacnet_types::error::Error> {
    let (client, mut events) = BACnetClient::sc_builder()
        .hub_url("wss://hub.example.com/bacnet")
        .tls_config(tls)
        .vmac([0x02, 0, 0, 0, 0, 0x01])
        .device_uuid([0x42; 16])
        .build_any_with_events()
        .await?;
    assert!(matches!(events.recv().await, Ok(ScEvent::Connected { .. })));
    drop(client);
    Ok(())
}
Source

pub async fn build_into_with_events<T>( self, ) -> Result<(BACnetClient<T>, Receiver<ScEvent>), Error>
where T: TransportPort + From<ScTransport<TlsWebSocket>> + 'static,

Self::build_into, also returning the hub-connector event receiver that Self::build_any_with_events returns (#2071).

Source§

impl ScClientBuilder

Source

pub fn event_channel_capacity(self, capacity: usize) -> Self

Set the independent event notification broadcast channel capacity.

Source

pub fn apdu_retries(self, retries: u8) -> Self

Set the number of APDU retries before a confirmed request times out.

Source

pub fn min_request_interval_ms(self, ms: u64) -> Self

Set the least time, in milliseconds, from the latest confirmed request to a destination finishing to the next being sent (default 0: no pacing; at most an hour). See ClientConfig::min_request_interval_ms.

Source

pub fn max_segments(self, max_segments: Option<u8>) -> Self

Set the maximum number of APDU segments this client accepts.

Values 0 and 1 fail at build time. Other finite non-rung values are conservatively rounded down in Confirmed-Request headers.

Source

pub fn segmented_response_accepted(self, accepted: bool) -> Self

Set whether this client accepts segmented responses.

Source

pub fn proposed_window_size(self, window_size: u8) -> Self

Set the proposed segmented-transfer window size.

Source

pub fn confirmed_cov_notification_ack_policy<F>(self, policy: F) -> Self

Set the response policy for decoded ConfirmedCOVNotifications.

Source§

impl ScClientBuilder

Source

pub fn hub_url(self, url: &str) -> Self

Set the hub WebSocket URL (e.g. wss://hub.example.com/bacnet).

Source

pub fn tls_config(self, config: ScNodeTlsConfig) -> Self

Set the validated local node TLS policy, shared across initial and reconnect attempts (including normal TLS resumption).

use bacnet_client::client::{BACnetClient, ScClientBuilder};
use bacnet_transport::sc_tls::ScNodeTlsConfig;
fn configured(tls: ScNodeTlsConfig) -> ScClientBuilder {
    BACnetClient::sc_builder().tls_config(tls)
}
ⓘ
use bacnet_client::client::BACnetClient;
fn raw(config: std::sync::Arc<tokio_rustls::rustls::ClientConfig>) {
    let _ = BACnetClient::sc_builder().tls_config(config);
}
Source

pub fn vmac(self, vmac: [u8; 6]) -> Self

Set the local VMAC address.

Source

pub fn device_uuid(self, uuid: [u8; 16]) -> Self

Set the persistent BACnet/SC device UUID.

Source

pub fn apdu_timeout_ms(self, ms: u64) -> Self

Set the APDU timeout in milliseconds.

Source

pub fn cov_channel_capacity(self, capacity: usize) -> Self

Set the COV notification broadcast channel capacity.

Source

pub fn heartbeat_interval_ms(self, ms: u64) -> Self

Set the heartbeat interval in milliseconds (default 30 000).

Source

pub fn heartbeat_timeout_ms(self, ms: u64) -> Self

Set the heartbeat timeout in milliseconds (default 60 000).

Source

pub fn connect_timeout_ms(self, ms: u64) -> Self

Set the connect timeout in milliseconds (default 10 000). It bounds each hub dial (host lookup, TCP, TLS and WebSocket upgrade), including the ones build() makes, and each Connect-Request handshake.

Source

pub fn npdu_per_origin_limit(self, limit: usize) -> Self

Set the maximum queued, unconsumed NPDUs per ingress path and source.

Defaults to 32. The accepted range is 1 through 256; checked before TLS lookup or dialing. The aggregate queue remains fixed at 256.

Source

pub fn control_rate_limit(self, limit: ScControlRateLimit) -> Self

Set the per-source limit on inbound network-layer messages (default ScControlRateLimit::default, #518, #1969); see ScTransport::with_control_rate_limit. Checked with the NPDU quota before TLS lookup or dialing.

Source

pub fn failover_hub_url(self, url: &str) -> Self

Set the failover hub WebSocket URL, dialled with the same TLS policy when the primary hub cannot be reached (Annex AB.5.2).

Requires reconnect: building with a failover hub and no reconnect configuration fails before any dial. Both URLs are checked (scheme, host) before dialling.

build starts on the failover hub when the primary cannot be dialled, and the connector also turns to it when the primary refuses the first Connect-Request or the reconnect attempts to the primary are used up. While on the failover hub it keeps trying the primary and returns to it when it can. Follow it through connection_events() and connection_status() on BACnetClient::transport.

Source

pub fn reconnect(self, config: ScReconnectConfig) -> Self

Enable automatic reconnection with the given configuration.

Reconnects redial the hub that was in use, with exponential backoff from initial_delay_ms up to max_delay_ms, for max_retries attempts, before turning to the failover hub, if one is set. Required by failover_hub_url.

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