use std::{
net::UdpSocket,
sync::mpsc::Receiver,
time::{Duration, Instant},
};
use crate::{
errors::UdpOptError,
utils::{
net_utils::{ClientCommand, interval_per_packet},
random_utils::RandomToSend,
udp_data::{FLAG_DATA, FLAG_FIN, UdpHeader, now_micros},
},
};
#[derive(Debug)]
pub struct UdpClient {
bitrate_bps: f64,
payload_size: usize,
timeout: Duration,
control_rx: Receiver<ClientCommand>,
}
impl UdpClient {
pub fn new(
bitrate_bps: f64,
payload_size: usize,
timeout: Duration,
control_rx: Receiver<ClientCommand>,
) -> Self {
Self {
bitrate_bps,
payload_size,
timeout,
control_rx,
}
}
pub fn run(&mut self, sock: &mut UdpSocket) -> Result<(), UdpOptError> {
let ipp = interval_per_packet(self.payload_size, self.bitrate_bps);
let mut seq: u64 = 0;
let mut buf = vec![0u8; self.payload_size];
let mut random = RandomToSend::new().map_err(|e| UdpOptError::FailToGetRandom(e))?;
match self.control_rx.recv() {
Ok(ClientCommand::Stop) => return Err(UdpOptError::UnexpectedCommand),
Ok(ClientCommand::Start) => {}
Err(_) => return Err(UdpOptError::ChannelClosed),
}
println!("client start");
let start = Instant::now();
loop {
if start.elapsed() >= self.timeout {
break;
}
random
.fill(&mut buf)
.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).map_err(|e| UdpOptError::SendFailed(e))?;
seq += 1;
time_to_next_target(seq, ipp, start);
}
let (sec, usec) = now_micros();
let mut fin = UdpHeader::new(seq, sec, usec, FLAG_FIN);
fin.write_header(&mut buf);
sock.send(&buf).map_err(|e| UdpOptError::SendFailed(e))?;
println!("Client done. Sent {} packets (+FIN)", seq);
Ok(())
}
}
#[inline]
fn time_to_next_target(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) {
std::thread::sleep(remaining - Duration::from_micros(100));
} else {
std::thread::yield_now();
}
}
}
#[cfg(test)]
mod udp_client_tests {
use crate::utils::udp_data::HEADER_SIZE;
use super::*;
use std::net::UdpSocket;
use std::sync::mpsc::{Sender, channel};
use std::thread;
use std::time::{Duration, Instant};
fn create_test_client(
bitrate_bps: f64,
payload_size: usize,
timeout: Duration,
) -> (UdpClient, Sender<ClientCommand>) {
let (tx, rx) = channel();
let client = UdpClient::new(bitrate_bps, payload_size, timeout, rx);
(client, tx)
}
fn create_socket_pair() -> (UdpSocket, UdpSocket) {
let server_sock = UdpSocket::bind("127.0.0.1:0").expect("Failed to bind server socket");
let client_sock = UdpSocket::bind("127.0.0.1:0").expect("Failed to bind client socket");
let server_addr = server_sock.local_addr().unwrap();
let client_addr = client_sock.local_addr().unwrap();
server_sock.connect(client_addr).unwrap();
client_sock.connect(server_addr).unwrap();
(server_sock, client_sock)
}
fn parse_header(buf: &[u8]) -> Option<(u64, u32)> {
if buf.len() < HEADER_SIZE {
return None;
}
let seq = u64::from_be_bytes(buf[0..8].try_into().unwrap());
let flags = u32::from_be_bytes(buf[20..24].try_into().unwrap());
Some((seq, flags))
}
fn receive_all_packets(sock: &mut UdpSocket, timeout: Duration) -> Vec<(u64, u32, usize)> {
sock.set_read_timeout(Some(timeout)).unwrap();
let mut packets = Vec::new();
let mut buf = vec![0u8; 65536];
loop {
match sock.recv(&mut buf) {
Ok(len) => {
if let Some((seq, flags)) = parse_header(&buf) {
packets.push((seq, flags, len));
if flags == FLAG_FIN {
break;
}
}
}
Err(_) => break,
}
}
packets
}
#[test]
fn test_client_waits_for_start_command() {
let (mut client, tx) = create_test_client(1_000_000.0, 1024, Duration::from_millis(100));
let (_server_sock, mut client_sock) = create_socket_pair();
client_sock
.set_write_timeout(Some(Duration::from_millis(100)))
.unwrap();
let handle = thread::spawn(move || client.run(&mut client_sock));
thread::sleep(Duration::from_millis(50));
tx.send(ClientCommand::Start).unwrap();
let result = handle.join().unwrap();
assert!(result.is_ok());
}
#[test]
fn test_client_sends_packets() {
let bitrate = 5_000_000.0; let payload_size = 512;
let timeout = Duration::from_millis(200);
let (mut client, tx) = create_test_client(bitrate, payload_size, timeout);
let (mut server_sock, mut client_sock) = create_socket_pair();
let handle = thread::spawn(move || client.run(&mut client_sock));
tx.send(ClientCommand::Start).unwrap();
let packets = receive_all_packets(&mut server_sock, Duration::from_millis(50));
let result = handle.join().unwrap();
assert!(result.is_ok());
assert!(
packets.len() > 0,
"Should have received at least one packet"
);
}
#[test]
fn test_client_sends_fin_packet() {
let bitrate = 10_000_000.0;
let payload_size = 512;
let timeout = Duration::from_millis(100);
let (mut client, tx) = create_test_client(bitrate, payload_size, timeout);
let (mut server_sock, mut client_sock) = create_socket_pair();
let handle = thread::spawn(move || client.run(&mut client_sock));
tx.send(ClientCommand::Start).unwrap();
let packets = receive_all_packets(&mut server_sock, Duration::from_millis(50));
let result = handle.join().unwrap();
assert!(result.is_ok());
let last_packet = packets.last().expect("Should have at least one packet");
assert_eq!(last_packet.1, FLAG_FIN, "Last packet should have FIN flag");
}
#[test]
fn test_sequence_numbers_increment_correctly() {
let bitrate = 10_000_000.0;
let payload_size = 512;
let timeout = Duration::from_millis(150);
let (mut client, tx) = create_test_client(bitrate, payload_size, timeout);
let (mut server_sock, mut client_sock) = create_socket_pair();
let handle = thread::spawn(move || client.run(&mut client_sock));
tx.send(ClientCommand::Start).unwrap();
let packets = receive_all_packets(&mut server_sock, Duration::from_millis(50));
let result = handle.join().unwrap();
assert!(result.is_ok());
assert!(packets.len() >= 2, "Should have at least 2 packets");
for i in 1..packets.len() {
let expected_seq = packets[i - 1].0 + 1;
assert_eq!(
packets[i].0,
expected_seq,
"Sequence number should increment from {} to {}",
packets[i - 1].0,
expected_seq
);
}
}
#[test]
fn test_client_timeout() {
let bitrate = 1_000_000.0;
let payload_size = 1024;
let timeout = Duration::from_millis(200);
let (mut client, tx) = create_test_client(bitrate, payload_size, timeout);
let (_server_sock, mut client_sock) = create_socket_pair();
tx.send(ClientCommand::Start).unwrap();
let start = Instant::now();
let result = client.run(&mut client_sock);
let elapsed = start.elapsed();
assert!(result.is_ok());
assert!(
elapsed >= timeout,
"Should run for at least timeout duration"
);
assert!(
elapsed < timeout + Duration::from_millis(100),
"Should not run much longer than timeout"
);
}
#[test]
fn test_zero_timeout_sends_only_fin() {
let bitrate = 1_000_000.0;
let payload_size = 1024;
let timeout = Duration::from_millis(0);
let (mut client, tx) = create_test_client(bitrate, payload_size, timeout);
let (mut server_sock, mut client_sock) = create_socket_pair();
let handle = thread::spawn(move || client.run(&mut client_sock));
tx.send(ClientCommand::Start).unwrap();
let packets = receive_all_packets(&mut server_sock, Duration::from_millis(50));
let result = handle.join().unwrap();
assert!(result.is_ok());
assert_eq!(packets.len(), 1, "Should only send FIN with zero timeout");
assert_eq!(packets[0].0, 0, "FIN should have sequence 0");
assert_eq!(packets[0].1, FLAG_FIN, "Should be FIN packet");
}
#[test]
fn test_no_duplicate_sequence_numbers() {
let bitrate = 10_000_000.0;
let payload_size = 512;
let timeout = Duration::from_millis(150);
let (mut client, tx) = create_test_client(bitrate, payload_size, timeout);
let (mut server_sock, mut client_sock) = create_socket_pair();
let handle = thread::spawn(move || client.run(&mut client_sock));
tx.send(ClientCommand::Start).unwrap();
let packets = receive_all_packets(&mut server_sock, Duration::from_millis(50));
let _ = handle.join().unwrap();
let mut seen_seqs = std::collections::HashSet::new();
for (seq, _, _) in &packets {
assert!(seen_seqs.insert(*seq), "Duplicate sequence number: {}", seq);
}
}
}