use std::fmt;
use std::hash::{Hash, Hasher};
use std::net::{IpAddr, SocketAddr};
use crate::error::SteamError;
use crate::networking::steam3::protocol_type::ProtocolTypes;
#[derive(Clone, Debug)]
pub enum EndPoint {
Ip(SocketAddr),
Dns {
host: String,
port: u16,
address: SocketAddr,
},
}
impl EndPoint {
pub fn dns(host: impl Into<String>, port: u16, address: SocketAddr) -> Self {
EndPoint::Dns {
host: host.into(),
port,
address,
}
}
pub fn socket_addr(&self) -> SocketAddr {
match self {
EndPoint::Ip(address) => *address,
EndPoint::Dns { address, .. } => *address,
}
}
pub fn host(&self) -> String {
match self {
EndPoint::Ip(address) => address.ip().to_string(),
EndPoint::Dns { host, .. } => host.clone(),
}
}
pub fn port(&self) -> u16 {
match self {
EndPoint::Ip(address) => address.port(),
EndPoint::Dns { port, .. } => *port,
}
}
}
impl PartialEq for EndPoint {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(EndPoint::Ip(left), EndPoint::Ip(right)) => left == right,
(
EndPoint::Dns {
host: left_host,
port: left_port,
..
},
EndPoint::Dns {
host: right_host,
port: right_port,
..
},
) => left_port == right_port && left_host.eq_ignore_ascii_case(right_host),
_ => false,
}
}
}
impl Eq for EndPoint {}
impl Hash for EndPoint {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
EndPoint::Ip(address) => {
0u8.hash(state);
address.hash(state);
}
EndPoint::Dns { host, port, .. } => {
1u8.hash(state);
port.hash(state);
host.to_ascii_lowercase().hash(state);
}
}
}
}
impl fmt::Display for EndPoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EndPoint::Ip(address) => write!(f, "{address}"),
EndPoint::Dns { host, port, .. } => write!(f, "{host}:{port}"),
}
}
}
impl From<SocketAddr> for EndPoint {
fn from(address: SocketAddr) -> Self {
EndPoint::Ip(address)
}
}
#[derive(Clone, Debug)]
pub struct ServerRecord {
end_point: EndPoint,
protocol_types: ProtocolTypes,
}
impl ServerRecord {
pub fn new(end_point: EndPoint, protocol_types: ProtocolTypes) -> Self {
ServerRecord {
end_point,
protocol_types,
}
}
pub fn end_point(&self) -> SocketAddr {
self.end_point.socket_addr()
}
pub fn endpoint(&self) -> &EndPoint {
&self.end_point
}
pub fn is_dns(&self) -> bool {
matches!(self.end_point, EndPoint::Dns { .. })
}
pub fn protocol_types(&self) -> ProtocolTypes {
self.protocol_types
}
pub fn get_host(&self) -> String {
self.end_point.host()
}
pub fn get_port(&self) -> u16 {
self.end_point.port()
}
pub async fn create_server(
host: &str,
port: u16,
protocol_types: ProtocolTypes,
) -> Result<Self, SteamError> {
match host.parse::<IpAddr>() {
Ok(address) => Ok(ServerRecord::new(
EndPoint::Ip(SocketAddr::new(address, port)),
protocol_types,
)),
Err(_) => {
let address = resolve_host(host, port).await?;
Ok(ServerRecord::new(
EndPoint::dns(host, port, address),
protocol_types,
))
}
}
}
pub fn create_socket_server(end_point: SocketAddr) -> Self {
ServerRecord::new(EndPoint::Ip(end_point), tcp_udp_protocol_types())
}
pub fn try_create_socket_server(address: &str) -> Option<Self> {
let end_point = address.parse::<SocketAddr>().ok()?;
if end_point.port() == 0 {
return None;
}
Some(ServerRecord::new(
EndPoint::Ip(end_point),
tcp_udp_protocol_types(),
))
}
pub async fn create_dns_socket_server(address: &str) -> Result<Self, SteamError> {
ServerRecord::create_server_from_dns(address, tcp_udp_protocol_types()).await
}
pub async fn create_websocket_server(address: &str) -> Result<Self, SteamError> {
ServerRecord::create_server_from_dns(address, ProtocolTypes::WEB_SOCKET).await
}
async fn create_server_from_dns(
address: &str,
protocol_types: ProtocolTypes,
) -> Result<Self, SteamError> {
const DEFAULT_PORT: u16 = 443;
let (host, port) = match address.find(':') {
Some(index) => {
let host = &address[..index];
let port = &address[index + 1..];
let port = port.parse::<u16>().map_err(|_| {
SteamError::InvalidArgument(
"Port number must be a valid integer value.".to_string(),
)
})?;
(host, port)
}
None => (address, DEFAULT_PORT),
};
let resolved = resolve_host(host, port).await?;
Ok(ServerRecord::new(
EndPoint::dns(host, port, resolved),
protocol_types,
))
}
}
impl PartialEq for ServerRecord {
fn eq(&self, other: &Self) -> bool {
self.end_point == other.end_point && self.protocol_types == other.protocol_types
}
}
impl Eq for ServerRecord {}
impl Hash for ServerRecord {
fn hash<H: Hasher>(&self, state: &mut H) {
self.end_point.hash(state);
self.protocol_types.hash(state);
}
}
impl fmt::Display for ServerRecord {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({:?})", self.end_point, self.protocol_types)
}
}
async fn resolve_host(host: &str, port: u16) -> Result<SocketAddr, SteamError> {
let mut addresses = tokio::net::lookup_host((host, port))
.await
.map_err(|error| SteamError::Connection(format!("Failed to resolve {host}: {error}")))?;
addresses.next().ok_or_else(|| {
SteamError::Connection(format!("Failed to resolve {host}: no addresses returned"))
})
}
pub(crate) fn tcp_udp_protocol_types() -> ProtocolTypes {
ProtocolTypes(ProtocolTypes::TCP.raw() | ProtocolTypes::UDP.raw())
}