#![warn(missing_docs)]
use std::sync::Arc;
use async_trait::async_trait;
use crate::dht::Did;
#[cfg(not(feature = "wasm"))]
pub type MeasureImpl = Arc<dyn BehaviourJudgement + Send + Sync>;
#[cfg(feature = "wasm")]
pub type MeasureImpl = Arc<dyn BehaviourJudgement>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MeasureCounter {
Sent,
FailedToSend,
Received,
FailedToReceive,
Connect,
Disconnected,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PeerQuality {
Healthy,
Unknown,
Degraded,
}
impl PeerQuality {
pub const fn connection_rank(self) -> u8 {
match self {
Self::Healthy => 0,
Self::Unknown => 1,
Self::Degraded => 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PeerQualityThresholds {
disconnected: u64,
failed_to_send: u64,
failed_to_receive: u64,
}
impl PeerQualityThresholds {
pub const fn new(disconnected: u64, failed_to_send: u64, failed_to_receive: u64) -> Self {
Self {
disconnected,
failed_to_send,
failed_to_receive,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PeerQualityEvidence {
connected: u64,
disconnected: u64,
sent: u64,
failed_to_send: u64,
received: u64,
failed_to_receive: u64,
}
impl PeerQualityEvidence {
pub const fn new(
connected: u64,
disconnected: u64,
sent: u64,
failed_to_send: u64,
received: u64,
failed_to_receive: u64,
) -> Self {
Self {
connected,
disconnected,
sent,
failed_to_send,
received,
failed_to_receive,
}
}
pub async fn from_measure<M>(measure: &M, did: Did) -> Self
where M: Measure + ?Sized {
Self {
connected: measure.get_count(did, MeasureCounter::Connect).await,
disconnected: measure.get_count(did, MeasureCounter::Disconnected).await,
sent: measure.get_count(did, MeasureCounter::Sent).await,
failed_to_send: measure.get_count(did, MeasureCounter::FailedToSend).await,
received: measure.get_count(did, MeasureCounter::Received).await,
failed_to_receive: measure
.get_count(did, MeasureCounter::FailedToReceive)
.await,
}
}
pub const fn classify(self, thresholds: PeerQualityThresholds) -> PeerQuality {
if self.reaches_failure_limit(thresholds) {
PeerQuality::Degraded
} else if self.has_positive_observation() {
PeerQuality::Healthy
} else {
PeerQuality::Unknown
}
}
pub const fn has_positive_observation(self) -> bool {
self.connected > 0 || self.sent > 0 || self.received > 0
}
pub const fn reaches_failure_limit(self, thresholds: PeerQualityThresholds) -> bool {
self.disconnected >= thresholds.disconnected
|| self.failed_to_send >= thresholds.failed_to_send
|| self.failed_to_receive >= thresholds.failed_to_receive
}
}
pub fn order_peers_by_quality(
candidates: impl IntoIterator<Item = (Did, PeerQuality)>,
) -> Vec<Did> {
let mut ranked = candidates
.into_iter()
.enumerate()
.map(|(index, (did, quality))| (quality.connection_rank(), index, did))
.collect::<Vec<_>>();
ranked.sort_by_key(|(rank, index, _)| (*rank, *index));
ranked.into_iter().map(|(_, _, did)| did).collect()
}
#[cfg_attr(feature = "wasm", async_trait(?Send))]
#[cfg_attr(not(feature = "wasm"), async_trait)]
pub trait Measure {
async fn incr(&self, did: Did, counter: MeasureCounter);
async fn get_count(&self, did: Did, counter: MeasureCounter) -> u64;
}
#[cfg_attr(feature = "wasm", async_trait(?Send))]
#[cfg_attr(not(feature = "wasm"), async_trait)]
pub trait BehaviourJudgement: Measure {
async fn quality(&self, did: Did) -> PeerQuality;
async fn good(&self, did: Did) -> bool;
}
#[cfg_attr(feature = "wasm", async_trait(?Send))]
#[cfg_attr(not(feature = "wasm"), async_trait)]
pub trait ConnectBehaviour<const THRESHOLD: u64>: Measure {
async fn good(&self, did: Did) -> bool {
let conn = self.get_count(did, MeasureCounter::Connect).await;
let disconn = self.get_count(did, MeasureCounter::Disconnected).await;
tracing::debug!(
"[ConnectBehaviour] in threshold: {:}, connect: {:}, disconn: {:}",
THRESHOLD,
conn,
disconn
);
disconn < THRESHOLD
}
}
#[cfg_attr(feature = "wasm", async_trait(?Send))]
#[cfg_attr(not(feature = "wasm"), async_trait)]
pub trait MessageSendBehaviour<const THRESHOLD: u64>: Measure {
async fn good(&self, did: Did) -> bool {
let failed = self.get_count(did, MeasureCounter::FailedToSend).await;
failed < THRESHOLD
}
}
#[cfg_attr(feature = "wasm", async_trait(?Send))]
#[cfg_attr(not(feature = "wasm"), async_trait)]
pub trait MessageRecvBehaviour<const THRESHOLD: u64>: Measure {
async fn good(&self, did: Did) -> bool {
let failed = self.get_count(did, MeasureCounter::FailedToReceive).await;
failed < THRESHOLD
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ecc::SecretKey;
fn did() -> Did {
SecretKey::random().address().into()
}
#[test]
fn peer_quality_evidence_classifies_unknown_healthy_and_degraded() {
let thresholds = PeerQualityThresholds::new(3, 10, 10);
assert_eq!(
PeerQualityEvidence::new(0, 0, 0, 0, 0, 0).classify(thresholds),
PeerQuality::Unknown
);
assert_eq!(
PeerQualityEvidence::new(1, 0, 0, 0, 0, 0).classify(thresholds),
PeerQuality::Healthy
);
assert_eq!(
PeerQualityEvidence::new(1, 3, 0, 0, 0, 0).classify(thresholds),
PeerQuality::Degraded
);
assert_eq!(
PeerQualityEvidence::new(1, 0, 0, 10, 0, 0).classify(thresholds),
PeerQuality::Degraded
);
assert_eq!(
PeerQualityEvidence::new(1, 0, 0, 0, 0, 10).classify(thresholds),
PeerQuality::Degraded
);
}
#[test]
fn order_peers_by_quality_is_stable_permutation() {
let degraded = did();
let unknown_a = did();
let healthy = did();
let unknown_b = did();
let ordered = order_peers_by_quality([
(degraded, PeerQuality::Degraded),
(unknown_a, PeerQuality::Unknown),
(healthy, PeerQuality::Healthy),
(unknown_b, PeerQuality::Unknown),
]);
assert_eq!(ordered, vec![healthy, unknown_a, unknown_b, degraded]);
}
}