1use crate::options::TraceSignalEntry;
18
19pub const ESTABLISHED_ROUTE_EXTRA_FLOOD_HOPS: u8 = 1;
25
26pub const STRONG_DIRECT_RSSI_DBM: i16 = -75;
29
30pub const WEAK_LAST_HOP_RSSI_DBM: i16 = -96;
33
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
36pub struct RouteEvidence {
37 pub hand_set: bool,
39 pub direct: bool,
41 pub last_hop: Option<TraceSignalEntry>,
44 pub peer_mobile: bool,
46 pub peer_retransmitted: bool,
50}
51
52pub fn established_route_slack(evidence: RouteEvidence) -> u8 {
62 if evidence.hand_set {
63 return 0;
64 }
65 if evidence.peer_retransmitted {
66 return ESTABLISHED_ROUTE_EXTRA_FLOOD_HOPS;
67 }
68 let last_hop_rssi = evidence.last_hop.map(TraceSignalEntry::rssi_dbm);
70 let strong_direct = evidence.direct
71 && match last_hop_rssi {
72 Some(None) => true,
73 Some(Some(rssi)) => rssi > STRONG_DIRECT_RSSI_DBM,
74 None => false,
75 };
76 if strong_direct {
77 return 0;
78 }
79 if evidence.peer_mobile {
80 return ESTABLISHED_ROUTE_EXTRA_FLOOD_HOPS;
81 }
82 match last_hop_rssi {
83 None => ESTABLISHED_ROUTE_EXTRA_FLOOD_HOPS,
84 Some(Some(rssi)) if rssi <= WEAK_LAST_HOP_RSSI_DBM => ESTABLISHED_ROUTE_EXTRA_FLOOD_HOPS,
85 Some(_) => 0,
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 const SLACK: u8 = ESTABLISHED_ROUTE_EXTRA_FLOOD_HOPS;
94
95 fn air(rssi_dbm: i16) -> Option<TraceSignalEntry> {
96 Some(TraceSignalEntry::new(rssi_dbm, 0))
97 }
98
99 fn direct(last_hop: Option<TraceSignalEntry>) -> RouteEvidence {
100 RouteEvidence {
101 direct: true,
102 last_hop,
103 ..RouteEvidence::default()
104 }
105 }
106
107 fn routed(last_hop: Option<TraceSignalEntry>) -> RouteEvidence {
108 RouteEvidence {
109 last_hop,
110 ..RouteEvidence::default()
111 }
112 }
113
114 #[test]
115 fn a_route_chosen_by_hand_never_gets_slack() {
116 for evidence in [direct(air(-120)), routed(None), routed(air(-120))] {
117 let evidence = RouteEvidence {
118 hand_set: true,
119 peer_mobile: true,
120 ..evidence
121 };
122 assert_eq!(established_route_slack(evidence), 0);
123 }
124 }
125
126 #[test]
127 fn an_ack_for_a_retransmission_gets_slack_however_strong_the_link() {
128 for evidence in [
129 direct(air(-30)),
130 direct(Some(TraceSignalEntry::POINT_TO_POINT)),
131 routed(air(-40)),
132 ] {
133 let evidence = RouteEvidence {
134 peer_retransmitted: true,
135 ..evidence
136 };
137 assert_eq!(established_route_slack(evidence), SLACK, "{evidence:?}");
138 }
139 let hand_set = RouteEvidence {
140 hand_set: true,
141 peer_retransmitted: true,
142 ..direct(air(-30))
143 };
144 assert_eq!(established_route_slack(hand_set), 0);
145 }
146
147 #[test]
148 fn a_strong_or_bridged_direct_peer_gets_none_even_when_mobile() {
149 for last_hop in [air(-74), air(-30), Some(TraceSignalEntry::POINT_TO_POINT)] {
150 let evidence = RouteEvidence {
151 peer_mobile: true,
152 ..direct(last_hop)
153 };
154 assert_eq!(established_route_slack(evidence), 0, "{last_hop:?}");
155 }
156 }
157
158 #[test]
159 fn a_direct_peer_below_the_strong_threshold_falls_through_to_the_last_hop_rules() {
160 assert_eq!(established_route_slack(direct(air(-75))), 0);
161 assert_eq!(established_route_slack(direct(air(-95))), 0);
162 assert_eq!(established_route_slack(direct(air(-96))), SLACK);
163 let mobile = RouteEvidence {
164 peer_mobile: true,
165 ..direct(air(-75))
166 };
167 assert_eq!(established_route_slack(mobile), SLACK);
168 }
169
170 #[test]
171 fn an_unmeasured_last_hop_gets_slack() {
172 assert_eq!(established_route_slack(direct(None)), SLACK);
173 assert_eq!(established_route_slack(routed(None)), SLACK);
174 }
175
176 #[test]
177 fn a_mobile_destination_gets_slack_past_any_routed_last_hop() {
178 for last_hop in [air(-40), Some(TraceSignalEntry::POINT_TO_POINT)] {
179 let evidence = RouteEvidence {
180 peer_mobile: true,
181 ..routed(last_hop)
182 };
183 assert_eq!(established_route_slack(evidence), SLACK);
184 }
185 }
186
187 #[test]
188 fn only_a_weak_routed_last_hop_gets_slack() {
189 assert_eq!(established_route_slack(routed(air(-95))), 0);
190 assert_eq!(established_route_slack(routed(air(-96))), SLACK);
191 assert_eq!(established_route_slack(routed(air(-120))), SLACK);
192 assert_eq!(
193 established_route_slack(routed(Some(TraceSignalEntry::POINT_TO_POINT))),
194 0
195 );
196 }
197}