1use std::sync::{Arc, Mutex};
2
3use bitwarden_threading::cancellation_token::CancellationToken;
4use serde::de::DeserializeOwned;
5use thiserror::Error;
6use tokio::select;
7
8use crate::{
9 constants::CHANNEL_BUFFER_CAPACITY,
10 error::{
11 AlreadyRunningError, ErrorKind, IpcErrorKind, ReceiveError, SendError, SubscribeError,
12 TypedReceiveError,
13 },
14 message::{
15 IncomingMessage, OutgoingMessage, PayloadTypeName, TypedIncomingMessage,
16 TypedOutgoingMessage,
17 },
18 rpc::{
19 exec::{handler::ErasedRpcHandler, handler_registry::RpcHandlerRegistry},
20 request_message::{RPC_REQUEST_PAYLOAD_TYPE_NAME, RpcRequestPayload},
21 response_message::OutgoingRpcResponseMessage,
22 },
23 serde_utils,
24 traits::{CommunicationBackend, CryptoProvider, SessionRepository},
25};
26
27pub struct IpcClientSubscription {
32 pub(crate) receiver: tokio::sync::broadcast::Receiver<IncomingMessage>,
33 pub(crate) topic: Option<String>,
34}
35
36pub struct IpcClientTypedSubscription<Payload: DeserializeOwned + PayloadTypeName>(
41 IpcClientSubscription,
42 std::marker::PhantomData<Payload>,
43);
44
45struct IpcClientInner<Crypto, Com, Ses>
47where
48 Crypto: CryptoProvider<Com, Ses>,
49 Com: CommunicationBackend,
50 Ses: SessionRepository<Crypto::Session>,
51{
52 crypto: Crypto,
53 communication: Com,
54 sessions: Ses,
55
56 handlers: RpcHandlerRegistry,
57 incoming: Mutex<Option<tokio::sync::broadcast::Receiver<IncomingMessage>>>,
58 cancellation_token: Mutex<Option<CancellationToken>>,
59}
60
61pub struct IpcClientImpl<Crypto, Com, Ses>
67where
68 Crypto: CryptoProvider<Com, Ses>,
69 Com: CommunicationBackend,
70 Ses: SessionRepository<Crypto::Session>,
71{
72 inner: Arc<IpcClientInner<Crypto, Com, Ses>>,
73}
74
75impl<Crypto, Com, Ses> Clone for IpcClientImpl<Crypto, Com, Ses>
76where
77 Crypto: CryptoProvider<Com, Ses>,
78 Com: CommunicationBackend,
79 Ses: SessionRepository<Crypto::Session>,
80{
81 fn clone(&self) -> Self {
82 Self {
83 inner: self.inner.clone(),
84 }
85 }
86}
87
88impl<Crypto, Com, Ses> IpcClientImpl<Crypto, Com, Ses>
89where
90 Crypto: CryptoProvider<Com, Ses>,
91 Com: CommunicationBackend,
92 Ses: SessionRepository<Crypto::Session>,
93{
94 pub fn new(crypto: Crypto, communication: Com, sessions: Ses) -> Self {
97 Self {
98 inner: Arc::new(IpcClientInner {
99 crypto,
100 communication,
101 sessions,
102
103 handlers: RpcHandlerRegistry::new(),
104 incoming: Mutex::new(None),
105 cancellation_token: Mutex::new(None),
106 }),
107 }
108 }
109}
110
111#[async_trait::async_trait]
112impl<Crypto, Com, Ses> crate::ipc_client_trait::IpcClient for IpcClientImpl<Crypto, Com, Ses>
113where
114 Crypto: CryptoProvider<Com, Ses>,
115 Com: CommunicationBackend,
116 Ses: SessionRepository<Crypto::Session>,
117{
118 async fn start(
119 &self,
120 cancellation_token: Option<CancellationToken>,
121 ) -> Result<(), AlreadyRunningError> {
122 if self.is_running() {
123 return Err(AlreadyRunningError);
124 }
125
126 let cancellation_token = cancellation_token.unwrap_or_default();
127 self.inner
128 .cancellation_token
129 .lock()
130 .expect("Failed to lock cancellation token mutex")
131 .replace(cancellation_token.clone());
132
133 let com_receiver = self.inner.communication.subscribe().await;
134 let (client_tx, client_rx) = tokio::sync::broadcast::channel(CHANNEL_BUFFER_CAPACITY);
135
136 self.inner
137 .incoming
138 .lock()
139 .expect("Failed to lock incoming mutex")
140 .replace(client_rx);
141
142 let inner = self.inner.clone();
143 let future = async move {
144 loop {
145 let rpc_topic = RPC_REQUEST_PAYLOAD_TYPE_NAME.to_owned();
146 select! {
147 _ = cancellation_token.cancelled() => {
148 tracing::debug!("Cancellation signal received, stopping IPC client");
149 break;
150 }
151 received = inner.crypto.receive(&com_receiver, &inner.communication, &inner.sessions) => {
152 match received {
153 Ok(message) if message.topic == Some(rpc_topic) => {
154 handle_rpc_request(&inner, message)
155 }
156 Ok(message) => {
157 if client_tx.send(message).is_err() {
158 tracing::error!("Failed to save incoming message");
159 break;
160 };
161 }
162 Err(error) if matches!(error.kind(), ErrorKind::Fatal) => {
163 tracing::error!(?error, "Fatal error receiving message, stopping IPC client");
164 break;
165 }
166 Err(error) => {
167 tracing::warn!(?error, "Recoverable error receiving message, continuing");
168 }
169 }
170 }
171 }
172 }
173 tracing::debug!("IPC client shutting down");
174 stop_inner(&inner);
175 };
176
177 #[cfg(not(target_arch = "wasm32"))]
178 tokio::spawn(future);
179
180 #[cfg(target_arch = "wasm32")]
181 wasm_bindgen_futures::spawn_local(future);
182
183 Ok(())
184 }
185
186 fn is_running(&self) -> bool {
187 let has_incoming = self
188 .inner
189 .incoming
190 .lock()
191 .expect("Failed to lock incoming mutex")
192 .as_ref()
193 .map(|receiver| !receiver.is_closed())
194 .unwrap_or(false);
195 let has_cancellation_token = self
196 .inner
197 .cancellation_token
198 .lock()
199 .expect("Failed to lock cancellation token mutex")
200 .is_some();
201 has_incoming && has_cancellation_token
202 }
203
204 async fn send(&self, message: OutgoingMessage) -> Result<(), SendError> {
205 let result = self
206 .inner
207 .crypto
208 .send(&self.inner.communication, &self.inner.sessions, message)
209 .await;
210
211 if let Err(ref error) = result {
212 match error.kind() {
213 ErrorKind::Fatal => {
214 tracing::error!(?error, "Fatal error sending message, stopping IPC client");
215 stop_inner(&self.inner);
216 }
217 ErrorKind::Unreachable => {}
219 ErrorKind::Other => {
221 tracing::warn!(
222 ?error,
223 "Recoverable error sending message, IPC client will continue running"
224 );
225 }
226 }
227 }
228
229 result.map_err(|e| match e.kind() {
230 ErrorKind::Unreachable => SendError::Unreachable,
231 _ => SendError::Other(format!("{e:?}")),
232 })
233 }
234
235 async fn subscribe(
236 &self,
237 topic: Option<String>,
238 ) -> Result<IpcClientSubscription, SubscribeError> {
239 Ok(IpcClientSubscription {
240 receiver: self
241 .inner
242 .incoming
243 .lock()
244 .expect("Failed to lock incoming mutex")
245 .as_ref()
246 .ok_or(SubscribeError::NotStarted)?
247 .resubscribe(),
248 topic,
249 })
250 }
251
252 async fn register_rpc_handler_erased(&self, name: &str, handler: Box<dyn ErasedRpcHandler>) {
253 self.inner
254 .handlers
255 .register_erased(name.to_owned(), handler)
256 .await;
257 }
258}
259
260fn stop_inner<Crypto, Com, Ses>(inner: &IpcClientInner<Crypto, Com, Ses>)
261where
262 Crypto: CryptoProvider<Com, Ses>,
263 Com: CommunicationBackend,
264 Ses: SessionRepository<Crypto::Session>,
265{
266 let mut cancellation_token = inner
267 .cancellation_token
268 .lock()
269 .expect("Failed to lock cancellation token mutex");
270
271 if let Some(cancellation_token) = cancellation_token.take() {
272 cancellation_token.cancel();
273 }
274}
275
276fn handle_rpc_request<Crypto, Com, Ses>(
277 inner: &Arc<IpcClientInner<Crypto, Com, Ses>>,
278 incoming_message: IncomingMessage,
279) where
280 Crypto: CryptoProvider<Com, Ses>,
281 Com: CommunicationBackend,
282 Ses: SessionRepository<Crypto::Session>,
283{
284 let inner = inner.clone();
285 let future = async move {
286 #[derive(Debug, Error)]
287 enum HandleError {
288 #[error("Failed to deserialize request message: {0}")]
289 Deserialize(String),
290
291 #[error("Failed to serialize response message: {0}")]
292 Serialize(String),
293 }
294
295 async fn handle(
296 incoming_message: IncomingMessage,
297 handlers: &RpcHandlerRegistry,
298 ) -> Result<OutgoingMessage, HandleError> {
299 let request = RpcRequestPayload::from_slice(incoming_message.payload.clone()).map_err(
300 |e: serde_utils::DeserializeError| HandleError::Deserialize(e.to_string()),
301 )?;
302
303 let response = handlers.handle(&request).await;
304
305 let response_message = OutgoingRpcResponseMessage {
306 request_id: request.request_id(),
307 request_type: request.request_type(),
308 result: response,
309 };
310
311 let outgoing = TypedOutgoingMessage {
312 payload: response_message,
313 destination: incoming_message.source.into(),
314 }
315 .try_into()
316 .map_err(|e: serde_utils::SerializeError| HandleError::Serialize(e.to_string()))?;
317
318 Ok(outgoing)
319 }
320
321 match handle(incoming_message, &inner.handlers).await {
322 Ok(outgoing_message) => {
323 let result = inner
326 .crypto
327 .send(&inner.communication, &inner.sessions, outgoing_message)
328 .await;
329 if result.is_err() {
330 tracing::error!("Failed to send response message");
331 }
332 }
333 Err(error) => {
334 tracing::error!(%error, "Error handling RPC request");
335 }
336 }
337 };
338
339 #[cfg(not(target_arch = "wasm32"))]
340 tokio::spawn(future);
341
342 #[cfg(target_arch = "wasm32")]
343 wasm_bindgen_futures::spawn_local(future);
344}
345
346impl IpcClientSubscription {
347 pub async fn receive(
350 &mut self,
351 cancellation_token: Option<CancellationToken>,
352 ) -> Result<IncomingMessage, ReceiveError> {
353 let cancellation_token = cancellation_token.unwrap_or_default();
354
355 loop {
356 select! {
357 _ = cancellation_token.cancelled() => {
358 return Err(ReceiveError::Cancelled)
359 }
360 result = self.receiver.recv() => {
361 let received = result?;
362 if self.topic.is_none() || received.topic == self.topic {
363 return Ok::<IncomingMessage, ReceiveError>(received);
364 }
365 }
366 }
367 }
368 }
369}
370
371impl<Payload> IpcClientTypedSubscription<Payload>
372where
373 Payload: DeserializeOwned + PayloadTypeName,
374{
375 pub(crate) fn new(subscription: IpcClientSubscription) -> Self {
376 Self(subscription, std::marker::PhantomData)
377 }
378
379 pub async fn receive(
382 &mut self,
383 cancellation_token: Option<CancellationToken>,
384 ) -> Result<TypedIncomingMessage<Payload>, TypedReceiveError> {
385 let received = self.0.receive(cancellation_token).await?;
386 received
387 .try_into()
388 .map_err(|e: serde_utils::DeserializeError| TypedReceiveError::Typing(e.to_string()))
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use std::{collections::HashMap, time::Duration};
395
396 use bitwarden_threading::time::sleep;
397 use serde::{Deserialize, Serialize};
398
399 use super::*;
400 use crate::{
401 IpcClientExt,
402 endpoint::{Endpoint, HostId, Source},
403 ipc_client_trait::IpcClient,
404 message::PayloadTypeName,
405 rpc::{
406 request::RpcRequest,
407 request_message::{RPC_REQUEST_PAYLOAD_TYPE_NAME, RpcRequestMessage},
408 response_message::IncomingRpcResponseMessage,
409 },
410 traits::{InMemorySessionRepository, NoEncryptionCryptoProvider, TestCommunicationBackend},
411 };
412
413 #[derive(Debug, Clone)]
416 struct TestCryptoError {
417 #[allow(dead_code)]
420 message: String,
421 fatal: bool,
422 }
423
424 impl IpcErrorKind for TestCryptoError {
425 fn kind(&self) -> ErrorKind {
426 if self.fatal {
427 ErrorKind::Fatal
428 } else {
429 ErrorKind::Other
430 }
431 }
432 }
433
434 struct TestCryptoProvider {
435 send_result: Option<Result<(), TestCryptoError>>,
437 receive_result: Option<Result<IncomingMessage, TestCryptoError>>,
439 }
440
441 type TestSessionRepository = InMemorySessionRepository<String>;
442 impl CryptoProvider<TestCommunicationBackend, TestSessionRepository> for TestCryptoProvider {
443 type Session = String;
444 type SendError = TestCryptoError;
445 type ReceiveError = TestCryptoError;
446
447 async fn receive(
448 &self,
449 _receiver: &<TestCommunicationBackend as CommunicationBackend>::Receiver,
450 _communication: &TestCommunicationBackend,
451 _sessions: &TestSessionRepository,
452 ) -> Result<IncomingMessage, Self::ReceiveError> {
453 match &self.receive_result {
454 Some(result) => {
455 sleep(Duration::from_millis(5)).await;
460 result.clone()
461 }
462 None => {
463 sleep(Duration::from_secs(600)).await;
465 Err(TestCryptoError {
466 message: "Simulated timeout".to_string(),
467 fatal: true,
468 })
469 }
470 }
471 }
472
473 async fn send(
474 &self,
475 _communication: &TestCommunicationBackend,
476 _sessions: &TestSessionRepository,
477 _message: OutgoingMessage,
478 ) -> Result<(), Self::SendError> {
479 match &self.send_result {
480 Some(result) => result.clone(),
481 None => {
482 sleep(Duration::from_secs(600)).await;
484 Err(TestCryptoError {
485 message: "Simulated timeout".to_string(),
486 fatal: true,
487 })
488 }
489 }
490 }
491 }
492
493 #[tokio::test]
494 async fn returns_send_error_when_crypto_provider_returns_error() {
495 let message = OutgoingMessage {
496 payload: vec![],
497 destination: Endpoint::BrowserBackground { id: HostId::Own },
498 topic: None,
499 };
500 let crypto_provider = TestCryptoProvider {
501 send_result: Some(Err(TestCryptoError {
502 message: "Crypto error".to_string(),
503 fatal: false,
504 })),
505 receive_result: Some(Err(TestCryptoError {
506 message: "Should not have be called".to_string(),
507 fatal: false,
508 })),
509 };
510 let communication_provider = TestCommunicationBackend::new();
511 let session_map = TestSessionRepository::new(HashMap::new());
512 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
513 let _ = client.start(None).await;
514
515 let error = client.send(message).await.unwrap_err();
516
517 assert!(error.to_string().contains("Crypto error"));
518 }
519
520 #[tokio::test]
521 async fn communication_provider_has_outgoing_message_when_sending_through_ipc_client() {
522 let message = OutgoingMessage {
523 payload: vec![],
524 destination: Endpoint::BrowserBackground { id: HostId::Own },
525 topic: None,
526 };
527 let crypto_provider = NoEncryptionCryptoProvider;
528 let communication_provider = TestCommunicationBackend::new();
529 let session_map = InMemorySessionRepository::new(HashMap::new());
530 let client =
531 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
532 let _ = client.start(None).await;
533
534 client.send(message.clone()).await.unwrap();
535
536 let outgoing_messages = communication_provider.outgoing().await;
537 assert_eq!(outgoing_messages, vec![message]);
538 }
539
540 #[tokio::test]
541 async fn returns_received_message_when_received_from_backend() {
542 let message = IncomingMessage {
543 payload: vec![],
544 source: Source::Web {
545 tab_id: 9001,
546 document_id: "doc-1".to_string(),
547 origin: "https://example.com".to_string(),
548 },
549 destination: Endpoint::BrowserBackground { id: HostId::Own },
550 topic: None,
551 };
552 let crypto_provider = NoEncryptionCryptoProvider;
553 let communication_provider = TestCommunicationBackend::new();
554 let session_map = InMemorySessionRepository::new(HashMap::new());
555 let client =
556 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
557 let _ = client.start(None).await;
558
559 let mut subscription = client
560 .subscribe(None)
561 .await
562 .expect("Subscribing should not fail");
563 communication_provider.push_incoming(message.clone());
564 let received_message = subscription.receive(None).await.unwrap();
565
566 assert_eq!(received_message, message);
567 }
568
569 #[tokio::test]
570 async fn skips_non_matching_topics_and_returns_first_matching_message() {
571 let non_matching_message = IncomingMessage {
572 payload: vec![],
573 source: Source::Web {
574 tab_id: 9001,
575 document_id: "doc-1".to_string(),
576 origin: "https://example.com".to_string(),
577 },
578 destination: Endpoint::BrowserBackground { id: HostId::Own },
579 topic: Some("non_matching_topic".to_owned()),
580 };
581 let matching_message = IncomingMessage {
582 payload: vec![109],
583 source: Source::Web {
584 tab_id: 9001,
585 document_id: "doc-1".to_string(),
586 origin: "https://example.com".to_string(),
587 },
588 destination: Endpoint::BrowserBackground { id: HostId::Own },
589 topic: Some("matching_topic".to_owned()),
590 };
591
592 let crypto_provider = NoEncryptionCryptoProvider;
593 let communication_provider = TestCommunicationBackend::new();
594 let session_map = InMemorySessionRepository::new(HashMap::new());
595 let client =
596 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
597 let _ = client.start(None).await;
598 let mut subscription = client
599 .subscribe(Some("matching_topic".to_owned()))
600 .await
601 .expect("Subscribing should not fail");
602 communication_provider.push_incoming(non_matching_message.clone());
603 communication_provider.push_incoming(non_matching_message.clone());
604 communication_provider.push_incoming(matching_message.clone());
605
606 let received_message: IncomingMessage = subscription.receive(None).await.unwrap();
607
608 assert_eq!(received_message, matching_message);
609 }
610
611 #[tokio::test]
612 async fn skips_unrelated_messages_and_returns_typed_message() {
613 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
614 struct TestPayload {
615 some_data: String,
616 }
617
618 impl PayloadTypeName for TestPayload {
619 const PAYLOAD_TYPE_NAME: &str = "TestPayload";
620 }
621
622 let unrelated = IncomingMessage {
623 payload: vec![],
624 source: Source::Web {
625 tab_id: 9001,
626 document_id: "doc-1".to_string(),
627 origin: "https://example.com".to_string(),
628 },
629 destination: Endpoint::BrowserBackground { id: HostId::Own },
630 topic: None,
631 };
632 let typed_message = crate::message::TypedIncomingMessage {
633 payload: TestPayload {
634 some_data: "Hello, world!".to_string(),
635 },
636 source: Source::Web {
637 tab_id: 9001,
638 document_id: "doc-1".to_string(),
639 origin: "https://example.com".to_string(),
640 },
641 destination: Endpoint::BrowserBackground { id: HostId::Own },
642 };
643
644 let crypto_provider = NoEncryptionCryptoProvider;
645 let communication_provider = TestCommunicationBackend::new();
646 let session_map = InMemorySessionRepository::new(HashMap::new());
647 let client =
648 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
649 let _ = client.start(None).await;
650 let mut subscription = client
651 .subscribe_typed::<TestPayload>()
652 .await
653 .expect("Subscribing should not fail");
654 communication_provider.push_incoming(unrelated.clone());
655 communication_provider.push_incoming(unrelated.clone());
656 communication_provider.push_incoming(
657 typed_message
658 .clone()
659 .try_into()
660 .expect("Serialization should not fail"),
661 );
662
663 let received_message = subscription.receive(None).await.unwrap();
664
665 assert_eq!(received_message, typed_message);
666 }
667
668 #[tokio::test]
669 async fn returns_error_if_related_message_was_not_deserializable() {
670 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
671 struct TestPayload {
672 some_data: String,
673 }
674
675 impl PayloadTypeName for TestPayload {
676 const PAYLOAD_TYPE_NAME: &str = "TestPayload";
677 }
678
679 let non_deserializable_message = IncomingMessage {
680 payload: vec![],
681 source: Source::Web {
682 tab_id: 9001,
683 document_id: "doc-1".to_string(),
684 origin: "https://example.com".to_string(),
685 },
686 destination: Endpoint::BrowserBackground { id: HostId::Own },
687 topic: Some("TestPayload".to_owned()),
688 };
689
690 let crypto_provider = NoEncryptionCryptoProvider;
691 let communication_provider = TestCommunicationBackend::new();
692 let session_map = InMemorySessionRepository::new(HashMap::new());
693 let client =
694 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
695 let _ = client.start(None).await;
696 let mut subscription = client
697 .subscribe_typed::<TestPayload>()
698 .await
699 .expect("Subscribing should not fail");
700 communication_provider.push_incoming(non_deserializable_message.clone());
701
702 let result = subscription.receive(None).await;
703 assert!(matches!(result, Err(TypedReceiveError::Typing(_))));
704 }
705
706 #[tokio::test]
707 async fn ipc_client_stops_if_crypto_returns_fatal_send_error() {
708 let message = OutgoingMessage {
709 payload: vec![],
710 destination: Endpoint::BrowserBackground { id: HostId::Own },
711 topic: None,
712 };
713 let crypto_provider = TestCryptoProvider {
714 send_result: Some(Err(TestCryptoError {
715 message: "Crypto error".to_string(),
716 fatal: true,
717 })),
718 receive_result: None,
719 };
720 let communication_provider = TestCommunicationBackend::new();
721 let session_map = TestSessionRepository::new(HashMap::new());
722 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
723 let _ = client.start(None).await;
724
725 let error = client.send(message).await.unwrap_err();
726 let is_running = client.is_running();
727
728 assert!(error.to_string().contains("Crypto error"));
729 assert!(!is_running);
730 }
731
732 #[tokio::test]
733 async fn ipc_client_keeps_running_if_crypto_returns_recoverable_send_error() {
734 let message = OutgoingMessage {
735 payload: vec![],
736 destination: Endpoint::BrowserBackground { id: HostId::Own },
737 topic: None,
738 };
739 let crypto_provider = TestCryptoProvider {
740 send_result: Some(Err(TestCryptoError {
743 message: "Crypto error".to_string(),
744 fatal: false,
745 })),
746 receive_result: None,
748 };
749 let communication_provider = TestCommunicationBackend::new();
750 let session_map = TestSessionRepository::new(HashMap::new());
751 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
752 let _ = client.start(None).await;
753
754 let error = client.send(message).await.unwrap_err();
755 let is_running = client.is_running();
756
757 assert!(error.to_string().contains("Crypto error"));
759 assert!(is_running);
761 }
762
763 #[tokio::test]
764 async fn ipc_client_stops_if_crypto_returns_fatal_receive_error() {
765 let crypto_provider = TestCryptoProvider {
766 send_result: None,
767 receive_result: Some(Err(TestCryptoError {
768 message: "Crypto error".to_string(),
769 fatal: true,
770 })),
771 };
772 let communication_provider = TestCommunicationBackend::new();
773 let session_map = TestSessionRepository::new(HashMap::new());
774 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
775 let cancellation_token = CancellationToken::new();
776 let _ = client.start(Some(cancellation_token.clone())).await;
777
778 tokio::time::sleep(Duration::from_millis(100)).await;
780 let is_running = client.is_running();
781
782 assert!(!is_running);
783 assert!(cancellation_token.is_cancelled());
784 }
785
786 #[tokio::test]
787 async fn ipc_client_keeps_running_if_crypto_returns_recoverable_receive_error() {
788 let crypto_provider = TestCryptoProvider {
789 send_result: None,
790 receive_result: Some(Err(TestCryptoError {
792 message: "Crypto error".to_string(),
793 fatal: false,
794 })),
795 };
796 let communication_provider = TestCommunicationBackend::new();
797 let session_map = TestSessionRepository::new(HashMap::new());
798 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
799 let cancellation_token = CancellationToken::new();
800 let _ = client.start(Some(cancellation_token.clone())).await;
801
802 tokio::time::sleep(Duration::from_millis(100)).await;
804 let is_running = client.is_running();
805
806 assert!(is_running);
807 assert!(!cancellation_token.is_cancelled());
808 }
809
810 #[tokio::test]
811 async fn ipc_client_is_not_running_if_cancellation_token_is_cancelled() {
812 let crypto_provider = TestCryptoProvider {
813 send_result: None,
814 receive_result: None,
815 };
816 let communication_provider = TestCommunicationBackend::new();
817 let session_map = TestSessionRepository::new(HashMap::new());
818 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
819 let cancellation_token = CancellationToken::new();
820 let _ = client.start(Some(cancellation_token.clone())).await;
821
822 tokio::time::sleep(Duration::from_millis(100)).await;
824
825 cancellation_token.cancel();
827 tokio::time::sleep(Duration::from_millis(100)).await;
828 let is_running = client.is_running();
829
830 assert!(!is_running);
831 }
832
833 #[tokio::test]
834 async fn ipc_client_is_running_if_no_errors_are_encountered() {
835 let crypto_provider = TestCryptoProvider {
836 send_result: None,
837 receive_result: None,
838 };
839 let communication_provider = TestCommunicationBackend::new();
840 let session_map = TestSessionRepository::new(HashMap::new());
841 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
842 let cancellation_token = CancellationToken::new();
843 let _ = client.start(Some(cancellation_token.clone())).await;
844
845 tokio::time::sleep(Duration::from_millis(100)).await;
847 let is_running = client.is_running();
848
849 assert!(is_running);
850 assert!(!cancellation_token.is_cancelled());
851 }
852
853 #[tokio::test]
854 async fn ipc_client_is_not_running_if_not_started() {
855 let crypto_provider = TestCryptoProvider {
856 send_result: None,
857 receive_result: None,
858 };
859 let communication_provider = TestCommunicationBackend::new();
860 let session_map = TestSessionRepository::new(HashMap::new());
861 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
862
863 tokio::time::sleep(Duration::from_millis(100)).await;
865 let is_running = client.is_running();
866
867 assert!(!is_running);
868 }
869
870 #[tokio::test]
871 async fn ipc_client_start_returns_error_if_already_running() {
872 let crypto_provider = TestCryptoProvider {
873 send_result: None,
874 receive_result: None,
875 };
876 let communication_provider = TestCommunicationBackend::new();
877 let session_map = TestSessionRepository::new(HashMap::new());
878 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
879 let cancellation_token = CancellationToken::new();
880 let first_result = client.start(Some(cancellation_token.clone())).await;
881 assert_eq!(first_result, Ok(()));
882
883 tokio::time::sleep(Duration::from_millis(100)).await;
885 assert!(client.is_running());
886
887 let second_result = client.start(Some(cancellation_token.clone())).await;
888 assert_eq!(second_result, Err(AlreadyRunningError));
889 }
890
891 mod request {
892 use super::*;
893 use crate::RpcHandler;
894
895 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
896 struct TestRequest {
897 a: i32,
898 b: i32,
899 }
900
901 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
902 struct TestResponse {
903 result: i32,
904 }
905
906 impl RpcRequest for TestRequest {
907 type Response = TestResponse;
908
909 const NAME: &str = "TestRequest";
910 }
911
912 struct TestHandler;
913
914 impl RpcHandler for TestHandler {
915 type Request = TestRequest;
916
917 async fn handle(&self, request: Self::Request) -> TestResponse {
918 TestResponse {
919 result: request.a + request.b,
920 }
921 }
922 }
923
924 #[tokio::test]
925 async fn request_sends_message_and_returns_response() {
926 let crypto_provider = NoEncryptionCryptoProvider;
927 let communication_provider = TestCommunicationBackend::new();
928 let session_map = InMemorySessionRepository::default();
929 let client =
930 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
931 let _ = client.start(None).await;
932 let request = TestRequest { a: 1, b: 2 };
933 let response = TestResponse { result: 3 };
934
935 let request_clone = request.clone();
937 let client_clone = client.clone();
938 let result_handle = tokio::spawn(async move {
939 client_clone
940 .request::<TestRequest>(
941 request_clone,
942 Endpoint::BrowserBackground { id: HostId::Own },
943 None,
944 )
945 .await
946 });
947 tokio::time::sleep(Duration::from_millis(100)).await;
948
949 let outgoing_messages = communication_provider.outgoing().await;
951 let outgoing_request: RpcRequestMessage<TestRequest> =
952 serde_utils::from_slice(&outgoing_messages[0].payload)
953 .expect("Deserialization should not fail");
954 assert_eq!(outgoing_request.request_type, "TestRequest");
955 assert_eq!(outgoing_request.request, request);
956
957 let simulated_response = IncomingRpcResponseMessage {
959 result: Ok(response),
960 request_id: outgoing_request.request_id.clone(),
961 request_type: outgoing_request.request_type.clone(),
962 };
963 let simulated_response = IncomingMessage {
964 payload: serde_utils::to_vec(&simulated_response)
965 .expect("Serialization should not fail"),
966 source: Source::BrowserBackground { id: HostId::Own },
967 destination: Endpoint::Web {
968 tab_id: 9001,
969 document_id: "doc-1".to_string(),
970 },
971 topic: Some(
972 IncomingRpcResponseMessage::<TestRequest>::PAYLOAD_TYPE_NAME.to_owned(),
973 ),
974 };
975 communication_provider.push_incoming(simulated_response);
976
977 let result = result_handle.await.unwrap();
979 assert_eq!(result.unwrap().result, 3);
980 }
981
982 #[tokio::test]
983 async fn incoming_rpc_message_handles_request_and_returns_response() {
984 let crypto_provider = NoEncryptionCryptoProvider;
985 let communication_provider = TestCommunicationBackend::new();
986 let session_map = InMemorySessionRepository::default();
987 let client =
988 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
989 let _ = client.start(None).await;
990 let request_id = uuid::Uuid::new_v4().to_string();
991 let request = TestRequest { a: 1, b: 2 };
992 let response = TestResponse { result: 3 };
993
994 client.register_rpc_handler(TestHandler).await;
996
997 let simulated_request = RpcRequestMessage {
999 request,
1000 request_id: request_id.clone(),
1001 request_type: "TestRequest".to_string(),
1002 };
1003 let simulated_request_message = IncomingMessage {
1004 payload: serde_utils::to_vec(&simulated_request)
1005 .expect("Serialization should not fail"),
1006 source: Source::Web {
1007 tab_id: 9001,
1008 document_id: "doc-1".to_string(),
1009 origin: "https://example.com".to_string(),
1010 },
1011 destination: Endpoint::BrowserBackground { id: HostId::Own },
1012 topic: Some(RPC_REQUEST_PAYLOAD_TYPE_NAME.to_owned()),
1013 };
1014 communication_provider.push_incoming(simulated_request_message);
1015
1016 tokio::time::sleep(Duration::from_millis(100)).await;
1018
1019 let outgoing_messages = communication_provider.outgoing().await;
1021 let outgoing_response: IncomingRpcResponseMessage<TestResponse> =
1022 serde_utils::from_slice(&outgoing_messages[0].payload)
1023 .expect("Deserialization should not fail");
1024
1025 assert_eq!(
1026 outgoing_messages[0].topic,
1027 Some(IncomingRpcResponseMessage::<TestResponse>::PAYLOAD_TYPE_NAME.to_owned())
1028 );
1029 assert_eq!(outgoing_response.request_type, "TestRequest");
1030 assert_eq!(outgoing_response.result, Ok(response));
1031 }
1032 }
1033}