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}