Skip to main content

microsandbox_protocol_client/
transport.rs

1//! Exclusively owned byte transports and repeatable dialers.
2
3use std::future::Future;
4use std::path::{Path, PathBuf};
5use std::pin::Pin;
6
7use tokio::io::{AsyncRead, AsyncWrite};
8use tokio::time::Instant;
9
10use crate::{ClientError, ClientResult, ErrorKind};
11
12//--------------------------------------------------------------------------------------------------
13// Types
14//--------------------------------------------------------------------------------------------------
15
16/// Owned transport; reader and writer progress independently after splitting.
17pub trait ByteTransport: AsyncRead + AsyncWrite + Unpin + Send + 'static {}
18
19/// Erased byte transport for external protocol and connector implementations.
20pub type BoxTransport = Box<dyn ByteTransport>;
21
22/// Owned Send future used at the public protocol/connector boundary.
23pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
24
25/// Opens independent transports, without negotiation or application parsing.
26pub trait Connector: Send + Sync {
27    /// Dial before the remaining absolute setup deadline; dropping cancels dial.
28    fn connect(&self, deadline: Instant) -> BoxFuture<'_, ClientResult<BoxTransport>>;
29}
30
31/// Native local endpoint supplied by the caller's existing endpoint helper.
32#[derive(Debug, Clone)]
33pub struct LocalConnector {
34    path: PathBuf,
35}
36
37//--------------------------------------------------------------------------------------------------
38// Methods
39//--------------------------------------------------------------------------------------------------
40
41impl LocalConnector {
42    /// Store an endpoint without altering Unix or Windows naming.
43    pub fn new(path: impl AsRef<Path>) -> Self {
44        Self {
45            path: path.as_ref().to_owned(),
46        }
47    }
48
49    /// The exact endpoint passed by the caller.
50    pub fn path(&self) -> &Path {
51        &self.path
52    }
53}
54
55//--------------------------------------------------------------------------------------------------
56// Trait Implementations
57//--------------------------------------------------------------------------------------------------
58
59impl<T: AsyncRead + AsyncWrite + Unpin + Send + 'static> ByteTransport for T {}
60
61impl Connector for LocalConnector {
62    fn connect(&self, deadline: Instant) -> BoxFuture<'_, ClientResult<BoxTransport>> {
63        Box::pin(async move {
64            tokio::time::timeout_at(deadline, async {
65                #[cfg(all(unix, feature = "uds"))]
66                {
67                    let stream = tokio::net::UnixStream::connect(&self.path).await?;
68                    Ok(Box::new(stream) as BoxTransport)
69                }
70                #[cfg(all(windows, feature = "named-pipe"))]
71                {
72                    loop {
73                        match tokio::net::windows::named_pipe::ClientOptions::new().open(&self.path)
74                        {
75                            Ok(stream) => return Ok(Box::new(stream) as BoxTransport),
76                            Err(error)
77                                if error.kind() == std::io::ErrorKind::NotFound
78                                    || error.raw_os_error() == Some(231) =>
79                            {
80                                tokio::time::sleep(std::time::Duration::from_millis(10)).await;
81                            }
82                            Err(error) => return Err(ClientError::from(error)),
83                        }
84                    }
85                }
86                #[cfg(not(any(all(unix, feature = "uds"), all(windows, feature = "named-pipe"))))]
87                Err(ClientError::new(ErrorKind::UnsupportedOperation))
88            })
89            .await
90            .map_err(|_| ClientError::new(ErrorKind::Timeout))?
91        })
92    }
93}