use std::time::{Duration, Instant};
use tokio::{net::UdpSocket, sync::mpsc::Receiver};
use crate::{
errors::UdpOptError,
utils::{
net_utils::{ClientCommand, interval_per_packet},
random_utils::AsyncRandomToSend,
udp_data::{FLAG_DATA, FLAG_FIN, UdpHeader, now_micros},
},
};
#[derive(Debug)]
pub struct AsyncUdpClient {
bitrate_bps: f64,
payload_size: usize,
timeout: Duration,
control_rx: Receiver<ClientCommand>,
}
impl AsyncUdpClient {
pub async fn new(
bitrate_bps: f64,
payload_size: usize,
timeout: Duration,
control_rx: Receiver<ClientCommand>,
) -> Self {
Self {
bitrate_bps,
payload_size,
timeout,
control_rx,
}
}
pub async fn run(&mut self, sock: &mut UdpSocket) -> Result<(), UdpOptError> {
let ipp = interval_per_packet(self.payload_size, self.bitrate_bps);
let mut seq = 0;
let mut buf = vec![0u8; self.payload_size];
let mut random = AsyncRandomToSend::new()
.await
.map_err(|e| UdpOptError::FailToGetRandom(e))?;
match self.control_rx.recv().await {
Some(ClientCommand::Stop) => return Err(UdpOptError::UnexpectedCommand),
Some(ClientCommand::Start) => {}
None => return Err(UdpOptError::ChannelClosed),
}
let start = Instant::now();
loop {
if start.elapsed() >= self.timeout {
break;
}
random
.fill(&mut buf)
.await
.map_err(|e| UdpOptError::FailToGetRandom(e))?;
let (sec, usec) = now_micros();
let mut header = UdpHeader::new(seq, sec, usec, FLAG_DATA);
header.write_header(&mut buf);
sock.send(&buf)
.await
.map_err(|e| UdpOptError::SendFailed(e))?;
seq += 1;
time_to_next_target_async(seq, ipp, start).await;
}
let (sec, usec) = now_micros();
let mut fin = UdpHeader::new(seq, sec, usec, FLAG_FIN);
fin.write_header(&mut buf);
sock.send(&buf)
.await
.map_err(|e| UdpOptError::SendFailed(e))?;
println!("Client done. Sent {} packets (+FIN)", seq);
Ok(())
}
}
async fn time_to_next_target_async(seq: u64, ipp: Duration, start: Instant) {
let next_target = start + Duration::from_secs_f64(seq as f64 * ipp.as_secs_f64());
loop {
let now = Instant::now();
if now >= next_target {
break;
}
let remaining = next_target - now;
if remaining > Duration::from_micros(200) {
tokio::time::sleep(remaining - Duration::from_micros(100)).await;
} else {
tokio::task::yield_now().await;
}
}
}