bacnet_bip_core/node/addressing.rs
1//! What a datagram's addresses mean to this node: where it was sent, whether
2//! its sender is a group address, and whether a reply could reach it.
3
4use core::net::{Ipv4Addr, SocketAddrV4};
5
6use bacnet_bip_codec::BvlcFunction;
7
8use super::config::Link;
9use crate::Arrival;
10
11/// What a datagram's destination address was, for this node. A host
12/// builds it with [`Addressed::classify`]. Both can hold at once, as where
13/// a loopback test's broadcast address is its own; neither, for a
14/// destination that is neither (another host's unicast, a multicast group).
15///
16/// It is a plain record, and closed on purpose: the receive rules ask a
17/// destination two questions, whether it was this node alone and whether
18/// it reached every node on the subnet (Annex J.2.11, J.2.12, J.4.1). Any
19/// other kind of destination answers the same two: for a B/IP-M node
20/// (Annex J.8, #941) the group address stands in for the broadcast one.
21#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
22pub struct Addressed {
23 /// Sent to this node's own unicast address.
24 pub node: bool,
25 /// Sent to the subnet's broadcast address or 255.255.255.255, or for a
26 /// B/IP-M node to its multicast group.
27 pub broadcast: bool,
28}
29
30/// The addresses a destination is judged against.
31///
32/// It is `#[non_exhaustive]`, so a new kind of address isn't a breaking
33/// change: outside this crate, build it with [`LocalAddresses::new`] and
34/// set [`wildcard`](Self::wildcard) for a socket bound to the wildcard
35/// address.
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37#[non_exhaustive]
38pub struct LocalAddresses<'a> {
39 /// The IP address of the node's own B/IP address.
40 pub own: Ipv4Addr,
41 /// The subnet's broadcast IP address, or a B/IP-M node's multicast
42 /// group (Annex J.8): the link's [`LocalSubnet::broadcast`](crate::LocalSubnet::broadcast).
43 /// A group here makes the group the only broadcast destination: see
44 /// [`Addressed::classify`].
45 pub broadcast: Ipv4Addr,
46 /// For a socket bound to the wildcard address, the host's own unicast
47 /// addresses: unicast to any of them is the node's. `None` for a socket
48 /// bound to `own`.
49 pub wildcard: Option<&'a [Ipv4Addr]>,
50}
51
52impl LocalAddresses<'_> {
53 /// The addresses of a socket bound to `own`, on the subnet whose
54 /// broadcast IP is `broadcast`.
55 #[must_use]
56 pub const fn new(own: Ipv4Addr, broadcast: Ipv4Addr) -> Self {
57 Self {
58 own,
59 broadcast,
60 wildcard: None,
61 }
62 }
63}
64
65impl Addressed {
66 /// Classify a datagram by its destination IP address and, when the host
67 /// reports it, whether the OS delivered it to a group (`os_group`). The
68 /// OS's word overrides the address: a group delivery is never unicast
69 /// to the node, and a reported unicast never a broadcast.
70 ///
71 /// When the broadcast address is a multicast group, the node is a
72 /// B/IP-M member (Annex J.8): the group is its only broadcast
73 /// destination, and 255.255.255.255 or a subnet's directed broadcast
74 /// is neither broadcast nor unicast to it. Members and plain B/IP
75 /// devices don't share a subnet (Annex J.8.1), so a subnet broadcast
76 /// isn't this network's, and taking it as well could deliver one
77 /// broadcast twice.
78 #[must_use]
79 pub fn classify(
80 destination: Ipv4Addr,
81 os_group: Option<bool>,
82 local: &LocalAddresses<'_>,
83 ) -> Self {
84 let to_broadcast = destination == local.broadcast
85 || (destination.is_broadcast() && !local.broadcast.is_multicast());
86 let node = match local.wildcard {
87 Some(own_ips) => {
88 !to_broadcast && !destination.is_multicast() && own_ips.contains(&destination)
89 }
90 None => destination == local.own,
91 } && os_group != Some(true);
92 Self {
93 node,
94 broadcast: to_broadcast && os_group != Some(false),
95 }
96 }
97
98 /// How a message of `function` arrived, or `None` when the function
99 /// doesn't fit the destination (Annex J.2.11, J.2.12, J.3, J.4.1).
100 ///
101 /// An Original-Unicast-NPDU must be sent to the node, an
102 /// Original-Broadcast-NPDU to the broadcast address, and a
103 /// Forwarded-NPDU to either (a broadcast when both fit). Every other
104 /// function is point-to-point, so only to the node.
105 pub(crate) fn arrival(self, function: BvlcFunction) -> Option<Arrival> {
106 let unicast = self.node.then_some(Arrival::Unicast);
107 let broadcast = self.broadcast.then_some(Arrival::Broadcast);
108 match function {
109 BvlcFunction::ORIGINAL_UNICAST_NPDU => unicast,
110 BvlcFunction::ORIGINAL_BROADCAST_NPDU => broadcast,
111 BvlcFunction::FORWARDED_NPDU => broadcast.or(unicast),
112 _ => unicast,
113 }
114 }
115}
116
117/// A link's group destinations: the B/IP addresses that reach more than one
118/// node. No node sends from one, so a datagram from one, or a
119/// Forwarded-NPDU naming one as its origin, is dropped: answering it would
120/// reach the whole group.
121///
122/// It is `#[non_exhaustive]`, so a link that comes to have another group
123/// destination of its own (a second broadcast address, say) isn't a
124/// breaking change: outside this crate, take it from [`Link::groups`] or
125/// build it with [`Groups::new`].
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127#[non_exhaustive]
128pub struct Groups {
129 /// The link's broadcast address: its broadcast IP at the network's port.
130 pub broadcast: SocketAddrV4,
131 /// The node's own interface IP. A broadcast IP equal to it, or a
132 /// loopback one, as loopback tests set up, is a group only at the
133 /// network's port.
134 pub interface: Ipv4Addr,
135 /// For a B/IP-M node, its IP subnet's directed broadcast address
136 /// ([`LocalSubnet::directed`](crate::LocalSubnet::directed)): no
137 /// longer where its broadcasts go, but still an address that reaches
138 /// more than one node. It counts as the broadcast IP does, at any port.
139 /// 0.0.0.0 for none: a plain address rather than an `Option`, so a
140 /// router's empty group rule stays all zero bits and its storage stays
141 /// in `.bss`.
142 pub directed: Ipv4Addr,
143}
144
145impl Groups {
146 /// The group destinations of a link whose broadcast address is
147 /// `broadcast`, on the interface whose IP is `interface`, with no
148 /// directed broadcast beside it.
149 #[must_use]
150 pub const fn new(broadcast: SocketAddrV4, interface: Ipv4Addr) -> Self {
151 Self {
152 broadcast,
153 interface,
154 directed: Ipv4Addr::UNSPECIFIED,
155 }
156 }
157
158 /// Whether `address` is a group destination: the link's broadcast
159 /// address, or any address whose IP is a group IP ([`Groups::contains_ip`]).
160 #[must_use]
161 pub const fn contains(&self, address: SocketAddrV4) -> bool {
162 let broadcast = &self.broadcast;
163 (address.ip().to_bits() == broadcast.ip().to_bits() && address.port() == broadcast.port())
164 || self.contains_ip(*address.ip())
165 }
166
167 /// Whether `ip` is a group at any port: 255.255.255.255, a multicast
168 /// address, or the broadcast IP or a member's directed broadcast when
169 /// it is neither loopback nor the node's own interface. This is the
170 /// test for a datagram's UDP source.
171 #[must_use]
172 pub const fn contains_ip(&self, ip: Ipv4Addr) -> bool {
173 let ip_bits = ip.to_bits();
174 let directed = !self.directed.is_unspecified() && self.directed.to_bits() == ip_bits;
175 let subnet = ip_bits == self.broadcast.ip().to_bits() || directed;
176 ip.is_broadcast()
177 || ip.is_multicast()
178 || (subnet && !ip.is_loopback() && ip_bits != self.interface.to_bits())
179 }
180}
181
182impl Link {
183 /// The link's group destinations, with the IP of the node's own B/IP
184 /// address as its interface.
185 ///
186 /// A host bound to the wildcard address may judge a destination by its
187 /// bind address (0.0.0.0) instead, as the Tokio transport's
188 /// `is_group_destination` does. The two differ only where the configured
189 /// broadcast IP is the node's own IP, a misconfiguration: this one then
190 /// takes a sender from that IP, which the own-echo rule drops at the
191 /// node's port.
192 #[must_use]
193 pub const fn groups(&self) -> Groups {
194 Groups {
195 broadcast: self.subnet.broadcast,
196 interface: *self.address.ip(),
197 directed: match self.subnet.directed {
198 Some(directed) => directed,
199 None => Ipv4Addr::UNSPECIFIED,
200 },
201 }
202 }
203
204 /// Whether `address` is this node's own B/IP address.
205 ///
206 /// The one test for the node's own echo, so an address the node is
207 /// also reached at (a NAT router's global address) can join it here.
208 #[must_use]
209 pub fn is_own(&self, address: SocketAddrV4) -> bool {
210 address == self.address
211 }
212
213 /// Whether a reply to `source` could reach a peer: not the unspecified
214 /// address, not port 0, not a group address, and not this node.
215 #[must_use]
216 pub fn is_answerable(&self, source: SocketAddrV4) -> bool {
217 !source.ip().is_unspecified()
218 && source.port() != 0
219 && !self.groups().contains_ip(*source.ip())
220 && !self.is_own(source)
221 }
222}