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 broadcast::{BroadcastGroup, EndpointSendResult},
10 constants::CHANNEL_BUFFER_CAPACITY,
11 error::{
12 AlreadyRunningError, BroadcastError, ErrorKind, IpcErrorKind, ReceiveError, SendError,
13 SubscribeError, TypedReceiveError,
14 },
15 fragmentation::{
16 FragmentationError, Fragments, MAX_PAYLOAD_SIZE, REASSEMBLY_IDLE_TIMEOUT,
17 REASSEMBLY_TIMEOUT, Reassembler,
18 },
19 ipc_client_trait::IpcClient,
20 message::{IncomingMessage, OutgoingMessage, PayloadTypeName, TypedIncomingMessage},
21 rpc::{
22 error::RpcError,
23 exec::{
24 handler::{ErasedRpcHandler, RpcRequestInfo},
25 handler_registry::RpcHandlerRegistry,
26 },
27 request_message::{RPC_REQUEST_PAYLOAD_TYPE_NAME, RpcRequestPayload},
28 response_message::OutgoingRpcResponseMessage,
29 },
30 serde_utils,
31 traits::{BroadcastResolution, CommunicationBackend, CryptoProvider, SessionRepository},
32};
33
34pub struct IpcClientSubscription {
39 pub(crate) receiver: tokio::sync::broadcast::Receiver<IncomingMessage>,
40 pub(crate) topic: Option<String>,
41}
42
43pub struct IpcClientTypedSubscription<Payload: DeserializeOwned + PayloadTypeName>(
48 IpcClientSubscription,
49 std::marker::PhantomData<Payload>,
50);
51
52struct IpcClientInner<Crypto, Com, Ses>
54where
55 Crypto: CryptoProvider<Com, Ses>,
56 Com: CommunicationBackend,
57 Ses: SessionRepository<Crypto::Session>,
58{
59 crypto: Crypto,
60 communication: Com,
61 sessions: Ses,
62
63 handlers: RpcHandlerRegistry,
64 reassembler: Reassembler,
65 incoming: Mutex<Option<tokio::sync::broadcast::WeakSender<IncomingMessage>>>,
66 cancellation_token: Mutex<Option<CancellationToken>>,
67}
68
69pub struct IpcClientImpl<Crypto, Com, Ses>
75where
76 Crypto: CryptoProvider<Com, Ses>,
77 Com: CommunicationBackend,
78 Ses: SessionRepository<Crypto::Session>,
79{
80 inner: Arc<IpcClientInner<Crypto, Com, Ses>>,
81}
82
83impl<Crypto, Com, Ses> Clone for IpcClientImpl<Crypto, Com, Ses>
84where
85 Crypto: CryptoProvider<Com, Ses>,
86 Com: CommunicationBackend,
87 Ses: SessionRepository<Crypto::Session>,
88{
89 fn clone(&self) -> Self {
90 Self {
91 inner: self.inner.clone(),
92 }
93 }
94}
95
96impl<Crypto, Com, Ses> IpcClientImpl<Crypto, Com, Ses>
97where
98 Crypto: CryptoProvider<Com, Ses>,
99 Com: CommunicationBackend,
100 Ses: SessionRepository<Crypto::Session>,
101{
102 pub fn new(crypto: Crypto, communication: Com, sessions: Ses) -> Self {
105 Self {
106 inner: Arc::new(IpcClientInner {
107 crypto,
108 communication,
109 sessions,
110
111 handlers: RpcHandlerRegistry::new(),
112 reassembler: Reassembler::new(REASSEMBLY_TIMEOUT, REASSEMBLY_IDLE_TIMEOUT),
113 incoming: Mutex::new(None),
114 cancellation_token: Mutex::new(None),
115 }),
116 }
117 }
118}
119
120#[async_trait::async_trait]
121impl<Crypto, Com, Ses> crate::ipc_client_trait::IpcClient for IpcClientImpl<Crypto, Com, Ses>
122where
123 Crypto: CryptoProvider<Com, Ses>,
124 Com: CommunicationBackend,
125 Ses: SessionRepository<Crypto::Session>,
126{
127 async fn start(
128 &self,
129 cancellation_token: Option<CancellationToken>,
130 ) -> Result<(), AlreadyRunningError> {
131 if self.is_running() {
132 return Err(AlreadyRunningError);
133 }
134
135 let cancellation_token = cancellation_token.unwrap_or_default();
136 self.inner
137 .cancellation_token
138 .lock()
139 .expect("Failed to lock cancellation token mutex")
140 .replace(cancellation_token.clone());
141
142 let com_receiver = self.inner.communication.subscribe().await;
143 let (client_tx, _) = tokio::sync::broadcast::channel(CHANNEL_BUFFER_CAPACITY);
144
145 self.inner
146 .incoming
147 .lock()
148 .expect("Failed to lock incoming mutex")
149 .replace(client_tx.downgrade());
150
151 let inner = self.inner.clone();
152 let future = async move {
153 loop {
154 select! {
155 _ = cancellation_token.cancelled() => {
156 tracing::debug!("Cancellation signal received, stopping IPC client");
157 break;
158 }
159 received = inner.crypto.receive(&com_receiver, &inner.communication, &inner.sessions) => {
160 match received {
161 Ok(message) => {
162 let Some(message) = inner.reassembler.process(message) else {
163 continue;
164 };
165 if message.topic.as_deref() == Some(RPC_REQUEST_PAYLOAD_TYPE_NAME) {
166 handle_rpc_request(&inner, message);
167 } else if client_tx.send(message).is_err() {
168 tracing::debug!("No subscriptions for incoming message, dropping it");
169 }
170 }
171 Err(error) if matches!(error.kind(), ErrorKind::Fatal) => {
172 tracing::error!(?error, "Fatal error receiving message, stopping IPC client");
173 break;
174 }
175 Err(error) => {
176 tracing::warn!(?error, "Recoverable error receiving message, continuing");
177 }
178 }
179 }
180 }
181 }
182 tracing::debug!("IPC client shutting down");
183 stop_inner(&inner);
184 };
185
186 bitwarden_threading::spawn(future);
187
188 Ok(())
189 }
190
191 fn is_running(&self) -> bool {
192 let has_incoming = self
193 .inner
194 .incoming
195 .lock()
196 .expect("Failed to lock incoming mutex")
197 .as_ref()
198 .is_some_and(|sender| sender.strong_count() > 0);
199 let has_cancellation_token = self
200 .inner
201 .cancellation_token
202 .lock()
203 .expect("Failed to lock cancellation token mutex")
204 .is_some();
205 has_incoming && has_cancellation_token
206 }
207
208 async fn send(&self, message: OutgoingMessage) -> Result<(), SendError> {
209 send_message(&self.inner, message).await.result
210 }
211
212 async fn broadcast(
213 &self,
214 group: BroadcastGroup,
215 payload: Vec<u8>,
216 topic: Option<String>,
217 ) -> Result<Vec<EndpointSendResult>, BroadcastError> {
218 let endpoints = match self.inner.communication.resolve(&group).await {
219 BroadcastResolution::Endpoints(endpoints) => endpoints,
220 BroadcastResolution::Delegate(_) => return Err(BroadcastError::DelegationUnsupported),
221 BroadcastResolution::Unsupported => return Err(BroadcastError::Unsupported),
222 };
223
224 if let Some(endpoint) = endpoints.iter().find(|endpoint| !group.contains(endpoint)) {
227 return Err(BroadcastError::OutOfScope(endpoint.clone()));
228 }
229
230 let mut results = Vec::with_capacity(endpoints.len());
235 for endpoint in endpoints {
236 let message = OutgoingMessage {
237 payload: payload.clone(),
238 destination: endpoint.clone(),
239 topic: topic.clone(),
240 };
241 let outcome = send_message(&self.inner, message).await;
242 results.push(EndpointSendResult {
243 endpoint,
244 result: outcome.result,
245 });
246 if outcome.fatal {
247 break;
248 }
249 }
250
251 Ok(results)
252 }
253
254 async fn subscribe(
255 &self,
256 topic: Option<String>,
257 ) -> Result<IpcClientSubscription, SubscribeError> {
258 Ok(IpcClientSubscription {
259 receiver: self
260 .inner
261 .incoming
262 .lock()
263 .expect("Failed to lock incoming mutex")
264 .as_ref()
265 .and_then(|sender| sender.upgrade())
266 .ok_or(SubscribeError::NotStarted)?
267 .subscribe(),
268 topic,
269 })
270 }
271
272 async fn register_rpc_handler_erased(&self, name: &str, handler: Box<dyn ErasedRpcHandler>) {
273 self.inner
274 .handlers
275 .register_erased(name.to_owned(), handler)
276 .await;
277 }
278}
279
280struct SendOutcome {
282 result: Result<(), SendError>,
283 fatal: bool,
285}
286
287async fn send_message<Crypto, Com, Ses>(
291 inner: &IpcClientInner<Crypto, Com, Ses>,
292 message: OutgoingMessage,
293) -> SendOutcome
294where
295 Crypto: CryptoProvider<Com, Ses>,
296 Com: CommunicationBackend,
297 Ses: SessionRepository<Crypto::Session>,
298{
299 let fragments = match Fragments::split(message, inner.crypto.max_payload_len()) {
300 Ok(fragments) => fragments,
301 Err(e) => {
302 return SendOutcome {
303 result: Err(SendError::Other(e.to_string())),
304 fatal: false,
305 };
306 }
307 };
308
309 for fragment in fragments {
310 let outcome = send_fragment(inner, fragment).await;
311 if outcome.result.is_err() {
312 return outcome;
313 }
314 }
315
316 SendOutcome {
317 result: Ok(()),
318 fatal: false,
319 }
320}
321
322async fn send_fragment<Crypto, Com, Ses>(
324 inner: &IpcClientInner<Crypto, Com, Ses>,
325 message: OutgoingMessage,
326) -> SendOutcome
327where
328 Crypto: CryptoProvider<Com, Ses>,
329 Com: CommunicationBackend,
330 Ses: SessionRepository<Crypto::Session>,
331{
332 let result = inner
333 .crypto
334 .send(&inner.communication, &inner.sessions, message)
335 .await;
336
337 if let Err(ref error) = result {
338 match error.kind() {
339 ErrorKind::Fatal => {
340 tracing::error!(?error, "Fatal error sending message, stopping IPC client");
341 stop_inner(inner);
342 }
343 ErrorKind::Unreachable => {}
345 ErrorKind::Other => {
347 tracing::debug!(
348 ?error,
349 "Recoverable error sending message, IPC client will continue running"
350 );
351 }
352 }
353 }
354
355 SendOutcome {
356 fatal: matches!(&result, Err(e) if e.kind() == ErrorKind::Fatal),
357 result: result.map_err(|e| match e.kind() {
358 ErrorKind::Unreachable => SendError::Unreachable,
359 _ => SendError::Other(format!("{e:?}")),
360 }),
361 }
362}
363
364fn stop_inner<Crypto, Com, Ses>(inner: &IpcClientInner<Crypto, Com, Ses>)
365where
366 Crypto: CryptoProvider<Com, Ses>,
367 Com: CommunicationBackend,
368 Ses: SessionRepository<Crypto::Session>,
369{
370 let mut cancellation_token = inner
371 .cancellation_token
372 .lock()
373 .expect("Failed to lock cancellation token mutex");
374
375 if let Some(cancellation_token) = cancellation_token.take() {
376 cancellation_token.cancel();
377 }
378}
379
380fn handle_rpc_request<Crypto, Com, Ses>(
381 inner: &Arc<IpcClientInner<Crypto, Com, Ses>>,
382 incoming_message: IncomingMessage,
383) where
384 Crypto: CryptoProvider<Com, Ses>,
385 Com: CommunicationBackend,
386 Ses: SessionRepository<Crypto::Session>,
387{
388 let inner = inner.clone();
389 let future = async move {
390 #[derive(Debug, Error)]
391 enum HandleError {
392 #[error("Failed to deserialize request message: {0}")]
393 Deserialize(String),
394
395 #[error("Failed to serialize response message: {0}")]
396 Serialize(String),
397 }
398
399 async fn handle(
400 incoming_message: IncomingMessage,
401 handlers: &RpcHandlerRegistry,
402 ) -> Result<OutgoingMessage, HandleError> {
403 let IncomingMessage {
404 payload, source, ..
405 } = incoming_message;
406 let request = RpcRequestPayload::from_slice(payload).map_err(
407 |e: serde_utils::DeserializeError| HandleError::Deserialize(e.to_string()),
408 )?;
409
410 let info = RpcRequestInfo {
411 source: source.clone(),
412 };
413 let response = handlers.handle(&request, info).await;
414
415 let response_message = OutgoingRpcResponseMessage {
416 request_id: request.request_id(),
417 request_type: request.request_type(),
418 result: response,
419 };
420
421 let mut payload = serde_utils::to_vec(&response_message)
423 .map_err(|e: serde_utils::SerializeError| HandleError::Serialize(e.to_string()))?;
424 if payload.len() > MAX_PAYLOAD_SIZE {
425 let error = FragmentationError::PayloadTooLarge(payload.len()).to_string();
427 let error_message = OutgoingRpcResponseMessage {
428 result: Err(RpcError::ResponseSerialization(error)),
429 ..response_message
430 };
431 payload = serde_utils::to_vec(&error_message).map_err(
432 |e: serde_utils::SerializeError| HandleError::Serialize(e.to_string()),
433 )?;
434 }
435
436 Ok(OutgoingMessage {
437 payload,
438 destination: source.into(),
439 topic: Some(request.response_topic().to_owned()),
440 })
441 }
442
443 match handle(incoming_message, &inner.handlers).await {
444 Ok(outgoing_message) => {
445 match (IpcClientImpl { inner }).send(outgoing_message).await {
447 Ok(()) | Err(SendError::Unreachable) => {}
449 Err(error) => tracing::error!(%error, "Failed to send response message"),
450 }
451 }
452 Err(error) => {
453 tracing::error!(%error, "Error handling RPC request");
454 }
455 }
456 };
457
458 bitwarden_threading::spawn(future);
459}
460
461impl IpcClientSubscription {
462 pub async fn receive(
465 &mut self,
466 cancellation_token: Option<CancellationToken>,
467 ) -> Result<IncomingMessage, ReceiveError> {
468 let cancellation_token = cancellation_token.unwrap_or_default();
469
470 loop {
471 select! {
472 _ = cancellation_token.cancelled() => {
473 return Err(ReceiveError::Cancelled)
474 }
475 result = self.receiver.recv() => {
476 let received = result?;
477 if self.topic.is_none() || received.topic == self.topic {
478 return Ok::<IncomingMessage, ReceiveError>(received);
479 }
480 }
481 }
482 }
483 }
484}
485
486impl<Payload> IpcClientTypedSubscription<Payload>
487where
488 Payload: DeserializeOwned + PayloadTypeName,
489{
490 pub(crate) fn new(subscription: IpcClientSubscription) -> Self {
491 Self(subscription, std::marker::PhantomData)
492 }
493
494 pub async fn receive(
497 &mut self,
498 cancellation_token: Option<CancellationToken>,
499 ) -> Result<TypedIncomingMessage<Payload>, TypedReceiveError> {
500 let received = self.0.receive(cancellation_token).await?;
501 received
502 .try_into()
503 .map_err(|e: serde_utils::DeserializeError| TypedReceiveError::Typing(e.to_string()))
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use std::{collections::HashMap, time::Duration};
510
511 use bitwarden_threading::time::sleep;
512 use serde::{Deserialize, Serialize};
513
514 use super::*;
515 use crate::{
516 IpcClientExt,
517 endpoint::{Endpoint, HostId, Source},
518 ipc_client_trait::IpcClient,
519 message::PayloadTypeName,
520 rpc::{
521 request::RpcRequest,
522 request_message::{RPC_REQUEST_PAYLOAD_TYPE_NAME, RpcRequestMessage},
523 response_message::IncomingRpcResponseMessage,
524 },
525 traits::{InMemorySessionRepository, NoEncryptionCryptoProvider, TestCommunicationBackend},
526 };
527
528 #[derive(Debug, Clone)]
531 struct TestCryptoError {
532 #[allow(dead_code)]
535 message: String,
536 fatal: bool,
537 }
538
539 impl IpcErrorKind for TestCryptoError {
540 fn kind(&self) -> ErrorKind {
541 if self.fatal {
542 ErrorKind::Fatal
543 } else {
544 ErrorKind::Other
545 }
546 }
547 }
548
549 struct TestCryptoProvider {
550 send_result: Option<Result<(), TestCryptoError>>,
552 receive_result: Option<Result<IncomingMessage, TestCryptoError>>,
554 }
555
556 type TestSessionRepository = InMemorySessionRepository<String>;
557 impl CryptoProvider<TestCommunicationBackend, TestSessionRepository> for TestCryptoProvider {
558 type Session = String;
559 type SendError = TestCryptoError;
560 type ReceiveError = TestCryptoError;
561
562 fn max_payload_len(&self) -> usize {
563 usize::MAX
564 }
565
566 async fn receive(
567 &self,
568 _receiver: &<TestCommunicationBackend as CommunicationBackend>::Receiver,
569 _communication: &TestCommunicationBackend,
570 _sessions: &TestSessionRepository,
571 ) -> Result<IncomingMessage, Self::ReceiveError> {
572 match &self.receive_result {
573 Some(result) => {
574 sleep(Duration::from_millis(5)).await;
579 result.clone()
580 }
581 None => {
582 sleep(Duration::from_secs(600)).await;
584 Err(TestCryptoError {
585 message: "Simulated timeout".to_string(),
586 fatal: true,
587 })
588 }
589 }
590 }
591
592 async fn send(
593 &self,
594 _communication: &TestCommunicationBackend,
595 _sessions: &TestSessionRepository,
596 _message: OutgoingMessage,
597 ) -> Result<(), Self::SendError> {
598 match &self.send_result {
599 Some(result) => result.clone(),
600 None => {
601 sleep(Duration::from_secs(600)).await;
603 Err(TestCryptoError {
604 message: "Simulated timeout".to_string(),
605 fatal: true,
606 })
607 }
608 }
609 }
610 }
611
612 #[tokio::test]
613 async fn returns_send_error_when_crypto_provider_returns_error() {
614 let message = OutgoingMessage {
615 payload: vec![],
616 destination: Endpoint::BrowserBackground { id: HostId::Own },
617 topic: None,
618 };
619 let crypto_provider = TestCryptoProvider {
620 send_result: Some(Err(TestCryptoError {
621 message: "Crypto error".to_string(),
622 fatal: false,
623 })),
624 receive_result: Some(Err(TestCryptoError {
625 message: "Should not have be called".to_string(),
626 fatal: false,
627 })),
628 };
629 let communication_provider = TestCommunicationBackend::new();
630 let session_map = TestSessionRepository::new(HashMap::new());
631 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
632 let _ = client.start(None).await;
633
634 let error = client.send(message).await.unwrap_err();
635
636 assert!(error.to_string().contains("Crypto error"));
637 }
638
639 #[tokio::test]
640 async fn communication_provider_has_outgoing_message_when_sending_through_ipc_client() {
641 let message = OutgoingMessage {
642 payload: vec![],
643 destination: Endpoint::BrowserBackground { id: HostId::Own },
644 topic: None,
645 };
646 let crypto_provider = NoEncryptionCryptoProvider;
647 let communication_provider = TestCommunicationBackend::new();
648 let session_map = InMemorySessionRepository::new(HashMap::new());
649 let client =
650 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
651 let _ = client.start(None).await;
652
653 client.send(message.clone()).await.unwrap();
654
655 let outgoing_messages = communication_provider.outgoing().await;
656 assert_eq!(outgoing_messages, vec![message]);
657 }
658
659 #[tokio::test]
660 async fn returns_received_message_when_received_from_backend() {
661 let message = IncomingMessage {
662 payload: vec![],
663 source: Source::Web {
664 tab_id: 9001,
665 document_id: "doc-1".to_string(),
666 origin: "https://example.com".to_string(),
667 },
668 destination: Endpoint::BrowserBackground { id: HostId::Own },
669 topic: None,
670 };
671 let crypto_provider = NoEncryptionCryptoProvider;
672 let communication_provider = TestCommunicationBackend::new();
673 let session_map = InMemorySessionRepository::new(HashMap::new());
674 let client =
675 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
676 let _ = client.start(None).await;
677
678 let mut subscription = client
679 .subscribe(None)
680 .await
681 .expect("Subscribing should not fail");
682 communication_provider.push_incoming(message.clone());
683 let received_message = subscription.receive(None).await.unwrap();
684
685 assert_eq!(received_message, message);
686 }
687
688 #[tokio::test]
689 async fn channel_keeps_no_message_every_subscription_has_received() {
690 let message = IncomingMessage {
691 payload: vec![1, 2, 3],
692 source: Source::DesktopMain,
693 destination: Endpoint::BrowserBackground { id: HostId::Own },
694 topic: Some("topic".to_owned()),
695 };
696 let communication_provider = TestCommunicationBackend::new();
697 let client = IpcClientImpl::new(
698 NoEncryptionCryptoProvider,
699 communication_provider.clone(),
700 InMemorySessionRepository::new(HashMap::new()),
701 );
702 let _ = client.start(None).await;
703 let queued = || {
704 client
705 .inner
706 .incoming
707 .lock()
708 .unwrap()
709 .as_ref()
710 .and_then(|sender| sender.upgrade())
711 .expect("Client should be running")
712 .len()
713 };
714
715 communication_provider.push_incoming(message.clone());
717 sleep(Duration::from_millis(50)).await;
718 assert_eq!(queued(), 0);
719 assert!(client.is_running());
720
721 let mut subscription = client.subscribe(None).await.unwrap();
722 communication_provider.push_incoming(message.clone());
723 assert_eq!(subscription.receive(None).await.unwrap(), message);
724 assert_eq!(queued(), 0);
725 }
726
727 #[tokio::test]
728 async fn skips_non_matching_topics_and_returns_first_matching_message() {
729 let non_matching_message = IncomingMessage {
730 payload: vec![],
731 source: Source::Web {
732 tab_id: 9001,
733 document_id: "doc-1".to_string(),
734 origin: "https://example.com".to_string(),
735 },
736 destination: Endpoint::BrowserBackground { id: HostId::Own },
737 topic: Some("non_matching_topic".to_owned()),
738 };
739 let matching_message = IncomingMessage {
740 payload: vec![109],
741 source: Source::Web {
742 tab_id: 9001,
743 document_id: "doc-1".to_string(),
744 origin: "https://example.com".to_string(),
745 },
746 destination: Endpoint::BrowserBackground { id: HostId::Own },
747 topic: Some("matching_topic".to_owned()),
748 };
749
750 let crypto_provider = NoEncryptionCryptoProvider;
751 let communication_provider = TestCommunicationBackend::new();
752 let session_map = InMemorySessionRepository::new(HashMap::new());
753 let client =
754 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
755 let _ = client.start(None).await;
756 let mut subscription = client
757 .subscribe(Some("matching_topic".to_owned()))
758 .await
759 .expect("Subscribing should not fail");
760 communication_provider.push_incoming(non_matching_message.clone());
761 communication_provider.push_incoming(non_matching_message.clone());
762 communication_provider.push_incoming(matching_message.clone());
763
764 let received_message: IncomingMessage = subscription.receive(None).await.unwrap();
765
766 assert_eq!(received_message, matching_message);
767 }
768
769 #[tokio::test]
770 async fn skips_unrelated_messages_and_returns_typed_message() {
771 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
772 struct TestPayload {
773 some_data: String,
774 }
775
776 impl PayloadTypeName for TestPayload {
777 const PAYLOAD_TYPE_NAME: &str = "TestPayload";
778 }
779
780 let unrelated = IncomingMessage {
781 payload: vec![],
782 source: Source::Web {
783 tab_id: 9001,
784 document_id: "doc-1".to_string(),
785 origin: "https://example.com".to_string(),
786 },
787 destination: Endpoint::BrowserBackground { id: HostId::Own },
788 topic: None,
789 };
790 let typed_message = crate::message::TypedIncomingMessage {
791 payload: TestPayload {
792 some_data: "Hello, world!".to_string(),
793 },
794 source: Source::Web {
795 tab_id: 9001,
796 document_id: "doc-1".to_string(),
797 origin: "https://example.com".to_string(),
798 },
799 destination: Endpoint::BrowserBackground { id: HostId::Own },
800 };
801
802 let crypto_provider = NoEncryptionCryptoProvider;
803 let communication_provider = TestCommunicationBackend::new();
804 let session_map = InMemorySessionRepository::new(HashMap::new());
805 let client =
806 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
807 let _ = client.start(None).await;
808 let mut subscription = client
809 .subscribe_typed::<TestPayload>()
810 .await
811 .expect("Subscribing should not fail");
812 communication_provider.push_incoming(unrelated.clone());
813 communication_provider.push_incoming(unrelated.clone());
814 communication_provider.push_incoming(
815 typed_message
816 .clone()
817 .try_into()
818 .expect("Serialization should not fail"),
819 );
820
821 let received_message = subscription.receive(None).await.unwrap();
822
823 assert_eq!(received_message, typed_message);
824 }
825
826 #[tokio::test]
827 async fn returns_error_if_related_message_was_not_deserializable() {
828 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
829 struct TestPayload {
830 some_data: String,
831 }
832
833 impl PayloadTypeName for TestPayload {
834 const PAYLOAD_TYPE_NAME: &str = "TestPayload";
835 }
836
837 let non_deserializable_message = IncomingMessage {
838 payload: vec![],
839 source: Source::Web {
840 tab_id: 9001,
841 document_id: "doc-1".to_string(),
842 origin: "https://example.com".to_string(),
843 },
844 destination: Endpoint::BrowserBackground { id: HostId::Own },
845 topic: Some("TestPayload".to_owned()),
846 };
847
848 let crypto_provider = NoEncryptionCryptoProvider;
849 let communication_provider = TestCommunicationBackend::new();
850 let session_map = InMemorySessionRepository::new(HashMap::new());
851 let client =
852 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
853 let _ = client.start(None).await;
854 let mut subscription = client
855 .subscribe_typed::<TestPayload>()
856 .await
857 .expect("Subscribing should not fail");
858 communication_provider.push_incoming(non_deserializable_message.clone());
859
860 let result = subscription.receive(None).await;
861 assert!(matches!(result, Err(TypedReceiveError::Typing(_))));
862 }
863
864 #[tokio::test]
865 async fn ipc_client_stops_if_crypto_returns_fatal_send_error() {
866 let message = OutgoingMessage {
867 payload: vec![],
868 destination: Endpoint::BrowserBackground { id: HostId::Own },
869 topic: None,
870 };
871 let crypto_provider = TestCryptoProvider {
872 send_result: Some(Err(TestCryptoError {
873 message: "Crypto error".to_string(),
874 fatal: true,
875 })),
876 receive_result: None,
877 };
878 let communication_provider = TestCommunicationBackend::new();
879 let session_map = TestSessionRepository::new(HashMap::new());
880 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
881 let _ = client.start(None).await;
882
883 let error = client.send(message).await.unwrap_err();
884 let is_running = client.is_running();
885
886 assert!(error.to_string().contains("Crypto error"));
887 assert!(!is_running);
888 }
889
890 #[tokio::test]
891 async fn ipc_client_keeps_running_if_crypto_returns_recoverable_send_error() {
892 let message = OutgoingMessage {
893 payload: vec![],
894 destination: Endpoint::BrowserBackground { id: HostId::Own },
895 topic: None,
896 };
897 let crypto_provider = TestCryptoProvider {
898 send_result: Some(Err(TestCryptoError {
901 message: "Crypto error".to_string(),
902 fatal: false,
903 })),
904 receive_result: None,
906 };
907 let communication_provider = TestCommunicationBackend::new();
908 let session_map = TestSessionRepository::new(HashMap::new());
909 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
910 let _ = client.start(None).await;
911
912 let error = client.send(message).await.unwrap_err();
913 let is_running = client.is_running();
914
915 assert!(error.to_string().contains("Crypto error"));
917 assert!(is_running);
919 }
920
921 #[tokio::test]
922 async fn ipc_client_stops_if_crypto_returns_fatal_receive_error() {
923 let crypto_provider = TestCryptoProvider {
924 send_result: None,
925 receive_result: Some(Err(TestCryptoError {
926 message: "Crypto error".to_string(),
927 fatal: true,
928 })),
929 };
930 let communication_provider = TestCommunicationBackend::new();
931 let session_map = TestSessionRepository::new(HashMap::new());
932 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
933 let cancellation_token = CancellationToken::new();
934 let _ = client.start(Some(cancellation_token.clone())).await;
935
936 tokio::time::sleep(Duration::from_millis(100)).await;
938 let is_running = client.is_running();
939
940 assert!(!is_running);
941 assert!(cancellation_token.is_cancelled());
942 }
943
944 #[tokio::test]
945 async fn ipc_client_keeps_running_if_crypto_returns_recoverable_receive_error() {
946 let crypto_provider = TestCryptoProvider {
947 send_result: None,
948 receive_result: Some(Err(TestCryptoError {
950 message: "Crypto error".to_string(),
951 fatal: false,
952 })),
953 };
954 let communication_provider = TestCommunicationBackend::new();
955 let session_map = TestSessionRepository::new(HashMap::new());
956 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
957 let cancellation_token = CancellationToken::new();
958 let _ = client.start(Some(cancellation_token.clone())).await;
959
960 tokio::time::sleep(Duration::from_millis(100)).await;
962 let is_running = client.is_running();
963
964 assert!(is_running);
965 assert!(!cancellation_token.is_cancelled());
966 }
967
968 #[tokio::test]
969 async fn ipc_client_is_not_running_if_cancellation_token_is_cancelled() {
970 let crypto_provider = TestCryptoProvider {
971 send_result: None,
972 receive_result: None,
973 };
974 let communication_provider = TestCommunicationBackend::new();
975 let session_map = TestSessionRepository::new(HashMap::new());
976 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
977 let cancellation_token = CancellationToken::new();
978 let _ = client.start(Some(cancellation_token.clone())).await;
979
980 tokio::time::sleep(Duration::from_millis(100)).await;
982
983 cancellation_token.cancel();
985 tokio::time::sleep(Duration::from_millis(100)).await;
986 let is_running = client.is_running();
987
988 assert!(!is_running);
989 }
990
991 #[tokio::test]
992 async fn ipc_client_is_running_if_no_errors_are_encountered() {
993 let crypto_provider = TestCryptoProvider {
994 send_result: None,
995 receive_result: None,
996 };
997 let communication_provider = TestCommunicationBackend::new();
998 let session_map = TestSessionRepository::new(HashMap::new());
999 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
1000 let cancellation_token = CancellationToken::new();
1001 let _ = client.start(Some(cancellation_token.clone())).await;
1002
1003 tokio::time::sleep(Duration::from_millis(100)).await;
1005 let is_running = client.is_running();
1006
1007 assert!(is_running);
1008 assert!(!cancellation_token.is_cancelled());
1009 }
1010
1011 #[tokio::test]
1012 async fn ipc_client_is_not_running_if_not_started() {
1013 let crypto_provider = TestCryptoProvider {
1014 send_result: None,
1015 receive_result: None,
1016 };
1017 let communication_provider = TestCommunicationBackend::new();
1018 let session_map = TestSessionRepository::new(HashMap::new());
1019 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
1020
1021 tokio::time::sleep(Duration::from_millis(100)).await;
1023 let is_running = client.is_running();
1024
1025 assert!(!is_running);
1026 }
1027
1028 #[tokio::test]
1029 async fn ipc_client_start_returns_error_if_already_running() {
1030 let crypto_provider = TestCryptoProvider {
1031 send_result: None,
1032 receive_result: None,
1033 };
1034 let communication_provider = TestCommunicationBackend::new();
1035 let session_map = TestSessionRepository::new(HashMap::new());
1036 let client = IpcClientImpl::new(crypto_provider, communication_provider, session_map);
1037 let cancellation_token = CancellationToken::new();
1038 let first_result = client.start(Some(cancellation_token.clone())).await;
1039 assert_eq!(first_result, Ok(()));
1040
1041 tokio::time::sleep(Duration::from_millis(100)).await;
1043 assert!(client.is_running());
1044
1045 let second_result = client.start(Some(cancellation_token.clone())).await;
1046 assert_eq!(second_result, Err(AlreadyRunningError));
1047 }
1048
1049 mod request {
1050 use super::*;
1051 use crate::{RpcHandler, RpcRequestInfo};
1052
1053 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
1054 struct TestRequest {
1055 a: i32,
1056 b: i32,
1057 }
1058
1059 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
1060 struct TestResponse {
1061 result: i32,
1062 }
1063
1064 impl RpcRequest for TestRequest {
1065 type Response = TestResponse;
1066
1067 const NAME: &str = "TestRequest";
1068 }
1069
1070 struct TestHandler;
1071
1072 impl RpcHandler for TestHandler {
1073 type Request = TestRequest;
1074
1075 async fn handle(&self, request: Self::Request, _info: RpcRequestInfo) -> TestResponse {
1076 TestResponse {
1077 result: request.a + request.b,
1078 }
1079 }
1080 }
1081
1082 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
1085 struct EchoSourceRequest;
1086
1087 impl RpcRequest for EchoSourceRequest {
1088 type Response = Source;
1089
1090 const NAME: &str = "EchoSourceRequest";
1091 }
1092
1093 struct EchoSourceHandler;
1094
1095 impl RpcHandler for EchoSourceHandler {
1096 type Request = EchoSourceRequest;
1097
1098 async fn handle(&self, _request: Self::Request, info: RpcRequestInfo) -> Source {
1099 info.source
1100 }
1101 }
1102
1103 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
1107 struct OtherRequest;
1108
1109 #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
1110 enum OtherResponse {
1111 Available,
1112 }
1113
1114 impl RpcRequest for OtherRequest {
1115 type Response = OtherResponse;
1116
1117 const NAME: &str = "OtherRequest";
1118 }
1119
1120 #[tokio::test]
1121 async fn request_sends_message_and_returns_response() {
1122 let crypto_provider = NoEncryptionCryptoProvider;
1123 let communication_provider = TestCommunicationBackend::new();
1124 let session_map = InMemorySessionRepository::default();
1125 let client =
1126 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
1127 let _ = client.start(None).await;
1128 let request = TestRequest { a: 1, b: 2 };
1129 let response = TestResponse { result: 3 };
1130
1131 let request_clone = request.clone();
1133 let client_clone = client.clone();
1134 let result_handle = tokio::spawn(async move {
1135 client_clone
1136 .request::<TestRequest>(
1137 request_clone,
1138 Endpoint::BrowserBackground { id: HostId::Own },
1139 None,
1140 )
1141 .await
1142 });
1143 tokio::time::sleep(Duration::from_millis(100)).await;
1144
1145 let outgoing_messages = communication_provider.outgoing().await;
1147 let outgoing_request: RpcRequestMessage<TestRequest> =
1148 serde_utils::from_slice(&outgoing_messages[0].payload)
1149 .expect("Deserialization should not fail");
1150 assert_eq!(outgoing_request.request_type, "TestRequest");
1151 assert_eq!(outgoing_request.request, request);
1152
1153 let simulated_response = IncomingRpcResponseMessage {
1155 result: Ok(response),
1156 request_id: outgoing_request.request_id.clone(),
1157 request_type: outgoing_request.request_type.clone(),
1158 };
1159 let simulated_response = IncomingMessage {
1160 payload: serde_utils::to_vec(&simulated_response)
1161 .expect("Serialization should not fail"),
1162 source: Source::BrowserBackground { id: HostId::Own },
1163 destination: Endpoint::Web {
1164 tab_id: 9001,
1165 document_id: "doc-1".to_string(),
1166 },
1167 topic: Some(outgoing_request.response_topic.clone()),
1170 };
1171 communication_provider.push_incoming(simulated_response);
1172
1173 let result = result_handle.await.unwrap();
1175 assert_eq!(result.unwrap().result, 3);
1176 }
1177
1178 #[tokio::test]
1183 async fn concurrent_request_of_a_different_type_does_not_disturb_a_pending_request() {
1184 let crypto_provider = NoEncryptionCryptoProvider;
1185 let communication_provider = TestCommunicationBackend::new();
1186 let session_map = InMemorySessionRepository::default();
1187 let client =
1188 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
1189 let _ = client.start(None).await;
1190
1191 let test_handle = {
1193 let client = client.clone();
1194 tokio::spawn(async move {
1195 client
1196 .request::<TestRequest>(
1197 TestRequest { a: 1, b: 2 },
1198 Endpoint::BrowserBackground { id: HostId::Own },
1199 None,
1200 )
1201 .await
1202 })
1203 };
1204 let other_handle = {
1205 let client = client.clone();
1206 tokio::spawn(async move {
1207 client
1208 .request::<OtherRequest>(
1209 OtherRequest,
1210 Endpoint::BrowserBackground { id: HostId::Own },
1211 None,
1212 )
1213 .await
1214 })
1215 };
1216 tokio::time::sleep(Duration::from_millis(100)).await;
1217
1218 let outgoing = communication_provider.outgoing().await;
1221 let requests: HashMap<String, RpcRequestMessage<serde_json::Value>> = outgoing
1222 .iter()
1223 .map(|message| {
1224 let partial: RpcRequestMessage<serde_json::Value> =
1225 serde_utils::from_slice(&message.payload)
1226 .expect("Deserialization should not fail");
1227 (partial.request_type.clone(), partial)
1228 })
1229 .collect();
1230
1231 let respond = |topic: String, payload: Vec<u8>| {
1232 communication_provider.push_incoming(IncomingMessage {
1233 payload,
1234 source: Source::BrowserBackground { id: HostId::Own },
1235 destination: Endpoint::Web {
1236 tab_id: 9001,
1237 document_id: "doc-1".to_string(),
1238 },
1239 topic: Some(topic),
1240 });
1241 };
1242
1243 respond(
1246 requests["OtherRequest"].response_topic.clone(),
1247 serde_utils::to_vec(&IncomingRpcResponseMessage {
1248 result: Ok(OtherResponse::Available),
1249 request_id: requests["OtherRequest"].request_id.clone(),
1250 request_type: "OtherRequest".to_string(),
1251 })
1252 .expect("Serialization should not fail"),
1253 );
1254
1255 respond(
1256 requests["TestRequest"].response_topic.clone(),
1257 serde_utils::to_vec(&IncomingRpcResponseMessage {
1258 result: Ok(TestResponse { result: 3 }),
1259 request_id: requests["TestRequest"].request_id.clone(),
1260 request_type: "TestRequest".to_string(),
1261 })
1262 .expect("Serialization should not fail"),
1263 );
1264
1265 assert_eq!(
1266 other_handle
1267 .await
1268 .unwrap()
1269 .expect("OtherRequest should succeed"),
1270 OtherResponse::Available
1271 );
1272 assert_eq!(
1273 test_handle
1274 .await
1275 .unwrap()
1276 .expect("TestRequest should succeed"),
1277 TestResponse { result: 3 }
1278 );
1279 }
1280
1281 #[tokio::test]
1286 async fn malformed_response_surfaces_error_instead_of_hanging() {
1287 let crypto_provider = NoEncryptionCryptoProvider;
1288 let communication_provider = TestCommunicationBackend::new();
1289 let session_map = InMemorySessionRepository::default();
1290 let client =
1291 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
1292 let _ = client.start(None).await;
1293
1294 let result_handle = {
1295 let client = client.clone();
1296 tokio::spawn(async move {
1297 client
1298 .request::<TestRequest>(
1299 TestRequest { a: 1, b: 2 },
1300 Endpoint::BrowserBackground { id: HostId::Own },
1301 None,
1302 )
1303 .await
1304 })
1305 };
1306 tokio::time::sleep(Duration::from_millis(100)).await;
1307
1308 let outgoing = communication_provider.outgoing().await;
1312 let request: RpcRequestMessage<TestRequest> =
1313 serde_utils::from_slice(&outgoing[0].payload)
1314 .expect("Deserialization should not fail");
1315 let malformed = IncomingRpcResponseMessage {
1316 result: Ok(OtherResponse::Available),
1317 request_id: request.request_id.clone(),
1318 request_type: request.request_type.clone(),
1319 };
1320 communication_provider.push_incoming(IncomingMessage {
1321 payload: serde_utils::to_vec(&malformed).expect("Serialization should not fail"),
1322 source: Source::BrowserBackground { id: HostId::Own },
1323 destination: Endpoint::Web {
1324 tab_id: 9001,
1325 document_id: "doc-1".to_string(),
1326 },
1327 topic: Some(request.response_topic.clone()),
1328 });
1329
1330 let result = tokio::time::timeout(Duration::from_secs(5), result_handle)
1331 .await
1332 .expect("request must not hang on a malformed response")
1333 .unwrap();
1334 assert!(matches!(
1335 result,
1336 Err(crate::error::RequestError::Rpc(
1337 crate::rpc::error::RpcError::ResponseDeserialization(_)
1338 ))
1339 ));
1340 }
1341
1342 fn test_response_from(
1344 request: &RpcRequestMessage<TestRequest>,
1345 result: i32,
1346 source: Source,
1347 ) -> IncomingMessage {
1348 let response = IncomingRpcResponseMessage {
1349 result: Ok(TestResponse { result }),
1350 request_id: request.request_id.clone(),
1351 request_type: request.request_type.clone(),
1352 };
1353 IncomingMessage {
1354 payload: serde_utils::to_vec(&response).expect("Serialization should not fail"),
1355 source,
1356 destination: Endpoint::DesktopRenderer,
1357 topic: Some(request.response_topic.clone()),
1358 }
1359 }
1360
1361 #[tokio::test]
1364 async fn response_from_other_source_is_ignored_and_destination_response_is_returned() {
1365 let communication_provider = TestCommunicationBackend::new();
1366 let client = IpcClientImpl::new(
1367 NoEncryptionCryptoProvider,
1368 communication_provider.clone(),
1369 InMemorySessionRepository::default(),
1370 );
1371 let _ = client.start(None).await;
1372
1373 let result_handle = {
1374 let client = client.clone();
1375 tokio::spawn(async move {
1376 client
1377 .request::<TestRequest>(
1378 TestRequest { a: 1, b: 2 },
1379 Endpoint::DesktopMain,
1380 None,
1381 )
1382 .await
1383 })
1384 };
1385 tokio::time::sleep(Duration::from_millis(100)).await;
1386
1387 let outgoing = communication_provider.outgoing().await;
1388 let request: RpcRequestMessage<TestRequest> =
1389 serde_utils::from_slice(&outgoing[0].payload)
1390 .expect("Deserialization should not fail");
1391
1392 communication_provider.push_incoming(test_response_from(
1393 &request,
1394 999,
1395 Source::BrowserBackground { id: HostId::Id(42) },
1396 ));
1397 communication_provider.push_incoming(test_response_from(
1398 &request,
1399 3,
1400 Source::DesktopMain,
1401 ));
1402
1403 let result = tokio::time::timeout(Duration::from_secs(5), result_handle)
1404 .await
1405 .expect("request must complete once the destination responds")
1406 .unwrap();
1407 assert_eq!(result, Ok(TestResponse { result: 3 }));
1408 }
1409
1410 #[tokio::test]
1413 async fn response_only_from_other_source_waits_until_cancelled() {
1414 let communication_provider = TestCommunicationBackend::new();
1415 let client = IpcClientImpl::new(
1416 NoEncryptionCryptoProvider,
1417 communication_provider.clone(),
1418 InMemorySessionRepository::default(),
1419 );
1420 let _ = client.start(None).await;
1421
1422 let cancellation_token = CancellationToken::new();
1423 let result_handle = {
1424 let client = client.clone();
1425 let cancellation_token = cancellation_token.clone();
1426 tokio::spawn(async move {
1427 client
1428 .request::<TestRequest>(
1429 TestRequest { a: 1, b: 2 },
1430 Endpoint::DesktopMain,
1431 Some(cancellation_token),
1432 )
1433 .await
1434 })
1435 };
1436 tokio::time::sleep(Duration::from_millis(100)).await;
1437
1438 let outgoing = communication_provider.outgoing().await;
1439 let request: RpcRequestMessage<TestRequest> =
1440 serde_utils::from_slice(&outgoing[0].payload)
1441 .expect("Deserialization should not fail");
1442
1443 communication_provider.push_incoming(test_response_from(
1444 &request,
1445 999,
1446 Source::DesktopRenderer,
1447 ));
1448 tokio::time::sleep(Duration::from_millis(100)).await;
1449 assert!(
1450 !result_handle.is_finished(),
1451 "request must not complete on a response from another source"
1452 );
1453
1454 cancellation_token.cancel();
1455 let result = tokio::time::timeout(Duration::from_secs(5), result_handle)
1456 .await
1457 .expect("request must end once cancelled")
1458 .unwrap();
1459 assert!(matches!(
1460 result,
1461 Err(crate::error::RequestError::Receive(
1462 TypedReceiveError::Cancelled
1463 ))
1464 ));
1465 }
1466
1467 #[tokio::test]
1470 async fn response_from_web_destination_is_accepted_regardless_of_origin() {
1471 let communication_provider = TestCommunicationBackend::new();
1472 let client = IpcClientImpl::new(
1473 NoEncryptionCryptoProvider,
1474 communication_provider.clone(),
1475 InMemorySessionRepository::default(),
1476 );
1477 let _ = client.start(None).await;
1478
1479 let result_handle = {
1480 let client = client.clone();
1481 tokio::spawn(async move {
1482 client
1483 .request::<TestRequest>(
1484 TestRequest { a: 1, b: 2 },
1485 Endpoint::Web {
1486 tab_id: 9001,
1487 document_id: "doc-1".to_string(),
1488 },
1489 None,
1490 )
1491 .await
1492 })
1493 };
1494 tokio::time::sleep(Duration::from_millis(100)).await;
1495
1496 let outgoing = communication_provider.outgoing().await;
1497 let request: RpcRequestMessage<TestRequest> =
1498 serde_utils::from_slice(&outgoing[0].payload)
1499 .expect("Deserialization should not fail");
1500
1501 communication_provider.push_incoming(test_response_from(
1503 &request,
1504 999,
1505 Source::Web {
1506 tab_id: 9001,
1507 document_id: "doc-2".to_string(),
1508 origin: "https://vault.bitwarden.com".to_string(),
1509 },
1510 ));
1511 communication_provider.push_incoming(test_response_from(
1512 &request,
1513 3,
1514 Source::Web {
1515 tab_id: 9001,
1516 document_id: "doc-1".to_string(),
1517 origin: "https://vault.bitwarden.com".to_string(),
1518 },
1519 ));
1520
1521 let result = tokio::time::timeout(Duration::from_secs(5), result_handle)
1522 .await
1523 .expect("request must complete once the destination responds")
1524 .unwrap();
1525 assert_eq!(result, Ok(TestResponse { result: 3 }));
1526 }
1527
1528 #[tokio::test]
1529 async fn incoming_rpc_message_handles_request_and_returns_response() {
1530 let crypto_provider = NoEncryptionCryptoProvider;
1531 let communication_provider = TestCommunicationBackend::new();
1532 let session_map = InMemorySessionRepository::default();
1533 let client =
1534 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
1535 let _ = client.start(None).await;
1536 let request_id = uuid::Uuid::new_v4().to_string();
1537 let request = TestRequest { a: 1, b: 2 };
1538 let response = TestResponse { result: 3 };
1539
1540 client.register_rpc_handler(TestHandler).await;
1542
1543 let response_topic = format!("RpcResponseMessage:{request_id}");
1545 let simulated_request = RpcRequestMessage {
1546 request,
1547 request_id: request_id.clone(),
1548 request_type: "TestRequest".to_string(),
1549 response_topic: response_topic.clone(),
1550 };
1551 let simulated_request_message = IncomingMessage {
1552 payload: serde_utils::to_vec(&simulated_request)
1553 .expect("Serialization should not fail"),
1554 source: Source::Web {
1555 tab_id: 9001,
1556 document_id: "doc-1".to_string(),
1557 origin: "https://example.com".to_string(),
1558 },
1559 destination: Endpoint::BrowserBackground { id: HostId::Own },
1560 topic: Some(RPC_REQUEST_PAYLOAD_TYPE_NAME.to_owned()),
1561 };
1562 communication_provider.push_incoming(simulated_request_message);
1563
1564 tokio::time::sleep(Duration::from_millis(100)).await;
1566
1567 let outgoing_messages = communication_provider.outgoing().await;
1569 let outgoing_response: IncomingRpcResponseMessage<TestResponse> =
1570 serde_utils::from_slice(&outgoing_messages[0].payload)
1571 .expect("Deserialization should not fail");
1572
1573 assert_eq!(outgoing_messages[0].topic, Some(response_topic));
1575 assert_eq!(outgoing_response.request_type, "TestRequest");
1576 assert_eq!(outgoing_response.result, Ok(response));
1577 }
1578
1579 #[tokio::test]
1580 async fn ipc_client_stops_if_rpc_response_send_returns_fatal_error() {
1581 let request = RpcRequestMessage {
1582 request: TestRequest { a: 1, b: 2 },
1583 request_id: "request".to_string(),
1584 request_type: "TestRequest".to_string(),
1585 response_topic: "RpcResponseMessage:request".to_string(),
1586 };
1587 let crypto_provider = TestCryptoProvider {
1588 send_result: Some(Err(TestCryptoError {
1589 message: "Crypto error".to_string(),
1590 fatal: true,
1591 })),
1592 receive_result: Some(Ok(IncomingMessage {
1593 payload: serde_utils::to_vec(&request).expect("Serialization should not fail"),
1594 source: Source::Web {
1595 tab_id: 9001,
1596 document_id: "doc-1".to_string(),
1597 origin: "https://example.com".to_string(),
1598 },
1599 destination: Endpoint::BrowserBackground { id: HostId::Own },
1600 topic: Some(RPC_REQUEST_PAYLOAD_TYPE_NAME.to_owned()),
1601 })),
1602 };
1603 let client = IpcClientImpl::new(
1604 crypto_provider,
1605 TestCommunicationBackend::new(),
1606 TestSessionRepository::new(HashMap::new()),
1607 );
1608 client.register_rpc_handler(TestHandler).await;
1609 let _ = client.start(None).await;
1610
1611 tokio::time::sleep(Duration::from_millis(100)).await;
1613
1614 assert!(!client.is_running());
1615 }
1616
1617 #[tokio::test]
1618 async fn incoming_rpc_message_passes_message_source_to_handler() {
1619 let crypto_provider = NoEncryptionCryptoProvider;
1620 let communication_provider = TestCommunicationBackend::new();
1621 let session_map = InMemorySessionRepository::default();
1622 let client =
1623 IpcClientImpl::new(crypto_provider, communication_provider.clone(), session_map);
1624 let _ = client.start(None).await;
1625 client.register_rpc_handler(EchoSourceHandler).await;
1626
1627 let source = Source::Web {
1628 tab_id: 9001,
1629 document_id: "doc-1".to_string(),
1630 origin: "https://example.com".to_string(),
1631 };
1632 let request_id = uuid::Uuid::new_v4().to_string();
1633 let simulated_request = RpcRequestMessage {
1634 request: EchoSourceRequest,
1635 request_id: request_id.clone(),
1636 request_type: EchoSourceRequest::NAME.to_string(),
1637 response_topic: format!("RpcResponseMessage:{request_id}"),
1638 };
1639 communication_provider.push_incoming(IncomingMessage {
1640 payload: serde_utils::to_vec(&simulated_request)
1641 .expect("Serialization should not fail"),
1642 source: source.clone(),
1643 destination: Endpoint::BrowserBackground { id: HostId::Own },
1644 topic: Some(RPC_REQUEST_PAYLOAD_TYPE_NAME.to_owned()),
1645 });
1646
1647 tokio::time::sleep(Duration::from_millis(100)).await;
1649
1650 let outgoing_messages = communication_provider.outgoing().await;
1651 let outgoing_response: IncomingRpcResponseMessage<Source> =
1652 serde_utils::from_slice(&outgoing_messages[0].payload)
1653 .expect("Deserialization should not fail");
1654
1655 assert_eq!(outgoing_response.result, Ok(source));
1656 }
1657 }
1658
1659 mod fragmentation {
1660 use super::*;
1661 use crate::{
1662 RequestError, RpcHandler, RpcRequestInfo,
1663 crypto_provider::noise::crypto_provider::NoiseCryptoProvider,
1664 fragmentation::{FRAGMENT_TOPIC, MAX_PAYLOAD_SIZE, test_support::*},
1665 traits::TestTwoWayCommunicationBackend,
1666 };
1667
1668 const LARGE_LEN: usize = 4 * 65535 + 123;
1670
1671 fn noise_client(
1672 backend: TestTwoWayCommunicationBackend,
1673 ) -> IpcClientImpl<
1674 NoiseCryptoProvider,
1675 TestTwoWayCommunicationBackend,
1676 InMemorySessionRepository<crate::NoiseCryptoProviderState>,
1677 > {
1678 IpcClientImpl::new(
1679 NoiseCryptoProvider::new(),
1680 backend,
1681 InMemorySessionRepository::new(HashMap::new()),
1682 )
1683 }
1684
1685 #[tokio::test]
1686 async fn large_payload_round_trips_through_noise_once_on_its_topic() {
1687 let (backend_1, backend_2) = TestTwoWayCommunicationBackend::new();
1688 let client_1 = noise_client(backend_1);
1689 let client_2 = noise_client(backend_2);
1690 client_1.start(None).await.unwrap();
1691 client_2.start(None).await.unwrap();
1692 let mut all_messages = client_2.subscribe(None).await.unwrap();
1693 let mut on_topic = client_2.subscribe(Some("bulk".to_owned())).await.unwrap();
1694 let mut on_fragment_topic = client_2
1695 .subscribe(Some(format!("{FRAGMENT_TOPIC}/bulk")))
1696 .await
1697 .unwrap();
1698
1699 let payload = large_payload(LARGE_LEN, 0);
1700 let message = OutgoingMessage {
1701 payload: payload.clone(),
1702 destination: Endpoint::DesktopMain,
1703 topic: Some("bulk".to_owned()),
1704 };
1705 client_1.send(message).await.unwrap();
1706
1707 let received = all_messages.receive(None).await.unwrap();
1708 assert_eq!(received.topic.as_deref(), Some("bulk"));
1709 assert_eq!(received.payload, payload);
1710
1711 let next = bitwarden_threading::time::timeout(
1713 Duration::from_millis(200),
1714 all_messages.receive(None),
1715 )
1716 .await;
1717 assert!(next.is_err(), "unexpected extra message: {next:?}");
1718
1719 assert_eq!(on_topic.receive(None).await.unwrap().payload, payload);
1721 let fragment = bitwarden_threading::time::timeout(
1722 Duration::from_millis(10),
1723 on_fragment_topic.receive(None),
1724 )
1725 .await;
1726 assert!(
1727 fragment.is_err(),
1728 "fragment reached a subscriber: {fragment:?}"
1729 );
1730 }
1731
1732 #[derive(Debug, Clone, Serialize, Deserialize)]
1733 struct EchoRequest {
1734 data: String,
1735 }
1736
1737 impl RpcRequest for EchoRequest {
1738 type Response = String;
1739
1740 const NAME: &str = "EchoRequest";
1741 }
1742
1743 struct EchoHandler;
1744
1745 impl RpcHandler for EchoHandler {
1746 type Request = EchoRequest;
1747
1748 async fn handle(&self, request: EchoRequest, _info: RpcRequestInfo) -> String {
1749 request.data.repeat(2)
1750 }
1751 }
1752
1753 #[tokio::test]
1754 async fn rpc_with_response_larger_than_one_noise_message_succeeds() {
1755 let (backend_1, backend_2) = TestTwoWayCommunicationBackend::new();
1756 let client_1 = noise_client(backend_1);
1757 let client_2 = noise_client(backend_2);
1758 client_1.start(None).await.unwrap();
1759 client_2.start(None).await.unwrap();
1760 client_2.register_rpc_handler(EchoHandler).await;
1761
1762 let data = "a".repeat(LARGE_LEN / 2);
1764 let response = client_1
1765 .request(
1766 EchoRequest { data: data.clone() },
1767 Endpoint::DesktopMain,
1768 None,
1769 )
1770 .await
1771 .unwrap();
1772
1773 assert_eq!(response, data.repeat(2));
1774 }
1775
1776 #[tokio::test]
1777 async fn rpc_response_over_the_maximum_returns_an_error_to_the_requester() {
1778 let (backend_1, backend_2) = TestTwoWayCommunicationBackend::new();
1779 let client_1 = noise_client(backend_1);
1780 let client_2 = noise_client(backend_2);
1781 client_1.start(None).await.unwrap();
1782 client_2.start(None).await.unwrap();
1783 client_2.register_rpc_handler(EchoHandler).await;
1784
1785 let data = "a".repeat(MAX_PAYLOAD_SIZE / 2 + 1);
1787 let result = bitwarden_threading::time::timeout(
1788 Duration::from_secs(10),
1789 client_1.request(EchoRequest { data }, Endpoint::DesktopMain, None),
1790 )
1791 .await
1792 .expect("Requester should receive an error response");
1793
1794 assert!(
1795 matches!(
1796 result,
1797 Err(RequestError::Rpc(RpcError::ResponseSerialization(_)))
1798 ),
1799 "{result:?}"
1800 );
1801 }
1802
1803 #[tokio::test]
1804 async fn client_keeps_receiving_after_invalid_fragments() {
1805 let backend = TestCommunicationBackend::new();
1806 let client = IpcClientImpl::new(
1807 NoEncryptionCryptoProvider,
1808 backend.clone(),
1809 InMemorySessionRepository::default(),
1810 );
1811 client.start(None).await.unwrap();
1812 let mut subscription = client.subscribe(None).await.unwrap();
1813 let source = web_source(1);
1814
1815 backend.push_incoming(incoming(
1816 outgoing(vec![1, 2, 3], FRAGMENT_TOPIC),
1817 source.clone(),
1818 ));
1819 backend.push_incoming(crafted(source.clone(), 1, 5, 1024));
1820 backend.push_incoming(crafted(source.clone(), 2, 0, MAX_PAYLOAD_SIZE + 1));
1821 backend.push_incoming(crafted(source.clone(), 3, 0, 1024));
1822 backend.push_incoming(crafted(source.clone(), 3, 0, 1024));
1823
1824 let payload = large_payload(FRAGMENT_LEN * 2 + 1, 0);
1825 for fragment in split_all(outgoing(payload.clone(), "bulk"), MAX_LEN) {
1826 backend.push_incoming(incoming(fragment, source.clone()));
1827 }
1828
1829 let received = subscription.receive(None).await.unwrap();
1830 assert_eq!(received.topic.as_deref(), Some("bulk"));
1831 assert_eq!(received.payload, payload);
1832 assert!(client.is_running());
1833 }
1834
1835 #[tokio::test]
1836 async fn send_rejects_reserved_fragment_topic() {
1837 let client = IpcClientImpl::new(
1838 NoEncryptionCryptoProvider,
1839 TestCommunicationBackend::new(),
1840 InMemorySessionRepository::default(),
1841 );
1842 let result = client.send(outgoing(vec![0; 16], FRAGMENT_TOPIC)).await;
1843 assert!(matches!(result, Err(SendError::Other(_))));
1844 }
1845
1846 struct FailingCryptoProvider {
1848 fail_at: usize,
1849 fatal: bool,
1850 sent: Arc<Mutex<Vec<OutgoingMessage>>>,
1851 }
1852
1853 impl CryptoProvider<TestCommunicationBackend, TestSessionRepository> for FailingCryptoProvider {
1854 type Session = String;
1855 type SendError = TestCryptoError;
1856 type ReceiveError = TestCryptoError;
1857
1858 fn max_payload_len(&self) -> usize {
1859 MAX_LEN
1860 }
1861
1862 async fn send(
1863 &self,
1864 _communication: &TestCommunicationBackend,
1865 _sessions: &TestSessionRepository,
1866 message: OutgoingMessage,
1867 ) -> Result<(), TestCryptoError> {
1868 let mut sent = self.sent.lock().unwrap();
1869 if sent.len() == self.fail_at {
1870 return Err(TestCryptoError {
1871 message: "Send failed".to_owned(),
1872 fatal: self.fatal,
1873 });
1874 }
1875 sent.push(message);
1876 Ok(())
1877 }
1878
1879 async fn receive(
1880 &self,
1881 _receiver: &<TestCommunicationBackend as CommunicationBackend>::Receiver,
1882 _communication: &TestCommunicationBackend,
1883 _sessions: &TestSessionRepository,
1884 ) -> Result<IncomingMessage, TestCryptoError> {
1885 std::future::pending().await
1886 }
1887 }
1888
1889 #[tokio::test]
1890 async fn failed_fragment_send_stops_the_message_and_fatal_errors_stop_the_client() {
1891 for fatal in [false, true] {
1892 let sent = Arc::new(Mutex::new(Vec::new()));
1893 let client = IpcClientImpl::new(
1894 FailingCryptoProvider {
1895 fail_at: 1,
1896 fatal,
1897 sent: sent.clone(),
1898 },
1899 TestCommunicationBackend::new(),
1900 TestSessionRepository::default(),
1901 );
1902 client.start(None).await.unwrap();
1903
1904 let message = outgoing(large_payload(FRAGMENT_LEN * 3, 0), "bulk");
1905 assert!(client.send(message).await.is_err());
1906
1907 assert_eq!(sent.lock().unwrap().len(), 1);
1909 assert_eq!(client.is_running(), !fatal);
1910 }
1911 }
1912 }
1913}