Skip to main content

steamkit/steam/discovery/
server_record.rs

1//! What's needed to connect to a CM server: an endpoint and the protocols it
2//! supports.
3//!
4//! Two records are equal only if both the endpoint and the protocols match.
5//!
6//! An endpoint is either an IP address and port, or a host name and port. For
7//! host names the name is kept, since a WebSocket connection needs it for TLS,
8//! along with the address it resolved to. Connections take a `SocketAddr`, so
9//! host names are resolved when the record is created; that's why the
10//! constructors taking a host name are `async`.
11
12use std::fmt;
13use std::hash::{Hash, Hasher};
14use std::net::{IpAddr, SocketAddr};
15
16use crate::error::SteamError;
17use crate::networking::steam3::protocol_type::ProtocolTypes;
18
19/// The endpoint of a server.
20#[derive(Clone, Debug)]
21pub enum EndPoint {
22    /// An IP address and port.
23    Ip(SocketAddr),
24    /// A host name and port, plus the address it resolved to.
25    Dns {
26        /// The host name of the endpoint.
27        host: String,
28        /// The port of the endpoint.
29        port: u16,
30        /// The address the host name resolved to.
31        address: SocketAddr,
32    },
33}
34
35impl EndPoint {
36    /// Creates a host name endpoint from the name, the port and the address it
37    /// resolved to.
38    pub fn dns(host: impl Into<String>, port: u16, address: SocketAddr) -> Self {
39        EndPoint::Dns {
40            host: host.into(),
41            port,
42            address,
43        }
44    }
45
46    /// The socket address of the endpoint.
47    pub fn socket_addr(&self) -> SocketAddr {
48        match self {
49            EndPoint::Ip(address) => *address,
50            EndPoint::Dns { address, .. } => *address,
51        }
52    }
53
54    /// The host: an IP address or a host name.
55    pub fn host(&self) -> String {
56        match self {
57            EndPoint::Ip(address) => address.ip().to_string(),
58            EndPoint::Dns { host, .. } => host.clone(),
59        }
60    }
61
62    /// The port.
63    pub fn port(&self) -> u16 {
64        match self {
65            EndPoint::Ip(address) => address.port(),
66            EndPoint::Dns { port, .. } => *port,
67        }
68    }
69}
70
71impl PartialEq for EndPoint {
72    /// Equal if both are the same kind and describe the same address. Host
73    /// names are compared case-insensitively.
74    fn eq(&self, other: &Self) -> bool {
75        match (self, other) {
76            (EndPoint::Ip(left), EndPoint::Ip(right)) => left == right,
77            (
78                EndPoint::Dns {
79                    host: left_host,
80                    port: left_port,
81                    ..
82                },
83                EndPoint::Dns {
84                    host: right_host,
85                    port: right_port,
86                    ..
87                },
88            ) => left_port == right_port && left_host.eq_ignore_ascii_case(right_host),
89            _ => false,
90        }
91    }
92}
93
94impl Eq for EndPoint {}
95
96impl Hash for EndPoint {
97    fn hash<H: Hasher>(&self, state: &mut H) {
98        match self {
99            EndPoint::Ip(address) => {
100                0u8.hash(state);
101                address.hash(state);
102            }
103            EndPoint::Dns { host, port, .. } => {
104                1u8.hash(state);
105                port.hash(state);
106                host.to_ascii_lowercase().hash(state);
107            }
108        }
109    }
110}
111
112impl fmt::Display for EndPoint {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            EndPoint::Ip(address) => write!(f, "{address}"),
116            EndPoint::Dns { host, port, .. } => write!(f, "{host}:{port}"),
117        }
118    }
119}
120
121impl From<SocketAddr> for EndPoint {
122    fn from(address: SocketAddr) -> Self {
123        EndPoint::Ip(address)
124    }
125}
126
127/// What's needed to connect to a CM server.
128#[derive(Clone, Debug)]
129pub struct ServerRecord {
130    end_point: EndPoint,
131    protocol_types: ProtocolTypes,
132}
133
134impl ServerRecord {
135    /// Creates a server record for the given endpoint and protocol types.
136    pub fn new(end_point: EndPoint, protocol_types: ProtocolTypes) -> Self {
137        ServerRecord {
138            end_point,
139            protocol_types,
140        }
141    }
142
143    /// The address to connect to. For the host name, if there is one, use
144    /// [`ServerRecord::endpoint`].
145    pub fn end_point(&self) -> SocketAddr {
146        self.end_point.socket_addr()
147    }
148
149    /// The full endpoint, including the host name if there is one.
150    pub fn endpoint(&self) -> &EndPoint {
151        &self.end_point
152    }
153
154    /// Whether the endpoint is a host name rather than an IP address.
155    pub fn is_dns(&self) -> bool {
156        matches!(self.end_point, EndPoint::Dns { .. })
157    }
158
159    /// The protocols that can be used to talk to this server.
160    pub fn protocol_types(&self) -> ProtocolTypes {
161        self.protocol_types
162    }
163
164    /// The host of the endpoint: an IP address or a host name.
165    pub fn get_host(&self) -> String {
166        self.end_point.host()
167    }
168
169    /// The port of the endpoint.
170    pub fn get_port(&self) -> u16 {
171        self.end_point.port()
172    }
173
174    /// Creates a record for the given host and port. A host name is resolved
175    /// to an address.
176    pub async fn create_server(
177        host: &str,
178        port: u16,
179        protocol_types: ProtocolTypes,
180    ) -> Result<Self, SteamError> {
181        match host.parse::<IpAddr>() {
182            Ok(address) => Ok(ServerRecord::new(
183                EndPoint::Ip(SocketAddr::new(address, port)),
184                protocol_types,
185            )),
186            Err(_) => {
187                let address = resolve_host(host, port).await?;
188                Ok(ServerRecord::new(
189                    EndPoint::dns(host, port, address),
190                    protocol_types,
191                ))
192            }
193        }
194    }
195
196    /// Creates a TCP/UDP server record for an IP endpoint.
197    pub fn create_socket_server(end_point: SocketAddr) -> Self {
198        ServerRecord::new(EndPoint::Ip(end_point), tcp_udp_protocol_types())
199    }
200
201    /// Creates a TCP/UDP server record from an address string. Returns `None`
202    /// if it can't be parsed.
203    pub fn try_create_socket_server(address: &str) -> Option<Self> {
204        let end_point = address.parse::<SocketAddr>().ok()?;
205        if end_point.port() == 0 {
206            return None;
207        }
208
209        Some(ServerRecord::new(
210            EndPoint::Ip(end_point),
211            tcp_udp_protocol_types(),
212        ))
213    }
214
215    /// Creates a TCP/UDP server record from a "host:port" address. The port
216    /// defaults to 443.
217    pub async fn create_dns_socket_server(address: &str) -> Result<Self, SteamError> {
218        ServerRecord::create_server_from_dns(address, tcp_udp_protocol_types()).await
219    }
220
221    /// Creates a WebSocket server record from a "host:port" address. The port
222    /// defaults to 443.
223    pub async fn create_websocket_server(address: &str) -> Result<Self, SteamError> {
224        ServerRecord::create_server_from_dns(address, ProtocolTypes::WEB_SOCKET).await
225    }
226
227    /// Creates a server record from a "host:port" address.
228    async fn create_server_from_dns(
229        address: &str,
230        protocol_types: ProtocolTypes,
231    ) -> Result<Self, SteamError> {
232        const DEFAULT_PORT: u16 = 443;
233
234        let (host, port) = match address.find(':') {
235            Some(index) => {
236                let host = &address[..index];
237                let port = &address[index + 1..];
238                let port = port.parse::<u16>().map_err(|_| {
239                    SteamError::InvalidArgument(
240                        "Port number must be a valid integer value.".to_string(),
241                    )
242                })?;
243                (host, port)
244            }
245            None => (address, DEFAULT_PORT),
246        };
247
248        let resolved = resolve_host(host, port).await?;
249
250        Ok(ServerRecord::new(
251            EndPoint::dns(host, port, resolved),
252            protocol_types,
253        ))
254    }
255}
256
257impl PartialEq for ServerRecord {
258    /// Equal when both the endpoints and the protocol types match.
259    fn eq(&self, other: &Self) -> bool {
260        self.end_point == other.end_point && self.protocol_types == other.protocol_types
261    }
262}
263
264impl Eq for ServerRecord {}
265
266impl Hash for ServerRecord {
267    fn hash<H: Hasher>(&self, state: &mut H) {
268        self.end_point.hash(state);
269        self.protocol_types.hash(state);
270    }
271}
272
273impl fmt::Display for ServerRecord {
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        write!(f, "{} ({:?})", self.end_point, self.protocol_types)
276    }
277}
278
279/// Resolves a host name to a socket address.
280async fn resolve_host(host: &str, port: u16) -> Result<SocketAddr, SteamError> {
281    let mut addresses = tokio::net::lookup_host((host, port))
282        .await
283        .map_err(|error| SteamError::Connection(format!("Failed to resolve {host}: {error}")))?;
284
285    addresses.next().ok_or_else(|| {
286        SteamError::Connection(format!("Failed to resolve {host}: no addresses returned"))
287    })
288}
289
290/// `Tcp | Udp`.
291pub(crate) fn tcp_udp_protocol_types() -> ProtocolTypes {
292    ProtocolTypes(ProtocolTypes::TCP.raw() | ProtocolTypes::UDP.raw())
293}