bacnet_router_core/table/
holds.rs1use core::fmt;
4
5use super::{Reachability, Table, Timing};
6use crate::npdu::RejectMessageReason;
7use crate::{Instant, PortId, swap_apart};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum RejectEffect {
16 Applied,
18 SameState,
21 HeldDown,
24 Kept,
27 NotNextHop,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub(crate) struct Hold {
38 pub(crate) network: u16,
40 pub(crate) port: PortId,
42 pub(crate) since: Instant,
44}
45
46const NO_HOLD: Hold = Hold {
48 network: 0,
49 port: PortId(0),
50 since: Instant::ZERO,
51};
52
53#[derive(Clone)]
55pub(super) struct Holds<const H: usize> {
56 records: [Hold; H],
57 len: usize,
58}
59
60impl<const H: usize> fmt::Debug for Holds<H> {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.debug_list().entries(self.stored()).finish()
64 }
65}
66
67impl<const H: usize> Holds<H> {
68 pub(super) const fn new() -> Self {
69 Self {
70 records: [NO_HOLD; H],
71 len: 0,
72 }
73 }
74
75 fn stored(&self) -> &[Hold] {
76 &self.records[..self.len]
77 }
78
79 fn since(&self, network: u16, port: PortId) -> Option<Instant> {
80 self.stored()
81 .iter()
82 .find(|hold| hold.network == network && hold.port == port)
83 .map(|hold| hold.since)
84 }
85
86 fn record(&mut self, network: u16, port: PortId, now: Instant) -> bool {
89 let hold = Hold {
90 network,
91 port,
92 since: now,
93 };
94 let stored = &mut self.records[..self.len];
95 if let Some(existing) = stored
96 .iter_mut()
97 .find(|hold| hold.network == network && hold.port == port)
98 {
99 *existing = hold;
100 return false;
101 }
102 if self.len < H {
103 self.records[self.len] = hold;
104 self.len += 1;
105 return false;
106 }
107 if let Some(oldest) = self.records.iter_mut().min_by_key(|hold| hold.since) {
108 *oldest = hold;
109 }
110 true
111 }
112
113 pub(super) fn clear(&mut self) {
115 self.records[..self.len].fill(NO_HOLD);
116 self.len = 0;
117 }
118
119 pub(super) fn forget(&mut self, network: u16) {
121 let mut kept = 0;
124 for index in 0..self.len {
125 if self.records[index].network != network {
126 swap_apart(&mut self.records, kept, index);
127 kept += 1;
128 }
129 }
130 self.records[kept..self.len].fill(NO_HOLD);
131 self.len = kept;
132 }
133}
134
135impl<const R: usize, const H: usize> Table<R, H> {
136 pub fn apply_reject(
150 &mut self,
151 network: u16,
152 port: PortId,
153 reason: RejectMessageReason,
154 now: Instant,
155 timing: &Timing,
156 ) -> RejectEffect {
157 let learned = self.lookup(network).is_some_and(|route| !route.direct);
158 let other = !matches!(
159 reason,
160 RejectMessageReason::NOT_DIRECTLY_CONNECTED | RejectMessageReason::ROUTER_BUSY
161 );
162 if learned && other {
163 let kept = &mut self.counters.reject_kept;
164 *kept = kept.saturating_add(1);
165 return RejectEffect::Kept;
166 }
167 let changes = self.lookup(network).is_some_and(|route| {
168 let lapsed = route.busy_until.is_some_and(|deadline| now >= deadline);
171 !route.direct
172 && match reason {
173 RejectMessageReason::NOT_DIRECTLY_CONNECTED => {
174 route.reachability != Reachability::Unreachable
175 }
176 RejectMessageReason::ROUTER_BUSY => {
177 route.reachability != Reachability::Unreachable
178 && (route.reachability != Reachability::Busy || lapsed)
179 }
180 _ => false,
181 }
182 });
183 let held_down = self
184 .holds
185 .since(network, port)
186 .is_some_and(|since| now.saturating_duration_since(since) < timing.hold_down);
187 let counters = &mut self.counters;
188 if !changes || held_down {
189 counters.reject_dampened = counters.reject_dampened.saturating_add(1);
190 return if changes {
191 counters.reject_dampened_hold_down =
192 counters.reject_dampened_hold_down.saturating_add(1);
193 RejectEffect::HeldDown
194 } else {
195 counters.reject_dampened_same_state =
196 counters.reject_dampened_same_state.saturating_add(1);
197 RejectEffect::SameState
198 };
199 }
200 counters.reject_applied = counters.reject_applied.saturating_add(1);
201 if reason == RejectMessageReason::NOT_DIRECTLY_CONNECTED {
202 self.mark_unreachable(network);
203 } else {
204 self.mark_busy(network, now + timing.busy_for);
206 }
207 if self.holds.record(network, port, now) {
208 self.counters.hold_evicted = self.counters.hold_evicted.saturating_add(1);
209 }
210 RejectEffect::Applied
211 }
212
213 #[cfg(test)]
216 pub(crate) fn held_since(&self, network: u16, port: PortId) -> Option<Instant> {
217 self.holds.since(network, port)
218 }
219
220 #[cfg(test)]
223 pub(crate) fn holds(&self) -> impl Iterator<Item = Hold> + '_ {
224 self.holds.stored().iter().copied()
225 }
226}