use std::io;
#[cfg(any(unix, target_os = "wasi"))]
use std::os::fd::{AsFd as AsSource, OwnedFd as Owned};
#[cfg(windows)]
use std::os::windows::io::{AsSocket as AsSource, OwnedSocket as Owned};
#[cfg(all(feature = "unix", unix))]
use std::time::Duration;
use socket2::{Domain, SockAddr, SockRef, Socket, Type};
use crate::{AsyncIo, Mode, Runtime};
#[cfg(all(feature = "unix", unix))]
pub(super) async fn connect_unix<S, M>(
runtime: &Runtime<M>,
address: &SockAddr,
) -> io::Result<AsyncIo<S, M>>
where
S: From<Owned> + AsSource + Send + Sync + 'static,
M: Mode,
{
loop {
match connect(runtime, Domain::UNIX, address).await {
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
runtime.sleep(BACKLOG_INTERVAL).await;
}
result => return result,
}
}
}
#[cfg(all(feature = "unix", unix))]
const BACKLOG_INTERVAL: Duration = Duration::from_millis(20);
pub(super) async fn connect<S, M>(
runtime: &Runtime<M>,
domain: Domain,
address: &SockAddr,
) -> io::Result<AsyncIo<S, M>>
where
S: From<Owned> + AsSource + Send + Sync + 'static,
M: Mode,
{
let socket = Socket::new(domain, Type::STREAM, None)?;
socket.set_nonblocking(true)?;
let under_way = match socket.connect(address) {
Ok(()) => false,
Err(e) if is_in_progress(&e) => true,
Err(e) => return Err(e),
};
let io = AsyncIo::from_nonblocking(runtime, S::from(Owned::from(socket)))?;
if under_way {
io.write_with(|socket| outcome(socket)).await?;
}
Ok(io)
}
fn is_in_progress(error: &io::Error) -> bool {
#[cfg(any(unix, target_os = "wasi"))]
{
rustix::io::Errno::from_io_error(error) == Some(rustix::io::Errno::INPROGRESS)
}
#[cfg(windows)]
{
error.kind() == io::ErrorKind::WouldBlock
}
}
fn outcome<S>(socket: &S) -> io::Result<()>
where
S: AsSource,
{
#[cfg(any(unix, target_os = "wasi"))]
{
let socket = SockRef::from(socket);
match socket.take_error()? {
#[cfg(target_os = "wasi")]
Some(e) if is_in_progress(&e) => Err(io::ErrorKind::WouldBlock.into()),
Some(e) => Err(e),
None => match socket.peer_addr() {
Ok(_) => Ok(()),
Err(e) if e.kind() == io::ErrorKind::NotConnected => {
Err(io::ErrorKind::WouldBlock.into())
}
Err(e) => Err(socket.take_error()?.unwrap_or(e)),
},
}
}
#[cfg(windows)]
{
match progress(socket)? {
Progress::Connected => Ok(()),
Progress::Failed => Err(SockRef::from(socket)
.take_error()?
.unwrap_or_else(|| io::Error::other("the connection failed"))),
Progress::UnderWay => Err(io::ErrorKind::WouldBlock.into()),
}
}
}
#[cfg(windows)]
enum Progress {
Connected,
Failed,
UnderWay,
}
#[cfg(windows)]
fn progress<S>(socket: &S) -> io::Result<Progress>
where
S: AsSource,
{
use std::{os::windows::io::AsRawSocket, ptr};
use windows_sys::Win32::Networking::WinSock::{
FD_SET, SOCKET, SOCKET_ERROR, TIMEVAL, WSAGetLastError, select,
};
let mut connected = FD_SET {
fd_count: 1,
..FD_SET::default()
};
connected.fd_array[0] = socket.as_socket().as_raw_socket() as SOCKET;
let mut failed = connected;
let immediately = TIMEVAL::default();
let ready = unsafe {
select(
1,
ptr::null_mut(),
&mut connected,
&mut failed,
&immediately,
)
};
if ready == SOCKET_ERROR {
return Err(io::Error::from_raw_os_error(unsafe { WSAGetLastError() }));
}
if connected.fd_count != 0 {
Ok(Progress::Connected)
} else if failed.fd_count != 0 {
Ok(Progress::Failed)
} else {
Ok(Progress::UnderWay)
}
}