Skip to main content

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}