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