use crate::dm::DmEnvelope;
use crate::identity::AgentId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;
use std::time::{Duration, Instant};
const RELAY_HEADER_SIGN_DOMAIN: &[u8] = b"x0x-relay-hdr-v1";
pub const DEFAULT_FAIL_THRESHOLD: u32 = 3;
pub const DEFAULT_FAIL_WINDOW: Duration = Duration::from_secs(60);
pub const DEFAULT_RELAY_FRESHNESS: Duration = Duration::from_secs(30);
pub const RELAY_CLOCK_SKEW_TOLERANCE_MS: u64 = 30_000;
pub const DEFAULT_RELAY_LIMIT_WINDOW: Duration = Duration::from_secs(60);
pub const MIN_RELAY_LIMIT_WINDOW: Duration = Duration::from_millis(1);
pub const DEFAULT_MAX_FORWARDS_PER_SENDER: u32 = 10;
pub const DEFAULT_MAX_TOTAL_FORWARDS: u32 = 100;
pub const DEFAULT_MAX_FORWARD_BYTES_PER_WINDOW: u64 = 1024 * 1024;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RelayHeader {
pub version: u16,
pub dst_agent_id: [u8; 32],
pub sender_agent_id: [u8; 32],
pub sender_public_key: Vec<u8>,
pub originated_at_unix_ms: u64,
pub signature: Vec<u8>,
}
impl RelayHeader {
pub const VERSION: u16 = 1;
#[must_use]
pub fn signing_bytes(
version: u16,
dst_agent_id: &[u8; 32],
sender_agent_id: &[u8; 32],
sender_public_key: &[u8],
originated_at_unix_ms: u64,
) -> Vec<u8> {
let mut out = Vec::with_capacity(
RELAY_HEADER_SIGN_DOMAIN.len() + 2 + 32 + 32 + sender_public_key.len() + 8,
);
out.extend_from_slice(RELAY_HEADER_SIGN_DOMAIN);
out.extend_from_slice(&version.to_be_bytes());
out.extend_from_slice(dst_agent_id);
out.extend_from_slice(sender_agent_id);
out.extend_from_slice(sender_public_key);
out.extend_from_slice(&originated_at_unix_ms.to_be_bytes());
out
}
#[must_use]
pub fn own_signing_bytes(&self) -> Vec<u8> {
Self::signing_bytes(
self.version,
&self.dst_agent_id,
&self.sender_agent_id,
&self.sender_public_key,
self.originated_at_unix_ms,
)
}
#[must_use]
pub fn verify(&self) -> bool {
if self.version != Self::VERSION {
return false;
}
let public_key = match ant_quic::MlDsaPublicKey::from_bytes(&self.sender_public_key) {
Ok(pk) => pk,
Err(_) => return false,
};
let derived = AgentId::from_public_key(&public_key);
if derived.0 != self.sender_agent_id {
return false;
}
let signature = match ant_quic::crypto::raw_public_keys::pqc::MlDsaSignature::from_bytes(
&self.signature,
) {
Ok(sig) => sig,
Err(_) => return false,
};
ant_quic::crypto::raw_public_keys::pqc::verify_with_ml_dsa(
&public_key,
&self.own_signing_bytes(),
&signature,
)
.is_ok()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayedDm {
pub header: RelayHeader,
pub inner: DmEnvelope,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelayDisposition {
DeliverLocally,
Forward { dst_agent_id: [u8; 32] },
Refuse(RelayRefusal),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelayRefusal {
BadSignature,
Stale,
PolicyDisabled,
NotAContact,
Blocked,
RateLimited,
BandwidthExceeded,
}
#[derive(Debug, Clone, Copy)]
pub struct RelayPolicy {
pub enabled: bool,
pub fail_threshold: u32,
pub fail_window: Duration,
pub freshness: Duration,
pub require_contact_to_relay: bool,
pub max_forwards_per_sender: u32,
pub max_total_forwards: u32,
pub limit_window: Duration,
pub max_forward_bytes_per_window: u64,
}
impl Default for RelayPolicy {
fn default() -> Self {
Self {
enabled: false,
fail_threshold: DEFAULT_FAIL_THRESHOLD,
fail_window: DEFAULT_FAIL_WINDOW,
freshness: DEFAULT_RELAY_FRESHNESS,
require_contact_to_relay: true,
max_forwards_per_sender: DEFAULT_MAX_FORWARDS_PER_SENDER,
max_total_forwards: DEFAULT_MAX_TOTAL_FORWARDS,
limit_window: DEFAULT_RELAY_LIMIT_WINDOW,
max_forward_bytes_per_window: DEFAULT_MAX_FORWARD_BYTES_PER_WINDOW,
}
}
}
impl RelayPolicy {
#[must_use]
pub fn enabled() -> Self {
Self {
enabled: true,
..Self::default()
}
}
#[must_use]
pub fn with_failure_trigger(mut self, threshold: u32, window: Duration) -> Self {
self.fail_threshold = threshold.max(1);
self.fail_window = window;
self
}
#[must_use]
pub fn with_forward_limits(
mut self,
max_per_sender: u32,
max_total: u32,
max_bytes: u64,
window: Duration,
) -> Self {
self.max_forwards_per_sender = max_per_sender;
self.max_total_forwards = max_total;
self.max_forward_bytes_per_window = max_bytes;
self.limit_window = window.max(MIN_RELAY_LIMIT_WINDOW);
self
}
}
#[derive(Debug, Default)]
struct PeerRelayState {
recent_failures: Vec<Instant>,
in_relay_mode: bool,
}
#[derive(Debug, Default)]
pub struct RelayStats {
relay_sent: AtomicU64,
relay_received: AtomicU64,
relay_forwarded: AtomicU64,
relay_refused_bad_signature: AtomicU64,
relay_refused_stale: AtomicU64,
relay_refused_policy_disabled: AtomicU64,
relay_dropped_revoked: AtomicU64,
direct_recovered_after_relay: AtomicU64,
relay_refused_not_a_contact: AtomicU64,
relay_refused_blocked: AtomicU64,
relay_refused_rate_limited: AtomicU64,
relay_refused_bandwidth_exceeded: AtomicU64,
relay_forward_bytes: AtomicU64,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct RelayStatsSnapshot {
pub relay_sent: u64,
pub relay_received: u64,
pub relay_forwarded: u64,
pub relay_refused_bad_signature: u64,
pub relay_refused_stale: u64,
pub relay_refused_policy_disabled: u64,
pub relay_dropped_revoked: u64,
pub direct_recovered_after_relay: u64,
pub relay_refused_not_a_contact: u64,
pub relay_refused_blocked: u64,
pub relay_refused_rate_limited: u64,
pub relay_refused_bandwidth_exceeded: u64,
pub relay_forward_bytes: u64,
}
impl RelayStats {
#[must_use]
pub fn snapshot(&self) -> RelayStatsSnapshot {
RelayStatsSnapshot {
relay_sent: self.relay_sent.load(Ordering::Relaxed),
relay_received: self.relay_received.load(Ordering::Relaxed),
relay_forwarded: self.relay_forwarded.load(Ordering::Relaxed),
relay_refused_bad_signature: self.relay_refused_bad_signature.load(Ordering::Relaxed),
relay_refused_stale: self.relay_refused_stale.load(Ordering::Relaxed),
relay_refused_policy_disabled: self
.relay_refused_policy_disabled
.load(Ordering::Relaxed),
relay_dropped_revoked: self.relay_dropped_revoked.load(Ordering::Relaxed),
direct_recovered_after_relay: self.direct_recovered_after_relay.load(Ordering::Relaxed),
relay_refused_not_a_contact: self.relay_refused_not_a_contact.load(Ordering::Relaxed),
relay_refused_blocked: self.relay_refused_blocked.load(Ordering::Relaxed),
relay_refused_rate_limited: self.relay_refused_rate_limited.load(Ordering::Relaxed),
relay_refused_bandwidth_exceeded: self
.relay_refused_bandwidth_exceeded
.load(Ordering::Relaxed),
relay_forward_bytes: self.relay_forward_bytes.load(Ordering::Relaxed),
}
}
}
#[derive(Debug)]
struct RelayCharge {
reservation_id: Option<u64>,
sender: [u8; 32],
recorded_at: Instant,
bytes: u64,
}
#[derive(Debug, Default)]
struct RelayLimiter {
charges: Vec<RelayCharge>,
next_reservation_id: u64,
}
impl RelayLimiter {
fn prune(&mut self, now: Instant, window: Duration) {
self.charges.retain(|charge| {
charge.reservation_id.is_some()
|| now.saturating_duration_since(charge.recorded_at) < window
});
}
fn would_exceed_bytes(&self, additional_bytes: u64, limit: u64) -> bool {
let total = self
.charges
.iter()
.try_fold(additional_bytes, |total, charge| {
total.checked_add(charge.bytes)
});
match total {
Some(total) => total > limit,
None => true,
}
}
fn reserve(&mut self, sender: [u8; 32], now: Instant, bytes: u64) -> u64 {
let reservation_id = self.next_reservation_id;
self.next_reservation_id = self.next_reservation_id.wrapping_add(1);
self.charges.push(RelayCharge {
reservation_id: Some(reservation_id),
sender,
recorded_at: now,
bytes,
});
reservation_id
}
fn commit(&mut self, reservation_id: u64, now: Instant) -> Option<u64> {
let charge = self
.charges
.iter_mut()
.find(|charge| charge.reservation_id == Some(reservation_id))?;
charge.reservation_id = None;
charge.recorded_at = now;
Some(charge.bytes)
}
fn cancel(&mut self, reservation_id: u64) {
self.charges
.retain(|charge| charge.reservation_id != Some(reservation_id));
}
}
#[must_use = "dropping the reservation cancels the relay admission"]
pub struct RelayForwardReservation<'a> {
relay: &'a PeerRelay,
reservation_id: Option<u64>,
}
impl RelayForwardReservation<'_> {
pub fn commit(mut self) {
let Some(reservation_id) = self.reservation_id.take() else {
return;
};
let committed_bytes = self
.relay
.limiter_lock()
.commit(reservation_id, Instant::now());
if let Some(bytes) = committed_bytes {
self.relay
.stats
.relay_forwarded
.fetch_add(1, Ordering::Relaxed);
self.relay
.stats
.relay_forward_bytes
.fetch_add(bytes, Ordering::Relaxed);
}
}
}
impl Drop for RelayForwardReservation<'_> {
fn drop(&mut self) {
if let Some(reservation_id) = self.reservation_id.take() {
self.relay.limiter_lock().cancel(reservation_id);
}
}
}
#[derive(Debug)]
pub struct PeerRelay {
policy: RelayPolicy,
stats: RelayStats,
per_peer: Mutex<HashMap<[u8; 32], PeerRelayState>>,
limiter: Mutex<RelayLimiter>,
}
impl Default for PeerRelay {
fn default() -> Self {
Self::new()
}
}
impl PeerRelay {
#[must_use]
pub fn new() -> Self {
Self {
policy: RelayPolicy::default(),
stats: RelayStats::default(),
per_peer: Mutex::new(HashMap::new()),
limiter: Mutex::new(RelayLimiter::default()),
}
}
#[must_use]
pub fn with_policy(policy: RelayPolicy) -> Self {
Self {
policy,
stats: RelayStats::default(),
per_peer: Mutex::new(HashMap::new()),
limiter: Mutex::new(RelayLimiter::default()),
}
}
#[must_use]
pub fn policy(&self) -> &RelayPolicy {
&self.policy
}
#[must_use]
pub fn stats(&self) -> &RelayStats {
&self.stats
}
pub fn record_relay_dropped_revoked(&self) {
self.stats
.relay_dropped_revoked
.fetch_add(1, Ordering::Relaxed);
}
fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<[u8; 32], PeerRelayState>> {
match self.per_peer.lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
}
}
fn limiter_lock(&self) -> std::sync::MutexGuard<'_, RelayLimiter> {
match self.limiter.lock() {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
}
}
pub fn record_direct_failure(&self, peer: &AgentId) {
let now = Instant::now();
let window = self.policy.fail_window;
let mut guard = self.lock();
let entry = guard.entry(peer.0).or_default();
entry
.recent_failures
.retain(|t| now.saturating_duration_since(*t) < window);
entry.recent_failures.push(now);
}
pub fn record_direct_success(&self, peer: &AgentId) {
let mut guard = self.lock();
if let Some(entry) = guard.get_mut(&peer.0) {
entry.recent_failures.clear();
if entry.in_relay_mode {
entry.in_relay_mode = false;
drop(guard);
self.stats
.direct_recovered_after_relay
.fetch_add(1, Ordering::Relaxed);
}
}
}
#[must_use]
pub fn needs_relay(&self, peer: &AgentId) -> bool {
if !self.policy.enabled {
return false;
}
let now = Instant::now();
let window = self.policy.fail_window;
let threshold = self.policy.fail_threshold as usize;
let mut guard = self.lock();
let Some(entry) = guard.get_mut(&peer.0) else {
return false;
};
entry
.recent_failures
.retain(|t| now.saturating_duration_since(*t) < window);
let needs = entry.recent_failures.len() >= threshold;
if needs {
entry.in_relay_mode = true;
}
needs
}
#[must_use]
pub fn select_relay(
&self,
candidates: &[AgentId],
dst: &AgentId,
sender: &AgentId,
) -> Option<AgentId> {
candidates
.iter()
.find(|c| c.0 != dst.0 && c.0 != sender.0)
.copied()
}
pub fn build_relayed_dm<F>(
&self,
dst: &AgentId,
sender: &AgentId,
sender_public_key: Vec<u8>,
originated_at_unix_ms: u64,
inner: DmEnvelope,
sign: F,
) -> Result<RelayedDm, String>
where
F: FnOnce(&[u8]) -> Result<Vec<u8>, String>,
{
let signing_bytes = RelayHeader::signing_bytes(
RelayHeader::VERSION,
&dst.0,
&sender.0,
&sender_public_key,
originated_at_unix_ms,
);
let signature = sign(&signing_bytes)?;
let header = RelayHeader {
version: RelayHeader::VERSION,
dst_agent_id: dst.0,
sender_agent_id: sender.0,
sender_public_key,
originated_at_unix_ms,
signature,
};
self.stats.relay_sent.fetch_add(1, Ordering::Relaxed);
Ok(RelayedDm { header, inner })
}
#[must_use]
pub fn disposition_for(
&self,
relayed: &RelayedDm,
local_agent_id: &AgentId,
now_unix_ms: u64,
is_sender_contact: bool,
is_sender_blocked: bool,
) -> RelayDisposition {
if !self.policy.enabled {
self.stats
.relay_refused_policy_disabled
.fetch_add(1, Ordering::Relaxed);
return RelayDisposition::Refuse(RelayRefusal::PolicyDisabled);
}
if !relayed.header.verify() {
self.stats
.relay_refused_bad_signature
.fetch_add(1, Ordering::Relaxed);
return RelayDisposition::Refuse(RelayRefusal::BadSignature);
}
let freshness_ms = self.policy.freshness.as_millis() as u64;
let originated = relayed.header.originated_at_unix_ms;
let from_future = originated > now_unix_ms.saturating_add(RELAY_CLOCK_SKEW_TOLERANCE_MS);
let too_old = now_unix_ms.saturating_sub(originated) > freshness_ms;
if from_future || too_old {
self.stats
.relay_refused_stale
.fetch_add(1, Ordering::Relaxed);
return RelayDisposition::Refuse(RelayRefusal::Stale);
}
if relayed.header.dst_agent_id == local_agent_id.0 {
self.stats.relay_received.fetch_add(1, Ordering::Relaxed);
return RelayDisposition::DeliverLocally;
}
if is_sender_blocked {
self.stats
.relay_refused_blocked
.fetch_add(1, Ordering::Relaxed);
return RelayDisposition::Refuse(RelayRefusal::Blocked);
}
if self.policy.require_contact_to_relay && !is_sender_contact {
self.stats
.relay_refused_not_a_contact
.fetch_add(1, Ordering::Relaxed);
return RelayDisposition::Refuse(RelayRefusal::NotAContact);
}
RelayDisposition::Forward {
dst_agent_id: relayed.header.dst_agent_id,
}
}
pub fn reserve_forward(
&self,
sender_agent_id: [u8; 32],
bytes: u64,
) -> Result<RelayForwardReservation<'_>, RelayRefusal> {
let now = Instant::now();
let window = self.policy.limit_window.max(MIN_RELAY_LIMIT_WINDOW);
let mut limiter = self.limiter_lock();
limiter.prune(now, window);
let sender_count = limiter
.charges
.iter()
.filter(|charge| charge.sender == sender_agent_id)
.count();
if sender_count >= self.policy.max_forwards_per_sender as usize
|| limiter.charges.len() >= self.policy.max_total_forwards as usize
{
self.stats
.relay_refused_rate_limited
.fetch_add(1, Ordering::Relaxed);
return Err(RelayRefusal::RateLimited);
}
if limiter.would_exceed_bytes(bytes, self.policy.max_forward_bytes_per_window) {
self.stats
.relay_refused_bandwidth_exceeded
.fetch_add(1, Ordering::Relaxed);
return Err(RelayRefusal::BandwidthExceeded);
}
let reservation_id = limiter.reserve(sender_agent_id, now, bytes);
Ok(RelayForwardReservation {
relay: self,
reservation_id: Some(reservation_id),
})
}
#[must_use]
pub fn tracked_peer_count(&self) -> usize {
self.lock().len()
}
pub fn forget_peer(&self, peer: &AgentId) {
self.lock().remove(&peer.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dm::{DmBody, DmPayload};
use crate::identity::AgentKeypair;
fn aid(seed: u8) -> AgentId {
AgentId([seed; 32])
}
fn dummy_inner() -> DmEnvelope {
DmEnvelope {
protocol_version: 1,
request_id: [7u8; 16],
sender_agent_id: [1u8; 32],
sender_machine_id: [2u8; 32],
recipient_agent_id: [3u8; 32],
created_at_unix_ms: 1_000,
expires_at_unix_ms: 60_000,
body: DmBody::Payload(DmPayload {
kem_ciphertext: vec![0u8; 8],
body_nonce: [0u8; 12],
body_ciphertext: vec![0u8; 8],
}),
signature: vec![0u8; 8],
}
}
#[test]
fn relay_disabled_by_default() {
let relay = PeerRelay::new();
assert!(!relay.policy().enabled);
let peer = aid(9);
for _ in 0..10 {
relay.record_direct_failure(&peer);
}
assert!(
!relay.needs_relay(&peer),
"disabled policy must never trigger relay regardless of failures"
);
}
#[test]
fn needs_relay_after_threshold_failures_within_window() {
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
let peer = aid(1);
relay.record_direct_failure(&peer);
relay.record_direct_failure(&peer);
assert!(
!relay.needs_relay(&peer),
"2 failures < default threshold 3 — no relay yet"
);
relay.record_direct_failure(&peer);
assert!(
relay.needs_relay(&peer),
"3 failures == threshold — peer now needs a relay"
);
}
#[test]
fn direct_success_clears_failures_and_counts_recovery() {
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
let peer = aid(2);
for _ in 0..3 {
relay.record_direct_failure(&peer);
}
assert!(relay.needs_relay(&peer), "peer entered relay mode");
relay.record_direct_success(&peer);
assert!(
!relay.needs_relay(&peer),
"direct success clears the failure history"
);
assert_eq!(
relay.stats().snapshot().direct_recovered_after_relay,
1,
"recovery from relay mode is counted once"
);
relay.record_direct_success(&peer);
assert_eq!(
relay.stats().snapshot().direct_recovered_after_relay,
1,
"recovery counter does not double-count"
);
}
#[test]
fn select_relay_skips_dst_and_sender() {
let relay = PeerRelay::new();
let sender = aid(1);
let dst = aid(2);
let r1 = aid(3);
let r2 = aid(4);
let candidates = vec![dst, sender, r1, r2];
assert_eq!(relay.select_relay(&candidates, &dst, &sender), Some(r1));
let only_endpoints = vec![dst, sender];
assert_eq!(
relay.select_relay(&only_endpoints, &dst, &sender),
None,
"no third party available — cannot relay"
);
}
#[test]
fn relay_header_sign_verify_roundtrip() {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let dst = aid(50);
let (pub_bytes, sec_bytes) = kp.to_bytes();
let originated = 1_700_000_000_000u64;
let signing_bytes = RelayHeader::signing_bytes(
RelayHeader::VERSION,
&dst.0,
&sender.0,
&pub_bytes,
originated,
);
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let signature =
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, &signing_bytes)
.expect("sign");
let header = RelayHeader {
version: RelayHeader::VERSION,
dst_agent_id: dst.0,
sender_agent_id: sender.0,
sender_public_key: pub_bytes,
originated_at_unix_ms: originated,
signature: signature.as_bytes().to_vec(),
};
assert!(header.verify(), "a correctly signed header must verify");
}
#[test]
fn relay_header_verify_rejects_tampered_dst() {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let dst = aid(50);
let (pub_bytes, sec_bytes) = kp.to_bytes();
let originated = 1_700_000_000_000u64;
let signing_bytes = RelayHeader::signing_bytes(
RelayHeader::VERSION,
&dst.0,
&sender.0,
&pub_bytes,
originated,
);
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let signature =
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, &signing_bytes)
.expect("sign");
let mut header = RelayHeader {
version: RelayHeader::VERSION,
dst_agent_id: dst.0,
sender_agent_id: sender.0,
sender_public_key: pub_bytes,
originated_at_unix_ms: originated,
signature: signature.as_bytes().to_vec(),
};
header.dst_agent_id = aid(99).0;
assert!(
!header.verify(),
"a tampered dst must break the header signature"
);
}
#[test]
fn relay_header_verify_rejects_forged_origin() {
let kp = AgentKeypair::generate().expect("keypair");
let (pub_bytes, sec_bytes) = kp.to_bytes();
let dst = aid(50);
let forged_sender = aid(123); let originated = 1_700_000_000_000u64;
let signing_bytes = RelayHeader::signing_bytes(
RelayHeader::VERSION,
&dst.0,
&forged_sender.0,
&pub_bytes,
originated,
);
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let signature =
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, &signing_bytes)
.expect("sign");
let header = RelayHeader {
version: RelayHeader::VERSION,
dst_agent_id: dst.0,
sender_agent_id: forged_sender.0,
sender_public_key: pub_bytes,
originated_at_unix_ms: originated,
signature: signature.as_bytes().to_vec(),
};
assert!(
!header.verify(),
"sender_agent_id must derive from sender_public_key"
);
}
#[test]
fn build_relayed_dm_increments_relay_sent_and_produces_verifiable_header() {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let dst = aid(60);
let (pub_bytes, sec_bytes) = kp.to_bytes();
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
let relayed = relay
.build_relayed_dm(
&dst,
&sender,
pub_bytes,
1_700_000_000_000,
dummy_inner(),
|bytes| {
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, bytes)
.map(|s| s.as_bytes().to_vec())
.map_err(|e| format!("{e:?}"))
},
)
.expect("build_relayed_dm");
assert!(
relayed.header.verify(),
"build_relayed_dm must produce a verifiable header"
);
assert_eq!(relay.stats().snapshot().relay_sent, 1);
}
#[test]
fn disposition_delivers_locally_when_we_are_the_dst() {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let (pub_bytes, sec_bytes) = kp.to_bytes();
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let local = aid(70);
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
let now_ms = 1_700_000_000_000u64;
let relayed = relay
.build_relayed_dm(&local, &sender, pub_bytes, now_ms, dummy_inner(), |bytes| {
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, bytes)
.map(|s| s.as_bytes().to_vec())
.map_err(|e| format!("{e:?}"))
})
.expect("build");
assert_eq!(
relay.disposition_for(&relayed, &local, now_ms + 100, false, false),
RelayDisposition::DeliverLocally
);
assert_eq!(relay.stats().snapshot().relay_received, 1);
}
#[test]
fn disposition_forwards_when_we_are_an_intermediate_relay() {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let (pub_bytes, sec_bytes) = kp.to_bytes();
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let dst = aid(80);
let we_are_the_relay = aid(81);
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
let now_ms = 1_700_000_000_000u64;
let relayed = relay
.build_relayed_dm(&dst, &sender, pub_bytes, now_ms, dummy_inner(), |bytes| {
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, bytes)
.map(|s| s.as_bytes().to_vec())
.map_err(|e| format!("{e:?}"))
})
.expect("build");
assert_eq!(
relay.disposition_for(&relayed, &we_are_the_relay, now_ms + 100, true, false),
RelayDisposition::Forward {
dst_agent_id: dst.0
}
);
assert_eq!(
relay.stats().snapshot().relay_forwarded,
0,
"disposition_for classifies only; admission happens in reserve_forward"
);
}
#[test]
fn disposition_refuses_stale_relayed_dm() {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let (pub_bytes, sec_bytes) = kp.to_bytes();
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let local = aid(90);
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
let originated_ms = 1_700_000_000_000u64;
let relayed = relay
.build_relayed_dm(
&local,
&sender,
pub_bytes,
originated_ms,
dummy_inner(),
|bytes| {
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, bytes)
.map(|s| s.as_bytes().to_vec())
.map_err(|e| format!("{e:?}"))
},
)
.expect("build");
let now_ms = originated_ms + 31_000;
assert_eq!(
relay.disposition_for(&relayed, &local, now_ms, false, false),
RelayDisposition::Refuse(RelayRefusal::Stale)
);
assert_eq!(relay.stats().snapshot().relay_refused_stale, 1);
}
#[test]
fn disposition_refuses_far_future_relayed_dm() {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let (pub_bytes, sec_bytes) = kp.to_bytes();
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let local = aid(91);
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
let now_ms = 1_700_000_000_000u64;
let originated_ms = now_ms + RELAY_CLOCK_SKEW_TOLERANCE_MS + 1_000;
let relayed = relay
.build_relayed_dm(
&local,
&sender,
pub_bytes,
originated_ms,
dummy_inner(),
|bytes| {
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, bytes)
.map(|s| s.as_bytes().to_vec())
.map_err(|e| format!("{e:?}"))
},
)
.expect("build");
assert_eq!(
relay.disposition_for(&relayed, &local, now_ms, false, false),
RelayDisposition::Refuse(RelayRefusal::Stale)
);
assert_eq!(relay.stats().snapshot().relay_refused_stale, 1);
let fresh = relay
.build_relayed_dm(
&local,
&sender,
kp.to_bytes().0,
now_ms + 1_000,
dummy_inner(),
|bytes| {
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, bytes)
.map(|s| s.as_bytes().to_vec())
.map_err(|e| format!("{e:?}"))
},
)
.expect("build");
assert_eq!(
relay.disposition_for(&fresh, &local, now_ms, false, false),
RelayDisposition::DeliverLocally
);
}
#[test]
fn disposition_refuses_when_policy_disabled() {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let (pub_bytes, sec_bytes) = kp.to_bytes();
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let local = aid(95);
let builder = PeerRelay::with_policy(RelayPolicy::enabled());
let now_ms = 1_700_000_000_000u64;
let relayed = builder
.build_relayed_dm(&local, &sender, pub_bytes, now_ms, dummy_inner(), |bytes| {
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, bytes)
.map(|s| s.as_bytes().to_vec())
.map_err(|e| format!("{e:?}"))
})
.expect("build");
let disabled = PeerRelay::new();
assert_eq!(
disabled.disposition_for(&relayed, &local, now_ms + 100, false, false),
RelayDisposition::Refuse(RelayRefusal::PolicyDisabled)
);
assert_eq!(disabled.stats().snapshot().relay_refused_policy_disabled, 1);
}
#[test]
fn forget_peer_drops_relay_state() {
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
let peer = aid(1);
relay.record_direct_failure(&peer);
assert_eq!(relay.tracked_peer_count(), 1);
relay.forget_peer(&peer);
assert_eq!(relay.tracked_peer_count(), 0);
}
fn signed_forward_envelope(dst: AgentId, now_ms: u64) -> RelayedDm {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let (pub_bytes, sec_bytes) = kp.to_bytes();
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let builder = PeerRelay::with_policy(RelayPolicy::enabled());
builder
.build_relayed_dm(&dst, &sender, pub_bytes, now_ms, dummy_inner(), |bytes| {
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, bytes)
.map(|s| s.as_bytes().to_vec())
.map_err(|e| format!("{e:?}"))
})
.expect("build")
}
#[test]
fn disposition_refuses_non_contact_when_require_contact_set() {
let dst = aid(40);
let we = aid(41);
let now_ms = 1_700_000_000_000u64;
let relayed = signed_forward_envelope(dst, now_ms);
let relay = PeerRelay::with_policy(RelayPolicy::enabled()); assert_eq!(
relay.disposition_for(&relayed, &we, now_ms + 100, false, false),
RelayDisposition::Refuse(RelayRefusal::NotAContact)
);
assert_eq!(relay.stats().snapshot().relay_refused_not_a_contact, 1);
assert_eq!(relay.stats().snapshot().relay_forwarded, 0);
}
#[test]
fn disposition_forwards_for_contact_when_require_contact_set() {
let dst = aid(42);
let we = aid(43);
let now_ms = 1_700_000_000_000u64;
let relayed = signed_forward_envelope(dst, now_ms);
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
assert_eq!(
relay.disposition_for(&relayed, &we, now_ms + 100, true, false),
RelayDisposition::Forward {
dst_agent_id: dst.0
}
);
assert_eq!(
relay.stats().snapshot().relay_forwarded,
0,
"disposition_for classifies only; admission happens in reserve_forward"
);
const FORWARD_BYTES: u64 = 512;
relay
.reserve_forward(relayed.header.sender_agent_id, FORWARD_BYTES)
.expect("contact-gated forward admits")
.commit();
let snap = relay.stats().snapshot();
assert_eq!(snap.relay_forwarded, 1);
assert_eq!(
snap.relay_forward_bytes, FORWARD_BYTES,
"commit charges the exact reserved byte count, once"
);
}
#[test]
fn open_relay_forwards_for_stranger_when_contact_gate_off() {
let dst = aid(44);
let we = aid(45);
let now_ms = 1_700_000_000_000u64;
let relayed = signed_forward_envelope(dst, now_ms);
let mut policy = RelayPolicy::enabled();
policy.require_contact_to_relay = false;
let relay = PeerRelay::with_policy(policy);
assert_eq!(
relay.disposition_for(&relayed, &we, now_ms + 100, false, false),
RelayDisposition::Forward {
dst_agent_id: dst.0
}
);
}
#[test]
fn deliver_locally_not_gated_by_contact_check() {
let kp = AgentKeypair::generate().expect("keypair");
let sender = kp.agent_id();
let (pub_bytes, sec_bytes) = kp.to_bytes();
let secret = ant_quic::MlDsaSecretKey::from_bytes(&sec_bytes).expect("secret");
let local = aid(46);
let relay = PeerRelay::with_policy(RelayPolicy::enabled());
let now_ms = 1_700_000_000_000u64;
let relayed = relay
.build_relayed_dm(&local, &sender, pub_bytes, now_ms, dummy_inner(), |b| {
ant_quic::crypto::raw_public_keys::pqc::sign_with_ml_dsa(&secret, b)
.map(|s| s.as_bytes().to_vec())
.map_err(|e| format!("{e:?}"))
})
.expect("build");
assert_eq!(
relay.disposition_for(&relayed, &local, now_ms + 100, false, false),
RelayDisposition::DeliverLocally
);
}
#[test]
fn reserve_forward_refuses_when_sender_rate_limit_exceeded() {
let sender = aid(7).0;
let policy = RelayPolicy::enabled().with_forward_limits(
2, 1_000_000, u64::MAX, Duration::from_secs(60),
);
let relay = PeerRelay::with_policy(policy);
let r1 = relay.reserve_forward(sender, 100).expect("first admits");
let r2 = relay.reserve_forward(sender, 100).expect("second admits");
assert_eq!(
relay.reserve_forward(sender, 100).err(),
Some(RelayRefusal::RateLimited)
);
let snap = relay.stats().snapshot();
assert_eq!(snap.relay_refused_rate_limited, 1);
assert_eq!(snap.relay_forwarded, 0, "nothing committed yet");
r1.commit();
r2.commit();
let snap = relay.stats().snapshot();
assert_eq!(snap.relay_forwarded, 2);
assert_eq!(snap.relay_forward_bytes, 200);
}
#[test]
fn reserve_forward_refuses_when_global_rate_limit_exceeded() {
let policy = RelayPolicy::enabled().with_forward_limits(
1_000_000, 1, u64::MAX, Duration::from_secs(60),
);
let relay = PeerRelay::with_policy(policy);
let held = relay
.reserve_forward(aid(1).0, 100)
.expect("first global forward admits");
assert_eq!(
relay.reserve_forward(aid(2).0, 100).err(),
Some(RelayRefusal::RateLimited)
);
assert_eq!(relay.stats().snapshot().relay_refused_rate_limited, 1);
drop(held);
}
#[test]
fn reserve_forward_refuses_when_bandwidth_cap_exceeded() {
let policy = RelayPolicy::enabled().with_forward_limits(
1_000_000, 1_000_000, 1, Duration::from_secs(60),
);
let relay = PeerRelay::with_policy(policy);
assert_eq!(
relay.reserve_forward(aid(1).0, 100).err(),
Some(RelayRefusal::BandwidthExceeded)
);
let snap = relay.stats().snapshot();
assert_eq!(snap.relay_refused_bandwidth_exceeded, 1);
assert_eq!(snap.relay_forwarded, 0);
assert_eq!(
snap.relay_forward_bytes, 0,
"a refused admission commits no bytes"
);
}
#[test]
fn disposition_refuses_blocked_sender_unconditionally() {
let dst = aid(70);
let we = aid(71);
let now_ms = 1_700_000_000_000u64;
let relayed = signed_forward_envelope(dst, now_ms);
let mut policy = RelayPolicy::enabled();
policy.require_contact_to_relay = false; let relay = PeerRelay::with_policy(policy);
assert_eq!(
relay.disposition_for(&relayed, &we, now_ms + 100, false, true),
RelayDisposition::Refuse(RelayRefusal::Blocked)
);
assert_eq!(
relay.disposition_for(&relayed, &we, now_ms + 100, true, true),
RelayDisposition::Refuse(RelayRefusal::Blocked)
);
let snap = relay.stats().snapshot();
assert_eq!(snap.relay_refused_blocked, 2);
assert_eq!(snap.relay_forwarded, 0);
}
#[test]
fn disposition_refuses_blocked_before_rate_limit() {
let dst = aid(72);
let we = aid(73);
let now_ms = 1_700_000_000_000u64;
let relayed = signed_forward_envelope(dst, now_ms);
let mut policy = RelayPolicy::enabled().with_forward_limits(
1,
1_000_000,
u64::MAX,
Duration::from_secs(60),
);
policy.require_contact_to_relay = false;
let relay = PeerRelay::with_policy(policy);
assert_eq!(
relay.disposition_for(&relayed, &we, now_ms + 100, false, true),
RelayDisposition::Refuse(RelayRefusal::Blocked)
);
let snap = relay.stats().snapshot();
assert_eq!(snap.relay_refused_blocked, 1);
assert_eq!(snap.relay_forwarded, 0, "a blocked sender never forwards");
assert_eq!(
snap.relay_refused_rate_limited, 0,
"Blocked must be reported, not RateLimited"
);
}
#[test]
fn unknown_contact_does_not_pass_contact_gate() {
let dst = aid(74);
let we = aid(75);
let now_ms = 1_700_000_000_000u64;
let relayed = signed_forward_envelope(dst, now_ms);
let relay = PeerRelay::with_policy(RelayPolicy::enabled()); assert_eq!(
relay.disposition_for(&relayed, &we, now_ms + 100, false, false),
RelayDisposition::Refuse(RelayRefusal::NotAContact)
);
assert_eq!(
relay.disposition_for(&relayed, &we, now_ms + 100, true, false),
RelayDisposition::Forward {
dst_agent_id: dst.0
}
);
}
#[test]
fn dropped_reservations_free_capacity_and_never_charge_counters() {
let sender = aid(5).0;
let policy = RelayPolicy::enabled().with_forward_limits(
2, 2, 256, Duration::from_secs(60),
);
let relay = PeerRelay::with_policy(policy);
for _ in 0..5 {
let reservation = relay
.reserve_forward(sender, 100)
.expect("dropped reservation admits while capacity is free");
drop(reservation); }
let snap = relay.stats().snapshot();
assert_eq!(snap.relay_forwarded, 0);
assert_eq!(snap.relay_forward_bytes, 0);
assert_eq!(snap.relay_refused_rate_limited, 0);
assert_eq!(snap.relay_refused_bandwidth_exceeded, 0);
let reusable = relay
.reserve_forward(sender, 100)
.expect("capacity is reusable after dropped reservations");
drop(reusable);
}
#[test]
fn failed_forward_then_retry_commits_exactly_once() {
let sender = aid(9).0;
let policy = RelayPolicy::enabled().with_forward_limits(
1, 1_000_000,
u64::MAX,
Duration::from_secs(60),
);
let relay = PeerRelay::with_policy(policy);
let failed = relay
.reserve_forward(sender, 64)
.expect("first attempt reserves");
drop(failed);
relay
.reserve_forward(sender, 64)
.expect("retry reserves after the failed attempt freed capacity")
.commit();
let snap = relay.stats().snapshot();
assert_eq!(snap.relay_forwarded, 1, "only the retry committed");
assert_eq!(snap.relay_forward_bytes, 64);
}
#[test]
fn dropping_one_reservation_frees_just_that_slot() {
let sender = aid(3).0;
let policy = RelayPolicy::enabled().with_forward_limits(
2, 1_000_000,
u64::MAX,
Duration::from_secs(60),
);
let relay = PeerRelay::with_policy(policy);
let r1 = relay.reserve_forward(sender, 10).expect("first slot");
let _r2 = relay.reserve_forward(sender, 10).expect("second slot");
assert_eq!(
relay.reserve_forward(sender, 10).err(),
Some(RelayRefusal::RateLimited)
);
drop(r1);
let _r3 = relay
.reserve_forward(sender, 10)
.expect("freed slot re-admits");
assert_eq!(
relay.reserve_forward(sender, 10).err(),
Some(RelayRefusal::RateLimited)
);
}
#[test]
fn commit_charges_counters_once_and_consumes_capacity() {
let policy = RelayPolicy::enabled().with_forward_limits(
1_000_000, 2, u64::MAX,
Duration::from_secs(60),
);
let relay = PeerRelay::with_policy(policy);
relay.reserve_forward(aid(1).0, 111).unwrap().commit();
relay.reserve_forward(aid(2).0, 222).unwrap().commit();
assert_eq!(
relay.reserve_forward(aid(3).0, 100).err(),
Some(RelayRefusal::RateLimited),
"committed charges still consume the global cap"
);
let snap = relay.stats().snapshot();
assert_eq!(snap.relay_forwarded, 2);
assert_eq!(snap.relay_forward_bytes, 333);
}
#[test]
fn concurrent_admissions_never_oversubscribe_global_cap() {
use std::sync::atomic::AtomicUsize;
use std::sync::{Arc, Barrier};
use std::thread;
const CAP: usize = 4;
const THREADS: usize = 16;
let relay = Arc::new(PeerRelay::with_policy(
RelayPolicy::enabled().with_forward_limits(
1_000_000, CAP as u32,
u64::MAX,
Duration::from_secs(60),
),
));
let start = Arc::new(Barrier::new(THREADS));
let hold = Arc::new(Barrier::new(THREADS + 1));
let admitted = Arc::new(AtomicUsize::new(0));
let refused = Arc::new(AtomicUsize::new(0));
let mut handles = Vec::new();
for i in 0..THREADS {
let relay = Arc::clone(&relay);
let start = Arc::clone(&start);
let hold = Arc::clone(&hold);
let admitted = Arc::clone(&admitted);
let refused = Arc::clone(&refused);
handles.push(thread::spawn(move || {
let sender = [i as u8; 32];
start.wait();
match relay.reserve_forward(sender, 64) {
Ok(r) => {
admitted.fetch_add(1, Ordering::SeqCst);
hold.wait(); drop(r);
}
Err(RelayRefusal::RateLimited) => {
refused.fetch_add(1, Ordering::SeqCst);
hold.wait();
}
Err(other) => panic!("unexpected refusal {other:?}"),
}
}));
}
hold.wait();
for h in handles {
h.join().unwrap();
}
assert_eq!(
admitted.load(Ordering::SeqCst),
CAP,
"exactly the global cap admits, never more"
);
assert_eq!(
refused.load(Ordering::SeqCst),
THREADS - CAP,
"the rest are refused with RateLimited"
);
assert_eq!(
relay.stats().snapshot().relay_forwarded,
0,
"none committed"
);
}
#[test]
fn relay_policy_with_forward_limits_clamps_zero_window() {
let zero = RelayPolicy::enabled().with_forward_limits(10, 100, 1_024, Duration::ZERO);
assert_eq!(
zero.limit_window, MIN_RELAY_LIMIT_WINDOW,
"Duration::ZERO must clamp to the 1 ms floor"
);
let one_ms =
RelayPolicy::enabled().with_forward_limits(10, 100, 1_024, Duration::from_millis(1));
assert_eq!(
one_ms.limit_window, MIN_RELAY_LIMIT_WINDOW,
"1 ms is the floor and survives unchanged"
);
let generous =
RelayPolicy::enabled().with_forward_limits(10, 100, 1_024, Duration::from_secs(60));
assert_eq!(generous.limit_window, Duration::from_secs(60));
}
}