1use super::*;
2
3pub(super) const MAX_DEVICE_BINDINGS: usize = 4096;
5
6pub(super) const OBSERVED_BINDING_TTL: Duration = Duration::from_secs(10 * 60);
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10enum DeviceBindingTarget {
11 Local {
12 peer_mac: MacAddr,
13 },
14 Routed {
15 network: u16,
16 final_mac: MacAddr,
17 router_mac: MacAddr,
18 },
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct DeviceBinding {
27 device: ObjectIdentifier,
28 target: DeviceBindingTarget,
29}
30
31impl DeviceBinding {
32 pub fn local(device: ObjectIdentifier, peer_mac: impl AsRef<[u8]>) -> Result<Self, Error> {
34 validate_device_identifier(device)?;
35 let peer_mac = MacAddr::from_slice(peer_mac.as_ref());
36 if peer_mac.is_empty() {
37 return Err(binding_error("local peer MAC must be non-empty"));
38 }
39 Ok(Self {
40 device,
41 target: DeviceBindingTarget::Local { peer_mac },
42 })
43 }
44
45 pub fn routed(
47 device: ObjectIdentifier,
48 network: u16,
49 final_mac: impl AsRef<[u8]>,
50 router_mac: impl AsRef<[u8]>,
51 ) -> Result<Self, Error> {
52 validate_device_identifier(device)?;
53 if !(1..=0xFFFE).contains(&network) {
54 return Err(binding_error("routed network must be in 1..=65534"));
55 }
56 let final_mac = MacAddr::from_slice(final_mac.as_ref());
57 if final_mac.is_empty() {
58 return Err(binding_error("routed final MAC must be non-empty"));
59 }
60 let router_mac = MacAddr::from_slice(router_mac.as_ref());
61 if router_mac.is_empty() {
62 return Err(binding_error("routed router MAC must be non-empty"));
63 }
64 Ok(Self {
65 device,
66 target: DeviceBindingTarget::Routed {
67 network,
68 final_mac,
69 router_mac,
70 },
71 })
72 }
73}
74
75fn binding_error(message: &str) -> Error {
76 Error::Encoding(format!("invalid device binding: {message}"))
77}
78
79fn validate_device_identifier(device: ObjectIdentifier) -> Result<(), Error> {
80 if device.object_type() != ObjectType::DEVICE {
81 return Err(binding_error("identifier must name a Device object"));
82 }
83 Ok(())
84}
85
86pub(super) fn register_configured_binding(
87 bindings: &mut Vec<DeviceBinding>,
88 binding: DeviceBinding,
89) -> Result<(), Error> {
90 if bindings.len() >= MAX_DEVICE_BINDINGS {
91 return Err(binding_error("configured binding capacity exceeded"));
92 }
93 if bindings
94 .iter()
95 .any(|configured| configured.device == binding.device)
96 {
97 return Err(binding_error("duplicate configured Device identifier"));
98 }
99 bindings.push(binding);
100 Ok(())
101}
102
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub(super) enum DeviceResolution {
105 ResolvedLocal {
106 peer_mac: MacAddr,
107 freshness: BindingFreshness,
108 },
109 ResolvedRouted {
110 network: u16,
111 final_mac: MacAddr,
112 router_mac: MacAddr,
113 freshness: BindingFreshness,
114 },
115 Unknown,
116 Stale,
117 Invalid,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub(super) enum BindingFreshness {
122 Configured,
123 ObservedUntil(tokio::time::Instant),
124}
125
126impl BindingFreshness {
127 pub(super) fn permits_attempt_at(self, now: tokio::time::Instant) -> bool {
128 match self {
129 Self::Configured => true,
130 Self::ObservedUntil(deadline) => now < deadline,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub(super) enum ObservationOutcome {
137 Inserted,
138 Refreshed,
139 ConfiguredPreserved,
140 RejectedInvalid,
141 RejectedCapacity,
142}
143
144#[derive(Debug, Clone)]
145enum BindingEntry {
146 Configured(DeviceBindingTarget),
147 Observed {
148 target: DeviceBindingTarget,
149 observed_at: Instant,
150 },
151}
152
153#[derive(Debug, Default)]
154pub(super) struct DeviceBindingTable {
155 entries: HashMap<ObjectIdentifier, BindingEntry>,
156}
157
158impl DeviceBindingTable {
159 pub(super) fn new() -> Self {
160 Self::default()
161 }
162
163 pub(super) fn from_configured(
164 bindings: Vec<DeviceBinding>,
165 is_broadcast: impl Fn(&[u8]) -> bool,
166 ) -> Result<Self, Error> {
167 let mut table = Self::new();
168 for binding in bindings {
169 table.insert_configured(binding, &is_broadcast)?;
170 }
171 Ok(table)
172 }
173
174 #[cfg(test)]
175 pub(super) fn len(&self) -> usize {
176 self.entries.len()
177 }
178
179 pub(super) fn insert_configured(
180 &mut self,
181 binding: DeviceBinding,
182 is_broadcast: impl Fn(&[u8]) -> bool,
183 ) -> Result<(), Error> {
184 validate_device_identifier(binding.device)?;
185 if !target_is_usable(&binding.target, &is_broadcast) {
186 return Err(binding_error(
187 "local peer or next-hop router is a broadcast address",
188 ));
189 }
190 if self.entries.contains_key(&binding.device) {
191 return Err(binding_error("duplicate configured Device identifier"));
192 }
193 if self.entries.len() >= MAX_DEVICE_BINDINGS {
194 return Err(binding_error("configured binding capacity exceeded"));
195 }
196 self.entries
197 .insert(binding.device, BindingEntry::Configured(binding.target));
198 Ok(())
199 }
200
201 pub(super) fn observe_i_am_at(
202 &mut self,
203 device: ObjectIdentifier,
204 source_mac: &[u8],
205 source_network: Option<&NpduAddress>,
206 now: Instant,
207 is_broadcast: impl Fn(&[u8]) -> bool,
208 ) -> ObservationOutcome {
209 if validate_device_identifier(device).is_err() {
210 return ObservationOutcome::RejectedInvalid;
211 }
212 let target = match source_network {
213 None => DeviceBindingTarget::Local {
214 peer_mac: MacAddr::from_slice(source_mac),
215 },
216 Some(source)
217 if (1..=0xFFFE).contains(&source.network) && !source.mac_address.is_empty() =>
218 {
219 DeviceBindingTarget::Routed {
220 network: source.network,
221 final_mac: source.mac_address.clone(),
222 router_mac: MacAddr::from_slice(source_mac),
223 }
224 }
225 Some(_) => return ObservationOutcome::RejectedInvalid,
226 };
227 if !target_is_usable(&target, &is_broadcast) {
228 return ObservationOutcome::RejectedInvalid;
229 }
230
231 match self.entries.get_mut(&device) {
232 Some(BindingEntry::Configured(_)) => ObservationOutcome::ConfiguredPreserved,
233 Some(BindingEntry::Observed {
234 target: observed,
235 observed_at,
236 }) => {
237 *observed = target;
238 *observed_at = now;
239 ObservationOutcome::Refreshed
240 }
241 None => {
242 if self.entries.len() >= MAX_DEVICE_BINDINGS {
243 self.entries.retain(|_, entry| match entry {
244 BindingEntry::Configured(_) => true,
245 BindingEntry::Observed { observed_at, .. } => {
246 now.saturating_duration_since(*observed_at) < OBSERVED_BINDING_TTL
247 }
248 });
249 }
250 if self.entries.len() >= MAX_DEVICE_BINDINGS {
251 return ObservationOutcome::RejectedCapacity;
252 }
253 self.entries.insert(
254 device,
255 BindingEntry::Observed {
256 target,
257 observed_at: now,
258 },
259 );
260 ObservationOutcome::Inserted
261 }
262 }
263 }
264
265 pub(super) fn resolve_at(
266 &self,
267 device: &ObjectIdentifier,
268 now: Instant,
269 is_broadcast: impl Fn(&[u8]) -> bool,
270 ) -> DeviceResolution {
271 if device.object_type() != ObjectType::DEVICE {
272 return DeviceResolution::Invalid;
273 }
274 let (target, freshness) = match self.entries.get(device) {
275 Some(BindingEntry::Configured(target)) => (target, BindingFreshness::Configured),
276 Some(BindingEntry::Observed {
277 target,
278 observed_at,
279 }) => {
280 if now.saturating_duration_since(*observed_at) >= OBSERVED_BINDING_TTL {
281 return DeviceResolution::Stale;
282 }
283 (
284 target,
285 BindingFreshness::ObservedUntil(tokio::time::Instant::from_std(
286 *observed_at + OBSERVED_BINDING_TTL,
287 )),
288 )
289 }
290 None => return DeviceResolution::Unknown,
291 };
292 if !target_is_usable(target, is_broadcast) {
293 return DeviceResolution::Invalid;
294 }
295 match target {
296 DeviceBindingTarget::Local { peer_mac } => DeviceResolution::ResolvedLocal {
297 peer_mac: peer_mac.clone(),
298 freshness,
299 },
300 DeviceBindingTarget::Routed {
301 network,
302 final_mac,
303 router_mac,
304 } => DeviceResolution::ResolvedRouted {
305 network: *network,
306 final_mac: final_mac.clone(),
307 router_mac: router_mac.clone(),
308 freshness,
309 },
310 }
311 }
312}
313
314fn target_is_usable(target: &DeviceBindingTarget, is_broadcast: impl Fn(&[u8]) -> bool) -> bool {
315 match target {
316 DeviceBindingTarget::Local { peer_mac } => !peer_mac.is_empty() && !is_broadcast(peer_mac),
317 DeviceBindingTarget::Routed {
318 network,
319 final_mac,
320 router_mac,
321 } => {
322 (1..=0xFFFE).contains(network)
323 && !final_mac.is_empty()
324 && !router_mac.is_empty()
325 && !is_broadcast(router_mac)
326 }
327 }
328}