Skip to main content

base_endpoint/
host_endpoint.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::{AddressEndpoint, BindEndpoint, BindOptions, CompositeEndpoint, ConnectEndpoint};
5use base_scheduler::{
6    AddressHints, AddressInfoCollection, AddressInfoView, AddressResolution, AddressService,
7    Cancellation, OperationResult, SocketBind, SocketClose, SocketConnect, SocketDomain,
8    SocketListen, SocketOpen, SocketProtocol, SocketType,
9};
10use std::fmt;
11
12/// A TCP endpoint resolved afresh for each connect or bind. Connection attempts follow resolver order; binding creates a listener for every resolved address. There is no cache or background resolver task.
13#[derive(Clone, Debug, Eq, PartialEq)]
14pub struct HostEndpoint {
15    hostname: Option<String>,
16    port: u16,
17    domain: Option<SocketDomain>,
18}
19
20impl HostEndpoint {
21    /// Describe a TCP hostname and numeric port. Pass an unbracketed hostname or IP literal without a port.
22    pub fn new(hostname: impl Into<String>, port: u16) -> Self {
23        Self {
24            hostname: Some(hostname.into()),
25            port,
26            domain: None,
27        }
28    }
29
30    /// Describe all local interfaces for binding. When connecting, an absent hostname selects loopback addresses according to the resolver's contract.
31    pub const fn wildcard(port: u16) -> Self {
32        Self {
33            hostname: None,
34            port,
35            domain: None,
36        }
37    }
38
39    /// Restrict resolution to the selected IP family. By default both families are eligible.
40    pub fn with_domain(mut self, domain: SocketDomain) -> Self {
41        self.domain = Some(domain);
42        self
43    }
44
45    /// The configured hostname, or `None` for wildcard binding and loopback connecting.
46    pub fn hostname(&self) -> Option<&str> {
47        self.hostname.as_deref()
48    }
49
50    /// The configured port.
51    pub const fn port(&self) -> u16 {
52        self.port
53    }
54
55    /// Resolve to numeric TCP endpoints, preserving order and duplicates. `passive` selects wildcard addresses for an absent hostname; otherwise an absent hostname selects loopback. Empty results are preserved here and rejected by connect/bind.
56    pub async fn resolve<S: AddressResolution + ?Sized>(
57        &self,
58        scheduler: &S,
59        passive: bool,
60        cancellation: &Cancellation,
61    ) -> OperationResult<Vec<AddressEndpoint>> {
62        cancellation.check()?;
63        let addresses = scheduler
64            .address_resolve(
65                self.hostname(),
66                AddressService::Port(self.port),
67                AddressHints {
68                    domain: self.domain,
69                    socket_type: Some(SocketType::Stream),
70                    protocol: Some(SocketProtocol::Tcp),
71                    passive,
72                    ..AddressHints::default()
73                },
74                cancellation,
75            )
76            .await?;
77        Ok(addresses
78            .entries()
79            .map(|entry| AddressEndpoint::new(entry.address()))
80            .collect())
81    }
82}
83
84impl fmt::Display for HostEndpoint {
85    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86        let hostname = self.hostname().unwrap_or("*");
87        if hostname.contains(':') {
88            write!(formatter, "tcp://[{hostname}]:{}", self.port)
89        } else {
90            write!(formatter, "tcp://{hostname}:{}", self.port)
91        }
92    }
93}
94
95impl<S> ConnectEndpoint<S> for HostEndpoint
96where
97    S: AddressResolution + SocketOpen + SocketConnect + SocketClose + ?Sized,
98{
99    type Connection = S::Socket;
100
101    async fn connect(
102        &self,
103        scheduler: &S,
104        cancellation: &Cancellation,
105    ) -> OperationResult<S::Socket> {
106        CompositeEndpoint::new(self.resolve(scheduler, false, cancellation).await?)
107            .connect(scheduler, cancellation)
108            .await
109    }
110}
111
112impl<S> BindEndpoint<S> for HostEndpoint
113where
114    S: AddressResolution + SocketOpen + SocketBind + SocketListen + SocketClose + ?Sized,
115{
116    type Listener = S::Socket;
117
118    async fn bind(
119        &self,
120        scheduler: &S,
121        options: BindOptions,
122        cancellation: &Cancellation,
123    ) -> OperationResult<Vec<S::Socket>> {
124        CompositeEndpoint::new(self.resolve(scheduler, true, cancellation).await?)
125            .bind(scheduler, options, cancellation)
126            .await
127    }
128}