use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BandwidthClass {
VeryLow,
Low,
Medium,
High,
}
impl BandwidthClass {
pub fn from_bps(bps: u64) -> Self {
match bps {
0..=999 => Self::VeryLow,
1000..=99_999 => Self::Low,
100_000..=9_999_999 => Self::Medium,
_ => Self::High,
}
}
}
#[derive(Debug, Clone)]
pub struct TransportCapabilities {
pub bandwidth_bps: u64,
pub mtu: usize,
pub typical_rtt: Duration,
pub max_rtt: Duration,
pub half_duplex: bool,
pub broadcast: bool,
pub metered: bool,
pub loss_rate: f32,
pub power_constrained: bool,
pub link_layer_acks: bool,
pub availability: f32,
}
impl TransportCapabilities {
pub fn supports_full_quic(&self) -> bool {
self.bandwidth_bps >= 10_000
&& self.mtu >= 1200
&& self.typical_rtt < Duration::from_secs(2)
}
pub fn bandwidth_class(&self) -> BandwidthClass {
BandwidthClass::from_bps(self.bandwidth_bps)
}
pub fn estimate_transmission_time(&self, bytes: usize) -> Duration {
if self.bandwidth_bps == 0 {
return Duration::MAX;
}
let bits = bytes as u64 * 8;
Duration::from_secs_f64(bits as f64 / self.bandwidth_bps as f64)
}
pub fn effective_bandwidth_bps(&self) -> u64 {
((1.0 - self.loss_rate) * self.bandwidth_bps as f32) as u64
}
pub fn broadband() -> Self {
Self {
bandwidth_bps: 100_000_000, mtu: 1200,
typical_rtt: Duration::from_millis(50),
max_rtt: Duration::from_secs(5),
half_duplex: false,
broadcast: true,
metered: false,
loss_rate: 0.001,
power_constrained: false,
link_layer_acks: false,
availability: 0.99,
}
}
pub fn ble() -> Self {
Self {
bandwidth_bps: 125_000, mtu: 244, typical_rtt: Duration::from_millis(100),
max_rtt: Duration::from_secs(5),
half_duplex: false,
broadcast: true, metered: false,
loss_rate: 0.02,
power_constrained: true,
link_layer_acks: true,
availability: 0.95,
}
}
pub fn lora_long_range() -> Self {
Self {
bandwidth_bps: 293, mtu: 222, typical_rtt: Duration::from_secs(5),
max_rtt: Duration::from_secs(60),
half_duplex: true,
broadcast: true,
metered: false,
loss_rate: 0.1,
power_constrained: true,
link_layer_acks: false,
availability: 0.95,
}
}
pub fn lora_fast() -> Self {
Self {
bandwidth_bps: 21_875, mtu: 222,
typical_rtt: Duration::from_millis(500),
max_rtt: Duration::from_secs(10),
half_duplex: true,
broadcast: true,
metered: false,
loss_rate: 0.05,
power_constrained: true,
link_layer_acks: false,
availability: 0.90,
}
}
pub fn serial_115200() -> Self {
Self {
bandwidth_bps: 115_200,
mtu: 1024,
typical_rtt: Duration::from_millis(50),
max_rtt: Duration::from_secs(5),
half_duplex: true,
broadcast: false, metered: false,
loss_rate: 0.001,
power_constrained: false,
link_layer_acks: false,
availability: 1.0, }
}
pub fn packet_radio_1200() -> Self {
Self {
bandwidth_bps: 1_200,
mtu: 256,
typical_rtt: Duration::from_secs(2),
max_rtt: Duration::from_secs(30),
half_duplex: true,
broadcast: true,
metered: false,
loss_rate: 0.15,
power_constrained: true,
link_layer_acks: true, availability: 0.80,
}
}
pub fn i2p() -> Self {
Self {
bandwidth_bps: 50_000, mtu: 61_440, typical_rtt: Duration::from_secs(2),
max_rtt: Duration::from_secs(30),
half_duplex: false,
broadcast: false,
metered: false,
loss_rate: 0.05,
power_constrained: false,
link_layer_acks: false,
availability: 0.90,
}
}
pub fn yggdrasil() -> Self {
Self {
bandwidth_bps: 10_000_000, mtu: 65535, typical_rtt: Duration::from_millis(200),
max_rtt: Duration::from_secs(10),
half_duplex: false,
broadcast: false,
metered: false,
loss_rate: 0.02,
power_constrained: false,
link_layer_acks: false,
availability: 0.95,
}
}
pub fn custom() -> TransportCapabilitiesBuilder {
TransportCapabilitiesBuilder::default()
}
}
impl Default for TransportCapabilities {
fn default() -> Self {
Self::broadband()
}
}
#[derive(Debug, Default)]
pub struct TransportCapabilitiesBuilder {
bandwidth_bps: Option<u64>,
mtu: Option<usize>,
typical_rtt: Option<Duration>,
max_rtt: Option<Duration>,
half_duplex: Option<bool>,
broadcast: Option<bool>,
metered: Option<bool>,
loss_rate: Option<f32>,
power_constrained: Option<bool>,
link_layer_acks: Option<bool>,
availability: Option<f32>,
}
impl TransportCapabilitiesBuilder {
pub fn bandwidth_bps(mut self, bps: u64) -> Self {
self.bandwidth_bps = Some(bps);
self
}
pub fn mtu(mut self, mtu: usize) -> Self {
self.mtu = Some(mtu);
self
}
pub fn typical_rtt(mut self, rtt: Duration) -> Self {
self.typical_rtt = Some(rtt);
self
}
pub fn max_rtt(mut self, rtt: Duration) -> Self {
self.max_rtt = Some(rtt);
self
}
pub fn half_duplex(mut self, enabled: bool) -> Self {
self.half_duplex = Some(enabled);
self
}
pub fn broadcast(mut self, enabled: bool) -> Self {
self.broadcast = Some(enabled);
self
}
pub fn metered(mut self, enabled: bool) -> Self {
self.metered = Some(enabled);
self
}
pub fn loss_rate(mut self, rate: f32) -> Self {
self.loss_rate = Some(rate.clamp(0.0, 1.0));
self
}
pub fn power_constrained(mut self, enabled: bool) -> Self {
self.power_constrained = Some(enabled);
self
}
pub fn link_layer_acks(mut self, enabled: bool) -> Self {
self.link_layer_acks = Some(enabled);
self
}
pub fn availability(mut self, avail: f32) -> Self {
self.availability = Some(avail.clamp(0.0, 1.0));
self
}
pub fn build(self) -> TransportCapabilities {
let defaults = TransportCapabilities::broadband();
TransportCapabilities {
bandwidth_bps: self.bandwidth_bps.unwrap_or(defaults.bandwidth_bps),
mtu: self.mtu.unwrap_or(defaults.mtu),
typical_rtt: self.typical_rtt.unwrap_or(defaults.typical_rtt),
max_rtt: self.max_rtt.unwrap_or(defaults.max_rtt),
half_duplex: self.half_duplex.unwrap_or(defaults.half_duplex),
broadcast: self.broadcast.unwrap_or(defaults.broadcast),
metered: self.metered.unwrap_or(defaults.metered),
loss_rate: self.loss_rate.unwrap_or(defaults.loss_rate),
power_constrained: self.power_constrained.unwrap_or(defaults.power_constrained),
link_layer_acks: self.link_layer_acks.unwrap_or(defaults.link_layer_acks),
availability: self.availability.unwrap_or(defaults.availability),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_broadband_supports_quic() {
let caps = TransportCapabilities::broadband();
assert!(caps.supports_full_quic());
assert_eq!(caps.bandwidth_class(), BandwidthClass::High);
}
#[test]
fn test_ble_supports_quic() {
let caps = TransportCapabilities::ble();
assert!(!caps.supports_full_quic());
assert_eq!(caps.bandwidth_class(), BandwidthClass::Medium);
}
#[test]
fn test_lora_long_range_no_quic() {
let caps = TransportCapabilities::lora_long_range();
assert!(!caps.supports_full_quic());
assert_eq!(caps.bandwidth_class(), BandwidthClass::VeryLow);
}
#[test]
fn test_lora_fast_no_quic() {
let caps = TransportCapabilities::lora_fast();
assert!(!caps.supports_full_quic());
assert_eq!(caps.bandwidth_class(), BandwidthClass::Low);
}
#[test]
fn test_serial_no_quic() {
let caps = TransportCapabilities::serial_115200();
assert!(!caps.supports_full_quic());
assert_eq!(caps.bandwidth_class(), BandwidthClass::Medium);
}
#[test]
fn test_i2p_bandwidth() {
let caps = TransportCapabilities::i2p();
assert_eq!(caps.bandwidth_class(), BandwidthClass::Low);
}
#[test]
fn test_yggdrasil_supports_quic() {
let caps = TransportCapabilities::yggdrasil();
assert!(caps.supports_full_quic());
assert_eq!(caps.bandwidth_class(), BandwidthClass::High);
}
#[test]
fn test_estimate_transmission_time() {
let caps = TransportCapabilities::lora_long_range();
let time = caps.estimate_transmission_time(222);
assert!(time > Duration::from_secs(5));
assert!(time < Duration::from_secs(7));
}
#[test]
fn test_effective_bandwidth() {
let caps = TransportCapabilities::custom()
.bandwidth_bps(1000)
.loss_rate(0.1)
.build();
assert_eq!(caps.effective_bandwidth_bps(), 900);
}
#[test]
fn test_custom_capabilities() {
let caps = TransportCapabilities::custom()
.bandwidth_bps(9600)
.mtu(512)
.typical_rtt(Duration::from_millis(200))
.half_duplex(true)
.power_constrained(true)
.build();
assert_eq!(caps.bandwidth_bps, 9600);
assert_eq!(caps.mtu, 512);
assert!(caps.half_duplex);
assert!(caps.power_constrained);
assert!(!caps.supports_full_quic()); }
#[test]
fn test_bandwidth_class_boundaries() {
assert_eq!(BandwidthClass::from_bps(0), BandwidthClass::VeryLow);
assert_eq!(BandwidthClass::from_bps(999), BandwidthClass::VeryLow);
assert_eq!(BandwidthClass::from_bps(1000), BandwidthClass::Low);
assert_eq!(BandwidthClass::from_bps(99_999), BandwidthClass::Low);
assert_eq!(BandwidthClass::from_bps(100_000), BandwidthClass::Medium);
assert_eq!(BandwidthClass::from_bps(9_999_999), BandwidthClass::Medium);
assert_eq!(BandwidthClass::from_bps(10_000_000), BandwidthClass::High);
}
#[test]
fn test_loss_rate_clamping() {
let caps = TransportCapabilities::custom()
.loss_rate(1.5) .build();
assert_eq!(caps.loss_rate, 1.0);
let caps = TransportCapabilities::custom()
.loss_rate(-0.5) .build();
assert_eq!(caps.loss_rate, 0.0);
}
#[test]
fn test_availability_clamping() {
let caps = TransportCapabilities::custom()
.availability(2.0) .build();
assert_eq!(caps.availability, 1.0);
let caps = TransportCapabilities::custom()
.availability(-1.0) .build();
assert_eq!(caps.availability, 0.0);
}
}