use super::*;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::os::fd::AsRawFd;
use std::time::{Duration, Instant};
const CLIENT_BYTES: usize = 3 * 1024;
const SERVER_BYTES: usize = 1024;
const PATIENCE: Duration = Duration::from_secs(20);
const LOOK: Duration = Duration::from_millis(20);
fn inode_of(stream: &TcpStream) -> u64 {
let link: std::path::PathBuf = std::fs::read_link(format!("/proc/self/fd/{}", stream.as_raw_fd())).expect("the descriptor link");
let text: String = link.to_string_lossy().into_owned();
let number: &str = text.strip_prefix("socket:[").and_then(|rest| { return rest.strip_suffix(']'); }).expect("a socket link");
return number.parse::<u64>().expect("an inode");
}
fn report_of(inode: u64) -> Option<TcpSocket> {
return established_sockets().expect("the kernel answers").into_iter().find(|socket| { return socket.inode == inode; });
}
fn pair() -> (TcpStream, TcpStream) {
let listener: TcpListener = TcpListener::bind("127.0.0.1:0").expect("a loopback listener");
let client: TcpStream = TcpStream::connect(listener.local_addr().expect("its address")).expect("connect");
let (server, _peer) = listener.accept().expect("accept");
return (client, server);
}
#[test]
fn a_connected_pair_reports_its_state_and_the_bytes_of_both_directions() {
let (mut client, mut server) = pair();
let (client_inode, server_inode): (u64, u64) = (inode_of(&client), inode_of(&server));
let before_client: TcpSocket = report_of(client_inode).expect("the client socket is listed");
let before_server: TcpSocket = report_of(server_inode).expect("the server socket is listed");
client.write_all(&[1; CLIENT_BYTES]).expect("client sends");
server.read_exact(&mut [0; CLIENT_BYTES]).expect("server reads");
server.write_all(&[2; SERVER_BYTES]).expect("server sends");
client.read_exact(&mut [0; SERVER_BYTES]).expect("client reads");
let grown = |now: &TcpSocket, before: &TcpSocket| -> (u64, u64) {
return (now.bytes_acked - before.bytes_acked, now.bytes_received - before.bytes_received);
};
let began: Instant = Instant::now();
loop {
let on_client: Option<TcpSocket> = report_of(client_inode);
let on_server: Option<TcpSocket> = report_of(server_inode);
let settled: bool = on_client.is_some_and(|socket| { return grown(&socket, &before_client) == (CLIENT_BYTES as u64, SERVER_BYTES as u64); })
&& on_server.is_some_and(|socket| { return grown(&socket, &before_server) == (SERVER_BYTES as u64, CLIENT_BYTES as u64); });
if settled {
assert_eq!((on_client.expect("listed").state, on_server.expect("listed").state), (ESTABLISHED, ESTABLISHED));
return;
}
assert!(
began.elapsed() < PATIENCE,
"the counts never grew by the bytes moved: client {on_client:?} from {before_client:?}, server {on_server:?} from {before_server:?}"
);
std::thread::sleep(LOOK);
}
}
#[test]
fn a_socket_whose_peer_closed_is_not_listed_as_connected() {
let (client, server) = pair();
let server_inode: u64 = inode_of(&server);
assert!(report_of(server_inode).is_some(), "a connected socket is listed");
drop(client);
let began: Instant = Instant::now();
while report_of(server_inode).is_some() {
assert!(began.elapsed() < PATIENCE, "a socket in CLOSE_WAIT is still listed as connected");
std::thread::sleep(LOOK);
}
}
#[test]
fn the_sockets_of_a_process_are_the_inodes_of_its_socket_links() {
let (client, server) = pair();
let mine: Vec<u64> = socket_inodes_of(i32::try_from(std::process::id()).expect("a pid"));
assert!(mine.contains(&inode_of(&client)) && mine.contains(&inode_of(&server)), "{mine:?}");
assert_eq!(socket_inodes_of(i32::MAX), Vec::<u64>::new());
}