use std::{fmt::Debug, net::SocketAddr, time::Duration};
#[cfg(feature = "blocking")]
use crate::net::blocking_impl::{Framed as BlockingFramed, FramedInner as BlockingFramedInner};
#[cfg(feature = "tokio")]
use crate::net::tokio_impl::{Framed as AsyncFramed, FramedInner as AsyncFramedInner};
use crate::{
identifiers::RequestId,
insim::{Isi, IsiFlags},
net::{Codec, Mode},
relay::Sel,
result::Result,
};
#[derive(Clone, Debug, Default)]
pub enum Proto {
#[default]
Tcp,
Udp,
Relay,
}
#[derive(Debug)]
pub struct Builder {
proto: Proto,
connect_timeout: Duration,
handshake_timeout: Duration,
remote: SocketAddr,
verify_version: bool,
mode: Mode,
isi_admin_password: Option<String>,
isi_flags: IsiFlags,
isi_prefix: Option<char>,
isi_interval: Option<Duration>,
isi_iname: Option<String>,
isi_reqi: RequestId,
tcp_nodelay: bool,
udp_local_address: Option<SocketAddr>,
relay_select_host: Option<String>,
relay_spectator_password: Option<String>,
relay_admin_password: Option<String>,
#[cfg(feature = "websocket")]
relay_websocket: bool,
}
impl Default for Builder {
fn default() -> Self {
Self {
connect_timeout: Duration::from_secs(10),
handshake_timeout: Duration::from_secs(30),
proto: Proto::Tcp,
remote: "127.0.0.1:29999".parse().unwrap(),
verify_version: true,
mode: Mode::Compressed,
tcp_nodelay: true,
udp_local_address: None,
relay_select_host: None,
relay_spectator_password: None,
relay_admin_password: None,
#[cfg(feature = "websocket")]
relay_websocket: false,
isi_admin_password: None,
isi_flags: IsiFlags::default(),
isi_prefix: None,
isi_iname: None,
isi_interval: None,
isi_reqi: RequestId(0),
}
}
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
pub fn tcp<R: Into<SocketAddr>>(mut self, remote_addr: R) -> Self {
self.proto = Proto::Tcp;
self.remote = remote_addr.into();
self
}
pub fn udp<L: Into<Option<SocketAddr>>, R: Into<SocketAddr>>(
mut self,
remote_addr: R,
local_addr: L,
) -> Self {
self.proto = Proto::Udp;
self.remote = remote_addr.into();
self.udp_local_address = local_addr.into();
self
}
pub fn relay(mut self) -> Self {
self.proto = Proto::Relay;
self
}
#[cfg(feature = "websocket")]
pub fn relay_websocket(mut self, ws: bool) -> Self {
self.relay_websocket = ws;
self
}
pub fn connect_timeout(mut self, duration: Duration) -> Self {
self.connect_timeout = duration;
self
}
pub fn mode(mut self, mode: Mode) -> Self {
self.mode = mode;
self
}
pub fn compressed(self) -> Self {
self.mode(Mode::Compressed)
}
pub fn uncompressed(self) -> Self {
self.mode(Mode::Uncompressed)
}
pub fn verify_version(mut self, verify: bool) -> Self {
self.verify_version = verify;
self
}
pub fn tcp_nodelay(mut self, no_delay: bool) -> Self {
self.tcp_nodelay = no_delay;
self
}
pub fn relay_select_host<H: Into<Option<String>>>(mut self, host: H) -> Self {
self.relay_select_host = host.into();
self
}
pub fn relay_spectator_password<P: Into<Option<String>>>(mut self, password: P) -> Self {
self.relay_spectator_password = password.into();
self
}
pub fn relay_admin_password<P: Into<Option<String>>>(mut self, password: P) -> Self {
self.relay_admin_password = password.into();
self
}
pub fn isi_admin_password<P: Into<Option<String>>>(mut self, password: P) -> Self {
self.isi_admin_password = password.into();
self
}
pub fn isi_reqi(mut self, i: RequestId) -> Self {
self.isi_reqi = i;
self
}
pub fn isi_flags(mut self, flags: IsiFlags) -> Self {
self.isi_flags = flags;
self
}
pub fn isi_prefix<C: Into<Option<char>>>(mut self, c: C) -> Self {
self.isi_prefix = c.into();
self
}
pub fn isi_iname<N: Into<Option<String>>>(mut self, iname: N) -> Self {
self.isi_iname = iname.into();
self
}
pub fn isi_interval<D: Into<Option<Duration>>>(mut self, duration: D) -> Self {
self.isi_interval = duration.into();
self
}
pub fn isi(&self) -> Isi {
let udpport = match self.proto {
Proto::Udp => self.udp_local_address.unwrap().port(),
_ => 0,
};
Isi {
reqi: self.isi_reqi,
udpport,
flags: self.isi_flags,
admin: self.isi_admin_password.as_deref().unwrap_or("").to_owned(),
iname: self
.isi_iname
.as_deref()
.unwrap_or(Isi::DEFAULT_INAME)
.to_owned(),
prefix: self.isi_prefix.unwrap_or(0 as char),
interval: self.isi_interval.unwrap_or(Duration::ZERO),
..Default::default()
}
}
#[cfg(feature = "blocking")]
pub fn connect_blocking(&self) -> Result<BlockingFramed> {
use std::net::ToSocketAddrs;
use crate::LFSW_RELAY_ADDR;
match self.proto {
Proto::Tcp => {
let stream =
std::net::TcpStream::connect_timeout(&self.remote, self.connect_timeout)?;
stream.set_nodelay(self.tcp_nodelay)?;
let mut stream = BlockingFramedInner::new(stream, Codec::new(self.mode.clone()));
stream.verify_version(self.verify_version);
stream.handshake(self.isi())?;
Ok(BlockingFramed::Tcp(stream))
},
Proto::Udp => {
let local = self.udp_local_address.unwrap_or("0.0.0.0:0".parse()?);
let stream = std::net::UdpSocket::bind(local)?;
stream.connect(self.remote)?;
let mut isi = self.isi();
if self.udp_local_address.is_none() {
isi.udpport = local.port();
}
let mut stream = BlockingFramedInner::new(stream, Codec::new(self.mode.clone()));
stream.verify_version(self.verify_version);
stream.handshake(isi)?;
Ok(BlockingFramed::Udp(stream))
},
Proto::Relay => {
let addrs = LFSW_RELAY_ADDR.to_socket_addrs()?;
let stream = std::net::TcpStream::connect_timeout(
&addrs.into_iter().nth(0).unwrap(),
self.connect_timeout,
)?;
stream.set_nodelay(self.tcp_nodelay)?;
let mut stream = BlockingFramedInner::new(stream, Codec::new(Mode::Uncompressed));
if let Some(hostname) = &self.relay_select_host {
let packet = Sel {
reqi: RequestId(1),
hname: hostname.to_string(),
admin: self
.relay_admin_password
.as_deref()
.unwrap_or("")
.to_owned(),
spec: self
.relay_spectator_password
.as_deref()
.unwrap_or("")
.to_owned(),
};
stream.write(packet.into())?;
}
Ok(BlockingFramed::Tcp(stream))
},
}
}
#[cfg(feature = "tokio")]
pub async fn connect_async(&self) -> Result<AsyncFramed> {
use tokio::{io::BufWriter, time::timeout};
match self.proto {
Proto::Tcp => {
let stream = timeout(
self.connect_timeout,
tokio::net::TcpStream::connect(self.remote),
)
.await??;
stream.set_nodelay(self.tcp_nodelay)?;
let stream = BufWriter::new(stream);
let mut stream = AsyncFramedInner::new(stream, Codec::new(self.mode.clone()));
stream.verify_version(self.verify_version);
stream.handshake(self.isi(), self.handshake_timeout).await?;
Ok(AsyncFramed::BufferedTcp(stream))
},
Proto::Udp => {
let local = self.udp_local_address.unwrap_or("0.0.0.0:0".parse()?);
let stream = tokio::net::UdpSocket::bind(local).await?;
stream.connect(self.remote).await.unwrap();
let mut isi = self.isi();
if self.udp_local_address.is_none() {
isi.udpport = local.port();
}
let mut stream = AsyncFramedInner::new(stream, Codec::new(self.mode.clone()));
stream.verify_version(self.verify_version);
stream.handshake(isi, self.handshake_timeout).await?;
Ok(AsyncFramed::Udp(stream))
},
Proto::Relay => {
let mut stream = self._connect_relay().await?;
if let Some(hostname) = &self.relay_select_host {
let packet = Sel {
reqi: RequestId(1),
hname: hostname.to_string(),
admin: self
.relay_admin_password
.as_deref()
.unwrap_or("")
.to_owned(),
spec: self
.relay_spectator_password
.as_deref()
.unwrap_or("")
.to_owned(),
};
stream.write(packet).await?;
}
Ok(stream)
},
}
}
#[cfg(feature = "tokio")]
async fn _connect_relay(&self) -> Result<AsyncFramed> {
use tokio::time::timeout;
#[cfg(feature = "websocket")]
if self.relay_websocket {
let stream = timeout(
self.connect_timeout,
crate::net::tokio_impl::websocket::connect_to_relay(self.tcp_nodelay),
)
.await??;
let mut inner = AsyncFramedInner::new(stream, Codec::new(Mode::Uncompressed));
inner.verify_version(self.verify_version);
return Ok(AsyncFramed::WebSocket(inner));
}
let stream = timeout(
self.connect_timeout,
tokio::net::TcpStream::connect(crate::LFSW_RELAY_ADDR),
)
.await??;
stream.set_nodelay(self.tcp_nodelay)?;
let mut inner = AsyncFramedInner::new(stream, Codec::new(Mode::Uncompressed));
inner.verify_version(self.verify_version);
Ok(AsyncFramed::Tcp(inner))
}
}