Skip to main content

bacnet_network/
layer_receive.rs

1//! What [`NetworkLayer`](super::NetworkLayer)'s dispatch does with each
2//! frame: `bacnet-router-core`'s [`Node`] decides, by the same admission
3//! checks the router's engine runs (#1795), and its drained counts are
4//! added to the layer's totals ([`Counters`]).
5
6use bacnet_encoding::npdu::{Npdu, NpduAddress};
7use bacnet_router_core::node::{self, Node};
8use bacnet_router_core::router::Discard;
9use bacnet_transport::warn_throttle::WarnThrottle;
10use bytes::{Buf, Bytes};
11use tracing::{debug, warn};
12
13use crate::totals::totals;
14
15/// What a [`NetworkLayer`](super::NetworkLayer) has dropped on receipt
16/// since it was created, by reason. Each total saturates at `u64::MAX`.
17/// A dropped NPDU reaches neither the APDU nor the control receiver, so
18/// it never shows up in their admission counters. The totals never change
19/// a decision, and name no peer.
20///
21/// It is `#[non_exhaustive]`, so a new count isn't a breaking change.
22/// Outside this crate, build one from `Counters::default()` and set its
23/// fields.
24#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
25#[non_exhaustive]
26pub struct Counters {
27    /// NPDUs with an address past [`MAX_MAC_LEN`](bacnet_npdu::MAX_MAC_LEN):
28    /// their DLEN or SLEN (#1141), or the link-layer source MAC the
29    /// transport reported (#1198).
30    pub address_length_drops: u64,
31    /// NPDUs whose DNET 0xFFFF also carried a DADR (#1379).
32    pub global_broadcast_dadr_drops: u64,
33    /// Broadcasts, global or remote, whose APDU wasn't an
34    /// Unconfirmed-Request (#1491).
35    pub broadcast_pdu_type_drops: u64,
36    /// Frames that didn't parse as an NPDU for any other reason.
37    pub malformed: u64,
38    /// NPDUs whose DNET named one network: for a device elsewhere, which a
39    /// node that doesn't route drops (Clauses 6.5.2.1, 6.5.2.2 and 6.5.4).
40    pub routed_drops: u64,
41}
42
43totals!(
44    /// A layer's totals since it was created, one atomic each. Its dispatch
45    /// task adds a frame's counts when the node dropped the frame.
46    NodeTotals: node::Counters => Counters {
47        address_length_drops,
48        global_broadcast_dadr_drops,
49        broadcast_pdu_type_drops,
50        malformed,
51        routed_drops,
52    }
53);
54
55/// What one frame is for, owned, so the frame it was read from can move.
56pub(crate) enum Inbound {
57    /// An APDU for the application.
58    Apdu {
59        /// The APDU: the frame, advanced past the NPCI, not a copy.
60        apdu: Bytes,
61        /// SNET and SADR, when a router relayed it.
62        source_network: Option<NpduAddress>,
63        /// Whether it was sent to a group.
64        is_group: bool,
65        /// Whether it was a global broadcast.
66        global_broadcast: bool,
67    },
68    /// A network message, for the control receiver: `None` when the layer
69    /// has none, so nothing was copied.
70    Control(Option<Npdu>),
71    /// Dropped, and counted.
72    Dropped,
73}
74
75/// Run `frame`, from link source `source_mac`, through `node`, adding what
76/// it counted to `totals`. `controls` says whether a control receiver
77/// wants network messages. A drop warns at most once per `drop_warning`
78/// interval, with the drops since the last warning; the rest log at debug,
79/// so a flood of bad frames doesn't flood the log.
80pub(crate) fn admit(
81    node: &mut Node,
82    totals: &NodeTotals,
83    drop_warning: &WarnThrottle,
84    source_mac: &[u8],
85    link_group: bool,
86    frame: Bytes,
87    controls: bool,
88) -> Inbound {
89    let (offset, inbound) = match node.receive(source_mac, link_group, &frame) {
90        node::Received::Apdu(apdu) => (
91            apdu.npdu.payload_offset,
92            Inbound::Apdu {
93                apdu: Bytes::new(),
94                source_network: apdu.npdu.source.map(NpduAddress::from),
95                is_group: apdu.group,
96                global_broadcast: apdu.global,
97            },
98        ),
99        node::Received::Message(npdu) if controls => (
100            npdu.payload_offset,
101            Inbound::Control(Some(Npdu::header_from(&npdu))),
102        ),
103        node::Received::Message(npdu) => {
104            debug!(
105                message_type = ?npdu.message_type,
106                "Ignoring network layer message (non-router mode)"
107            );
108            return Inbound::Control(None);
109        }
110        node::Received::Discarded(discard) => {
111            totals.add(&node.take_counters());
112            if discard == Discard::NotRouted {
113                debug!("Discarding routed message (non-router)");
114            } else if let Some(dropped) = drop_warning.note() {
115                warn!(
116                    ?discard,
117                    dropped, "Dropping an inbound NPDU (drops since the last warning)"
118                );
119            } else {
120                debug!(?discard, "Dropping an inbound NPDU");
121            }
122            return Inbound::Dropped;
123        }
124    };
125    // Advanced, not sliced: a slice is a clone, and the first clone of a
126    // frame received into a plain buffer allocates its shared header (#1713).
127    let mut payload = frame;
128    payload.advance(offset);
129    match inbound {
130        Inbound::Apdu {
131            source_network,
132            is_group,
133            global_broadcast,
134            ..
135        } => Inbound::Apdu {
136            apdu: payload,
137            source_network,
138            is_group,
139            global_broadcast,
140        },
141        Inbound::Control(Some(mut npdu)) => {
142            npdu.payload = payload;
143            Inbound::Control(Some(npdu))
144        }
145        other => other,
146    }
147}