1use std::future::Future;
6use std::net::SocketAddr;
7use std::pin::Pin;
8use std::sync::Arc;
9
10use tacenta_transport::{
11 AccountConnection, ClientTls, Connection, DirConnection, ProvisionConnection,
12};
13
14pub trait ByteStream: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + Sync {}
16impl<T: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + Sync> ByteStream for T {}
17
18pub type Connecting = Pin<Box<dyn Future<Output = std::io::Result<Box<dyn ByteStream>>> + Send>>;
20
21pub trait Connector: Send + Sync {
28 fn open(&self, service: &str) -> Connecting;
29}
30
31#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
33#[derive(Clone)]
34pub(crate) enum Dialer {
35 Tcp { trust: Option<(String, ClientTls)> },
38 WebSocket { base: String, tls: ClientTls },
42 Custom(Arc<dyn Connector>),
44}
45
46#[cfg(target_arch = "wasm32")]
49fn no_tcp<T>() -> std::io::Result<T> {
50 Err(std::io::Error::new(
51 std::io::ErrorKind::Unsupported,
52 "TCP is not available on this target; use the websocket carriage or a Connector",
53 ))
54}
55
56impl Dialer {
57 pub(crate) fn tcp(trust: Option<(&str, &ClientTls)>) -> Dialer {
58 Dialer::Tcp {
59 trust: trust.map(|(name, tls)| (name.to_owned(), tls.clone())),
60 }
61 }
62
63 #[cfg(not(target_arch = "wasm32"))]
64 fn url(base: &str, service: &str) -> String {
65 format!("{}/{service}", base.trim_end_matches('/'))
66 }
67
68 pub(crate) async fn accounts(&self, addr: SocketAddr) -> std::io::Result<AccountConnection> {
69 match self {
70 Dialer::Custom(connector) => {
71 let stream = connector.open("accounts").await?;
72 AccountConnection::establish(stream).await
73 }
74 #[cfg(not(target_arch = "wasm32"))]
75 Dialer::Tcp {
76 trust: Some((name, tls)),
77 } => AccountConnection::connect_tls(addr, name, tls).await,
78 #[cfg(not(target_arch = "wasm32"))]
79 Dialer::Tcp { trust: None } => AccountConnection::connect(addr).await,
80 #[cfg(not(target_arch = "wasm32"))]
81 Dialer::WebSocket { base, tls } => {
82 AccountConnection::connect_ws(&Dialer::url(base, "accounts"), tls).await
83 }
84 #[cfg(target_arch = "wasm32")]
85 Dialer::Tcp { .. } | Dialer::WebSocket { .. } => {
86 let _ = addr;
87 no_tcp()
88 }
89 }
90 }
91
92 pub(crate) async fn provisioning(
93 &self,
94 addr: SocketAddr,
95 ) -> std::io::Result<ProvisionConnection> {
96 match self {
97 Dialer::Custom(connector) => {
98 let stream = connector.open("provisioning").await?;
99 ProvisionConnection::establish(stream).await
100 }
101 #[cfg(not(target_arch = "wasm32"))]
102 Dialer::Tcp {
103 trust: Some((name, tls)),
104 } => ProvisionConnection::connect_tls(addr, name, tls).await,
105 #[cfg(not(target_arch = "wasm32"))]
106 Dialer::Tcp { trust: None } => ProvisionConnection::connect(addr).await,
107 #[cfg(not(target_arch = "wasm32"))]
108 Dialer::WebSocket { base, tls } => {
109 ProvisionConnection::connect_ws(&Dialer::url(base, "provisioning"), tls).await
110 }
111 #[cfg(target_arch = "wasm32")]
112 Dialer::Tcp { .. } | Dialer::WebSocket { .. } => {
113 let _ = addr;
114 no_tcp()
115 }
116 }
117 }
118
119 pub(crate) async fn directory(&self, addr: SocketAddr) -> std::io::Result<DirConnection> {
120 match self {
121 Dialer::Custom(connector) => {
122 let stream = connector.open("directory").await?;
123 DirConnection::establish(stream).await
124 }
125 #[cfg(not(target_arch = "wasm32"))]
126 Dialer::Tcp {
127 trust: Some((name, tls)),
128 } => DirConnection::connect_tls(addr, name, tls).await,
129 #[cfg(not(target_arch = "wasm32"))]
130 Dialer::Tcp { trust: None } => DirConnection::connect(addr).await,
131 #[cfg(not(target_arch = "wasm32"))]
132 Dialer::WebSocket { base, tls } => {
133 DirConnection::connect_ws(&Dialer::url(base, "directory"), tls).await
134 }
135 #[cfg(target_arch = "wasm32")]
136 Dialer::Tcp { .. } | Dialer::WebSocket { .. } => {
137 let _ = addr;
138 no_tcp()
139 }
140 }
141 }
142
143 pub(crate) async fn relay(
144 &self,
145 addr: SocketAddr,
146 device: &tacenta_relay::DeviceAddr,
147 sign: impl FnOnce(&[u8]) -> Vec<u8>,
148 signal: Arc<tokio::sync::Notify>,
149 ) -> std::io::Result<Connection> {
150 match self {
151 Dialer::Custom(connector) => {
152 let stream = connector.open("relay").await?;
153 Connection::establish_with_signal(stream, device, sign, signal).await
154 }
155 #[cfg(not(target_arch = "wasm32"))]
156 Dialer::Tcp {
157 trust: Some((name, tls)),
158 } => {
159 Connection::connect_as_tls_with_signal(addr, name, tls, device, sign, signal).await
160 }
161 #[cfg(not(target_arch = "wasm32"))]
162 Dialer::Tcp { trust: None } => {
163 Connection::connect_as_with_signal(addr, device, sign, signal).await
164 }
165 #[cfg(not(target_arch = "wasm32"))]
166 Dialer::WebSocket { base, tls } => {
167 Connection::connect_as_ws_with_signal(
168 &Dialer::url(base, "relay"),
169 tls,
170 device,
171 sign,
172 signal,
173 )
174 .await
175 }
176 #[cfg(target_arch = "wasm32")]
177 Dialer::Tcp { .. } | Dialer::WebSocket { .. } => {
178 let _ = (addr, device, sign, signal);
179 no_tcp()
180 }
181 }
182 }
183}