use crate::core::{random_u16, random_u32};
use crate::rtp::packet::{sequence_diff, RtpPacket};
use std::collections::VecDeque;
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct RtpSession {
ssrc: u32,
payload_type: u8,
clock_rate: u32,
next_seq: u16,
current_timestamp: u32,
packets_sent: u64,
octets_sent: u64,
start_time: Instant,
receiver_state: ReceiverState,
}
#[derive(Debug, Default)]
pub struct ReceiverState {
pub max_seq: u16,
pub base_seq: u16,
pub initialized: bool,
pub packets_received: u64,
pub packets_lost: u64,
pub expected_packets: u64,
pub last_timestamp: u32,
pub jitter: u32,
pub last_arrival: Option<Instant>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CongestionState {
SlowStart,
CongestionAvoidance,
Recovery,
}
#[derive(Debug)]
pub struct CongestionController {
state: CongestionState,
target_bitrate: u64,
min_bitrate: u64,
max_bitrate: u64,
ssthresh: u64,
last_increase: Instant,
last_decrease: Instant,
rtt_samples: VecDeque<Duration>,
smoothed_rtt: Duration,
rtt_var: Duration,
nacks_in_interval: u32,
packets_in_interval: u32,
last_remb: Option<u64>,
}
impl Default for CongestionController {
fn default() -> Self {
Self::new(500_000, 50_000, 5_000_000) }
}
impl CongestionController {
pub fn new(initial_bitrate: u64, min_bitrate: u64, max_bitrate: u64) -> Self {
Self {
state: CongestionState::SlowStart,
target_bitrate: initial_bitrate,
min_bitrate,
max_bitrate,
ssthresh: max_bitrate / 2,
last_increase: Instant::now(),
last_decrease: Instant::now(),
rtt_samples: VecDeque::with_capacity(10),
smoothed_rtt: Duration::from_millis(100),
rtt_var: Duration::from_millis(50),
nacks_in_interval: 0,
packets_in_interval: 0,
last_remb: None,
}
}
pub fn target_bitrate(&self) -> u64 {
self.target_bitrate
}
pub fn state(&self) -> CongestionState {
self.state
}
pub fn smoothed_rtt(&self) -> Duration {
self.smoothed_rtt
}
pub fn on_packet_sent(&mut self) {
self.packets_in_interval += 1;
}
pub fn on_nack(&mut self, lost_packets: u32) {
self.nacks_in_interval += lost_packets;
if self.last_decrease.elapsed() < self.smoothed_rtt {
return;
}
self.target_bitrate = (self.target_bitrate / 2).max(self.min_bitrate);
self.ssthresh = self.target_bitrate;
self.state = CongestionState::Recovery;
self.last_decrease = Instant::now();
tracing::debug!(
"NACK: Reduced bitrate to {} bps (lost {} packets)",
self.target_bitrate,
lost_packets
);
}
pub fn on_remb(&mut self, bitrate: u64) {
self.last_remb = Some(bitrate);
if bitrate < self.target_bitrate * 90 / 100 {
self.target_bitrate = bitrate.max(self.min_bitrate);
self.ssthresh = self.target_bitrate;
self.state = CongestionState::CongestionAvoidance;
self.last_decrease = Instant::now();
tracing::debug!("REMB: Reduced bitrate to {} bps", self.target_bitrate);
}
if bitrate < self.max_bitrate {
self.max_bitrate = bitrate;
}
}
pub fn on_rtt(&mut self, rtt: Duration) {
if self.rtt_samples.len() >= 10 {
self.rtt_samples.pop_front();
}
self.rtt_samples.push_back(rtt);
if self.rtt_samples.len() == 1 {
self.smoothed_rtt = rtt;
self.rtt_var = rtt / 2;
} else {
let diff = self.smoothed_rtt.abs_diff(rtt);
self.rtt_var = self.rtt_var * 3 / 4 + diff / 4;
self.smoothed_rtt = self.smoothed_rtt * 7 / 8 + rtt / 8;
}
}
pub fn update(&mut self) {
let now = Instant::now();
let loss_ratio = if self.packets_in_interval > 0 {
self.nacks_in_interval as f64 / self.packets_in_interval as f64
} else {
0.0
};
self.nacks_in_interval = 0;
self.packets_in_interval = 0;
if loss_ratio > 0.02 {
return;
}
let increase_interval = Duration::from_millis(100);
if now.duration_since(self.last_increase) < increase_interval {
return;
}
match self.state {
CongestionState::SlowStart => {
let increase = self.target_bitrate / 10; self.target_bitrate = (self.target_bitrate + increase).min(self.max_bitrate);
if self.target_bitrate >= self.ssthresh {
self.state = CongestionState::CongestionAvoidance;
tracing::debug!(
"Entering congestion avoidance at {} bps",
self.target_bitrate
);
}
}
CongestionState::CongestionAvoidance | CongestionState::Recovery => {
let packet_size_bits = 1200 * 8; let packets_per_second = self.target_bitrate / packet_size_bits as u64;
let increase = (packets_per_second / 10).max(10_000);
self.target_bitrate = (self.target_bitrate + increase).min(self.max_bitrate);
if let Some(remb) = self.last_remb {
self.target_bitrate = self.target_bitrate.min(remb);
}
}
}
self.last_increase = now;
}
pub fn pacing_interval(&self, packet_size_bytes: u32) -> Duration {
let packet_size_bits = packet_size_bytes as u64 * 8;
if self.target_bitrate == 0 {
return Duration::from_millis(20);
}
let interval_secs = packet_size_bits as f64 / self.target_bitrate as f64;
Duration::from_secs_f64(interval_secs)
}
pub fn reset(&mut self, initial_bitrate: u64) {
self.state = CongestionState::SlowStart;
self.target_bitrate = initial_bitrate;
self.ssthresh = self.max_bitrate / 2;
self.nacks_in_interval = 0;
self.packets_in_interval = 0;
self.last_remb = None;
}
}
impl RtpSession {
pub fn new(ssrc: u32, payload_type: u8, clock_rate: u32) -> Self {
let initial_seq = random_u16();
let initial_ts = random_u32();
Self {
ssrc,
payload_type,
clock_rate,
next_seq: initial_seq,
current_timestamp: initial_ts,
packets_sent: 0,
octets_sent: 0,
start_time: Instant::now(),
receiver_state: ReceiverState::default(),
}
}
pub fn ssrc(&self) -> u32 {
self.ssrc
}
pub fn payload_type(&self) -> u8 {
self.payload_type
}
pub fn clock_rate(&self) -> u32 {
self.clock_rate
}
pub fn packets_sent(&self) -> u64 {
self.packets_sent
}
pub fn octets_sent(&self) -> u64 {
self.octets_sent
}
pub fn create_packet(&mut self, payload: Vec<u8>, samples: u32, marker: bool) -> RtpPacket {
let seq = self.next_seq;
let timestamp = self.current_timestamp;
self.next_seq = self.next_seq.wrapping_add(1);
self.current_timestamp = self.current_timestamp.wrapping_add(samples);
self.packets_sent += 1;
self.octets_sent += payload.len() as u64;
RtpPacket::new(self.payload_type, seq, timestamp, self.ssrc)
.with_marker(marker)
.with_payload(payload)
}
pub fn create_packet_at(
&mut self,
payload: Vec<u8>,
timestamp: u32,
marker: bool,
) -> RtpPacket {
let seq = self.next_seq;
self.next_seq = self.next_seq.wrapping_add(1);
self.current_timestamp = timestamp;
self.packets_sent += 1;
self.octets_sent += payload.len() as u64;
RtpPacket::new(self.payload_type, seq, timestamp, self.ssrc)
.with_marker(marker)
.with_payload(payload)
}
pub fn update_timestamp(&mut self) {
let elapsed = self.start_time.elapsed();
let samples = (elapsed.as_secs_f64() * self.clock_rate as f64) as u32;
self.current_timestamp = self.current_timestamp.wrapping_add(samples);
self.start_time = Instant::now();
}
pub fn receive_packet(&mut self, packet: &RtpPacket) {
let now = Instant::now();
if !self.receiver_state.initialized {
self.receiver_state.base_seq = packet.sequence_number;
self.receiver_state.max_seq = packet.sequence_number;
self.receiver_state.initialized = true;
self.receiver_state.last_timestamp = packet.timestamp;
self.receiver_state.last_arrival = Some(now);
self.receiver_state.packets_received = 1;
self.receiver_state.expected_packets = 1;
return;
}
self.receiver_state.packets_received += 1;
let seq_diff = sequence_diff(packet.sequence_number, self.receiver_state.max_seq);
if seq_diff > 0 {
let expected_increase = seq_diff as u64;
self.receiver_state.expected_packets += expected_increase;
if expected_increase > 1 {
self.receiver_state.packets_lost += expected_increase - 1;
}
self.receiver_state.max_seq = packet.sequence_number;
}
if let Some(last_arrival) = self.receiver_state.last_arrival {
let arrival_diff = now.duration_since(last_arrival);
let arrival_samples = (arrival_diff.as_secs_f64() * self.clock_rate as f64) as i32;
let ts_diff = packet
.timestamp
.wrapping_sub(self.receiver_state.last_timestamp) as i32;
let d = (arrival_samples - ts_diff).unsigned_abs();
let adjustment = (d as i32 - self.receiver_state.jitter as i32) >> 4;
if adjustment >= 0 {
self.receiver_state.jitter =
self.receiver_state.jitter.saturating_add(adjustment as u32);
} else {
self.receiver_state.jitter = self
.receiver_state
.jitter
.saturating_sub((-adjustment) as u32);
}
}
self.receiver_state.last_timestamp = packet.timestamp;
self.receiver_state.last_arrival = Some(now);
}
pub fn receiver_stats(&self) -> &ReceiverState {
&self.receiver_state
}
pub fn packet_loss_percent(&self) -> f64 {
let state = &self.receiver_state;
if state.expected_packets == 0 {
return 0.0;
}
(state.packets_lost as f64 / state.expected_packets as f64) * 100.0
}
pub fn jitter_ms(&self) -> f64 {
let jitter_samples = self.receiver_state.jitter as f64;
(jitter_samples / self.clock_rate as f64) * 1000.0
}
pub fn timestamp_for_duration(&self, duration: Duration) -> u32 {
let samples = (duration.as_secs_f64() * self.clock_rate as f64) as u32;
self.current_timestamp.wrapping_add(samples)
}
pub fn create_sender_report(&self) -> crate::rtp::rtcp::SenderReport {
use crate::rtp::rtcp::{NtpTimestamp, ReportBlock, SenderReport};
let mut report_blocks = Vec::new();
if self.receiver_state.initialized {
let state = &self.receiver_state;
let fraction_lost = if state.expected_packets > 0 {
let loss_fraction = state.packets_lost as f64 / state.expected_packets as f64;
(loss_fraction * 256.0).min(255.0) as u8
} else {
0
};
let extended_seq = state.max_seq as u32;
report_blocks.push(ReportBlock {
ssrc: 0, fraction_lost,
cumulative_lost: state.packets_lost.min(0x7FFFFF) as i32,
extended_seq,
jitter: state.jitter,
last_sr: 0, delay_since_sr: 0,
});
}
SenderReport {
ssrc: self.ssrc,
ntp_timestamp: NtpTimestamp::now(),
rtp_timestamp: self.current_timestamp,
sender_packet_count: self.packets_sent as u32,
sender_octet_count: self.octets_sent as u32,
report_blocks,
}
}
pub fn create_receiver_report(&self, remote_ssrc: u32) -> crate::rtp::rtcp::ReceiverReport {
use crate::rtp::rtcp::{ReceiverReport, ReportBlock};
let mut report_blocks = Vec::new();
if self.receiver_state.initialized {
let state = &self.receiver_state;
let fraction_lost = if state.expected_packets > 0 {
let loss_fraction = state.packets_lost as f64 / state.expected_packets as f64;
(loss_fraction * 256.0).min(255.0) as u8
} else {
0
};
let extended_seq = state.max_seq as u32;
report_blocks.push(ReportBlock {
ssrc: remote_ssrc,
fraction_lost,
cumulative_lost: state.packets_lost.min(0x7FFFFF) as i32,
extended_seq,
jitter: state.jitter,
last_sr: 0,
delay_since_sr: 0,
});
}
ReceiverReport {
ssrc: self.ssrc,
report_blocks,
}
}
pub fn create_rtcp_compound(
&self,
cname: &str,
is_sender: bool,
) -> crate::rtp::rtcp::RtcpCompound {
use crate::rtp::rtcp::RtcpCompound;
if is_sender {
let sr = self.create_sender_report();
RtcpCompound::sender_compound(sr, cname)
} else {
let rr = self.create_receiver_report(0);
RtcpCompound::receiver_compound(rr, cname)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use std::sync::Once;
fn init_tracing() {
static INIT: Once = Once::new();
INIT.call_once(|| {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_test_writer()
.try_init();
});
}
#[test]
fn test_receiver_state_default() {
let state = ReceiverState::default();
assert_eq!(state.max_seq, 0);
assert_eq!(state.base_seq, 0);
assert!(!state.initialized);
assert_eq!(state.packets_received, 0);
assert_eq!(state.packets_lost, 0);
assert_eq!(state.expected_packets, 0);
assert_eq!(state.last_timestamp, 0);
assert_eq!(state.jitter, 0);
assert!(state.last_arrival.is_none());
}
#[test]
fn test_receiver_state_debug() {
let state = ReceiverState::default();
let debug = format!("{:?}", state);
assert!(debug.contains("ReceiverState"));
}
#[test]
fn test_congestion_state_debug() {
assert!(format!("{:?}", CongestionState::SlowStart).contains("SlowStart"));
assert!(
format!("{:?}", CongestionState::CongestionAvoidance).contains("CongestionAvoidance")
);
assert!(format!("{:?}", CongestionState::Recovery).contains("Recovery"));
}
#[test]
fn test_congestion_state_eq() {
assert_eq!(CongestionState::SlowStart, CongestionState::SlowStart);
assert_ne!(CongestionState::SlowStart, CongestionState::Recovery);
}
#[test]
fn test_congestion_state_clone() {
let state = CongestionState::CongestionAvoidance;
let cloned = state;
assert_eq!(state, cloned);
}
#[test]
fn test_create_session() {
let session = RtpSession::new(12345, 0, 8000);
assert_eq!(session.ssrc(), 12345);
assert_eq!(session.payload_type(), 0);
assert_eq!(session.clock_rate(), 8000);
}
#[test]
fn test_session_debug() {
let session = RtpSession::new(12345, 0, 8000);
let debug = format!("{:?}", session);
assert!(debug.contains("RtpSession"));
}
#[test]
fn test_create_packet() {
let mut session = RtpSession::new(12345, 0, 8000);
let payload = vec![0x01, 0x02, 0x03, 0x04];
let pkt1 = session.create_packet(payload.clone(), 160, true);
assert!(pkt1.marker);
assert_eq!(pkt1.payload_type, 0);
assert_eq!(pkt1.ssrc, 12345);
let pkt2 = session.create_packet(payload, 160, false);
assert!(!pkt2.marker);
assert_eq!(pkt2.sequence_number, pkt1.sequence_number.wrapping_add(1));
assert_eq!(pkt2.timestamp, pkt1.timestamp.wrapping_add(160));
}
#[test]
fn test_create_packet_at() {
let mut session = RtpSession::new(12345, 0, 8000);
let payload = vec![0x01, 0x02, 0x03];
let specific_ts = 1000000;
let pkt = session.create_packet_at(payload.clone(), specific_ts, true);
assert_eq!(pkt.timestamp, specific_ts);
assert!(pkt.marker);
assert_eq!(session.packets_sent(), 1);
assert_eq!(session.octets_sent(), 3);
}
#[test]
fn test_packets_sent() {
let mut session = RtpSession::new(12345, 0, 8000);
session.create_packet(vec![0; 160], 160, false);
session.create_packet(vec![0; 160], 160, false);
session.create_packet(vec![0; 160], 160, false);
assert_eq!(session.packets_sent(), 3);
assert_eq!(session.octets_sent(), 480);
}
#[test]
fn test_update_timestamp() {
let mut session = RtpSession::new(12345, 0, 8000);
let _initial_ts = session.current_timestamp;
std::thread::sleep(Duration::from_millis(10));
session.update_timestamp();
}
#[test]
fn test_receive_packet() {
let mut session = RtpSession::new(12345, 0, 8000);
let pkt1 = RtpPacket::new(0, 100, 0, 99999).with_payload(Bytes::from_static(&[0; 160]));
session.receive_packet(&pkt1);
assert_eq!(session.receiver_stats().packets_received, 1);
assert!(session.receiver_stats().initialized);
let pkt2 = RtpPacket::new(0, 101, 160, 99999).with_payload(Bytes::from_static(&[0; 160]));
session.receive_packet(&pkt2);
assert_eq!(session.receiver_stats().packets_received, 2);
assert_eq!(session.receiver_stats().max_seq, 101);
}
#[test]
fn test_receive_packet_out_of_order() {
let mut session = RtpSession::new(12345, 0, 8000);
let pkt1 = RtpPacket::new(0, 100, 0, 99999);
session.receive_packet(&pkt1);
let pkt3 = RtpPacket::new(0, 102, 320, 99999);
session.receive_packet(&pkt3);
let pkt2 = RtpPacket::new(0, 101, 160, 99999);
session.receive_packet(&pkt2);
assert_eq!(session.receiver_stats().max_seq, 102);
assert_eq!(session.receiver_stats().packets_received, 3);
}
#[test]
fn test_packet_loss_detection() {
let mut session = RtpSession::new(12345, 0, 8000);
let pkt1 = RtpPacket::new(0, 100, 0, 99999);
session.receive_packet(&pkt1);
let pkt3 = RtpPacket::new(0, 102, 320, 99999);
session.receive_packet(&pkt3);
assert_eq!(session.receiver_stats().packets_lost, 1);
assert_eq!(session.receiver_stats().expected_packets, 3);
}
#[test]
fn test_packet_loss_percent() {
let mut session = RtpSession::new(12345, 0, 8000);
assert_eq!(session.packet_loss_percent(), 0.0);
let pkt1 = RtpPacket::new(0, 100, 0, 99999);
session.receive_packet(&pkt1);
let pkt2 = RtpPacket::new(0, 105, 800, 99999);
session.receive_packet(&pkt2);
let loss = session.packet_loss_percent();
assert!(loss > 60.0);
assert!(loss < 70.0);
}
#[test]
fn test_jitter_ms() {
let session = RtpSession::new(12345, 0, 8000);
assert_eq!(session.jitter_ms(), 0.0);
}
#[test]
fn test_jitter_calculation() {
let mut session = RtpSession::new(12345, 0, 8000);
let pkt1 = RtpPacket::new(0, 100, 0, 99999);
session.receive_packet(&pkt1);
std::thread::sleep(Duration::from_millis(25));
let pkt2 = RtpPacket::new(0, 101, 160, 99999);
session.receive_packet(&pkt2);
}
#[test]
fn test_receive_packet_without_last_arrival() {
let mut session = RtpSession::new(12345, 0, 8000);
session.receiver_state.initialized = true;
session.receiver_state.base_seq = 10;
session.receiver_state.max_seq = 10;
session.receiver_state.last_timestamp = 0;
session.receiver_state.last_arrival = None;
let pkt = RtpPacket::new(0, 11, 160, 99999);
session.receive_packet(&pkt);
assert!(session.receiver_state.last_arrival.is_some());
}
#[test]
fn test_jitter_adjustment_negative() {
let mut session = RtpSession::new(12345, 0, 8000);
let pkt1 = RtpPacket::new(0, 100, 0, 99999);
session.receive_packet(&pkt1);
session.receiver_state.jitter = 1000;
session.receiver_state.last_arrival = Some(Instant::now() - Duration::from_millis(20));
session.receiver_state.last_timestamp = 160;
let pkt2 = RtpPacket::new(0, 101, 320, 99999);
session.receive_packet(&pkt2);
assert!(session.receiver_state.jitter < 1000);
}
#[test]
fn test_timestamp_for_duration() {
let session = RtpSession::new(12345, 0, 8000);
let ts = session.timestamp_for_duration(Duration::from_secs(1));
let expected = session.current_timestamp.wrapping_add(8000);
assert_eq!(ts, expected);
let ts2 = session.timestamp_for_duration(Duration::from_millis(500));
let expected2 = session.current_timestamp.wrapping_add(4000);
assert_eq!(ts2, expected2);
}
#[test]
fn test_create_sender_report() {
let mut session = RtpSession::new(12345, 0, 8000);
session.create_packet(vec![0; 160], 160, false);
session.create_packet(vec![0; 160], 160, false);
let sr = session.create_sender_report();
assert_eq!(sr.ssrc, 12345);
assert_eq!(sr.sender_packet_count, 2);
assert_eq!(sr.sender_octet_count, 320);
}
#[test]
fn test_create_sender_report_with_receiver_stats() {
let mut session = RtpSession::new(12345, 0, 8000);
let pkt = RtpPacket::new(0, 100, 0, 99999);
session.receive_packet(&pkt);
session.create_packet(vec![0; 160], 160, false);
let sr = session.create_sender_report();
assert!(!sr.report_blocks.is_empty());
}
#[test]
fn test_sender_report_expected_zero_fraction_lost() {
let mut session = RtpSession::new(12345, 0, 8000);
session.receiver_state.initialized = true;
session.receiver_state.expected_packets = 0;
session.receiver_state.packets_lost = 5;
session.receiver_state.max_seq = 42;
let sr = session.create_sender_report();
assert_eq!(sr.report_blocks.len(), 1);
assert_eq!(sr.report_blocks[0].fraction_lost, 0);
}
#[test]
fn test_create_receiver_report() {
let mut session = RtpSession::new(12345, 0, 8000);
let pkt1 = RtpPacket::new(0, 100, 0, 99999);
session.receive_packet(&pkt1);
let pkt2 = RtpPacket::new(0, 101, 160, 99999);
session.receive_packet(&pkt2);
let rr = session.create_receiver_report(99999);
assert_eq!(rr.ssrc, 12345);
assert!(!rr.report_blocks.is_empty());
assert_eq!(rr.report_blocks[0].ssrc, 99999);
}
#[test]
fn test_receiver_report_expected_zero_fraction_lost() {
let mut session = RtpSession::new(12345, 0, 8000);
session.receiver_state.initialized = true;
session.receiver_state.expected_packets = 0;
session.receiver_state.packets_lost = 3;
session.receiver_state.max_seq = 10;
let rr = session.create_receiver_report(99999);
assert_eq!(rr.report_blocks.len(), 1);
assert_eq!(rr.report_blocks[0].fraction_lost, 0);
}
#[test]
fn test_create_receiver_report_uninitialized() {
let session = RtpSession::new(12345, 0, 8000);
let rr = session.create_receiver_report(99999);
assert!(rr.report_blocks.is_empty());
}
#[test]
fn test_create_rtcp_compound_sender() {
let mut session = RtpSession::new(12345, 0, 8000);
session.create_packet(vec![0; 160], 160, false);
let compound = session.create_rtcp_compound("user@host.example.com", true);
assert!(!compound.packets.is_empty());
let debug = format!("{:?}", compound.packets[0]);
assert!(debug.contains("SenderReport"));
}
#[test]
fn test_create_rtcp_compound_receiver() {
let session = RtpSession::new(12345, 0, 8000);
let compound = session.create_rtcp_compound("user@host.example.com", false);
assert!(!compound.packets.is_empty());
let debug = format!("{:?}", compound.packets[0]);
assert!(debug.contains("ReceiverReport"));
}
#[test]
fn test_congestion_controller_creation() {
let cc = CongestionController::default();
assert_eq!(cc.target_bitrate(), 500_000);
assert_eq!(cc.state(), CongestionState::SlowStart);
}
#[test]
fn test_congestion_controller_custom() {
let cc = CongestionController::new(1_000_000, 100_000, 10_000_000);
assert_eq!(cc.target_bitrate(), 1_000_000);
}
#[test]
fn test_congestion_controller_debug() {
let cc = CongestionController::default();
let debug = format!("{:?}", cc);
assert!(debug.contains("CongestionController"));
}
#[test]
fn test_on_packet_sent() {
let mut cc = CongestionController::default();
cc.on_packet_sent();
cc.on_packet_sent();
cc.on_packet_sent();
}
#[test]
fn test_nack_reduces_bitrate() {
init_tracing();
let mut cc = CongestionController::new(1_000_000, 100_000, 10_000_000);
cc.last_decrease = Instant::now() - Duration::from_millis(200);
cc.on_nack(5);
assert_eq!(cc.target_bitrate(), 500_000);
assert_eq!(cc.state(), CongestionState::Recovery);
}
#[test]
fn test_nack_rate_limited() {
let mut cc = CongestionController::new(1_000_000, 100_000, 10_000_000);
cc.on_nack(5);
assert_eq!(cc.target_bitrate(), 1_000_000);
}
#[test]
fn test_nack_min_bitrate() {
let mut cc = CongestionController::new(100_000, 100_000, 10_000_000);
std::thread::sleep(Duration::from_millis(150));
cc.on_nack(5);
assert_eq!(cc.target_bitrate(), 100_000);
}
#[test]
fn test_remb_caps_bitrate() {
let mut cc = CongestionController::new(1_000_000, 100_000, 10_000_000);
cc.on_remb(500_000);
assert!(cc.target_bitrate() <= 500_000);
}
#[test]
fn test_remb_slightly_lower() {
let mut cc = CongestionController::new(1_000_000, 100_000, 10_000_000);
cc.on_remb(950_000);
assert_eq!(cc.target_bitrate(), 1_000_000);
}
#[test]
fn test_remb_updates_max() {
let mut cc = CongestionController::new(1_000_000, 100_000, 10_000_000);
cc.on_remb(2_000_000);
}
#[test]
fn test_remb_above_max_does_not_raise_cap() {
let mut cc = CongestionController::new(1_000_000, 100_000, 2_000_000);
cc.on_remb(5_000_000);
assert_eq!(cc.max_bitrate, 2_000_000);
assert_eq!(cc.target_bitrate(), 1_000_000);
}
#[test]
fn test_rtt_smoothing() {
let mut cc = CongestionController::default();
cc.on_rtt(Duration::from_millis(100));
cc.on_rtt(Duration::from_millis(120));
cc.on_rtt(Duration::from_millis(90));
let srtt = cc.smoothed_rtt();
assert!(srtt > Duration::from_millis(80));
assert!(srtt < Duration::from_millis(130));
}
#[test]
fn test_rtt_first_sample() {
let mut cc = CongestionController::default();
cc.on_rtt(Duration::from_millis(50));
assert_eq!(cc.smoothed_rtt(), Duration::from_millis(50));
}
#[test]
fn test_rtt_many_samples() {
let mut cc = CongestionController::default();
for i in 0..15 {
cc.on_rtt(Duration::from_millis(100 + i * 5));
}
let srtt = cc.smoothed_rtt();
assert!(srtt > Duration::from_millis(100));
assert!(srtt < Duration::from_millis(200));
}
#[test]
fn test_update_slow_start() {
let mut cc = CongestionController::new(100_000, 10_000, 1_000_000);
std::thread::sleep(Duration::from_millis(110));
cc.update();
assert!(cc.target_bitrate() > 100_000);
}
#[test]
fn test_update_rate_limited_before_interval() {
let mut cc = CongestionController::new(100_000, 10_000, 1_000_000);
cc.last_increase = Instant::now();
let before = cc.target_bitrate();
cc.update();
assert_eq!(cc.target_bitrate(), before);
}
#[test]
fn test_update_with_loss() {
let mut cc = CongestionController::new(500_000, 100_000, 10_000_000);
for _ in 0..10 {
cc.on_packet_sent();
}
cc.nacks_in_interval = 1;
std::thread::sleep(Duration::from_millis(110));
let initial_bitrate = cc.target_bitrate();
cc.update();
assert_eq!(cc.target_bitrate(), initial_bitrate);
}
#[test]
fn test_update_congestion_avoidance() {
let mut cc = CongestionController::new(800_000, 100_000, 1_000_000);
cc.state = CongestionState::CongestionAvoidance;
cc.ssthresh = 700_000;
std::thread::sleep(Duration::from_millis(110));
cc.update();
assert!(cc.target_bitrate() > 800_000);
}
#[test]
fn test_update_respects_remb() {
let mut cc = CongestionController::new(400_000, 100_000, 1_000_000);
cc.state = CongestionState::CongestionAvoidance;
cc.on_remb(450_000);
std::thread::sleep(Duration::from_millis(110));
cc.update();
assert!(cc.target_bitrate() <= 450_000);
}
#[test]
fn test_pacing_interval() {
let cc = CongestionController::new(1_000_000, 100_000, 10_000_000);
let interval = cc.pacing_interval(1200);
assert!(interval > Duration::from_millis(9));
assert!(interval < Duration::from_millis(10));
}
#[test]
fn test_pacing_interval_zero_bitrate() {
let mut cc = CongestionController::new(0, 0, 10_000_000);
cc.target_bitrate = 0;
let interval = cc.pacing_interval(1200);
assert_eq!(interval, Duration::from_millis(20));
}
#[test]
fn test_pacing_interval_small_packet() {
let cc = CongestionController::new(1_000_000, 100_000, 10_000_000);
let interval = cc.pacing_interval(100);
assert!(interval < Duration::from_millis(1));
}
#[test]
fn test_congestion_controller_reset() {
let mut cc = CongestionController::new(1_000_000, 100_000, 10_000_000);
std::thread::sleep(Duration::from_millis(150));
cc.on_nack(5);
cc.on_remb(800_000);
cc.reset(500_000);
assert_eq!(cc.target_bitrate(), 500_000);
assert_eq!(cc.state(), CongestionState::SlowStart);
}
#[test]
fn test_transition_to_congestion_avoidance() {
init_tracing();
let mut cc = CongestionController::new(100_000, 10_000, 500_000);
cc.ssthresh = cc.target_bitrate;
cc.last_increase = Instant::now() - Duration::from_millis(200);
cc.packets_in_interval = 10;
cc.nacks_in_interval = 0;
cc.update();
assert_eq!(cc.state(), CongestionState::CongestionAvoidance);
}
}