Skip to main content

libsignal_service/websocket/
mod.rs

1use std::collections::{HashMap, HashSet};
2use std::marker::PhantomData;
3use std::sync::{Arc, Mutex, MutexGuard};
4
5use std::future::Future;
6
7use bytes::Bytes;
8use futures::channel::oneshot::Canceled;
9use futures::channel::{mpsc, oneshot};
10use futures::future::BoxFuture;
11use futures::prelude::*;
12use futures::stream::FuturesUnordered;
13use reqwest::Method;
14use reqwest_websocket::WebSocket;
15use tokio::time::Instant;
16use tracing::debug;
17
18use crate::configuration::SignalServers;
19use crate::prelude::PushService;
20use crate::proto::{
21    web_socket_message, WebSocketMessage, WebSocketRequestMessage,
22    WebSocketResponseMessage,
23};
24use crate::push_service::{self, ServiceError, SignalServiceResponse};
25
26pub mod account;
27#[cfg(feature = "cdsi")]
28pub mod directory;
29pub mod keys;
30pub mod linking;
31pub mod profile;
32pub mod registration;
33mod request;
34mod sender;
35pub mod stickers;
36mod usernames;
37
38pub use request::WebSocketRequestMessageBuilder;
39
40type RequestStreamItem = (
41    WebSocketRequestMessage,
42    oneshot::Sender<WebSocketResponseMessage>,
43);
44
45pub struct SignalRequestStream {
46    inner: mpsc::UnboundedReceiver<RequestStreamItem>,
47}
48
49impl Stream for SignalRequestStream {
50    type Item = RequestStreamItem;
51
52    fn poll_next(
53        mut self: std::pin::Pin<&mut Self>,
54        cx: &mut std::task::Context<'_>,
55    ) -> std::task::Poll<Option<Self::Item>> {
56        let inner = &mut self.inner;
57        futures::pin_mut!(inner);
58        Stream::poll_next(inner, cx)
59    }
60}
61
62#[derive(Debug, Clone)]
63pub struct Identified;
64
65#[derive(Debug, Clone)]
66pub struct Unidentified;
67
68pub trait WebSocketType: 'static {}
69
70impl WebSocketType for Identified {}
71
72impl WebSocketType for Unidentified {}
73
74/// A dispatching web socket client for the Signal web socket API.
75///
76/// This structure can be freely cloned, since this acts as a *facade* for multiple entry and exit
77/// points.
78#[derive(Clone)]
79pub struct SignalWebSocket<C: WebSocketType> {
80    _type: PhantomData<C>,
81    // XXX: at the end of the migration, this should be CDN operations only
82    pub(crate) unidentified_push_service: PushService,
83    inner: Arc<Mutex<SignalWebSocketInner>>,
84    request_sink: mpsc::Sender<(
85        WebSocketRequestMessage,
86        oneshot::Sender<Result<WebSocketResponseMessage, ServiceError>>,
87    )>,
88}
89
90struct SignalWebSocketInner {
91    stream: Option<SignalRequestStream>,
92}
93
94struct SignalWebSocketProcess {
95    /// Whether to enable keep-alive or not (and send a request to this path)
96    keep_alive_path: String,
97
98    /// Receives requests from the application, which we forward to Signal.
99    requests: mpsc::Receiver<(
100        WebSocketRequestMessage,
101        oneshot::Sender<Result<WebSocketResponseMessage, ServiceError>>,
102    )>,
103    /// Signal's requests should go in here, to be delivered to the application.
104    request_sink: mpsc::UnboundedSender<RequestStreamItem>,
105
106    outgoing_requests: HashMap<
107        u64,
108        oneshot::Sender<Result<WebSocketResponseMessage, ServiceError>>,
109    >,
110
111    outgoing_keep_alive_set: HashSet<u64>,
112
113    outgoing_responses: FuturesUnordered<
114        BoxFuture<'static, Result<WebSocketResponseMessage, Canceled>>,
115    >,
116
117    ws: WebSocket,
118}
119
120impl SignalWebSocketProcess {
121    async fn process_frame(
122        &mut self,
123        frame: Bytes,
124    ) -> Result<(), ServiceError> {
125        use prost::Message;
126        let msg = WebSocketMessage::decode(frame)?;
127        if let Some(request) = &msg.request {
128            tracing::trace!(
129                msg_type =? msg.r#type(),
130                request.id,
131                request.verb,
132                request.path,
133                request_body_size_bytes = request.body.as_ref().map(|x| x.len()).unwrap_or(0),
134                ?request.headers,
135                "decoded WebSocketMessage request"
136            );
137        } else if let Some(response) = &msg.response {
138            tracing::trace!(
139                msg_type =? msg.r#type(),
140                response.status,
141                response.message,
142                response_body_size_bytes = response.body.as_ref().map(|x| x.len()).unwrap_or(0),
143                ?response.headers,
144                response.id,
145                "decoded WebSocketMessage response"
146            );
147        } else {
148            tracing::debug!("decoded {msg:?}");
149        }
150
151        use web_socket_message::Type;
152        match (msg.r#type(), msg.request, msg.response) {
153            (Type::Unknown, _, _) => Err(ServiceError::InvalidFrame {
154                reason: "unknown frame type",
155            }),
156            (Type::Request, Some(request), _) => {
157                let (sink, recv) = oneshot::channel();
158                tracing::trace!("sending request with body");
159                self.request_sink.send((request, sink)).await.map_err(
160                    |_| ServiceError::WsClosing {
161                        reason: "request handler failed",
162                    },
163                )?;
164                self.outgoing_responses.push(Box::pin(recv));
165
166                Ok(())
167            },
168            (Type::Request, None, _) => Err(ServiceError::InvalidFrame {
169                reason: "type was request, but does not contain request",
170            }),
171            (Type::Response, _, Some(response)) => {
172                if let Some(id) = response.id {
173                    if let Some(responder) = self.outgoing_requests.remove(&id)
174                    {
175                        if let Err(e) = responder.send(Ok(response)) {
176                            tracing::warn!(
177                                "Could not deliver response for id {}: {:?}",
178                                id,
179                                e
180                            );
181                        }
182                    } else if let Some(_x) =
183                        self.outgoing_keep_alive_set.take(&id)
184                    {
185                        let status = reqwest::StatusCode::from_u16(
186                            response.status() as _,
187                        )
188                        .map_err(|e| {
189                            ServiceError::IO(std::io::Error::other(format!(
190                                "invalid http status code {} - {e}",
191                                response.status()
192                            )))
193                        })?;
194                        if !status.is_success() {
195                            tracing::warn!(
196                                %status,
197                                "response code for keep-alive not successful"
198                            );
199                            return Err(ServiceError::UnhandledResponseCode {
200                                status,
201                                body: String::from_utf8_lossy(response.body())
202                                    .into_owned(),
203                            });
204                        }
205                    } else {
206                        tracing::warn!(
207                            ?response,
208                            "response for non existing request"
209                        );
210                    }
211                }
212
213                Ok(())
214            },
215            (Type::Response, _, None) => Err(ServiceError::InvalidFrame {
216                reason: "type was response, but does not contain response",
217            }),
218        }
219    }
220
221    fn next_request_id(&self) -> u64 {
222        use rand::Rng;
223        let mut rng = rand::rng();
224        loop {
225            let id = rng.random();
226            if !self.outgoing_requests.contains_key(&id) {
227                return id;
228            }
229        }
230    }
231
232    async fn run(mut self) -> Result<(), ServiceError> {
233        // A keepalive sent before the server registers our presence makes it close the socket.
234        let mut ka_interval = tokio::time::interval_at(
235            Instant::now() + push_service::KEEPALIVE_TIMEOUT_SECONDS,
236            push_service::KEEPALIVE_TIMEOUT_SECONDS,
237        );
238
239        loop {
240            futures::select! {
241                _ = ka_interval.tick().fuse() => {
242                    use prost::Message;
243                    if !self.outgoing_keep_alive_set.is_empty() {
244                        tracing::warn!("Websocket will be closed due to failed keepalives.");
245                        if let Err(e) = self.ws.close(reqwest_websocket::CloseCode::Away, None).await {
246                            tracing::debug!("Could not close WebSocket: {:?}", e);
247                        }
248                        self.outgoing_keep_alive_set.clear();
249                        break;
250                    }
251                    tracing::debug!("sending keep-alive");
252                    let request = WebSocketRequestMessage::new(Method::GET)
253                        .id(self.next_request_id())
254                        .path(&self.keep_alive_path)
255                        .build();
256                    self.outgoing_keep_alive_set.insert(request.id.unwrap());
257                    let msg = WebSocketMessage {
258                        r#type: Some(web_socket_message::Type::Request.into()),
259                        request: Some(request),
260                        ..Default::default()
261                    };
262                    let buffer = msg.encode_to_vec();
263                    if let Err(e) = self.ws.send(reqwest_websocket::Message::Binary(buffer.into())).await {
264                        tracing::info!("Websocket sink has closed: {:?}.", e);
265                        break;
266                    };
267                },
268                // Process requests from the application, forward them to Signal
269                x = self.requests.next() => {
270                    match x {
271                        Some((mut request, responder)) => {
272                            use prost::Message;
273
274                            // Regenerate ID if already in the table
275                            request.id = Some(
276                                request
277                                    .id
278                                    .filter(|x| !self.outgoing_requests.contains_key(x))
279                                    .unwrap_or_else(|| self.next_request_id()),
280                            );
281                            tracing::trace!(
282                                request.id,
283                                request.verb,
284                                request.path,
285                                request_body_size_bytes = request.body.as_ref().map(|x| x.len()),
286                                ?request.headers,
287                                "sending WebSocketRequestMessage",
288                            );
289
290                            self.outgoing_requests.insert(request.id.unwrap(), responder);
291                            let msg = WebSocketMessage {
292                                r#type: Some(web_socket_message::Type::Request.into()),
293                                request: Some(request),
294                                ..Default::default()
295                            };
296                            let buffer = msg.encode_to_vec();
297                            self.ws.send(reqwest_websocket::Message::Binary(buffer.into())).await?
298                        }
299                        None => {
300                            debug!("end of application request stream; websocket closing");
301                            return Ok(());
302                        }
303                    }
304                }
305                // Incoming websocket message
306                web_socket_item = self.ws.next().fuse() => {
307                    use reqwest_websocket::Message;
308                    match web_socket_item {
309                        Some(Ok(Message::Close { code, reason })) => {
310                            tracing::warn!(%code, reason, "websocket closed");
311                            break;
312                        },
313                        Some(Ok(Message::Binary(frame))) => {
314                            self.process_frame(frame).await?;
315                        }
316                        Some(Ok(Message::Ping(_))) => {
317                            tracing::trace!("received ping");
318                        }
319                        Some(Ok(Message::Pong(_))) => {
320                            tracing::trace!("received pong");
321                        }
322                        Some(Ok(Message::Text(_))) => {
323                            tracing::trace!("received text (unsupported, skipping)");
324                        }
325                        Some(Err(e)) => return Err(e.into()),
326                        None => {
327                            return Err(ServiceError::WsClosing {
328                                reason: "end of web request stream; socket closing"
329                            });
330                        }
331                    }
332                }
333                response = self.outgoing_responses.next() => {
334                    use prost::Message;
335                    match response {
336                        Some(Ok(response)) => {
337                            tracing::trace!("sending response {:?}", response);
338
339                            let msg = WebSocketMessage {
340                                r#type: Some(web_socket_message::Type::Response.into()),
341                                response: Some(response),
342                                ..Default::default()
343                            };
344                            let buffer = msg.encode_to_vec();
345                            self.ws.send(buffer.into()).await?;
346                        }
347                        Some(Err(error)) => {
348                            tracing::error!(%error, "could not generate response to a Signal request; responder was canceled. continuing.");
349                        }
350                        None => {
351                            unreachable!("outgoing responses should never fuse")
352                        }
353                    }
354                }
355            }
356        }
357        Ok(())
358    }
359}
360
361impl<C: WebSocketType> SignalWebSocket<C> {
362    fn inner_locked(&self) -> MutexGuard<'_, SignalWebSocketInner> {
363        self.inner.lock().unwrap()
364    }
365
366    pub fn new(
367        ws: WebSocket,
368        keep_alive_path: String,
369        unidentified_push_service: PushService,
370    ) -> (Self, impl Future<Output = ()>) {
371        // Create process
372        let (incoming_request_sink, incoming_request_stream) =
373            mpsc::unbounded();
374        let (outgoing_request_sink, outgoing_requests) = mpsc::channel(1);
375
376        let process = SignalWebSocketProcess {
377            keep_alive_path,
378            requests: outgoing_requests,
379            request_sink: incoming_request_sink,
380            outgoing_requests: HashMap::default(),
381            outgoing_keep_alive_set: HashSet::new(),
382            // Initializing the FuturesUnordered with a `pending` future means it will never fuse
383            // itself, so an "empty" FuturesUnordered will still allow new futures to be added.
384            outgoing_responses: vec![
385                Box::pin(futures::future::pending()) as BoxFuture<_>
386            ]
387            .into_iter()
388            .collect(),
389            ws,
390        };
391        let process = process.run().map(|x| match x {
392            Ok(()) => (),
393            Err(e) => {
394                tracing::error!("SignalWebSocket: {}", e);
395            },
396        });
397
398        (
399            Self {
400                _type: PhantomData,
401                request_sink: outgoing_request_sink,
402                unidentified_push_service,
403                inner: Arc::new(Mutex::new(SignalWebSocketInner {
404                    stream: Some(SignalRequestStream {
405                        inner: incoming_request_stream,
406                    }),
407                })),
408            },
409            process,
410        )
411    }
412
413    pub fn servers(&self) -> SignalServers {
414        self.unidentified_push_service.servers
415    }
416
417    pub fn is_closed(&self) -> bool {
418        self.request_sink.is_closed()
419    }
420
421    pub fn is_used(&self) -> bool {
422        self.inner_locked().stream.is_none()
423    }
424
425    pub(crate) fn take_request_stream(
426        &mut self,
427    ) -> Option<SignalRequestStream> {
428        self.inner_locked().stream.take()
429    }
430
431    pub(crate) fn return_request_stream(&mut self, r: SignalRequestStream) {
432        self.inner_locked().stream.replace(r);
433    }
434
435    // XXX Ideally, this should take an *async* closure, then we could get rid of the
436    // `take_request_stream` and `return_request_stream`.
437    pub async fn with_request_stream<
438        R: 'static,
439        F: FnOnce(&mut SignalRequestStream) -> R,
440    >(
441        &mut self,
442        f: F,
443    ) -> R {
444        let mut s = self
445            .inner_locked()
446            .stream
447            .take()
448            .expect("request stream invariant");
449        let r = f(&mut s);
450        self.inner_locked().stream.replace(s);
451        r
452    }
453
454    pub fn request(
455        &mut self,
456        r: WebSocketRequestMessage,
457    ) -> impl Future<Output = Result<WebSocketResponseMessage, ServiceError>>
458    {
459        let (sink, recv): (
460            oneshot::Sender<Result<WebSocketResponseMessage, ServiceError>>,
461            _,
462        ) = oneshot::channel();
463
464        let mut request_sink = self.request_sink.clone();
465        async move {
466            if let Err(_e) = request_sink.send((r, sink)).await {
467                return Err(ServiceError::WsClosing {
468                    reason: "WebSocket closing while sending request",
469                });
470            }
471            // Handle the oneshot sender error for dropped senders.
472            match recv.await {
473                Ok(x) => x,
474                Err(_) => Err(ServiceError::WsClosing {
475                    reason: "WebSocket closing while waiting for a response",
476                }),
477            }
478        }
479    }
480
481    pub(crate) async fn request_json_with<T, F, Fut>(
482        &mut self,
483        r: WebSocketRequestMessage,
484        decode: F,
485    ) -> Result<T, ServiceError>
486    where
487        for<'de> T: serde::Deserialize<'de>,
488        F: FnOnce(WebSocketResponseMessage) -> Fut + Send,
489        Fut: Future<Output = Result<WebSocketResponseMessage, ServiceError>>
490            + Send,
491    {
492        self.request(r)
493            .await?
494            .service_error_for_status_with(decode)
495            .await?
496            .json()
497            .await
498    }
499}
500
501impl WebSocketResponseMessage {
502    pub async fn json<T: for<'a> serde::Deserialize<'a>>(
503        &self,
504    ) -> Result<T, ServiceError> {
505        self.body
506            .as_ref()
507            .ok_or(ServiceError::UnsupportedContent)
508            .and_then(|b| serde_json::from_slice(b).map_err(Into::into))
509    }
510}