use offline_protocol_core::{Message, MessagePriority};
use offline_protocol_reliability::{RelaySeenCache, RelaySeenConfig};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::time::{Duration, Instant};
use super::custody::CUSTODY_META_KEY;
pub const DEFAULT_RELAY_MAX_TTL: u8 = 8;
pub const DEFAULT_RELAY_DENSE_MAX_TTL: u8 = 5;
pub const DEFAULT_RELAY_DENSE_DEGREE: usize = 6;
pub const RELAY_MAX_HOP_COUNT: u8 = 16;
pub const DEFAULT_RELAY_FANOUT: usize = 3;
pub const DEFAULT_RELAY_JITTER_MIN_MS: u64 = 20;
pub const DEFAULT_RELAY_JITTER_MAX_MS: u64 = 200;
pub const DEFAULT_RELAY_RATE_PER_SEC: f32 = 10.0;
pub const DEFAULT_RELAY_BURST: f32 = 30.0;
pub const RELAY_OWN_TRAFFIC_RESERVE: f32 = 5.0;
pub const DEFAULT_RELAY_PEER_RATE_PER_SEC: f32 = 5.0;
pub const DEFAULT_RELAY_PEER_BURST: f32 = 15.0;
pub const DEFAULT_RELAY_QUEUE_CAPACITY: usize = 256;
pub const MAX_RELAY_RATE_PEERS: usize = 256;
pub const RELAY_QUEUE_MAX_OVERDUE: Duration = Duration::from_secs(5);
pub const DEFAULT_RELAY_BIAS_MIN_SCALE: f32 = 0.25;
pub const DEFAULT_RELAY_BIAS_MAX_HANDICAP_MS: u64 = 400;
pub const DEFAULT_RELAY_ACTIVITY_WINDOW_SECS: u64 = 60;
pub const DEFAULT_RELAY_ACTIVITY_MIN_FORWARDS: u64 = 3;
pub const DEFAULT_RELAY_ACTIVITY_IDLE_WINDOWS: u32 = 2;
#[derive(Debug, Clone)]
pub struct MeshRelayConfig {
pub max_ttl: u8,
pub dense_max_ttl: u8,
pub dense_degree: usize,
pub fanout: usize,
pub jitter_min: Duration,
pub jitter_max: Duration,
pub rate_per_sec: f32,
pub burst: f32,
pub peer_rate_per_sec: f32,
pub peer_burst: f32,
pub queue_capacity: usize,
pub seen: RelaySeenConfig,
pub bias_min_scale: f32,
pub bias_max_handicap: Duration,
pub activity_window: Duration,
pub activity_min_forwards: u64,
pub activity_idle_windows: u32,
}
impl Default for MeshRelayConfig {
fn default() -> Self {
Self {
max_ttl: DEFAULT_RELAY_MAX_TTL,
dense_max_ttl: DEFAULT_RELAY_DENSE_MAX_TTL,
dense_degree: DEFAULT_RELAY_DENSE_DEGREE,
fanout: DEFAULT_RELAY_FANOUT,
jitter_min: Duration::from_millis(DEFAULT_RELAY_JITTER_MIN_MS),
jitter_max: Duration::from_millis(DEFAULT_RELAY_JITTER_MAX_MS),
rate_per_sec: DEFAULT_RELAY_RATE_PER_SEC,
burst: DEFAULT_RELAY_BURST,
peer_rate_per_sec: DEFAULT_RELAY_PEER_RATE_PER_SEC,
peer_burst: DEFAULT_RELAY_PEER_BURST,
queue_capacity: DEFAULT_RELAY_QUEUE_CAPACITY,
seen: RelaySeenConfig::default(),
bias_min_scale: DEFAULT_RELAY_BIAS_MIN_SCALE,
bias_max_handicap: Duration::from_millis(DEFAULT_RELAY_BIAS_MAX_HANDICAP_MS),
activity_window: Duration::from_secs(DEFAULT_RELAY_ACTIVITY_WINDOW_SECS),
activity_min_forwards: DEFAULT_RELAY_ACTIVITY_MIN_FORWARDS,
activity_idle_windows: DEFAULT_RELAY_ACTIVITY_IDLE_WINDOWS,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
struct RelayConditions {
battery: Option<u8>,
is_charging: bool,
eager: bool,
soft_floor: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelayActivity {
Began {
forwarded: u64,
},
Ceased,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelayRejection {
AlreadySeen,
NarrowedByDuplicate,
HopLimitReached,
PeerRateLimited,
QueueFull,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelayAdmission {
Queued,
Rejected(RelayRejection),
}
#[derive(Debug, Clone)]
pub struct PendingRelay {
pub message: Message,
pub arrival_peer: Option<String>,
pub heard_from: Vec<String>,
pub due_at: Instant,
pub custody_request: Option<String>,
pub custody_target: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MeshRelayCounters {
pub queued: u64,
pub transmissions: u64,
pub forwarded: u64,
pub requeued: u64,
pub queue_evicted: u64,
pub duplicates_suppressed: u64,
pub cancelled_by_duplicate: u64,
pub peer_rate_limited: u64,
pub queue_full: u64,
pub hop_limit_reached: u64,
pub rate_deferred: u64,
pub abandoned_overdue: u64,
pub ttl_clamped: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct MeshRelayStats {
pub forwarded: u64,
pub transmissions: u64,
pub queued: u64,
pub awaiting_transmission: usize,
pub duplicates_suppressed: u64,
pub covered_by_a_neighbor: u64,
pub peer_rate_limited: u64,
pub refused_queue_full: u64,
pub rate_deferred: u64,
pub abandoned_overdue: u64,
pub hop_limit_reached: u64,
pub reach_clamped: u64,
pub dropped_for_capacity: u64,
}
#[derive(Debug, Clone)]
struct TokenBucket {
tokens: f32,
capacity: f32,
refill_per_sec: f32,
last_refill: Instant,
}
impl TokenBucket {
fn new(capacity: f32, refill_per_sec: f32, now: Instant) -> Self {
Self {
tokens: capacity.max(0.0),
capacity: capacity.max(0.0),
refill_per_sec: refill_per_sec.max(0.0),
last_refill: now,
}
}
fn refill(&mut self, now: Instant) {
let elapsed = now
.saturating_duration_since(self.last_refill)
.as_secs_f32();
if elapsed > 0.0 {
self.tokens = (self.tokens + elapsed * self.refill_per_sec).min(self.capacity);
self.last_refill = now;
}
}
fn resize(&mut self, capacity: f32, refill_per_sec: f32, now: Instant) {
self.refill(now);
self.capacity = capacity.max(0.0);
self.refill_per_sec = refill_per_sec.max(0.0);
self.tokens = self.tokens.min(self.capacity);
}
fn try_take_above(&mut self, floor: f32, now: Instant) -> bool {
self.refill(now);
if self.tokens >= 1.0 + floor {
self.tokens -= 1.0;
true
} else {
false
}
}
fn has_above(&mut self, floor: f32, now: Instant) -> bool {
self.refill(now);
self.tokens >= 1.0 + floor
}
fn spend(&mut self) {
self.tokens = (self.tokens - 1.0).max(0.0);
}
}
#[derive(Debug)]
pub struct MeshRelayGovernor {
config: MeshRelayConfig,
seen: RelaySeenCache,
pending: Vec<PendingRelay>,
send_budget: TokenBucket,
forward_budget: TokenBucket,
own_reserve: f32,
peer_budgets: HashMap<String, TokenBucket>,
counters: MeshRelayCounters,
local_id: String,
conditions: RelayConditions,
active_relay: bool,
window_started_at: Instant,
window_forwards: u64,
idle_windows: u32,
}
impl MeshRelayGovernor {
pub fn with_config(local_id: impl Into<String>, config: MeshRelayConfig) -> Self {
let now = Instant::now();
let seen = RelaySeenCache::with_config(config.seen.clone());
Self {
send_budget: TokenBucket::new(config.burst, config.rate_per_sec, now),
forward_budget: TokenBucket::new(config.burst, config.rate_per_sec, now),
own_reserve: RELAY_OWN_TRAFFIC_RESERVE.min(config.burst.max(0.0) / 2.0),
seen,
pending: Vec::new(),
peer_budgets: HashMap::new(),
counters: MeshRelayCounters::default(),
local_id: local_id.into(),
conditions: RelayConditions::default(),
active_relay: false,
window_started_at: now,
window_forwards: 0,
idle_windows: 0,
config,
}
}
pub fn set_local_id(&mut self, local_id: impl Into<String>) {
self.local_id = local_id.into();
}
pub fn config(&self) -> &MeshRelayConfig {
&self.config
}
pub fn set_conditions(
&mut self,
battery: Option<u8>,
is_charging: bool,
eager: bool,
soft_floor: u8,
) {
self.conditions = RelayConditions {
battery,
is_charging,
eager,
soft_floor,
};
let scale = self.bias_scale();
self.forward_budget.resize(
self.config.burst * scale,
self.config.rate_per_sec * scale,
Instant::now(),
);
}
fn bias_scale(&self) -> f32 {
let min_scale = self.config.bias_min_scale.clamp(0.0, 1.0);
if self.conditions.is_charging || self.conditions.eager {
return 1.0;
}
let Some(level) = self.conditions.battery else {
return 1.0;
};
let floor = self.conditions.soft_floor.min(100);
if level >= 100 {
return 1.0;
}
if level <= floor {
return min_scale;
}
let headroom = (level - floor) as f32 / (100 - floor).max(1) as f32;
min_scale + headroom * (1.0 - min_scale)
}
pub fn is_active_relay(&self) -> bool {
self.active_relay
}
pub fn observe_activity(&mut self, now: Instant) -> Option<RelayActivity> {
if now.saturating_duration_since(self.window_started_at) < self.config.activity_window {
return None;
}
let forwarded = std::mem::take(&mut self.window_forwards);
self.window_started_at = now;
if forwarded >= self.config.activity_min_forwards.max(1) {
self.idle_windows = 0;
if !self.active_relay {
self.active_relay = true;
return Some(RelayActivity::Began { forwarded });
}
return None;
}
if !self.active_relay {
return None;
}
self.idle_windows = self.idle_windows.saturating_add(1);
if self.idle_windows >= self.config.activity_idle_windows.max(1) {
self.active_relay = false;
return Some(RelayActivity::Ceased);
}
None
}
pub fn force_inactive(&mut self) -> bool {
self.window_forwards = 0;
self.idle_windows = 0;
std::mem::replace(&mut self.active_relay, false)
}
pub fn counters(&self) -> &MeshRelayCounters {
&self.counters
}
pub fn pending_len(&self) -> usize {
self.pending.len()
}
pub fn seen_capacity_evictions(&self) -> u64 {
self.seen.capacity_evictions()
}
pub fn admit(
&mut self,
message: &Message,
arrival_peer: Option<&str>,
degree: usize,
) -> RelayAdmission {
let now = Instant::now();
let message_id = message.id.as_str();
let suppressed = self.seen.contains(&message_id);
let origin_resend = suppressed && self.is_origin_resend(message, arrival_peer);
if suppressed && !origin_resend {
self.counters.duplicates_suppressed =
self.counters.duplicates_suppressed.saturating_add(1);
if let Some(pending) = self
.pending
.iter_mut()
.find(|p| p.message.id.as_str() == message_id)
{
if let Some(peer) = arrival_peer {
if pending.arrival_peer.as_deref() != Some(peer)
&& !pending.heard_from.iter().any(|p| p == peer)
{
pending.heard_from.push(peer.to_string());
}
}
return RelayAdmission::Rejected(RelayRejection::NarrowedByDuplicate);
}
return RelayAdmission::Rejected(RelayRejection::AlreadySeen);
}
if let Some(peer) = arrival_peer {
if !self.take_peer_token(peer, now) {
self.counters.peer_rate_limited = self.counters.peer_rate_limited.saturating_add(1);
return RelayAdmission::Rejected(RelayRejection::PeerRateLimited);
}
}
let custody_request = message.metadata.get(CUSTODY_META_KEY).cloned();
let Some(forwarded) = self.prepare_hop(message, degree) else {
self.counters.hop_limit_reached = self.counters.hop_limit_reached.saturating_add(1);
return RelayAdmission::Rejected(RelayRejection::HopLimitReached);
};
if self.pending.len() >= self.config.queue_capacity && !self.evict_for(&forwarded, now) {
self.counters.queue_full = self.counters.queue_full.saturating_add(1);
return RelayAdmission::Rejected(RelayRejection::QueueFull);
}
if origin_resend {
self.seen.forget(&message_id);
}
self.seen.observe(&message_id);
let due_at = now + self.jitter_for(&message_id, degree);
self.pending.push(PendingRelay {
message: forwarded,
arrival_peer: arrival_peer.map(str::to_string),
heard_from: Vec::new(),
due_at,
custody_request,
custody_target: None,
});
self.counters.queued = self.counters.queued.saturating_add(1);
RelayAdmission::Queued
}
pub fn admit_held(
&mut self,
message: Message,
target: &str,
now: Instant,
) -> Result<(), RelayRejection> {
if !self.take_peer_token(target, now) {
return Err(RelayRejection::PeerRateLimited);
}
if self.pending.len() >= self.config.queue_capacity && !self.evict_for(&message, now) {
return Err(RelayRejection::QueueFull);
}
self.pending.push(PendingRelay {
message,
arrival_peer: None,
heard_from: Vec::new(),
due_at: now,
custody_request: None,
custody_target: Some(target.to_string()),
});
Ok(())
}
#[cfg(test)]
pub fn take_due(&mut self, now: Instant) -> Vec<PendingRelay> {
self.release_due(now).0
}
pub fn release_due(&mut self, now: Instant) -> (Vec<PendingRelay>, Vec<PendingRelay>) {
self.seen.expire(now);
let mut due = Vec::new();
let mut abandoned = Vec::new();
let mut keep: Vec<PendingRelay> = Vec::with_capacity(self.pending.len());
for relay in std::mem::take(&mut self.pending) {
if relay.due_at > now {
keep.push(relay);
continue;
}
if now.saturating_duration_since(relay.due_at) > RELAY_QUEUE_MAX_OVERDUE {
if relay.custody_target.is_none() {
self.seen.forget(&relay.message.id.as_str());
self.counters.abandoned_overdue =
self.counters.abandoned_overdue.saturating_add(1);
}
abandoned.push(relay);
continue;
}
if self.has_send_budget(now) {
due.push(relay);
} else {
self.counters.rate_deferred = self.counters.rate_deferred.saturating_add(1);
keep.push(relay);
}
}
self.pending = keep;
(due, abandoned)
}
fn has_send_budget(&mut self, now: Instant) -> bool {
self.send_budget.has_above(self.own_reserve, now) && self.forward_budget.has_above(0.0, now)
}
pub fn take_send_token(&mut self) -> bool {
let now = Instant::now();
if !self.has_send_budget(now) {
self.counters.rate_deferred = self.counters.rate_deferred.saturating_add(1);
return false;
}
self.send_budget.spend();
self.forward_budget.spend();
self.counters.transmissions = self.counters.transmissions.saturating_add(1);
true
}
pub fn take_own_send_token(&mut self) -> bool {
self.claim_transmission(0.0)
}
fn claim_transmission(&mut self, floor: f32) -> bool {
if self.send_budget.try_take_above(floor, Instant::now()) {
self.counters.transmissions = self.counters.transmissions.saturating_add(1);
true
} else {
self.counters.rate_deferred = self.counters.rate_deferred.saturating_add(1);
false
}
}
pub fn record_forwarded(&mut self) {
self.counters.forwarded = self.counters.forwarded.saturating_add(1);
self.window_forwards = self.window_forwards.saturating_add(1);
}
pub fn requeue(&mut self, relay: PendingRelay) -> Option<PendingRelay> {
if self.pending.len() >= self.config.queue_capacity {
if relay.custody_target.is_some() {
return Some(relay);
}
self.seen.forget(&relay.message.id.as_str());
self.counters.queue_full = self.counters.queue_full.saturating_add(1);
return None;
}
self.counters.requeued = self.counters.requeued.saturating_add(1);
self.pending.push(relay);
None
}
pub fn drop_covered(&mut self, relay: PendingRelay) {
debug_assert!(relay.custody_target.is_none());
self.counters.cancelled_by_duplicate =
self.counters.cancelled_by_duplicate.saturating_add(1);
}
pub fn drop_held(&mut self) {
self.pending.retain(|relay| relay.custody_target.is_none());
}
#[cfg(test)]
pub fn is_suppressed(&self, message_id: &str) -> bool {
self.seen.contains(message_id)
}
pub fn mark_handled(&mut self, message_id: &str) {
self.seen.observe(message_id);
}
fn is_origin_resend(&self, message: &Message, arrival_peer: Option<&str>) -> bool {
arrival_peer == Some(message.sender.as_str())
&& message.hop_count.value() == 0
&& !self
.pending
.iter()
.any(|p| p.message.id.as_str() == message.id.as_str())
}
pub fn absorb_settled_duplicate(
&mut self,
message: &Message,
arrival_peer: Option<&str>,
) -> bool {
let message_id = message.id.as_str();
if !self.seen.contains(&message_id) || self.is_origin_resend(message, arrival_peer) {
return false;
}
if self
.pending
.iter()
.any(|p| p.message.id.as_str() == message_id)
{
return false;
}
self.counters.duplicates_suppressed = self.counters.duplicates_suppressed.saturating_add(1);
true
}
pub fn select_targets<'a>(
&self,
neighbors: impl IntoIterator<Item = (&'a str, u8)>,
exclude: &[&str],
message_id: &str,
) -> Vec<String> {
self.select_within_fanout(neighbors, exclude, message_id, self.biased_fanout())
}
pub fn select_origination_targets<'a>(
&self,
neighbors: impl IntoIterator<Item = (&'a str, u8)>,
exclude: &[&str],
message_id: &str,
) -> Vec<String> {
self.select_within_fanout(neighbors, exclude, message_id, self.config.fanout.max(1))
}
fn biased_fanout(&self) -> usize {
((self.config.fanout as f32 * self.bias_scale()).round() as usize).max(1)
}
fn select_within_fanout<'a>(
&self,
neighbors: impl IntoIterator<Item = (&'a str, u8)>,
exclude: &[&str],
message_id: &str,
fanout: usize,
) -> Vec<String> {
let mut candidates: Vec<(&str, u8)> = neighbors
.into_iter()
.filter(|(peer, _)| !exclude.contains(peer))
.collect();
if candidates.len() <= fanout {
return candidates
.into_iter()
.map(|(peer, _)| peer.to_string())
.collect();
}
candidates.sort_by(|(a_peer, a_quality), (b_peer, b_quality)| {
b_quality.cmp(a_quality).then_with(|| {
Self::hash_of(&[message_id, a_peer, &self.local_id]).cmp(&Self::hash_of(&[
message_id,
b_peer,
&self.local_id,
]))
})
});
candidates
.into_iter()
.take(fanout)
.map(|(peer, _)| peer.to_string())
.collect()
}
pub fn forget_peer(&mut self, peer_id: &str) {
self.peer_budgets.remove(peer_id);
}
pub fn maintain(&mut self, now: Instant) {
self.seen.expire(now);
}
fn prepare_hop(&mut self, message: &Message, degree: usize) -> Option<Message> {
if message.hop_count.value() >= RELAY_MAX_HOP_COUNT {
return None;
}
let ceiling = if degree >= self.config.dense_degree {
self.config.dense_max_ttl
} else {
self.config.max_ttl
};
let claimed = message.ttl.value();
let effective = claimed.min(ceiling);
if effective < claimed {
self.counters.ttl_clamped = self.counters.ttl_clamped.saturating_add(1);
}
let remaining = offline_protocol_core::TTL::from_value(effective).decrement()?;
let mut forwarded = message.clone();
forwarded.ttl = remaining;
let _ = forwarded.increment_hop();
forwarded.metadata.remove(CUSTODY_META_KEY);
Some(forwarded)
}
fn evict_for(&mut self, candidate: &Message, now: Instant) -> bool {
let candidate_rank = priority_rank(candidate.priority);
let lower_priority = |p: &PendingRelay| priority_rank(p.message.priority) < candidate_rank;
let victim = self
.pending
.iter()
.enumerate()
.filter(|(_, p)| lower_priority(p) && p.due_at > now)
.min_by_key(|(_, p)| priority_rank(p.message.priority))
.map(|(index, _)| index)
.or_else(|| {
self.pending
.iter()
.enumerate()
.filter(|(_, p)| lower_priority(p))
.min_by_key(|(_, p)| priority_rank(p.message.priority))
.map(|(index, _)| index)
});
match victim {
Some(index) => {
let displaced = self.pending.remove(index);
self.seen.forget(&displaced.message.id.as_str());
self.counters.queue_evicted = self.counters.queue_evicted.saturating_add(1);
true
}
None => false,
}
}
fn take_peer_token(&mut self, peer_id: &str, now: Instant) -> bool {
if !self.peer_budgets.contains_key(peer_id) {
if self.peer_budgets.len() >= MAX_RELAY_RATE_PEERS {
for bucket in self.peer_budgets.values_mut() {
bucket.refill(now);
}
let most_recovered = self
.peer_budgets
.iter()
.max_by(|(_, a), (_, b)| a.tokens.total_cmp(&b.tokens))
.map(|(peer, _)| peer.clone());
if let Some(peer) = most_recovered {
self.peer_budgets.remove(&peer);
}
}
self.peer_budgets.insert(
peer_id.to_string(),
TokenBucket::new(self.config.peer_burst, self.config.peer_rate_per_sec, now),
);
}
self.peer_budgets
.get_mut(peer_id)
.map(|bucket| bucket.try_take_above(0.0, now))
.unwrap_or(true)
}
fn jitter_for(&self, message_id: &str, degree: usize) -> Duration {
let min_ms = self.config.jitter_min.as_millis() as u64;
let max_ms = (self.config.jitter_max.as_millis() as u64).max(min_ms);
let density_scale = 1 + (degree / self.config.dense_degree.max(1)) as u64;
let span = (max_ms - min_ms).saturating_mul(density_scale).max(1);
let handicap_ms = ((1.0 - self.bias_scale()).clamp(0.0, 1.0)
* self.config.bias_max_handicap.as_millis() as f32) as u64;
let hash = Self::hash_of(&[message_id, &self.local_id]);
Duration::from_millis(min_ms + handicap_ms + (hash % span))
}
fn hash_of(parts: &[&str]) -> u64 {
let mut hasher = DefaultHasher::new();
for part in parts {
part.hash(&mut hasher);
}
hasher.finish()
}
}
fn priority_rank(priority: MessagePriority) -> u8 {
match priority {
MessagePriority::Low => 0,
MessagePriority::Medium => 1,
MessagePriority::High => 2,
MessagePriority::Critical => 3,
}
}
#[cfg(test)]
mod tests {
use super::*;
use offline_protocol_core::{AppId, UserId, TTL};
fn immediate_config() -> MeshRelayConfig {
MeshRelayConfig {
jitter_min: Duration::from_millis(0),
jitter_max: Duration::from_millis(0),
..Default::default()
}
}
fn governor() -> MeshRelayGovernor {
MeshRelayGovernor::with_config("relay-node", immediate_config())
}
fn frame_with(ttl: u8, priority: MessagePriority) -> Message {
Message::builder(
UserId::new("alice").unwrap(),
UserId::new("bob").unwrap(),
AppId::new("app").unwrap(),
)
.content("hello")
.priority(priority)
.ttl(TTL::new(ttl).unwrap())
.build()
}
fn frame() -> Message {
frame_with(8, MessagePriority::Medium)
}
#[test]
fn a_frame_is_forwarded_once_however_many_copies_arrive() {
let mut gov = governor();
let msg = frame();
assert_eq!(gov.admit(&msg, Some("b"), 3), RelayAdmission::Queued);
let due = gov.take_due(Instant::now());
assert_eq!(due.len(), 1);
for _ in 0..5 {
assert_eq!(
gov.admit(&msg, Some("c"), 3),
RelayAdmission::Rejected(RelayRejection::AlreadySeen)
);
}
assert_eq!(gov.counters().queued, 1);
assert_eq!(gov.counters().duplicates_suppressed, 5);
}
#[test]
fn a_copy_arriving_while_we_wait_narrows_our_forward_rather_than_cancelling_it() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
jitter_min: Duration::from_millis(50),
jitter_max: Duration::from_millis(200),
..Default::default()
},
);
let msg = frame();
assert_eq!(gov.admit(&msg, Some("b"), 6), RelayAdmission::Queued);
assert_eq!(
gov.admit(&msg, Some("c"), 6),
RelayAdmission::Rejected(RelayRejection::NarrowedByDuplicate)
);
gov.admit(&msg, Some("c"), 6);
gov.admit(&msg, Some("b"), 6);
assert_eq!(gov.pending_len(), 1, "the forward must survive a copy");
assert_eq!(gov.counters().cancelled_by_duplicate, 0);
let due = gov.take_due(Instant::now() + Duration::from_secs(1));
assert_eq!(due.len(), 1);
assert_eq!(due[0].arrival_peer.as_deref(), Some("b"));
assert_eq!(due[0].heard_from, vec!["c".to_string()]);
}
#[test]
fn a_forward_every_neighbor_already_holds_is_dropped_as_covered() {
let mut gov = governor();
let msg = frame();
gov.admit(&msg, Some("b"), 3);
gov.admit(&msg, Some("c"), 3);
let relay = gov
.take_due(Instant::now() + Duration::from_secs(1))
.pop()
.expect("due");
gov.drop_covered(relay);
assert_eq!(gov.counters().cancelled_by_duplicate, 1);
assert!(
gov.is_suppressed(&msg.id.as_str()),
"a covered frame is held all around, so later copies stay absorbed"
);
}
#[test]
fn an_inflated_hop_budget_is_cut_to_local_policy() {
let mut gov = governor();
let msg = frame_with(255, MessagePriority::Medium);
assert_eq!(gov.admit(&msg, Some("b"), 2), RelayAdmission::Queued);
let due = gov.take_due(Instant::now());
assert_eq!(due.len(), 1);
assert_eq!(due[0].message.ttl.value(), DEFAULT_RELAY_MAX_TTL - 1);
assert_eq!(gov.counters().ttl_clamped, 1);
}
#[test]
fn a_dense_neighborhood_uses_the_lower_hop_ceiling() {
let mut gov = governor();
let msg = frame_with(255, MessagePriority::Medium);
assert_eq!(
gov.admit(&msg, Some("b"), DEFAULT_RELAY_DENSE_DEGREE),
RelayAdmission::Queued
);
let due = gov.take_due(Instant::now());
assert_eq!(due[0].message.ttl.value(), DEFAULT_RELAY_DENSE_MAX_TTL - 1);
}
#[test]
fn a_frame_out_of_hops_is_not_forwarded() {
let mut gov = governor();
let msg = frame_with(1, MessagePriority::Medium);
assert_eq!(
gov.admit(&msg, Some("b"), 3),
RelayAdmission::Rejected(RelayRejection::HopLimitReached)
);
assert_eq!(gov.counters().hop_limit_reached, 1);
}
#[test]
fn the_last_hop_is_forwarded_and_the_one_after_it_is_not() {
let mut gov = governor();
assert_eq!(
gov.admit(&frame_with(2, MessagePriority::Medium), Some("b"), 3),
RelayAdmission::Queued
);
let due = gov.take_due(Instant::now());
assert_eq!(due[0].message.ttl.value(), 1);
assert!(due[0].message.is_ttl_exhausted());
}
#[test]
fn the_hop_ceiling_stops_a_frame_that_evaded_the_budget() {
let mut gov = governor();
let mut msg = frame_with(255, MessagePriority::Medium);
for _ in 0..RELAY_MAX_HOP_COUNT {
let _ = msg.increment_hop();
}
assert_eq!(
gov.admit(&msg, Some("b"), 3),
RelayAdmission::Rejected(RelayRejection::HopLimitReached)
);
}
#[test]
fn one_noisy_neighbor_cannot_consume_the_whole_node() {
let mut gov = governor();
let mut accepted_from_noisy = 0;
for _ in 0..200 {
if gov.admit(&frame(), Some("noisy"), 3) == RelayAdmission::Queued {
accepted_from_noisy += 1;
}
}
assert!(
accepted_from_noisy <= DEFAULT_RELAY_PEER_BURST as usize + 2,
"accepted {accepted_from_noisy} frames from one neighbor"
);
assert!(gov.counters().peer_rate_limited > 0);
assert_eq!(
gov.admit(&frame(), Some("quiet"), 3),
RelayAdmission::Queued
);
}
#[test]
fn transmission_never_exceeds_the_node_budget() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
queue_capacity: 512,
peer_rate_per_sec: 10_000.0,
peer_burst: 10_000.0,
..immediate_config()
},
);
for _ in 0..200 {
gov.admit(&frame(), Some("b"), 3);
}
let due = gov.take_due(Instant::now());
assert!(!due.is_empty());
let granted = (0..1000).filter(|_| gov.take_send_token()).count();
assert!(
granted <= DEFAULT_RELAY_BURST as usize + 1,
"granted {granted} transmissions against a burst of {DEFAULT_RELAY_BURST}"
);
assert_eq!(gov.counters().transmissions as usize, granted);
assert!(gov.counters().rate_deferred > 0);
}
#[test]
fn a_frame_that_reached_no_neighbor_for_budget_goes_back_on_the_queue() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
peer_rate_per_sec: 10_000.0,
peer_burst: 10_000.0,
..immediate_config()
},
);
for _ in 0..40 {
gov.admit(&frame(), Some("b"), 3);
}
let due = gov.take_due(Instant::now());
assert!(
due.len() > DEFAULT_RELAY_BURST as usize,
"the batch must outrun the budget for this to be the case under test"
);
let mut requeued = 0;
for relay in due {
if gov.take_send_token() {
continue;
}
gov.requeue(relay);
requeued += 1;
}
assert!(requeued > 0, "the budget must have run out mid-batch");
assert_eq!(gov.pending_len(), requeued, "every starved frame is kept");
assert_eq!(gov.counters().requeued as usize, requeued);
}
#[test]
fn a_requeued_frame_is_still_abandoned_once_it_is_far_past_its_turn() {
let mut gov = governor();
gov.admit(&frame(), Some("b"), 3);
let mut due = gov.take_due(Instant::now());
assert_eq!(due.len(), 1);
gov.requeue(due.remove(0));
assert_eq!(gov.pending_len(), 1);
let later = gov.take_due(Instant::now() + RELAY_QUEUE_MAX_OVERDUE + Duration::from_secs(1));
assert!(later.is_empty());
assert_eq!(gov.counters().abandoned_overdue, 1);
assert_eq!(gov.pending_len(), 0);
}
#[test]
fn a_frame_abandoned_for_waiting_too_long_releases_its_id() {
let mut gov = governor();
let msg = frame();
gov.admit(&msg, Some("b"), 3);
let due = gov.take_due(Instant::now() + RELAY_QUEUE_MAX_OVERDUE + Duration::from_secs(1));
assert!(due.is_empty());
assert_eq!(gov.counters().abandoned_overdue, 1);
assert_eq!(
gov.admit(&msg, Some("c"), 3),
RelayAdmission::Queued,
"the sender's retransmission must still be carried"
);
}
#[test]
fn a_frame_displaced_from_the_queue_releases_its_id() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
queue_capacity: 1,
peer_rate_per_sec: 10_000.0,
peer_burst: 10_000.0,
..immediate_config()
},
);
let routine = frame_with(8, MessagePriority::Low);
gov.admit(&routine, Some("b"), 3);
gov.admit(&frame_with(8, MessagePriority::Critical), Some("b"), 3);
assert_eq!(gov.counters().queue_evicted, 1);
gov.take_due(Instant::now());
assert_eq!(
gov.admit(&routine, Some("c"), 3),
RelayAdmission::Queued,
"the displaced frame's id must not be blackholed"
);
}
#[test]
fn a_frame_refused_room_on_the_way_back_releases_its_id() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
queue_capacity: 1,
peer_rate_per_sec: 10_000.0,
peer_burst: 10_000.0,
..immediate_config()
},
);
let starved = frame();
gov.admit(&starved, Some("b"), 3);
let mut due = gov.take_due(Instant::now());
assert_eq!(due.len(), 1);
gov.admit(&frame(), Some("b"), 3);
gov.requeue(due.remove(0));
assert_eq!(gov.counters().queue_full, 1);
assert_eq!(gov.pending_len(), 1, "the dropped frame is not held");
gov.take_due(Instant::now());
assert_eq!(
gov.admit(&starved, Some("c"), 3),
RelayAdmission::Queued,
"a frame that got nowhere and could not be kept must stay carryable"
);
}
#[test]
fn forwarding_leaves_room_for_this_devices_own_traffic() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
queue_capacity: 512,
peer_rate_per_sec: 10_000.0,
peer_burst: 10_000.0,
..immediate_config()
},
);
for _ in 0..200 {
gov.admit(&frame(), Some("b"), 3);
}
gov.take_due(Instant::now());
let forwarded = (0..1000).filter(|_| gov.take_send_token()).count();
assert!(forwarded > 0, "forwarding must get its share first");
assert!(
!gov.take_send_token(),
"forwarding must stop at the reserve, not at empty"
);
let own = (0..1000).filter(|_| gov.take_own_send_token()).count();
assert!(
own >= RELAY_OWN_TRAFFIC_RESERVE as usize,
"own traffic got {own} transmissions against a reserve of {RELAY_OWN_TRAFFIC_RESERVE}"
);
assert!(
(forwarded + own) <= DEFAULT_RELAY_BURST as usize + 1,
"{forwarded} forwarded + {own} own exceeds the burst of {DEFAULT_RELAY_BURST}"
);
}
#[test]
fn a_small_configured_burst_still_forwards() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
burst: 2.0,
rate_per_sec: 1.0,
peer_rate_per_sec: 10_000.0,
peer_burst: 10_000.0,
..immediate_config()
},
);
gov.admit(&frame(), Some("b"), 3);
assert!(
!gov.take_due(Instant::now()).is_empty(),
"a small burst must still release forwards"
);
assert!(gov.take_send_token(), "and still allow transmitting them");
}
#[test]
fn a_settled_duplicate_is_absorbed_without_the_full_admission_path() {
let mut gov = governor();
let msg = frame();
assert!(
!gov.absorb_settled_duplicate(&msg, Some("b")),
"an id never seen is not a duplicate"
);
gov.admit(&msg, Some("b"), 3);
assert!(!gov.absorb_settled_duplicate(&msg, Some("b")));
gov.take_due(Instant::now());
assert!(gov.absorb_settled_duplicate(&msg, Some("b")));
assert_eq!(gov.counters().duplicates_suppressed, 1);
}
#[test]
fn the_origin_re_sending_a_forwarded_frame_is_forwarded_again() {
let mut gov = governor();
let msg = frame();
assert_eq!(gov.admit(&msg, Some("alice"), 3), RelayAdmission::Queued);
assert_eq!(gov.take_due(Instant::now()).len(), 1);
assert!(
gov.absorb_settled_duplicate(&msg, Some("carol")),
"an echo from another neighbour is absorbed"
);
assert!(
!gov.absorb_settled_duplicate(&msg, Some("alice")),
"the origin's re-send is not"
);
assert_eq!(gov.admit(&msg, Some("alice"), 3), RelayAdmission::Queued);
assert_eq!(gov.take_due(Instant::now()).len(), 1, "and goes out again");
let mut echoed = msg.clone();
echoed.hop_count = echoed.hop_count.increment().unwrap();
assert!(gov.absorb_settled_duplicate(&echoed, Some("alice")));
}
#[test]
fn a_refused_origin_re_send_leaves_the_id_suppressed() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
peer_burst: 1.0,
peer_rate_per_sec: 0.0,
..immediate_config()
},
);
let msg = frame();
assert_eq!(gov.admit(&msg, Some("alice"), 3), RelayAdmission::Queued);
assert_eq!(gov.take_due(Instant::now()).len(), 1);
assert_eq!(
gov.admit(&msg, Some("alice"), 3),
RelayAdmission::Rejected(RelayRejection::PeerRateLimited)
);
assert!(
gov.is_suppressed(&msg.id.as_str()),
"a refused re-send releases nothing"
);
let mut echoed = msg.clone();
echoed.hop_count = echoed.hop_count.increment().unwrap();
assert!(gov.absorb_settled_duplicate(&echoed, Some("carol")));
assert_eq!(
gov.admit(&echoed, Some("carol"), 3),
RelayAdmission::Rejected(RelayRejection::AlreadySeen)
);
assert!(
gov.take_due(Instant::now()).is_empty(),
"the echo stays put"
);
}
#[test]
fn eviction_prefers_a_frame_that_has_not_yet_gone_out() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
queue_capacity: 2,
jitter_min: Duration::from_millis(0),
jitter_max: Duration::from_millis(0),
peer_rate_per_sec: 10_000.0,
peer_burst: 10_000.0,
..Default::default()
},
);
let due_now = frame_with(8, MessagePriority::Low);
gov.admit(&due_now, Some("b"), 3);
gov.config.jitter_min = Duration::from_millis(500);
gov.config.jitter_max = Duration::from_millis(500);
let waiting = frame_with(8, MessagePriority::Low);
gov.admit(&waiting, Some("b"), 3);
gov.admit(&frame_with(8, MessagePriority::Critical), Some("b"), 3);
assert_eq!(gov.counters().queue_evicted, 1);
assert_eq!(
gov.counters().queue_full,
0,
"making room is not the same as refusing"
);
assert!(
gov.pending
.iter()
.any(|p| p.message.id.as_str() == due_now.id.as_str()),
"the frame already due must survive"
);
assert!(
!gov.pending
.iter()
.any(|p| p.message.id.as_str() == waiting.id.as_str()),
"the frame still waiting is the one displaced"
);
}
#[test]
fn a_full_peer_table_keeps_the_links_that_are_spending() {
let mut gov = MeshRelayGovernor::with_config("relay-node", immediate_config());
for _ in 0..(DEFAULT_RELAY_PEER_BURST as usize - 1) {
gov.admit(&frame(), Some("noisy"), 3);
}
for i in 0..MAX_RELAY_RATE_PEERS {
gov.admit(&frame(), Some(&format!("idle-{i}")), 3);
}
assert!(gov.peer_budgets.len() <= MAX_RELAY_RATE_PEERS);
let noisy = gov
.peer_budgets
.get("noisy")
.expect("a link mid-spend must not be forgotten to make room for idle ones");
assert!(
noisy.tokens < noisy.capacity,
"the noisy link kept its spent allowance rather than being handed a fresh burst"
);
}
#[test]
fn a_refused_frame_leaves_the_way_open_for_another_copy() {
let mut gov = governor();
let msg = frame_with(1, MessagePriority::Medium);
assert_eq!(
gov.admit(&msg, Some("hostile"), 3),
RelayAdmission::Rejected(RelayRejection::HopLimitReached)
);
let mut healthy = msg.clone();
healthy.ttl = TTL::new(8).unwrap();
assert_eq!(
gov.admit(&healthy, Some("good"), 3),
RelayAdmission::Queued,
"a refused frame must not blackhole its id"
);
}
#[test]
fn a_rate_limited_neighbor_does_not_blackhole_the_ids_it_sent() {
let mut gov = governor();
let mut refused = Vec::new();
for _ in 0..200 {
let msg = frame();
if gov.admit(&msg, Some("noisy"), 3)
== RelayAdmission::Rejected(RelayRejection::PeerRateLimited)
{
refused.push(msg);
}
}
assert!(!refused.is_empty());
let victim = &refused[0];
assert_eq!(gov.admit(victim, Some("quiet"), 3), RelayAdmission::Queued);
}
#[test]
fn the_last_hop_does_not_stand_down_for_a_neighbor() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
jitter_min: Duration::from_millis(50),
jitter_max: Duration::from_millis(200),
..Default::default()
},
);
let msg = frame();
assert_eq!(gov.admit(&msg, Some("b"), 3), RelayAdmission::Queued);
assert_eq!(
gov.admit(&msg, Some("c"), 3),
RelayAdmission::Rejected(RelayRejection::NarrowedByDuplicate)
);
assert_eq!(gov.pending_len(), 1, "the delivering copy must survive");
assert_eq!(gov.counters().cancelled_by_duplicate, 0);
}
#[test]
fn a_forward_waiting_far_past_its_turn_is_abandoned() {
let mut gov = governor();
gov.admit(&frame(), Some("b"), 3);
let due = gov.take_due(Instant::now() + RELAY_QUEUE_MAX_OVERDUE + Duration::from_secs(1));
assert!(due.is_empty());
assert_eq!(gov.counters().abandoned_overdue, 1);
assert_eq!(gov.pending_len(), 0);
}
#[test]
fn a_full_queue_refuses_rather_than_growing() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
queue_capacity: 4,
jitter_min: Duration::from_millis(500),
jitter_max: Duration::from_millis(500),
peer_rate_per_sec: 10_000.0,
peer_burst: 10_000.0,
..Default::default()
},
);
for _ in 0..4 {
assert_eq!(gov.admit(&frame(), Some("b"), 3), RelayAdmission::Queued);
}
assert_eq!(
gov.admit(&frame(), Some("b"), 3),
RelayAdmission::Rejected(RelayRejection::QueueFull)
);
assert_eq!(gov.pending_len(), 4);
}
#[test]
fn a_full_queue_still_takes_an_urgent_frame_over_a_routine_one() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
queue_capacity: 2,
jitter_min: Duration::from_millis(500),
jitter_max: Duration::from_millis(500),
peer_rate_per_sec: 10_000.0,
peer_burst: 10_000.0,
..Default::default()
},
);
gov.admit(&frame_with(8, MessagePriority::Low), Some("b"), 3);
gov.admit(&frame_with(8, MessagePriority::Low), Some("b"), 3);
assert_eq!(
gov.admit(&frame_with(8, MessagePriority::Critical), Some("b"), 3,),
RelayAdmission::Queued
);
assert_eq!(gov.pending_len(), 2);
assert!(gov
.take_due(Instant::now() + Duration::from_secs(1))
.iter()
.any(|r| r.message.priority == MessagePriority::Critical));
}
#[test]
fn a_frame_is_never_sent_back_where_it_came_from() {
let gov = governor();
let targets = gov.select_targets(
[("alice", 80), ("b", 80), ("c", 80), ("d", 80)],
&["b", "alice"], "msg-1",
);
assert!(!targets.contains(&"b".to_string()));
assert!(!targets.contains(&"alice".to_string()));
assert!(targets.contains(&"c".to_string()));
assert!(targets.contains(&"d".to_string()));
}
#[test]
fn fanout_is_capped_and_stable_for_a_given_frame() {
let gov = governor();
let neighbors = [
("n0", 70),
("n1", 70),
("n2", 70),
("n3", 70),
("n4", 70),
("n5", 70),
("n6", 70),
("n7", 70),
];
let first = gov.select_targets(neighbors, &[], "msg-1");
let again = gov.select_targets(neighbors, &[], "msg-1");
assert_eq!(first.len(), DEFAULT_RELAY_FANOUT);
assert_eq!(first, again, "a second copy must pick the same neighbors");
let other = gov.select_targets(neighbors, &[], "msg-2");
assert_eq!(other.len(), DEFAULT_RELAY_FANOUT);
}
#[test]
fn a_frame_we_originated_is_not_carried_back_for_someone_else() {
let mut gov = governor();
let msg = frame();
gov.mark_handled(&msg.id.as_str());
assert_eq!(
gov.admit(&msg, Some("b"), 3),
RelayAdmission::Rejected(RelayRejection::AlreadySeen)
);
}
#[test]
fn the_suppression_cache_holds_a_saturated_node_for_its_full_window() {
let gov = governor();
let max_admissions_per_sec = DEFAULT_RELAY_PEER_RATE_PER_SEC * MAX_RELAY_RATE_PEERS as f32;
let sustained_ceiling = DEFAULT_RELAY_RATE_PER_SEC.min(max_admissions_per_sec);
let ids_in_window = sustained_ceiling * gov.config().seen.retention.as_secs_f32();
assert!(
(gov.config().seen.capacity as f32) >= ids_in_window,
"cache holds {} ids but a saturated node sees {} within the retention window",
gov.config().seen.capacity,
ids_in_window
);
}
fn biased_governor() -> MeshRelayGovernor {
MeshRelayGovernor::with_config("relay-node", MeshRelayConfig::default())
}
#[test]
fn a_weak_device_always_waits_longer_than_a_capable_one() {
let msg = frame();
let mut capable = biased_governor();
capable.set_conditions(Some(100), false, false, 30);
let mut weak = biased_governor();
weak.set_conditions(Some(30), false, false, 30);
let capable_delay = capable.jitter_for(&msg.id.as_str(), 3);
let weak_delay = weak.jitter_for(&msg.id.as_str(), 3);
assert!(
weak_delay > capable_delay,
"weak waited {weak_delay:?}, capable {capable_delay:?}"
);
let span = DEFAULT_RELAY_JITTER_MAX_MS - DEFAULT_RELAY_JITTER_MIN_MS;
let reachable_handicap_ms = ((1.0 - DEFAULT_RELAY_BIAS_MIN_SCALE)
* DEFAULT_RELAY_BIAS_MAX_HANDICAP_MS as f32) as u64;
assert!(
reachable_handicap_ms >= span,
"the reachable handicap ({reachable_handicap_ms}ms: a \
{DEFAULT_RELAY_BIAS_MAX_HANDICAP_MS}ms ceiling scaled by 1 - \
{DEFAULT_RELAY_BIAS_MIN_SCALE}) must cover the jitter span ({span}ms) or the two \
windows overlap"
);
}
#[test]
fn bias_adds_a_bounded_delay_rather_than_a_multiplied_one() {
let msg = frame();
let cap = Duration::from_millis(DEFAULT_RELAY_BIAS_MAX_HANDICAP_MS);
for degree in [0usize, 3, 6, 30, 100, MAX_RELAY_RATE_PEERS] {
let mut capable = biased_governor();
capable.set_conditions(Some(100), false, false, 30);
let mut weak = biased_governor();
weak.set_conditions(Some(0), false, false, 30);
let capable_delay = capable.jitter_for(&msg.id.as_str(), degree);
let weak_delay = weak.jitter_for(&msg.id.as_str(), degree);
assert!(weak_delay >= capable_delay, "degree {degree}");
assert!(
weak_delay - capable_delay <= cap,
"at degree {degree} bias added {:?}, past its {cap:?} ceiling",
weak_delay - capable_delay
);
}
}
#[test]
fn a_biased_forward_still_transmits_inside_the_overdue_cutoff() {
let config = MeshRelayConfig::default();
let min_ms = config.jitter_min.as_millis() as u64;
let max_ms = config.jitter_max.as_millis() as u64;
let degree = 100usize;
let density_scale = 1 + (degree / config.dense_degree.max(1)) as u64;
let span = (max_ms - min_ms).saturating_mul(density_scale).max(1);
let worst = Duration::from_millis(
min_ms + config.bias_max_handicap.as_millis() as u64 + (span - 1),
);
assert!(
worst < RELAY_QUEUE_MAX_OVERDUE,
"the worst biased wait at degree {degree} is {worst:?}, past the \
{RELAY_QUEUE_MAX_OVERDUE:?} cut-off"
);
}
#[test]
fn charging_and_eager_devices_are_not_biased_down() {
let msg = frame();
let mut unfed = biased_governor();
let baseline = unfed.jitter_for(&msg.id.as_str(), 3);
let mut charging = biased_governor();
charging.set_conditions(Some(20), true, false, 30);
assert_eq!(charging.jitter_for(&msg.id.as_str(), 3), baseline);
let mut eager = biased_governor();
eager.set_conditions(Some(20), false, true, 30);
assert_eq!(eager.jitter_for(&msg.id.as_str(), 3), baseline);
unfed.set_conditions(None, false, false, 30);
assert_eq!(unfed.jitter_for(&msg.id.as_str(), 3), baseline);
}
#[test]
fn a_weak_device_forwards_to_fewer_neighbors_but_never_none() {
let neighbors: Vec<(&str, u8)> = vec![("a", 90), ("b", 80), ("c", 70), ("d", 60)];
let mut capable = biased_governor();
capable.set_conditions(Some(100), false, false, 30);
let capable_targets = capable.select_targets(neighbors.clone(), &[], "msg-1");
assert_eq!(capable_targets.len(), DEFAULT_RELAY_FANOUT);
let mut weak = biased_governor();
weak.set_conditions(Some(0), false, false, 30);
let weak_targets = weak.select_targets(neighbors, &[], "msg-1");
assert!(
!weak_targets.is_empty(),
"a fan-out of zero is a silent drop, not a cheaper forward"
);
assert!(
weak_targets.len() < capable_targets.len(),
"weak fanned out to {} neighbors, capable to {}",
weak_targets.len(),
capable_targets.len()
);
}
#[test]
fn a_weak_device_offers_its_own_frames_to_every_neighbor_it_would_have() {
let neighbors: Vec<(&str, u8)> = vec![("a", 90), ("b", 80), ("c", 70), ("d", 60)];
let mut weak = biased_governor();
weak.set_conditions(Some(0), false, false, 30);
assert!(
weak.select_targets(neighbors.clone(), &[], "msg-1").len() < DEFAULT_RELAY_FANOUT,
"the forwarding path must still be biased",
);
assert_eq!(
weak.select_origination_targets(neighbors.clone(), &[], "msg-1")
.len(),
DEFAULT_RELAY_FANOUT,
"this device's own frame must reach as many neighbors as a capable device's",
);
let mut capable = biased_governor();
capable.set_conditions(Some(100), false, false, 30);
assert_eq!(
weak.select_origination_targets(neighbors.clone(), &[], "msg-1"),
capable.select_origination_targets(neighbors, &[], "msg-1"),
);
}
#[test]
fn bias_does_not_throttle_this_devices_own_traffic() {
let mut capable = MeshRelayGovernor::with_config("relay-node", immediate_config());
capable.set_conditions(Some(100), false, false, 30);
let capable_own = (0..1000).filter(|_| capable.take_own_send_token()).count();
let mut weak = MeshRelayGovernor::with_config("relay-node", immediate_config());
weak.set_conditions(Some(0), false, false, 30);
let weak_own = (0..1000).filter(|_| weak.take_own_send_token()).count();
assert_eq!(
weak_own, capable_own,
"a weak device got {weak_own} of its own transmissions against a capable \
device's {capable_own}",
);
assert!(capable_own > 0, "the fixture must actually grant something");
}
#[test]
fn a_weak_device_transmits_less_before_hitting_its_ceiling() {
let mut capable = MeshRelayGovernor::with_config("relay-node", immediate_config());
capable.set_conditions(Some(100), false, false, 30);
let capable_grants = (0..1000).filter(|_| capable.take_send_token()).count();
let mut weak = MeshRelayGovernor::with_config("relay-node", immediate_config());
weak.set_conditions(Some(0), false, false, 30);
let weak_grants = (0..1000).filter(|_| weak.take_send_token()).count();
assert!(
weak_grants < capable_grants,
"weak got {weak_grants} transmissions, capable {capable_grants}"
);
assert!(
weak_grants > 0,
"bias scales the budget down, it does not switch forwarding off"
);
}
#[test]
fn a_worsening_battery_does_not_mint_a_fresh_burst() {
let mut gov = MeshRelayGovernor::with_config("relay-node", immediate_config());
gov.set_conditions(Some(100), false, false, 30);
let first = (0..1000).filter(|_| gov.take_send_token()).count();
assert!(first > 0);
assert!(!gov.take_send_token(), "the budget must be spent");
for level in [90, 100, 90, 100] {
gov.set_conditions(Some(level), false, false, 30);
assert!(
!gov.take_send_token(),
"re-sizing the budget handed back tokens that were already spent"
);
}
}
fn activity_config() -> MeshRelayConfig {
MeshRelayConfig {
activity_window: Duration::from_secs(10),
activity_min_forwards: 2,
activity_idle_windows: 2,
..immediate_config()
}
}
#[test]
fn carrying_traffic_is_what_makes_a_device_a_relay() {
let mut gov = MeshRelayGovernor::with_config("relay-node", activity_config());
let start = Instant::now();
assert!(!gov.is_active_relay());
assert_eq!(gov.observe_activity(start + Duration::from_secs(11)), None);
assert!(!gov.is_active_relay());
gov.record_forwarded();
gov.record_forwarded();
assert_eq!(
gov.observe_activity(start + Duration::from_secs(22)),
Some(RelayActivity::Began { forwarded: 2 })
);
assert!(gov.is_active_relay());
gov.record_forwarded();
gov.record_forwarded();
assert_eq!(
gov.observe_activity(start + Duration::from_secs(33)),
None,
"a relay that keeps carrying traffic must not re-announce"
);
assert!(gov.is_active_relay());
}
#[test]
fn a_relay_stops_being_one_only_after_sustained_quiet() {
let mut gov = MeshRelayGovernor::with_config("relay-node", activity_config());
let start = Instant::now();
gov.record_forwarded();
gov.record_forwarded();
assert!(matches!(
gov.observe_activity(start + Duration::from_secs(11)),
Some(RelayActivity::Began { .. })
));
assert_eq!(gov.observe_activity(start + Duration::from_secs(22)), None);
assert!(gov.is_active_relay());
assert_eq!(
gov.observe_activity(start + Duration::from_secs(33)),
Some(RelayActivity::Ceased)
);
assert!(!gov.is_active_relay());
assert_eq!(gov.observe_activity(start + Duration::from_secs(44)), None);
}
#[test]
fn traffic_within_a_window_resets_the_run_of_quiet_ones() {
let mut gov = MeshRelayGovernor::with_config("relay-node", activity_config());
let start = Instant::now();
gov.record_forwarded();
gov.record_forwarded();
assert!(matches!(
gov.observe_activity(start + Duration::from_secs(11)),
Some(RelayActivity::Began { .. })
));
assert_eq!(gov.observe_activity(start + Duration::from_secs(22)), None);
gov.record_forwarded();
gov.record_forwarded();
assert_eq!(gov.observe_activity(start + Duration::from_secs(33)), None);
assert_eq!(gov.observe_activity(start + Duration::from_secs(44)), None);
assert!(gov.is_active_relay(), "the quiet run restarted");
assert_eq!(
gov.observe_activity(start + Duration::from_secs(55)),
Some(RelayActivity::Ceased)
);
}
#[test]
fn observing_before_a_window_has_elapsed_does_nothing() {
let mut gov = MeshRelayGovernor::with_config("relay-node", activity_config());
let start = Instant::now();
gov.record_forwarded();
gov.record_forwarded();
assert_eq!(gov.observe_activity(start + Duration::from_secs(5)), None);
assert!(
!gov.is_active_relay(),
"the window must complete before it counts"
);
assert!(matches!(
gov.observe_activity(start + Duration::from_secs(11)),
Some(RelayActivity::Began { forwarded: 2 })
));
}
#[test]
fn a_closing_gate_drops_the_relay_standing_at_once() {
let mut gov = MeshRelayGovernor::with_config("relay-node", activity_config());
let start = Instant::now();
gov.record_forwarded();
gov.record_forwarded();
assert!(matches!(
gov.observe_activity(start + Duration::from_secs(11)),
Some(RelayActivity::Began { .. })
));
assert!(gov.force_inactive(), "reports that the standing was held");
assert!(!gov.is_active_relay());
assert!(
!gov.force_inactive(),
"a device that was not a relay must not report a demotion"
);
assert_eq!(gov.observe_activity(start + Duration::from_secs(22)), None);
assert!(!gov.is_active_relay());
}
fn deposit_frame() -> Message {
let mut msg = frame();
msg.metadata
.insert(CUSTODY_META_KEY.to_string(), "data".to_string());
msg
}
#[test]
fn a_forwarder_strips_the_deposit_request_and_the_drop_point_keeps_it() {
let mut gov = governor();
let msg = deposit_frame();
assert_eq!(gov.admit(&msg, Some("alice"), 3), RelayAdmission::Queued);
let (due, abandoned) = gov.release_due(Instant::now());
assert!(abandoned.is_empty());
let relay = &due[0];
assert!(
!relay.message.metadata.contains_key(CUSTODY_META_KEY),
"the copy that travels on carries no request"
);
assert_eq!(
relay.custody_request.as_deref(),
Some("data"),
"the request is kept beside the copy, for the drop point alone"
);
assert_eq!(
relay.custody_target, None,
"an ordinary forward is not marked"
);
assert_eq!(
relay.message.content, msg.content,
"only the outer metadata is touched"
);
}
#[test]
fn an_abandoned_forward_is_handed_to_the_drop_point_with_its_id_released() {
let mut gov = governor();
let msg = deposit_frame();
gov.admit(&msg, Some("alice"), 3);
let (due, _) = gov.release_due(Instant::now());
gov.requeue(due.into_iter().next().unwrap());
let later = Instant::now() + RELAY_QUEUE_MAX_OVERDUE + Duration::from_millis(1);
let (due, abandoned) = gov.release_due(later);
assert!(due.is_empty());
assert_eq!(
abandoned.len(),
1,
"the drop point sees the abandoned forward"
);
assert_eq!(abandoned[0].custody_request.as_deref(), Some("data"));
assert!(
!gov.is_suppressed(&msg.id.as_str()),
"released before custody judges it, so a custodian never blanks its own route"
);
assert_eq!(gov.counters().abandoned_overdue, 1);
}
#[test]
fn the_held_intake_skips_suppression_and_hop_accounting() {
let mut gov = governor();
let msg = frame_with(3, MessagePriority::Medium);
assert_eq!(gov.admit(&msg, Some("alice"), 3), RelayAdmission::Queued);
let (due, _) = gov.release_due(Instant::now());
assert_eq!(due.len(), 1);
assert_eq!(
gov.admit(&msg, Some("carol"), 3),
RelayAdmission::Rejected(RelayRejection::AlreadySeen)
);
let held = due.into_iter().next().unwrap().message;
let hop_before = held.hop_count.value();
let ttl_before = held.ttl.value();
gov.admit_held(held, "dave", Instant::now())
.expect("queued");
let (due, _) = gov.release_due(Instant::now());
assert_eq!(due.len(), 1);
let relay = &due[0];
assert_eq!(relay.custody_target.as_deref(), Some("dave"));
assert_eq!(relay.arrival_peer, None);
assert_eq!(relay.message.hop_count.value(), hop_before, "no hop spent");
assert_eq!(relay.message.ttl.value(), ttl_before, "no budget spent");
let seen_before = gov.seen.len();
gov.admit_held(relay.message.clone(), "erin", Instant::now())
.expect("queued");
assert_eq!(gov.seen.len(), seen_before);
}
#[test]
fn a_marked_forward_never_touches_the_suppression_cache() {
let mut gov = governor();
let msg = frame();
gov.admit(&msg, Some("alice"), 3);
let (due, _) = gov.release_due(Instant::now());
assert_eq!(due.len(), 1, "transmitted");
assert!(gov.is_suppressed(&msg.id.as_str()));
gov.admit_held(msg.clone(), "dave", Instant::now())
.expect("queued");
let later = Instant::now() + RELAY_QUEUE_MAX_OVERDUE + Duration::from_millis(1);
let (_, abandoned) = gov.release_due(later);
let held: Vec<_> = abandoned
.iter()
.filter(|r| r.custody_target.is_some())
.collect();
assert_eq!(held.len(), 1);
assert!(
gov.is_suppressed(&msg.id.as_str()),
"abandoning a marked forward releases nothing"
);
let counters_before = gov.counters().clone();
let mut full = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
queue_capacity: 1,
..immediate_config()
},
);
full.admit(&frame(), Some("alice"), 3);
let returned = full.requeue(PendingRelay {
message: frame(),
arrival_peer: None,
heard_from: Vec::new(),
due_at: Instant::now(),
custody_request: None,
custody_target: Some("dave".to_string()),
});
assert!(returned.is_some(), "handed back to the custody store");
assert_eq!(full.counters().queue_full, 0, "not counted as a loss");
assert_eq!(
gov.counters().abandoned_overdue,
counters_before.abandoned_overdue
);
}
#[test]
fn dropping_held_forwards_leaves_the_ordinary_ones_queued() {
let mut gov = governor();
gov.admit(&frame(), Some("alice"), 3);
gov.admit_held(frame(), "dave", Instant::now())
.expect("queued");
gov.admit_held(frame(), "erin", Instant::now())
.expect("queued");
assert_eq!(gov.pending_len(), 3);
gov.drop_held();
assert_eq!(gov.pending_len(), 1);
let (due, _) = gov.release_due(Instant::now());
assert_eq!(due.len(), 1);
assert!(due[0].custody_target.is_none());
}
#[test]
fn the_held_intake_still_takes_the_queue_and_the_neighbor_rate() {
let mut gov = MeshRelayGovernor::with_config(
"relay-node",
MeshRelayConfig {
queue_capacity: 1,
peer_burst: 1.0,
peer_rate_per_sec: 0.0001,
..immediate_config()
},
);
let now = Instant::now();
gov.admit_held(frame(), "dave", now)
.expect("first one queues");
assert_eq!(
gov.admit_held(frame(), "erin", now).err(),
Some(RelayRejection::QueueFull),
"a full queue is a full queue"
);
let (due, _) = gov.release_due(now);
assert_eq!(due.len(), 1);
assert_eq!(
gov.admit_held(frame(), "dave", now).err(),
Some(RelayRejection::PeerRateLimited),
"one neighbor's share is spent"
);
assert!(gov.admit_held(frame(), "frank", now).is_ok());
}
}