1#![warn(missing_docs)]
4use std::sync::Arc;
5
6use async_trait::async_trait;
7
8use crate::dht::Did;
9
10#[cfg(not(feature = "wasm"))]
12pub type MeasureImpl = Arc<dyn BehaviourJudgement + Send + Sync>;
13
14#[cfg(feature = "wasm")]
16pub type MeasureImpl = Arc<dyn BehaviourJudgement>;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum MeasureCounter {
21 Sent,
23 FailedToSend,
25 Received,
27 FailedToReceive,
29 Connect,
31 Disconnected,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum PeerQuality {
41 Healthy,
43 Unknown,
45 Degraded,
47}
48
49impl PeerQuality {
50 pub const fn connection_rank(self) -> u8 {
52 match self {
53 Self::Healthy => 0,
54 Self::Unknown => 1,
55 Self::Degraded => 2,
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct PeerQualityThresholds {
63 disconnected: u64,
64 failed_to_send: u64,
65 failed_to_receive: u64,
66}
67
68impl PeerQualityThresholds {
69 pub const fn new(disconnected: u64, failed_to_send: u64, failed_to_receive: u64) -> Self {
71 Self {
72 disconnected,
73 failed_to_send,
74 failed_to_receive,
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct PeerQualityEvidence {
85 connected: u64,
86 disconnected: u64,
87 sent: u64,
88 failed_to_send: u64,
89 received: u64,
90 failed_to_receive: u64,
91}
92
93impl PeerQualityEvidence {
94 pub const fn new(
96 connected: u64,
97 disconnected: u64,
98 sent: u64,
99 failed_to_send: u64,
100 received: u64,
101 failed_to_receive: u64,
102 ) -> Self {
103 Self {
104 connected,
105 disconnected,
106 sent,
107 failed_to_send,
108 received,
109 failed_to_receive,
110 }
111 }
112
113 pub async fn from_measure<M>(measure: &M, did: Did) -> Self
115 where M: Measure + ?Sized {
116 Self {
117 connected: measure.get_count(did, MeasureCounter::Connect).await,
118 disconnected: measure.get_count(did, MeasureCounter::Disconnected).await,
119 sent: measure.get_count(did, MeasureCounter::Sent).await,
120 failed_to_send: measure.get_count(did, MeasureCounter::FailedToSend).await,
121 received: measure.get_count(did, MeasureCounter::Received).await,
122 failed_to_receive: measure
123 .get_count(did, MeasureCounter::FailedToReceive)
124 .await,
125 }
126 }
127
128 pub const fn classify(self, thresholds: PeerQualityThresholds) -> PeerQuality {
130 if self.reaches_failure_limit(thresholds) {
131 PeerQuality::Degraded
132 } else if self.has_positive_observation() {
133 PeerQuality::Healthy
134 } else {
135 PeerQuality::Unknown
136 }
137 }
138
139 pub const fn has_positive_observation(self) -> bool {
141 self.connected > 0 || self.sent > 0 || self.received > 0
142 }
143
144 pub const fn reaches_failure_limit(self, thresholds: PeerQualityThresholds) -> bool {
146 self.disconnected >= thresholds.disconnected
147 || self.failed_to_send >= thresholds.failed_to_send
148 || self.failed_to_receive >= thresholds.failed_to_receive
149 }
150}
151
152pub fn order_peers_by_quality(
161 candidates: impl IntoIterator<Item = (Did, PeerQuality)>,
162) -> Vec<Did> {
163 let mut ranked = candidates
164 .into_iter()
165 .enumerate()
166 .map(|(index, (did, quality))| (quality.connection_rank(), index, did))
167 .collect::<Vec<_>>();
168 ranked.sort_by_key(|(rank, index, _)| (*rank, *index));
169 ranked.into_iter().map(|(_, _, did)| did).collect()
170}
171
172#[cfg_attr(feature = "wasm", async_trait(?Send))]
176#[cfg_attr(not(feature = "wasm"), async_trait)]
177pub trait Measure {
178 async fn incr(&self, did: Did, counter: MeasureCounter);
180 async fn get_count(&self, did: Did, counter: MeasureCounter) -> u64;
182}
183
184#[cfg_attr(feature = "wasm", async_trait(?Send))]
186#[cfg_attr(not(feature = "wasm"), async_trait)]
187pub trait BehaviourJudgement: Measure {
188 async fn quality(&self, did: Did) -> PeerQuality;
193
194 async fn good(&self, did: Did) -> bool;
200}
201
202#[cfg_attr(feature = "wasm", async_trait(?Send))]
206#[cfg_attr(not(feature = "wasm"), async_trait)]
207pub trait ConnectBehaviour<const THRESHOLD: u64>: Measure {
208 async fn good(&self, did: Did) -> bool {
210 let conn = self.get_count(did, MeasureCounter::Connect).await;
211 let disconn = self.get_count(did, MeasureCounter::Disconnected).await;
212 tracing::debug!(
213 "[ConnectBehaviour] in threshold: {:}, connect: {:}, disconn: {:}",
214 THRESHOLD,
215 conn,
216 disconn
217 );
218 disconn < THRESHOLD
219 }
220}
221
222#[cfg_attr(feature = "wasm", async_trait(?Send))]
226#[cfg_attr(not(feature = "wasm"), async_trait)]
227pub trait MessageSendBehaviour<const THRESHOLD: u64>: Measure {
228 async fn good(&self, did: Did) -> bool {
230 let failed = self.get_count(did, MeasureCounter::FailedToSend).await;
231 failed < THRESHOLD
232 }
233}
234
235#[cfg_attr(feature = "wasm", async_trait(?Send))]
239#[cfg_attr(not(feature = "wasm"), async_trait)]
240pub trait MessageRecvBehaviour<const THRESHOLD: u64>: Measure {
241 async fn good(&self, did: Did) -> bool {
243 let failed = self.get_count(did, MeasureCounter::FailedToReceive).await;
244 failed < THRESHOLD
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::ecc::SecretKey;
252
253 fn did() -> Did {
254 SecretKey::random().address().into()
255 }
256
257 #[test]
258 fn peer_quality_evidence_classifies_unknown_healthy_and_degraded() {
259 let thresholds = PeerQualityThresholds::new(3, 10, 10);
260 assert_eq!(
261 PeerQualityEvidence::new(0, 0, 0, 0, 0, 0).classify(thresholds),
262 PeerQuality::Unknown
263 );
264 assert_eq!(
265 PeerQualityEvidence::new(1, 0, 0, 0, 0, 0).classify(thresholds),
266 PeerQuality::Healthy
267 );
268 assert_eq!(
269 PeerQualityEvidence::new(1, 3, 0, 0, 0, 0).classify(thresholds),
270 PeerQuality::Degraded
271 );
272 assert_eq!(
273 PeerQualityEvidence::new(1, 0, 0, 10, 0, 0).classify(thresholds),
274 PeerQuality::Degraded
275 );
276 assert_eq!(
277 PeerQualityEvidence::new(1, 0, 0, 0, 0, 10).classify(thresholds),
278 PeerQuality::Degraded
279 );
280 }
281
282 #[test]
283 fn order_peers_by_quality_is_stable_permutation() {
284 let degraded = did();
285 let unknown_a = did();
286 let healthy = did();
287 let unknown_b = did();
288
289 let ordered = order_peers_by_quality([
290 (degraded, PeerQuality::Degraded),
291 (unknown_a, PeerQuality::Unknown),
292 (healthy, PeerQuality::Healthy),
293 (unknown_b, PeerQuality::Unknown),
294 ]);
295
296 assert_eq!(ordered, vec![healthy, unknown_a, unknown_b, degraded]);
297 }
298}