feat: Add connect timeout to TimeoutSettings (#158)

Because TcpSocket connects in Socket::new TimeoutSettings are now
required for Socket::new. Since we already have TimeoutSettings there
Sockets are now expected to apply timeout settings in Socket::new.
This commit is contained in:
Tom 2023-11-22 10:40:22 +00:00 committed by GitHub
parent 7416d54b14
commit e3bdbc2a41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 97 additions and 42 deletions

View file

@ -28,6 +28,10 @@ Game:
Protocols: Protocols:
- Valve: Removed `SteamApp` due to it not being really useful at all, replaced all instances with `Engine`. - Valve: Removed `SteamApp` due to it not being really useful at all, replaced all instances with `Engine`.
Query:
- Added a connection timeout to TimeoutSettings (at the moment this only applies to TCP)
- Sockets are now expected to apply timeout settings in new()
# 0.4.1 - 13/10/2023 # 0.4.1 - 13/10/2023
### Changes: ### Changes:
Game: Game:

View file

@ -45,6 +45,7 @@ fn main() {
let timeout_settings = TimeoutSettings::new( let timeout_settings = TimeoutSettings::new(
TimeoutSettings::default().get_read(), TimeoutSettings::default().get_read(),
TimeoutSettings::default().get_write(), TimeoutSettings::default().get_write(),
TimeoutSettings::default().get_connect(),
2, 2,
) )
.unwrap(); .unwrap();
@ -90,6 +91,7 @@ mod test {
fn test_game(game_name: &str) { fn test_game(game_name: &str) {
let timeout_settings = Some( let timeout_settings = Some(
TimeoutSettings::new( TimeoutSettings::new(
Some(Duration::from_nanos(1)),
Some(Duration::from_nanos(1)), Some(Duration::from_nanos(1)),
Some(Duration::from_nanos(1)), Some(Duration::from_nanos(1)),
0, 0,

View file

@ -15,8 +15,15 @@ fn main() {
let read_timeout = Duration::from_secs(2); let read_timeout = Duration::from_secs(2);
let write_timeout = Duration::from_secs(3); let write_timeout = Duration::from_secs(3);
let connect_timeout = Duration::from_secs(4);
let retries = 1; // does another request if the first one fails. let retries = 1; // does another request if the first one fails.
let timeout_settings = TimeoutSettings::new(Some(read_timeout), Some(write_timeout), retries).unwrap(); let timeout_settings = TimeoutSettings::new(
Some(read_timeout),
Some(write_timeout),
Some(connect_timeout),
retries,
)
.unwrap();
let response = valve::query( let response = valve::query(
address, address,

View file

@ -21,8 +21,7 @@ pub struct Bedrock {
impl Bedrock { impl Bedrock {
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> { fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = UdpSocket::new(address)?; let socket = UdpSocket::new(address, &timeout_settings)?;
socket.apply_timeout(&timeout_settings)?;
let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings); let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings);
Ok(Self { Ok(Self {

View file

@ -24,8 +24,7 @@ impl Java {
timeout_settings: Option<TimeoutSettings>, timeout_settings: Option<TimeoutSettings>,
request_settings: Option<RequestSettings>, request_settings: Option<RequestSettings>,
) -> GDResult<Self> { ) -> GDResult<Self> {
let socket = TcpSocket::new(address)?; let socket = TcpSocket::new(address, &timeout_settings)?;
socket.apply_timeout(&timeout_settings)?;
let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings); let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings);
Ok(Self { Ok(Self {

View file

@ -19,8 +19,7 @@ pub struct LegacyV1_4 {
impl LegacyV1_4 { impl LegacyV1_4 {
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> { fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address)?; let socket = TcpSocket::new(address, &timeout_settings)?;
socket.apply_timeout(&timeout_settings)?;
let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings); let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings);
Ok(Self { Ok(Self {

View file

@ -18,8 +18,7 @@ pub struct LegacyV1_6 {
impl LegacyV1_6 { impl LegacyV1_6 {
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> { fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address)?; let socket = TcpSocket::new(address, &timeout_settings)?;
socket.apply_timeout(&timeout_settings)?;
let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings); let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings);
Ok(Self { Ok(Self {

View file

@ -19,8 +19,7 @@ pub struct LegacyVB1_8 {
impl LegacyVB1_8 { impl LegacyVB1_8 {
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> { fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = TcpSocket::new(address)?; let socket = TcpSocket::new(address, &timeout_settings)?;
socket.apply_timeout(&timeout_settings)?;
let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings); let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings);
Ok(Self { Ok(Self {

View file

@ -24,8 +24,7 @@ fn get_server_values(
address: &SocketAddr, address: &SocketAddr,
timeout_settings: &Option<TimeoutSettings>, timeout_settings: &Option<TimeoutSettings>,
) -> GDResult<HashMap<String, String>> { ) -> GDResult<HashMap<String, String>> {
let mut socket = UdpSocket::new(address)?; let mut socket = UdpSocket::new(address, timeout_settings)?;
socket.apply_timeout(timeout_settings)?;
retry_on_timeout( retry_on_timeout(
TimeoutSettings::get_retries_or_default(timeout_settings), TimeoutSettings::get_retries_or_default(timeout_settings),
move || get_server_values_impl(&mut socket), move || get_server_values_impl(&mut socket),

View file

@ -52,9 +52,8 @@ const DEFAULT_PAYLOAD: [u8; 4] = [0xFF, 0xFF, 0xFF, 0x01];
impl GameSpy3 { impl GameSpy3 {
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> { fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = UdpSocket::new(address)?; let socket = UdpSocket::new(address, &timeout_settings)?;
let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings); let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings);
socket.apply_timeout(&timeout_settings)?;
Ok(Self { Ok(Self {
socket, socket,
@ -70,9 +69,8 @@ impl GameSpy3 {
payload: [u8; 4], payload: [u8; 4],
single_packets: bool, single_packets: bool,
) -> GDResult<Self> { ) -> GDResult<Self> {
let socket = UdpSocket::new(address)?; let socket = UdpSocket::new(address, &timeout_settings)?;
let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings); let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings);
socket.apply_timeout(&timeout_settings)?;
Ok(Self { Ok(Self {
socket, socket,

View file

@ -79,9 +79,8 @@ fn data_as_table(data: &mut Buffer<BigEndian>) -> GDResult<(HashMap<String, Vec<
impl GameSpy2 { impl GameSpy2 {
fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> { fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = UdpSocket::new(address)?; let socket = UdpSocket::new(address, &timeout_settings)?;
let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings); let retry_count = TimeoutSettings::get_retries_or_default(&timeout_settings);
socket.apply_timeout(&timeout_settings)?;
Ok(Self { Ok(Self {
socket, socket,

View file

@ -25,8 +25,7 @@ fn get_data<Client: QuakeClient>(
address: &SocketAddr, address: &SocketAddr,
timeout_settings: &Option<TimeoutSettings>, timeout_settings: &Option<TimeoutSettings>,
) -> GDResult<Vec<u8>> { ) -> GDResult<Vec<u8>> {
let mut socket = UdpSocket::new(address)?; let mut socket = UdpSocket::new(address, timeout_settings)?;
socket.apply_timeout(timeout_settings)?;
retry_on_timeout( retry_on_timeout(
TimeoutSettings::get_retries_or_default(timeout_settings), TimeoutSettings::get_retries_or_default(timeout_settings),
move || get_data_impl::<Client>(&mut socket), move || get_data_impl::<Client>(&mut socket),

View file

@ -150,6 +150,7 @@ pub struct CommonPlayerJson<'a> {
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TimeoutSettings { pub struct TimeoutSettings {
connect: Option<Duration>,
read: Option<Duration>, read: Option<Duration>,
write: Option<Duration>, write: Option<Duration>,
retries: usize, retries: usize,
@ -164,22 +165,34 @@ impl TimeoutSettings {
/// "1" will try the request again once if it fails. /// "1" will try the request again once if it fails.
/// The retry count is per-request so for multi-request queries (valve) if a /// The retry count is per-request so for multi-request queries (valve) if a
/// single part fails that part can be retried up to `retries` times. /// single part fails that part can be retried up to `retries` times.
pub fn new(read: Option<Duration>, write: Option<Duration>, retries: usize) -> GDResult<Self> { pub fn new(
read: Option<Duration>,
write: Option<Duration>,
connect: Option<Duration>,
retries: usize,
) -> GDResult<Self> {
if let Some(read_duration) = read { if let Some(read_duration) = read {
if read_duration == Duration::new(0, 0) { if read_duration.is_zero() {
return Err(InvalidInput.context("Read duration must not be 0")); return Err(InvalidInput.context("Read duration must not be 0"));
} }
} }
if let Some(write_duration) = write { if let Some(write_duration) = write {
if write_duration == Duration::new(0, 0) { if write_duration.is_zero() {
return Err(InvalidInput.context("Write duration must not be 0")); return Err(InvalidInput.context("Write duration must not be 0"));
} }
} }
if let Some(connect_duration) = connect {
if connect_duration.is_zero() {
return Err(InvalidInput.context("Connect duration must not be 0"));
}
}
Ok(Self { Ok(Self {
read, read,
write, write,
connect,
retries, retries,
}) })
} }
@ -190,6 +203,9 @@ impl TimeoutSettings {
/// Get the write timeout. /// Get the write timeout.
pub const fn get_write(&self) -> Option<Duration> { self.write } pub const fn get_write(&self) -> Option<Duration> { self.write }
/// Get the connect timeout.
pub const fn get_connect(&self) -> Option<Duration> { self.connect }
/// Get number of retries /// Get number of retries
pub const fn get_retries(&self) -> usize { self.retries } pub const fn get_retries(&self) -> usize { self.retries }
@ -216,11 +232,21 @@ impl TimeoutSettings {
} }
} }
/// Get the connect duration given timeout settings or get the default.
pub const fn get_connect_or_default(timeout_settings: &Option<TimeoutSettings>) -> Option<Duration> {
if let Some(timeout_settings) = timeout_settings {
timeout_settings.get_connect()
} else {
TimeoutSettings::const_default().get_connect()
}
}
/// Default values are 4 seconds for both read and write, no retries. /// Default values are 4 seconds for both read and write, no retries.
pub const fn const_default() -> Self { pub const fn const_default() -> Self {
Self { Self {
read: Some(Duration::from_secs(4)), read: Some(Duration::from_secs(4)),
write: Some(Duration::from_secs(4)), write: Some(Duration::from_secs(4)),
connect: Some(Duration::from_secs(4)),
retries: 0, retries: 0,
} }
} }
@ -320,9 +346,15 @@ mod tests {
// Define valid read and write durations // Define valid read and write durations
let read_duration = Duration::from_secs(1); let read_duration = Duration::from_secs(1);
let write_duration = Duration::from_secs(2); let write_duration = Duration::from_secs(2);
let connect_duration = Duration::from_secs(3);
// Create new TimeoutSettings with the valid durations // Create new TimeoutSettings with the valid durations
let timeout_settings = TimeoutSettings::new(Some(read_duration), Some(write_duration), 0)?; let timeout_settings = TimeoutSettings::new(
Some(read_duration),
Some(write_duration),
Some(connect_duration),
0,
)?;
// Verify that the get_read and get_write methods return the expected values // Verify that the get_read and get_write methods return the expected values
assert_eq!(timeout_settings.get_read(), Some(read_duration)); assert_eq!(timeout_settings.get_read(), Some(read_duration));
@ -337,10 +369,16 @@ mod tests {
// Define a zero read duration and a valid write duration // Define a zero read duration and a valid write duration
let read_duration = Duration::new(0, 0); let read_duration = Duration::new(0, 0);
let write_duration = Duration::from_secs(2); let write_duration = Duration::from_secs(2);
let connect_duration = Duration::from_secs(3);
// Try to create new TimeoutSettings with the zero read duration (this should // Try to create new TimeoutSettings with the zero read duration (this should
// fail) // fail)
let result = TimeoutSettings::new(Some(read_duration), Some(write_duration), 0); let result = TimeoutSettings::new(
Some(read_duration),
Some(write_duration),
Some(connect_duration),
0,
);
// Verify that the function returned an error and that the error type is // Verify that the function returned an error and that the error type is
// InvalidInput // InvalidInput

View file

@ -32,12 +32,11 @@ pub(crate) struct Unreal2Protocol {
impl Unreal2Protocol { impl Unreal2Protocol {
pub fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> { pub fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = UdpSocket::new(address)?; let socket = UdpSocket::new(address, &timeout_settings)?;
let retry_count = timeout_settings let retry_count = timeout_settings
.as_ref() .as_ref()
.map(|t| t.get_retries()) .map(|t| t.get_retries())
.unwrap_or_else(|| TimeoutSettings::default().get_retries()); .unwrap_or_else(|| TimeoutSettings::default().get_retries());
socket.apply_timeout(&timeout_settings)?;
Ok(Self { Ok(Self {
socket, socket,

View file

@ -126,12 +126,11 @@ static PACKET_SIZE: usize = 6144;
impl ValveProtocol { impl ValveProtocol {
pub fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> { pub fn new(address: &SocketAddr, timeout_settings: Option<TimeoutSettings>) -> GDResult<Self> {
let socket = UdpSocket::new(address)?; let socket = UdpSocket::new(address, &timeout_settings)?;
let retry_count = timeout_settings let retry_count = timeout_settings
.as_ref() .as_ref()
.map(|t| t.get_retries()) .map(|t| t.get_retries())
.unwrap_or_else(|| TimeoutSettings::default().get_retries()); .unwrap_or_else(|| TimeoutSettings::default().get_retries());
socket.apply_timeout(&timeout_settings)?;
Ok(Self { Ok(Self {
socket, socket,

View file

@ -49,8 +49,7 @@ pub struct ValveMasterServer {
impl ValveMasterServer { impl ValveMasterServer {
/// Construct a new struct. /// Construct a new struct.
pub fn new(master_address: &SocketAddr) -> GDResult<Self> { pub fn new(master_address: &SocketAddr) -> GDResult<Self> {
let socket = UdpSocket::new(master_address)?; let socket = UdpSocket::new(master_address, &None)?;
socket.apply_timeout(&None)?;
Ok(Self { socket }) Ok(Self { socket })
} }

View file

@ -13,7 +13,10 @@ use std::{
const DEFAULT_PACKET_SIZE: usize = 1024; const DEFAULT_PACKET_SIZE: usize = 1024;
pub trait Socket { pub trait Socket {
fn new(address: &SocketAddr) -> GDResult<Self> /// Create a new socket and connect to the remote address (if required).
///
/// Calls [Self::apply_timeout] with the given timeout settings.
fn new(address: &SocketAddr, timeout_settings: &Option<TimeoutSettings>) -> GDResult<Self>
where Self: Sized; where Self: Sized;
fn apply_timeout(&self, timeout_settings: &Option<TimeoutSettings>) -> GDResult<()>; fn apply_timeout(&self, timeout_settings: &Option<TimeoutSettings>) -> GDResult<()>;
@ -30,11 +33,21 @@ pub struct TcpSocket {
} }
impl Socket for TcpSocket { impl Socket for TcpSocket {
fn new(address: &SocketAddr) -> GDResult<Self> { fn new(address: &SocketAddr, timeout_settings: &Option<TimeoutSettings>) -> GDResult<Self> {
Ok(Self { let socket = if let Some(timeout) = TimeoutSettings::get_connect_or_default(timeout_settings) {
socket: net::TcpStream::connect(address).map_err(|e| SocketConnect.context(e))?, net::TcpStream::connect_timeout(address, timeout)
} else {
net::TcpStream::connect(address)
};
let socket = Self {
socket: socket.map_err(|e| SocketConnect.context(e))?,
address: *address, address: *address,
}) };
socket.apply_timeout(timeout_settings)?;
Ok(socket)
} }
fn apply_timeout(&self, timeout_settings: &Option<TimeoutSettings>) -> GDResult<()> { fn apply_timeout(&self, timeout_settings: &Option<TimeoutSettings>) -> GDResult<()> {
@ -68,13 +81,17 @@ pub struct UdpSocket {
} }
impl Socket for UdpSocket { impl Socket for UdpSocket {
fn new(address: &SocketAddr) -> GDResult<Self> { fn new(address: &SocketAddr, timeout_settings: &Option<TimeoutSettings>) -> GDResult<Self> {
let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|e| SocketBind.context(e))?; let socket = net::UdpSocket::bind("0.0.0.0:0").map_err(|e| SocketBind.context(e))?;
Ok(Self { let socket = Self {
socket, socket,
address: *address, address: *address,
}) };
socket.apply_timeout(timeout_settings)?;
Ok(socket)
} }
fn apply_timeout(&self, timeout_settings: &Option<TimeoutSettings>) -> GDResult<()> { fn apply_timeout(&self, timeout_settings: &Option<TimeoutSettings>) -> GDResult<()> {
@ -125,7 +142,7 @@ mod tests {
}); });
// Create a TCP socket and send a message to the server // Create a TCP socket and send a message to the server
let mut socket = TcpSocket::new(&bound_address).unwrap(); let mut socket = TcpSocket::new(&bound_address, &None).unwrap();
let message = b"hello, world!"; let message = b"hello, world!";
socket.send(message).unwrap(); socket.send(message).unwrap();
@ -156,7 +173,7 @@ mod tests {
}); });
// Create a UDP socket and send a message to the server // Create a UDP socket and send a message to the server
let mut socket = UdpSocket::new(&bound_address).unwrap(); let mut socket = UdpSocket::new(&bound_address, &None).unwrap();
let message = b"hello, world!"; let message = b"hello, world!";
socket.send(message).unwrap(); socket.send(message).unwrap();