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}