Skip to main content

cloudpub_common/transport/
websocket.rs

1use core::result::Result;
2use std::io::{Error, ErrorKind};
3#[cfg(unix)]
4use std::os::fd::AsRawFd;
5use std::pin::Pin;
6use std::task::{ready, Context, Poll};
7
8use super::{
9    AddrMaybeCached, Listener, NamedSocketAddr, ProtobufStream, SocketAddr, SocketOpts, Stream,
10    TcpTransport, Transport,
11};
12use crate::config::TransportConfig;
13use crate::constants::MESSAGE_TIMEOUT_SECS;
14use anyhow::{anyhow, Context as _};
15use async_trait::async_trait;
16use bytes::{Bytes, BytesMut};
17use futures_core::stream::Stream as AsyncStream;
18use std::time::Duration;
19use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, ReadBuf};
20use tokio::time::timeout;
21
22use crate::utils::{describe_message, trace_message};
23use dashmap::DashMap;
24#[cfg(unix)]
25use std::os::fd::RawFd;
26use std::sync::Arc;
27use tokio_tungstenite::tungstenite::client::IntoClientRequest;
28use tokio_tungstenite::tungstenite::handshake::server::{
29    Callback, ErrorResponse, Request, Response,
30};
31use tokio_tungstenite::tungstenite::protocol::{Message, WebSocketConfig};
32use tokio_tungstenite::{accept_hdr_async_with_config, client_async_with_config, WebSocketStream};
33use tokio_util::io::StreamReader;
34use tracing::{debug, error, trace};
35
36use futures_util::sink::{Sink, SinkExt};
37use futures_util::stream::StreamExt;
38
39#[cfg(feature = "rustls")]
40use super::tls::{get_stream, TlsStream, TlsTransport};
41
42use crate::protocol::message::Message as ProtocolMessage;
43use crate::protocol::ProstMessage;
44
45#[derive(Debug)]
46enum TransportStream {
47    Insecure(Stream),
48    #[cfg(feature = "rustls")]
49    Secure(Box<TlsStream<Stream>>),
50}
51
52impl TransportStream {
53    fn get_tcpstream(&self) -> &Stream {
54        match self {
55            TransportStream::Insecure(s) => s,
56            #[cfg(feature = "rustls")]
57            TransportStream::Secure(s) => get_stream(s.as_ref()),
58        }
59    }
60}
61
62impl AsyncRead for TransportStream {
63    fn poll_read(
64        self: Pin<&mut Self>,
65        cx: &mut Context<'_>,
66        buf: &mut ReadBuf<'_>,
67    ) -> Poll<std::io::Result<()>> {
68        match self.get_mut() {
69            TransportStream::Insecure(s) => Pin::new(s).poll_read(cx, buf),
70            #[cfg(feature = "rustls")]
71            TransportStream::Secure(s) => Pin::new(s.as_mut()).poll_read(cx, buf),
72        }
73    }
74}
75
76impl AsyncWrite for TransportStream {
77    fn poll_write(
78        self: Pin<&mut Self>,
79        cx: &mut Context<'_>,
80        buf: &[u8],
81    ) -> Poll<Result<usize, std::io::Error>> {
82        match self.get_mut() {
83            TransportStream::Insecure(s) => Pin::new(s).poll_write(cx, buf),
84            #[cfg(feature = "rustls")]
85            TransportStream::Secure(s) => Pin::new(s.as_mut()).poll_write(cx, buf),
86        }
87    }
88
89    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
90        match self.get_mut() {
91            TransportStream::Insecure(s) => Pin::new(s).poll_flush(cx),
92            #[cfg(feature = "rustls")]
93            TransportStream::Secure(s) => Pin::new(s.as_mut()).poll_flush(cx),
94        }
95    }
96
97    fn poll_shutdown(
98        self: Pin<&mut Self>,
99        cx: &mut Context<'_>,
100    ) -> Poll<Result<(), std::io::Error>> {
101        match self.get_mut() {
102            TransportStream::Insecure(s) => Pin::new(s).poll_shutdown(cx),
103            #[cfg(feature = "rustls")]
104            TransportStream::Secure(s) => Pin::new(s.as_mut()).poll_shutdown(cx),
105        }
106    }
107}
108
109#[derive(Debug)]
110struct StreamWrapper {
111    inner: WebSocketStream<TransportStream>,
112}
113
114#[async_trait]
115impl ProtobufStream for StreamWrapper {
116    async fn recv_message(&mut self) -> anyhow::Result<Option<ProtocolMessage>> {
117        let timeout_duration = Duration::from_secs(MESSAGE_TIMEOUT_SECS);
118        let result = timeout(timeout_duration, self.inner.next()).await;
119
120        match result {
121            Err(_) => Err(anyhow!(
122                "Timeout reading message after {} seconds",
123                MESSAGE_TIMEOUT_SECS
124            )),
125            Ok(msg_result) => match msg_result {
126                Some(Ok(Message::Binary(b))) => {
127                    let msg = crate::protocol::Message::decode(b.as_ref())
128                        .context("Failed to decode protobuf message")?;
129                    let msg = msg
130                        .message
131                        .context("Message field is missing in the protobuf message")?;
132                    trace_message("Recv", &msg);
133                    Ok(Some(msg))
134                }
135                Some(Ok(Message::Close(_))) => {
136                    debug!("WebSocket connection closed");
137                    Ok(None)
138                }
139                Some(Ok(Message::Ping(data))) => {
140                    debug!("Received ping, sending pong");
141                    self.inner
142                        .send(Message::Pong(data))
143                        .await
144                        .context("Failed to send pong")?;
145                    Ok(None)
146                }
147                Some(Ok(Message::Pong(_))) => {
148                    debug!("Received pong");
149                    Ok(None)
150                }
151                Some(Ok(Message::Text(_))) => {
152                    error!("Received unexpected text message");
153                    Err(anyhow!("Unexpected text message received"))
154                }
155                Some(Ok(m)) => {
156                    error!("Received unexpected  message: {:?}", m);
157                    Err(anyhow!("Unexpected  message received"))
158                }
159                None => Ok(None),
160                Some(Err(e)) => Err(anyhow!("WebSocket error: {}", e)),
161            },
162        }
163    }
164
165    async fn send_message(
166        &mut self,
167        msg: &crate::protocol::message::Message,
168    ) -> anyhow::Result<()> {
169        trace_message("Send", msg);
170        let mut buf = BytesMut::new();
171        // Kept apart from `msg` so the error path below can still name which
172        // message timed out (CLOUDPUB-1511); describe_message only runs there.
173        let wrapper = crate::protocol::Message {
174            message: Some(msg.clone()),
175        };
176        wrapper
177            .encode(&mut buf)
178            .context("Failed to encode protobuf message")?;
179
180        let timeout_duration = Duration::from_secs(MESSAGE_TIMEOUT_SECS);
181        timeout(
182            timeout_duration,
183            self.inner.send(Message::Binary(buf.into())),
184        )
185        .await
186        .map_err(|_| {
187            anyhow::anyhow!(
188                "Timeout writing {} after {} seconds",
189                describe_message(msg),
190                MESSAGE_TIMEOUT_SECS
191            )
192        })?
193        .context("Failed to send WebSocket message")?;
194        Ok(())
195    }
196
197    async fn close(&mut self) -> anyhow::Result<()> {
198        debug!("Closing WebSocket connection");
199        self.inner
200            .close(None)
201            .await
202            .context("Failed to close WebSocket stream")
203    }
204}
205
206impl AsyncStream for StreamWrapper {
207    type Item = Result<Bytes, Error>;
208
209    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
210        match Pin::new(&mut self.get_mut().inner).poll_next(cx) {
211            Poll::Pending => Poll::Pending,
212            Poll::Ready(None) => Poll::Ready(None),
213            Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(Error::other(err)))),
214            Poll::Ready(Some(Ok(res))) => {
215                if let Message::Binary(b) = res {
216                    Poll::Ready(Some(Ok(b)))
217                } else {
218                    Poll::Ready(Some(Err(Error::new(
219                        ErrorKind::InvalidData,
220                        "unexpected frame",
221                    ))))
222                }
223            }
224        }
225    }
226
227    fn size_hint(&self) -> (usize, Option<usize>) {
228        self.inner.size_hint()
229    }
230}
231
232#[derive(Debug)]
233pub struct WebsocketStream {
234    inner: StreamReader<StreamWrapper, Bytes>,
235}
236
237impl AsyncRead for WebsocketStream {
238    fn poll_read(
239        self: Pin<&mut Self>,
240        cx: &mut Context<'_>,
241        buf: &mut ReadBuf<'_>,
242    ) -> Poll<std::io::Result<()>> {
243        Pin::new(&mut self.get_mut().inner).poll_read(cx, buf)
244    }
245}
246
247impl AsyncBufRead for WebsocketStream {
248    fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<&[u8]>> {
249        Pin::new(&mut self.get_mut().inner).poll_fill_buf(cx)
250    }
251
252    fn consume(self: Pin<&mut Self>, amt: usize) {
253        Pin::new(&mut self.get_mut().inner).consume(amt)
254    }
255}
256
257impl AsyncWrite for WebsocketStream {
258    fn poll_write(
259        self: Pin<&mut Self>,
260        cx: &mut Context<'_>,
261        buf: &[u8],
262    ) -> Poll<Result<usize, std::io::Error>> {
263        let sw = self.get_mut().inner.get_mut();
264        ready!(Pin::new(&mut sw.inner).poll_ready(cx).map_err(Error::other))?;
265
266        let bbuf = BytesMut::from(buf);
267
268        match Pin::new(&mut sw.inner).start_send(Message::Binary(bbuf.into())) {
269            Ok(()) => Poll::Ready(Ok(buf.len())),
270            Err(e) => Poll::Ready(Err(Error::other(e))),
271        }
272    }
273
274    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
275        Pin::new(&mut self.get_mut().inner.get_mut().inner)
276            .poll_flush(cx)
277            .map_err(Error::other)
278    }
279
280    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
281        Pin::new(&mut self.get_mut().inner.get_mut().inner)
282            .poll_close(cx)
283            .map_err(Error::other)
284    }
285}
286
287#[async_trait]
288impl ProtobufStream for WebsocketStream {
289    async fn recv_message(&mut self) -> anyhow::Result<Option<ProtocolMessage>> {
290        self.inner.get_mut().recv_message().await
291    }
292
293    async fn send_message(&mut self, msg: &ProtocolMessage) -> anyhow::Result<()> {
294        self.inner.get_mut().send_message(msg).await
295    }
296
297    async fn close(&mut self) -> anyhow::Result<()> {
298        debug!("Closing WebSocket stream");
299        self.inner.get_mut().close().await
300    }
301}
302
303#[derive(Debug)]
304enum SubTransport {
305    #[cfg(feature = "rustls")]
306    Secure(TlsTransport),
307    Insecure(TcpTransport),
308}
309
310#[derive(Debug)]
311pub struct WebsocketTransport {
312    sub: SubTransport,
313    conf: WebSocketConfig,
314    headers: Arc<DashMap<String, String>>,
315    connect_timeout_secs: u64,
316}
317
318// The handshake-header collector, as a named Callback rather than the closure
319// it used to be (CLOUDPUB-1414). Callback's blanket impl pins a closure's
320// error type to ErrorResponse = Response<Option<String>> - 136 bytes that
321// clippy's result_large_err rejects on any closure returning it, with no way
322// to box or shrink the type from our side. A trait method carries the same
323// signature as one the trait imposes, which is where the lint stops; the
324// behavior is the collection itself and does not change.
325struct HeaderCollector {
326    headers: Arc<DashMap<String, String>>,
327}
328
329impl Callback for HeaderCollector {
330    fn on_request(self, req: &Request, res: Response) -> Result<Response, ErrorResponse> {
331        for ref header in req.headers() {
332            trace!("WS headers: {:?}", header);
333            self.headers.insert(
334                header.0.to_string(),
335                header.1.to_str().unwrap_or_default().to_string(),
336            );
337        }
338        Ok(res)
339    }
340}
341
342#[async_trait]
343impl Transport for WebsocketTransport {
344    type Acceptor = Listener;
345    type RawStream = Stream;
346    type Stream = WebsocketStream;
347
348    fn new(config: &TransportConfig) -> anyhow::Result<Self> {
349        let wsconfig = config
350            .websocket
351            .as_ref()
352            .ok_or_else(|| anyhow!("Missing websocket config"))?;
353
354        let conf = WebSocketConfig::default()
355            .write_buffer_size(0)
356            .read_buffer_size(crate::constants::WEBSOCKET_READ_BUFFER_SIZE);
357
358        let sub = match wsconfig.tls {
359            #[cfg(feature = "rustls")]
360            true => SubTransport::Secure(TlsTransport::new(config)?),
361            #[cfg(not(feature = "rustls"))]
362            true => unreachable!("TLS support not enabled"),
363            false => SubTransport::Insecure(TcpTransport::new(config)?),
364        };
365        let headers = Arc::new(DashMap::new());
366        Ok(WebsocketTransport {
367            sub,
368            conf,
369            headers,
370            connect_timeout_secs: config.tcp.connect_timeout_secs,
371        })
372    }
373
374    fn hint(conn: &Self::Stream, opt: SocketOpts) {
375        opt.apply(conn.inner.get_ref().inner.get_ref().get_tcpstream())
376    }
377
378    #[cfg(unix)]
379    fn as_raw_fd(conn: &Self::Stream) -> RawFd {
380        match conn.inner.get_ref().inner.get_ref().get_tcpstream() {
381            Stream::Tcp(tcp_stream) => tcp_stream.as_raw_fd(),
382            Stream::Unix(unix_stream) => unix_stream.as_raw_fd(),
383        }
384    }
385
386    async fn bind(&self, addr: NamedSocketAddr) -> anyhow::Result<Self::Acceptor> {
387        Listener::bind(&addr).await.map_err(Into::into)
388    }
389
390    async fn accept(&self, a: &Self::Acceptor) -> anyhow::Result<(Self::RawStream, SocketAddr)> {
391        let (s, addr) = match &self.sub {
392            SubTransport::Insecure(t) => t.accept(a).await?,
393            #[cfg(feature = "rustls")]
394            SubTransport::Secure(t) => t.accept(a).await?,
395        };
396        Ok((s, addr))
397    }
398
399    async fn handshake(&self, conn: Self::RawStream) -> anyhow::Result<Self::Stream> {
400        let tsream = match &self.sub {
401            SubTransport::Insecure(t) => {
402                TransportStream::Insecure(t.handshake(conn).await?.into_stream())
403            }
404            #[cfg(feature = "rustls")]
405            SubTransport::Secure(t) => TransportStream::Secure(Box::new(t.handshake(conn).await?)),
406        };
407
408        let wsstream = accept_hdr_async_with_config(
409            tsream,
410            HeaderCollector {
411                headers: self.headers.clone(),
412            },
413            Some(self.conf),
414        )
415        .await?;
416
417        let tun = WebsocketStream {
418            inner: StreamReader::new(StreamWrapper { inner: wsstream }),
419        };
420        Ok(tun)
421    }
422
423    async fn connect(&self, addr: &AddrMaybeCached) -> anyhow::Result<Self::Stream> {
424        let u = format!("wss://{}/endpoint/v3", addr.addr.as_str());
425        // Covers the nested TCP connect and TLS handshake too, so the whole
426        // dial is bounded by one deadline rather than by the sum of the
427        // per-step ones.
428        super::with_connect_deadline(self.connect_timeout_secs, "WebSocket connect", async {
429            let tstream = match &self.sub {
430                SubTransport::Insecure(t) => {
431                    TransportStream::Insecure(t.connect(addr).await?.into_stream())
432                }
433                #[cfg(feature = "rustls")]
434                SubTransport::Secure(t) => {
435                    TransportStream::Secure(Box::new(t.connect(addr).await?))
436                }
437            };
438            debug!("Connecting to {}", u);
439            let (wsstream, _) = client_async_with_config(
440                u.clone()
441                    .into_client_request()
442                    .context("Failed to create client request")?,
443                tstream,
444                Some(self.conf),
445            )
446            .await
447            .with_context(|| format!("Failed to connect to {}", u))?;
448
449            debug!("Connected");
450
451            let tun = WebsocketStream {
452                inner: StreamReader::new(StreamWrapper { inner: wsstream }),
453            };
454            Ok(tun)
455        })
456        .await
457    }
458
459    fn get_header(&self, name: &str) -> Option<String> {
460        self.headers.get(&name.to_lowercase()).map(|v| v.clone())
461    }
462}