pub mod fallback;
pub mod health;
pub use fallback::FallbackController;
pub use health::HealthMonitor;
use std::time::{Duration, Instant};
use crate::transport::TransportKind;
const DEFAULT_BLOCK_THRESHOLD: u32 = 5;
pub struct Channel {
pub transport: TransportKind,
is_shadow: bool,
monitor: HealthMonitor,
opened_at: Instant,
bytes_sent: u64,
bytes_recv: u64,
}
impl Channel {
pub fn new(transport: TransportKind) -> Self {
Self {
transport,
is_shadow: false,
monitor: HealthMonitor::new(DEFAULT_BLOCK_THRESHOLD),
opened_at: Instant::now(),
bytes_sent: 0,
bytes_recv: 0,
}
}
pub fn shadow(transport: TransportKind) -> Self {
Self {
is_shadow: true,
..Self::new(transport)
}
}
pub fn with_block_threshold(transport: TransportKind, threshold: u32) -> Self {
Self {
monitor: HealthMonitor::new(threshold),
..Self::new(transport)
}
}
pub fn record_success(&mut self, rtt: Duration) {
self.monitor.record_success(rtt);
}
pub fn record_failure(&mut self) {
self.monitor.record_failure();
}
pub fn is_blocked(&self) -> bool {
self.monitor.is_blocked()
}
pub fn is_healthy(&self) -> bool {
!self.monitor.is_blocked()
}
pub fn score(&self) -> f64 {
self.monitor.score()
}
pub fn srtt(&self) -> Duration {
self.monitor.srtt
}
pub fn consecutive_failures(&self) -> u32 {
self.monitor.consecutive_failures
}
pub fn monitor(&self) -> &HealthMonitor {
&self.monitor
}
pub fn monitor_mut(&mut self) -> &mut HealthMonitor {
&mut self.monitor
}
pub fn is_shadow(&self) -> bool {
self.is_shadow
}
pub fn promote(&mut self) {
self.is_shadow = false;
}
pub fn demote(&mut self) {
self.is_shadow = true;
}
pub fn add_bytes_sent(&mut self, n: u64) {
self.bytes_sent += n;
}
pub fn add_bytes_recv(&mut self, n: u64) {
self.bytes_recv += n;
}
pub fn bytes_sent(&self) -> u64 {
self.bytes_sent
}
pub fn bytes_recv(&self) -> u64 {
self.bytes_recv
}
pub fn uptime(&self) -> Duration {
self.opened_at.elapsed()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_channel_is_healthy() {
let ch = Channel::new(TransportKind::Tcp);
assert!(ch.is_healthy());
assert!(!ch.is_blocked());
assert!(!ch.is_shadow());
assert!(ch.score() > 0.0);
}
#[test]
fn shadow_channel_flag() {
let ch = Channel::shadow(TransportKind::Udp);
assert!(ch.is_shadow());
assert!(ch.is_healthy());
}
#[test]
fn record_success_updates_srtt() {
let mut ch = Channel::new(TransportKind::Tcp);
let before = ch.srtt();
ch.record_success(Duration::from_millis(200));
assert!(ch.srtt() > before);
}
#[test]
fn blocked_after_threshold() {
let mut ch = Channel::with_block_threshold(TransportKind::Quic, 3);
ch.record_failure();
ch.record_failure();
assert!(ch.is_healthy());
ch.record_failure();
assert!(ch.is_blocked());
assert_eq!(ch.score(), 0.0);
}
#[test]
fn success_resets_failures() {
let mut ch = Channel::with_block_threshold(TransportKind::Tcp, 2);
ch.record_failure();
ch.record_success(Duration::from_millis(10));
ch.record_failure();
assert!(ch.is_healthy());
}
#[test]
fn promote_demote_lifecycle() {
let mut ch = Channel::shadow(TransportKind::WebSocket);
assert!(ch.is_shadow());
ch.promote();
assert!(!ch.is_shadow());
ch.demote();
assert!(ch.is_shadow());
}
#[test]
fn bytes_tracking() {
let mut ch = Channel::new(TransportKind::Tcp);
assert_eq!(ch.bytes_sent(), 0);
assert_eq!(ch.bytes_recv(), 0);
ch.add_bytes_sent(1024);
ch.add_bytes_recv(512);
ch.add_bytes_sent(256);
assert_eq!(ch.bytes_sent(), 1280);
assert_eq!(ch.bytes_recv(), 512);
}
#[test]
fn uptime_increases() {
let ch = Channel::new(TransportKind::Tcp);
assert!(ch.uptime() < Duration::from_secs(1));
}
}