use std::collections::{BTreeMap, VecDeque};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::{Notify, Semaphore};
use tokio::time::sleep;
use tracing::{debug, trace, warn};
use crate::packet::{rtcp::RtcpPacket, RtpPacket};
use crate::RtpSsrc;
use super::{BufferPool, GlobalBufferManager};
pub const DEFAULT_TRANSMIT_BUFFER_CAPACITY: usize = 1000;
pub const DEFAULT_CONGESTION_WINDOW: usize = 64;
pub const DEFAULT_MIN_RTO_MS: u64 = 70;
pub const DEFAULT_MAX_RTO_MS: u64 = 1000;
pub const DEFAULT_INITIAL_RTO_MS: u64 = 200;
pub const DEFAULT_MAX_BURST: usize = 16;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum PacketPriority {
Control = 0,
High = 1,
Normal = 2,
Low = 3,
}
impl Default for PacketPriority {
fn default() -> Self {
Self::Normal
}
}
#[allow(dead_code)] struct QueuedPacket {
packet: RtpPacket,
queue_time: Instant,
#[allow(dead_code)] priority: PacketPriority,
#[allow(dead_code)] is_retransmission: bool,
metadata: Option<PacketMetadata>,
}
#[derive(Debug, Clone)]
pub struct PacketMetadata {
pub first_send_time: Option<Instant>,
pub transmit_count: u32,
pub acknowledged: bool,
pub last_send_time: Option<Instant>,
}
#[allow(dead_code)] struct CongestionState {
cwnd: usize,
ssthresh: usize,
rto_ms: u64,
srtt_ms: Option<f64>,
rttvar_ms: Option<f64>,
#[allow(dead_code)] last_window_reduction: Instant,
#[allow(dead_code)] estimated_bps: u64,
in_flight: usize,
#[allow(dead_code)] lost_packets: u64,
total_sent: u64,
last_seq_sent: u16,
in_slow_start: bool,
}
impl Default for CongestionState {
fn default() -> Self {
Self {
cwnd: DEFAULT_CONGESTION_WINDOW,
ssthresh: usize::MAX,
rto_ms: DEFAULT_INITIAL_RTO_MS,
srtt_ms: None,
rttvar_ms: None,
last_window_reduction: Instant::now(),
estimated_bps: 1_000_000, in_flight: 0,
lost_packets: 0,
total_sent: 0,
last_seq_sent: 0,
in_slow_start: true,
}
}
}
#[derive(Debug, Clone)]
pub struct TransmitBufferConfig {
pub max_packets: usize,
pub initial_cwnd: usize,
pub min_rto_ms: u64,
pub max_rto_ms: u64,
pub initial_rto_ms: u64,
pub congestion_control_enabled: bool,
pub max_burst: usize,
pub prioritize_packets: bool,
pub max_packet_age_ms: u64,
pub clock_rate: u32,
}
impl Default for TransmitBufferConfig {
fn default() -> Self {
Self {
max_packets: DEFAULT_TRANSMIT_BUFFER_CAPACITY,
initial_cwnd: DEFAULT_CONGESTION_WINDOW,
min_rto_ms: DEFAULT_MIN_RTO_MS,
max_rto_ms: DEFAULT_MAX_RTO_MS,
initial_rto_ms: DEFAULT_INITIAL_RTO_MS,
congestion_control_enabled: true,
max_burst: DEFAULT_MAX_BURST,
prioritize_packets: true,
max_packet_age_ms: 1000, clock_rate: 8000, }
}
}
#[derive(Debug, Clone)]
pub struct TransmitBufferStats {
pub queued_packets: usize,
pub packets_sent: u64,
pub packets_dropped_overflow: u64,
pub packets_dropped_aged: u64,
pub packets_retransmitted: u64,
pub cwnd: usize,
pub rto_ms: u64,
pub srtt_ms: Option<f64>,
pub estimated_bps: u64,
pub in_flight: usize,
pub loss_rate: f64,
pub buffer_fullness: f32,
pub packets_queued: usize,
pub packets_dropped: u64,
}
impl Default for TransmitBufferStats {
fn default() -> Self {
Self {
queued_packets: 0,
packets_sent: 0,
packets_dropped_overflow: 0,
packets_dropped_aged: 0,
packets_retransmitted: 0,
cwnd: DEFAULT_CONGESTION_WINDOW,
rto_ms: DEFAULT_INITIAL_RTO_MS,
srtt_ms: None,
estimated_bps: 1_000_000, in_flight: 0,
loss_rate: 0.0,
buffer_fullness: 0.0,
packets_queued: 0,
packets_dropped: 0,
}
}
}
#[allow(dead_code)] pub struct TransmitBuffer {
config: TransmitBufferConfig,
queues: BTreeMap<PacketPriority, VecDeque<QueuedPacket>>,
congestion: CongestionState,
stats: TransmitBufferStats,
buffer_manager: Option<Arc<GlobalBufferManager>>,
packet_notify: Arc<Notify>,
packet_tracking: BTreeMap<u16, PacketMetadata>,
cwnd_semaphore: Arc<Semaphore>,
packet_buffer: Option<Arc<BufferPool>>,
#[allow(dead_code)] ssrc: RtpSsrc,
last_send_time: Option<Instant>,
pacing_interval_us: u64,
}
impl TransmitBuffer {
pub fn new(ssrc: RtpSsrc, config: TransmitBufferConfig) -> Self {
let mut queues = BTreeMap::new();
queues.insert(PacketPriority::Control, VecDeque::with_capacity(100));
queues.insert(
PacketPriority::High,
VecDeque::with_capacity(config.max_packets / 4),
);
queues.insert(
PacketPriority::Normal,
VecDeque::with_capacity(config.max_packets / 2),
);
queues.insert(
PacketPriority::Low,
VecDeque::with_capacity(config.max_packets / 4),
);
let mut congestion = CongestionState::default();
congestion.cwnd = config.initial_cwnd;
congestion.rto_ms = config.initial_rto_ms;
let stats = TransmitBufferStats {
queued_packets: 0,
packets_sent: 0,
packets_dropped_overflow: 0,
packets_dropped_aged: 0,
packets_retransmitted: 0,
cwnd: config.initial_cwnd,
rto_ms: config.initial_rto_ms,
srtt_ms: None,
estimated_bps: 1_000_000, in_flight: 0,
loss_rate: 0.0,
buffer_fullness: 0.0,
packets_queued: 0,
packets_dropped: 0,
};
let cwnd_semaphore = Arc::new(Semaphore::new(config.initial_cwnd));
Self {
config,
queues,
congestion,
stats,
buffer_manager: None,
packet_notify: Arc::new(Notify::new()),
packet_tracking: BTreeMap::new(),
cwnd_semaphore,
packet_buffer: None,
ssrc,
last_send_time: None,
pacing_interval_us: 0,
}
}
pub fn with_buffer_manager(
ssrc: RtpSsrc,
config: TransmitBufferConfig,
buffer_manager: Arc<GlobalBufferManager>,
packet_buffer: Arc<BufferPool>,
) -> Self {
let mut buffer = Self::new(ssrc, config);
buffer.buffer_manager = Some(buffer_manager);
buffer.packet_buffer = Some(packet_buffer);
buffer
}
pub async fn queue_packet(&mut self, packet: RtpPacket, priority: PacketPriority) -> bool {
let total_packets = self.total_queued_packets();
if total_packets >= self.config.max_packets {
if priority != PacketPriority::High {
trace!(
"Buffer full ({}/{}), dropping {:?} priority packet with seq={}",
total_packets,
self.config.max_packets,
priority,
packet.header.sequence_number
);
self.stats.packets_dropped_overflow += 1;
return false;
} else {
if !self.drop_low_priority_packets() {
trace!("Buffer full, nowhere to make room for high priority packet");
self.stats.packets_dropped_overflow += 1;
return false;
}
}
}
let metadata = PacketMetadata {
first_send_time: None,
transmit_count: 0,
acknowledged: false,
last_send_time: None,
};
let queued = QueuedPacket {
packet,
queue_time: Instant::now(),
priority,
is_retransmission: false,
metadata: Some(metadata),
};
let queue = self
.queues
.entry(priority)
.or_insert_with(|| VecDeque::new());
queue.push_back(queued);
self.stats.queued_packets = self.total_queued_packets();
self.packet_notify.notify_one();
true
}
pub async fn schedule_retransmission(&mut self, seq: u16) -> bool {
if let Some(metadata) = self.packet_tracking.get_mut(&seq) {
if !metadata.acknowledged {
for (_priority, queue) in self.queues.iter() {
for queued_packet in queue {
if queued_packet.packet.header.sequence_number == seq {
trace!("Packet seq={} already queued for retransmission", seq);
return true;
}
}
}
warn!(
"Requested retransmission for seq={} but packet not available",
seq
);
self.stats.packets_retransmitted += 1;
return false;
}
}
false
}
pub async fn get_next_packet(&mut self) -> Option<RtpPacket> {
if self.total_queued_packets() == 0 {
return None;
}
if !self.config.congestion_control_enabled {
return self.get_packet_without_congestion_control().await;
}
if self.congestion.in_flight > 0 {
if self.congestion.in_flight >= self.congestion.cwnd {
trace!(
"Congestion window full ({}/{}), not sending new packets",
self.congestion.in_flight,
self.congestion.cwnd
);
return None;
}
}
match self.cwnd_semaphore.try_acquire() {
Ok(permit) => {
drop(permit);
}
Err(_) => {
trace!("No congestion permits available, not sending");
return None;
}
}
if self.pacing_interval_us > 0 {
self.apply_pacing().await;
}
let packet_option = self.dequeue_highest_priority_packet();
if let Some(_packet) = &packet_option {
self.congestion.in_flight += 1;
self.congestion.total_sent += 1;
self.stats.in_flight = self.congestion.in_flight;
self.stats.packets_sent += 1;
self.last_send_time = Some(Instant::now());
}
packet_option
}
pub async fn wait_for_packet(&self, timeout: Duration) -> bool {
if self.total_queued_packets() > 0 && self.cwnd_semaphore.available_permits() > 0 {
return true;
}
let notify = self.packet_notify.clone();
tokio::select! {
_ = notify.notified() => true,
_ = tokio::time::sleep(timeout) => false,
}
}
pub fn process_rtcp_feedback(&mut self, rtcp: &RtcpPacket) {
match rtcp {
RtcpPacket::ReceiverReport(_) => {
let rtt_ms = 50.0; self.update_rtt_estimate(rtt_ms);
self.stats.loss_rate = 0.01;
self.update_congestion_window(None);
}
RtcpPacket::SenderReport(_) => {
}
_ => {
}
}
}
pub fn acknowledge_packet(&mut self, seq: u16) {
if let Some(metadata) = self.packet_tracking.get_mut(&seq) {
metadata.acknowledged = true;
if self.congestion.in_flight > 0 {
self.congestion.in_flight -= 1;
self.stats.in_flight = self.congestion.in_flight;
}
self.cwnd_semaphore.add_permits(1);
if self.config.congestion_control_enabled {
self.update_congestion_window(Some(seq));
}
}
}
fn update_rtt_estimate(&mut self, rtt_ms: f64) {
if let (Some(srtt), Some(rttvar)) = (self.congestion.srtt_ms, self.congestion.rttvar_ms) {
let alpha = 0.125;
let beta = 0.25;
let new_rttvar = (1.0 - beta) * rttvar + beta * (srtt - rtt_ms).abs();
let new_srtt = (1.0 - alpha) * srtt + alpha * rtt_ms;
self.congestion.rttvar_ms = Some(new_rttvar);
self.congestion.srtt_ms = Some(new_srtt);
let new_rto = new_srtt + 4.0 * new_rttvar;
let clamped_rto = (new_rto.round() as u64)
.max(self.config.min_rto_ms)
.min(self.config.max_rto_ms);
self.congestion.rto_ms = clamped_rto;
} else {
let srtt = rtt_ms;
let rttvar = rtt_ms / 2.0;
self.congestion.srtt_ms = Some(srtt);
self.congestion.rttvar_ms = Some(rttvar);
let new_rto = srtt + 4.0 * rttvar;
let clamped_rto = (new_rto.round() as u64)
.max(self.config.min_rto_ms)
.min(self.config.max_rto_ms);
self.congestion.rto_ms = clamped_rto;
}
self.stats.srtt_ms = self.congestion.srtt_ms;
self.stats.rto_ms = self.congestion.rto_ms;
}
fn update_congestion_window(&mut self, seq: Option<u16>) {
if self.congestion.in_slow_start {
let new_cwnd = self.congestion.cwnd + 1;
if new_cwnd >= self.congestion.ssthresh {
self.congestion.in_slow_start = false;
}
self.congestion.cwnd = new_cwnd;
} else {
if let Some(ack_seq) = seq {
let cwnd = self.congestion.cwnd;
if ack_seq % (cwnd as u16) == 0 {
self.congestion.cwnd += 1;
}
}
}
self.update_pacing();
self.stats.cwnd = self.congestion.cwnd;
}
fn update_pacing(&mut self) {
if let Some(srtt_ms) = self.congestion.srtt_ms {
let srtt_us = (srtt_ms * 1000.0) as u64;
const MIN_INTERVAL_US: u64 = 100;
let interval = if self.congestion.cwnd > 0 {
srtt_us / self.congestion.cwnd as u64
} else {
srtt_us
};
self.pacing_interval_us = interval.max(MIN_INTERVAL_US);
} else {
self.pacing_interval_us = 1000; }
}
async fn apply_pacing(&mut self) {
if self.pacing_interval_us == 0 {
return;
}
if let Some(last_send_time) = self.last_send_time {
let now = Instant::now();
let elapsed_us = now.duration_since(last_send_time).as_micros() as u64;
if elapsed_us < self.pacing_interval_us {
let wait_us = self.pacing_interval_us - elapsed_us;
if wait_us > 100 {
sleep(Duration::from_micros(wait_us)).await;
}
}
}
self.last_send_time = Some(Instant::now());
}
fn total_queued_packets(&self) -> usize {
self.queues.values().map(|q| q.len()).sum()
}
fn drop_low_priority_packets(&mut self) -> bool {
let mut dropped = false;
if let Some(queue) = self.queues.get_mut(&PacketPriority::Low) {
if !queue.is_empty() {
queue.pop_front();
self.stats.packets_dropped_overflow += 1;
self.stats.queued_packets = self.total_queued_packets();
dropped = true;
trace!("Dropped low priority packet to make room");
return dropped;
}
}
if let Some(queue) = self.queues.get_mut(&PacketPriority::Normal) {
if !queue.is_empty() {
queue.pop_front();
self.stats.packets_dropped_overflow += 1;
self.stats.queued_packets = self.total_queued_packets();
dropped = true;
trace!("Dropped normal priority packet to make room");
return dropped;
}
}
trace!("No low/normal priority packets available to drop");
dropped
}
fn dequeue_highest_priority_packet(&mut self) -> Option<RtpPacket> {
for priority in [
PacketPriority::Control,
PacketPriority::High,
PacketPriority::Normal,
PacketPriority::Low,
] {
if let Some(queue) = self.queues.get_mut(&priority) {
if !queue.is_empty() {
let queued_packet = queue.pop_front().unwrap();
self.stats.queued_packets = self.total_queued_packets();
if let Some(mut metadata) = queued_packet.metadata {
let now = Instant::now();
let seq = queued_packet.packet.header.sequence_number;
if metadata.first_send_time.is_none() {
metadata.first_send_time = Some(now);
}
metadata.transmit_count += 1;
metadata.last_send_time = Some(now);
self.packet_tracking.insert(seq, metadata);
self.congestion.last_seq_sent = seq;
}
return Some(queued_packet.packet);
}
}
}
None
}
async fn get_packet_without_congestion_control(&mut self) -> Option<RtpPacket> {
let packet_option = self.dequeue_highest_priority_packet();
if packet_option.is_some() {
self.stats.packets_sent += 1;
self.last_send_time = Some(Instant::now());
}
packet_option
}
pub fn purge_expired_packets(&mut self) {
let now = Instant::now();
let max_age = Duration::from_millis(self.config.max_packet_age_ms);
for queue in self.queues.values_mut() {
let mut i = 0;
while i < queue.len() {
if now.duration_since(queue[i].queue_time) > max_age {
queue.remove(i);
self.stats.packets_dropped_aged += 1;
} else {
i += 1;
}
}
}
self.stats.queued_packets = self.total_queued_packets();
}
pub fn clear(&mut self) {
for queue in self.queues.values_mut() {
queue.clear();
}
self.packet_tracking.clear();
self.stats.queued_packets = 0;
}
pub fn get_stats(&self) -> TransmitBufferStats {
let total_capacity = self.config.max_packets;
let current_queued = self.total_queued_packets();
let buffer_fullness = if total_capacity > 0 {
current_queued as f32 / total_capacity as f32
} else {
0.0
};
let mut stats = self.stats.clone();
stats.queued_packets = current_queued;
stats.buffer_fullness = buffer_fullness;
stats.packets_queued = current_queued;
stats.packets_dropped = stats.packets_dropped_overflow + stats.packets_dropped_aged;
stats
}
pub fn update_config(&mut self, config: TransmitBufferConfig) {
let _old_max_packets = self.config.max_packets;
let old_cwnd = self.config.initial_cwnd;
let old_cc_enabled = self.config.congestion_control_enabled;
self.config = config;
if old_cwnd != self.config.initial_cwnd {
if !old_cc_enabled || !self.config.congestion_control_enabled {
self.congestion.cwnd = self.config.initial_cwnd;
let in_flight = self.congestion.in_flight;
let available = if in_flight < self.congestion.cwnd {
self.congestion.cwnd - in_flight
} else {
0
};
self.cwnd_semaphore = Arc::new(Semaphore::new(available));
self.stats.cwnd = self.congestion.cwnd;
}
}
self.update_pacing();
debug!(
"Updated transmit buffer config: max_packets={}, cwnd={}, cc_enabled={}",
self.config.max_packets, self.congestion.cwnd, self.config.congestion_control_enabled
);
}
pub fn set_priority_threshold(&mut self, buffer_fullness: f32, priority: PacketPriority) {
debug!("Setting priority threshold: at {:.1}% fullness, only {:?} or higher priority will be sent",
buffer_fullness * 100.0, priority);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::{RtpHeader, RtpPacket};
use bytes::Bytes;
fn create_test_packet(seq: u16, ts: u32, ssrc: RtpSsrc) -> RtpPacket {
let header = RtpHeader::new(96, seq, ts, ssrc);
let payload = Bytes::from_static(b"test");
RtpPacket::new(header, payload)
}
#[tokio::test]
async fn test_basic_queuing() {
let config = TransmitBufferConfig::default();
let mut buffer = TransmitBuffer::new(0x12345678, config);
buffer
.queue_packet(create_test_packet(1, 0, 0x12345678), PacketPriority::Normal)
.await;
buffer
.queue_packet(create_test_packet(2, 160, 0x12345678), PacketPriority::High)
.await;
let p1 = buffer.get_next_packet().await;
let p2 = buffer.get_next_packet().await;
assert!(p1.is_some());
assert!(p2.is_some());
assert_eq!(p1.unwrap().header.sequence_number, 2);
assert_eq!(p2.unwrap().header.sequence_number, 1);
}
#[tokio::test]
async fn test_buffer_overflow() {
let config = TransmitBufferConfig {
max_packets: 2,
..Default::default()
};
let mut buffer = TransmitBuffer::new(0x12345678, config);
assert!(
buffer
.queue_packet(create_test_packet(1, 0, 0x12345678), PacketPriority::Normal)
.await,
"First packet should be queued"
);
assert!(
buffer
.queue_packet(
create_test_packet(2, 160, 0x12345678),
PacketPriority::Normal
)
.await,
"Second packet should be queued"
);
let stats = buffer.get_stats();
assert_eq!(stats.queued_packets, 2, "Buffer should have 2 packets");
assert!(
!buffer
.queue_packet(
create_test_packet(3, 320, 0x12345678),
PacketPriority::Normal
)
.await,
"Third normal packet should be rejected"
);
assert!(
buffer
.queue_packet(create_test_packet(3, 320, 0x12345678), PacketPriority::High)
.await,
"High priority packet should be accepted"
);
let stats = buffer.get_stats();
assert_eq!(
stats.queued_packets, 2,
"Buffer should still have 2 packets"
);
let p1 = buffer.get_next_packet().await;
let p2 = buffer.get_next_packet().await;
let p3 = buffer.get_next_packet().await;
assert!(
p1.is_some(),
"First packet (high priority) should be available"
);
assert!(p2.is_some(), "Second packet should be available");
assert!(p3.is_none(), "Buffer should be empty after 2 packets");
assert_eq!(
p1.unwrap().header.sequence_number,
3,
"First packet should be the high priority one"
);
assert_eq!(
p2.unwrap().header.sequence_number,
2,
"Second packet should be the remaining normal one"
);
}
#[tokio::test]
async fn test_congestion_control() {
let config = TransmitBufferConfig {
initial_cwnd: 2, congestion_control_enabled: true,
..Default::default()
};
let mut buffer = TransmitBuffer::new(0x12345678, config);
for i in 1..=5 {
assert!(
buffer
.queue_packet(
create_test_packet(i, (i as u32) * 160, 0x12345678),
PacketPriority::Normal
)
.await,
"Packet {} should be queued",
i
);
}
let stats = buffer.get_stats();
assert_eq!(
stats.queued_packets, 5,
"Buffer should have 5 queued packets"
);
let p1 = buffer.get_next_packet().await;
assert!(p1.is_some(), "First packet should be available");
let p2 = buffer.get_next_packet().await;
assert!(p2.is_some(), "Second packet should be available");
let stats = buffer.get_stats();
assert_eq!(stats.in_flight, 2, "Should have 2 packets in flight");
let p3 = buffer.get_next_packet().await;
assert!(
p3.is_none(),
"Third packet should be blocked by congestion window"
);
buffer.acknowledge_packet(1);
let stats = buffer.get_stats();
assert_eq!(
stats.in_flight, 1,
"Should have 1 packet in flight after ACK"
);
let p3 = buffer.get_next_packet().await;
assert!(p3.is_some(), "Third packet should be available after ACK");
assert_eq!(
p3.unwrap().header.sequence_number,
3,
"Third packet should have seq=3"
);
}
}