Skip to main content

bacnet_transport/
port.rs

1//! TransportPort trait for BACnet data-link transport abstraction.
2//!
3//! MAC addresses are opaque byte slices whose format depends on the transport:
4//! - BACnet/IP (Annex J): 6 bytes (4-byte IPv4 + 2-byte port, big-endian)
5//! - BACnet/IPv6 (Annex U): 3 bytes, the node's VMAC (U.1.3); the transport
6//!   maps it to the IPv6 address and port itself
7//! - BACnet/Ethernet (Clause 7): 6 bytes (IEEE 802 MAC)
8//! - MS/TP (Clause 9): 1 byte (station address 0-254)
9//! - BACnet/SC (Annex AB): 6 bytes, the node's VMAC
10
11pub use crate::direct_response::{DirectResponse, DirectResponseScope, ResponseScopeGuard};
12pub use crate::principal::ScPrincipal;
13use bacnet_npdu::NpduLimits;
14use bacnet_types::MacAddr;
15use bacnet_types::error::Error;
16use bytes::Bytes;
17use std::sync::Arc;
18use tokio::sync::{mpsc, oneshot, watch};
19
20/// An owned copy of a link's group-destination rule
21/// ([`TransportPort::is_group_destination`]), for a sender that checks a
22/// destination away from the task that owns the transport, before it takes
23/// any state for the send (#1479). Clones share one rule.
24#[derive(Clone, Default)]
25pub struct GroupDestinations(Option<Arc<GroupRule>>);
26
27/// The predicate inside a [`GroupDestinations`].
28type GroupRule = dyn Fn(&[u8]) -> bool + Send + Sync;
29
30impl GroupDestinations {
31    /// The rule `is_group` gives.
32    pub fn new(is_group: impl Fn(&[u8]) -> bool + Send + Sync + 'static) -> Self {
33        Self(Some(Arc::new(is_group)))
34    }
35
36    /// No rule: nothing matches. A sender holding this leaves the check to the
37    /// send path, which asks the transport itself.
38    pub const fn unknown() -> Self {
39        Self(None)
40    }
41
42    /// Whether `mac` is a group destination under this rule.
43    pub fn contains(&self, mac: &[u8]) -> bool {
44        self.0.as_ref().is_some_and(|is_group| is_group(mac))
45    }
46}
47
48impl std::fmt::Debug for GroupDestinations {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        f.debug_tuple("GroupDestinations")
51            .field(&if self.0.is_some() { "rule" } else { "unknown" })
52            .finish()
53    }
54}
55
56/// Data-link attributes that accompany an NPDU.
57///
58/// BACnet/SC maps these to Annex AB Data Options. Transports that cannot
59/// carry data attributes ignore them on send and report an empty list on
60/// receive.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct DataAttribute {
63    /// Attribute/header option type. BACnet/SC uses values 1..31.
64    pub option_type: u8,
65    /// Whether the final consumer must understand this attribute.
66    pub must_understand: bool,
67    /// Attribute payload bytes, if any.
68    pub data: Vec<u8>,
69}
70
71/// Verified immediate direct-SC peer and connection incarnation.
72///
73/// The fingerprint is SHA-256 of the exact verified leaf DER, not of the
74/// claimed VMAC, UUID or routed address. Certificate renewal changes the
75/// fingerprint; reconnecting with the same leaf changes the incarnation.
76/// This sealed snapshot survives queueing and connection retirement. It is
77/// evidence for application policy, not authorization or a response route.
78///
79/// Verified identities cannot be constructed by downstream callers:
80/// ```compile_fail
81/// use bacnet_transport::port::DirectScIdentity;
82/// let forged = DirectScIdentity { leaf_sha256: [0; 32], incarnation: 1 };
83/// ```
84#[derive(Clone, Copy, PartialEq, Eq, Hash)]
85pub struct DirectScIdentity {
86    leaf_sha256: [u8; 32],
87    incarnation: u64,
88}
89impl DirectScIdentity {
90    /// Exact verified leaf-DER SHA-256, suitable for an installation's pins.
91    pub fn leaf_sha256(self) -> [u8; 32] {
92        self.leaf_sha256
93    }
94
95    /// Opaque process-lifetime connection identifier. Never persist or reuse it
96    /// as an identity across processes; no ordering contract is exposed.
97    pub fn incarnation(self) -> u64 {
98        self.incarnation
99    }
100
101    /// This hop's leaf-certificate principal, without the connection
102    /// incarnation. For a routed request it is the forwarding router's.
103    pub fn principal(self) -> ScPrincipal {
104        ScPrincipal::from_leaf_sha256(self.leaf_sha256)
105    }
106
107    // Only the sc-tls direct accept and dial paths mint verified identities.
108    #[cfg(any(test, feature = "sc-tls"))]
109    pub(crate) fn verified(leaf_sha256: [u8; 32], incarnation: u64) -> Self {
110        Self {
111            leaf_sha256,
112            incarnation,
113        }
114    }
115}
116impl std::fmt::Debug for DirectScIdentity {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        f.debug_struct("DirectScIdentity").finish_non_exhaustive()
119    }
120}
121
122/// Sealed transport provenance, copied unchanged through queued ingress.
123///
124/// Direct SC provenance records the verified immediate TLS leaf and the
125/// committed connection incarnation after Connect acceptance. Complete work
126/// already admitted may finish under this original snapshot after close or
127/// replacement. Claimed VMAC/UUID/SNET/SADR remain separate claims; there is
128/// no certificate-to-claim binding or response-channel confinement here.
129///
130/// Relayed provenance asserts the authenticated Hub channel and admitted
131/// origin-field shape, never an end-to-end leaf principal. Hub admission's
132/// own TLS-client channel has a separate scope-only assertion, before Connect
133/// acceptance, and cannot mint a downstream direct identity.
134///
135/// Plain transports and test doubles use [`Self::unverified`]. Verified
136/// constructors are crate-private to trusted TLS/admission owners. A custom
137/// transport that forwards a verified envelope is part of that trust boundary.
138/// Data attributes remain wire values and cannot upgrade provenance.
139///
140/// With `sc-tls`, direct SC provenance also holds the verified leaf
141/// certificate (`leaf_certificate()`). A connection's direct provenance is
142/// built once and shared, so a clone is a reference count. Equality and
143/// hashing follow the [`DirectScIdentity`], whose SHA-256 already names that
144/// certificate.
145///
146/// ```compile_fail
147/// use bacnet_transport::port::TransportProvenance;
148/// let forged = TransportProvenance::verified_hub_channel();
149/// ```
150#[derive(Clone, PartialEq, Eq, Hash)]
151pub struct TransportProvenance {
152    kind: ProvenanceKind,
153}
154
155#[derive(Clone, PartialEq, Eq, Hash)]
156enum ProvenanceKind {
157    Unverified,
158    // Constructed only by sc-tls direct ingress.
159    #[cfg_attr(not(feature = "sc-tls"), allow(dead_code))]
160    DirectPeer(Arc<VerifiedDirectPeer>),
161    HubChannel,
162    RelayedOrigin,
163}
164
165/// One direct connection's verified identity and leaf certificate, shared by
166/// every NPDU it admits. Equality and hashing use the identity alone: its
167/// SHA-256 names the certificate.
168struct VerifiedDirectPeer {
169    identity: DirectScIdentity,
170    // Read only through `leaf_certificate()`, with `sc-tls`.
171    #[cfg_attr(not(feature = "sc-tls"), allow(dead_code))]
172    certificate: LeafCertificate,
173}
174
175impl PartialEq for VerifiedDirectPeer {
176    fn eq(&self, other: &Self) -> bool {
177        self.identity == other.identity
178    }
179}
180impl Eq for VerifiedDirectPeer {}
181impl std::hash::Hash for VerifiedDirectPeer {
182    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
183        self.identity.hash(state);
184    }
185}
186
187/// The verified leaf certificate direct SC provenance carries (#1965):
188/// present with `sc-tls` when the TLS layer supplied it, empty otherwise.
189#[derive(Default)]
190pub(crate) struct LeafCertificate(
191    #[cfg(feature = "sc-tls")] Option<Arc<crate::sc_tls::ScPeerCertificate>>,
192);
193
194impl LeafCertificate {
195    /// The verified leaf `certificate` of a direct SC connection.
196    #[cfg(feature = "sc-tls")]
197    pub(crate) fn verified(certificate: Arc<crate::sc_tls::ScPeerCertificate>) -> Self {
198        Self(Some(certificate))
199    }
200}
201
202impl TransportProvenance {
203    /// Unverified legacy origin; no authentication assertion.
204    pub fn unverified() -> Self {
205        Self {
206            kind: ProvenanceKind::Unverified,
207        }
208    }
209
210    /// Only built-in verified direct TLS ingress may mint this principal-bearing
211    /// value, once per connection: clones share it.
212    #[cfg(any(test, feature = "sc-tls"))]
213    pub(crate) fn verified_direct_peer(
214        identity: DirectScIdentity,
215        certificate: LeafCertificate,
216    ) -> Self {
217        Self {
218            kind: ProvenanceKind::DirectPeer(Arc::new(VerifiedDirectPeer {
219                identity,
220                certificate,
221            })),
222        }
223    }
224
225    /// Hub registration callback's own verified TLS-client channel, not a leaf
226    /// identity for application ingress relayed through that Hub.
227    #[cfg(any(test, feature = "sc-tls"))]
228    pub(crate) fn verified_hub_channel() -> Self {
229        Self {
230            kind: ProvenanceKind::HubChannel,
231        }
232    }
233
234    pub(crate) fn verified_relayed_origin() -> Self {
235        Self {
236            kind: ProvenanceKind::RelayedOrigin,
237        }
238    }
239
240    /// Verified direct identity, absent on unverified and both Hub scopes.
241    pub fn direct_sc_identity(&self) -> Option<DirectScIdentity> {
242        match &self.kind {
243            ProvenanceKind::DirectPeer(peer) => Some(peer.identity),
244            _ => None,
245        }
246    }
247
248    /// The immediate direct-SC hop's verified leaf certificate, the one
249    /// [`Self::principal`] fingerprints: for a routed request, the router
250    /// that forwarded it. `None` for every other ingress, a hub's relayed
251    /// origins and its own channel included. The server's policy contexts
252    /// withhold it for routed requests, as they do the principal.
253    #[cfg(feature = "sc-tls")]
254    pub fn leaf_certificate(&self) -> Option<&crate::sc_tls::ScPeerCertificate> {
255        match &self.kind {
256            ProvenanceKind::DirectPeer(peer) => peer.certificate.0.as_deref(),
257            _ => None,
258        }
259    }
260
261    /// The immediate direct-SC hop's leaf-certificate principal: for a routed
262    /// request, the router that forwarded it. `None` for every other ingress,
263    /// a hub's relayed origins and its own channel included. The server's
264    /// policy contexts withhold it from `principal()` for routed requests.
265    pub fn principal(&self) -> Option<ScPrincipal> {
266        self.direct_sc_identity().map(DirectScIdentity::principal)
267    }
268
269    /// True only for the unverified legacy variant.
270    pub fn is_unverified(&self) -> bool {
271        self.kind == ProvenanceKind::Unverified
272    }
273
274    /// True only for direct-SC ingress with a verified identity.
275    pub fn is_direct_peer(&self) -> bool {
276        matches!(self.kind, ProvenanceKind::DirectPeer(..))
277    }
278
279    /// True only for the Hub admission callback's own TLS-client channel.
280    pub fn is_hub_channel(&self) -> bool {
281        self.kind == ProvenanceKind::HubChannel
282    }
283
284    /// True only for the verified relayed-origin variant.
285    pub fn is_relayed_origin(&self) -> bool {
286        self.kind == ProvenanceKind::RelayedOrigin
287    }
288
289    /// True for any verified scope; this does not imply a direct principal.
290    pub fn is_verified(&self) -> bool {
291        self.kind != ProvenanceKind::Unverified
292    }
293
294    /// Human-readable scope, with no identity material.
295    pub fn scope(&self) -> &'static str {
296        match self.kind {
297            ProvenanceKind::Unverified => "unverified legacy origin (no assertion)",
298            ProvenanceKind::DirectPeer(..) => {
299                "authenticated immediate direct SC-TLS peer and connection incarnation"
300            }
301            ProvenanceKind::HubChannel => {
302                "authenticated Hub admission TLS-client channel (scope only)"
303            }
304            ProvenanceKind::RelayedOrigin => {
305                "independently validated SC-hub relayed origin (post source_admission; hub peer is not the leaf)"
306            }
307        }
308    }
309}
310impl Default for TransportProvenance {
311    fn default() -> Self {
312        Self::unverified()
313    }
314}
315impl std::fmt::Debug for TransportProvenance {
316    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317        let label = match self.kind {
318            ProvenanceKind::Unverified => "unverified",
319            ProvenanceKind::DirectPeer(..) => "verified-direct-peer",
320            ProvenanceKind::HubChannel => "verified-hub-channel",
321            ProvenanceKind::RelayedOrigin => "verified-relayed-origin",
322        };
323        f.debug_struct("TransportProvenance")
324            .field("kind", &label)
325            .finish_non_exhaustive()
326    }
327}
328
329/// A received NPDU from the transport layer.
330pub struct ReceivedNpdu {
331    /// Raw NPDU bytes (NPDU header + APDU/network-message payload).
332    pub npdu: Bytes,
333    /// Source MAC address in transport-native format (claimed identity; see
334    /// [`TransportProvenance`]. On SC this is the source VMAC, not a
335    /// certificate principal).
336    pub source_mac: MacAddr,
337    /// Whether the NPDU arrived using a data-link multicast or broadcast destination.
338    ///
339    /// This is ingress provenance, not a conclusion about the NPDU's logical
340    /// destination: routers may legitimately send a routed unicast over a
341    /// group data-link destination.
342    pub link_layer_group: bool,
343    /// Optional data attributes carried by the data link.
344    pub data_attributes: Vec<DataAttribute>,
345    /// Honest transport + origin provenance, immutable by value. Cloning
346    /// preserves the meaning; it never upgrades or downgrades trust.
347    pub provenance: TransportProvenance,
348    /// Optional sealed original-connection response capability. A verified
349    /// direct server reply must fail closed if this is missing or mismatched.
350    pub direct_response: Option<DirectResponse>,
351    /// Optional reply channel for MS/TP DataExpectingReply frames.
352    /// When present, the application layer should send the reply NPDU bytes
353    /// through this channel instead of via normal send_unicast.
354    pub reply_tx: Option<oneshot::Sender<Bytes>>,
355}
356
357impl ReceivedNpdu {
358    /// Build an explicitly unverified legacy-origin envelope.
359    ///
360    /// The one-line helper for plain transports, `AnyTransport` delegation
361    /// sites that synthesize, loopback, and test doubles: no data-link
362    /// implementation is forced to pretend auth support.
363    pub fn unverified(
364        npdu: Bytes,
365        source_mac: MacAddr,
366        link_layer_group: bool,
367        data_attributes: Vec<DataAttribute>,
368        reply_tx: Option<oneshot::Sender<Bytes>>,
369    ) -> Self {
370        Self {
371            npdu,
372            source_mac,
373            link_layer_group,
374            data_attributes,
375            provenance: TransportProvenance::unverified(),
376            direct_response: None,
377            reply_tx,
378        }
379    }
380}
381
382impl Clone for ReceivedNpdu {
383    fn clone(&self) -> Self {
384        Self {
385            npdu: self.npdu.clone(),
386            source_mac: self.source_mac.clone(),
387            link_layer_group: self.link_layer_group,
388            data_attributes: self.data_attributes.clone(),
389            provenance: self.provenance.clone(),
390            direct_response: self.direct_response.clone(),
391            reply_tx: None, // oneshot::Sender is not Clone; clones lose the reply channel
392        }
393    }
394}
395
396impl std::fmt::Debug for ReceivedNpdu {
397    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398        // Redacted: MAC bytes and NPDU payload lengths only; no key material.
399        // Follows the TimeSyncSourceRestriction finish_non_exhaustive pattern.
400        f.debug_struct("ReceivedNpdu")
401            .field("npdu_len", &self.npdu.len())
402            .field("source_mac_len", &self.source_mac.len())
403            .field("link_layer_group", &self.link_layer_group)
404            .field("data_attributes", &self.data_attributes)
405            .field("provenance", &self.provenance)
406            .field("reply_tx", &self.reply_tx.as_ref().map(|_| "Some(Sender)"))
407            .finish_non_exhaustive()
408    }
409}
410
411/// What a B/IP transport reports for registration as a Network Port
412/// ([`TransportPort::bip_port`]).
413///
414/// It is `#[non_exhaustive]`: outside this crate, build it with
415/// [`BipPort::new`] and add what the port lends with the `with_` methods.
416#[derive(Clone)]
417#[non_exhaustive]
418pub struct BipPort {
419    /// The IPv4 address and UDP port: configured before start, announced
420    /// after.
421    pub endpoint: std::net::SocketAddrV4,
422    /// The B/IP mode, with what its Network Port properties need.
423    pub mode: bacnet_types::bip_port::BipPortMode,
424    /// The port's B/IP multicast group (Annex J.8), lent when the transport
425    /// can use multicast at all, on or off: the Network Port then serves
426    /// BACnet_IP_Multicast_Address. `None` for a port that can't.
427    pub multicast: Option<std::sync::Arc<dyn bacnet_types::bip_port::IpMulticast>>,
428}
429
430impl BipPort {
431    /// A port at `endpoint` in `mode`, lending no multicast group.
432    pub fn new(
433        endpoint: std::net::SocketAddrV4,
434        mode: bacnet_types::bip_port::BipPortMode,
435    ) -> Self {
436        Self {
437            endpoint,
438            mode,
439            multicast: None,
440        }
441    }
442
443    /// This port, lending `multicast` as its B/IP multicast group.
444    pub fn with_multicast(
445        self,
446        multicast: Option<std::sync::Arc<dyn bacnet_types::bip_port::IpMulticast>>,
447    ) -> Self {
448        Self { multicast, ..self }
449    }
450}
451
452impl std::fmt::Debug for BipPort {
453    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454        let group = self
455            .multicast
456            .as_ref()
457            .map(|multicast| std::net::Ipv4Addr::from(multicast.group()));
458        f.debug_struct("BipPort")
459            .field("endpoint", &self.endpoint)
460            .field("mode", &self.mode)
461            .field("multicast", &group)
462            .finish()
463    }
464}
465
466/// What a B/IPv6 transport reports for registration as a Network Port
467/// ([`TransportPort::bip6_port`]).
468#[derive(Debug, Clone)]
469pub struct Bip6Port {
470    /// The IPv6 address and UDP port: configured before start, bound
471    /// after. The scope ID and flow label are not part of the port's
472    /// identity and are zero.
473    pub endpoint: std::net::SocketAddrV6,
474    /// The node's 3-octet VMAC (Annex U.1.3): known before start only when
475    /// it comes from a configured Device instance, always after.
476    pub vmac: Option<[u8; 3]>,
477    /// The multicast group broadcasts go to; the transport sends them to
478    /// it at its own UDP port.
479    pub multicast_group: std::net::Ipv6Addr,
480    /// The B/IPv6 mode, with what its Network Port properties need.
481    pub mode: bacnet_types::bip_port::BipPortMode,
482}
483
484/// Trait for BACnet data-link transports.
485///
486/// Implementations handle the data-link framing (e.g., BVLL for BACnet/IP)
487/// and expose a simple send/receive interface for NPDU bytes.
488pub trait TransportPort: Send + Sync {
489    /// Configured broadcast endpoint when this link uses IPv4 BACnet/IP.
490    /// `None` means the link does not expose that capability. Wrappers must
491    /// forward the underlying value. After `start`, the port is the actual
492    /// bound UDP port, including when configuration requested port zero.
493    fn bip_broadcast_endpoint(&self) -> Option<std::net::SocketAddrV4> {
494        None
495    }
496
497    /// Opt in to the existing single-link nonrouter Network Number owner.
498    ///
499    /// Local What-Is-Network-Number may arrive by unicast or logical broadcast;
500    /// Network-Number-Is must be classified as logical broadcast to teach state.
501    /// `send_broadcast` must emit the reply on this same local link. This grants
502    /// neither configured Network Port authority nor router/control-origin trust.
503    /// Built-in B/IP (normal, BBMD or foreign), B/IPv6 (normal or foreign), SC,
504    /// MS/TP, and Linux Ethernet opt in. Other
505    /// links default to false until qualified. Wrappers must delegate.
506    fn supports_local_nonrouter_number_controls(&self) -> bool {
507        false
508    }
509
510    /// Explicit capability for registration as one IPv4 Network Port, with
511    /// the B/IP mode the port reports (Clause 12.56.21).
512    ///
513    /// Before start the endpoint is the configured interface and UDP port;
514    /// after start it is the address the transport announces. In BBMD mode
515    /// the tables appear once start creates them, and stay the transport's
516    /// own live state. A transport with no single mode to report, such as a
517    /// BBMD that also registers as a foreign device, returns None. Custom
518    /// implementations asserting this capability must keep the mode they
519    /// report through ownership. The default refuses registration; broadcast
520    /// support alone is insufficient.
521    fn bip_port(&self) -> Option<BipPort> {
522        None
523    }
524
525    /// Explicit capability for registration as one IPv6 Network Port, with
526    /// the B/IPv6 mode the port reports (Clause 12.56.39): the B/IPv6
527    /// counterpart of [`bip_port`](Self::bip_port), under the same
528    /// contract. A transport should report at most one of the two; when
529    /// one reports both, registration takes the B/IP port and ignores this
530    /// one. The default refuses registration.
531    fn bip6_port(&self) -> Option<Bip6Port> {
532        None
533    }
534
535    /// Retain selected Network Port protection through the last socket owner.
536    /// Must be installed once before start, only on a transport that reports
537    /// a [`bip_port`](Self::bip_port) or a [`bip6_port`](Self::bip6_port).
538    /// Successful stop releases it after quiescence; abort/drop must retain it
539    /// in every surviving socket worker. Custom implementors own this contract.
540    #[doc(hidden)]
541    fn retain_network_port_lease_internal(
542        &mut self,
543        _lease: std::sync::Arc<()>,
544    ) -> Result<(), Error> {
545        Err(Error::Encoding(
546            "transport cannot retain a registered B/IP port".into(),
547        ))
548    }
549
550    /// Prepare the transport to be a port of a router, before it starts.
551    ///
552    /// `bacnet_network`'s `BACnetRouter` calls it on every port it routes,
553    /// right before starting it. A data link whose standard asks more of a
554    /// routing node than of other nodes turns that on here: MS/TP turns on
555    /// extended frames, which Clause 9.3 requires of routing nodes
556    /// ([`MstpConfig::extended_frames`](crate::mstp::MstpConfig::extended_frames)).
557    /// The default does nothing. Wrappers must delegate.
558    fn prepare_for_routing(&mut self) {}
559
560    /// Start the transport. Returns a receiver for incoming NPDUs.
561    ///
562    /// The transport spawns a background receive task that decodes incoming
563    /// frames and sends `ReceivedNpdu` through the returned channel.
564    fn start(
565        &mut self,
566    ) -> impl std::future::Future<Output = Result<mpsc::Receiver<ReceivedNpdu>, Error>> + Send;
567
568    /// Stop the transport and clean up resources.
569    fn stop(&mut self) -> impl std::future::Future<Output = Result<(), Error>> + Send;
570
571    /// Synchronously abort background work and release owned transport resources.
572    ///
573    /// This hook is intended for `Drop` paths where async [`Self::stop`] cannot
574    /// be awaited. Implementations should not attempt graceful protocol shutdown
575    /// here; graceful disconnects remain the responsibility of [`Self::stop`].
576    fn abort(&mut self) {}
577
578    /// Send NPDU bytes to a specific MAC address (unicast).
579    ///
580    /// When [`Error::SendTooLong`] is returned, the NPDU or its encoded link
581    /// frame exceeded a bound before any bytes were sent or queued for
582    /// writing. BACnet/SC uses it for NPDU and frame-length checks; other
583    /// transports may return their existing error variants. Wrappers must
584    /// preserve `SendTooLong`. Other variants do not identify a definite
585    /// length refusal for the router.
586    ///
587    /// B/IP refuses a MAC that is one of its group addresses
588    /// ([`Self::is_group_destination`]) with an error, sending nothing
589    /// (#1677): its receivers drop an Original-Unicast-NPDU that wasn't sent
590    /// to their own address. Send a local broadcast with
591    /// [`Self::send_broadcast`] instead, as `NetworkLayer::send_apdu` and
592    /// `BACnetRouter` do for an Unconfirmed-Request.
593    fn send_unicast(
594        &self,
595        npdu: &[u8],
596        mac: &[u8],
597    ) -> impl std::future::Future<Output = Result<(), Error>> + Send;
598
599    /// Send NPDU bytes with data attributes to a specific MAC address.
600    ///
601    /// Transports that cannot carry data attributes ignore them and send the
602    /// NPDU normally. Attribute-capable transports should override this.
603    fn send_unicast_with_data_attributes<'a>(
604        &'a self,
605        npdu: &'a [u8],
606        mac: &'a [u8],
607        _data_attributes: &'a [DataAttribute],
608    ) -> impl std::future::Future<Output = Result<(), Error>> + Send + 'a {
609        // The send's own future, not one wrapping it: every response future
610        // above this carries its size (#1713).
611        self.send_unicast(npdu, mac)
612    }
613
614    /// Broadcast NPDU bytes on the local network.
615    /// [`Error::SendTooLong`], when returned, has the same meaning as in
616    /// [`Self::send_unicast`].
617    fn send_broadcast(
618        &self,
619        npdu: &[u8],
620    ) -> impl std::future::Future<Output = Result<(), Error>> + Send;
621
622    /// Broadcast NPDU bytes with data attributes on the local network.
623    ///
624    /// Transports that cannot carry data attributes ignore them and broadcast
625    /// the NPDU normally. Attribute-capable transports should override this.
626    fn send_broadcast_with_data_attributes<'a>(
627        &'a self,
628        npdu: &'a [u8],
629        _data_attributes: &'a [DataAttribute],
630    ) -> impl std::future::Future<Output = Result<(), Error>> + Send + 'a {
631        async move { self.send_broadcast(npdu).await }
632    }
633
634    /// This transport's local MAC address.
635    fn local_mac(&self) -> &[u8];
636
637    /// Stable local APDU receive capacity, independent of peers and egress routes.
638    ///
639    /// Implementations and wrappers must report their actual local capacity.
640    /// Negotiation, reconnects and failover must not change this declaration.
641    /// A stack reads it once, at start (a server fixes its Device's
642    /// Max_APDU_Length_Accepted from it, #2093). A transport whose paths can
643    /// shrink later, such as a router's application port, keeps the value it
644    /// was made with and lets the path refuse what no longer fits (#2087);
645    /// [`Self::egress_apdu_limit`] is the limit that follows such changes.
646    fn local_receive_apdu_capacity(&self) -> u16;
647
648    /// Current outgoing APDU limit for the transport's ordinary send path.
649    /// This may change as a connection negotiates remote receive limits.
650    fn egress_apdu_limit(&self) -> u16 {
651        1476
652    }
653
654    /// The longest NPDU this link sends, to one node and to every node.
655    ///
656    /// `bacnet_network`'s `BACnetRouter` reads it when it starts the port,
657    /// and follows it after that through [`Self::max_npdu_changes`]. It
658    /// answers an NPDU it would route here that is longer than that with a
659    /// reject (reason 4, Clause 6.4.4, #1665), where it used to hand it to a
660    /// send that failed. The default, [`NpduLimits::STANDARD`], is what
661    /// B/IPv6 and Ethernet carry; a link that carries less must override
662    /// it, or a router assumes 1,497 octets. MS/TP reports 501 octets
663    /// without extended frames, and B/IP its node's budget for each kind of
664    /// send.
665    ///
666    /// BACnet/SC reports, once started, what its hub connection carries
667    /// with no data options: the hub's Max-NPDU-Length within its
668    /// Max-BVLC-Length less the encapsulation. Data attributes a router
669    /// forwards with an NPDU (Table 6-1, note 1) take more of the BVLC, so
670    /// one at this limit with attributes can still fail the transport's
671    /// Max-BVLC-Length check. A failover or reconnect can change it, which
672    /// [`Self::max_npdu_changes`] publishes (#1696). A direct connection's
673    /// own limit isn't part of it.
674    ///
675    /// Wrappers must delegate.
676    fn max_npdu(&self) -> NpduLimits {
677        NpduLimits::default()
678    }
679
680    /// The changes to [`Self::max_npdu`] while the transport runs, for a
681    /// link whose limit can change after start, or `None`, the default,
682    /// when it never does.
683    ///
684    /// The receiver holds the limit `max_npdu` reports once started, and
685    /// changes only when that limit does. `BACnetRouter` takes one for each
686    /// port after starting it, and gives the port each new limit as soon as
687    /// it is published: every frame it routes after that goes by it, so an
688    /// NPDU too long for the new limit draws reason 4. A send already on
689    /// the port's queue when the limit falls can still be refused by the
690    /// transport as too long; [`Error::SendTooLong`] lets the router count
691    /// it and return reason 4 to an eligible origin. Forwarded rejects and
692    /// fan-outs never draw another reject.
693    ///
694    /// BACnet/SC returns one: its limit comes from the hub connection, and
695    /// it publishes the new one whenever it connects to a hub that carries
696    /// more or less, the first time, on failover to the other hub, and on
697    /// reconnect. The other built-in transports keep the default.
698    ///
699    /// Wrappers must delegate.
700    fn max_npdu_changes(&self) -> Option<watch::Receiver<NpduLimits>> {
701        None
702    }
703
704    /// Whether `mac` is this data link's broadcast address.
705    ///
706    /// A destination can spell a broadcast two ways: the network-layer form
707    /// (a zero-length MAC) or the medium's literal broadcast MAC — Clause 6.3
708    /// names `X'FFFFFFFFFFFF'` for Ethernet, `X'FF'` for MS/TP, and for BACnet/IP
709    /// an address whose host bits are all set (a directed broadcast). Only the transport
710    /// knows its own literal spelling, so senders that must not unicast to a
711    /// broadcast (Clause 6.3 restricts broadcast to Unconfirmed-Request-PDUs)
712    /// ask here. The default recognizes nothing, which leaves such a MAC
713    /// treated as a unicast.
714    fn is_broadcast_mac(&self, _mac: &[u8]) -> bool {
715        false
716    }
717
718    /// Whether a send to `mac` with no DNET reaches a group of nodes rather
719    /// than one: this link's broadcast MAC, or any other broadcast or
720    /// multicast address the medium carries, such as a B/IP limited
721    /// broadcast or an Ethernet multicast MAC (#1493). A B/IPv6 MAC is a
722    /// 3-octet VMAC, which never names a group. Such a send is a local
723    /// broadcast, which may carry only an Unconfirmed-Request (Clause 6.3),
724    /// so a sender that takes a caller-chosen MAC refuses anything else to it
725    /// (#1479). The server binds
726    /// no device to one, and the confirmed requests it starts itself, to
727    /// event recipients, Channel and Command targets, audit recipients and
728    /// bound devices, never go to one (#1493). Its replies and COV
729    /// notifications go to the source a request came from, and the server,
730    /// the client and the endpoint ignore a confirmed request whose source
731    /// MAC is one (#1504), so those don't go to a group on this link. A
732    /// routed request's SADR names a node on another network, which this
733    /// link can't judge: a reply to a group SADR is dropped only where the
734    /// final router is a `BACnetRouter`, which delivers only an
735    /// Unconfirmed-Request to a DADR that is a group on its delivery port,
736    /// as that port's local broadcast when it is one of
737    /// [`Self::local_broadcast_destinations`], and drops anything else,
738    /// network messages included.
739    ///
740    /// B/IP refuses [`Self::send_unicast`] to a group address (#1677), so a
741    /// sender must broadcast there: `NetworkLayer::send_apdu` sends an
742    /// Unconfirmed-Request for one of [`Self::local_broadcast_destinations`]
743    /// through [`Self::send_broadcast`] itself.
744    ///
745    /// [`Self::is_broadcast_mac`] keeps its narrower meaning, this link's own
746    /// broadcast, which routing and recipient checks rely on. The default is
747    /// that answer.
748    fn is_group_destination(&self, mac: &[u8]) -> bool {
749        self.is_broadcast_mac(mac)
750    }
751
752    /// [`Self::is_group_destination`] as an owned rule that a sender can keep
753    /// once the transport has moved into the task that runs it. The built-in
754    /// transports give their rule; the default is
755    /// [`GroupDestinations::unknown`], which leaves the check to the send path.
756    fn group_destinations(&self) -> GroupDestinations {
757        GroupDestinations::unknown()
758    }
759
760    /// The group destinations a send with no DNET may reach as this link's
761    /// local broadcast instead: `NetworkLayer::send_apdu` and `BACnetRouter`
762    /// send an Unconfirmed-Request for one through [`Self::send_broadcast`]
763    /// (#1677). It is a part of [`Self::group_destinations`], and the default
764    /// is all of it. B/IP keeps the groups at the link's own UDP port: one
765    /// at another port belongs to another B/IP network, so a send there is
766    /// refused, not turned into this one's broadcast.
767    fn local_broadcast_destinations(&self) -> GroupDestinations {
768        self.group_destinations()
769    }
770}