1use crate::prelude::results::PeerConnectSuccess;
68use crate::prelude::*;
69use crate::test_common::wait_for_peers;
70use citadel_io::tokio::sync::mpsc::{Receiver, UnboundedSender};
71use citadel_io::{tokio, Mutex};
72use citadel_proto::prelude::async_trait;
73use citadel_user::hypernode_account::UserIdentifierExt;
74use futures::stream::FuturesUnordered;
75use futures::TryStreamExt;
76use std::collections::HashMap;
77use std::future::Future;
78use std::marker::PhantomData;
79use std::sync::Arc;
80use uuid::Uuid;
81
82pub struct PeerConnectionKernel<'a, F, Fut, R: Ratchet> {
85 inner_kernel: Box<dyn NetKernel<R> + 'a>,
86 shared: Shared,
87 _pd: PhantomData<fn() -> (F, Fut)>,
89}
90
91#[derive(Clone)]
92#[doc(hidden)]
93pub struct Shared {
94 active_peer_conns: Arc<Mutex<HashMap<PeerConnectionType, PeerContext>>>,
95}
96
97struct PeerContext {
98 #[allow(dead_code)]
99 conn_type: PeerConnectionType,
100 send_file_transfer_tx: UnboundedSender<ObjectTransferHandler>,
101}
102
103#[derive(Debug)]
104pub struct FileTransferHandleRx {
105 pub inner: citadel_io::tokio::sync::mpsc::UnboundedReceiver<ObjectTransferHandler>,
106 pub conn_type: VirtualTargetType,
107}
108
109impl FileTransferHandleRx {
110 pub fn accept_all(mut self) {
112 let task = tokio::task::spawn(async move {
113 let rx = &mut self.inner;
114 while let Some(mut handle) = rx.recv().await {
115 let task = tokio::task::spawn(async move {
116 if let Err(err) = handle.exhaust_stream().await {
117 let orientation = handle.orientation;
118 log::warn!(target: "citadel", "Error background handling of file transfer for {orientation:?}: {err:?}");
119 }
120 });
121
122 drop(task);
123 }
124 });
125
126 drop(task);
127 }
128}
129
130impl std::ops::Deref for FileTransferHandleRx {
131 type Target = citadel_io::tokio::sync::mpsc::UnboundedReceiver<ObjectTransferHandler>;
132
133 fn deref(&self) -> &Self::Target {
134 &self.inner
135 }
136}
137
138impl std::ops::DerefMut for FileTransferHandleRx {
139 fn deref_mut(&mut self) -> &mut Self::Target {
140 &mut self.inner
141 }
142}
143
144impl Drop for FileTransferHandleRx {
145 fn drop(&mut self) {
146 log::trace!(target: "citadel", "Dropping file transfer handle receiver {:?}", self.conn_type);
147 }
148}
149
150#[async_trait]
151impl<F, Fut, R: Ratchet> NetKernel<R> for PeerConnectionKernel<'_, F, Fut, R> {
152 fn load_remote(&mut self, server_remote: NodeRemote<R>) -> Result<(), NetworkError> {
153 self.inner_kernel.load_remote(server_remote)
154 }
155
156 async fn on_start(&self) -> Result<(), NetworkError> {
157 self.inner_kernel.on_start().await
158 }
159
160 #[allow(clippy::collapsible_else_if)]
161 async fn on_node_event_received(&self, message: NodeResult<R>) -> Result<(), NetworkError> {
162 match message {
163 NodeResult::ObjectTransferHandle(ObjectTransferHandle {
164 ticket: _,
165 handle,
166 session_cid,
167 }) => {
168 let is_revfs = matches!(
169 handle.metadata.transfer_type,
170 TransferType::RemoteEncryptedVirtualFilesystem { .. }
171 );
172 let active_peers = self.shared.active_peer_conns.lock();
173 let v_conn = if is_revfs {
174 let peer_cid = if session_cid != handle.source {
175 handle.source
176 } else {
177 handle.receiver
178 };
179 PeerConnectionType::LocalGroupPeer {
180 session_cid,
181 peer_cid,
182 }
183 } else {
184 if matches!(
185 handle.orientation,
186 ObjectTransferOrientation::Receiver { .. }
187 ) {
188 PeerConnectionType::LocalGroupPeer {
189 session_cid,
190 peer_cid: handle.source,
191 }
192 } else {
193 PeerConnectionType::LocalGroupPeer {
194 session_cid,
195 peer_cid: handle.receiver,
196 }
197 }
198 };
199
200 if let Some(peer_ctx) = active_peers.get(&v_conn) {
201 if let Err(err) = peer_ctx.send_file_transfer_tx.send(handle) {
202 log::warn!(target: "citadel", "Error forwarding file transfer handle: {:?}", err.to_string());
203 }
204 } else {
205 log::warn!(target: "citadel", "Unable to find key for inbound file transfer handle: {:?}\n Active Peers: {:?} \n handle_source = {}, handle_receiver = {}", v_conn, active_peers.keys().cloned().collect::<Vec<_>>(), handle.source, handle.receiver);
206 }
207
208 Ok(())
209 }
210
211 unprocessed @ NodeResult::Disconnect(..) | unprocessed => {
215 self.inner_kernel.on_node_event_received(unprocessed).await
217 }
218 }
219 }
220
221 async fn on_stop(&mut self) -> Result<(), NetworkError> {
222 self.inner_kernel.on_stop().await
223 }
224}
225
226#[derive(Debug, Default, Clone)]
229pub struct PeerConnectionSetupAggregator {
230 inner: Vec<PeerConnectionSettings>,
231}
232
233#[derive(Debug, Clone)]
234struct PeerConnectionSettings {
235 id: UserIdentifier,
236 session_security_settings: SessionSecuritySettings,
237 udp_mode: UdpMode,
238 ensure_registered: bool,
239 peer_session_password: Option<PreSharedKey>,
240 turn: Option<TurnRelayConfig>,
241}
242
243pub struct AddedPeer {
244 list: PeerConnectionSetupAggregator,
245 id: UserIdentifier,
246 session_security_settings: Option<SessionSecuritySettings>,
247 ensure_registered: bool,
248 udp_mode: Option<UdpMode>,
249 peer_session_password: Option<PreSharedKey>,
250 turn: Option<TurnRelayConfig>,
251}
252
253impl AddedPeer {
254 pub fn add(mut self) -> PeerConnectionSetupAggregator {
256 let new = PeerConnectionSettings {
257 id: self.id,
258 session_security_settings: self.session_security_settings.unwrap_or_default(),
259 udp_mode: self.udp_mode.unwrap_or_default(),
260 ensure_registered: self.ensure_registered,
261 peer_session_password: self.peer_session_password,
262 turn: self.turn,
263 };
264
265 self.list.inner.push(new);
266 self.list
267 }
268
269 pub fn with_udp_mode(mut self, udp_mode: UdpMode) -> Self {
271 self.udp_mode = Some(udp_mode);
272 self
273 }
274
275 pub fn with_turn_config(mut self, turn: TurnRelayConfig) -> Self {
278 self.turn = Some(turn);
279 self
280 }
281
282 pub fn disable_udp(self) -> Self {
284 self.with_udp_mode(UdpMode::Disabled)
285 }
286
287 pub fn enable_udp(self) -> Self {
289 self.with_udp_mode(UdpMode::Enabled)
290 }
291
292 pub fn with_session_security_settings(
294 mut self,
295 session_security_settings: SessionSecuritySettings,
296 ) -> Self {
297 self.session_security_settings = Some(session_security_settings);
298 self
299 }
300
301 pub fn ensure_registered(mut self) -> Self {
303 self.ensure_registered = true;
304 self
305 }
306
307 pub fn with_session_password<T: Into<PreSharedKey>>(mut self, password: T) -> Self {
310 self.peer_session_password = Some(password.into());
311 self
312 }
313}
314
315impl PeerConnectionSetupAggregator {
316 pub fn with_peer<T: Into<UserIdentifier>>(self, peer: T) -> PeerConnectionSetupAggregator {
325 self.with_peer_custom(peer).add()
326 }
327
328 pub fn with_peer_custom<T: Into<UserIdentifier>>(self, peer: T) -> AddedPeer {
344 AddedPeer {
345 list: self,
346 id: peer.into(),
347 ensure_registered: false,
348 session_security_settings: None,
349 udp_mode: None,
350 peer_session_password: None,
351 turn: None,
352 }
353 }
354}
355
356impl From<PeerConnectionSetupAggregator> for Vec<PeerConnectionSettings> {
357 fn from(this: PeerConnectionSetupAggregator) -> Self {
358 this.inner
359 }
360}
361
362impl From<Vec<UserIdentifier>> for PeerConnectionSetupAggregator {
363 fn from(ids: Vec<UserIdentifier>) -> Self {
364 let mut this = PeerConnectionSetupAggregator::default();
365 for peer in ids {
366 this = this.with_peer(peer);
367 }
368
369 this
370 }
371}
372
373impl From<UserIdentifier> for PeerConnectionSetupAggregator {
374 fn from(this: UserIdentifier) -> Self {
375 Self::from(vec![this])
376 }
377}
378
379impl From<Uuid> for PeerConnectionSetupAggregator {
380 fn from(user: Uuid) -> Self {
381 let user_identifier: UserIdentifier = user.into();
382 user_identifier.into()
383 }
384}
385
386impl From<String> for PeerConnectionSetupAggregator {
387 fn from(this: String) -> Self {
388 let user_identifier: UserIdentifier = this.into();
389 user_identifier.into()
390 }
391}
392
393impl From<&str> for PeerConnectionSetupAggregator {
394 fn from(this: &str) -> Self {
395 let user_identifier: UserIdentifier = this.into();
396 user_identifier.into()
397 }
398}
399
400impl From<u64> for PeerConnectionSetupAggregator {
401 fn from(this: u64) -> Self {
402 let user_identifier: UserIdentifier = this.into();
403 user_identifier.into()
404 }
405}
406
407#[async_trait]
408impl<'a, F, Fut, T: Into<PeerConnectionSetupAggregator> + Send + 'a, R: Ratchet>
409 PrefabFunctions<'a, T, R> for PeerConnectionKernel<'a, F, Fut, R>
410where
411 F: FnOnce(
412 Receiver<Result<PeerConnectSuccess<R>, NetworkError>>,
413 CitadelClientServerConnection<R>,
414 ) -> Fut
415 + Send
416 + 'a,
417 Fut: Future<Output = Result<(), NetworkError>> + Send + 'a,
418{
419 type UserLevelInputFunction = F;
420 type SharedBundle = Shared;
421
422 fn get_shared_bundle(&self) -> Self::SharedBundle {
423 self.shared.clone()
424 }
425
426 #[allow(clippy::blocks_in_conditions)]
427 #[cfg_attr(
428 feature = "localhost-testing",
429 tracing::instrument(level = "trace", target = "citadel", skip_all, ret, err(Debug))
430 )]
431 async fn on_c2s_channel_received(
432 connect_success: CitadelClientServerConnection<R>,
433 peers_to_connect: T,
434 f: Self::UserLevelInputFunction,
435 shared: Shared,
436 ) -> Result<(), NetworkError> {
437 let shared = &shared;
438 let session_cid = connect_success.cid;
439 let mut peers_already_registered = vec![];
440
441 wait_for_peers().await;
442 let peers_to_connect = peers_to_connect.into().inner;
443
444 for peer in &peers_to_connect {
445 peers_already_registered.push(
447 peer.id
448 .search_peer(session_cid, connect_success.account_manager())
449 .await?,
450 )
451 }
452
453 let remote = connect_success.clone();
454 let (tx, rx) = citadel_io::tokio::sync::mpsc::channel(peers_to_connect.len());
455 let requests = FuturesUnordered::new();
456
457 for (mutually_registered, peer_to_connect) in
458 peers_already_registered.into_iter().zip(peers_to_connect)
459 {
460 let remote = remote.clone();
463 let tx = tx.clone();
464 let PeerConnectionSettings {
465 id,
466 session_security_settings,
467 udp_mode,
468 ensure_registered,
469 peer_session_password,
470 turn,
471 } = peer_to_connect;
472
473 let task = async move {
474 let inner_task = async move {
475 let (file_transfer_tx, file_transfer_rx) =
476 citadel_io::tokio::sync::mpsc::unbounded_channel();
477
478 let peer_cid = if let Some(mutual_peer) = &mutually_registered {
480 mutual_peer.cid
481 } else {
482 id.get_cid()
483 };
484
485 let handle = if let Some(_already_registered) = mutually_registered {
486 remote.find_target(session_cid, id).await?
487 } else {
488 log::info!(target: "citadel", "{session_cid} proposing target {id:?} to central node");
490 let handle = remote.propose_target(session_cid, id.clone()).await?;
491 if ensure_registered {
494 loop {
495 if handle.is_peer_registered().await? {
496 break;
497 }
498 citadel_io::time::sleep(std::time::Duration::from_millis(200))
499 .await;
500 }
501 }
502
503 log::info!(target: "citadel", "{session_cid} registering to peer {id:?}");
504 let registration = handle.register_to_peer().await?;
511 if let Some(reason) = registration.refusal_reason() {
512 return Err(NetworkError::generic(format!(
513 "Cannot connect to peer {id:?}: {reason}"
514 )));
515 }
516 log::info!(target: "citadel", "{session_cid} registered to peer {id:?} registered || success -> now connecting");
517 handle
518 };
519
520 let peer_conn = PeerConnectionType::LocalGroupPeer {
523 session_cid,
524 peer_cid,
525 };
526 let peer_context = PeerContext {
527 conn_type: peer_conn,
528 send_file_transfer_tx: file_transfer_tx.clone(),
529 };
530 log::debug!(target: "citadel", "Early registering peer connection: {peer_conn:?}");
531 let _ = shared
532 .active_peer_conns
533 .lock()
534 .insert(peer_conn, peer_context);
535
536 if let Some(turn) = turn {
537 handle.set_turn_config(Some(turn)).await?;
538 }
539
540 handle
541 .connect_to_peer_custom(
542 session_security_settings,
543 udp_mode,
544 peer_session_password,
545 )
546 .await
547 .map(|mut success| {
548 let actual_peer_conn = success.channel.get_peer_conn_type().unwrap();
549
550 if actual_peer_conn != peer_conn {
553 log::debug!(target: "citadel", "Updating peer connection registration from {peer_conn:?} to {actual_peer_conn:?}");
554 let mut active_peers = shared.active_peer_conns.lock();
555 if let Some(peer_ctx) = active_peers.remove(&peer_conn) {
556 let _ = active_peers.insert(actual_peer_conn, peer_ctx);
557 }
558 }
559 success.incoming_object_transfer_handles = Some(FileTransferHandleRx {
561 inner: file_transfer_rx,
562 conn_type: actual_peer_conn.as_virtual_connection(),
563 });
564 success
565 })
566 .inspect_err(|_err| {
567 let _ = shared.active_peer_conns.lock().remove(&peer_conn);
569 })
570 };
571
572 tx.send(inner_task.await)
573 .await
574 .map_err(|err| NetworkError::generic(err.to_string()))
575 };
576
577 requests.push(Box::pin(task))
578 }
579
580 drop(tx);
583
584 let (collection_result, user_result) =
587 citadel_io::tokio::join!(requests.try_collect::<()>(), f(rx, connect_success));
588
589 collection_result?;
591 user_result
592 }
593
594 fn construct(kernel: Box<dyn NetKernel<R> + 'a>) -> Self {
595 Self {
596 inner_kernel: kernel,
597 shared: Shared {
598 active_peer_conns: Arc::new(Mutex::new(Default::default())),
599 },
600 _pd: Default::default(),
601 }
602 }
603}
604
605#[cfg(all(test, feature = "localhost-testing"))]
606mod tests {
607 use crate::prefabs::client::peer_connection::PeerConnectionKernel;
608 use crate::prefabs::client::DefaultServerConnectionSettingsBuilder;
609 use crate::prelude::*;
610 use crate::remote_ext::results::PeerConnectSuccess;
611 use crate::test_common::{server_info, wait_for_peers, TestBarrier};
612 use citadel_io::tokio;
613 use citadel_io::tokio::sync::mpsc::{Receiver, UnboundedSender};
614 use citadel_user::prelude::UserIdentifierExt;
615 use futures::stream::FuturesUnordered;
616 use futures::TryStreamExt;
617 use rstest::rstest;
618 use std::collections::HashMap;
619 use std::future::Future;
620 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
621 use std::time::Duration;
622 use uuid::Uuid;
623
624 lazy_static::lazy_static! {
625 pub static ref PEERS: Vec<(String, String, String)> = {
626 ["alpha", "beta", "charlie", "echo", "delta", "epsilon", "foxtrot"]
627 .iter().map(|base| (format!("{base}.username"), format!("{base}.password"), format!("{base}.full_name")))
628 .collect()
629 };
630 }
631
632 #[rstest]
633 #[case(2, UdpMode::Enabled)]
634 #[case(3, UdpMode::Disabled)]
635 #[timeout(Duration::from_secs(90))]
636 #[tokio::test(flavor = "multi_thread")]
637 async fn peer_to_peer_connect(#[case] peer_count: usize, #[case] udp_mode: UdpMode) {
638 assert!(peer_count > 1);
639 citadel_logging::setup_log();
640 TestBarrier::setup(peer_count);
641
642 let client_success = &AtomicUsize::new(0);
643 let (server, server_addr) = server_info::<StackedRatchet>();
644
645 let client_kernels = FuturesUnordered::new();
646 let total_peers = (0..peer_count)
647 .map(|idx| PEERS.get(idx).unwrap().0.clone())
648 .collect::<Vec<String>>();
649
650 for idx in 0..peer_count {
651 let (username, password, full_name) = PEERS.get(idx).unwrap();
652 let peers = total_peers
653 .clone()
654 .into_iter()
655 .filter(|r| r != username)
656 .map(UserIdentifier::Username)
657 .collect::<Vec<UserIdentifier>>();
658
659 let mut agg = PeerConnectionSetupAggregator::default();
660
661 for peer in peers {
662 agg = agg
663 .with_peer_custom(peer)
664 .ensure_registered()
665 .with_udp_mode(udp_mode)
666 .with_session_security_settings(SessionSecuritySettings::default())
667 .add();
668 }
669
670 let server_connection_settings =
671 DefaultServerConnectionSettingsBuilder::credentialed_registration(
672 server_addr,
673 username,
674 full_name,
675 password.as_str(),
676 )
677 .build()
678 .unwrap();
679
680 let username = username.clone();
681
682 let client_kernel = PeerConnectionKernel::new(
683 server_connection_settings,
684 agg.clone(),
685 move |results, connection| async move {
686 log::info!(target: "citadel", "***PEER {username} CONNECTED ***");
687 let session_cid = connection.conn_type.get_session_cid();
688 let check = move |conn: PeerConnectSuccess<_>| async move {
689 let session_cid = conn.channel.get_session_cid();
690 let _mutual_peers = conn
691 .remote
692 .remote()
693 .get_local_group_mutual_peers(session_cid)
694 .await
695 .unwrap();
696 conn
697 };
698 let p2p_remotes = handle_peer_connect_successes(
699 results,
700 session_cid,
701 peer_count,
702 udp_mode,
703 check,
704 )
705 .await
706 .into_iter()
707 .map(|r| (r.channel.get_peer_cid(), r.remote))
708 .collect::<HashMap<_, _>>();
709
710 let network_peers = connection.get_peers(None).await.unwrap();
714 for user in agg.inner {
715 let peer_cid = user.id.get_cid();
716 assert!(network_peers.iter().any(|r| r.cid == peer_cid))
717 }
718
719 let session_cid = connection.conn_type.get_session_cid();
721 let mutual_peers = connection
722 .get_local_group_mutual_peers(session_cid)
723 .await
724 .unwrap();
725 for (peer_cid, _) in p2p_remotes {
726 assert!(mutual_peers.iter().any(|r| r.cid == peer_cid))
727 }
728
729 log::info!(target: "citadel", "***PEER {username} finished all checks***");
730 let _ = client_success.fetch_add(1, Ordering::Relaxed);
731 wait_for_peers().await;
732 connection.shutdown_kernel().await
733 },
734 );
735
736 let client = DefaultNodeBuilder::default()
737 .with_stun_servers(crate::test_common::LOCALHOST_STUN_SERVERS)
738 .build(client_kernel)
739 .unwrap();
740 client_kernels.push(async move { client.await.map(|_| ()) });
741 }
742
743 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
744
745 assert!(futures::future::try_select(server, clients).await.is_ok());
746
747 assert_eq!(client_success.load(Ordering::Relaxed), peer_count);
748 }
749
750 #[rstest]
751 #[case(2, HeaderObfuscatorSettings::default())]
752 #[case(2, HeaderObfuscatorSettings::Enabled)]
753 #[case(2, HeaderObfuscatorSettings::EnabledWithKey(12345))]
754 #[case(3, HeaderObfuscatorSettings::default())]
755 #[timeout(Duration::from_secs(90))]
756 #[tokio::test(flavor = "multi_thread")]
757 async fn peer_to_peer_connect_transient(
758 #[case] peer_count: usize,
759 #[case] header_obfuscator_settings: HeaderObfuscatorSettings,
760 ) -> Result<(), Box<dyn std::error::Error>> {
761 assert!(peer_count > 1);
762 citadel_logging::setup_log();
763 TestBarrier::setup(peer_count);
764 let udp_mode = UdpMode::Enabled;
765
766 let do_deregister = peer_count == 2;
767
768 let client_success = &AtomicUsize::new(0);
769 let (server, server_addr) = server_info::<StackedRatchet>();
770
771 let client_kernels = FuturesUnordered::new();
772 let total_peers = (0..peer_count)
773 .map(|_| Uuid::new_v4())
774 .collect::<Vec<Uuid>>();
775
776 for idx in 0..peer_count {
777 let uuid = total_peers.get(idx).cloned().unwrap();
778 let peers = total_peers
779 .clone()
780 .into_iter()
781 .filter(|r| r != &uuid)
782 .map(UserIdentifier::from)
783 .collect::<Vec<UserIdentifier>>();
784
785 let mut agg = PeerConnectionSetupAggregator::default();
786
787 for peer in peers {
788 let security_settings = SessionSecuritySettings {
789 header_obfuscator_settings,
790 ..Default::default()
791 };
792 agg = agg
793 .with_peer_custom(peer)
794 .with_udp_mode(udp_mode)
795 .ensure_registered()
796 .with_session_security_settings(security_settings)
797 .add();
798 }
799
800 let server_connection_settings =
801 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
802 .build()
803 .unwrap();
804
805 let client_kernel = PeerConnectionKernel::new(
806 server_connection_settings,
807 agg,
808 move |results, remote| async move {
809 log::info!(target: "citadel", "***PEER {uuid} CONNECTED***");
810 let session_cid = remote.conn_type.get_session_cid();
811
812 let check = move |conn: PeerConnectSuccess<_>| async move {
813 if do_deregister {
814 conn.remote
815 .deregister()
816 .await
817 .expect("Deregistration failed");
818 assert!(!conn
819 .remote
820 .inner
821 .account_manager()
822 .get_persistence_handler()
823 .hyperlan_peer_exists(session_cid, conn.channel.get_peer_cid())
824 .await
825 .unwrap());
826 }
827 conn
828 };
829
830 let _ = handle_peer_connect_successes(
831 results,
832 session_cid,
833 peer_count,
834 udp_mode,
835 check,
836 )
837 .await;
838
839 log::info!(target: "citadel", "***PEER {uuid} finished all checks***");
840 let _ = client_success.fetch_add(1, Ordering::Relaxed);
841 wait_for_peers().await;
842 remote.shutdown_kernel().await
843 },
844 );
845
846 let client = DefaultNodeBuilder::default()
847 .with_stun_servers(crate::test_common::LOCALHOST_STUN_SERVERS)
848 .build(client_kernel)?;
849 client_kernels.push(async move { client.await.map(|_| ()) });
850 }
851
852 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
853
854 if let Err(err) = futures::future::try_select(server, clients).await {
855 return match err {
856 futures::future::Either::Left(res) => Err(res.0.into_string().into()),
857 futures::future::Either::Right(res) => Err(res.0.into_string().into()),
858 };
859 }
860
861 assert_eq!(client_success.load(Ordering::Relaxed), peer_count);
862 Ok(())
863 }
864
865 #[rstest]
866 #[case(2)]
867 #[case(3)]
868 #[timeout(std::time::Duration::from_secs(180))]
869 #[tokio::test(flavor = "multi_thread")]
870 async fn test_peer_to_peer_file_transfer(
871 #[case] peer_count: usize,
872 ) -> Result<(), Box<dyn std::error::Error>> {
873 assert!(peer_count > 1);
874 citadel_logging::setup_log();
875 TestBarrier::setup(peer_count);
876 let udp_mode = UdpMode::Enabled;
877
878 let sender_success = &AtomicBool::new(false);
879 let receiver_success = &AtomicBool::new(false);
880
881 let (server, server_addr) = server_info::<StackedRatchet>();
882
883 let client_kernels = FuturesUnordered::new();
884 let total_peers = (0..peer_count)
885 .map(|_| Uuid::new_v4())
886 .collect::<Vec<Uuid>>();
887
888 let sender_uuid = total_peers[0];
889
890 for idx in 0..peer_count {
891 let uuid = total_peers.get(idx).cloned().unwrap();
892 let mut peers = total_peers
893 .clone()
894 .into_iter()
895 .filter(|r| r != &uuid)
896 .map(UserIdentifier::from)
897 .collect::<Vec<UserIdentifier>>();
898 if idx != 0 {
904 peers = vec![sender_uuid.into()];
905 }
906
907 let mut agg = PeerConnectionSetupAggregator::default();
908
909 for peer in peers {
910 agg = agg
911 .with_peer_custom(peer)
912 .ensure_registered()
913 .with_udp_mode(udp_mode)
914 .with_session_security_settings(SessionSecuritySettings::default())
915 .add();
916 }
917
918 let server_connection_settings =
919 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
920 .build()
921 .unwrap();
922
923 let client_kernel = PeerConnectionKernel::new(
924 server_connection_settings,
925 agg,
926 move |results, remote| async move {
927 log::info!(target: "citadel", "***PEER {uuid} CONNECTED***");
928 wait_for_peers().await;
929 let session_cid = remote.conn_type.get_session_cid();
930 let is_sender = idx == 0; let check = move |mut conn: PeerConnectSuccess<_>| async move {
932 if is_sender {
933 conn.remote
934 .send_file_with_custom_opts(
935 "../resources/TheBridge.pdf",
936 32 * 1024,
937 TransferType::FileTransfer,
938 )
939 .await
940 .expect("Failed to send file");
941 } else {
942 const HANDLE_BUDGET: std::time::Duration =
959 std::time::Duration::from_secs(60);
960 const STATUS_BUDGET: std::time::Duration =
961 std::time::Duration::from_secs(60);
962
963 let mut handles = conn.incoming_object_transfer_handles.take().unwrap();
964 let mut handle = match citadel_io::tokio::time::timeout(
965 HANDLE_BUDGET,
966 handles.recv(),
967 )
968 .await
969 {
970 Ok(Some(handle)) => handle,
971 Ok(None) => panic!(
972 "the incoming-transfer channel closed before any handle arrived"
973 ),
974 Err(_) => panic!(
975 "no incoming file-transfer handle within {HANDLE_BUDGET:?}: the sender never started, or its request never routed here"
976 ),
977 };
978 handle.accept().unwrap();
979
980 use citadel_types::proto::ObjectTransferStatus;
981 use futures::StreamExt;
982 let mut path = None;
983 while let Some(status) = match citadel_io::tokio::time::timeout(
984 STATUS_BUDGET,
985 handle.next(),
986 )
987 .await
988 {
989 Ok(status) => status,
990 Err(_) => panic!(
991 "the transfer stalled: no status within {STATUS_BUDGET:?}, last seen {path:?}"
992 ),
993 } {
994 match status {
995 ObjectTransferStatus::ReceptionComplete => {
996 let cmp =
997 include_bytes!("../../../../resources/TheBridge.pdf");
998 let streamed_data =
999 citadel_io::tokio::fs::read(path.clone().unwrap())
1000 .await
1001 .unwrap();
1002 assert_eq!(
1003 cmp,
1004 streamed_data.as_slice(),
1005 "Original data and streamed data does not match"
1006 );
1007
1008 log::info!(target: "citadel", "Peer has finished receiving and verifying the file!");
1009 break;
1010 }
1011
1012 ObjectTransferStatus::ReceptionBeginning(file_path, vfm) => {
1013 path = Some(file_path);
1014 assert_eq!(vfm.name, "TheBridge.pdf")
1015 }
1016
1017 _ => {}
1018 }
1019 }
1020 }
1021
1022 conn
1023 };
1024 let peer_count = if idx == 0 { peer_count } else { 2 };
1027 let _ = handle_peer_connect_successes(
1028 results,
1029 session_cid,
1030 peer_count,
1031 udp_mode,
1032 check,
1033 )
1034 .await;
1035
1036 if is_sender {
1037 sender_success.store(true, Ordering::Relaxed);
1038 } else {
1039 receiver_success.store(true, Ordering::Relaxed);
1040 }
1041
1042 log::info!(target: "citadel", "***PEER {uuid} (is_sender: {is_sender}) finished all checks***");
1043 wait_for_peers().await;
1044 log::info!(target: "citadel", "***PEER {uuid} (is_sender: {is_sender}) shutting down***");
1045 remote.shutdown_kernel().await
1046 },
1047 );
1048
1049 let client = DefaultNodeBuilder::default()
1050 .with_stun_servers(crate::test_common::LOCALHOST_STUN_SERVERS)
1051 .build(client_kernel)
1052 .unwrap();
1053 client_kernels.push(async move { client.await.map(|_| ()) });
1054 }
1055
1056 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
1057
1058 if let Err(err) = futures::future::try_select(server, clients).await {
1059 return match err {
1060 futures::future::Either::Left(res) => Err(res.0.into_string().into()),
1061 futures::future::Either::Right(res) => Err(res.0.into_string().into()),
1062 };
1063 }
1064
1065 assert!(sender_success.load(Ordering::Relaxed));
1066 assert!(receiver_success.load(Ordering::Relaxed));
1067 Ok(())
1068 }
1069
1070 #[rstest]
1071 #[case(2)]
1072 #[timeout(std::time::Duration::from_secs(90))]
1073 #[tokio::test(flavor = "multi_thread")]
1074 async fn test_peer_to_peer_rekey(
1075 #[case] peer_count: usize,
1076 ) -> Result<(), Box<dyn std::error::Error>> {
1077 assert!(peer_count > 1);
1078 citadel_logging::setup_log();
1079 TestBarrier::setup(peer_count);
1080 let udp_mode = UdpMode::Enabled;
1081
1082 let client_success = &AtomicUsize::new(0);
1083 let (server, server_addr) = server_info::<StackedRatchet>();
1084
1085 let client_kernels = FuturesUnordered::new();
1086 let total_peers = (0..peer_count)
1087 .map(|_| Uuid::new_v4())
1088 .collect::<Vec<Uuid>>();
1089
1090 for idx in 0..peer_count {
1091 let uuid = total_peers.get(idx).cloned().unwrap();
1092 let peers = total_peers
1093 .clone()
1094 .into_iter()
1095 .filter(|r| r != &uuid)
1096 .map(UserIdentifier::from)
1097 .collect::<Vec<UserIdentifier>>();
1098
1099 let mut agg = PeerConnectionSetupAggregator::default();
1100
1101 for peer in peers {
1102 agg = agg
1103 .with_peer_custom(peer)
1104 .ensure_registered()
1105 .with_udp_mode(udp_mode)
1106 .with_session_security_settings(SessionSecuritySettings::default())
1107 .add();
1108 }
1109
1110 let server_connection_settings =
1111 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
1112 .build()
1113 .unwrap();
1114
1115 let client_kernel = PeerConnectionKernel::new(
1116 server_connection_settings,
1117 agg,
1118 move |results, remote| async move {
1119 log::info!(target: "citadel", "***PEER {uuid} CONNECTED***");
1120 let session_cid = remote.conn_type.get_session_cid();
1121
1122 let check = move |conn: PeerConnectSuccess<_>| async move {
1123 if idx == 0 {
1124 for x in 1..10 {
1125 assert_eq!(
1126 conn.remote.rekey().await.expect("Failed to rekey"),
1127 Some(x)
1128 );
1129 }
1130 }
1131
1132 conn
1133 };
1134
1135 let results = handle_peer_connect_successes(
1136 results,
1137 session_cid,
1138 peer_count,
1139 udp_mode,
1140 check,
1141 )
1142 .await;
1143
1144 log::info!(target: "citadel", "***PEER {uuid} finished all check (count: {})s***", results.len());
1145 let _ = client_success.fetch_add(1, Ordering::Relaxed);
1146 wait_for_peers().await;
1147 remote.shutdown_kernel().await
1148 },
1149 );
1150
1151 let client = DefaultNodeBuilder::default()
1152 .with_stun_servers(crate::test_common::LOCALHOST_STUN_SERVERS)
1153 .build(client_kernel)?;
1154 client_kernels.push(async move { client.await.map(|_| ()) });
1155 }
1156
1157 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
1158
1159 if let Err(err) = futures::future::try_select(server, clients).await {
1160 return match err {
1161 futures::future::Either::Left(res) => Err(res.0.into_string().into()),
1162 futures::future::Either::Right(res) => Err(res.0.into_string().into()),
1163 };
1164 }
1165
1166 assert_eq!(client_success.load(Ordering::Relaxed), peer_count);
1167 Ok(())
1168 }
1169
1170 #[rstest]
1171 #[case(2)]
1172 #[timeout(std::time::Duration::from_secs(90))]
1173 #[tokio::test(flavor = "multi_thread")]
1174 async fn test_peer_to_peer_disconnect(
1175 #[case] peer_count: usize,
1176 ) -> Result<(), Box<dyn std::error::Error>> {
1177 assert!(peer_count > 1);
1178 citadel_logging::setup_log();
1179 TestBarrier::setup(peer_count);
1180 let udp_mode = UdpMode::Enabled;
1181
1182 let client_success = &AtomicUsize::new(0);
1183 let (server, server_addr) = server_info::<StackedRatchet>();
1184
1185 let client_kernels = FuturesUnordered::new();
1186 let total_peers = (0..peer_count)
1187 .map(|_| Uuid::new_v4())
1188 .collect::<Vec<Uuid>>();
1189
1190 for idx in 0..peer_count {
1191 let uuid = total_peers.get(idx).cloned().unwrap();
1192 let peers = total_peers
1193 .clone()
1194 .into_iter()
1195 .filter(|r| r != &uuid)
1196 .map(UserIdentifier::from)
1197 .collect::<Vec<UserIdentifier>>();
1198
1199 let mut agg = PeerConnectionSetupAggregator::default();
1200
1201 for peer in peers {
1202 agg = agg
1203 .with_peer_custom(peer)
1204 .ensure_registered()
1205 .with_udp_mode(udp_mode)
1206 .with_session_security_settings(SessionSecuritySettings::default())
1207 .add();
1208 }
1209
1210 let server_connection_settings =
1211 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
1212 .build()
1213 .unwrap();
1214
1215 let client_kernel = PeerConnectionKernel::new(
1216 server_connection_settings,
1217 agg,
1218 move |results, remote| async move {
1219 log::info!(target: "citadel", "***PEER {uuid} CONNECTED***");
1220 wait_for_peers().await;
1221 let session_cid = remote.conn_type.get_session_cid();
1222
1223 let check = move |conn: PeerConnectSuccess<_>| async move {
1224 conn.remote
1225 .disconnect()
1226 .await
1227 .expect("Failed to p2p disconnect");
1228 conn
1229 };
1230 let _ = handle_peer_connect_successes(
1231 results,
1232 session_cid,
1233 peer_count,
1234 udp_mode,
1235 check,
1236 )
1237 .await;
1238 log::info!(target: "citadel", "***PEER {uuid} finished all checks***");
1239
1240 let _ = client_success.fetch_add(1, Ordering::Relaxed);
1241 wait_for_peers().await;
1242 remote.shutdown_kernel().await
1243 },
1244 );
1245
1246 let client = DefaultNodeBuilder::default()
1247 .with_stun_servers(crate::test_common::LOCALHOST_STUN_SERVERS)
1248 .build(client_kernel)?;
1249 client_kernels.push(async move { client.await.map(|_| ()) });
1250 }
1251
1252 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
1253
1254 if let Err(err) = futures::future::try_select(server, clients).await {
1255 return match err {
1256 futures::future::Either::Left(res) => Err(res.0.into_string().into()),
1257 futures::future::Either::Right(res) => Err(res.0.into_string().into()),
1258 };
1259 }
1260
1261 assert_eq!(client_success.load(Ordering::Relaxed), peer_count);
1262 Ok(())
1263 }
1264
1265 #[rstest]
1266 #[case(SecrecyMode::BestEffort, Some("test-p2p-password"))]
1267 #[timeout(std::time::Duration::from_secs(240))]
1268 #[citadel_io::tokio::test(flavor = "multi_thread")]
1269 async fn test_p2p_wrong_session_password(
1270 #[case] secrecy_mode: SecrecyMode,
1271 #[case] p2p_password: Option<&'static str>,
1272 #[values(KemAlgorithm::MlKem)] kem: KemAlgorithm,
1273 #[values(EncryptionAlgorithm::AES_GCM_256)] enx: EncryptionAlgorithm,
1274 ) {
1275 citadel_logging::setup_log_no_panic_hook();
1276 TestBarrier::setup(2);
1277 let (server, server_addr) = server_info::<StackedRatchet>();
1278 let peer_0_error_received = &AtomicBool::new(false);
1279 let peer_1_error_received = &AtomicBool::new(false);
1280
1281 let uuid0 = Uuid::new_v4();
1282 let uuid1 = Uuid::new_v4();
1283 let session_security = SessionSecuritySettingsBuilder::default()
1284 .with_secrecy_mode(secrecy_mode)
1285 .with_crypto_params(kem + enx)
1286 .build()
1287 .unwrap();
1288
1289 let mut peer0_agg = PeerConnectionSetupAggregator::default()
1290 .with_peer_custom(uuid1)
1291 .ensure_registered()
1292 .with_session_security_settings(session_security);
1293
1294 if let Some(password) = p2p_password {
1295 peer0_agg = peer0_agg.with_session_password(password);
1296 }
1297
1298 let peer0_connection = peer0_agg.add();
1299
1300 let mut peer1_agg = PeerConnectionSetupAggregator::default()
1301 .with_peer_custom(uuid0)
1302 .ensure_registered()
1303 .with_session_security_settings(session_security);
1304
1305 if let Some(_password) = p2p_password {
1306 peer1_agg = peer1_agg.with_session_password("wrong password");
1307 }
1308
1309 let peer1_connection = peer1_agg.add();
1310
1311 let server_connection_settings0 =
1312 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid0)
1313 .with_udp_mode(UdpMode::Enabled)
1314 .with_session_security_settings(session_security)
1315 .build()
1316 .unwrap();
1317
1318 let server_connection_settings1 =
1319 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid1)
1320 .with_udp_mode(UdpMode::Enabled)
1321 .with_session_security_settings(session_security)
1322 .build()
1323 .unwrap();
1324
1325 let client_kernel0 = PeerConnectionKernel::new(
1326 server_connection_settings0,
1327 peer0_connection,
1328 move |mut connection, remote| async move {
1329 wait_for_peers().await;
1330 let conn = connection.recv().await.unwrap();
1331 log::trace!(target: "citadel", "Peer 0 {} received: {:?}", remote.conn_type.get_session_cid(), conn);
1332 if conn.is_ok() {
1333 peer_0_error_received.store(true, Ordering::SeqCst);
1334 }
1335 wait_for_peers().await;
1336 remote.shutdown_kernel().await
1337 },
1338 );
1339
1340 let client_kernel1 = PeerConnectionKernel::new(
1341 server_connection_settings1,
1342 peer1_connection,
1343 move |mut connection, remote| async move {
1344 wait_for_peers().await;
1345 let conn = connection.recv().await.unwrap();
1346 log::trace!(target: "citadel", "Peer 1 {} received: {:?}", remote.conn_type.get_session_cid(), conn);
1347 if conn.is_ok() {
1348 peer_1_error_received.store(true, Ordering::SeqCst);
1349 }
1350 wait_for_peers().await;
1351 remote.shutdown_kernel().await
1352 },
1353 );
1354
1355 let client0 = DefaultNodeBuilder::default()
1356 .with_stun_servers(crate::test_common::LOCALHOST_STUN_SERVERS)
1357 .build(client_kernel0)
1358 .unwrap();
1359 let client1 = DefaultNodeBuilder::default()
1360 .with_stun_servers(crate::test_common::LOCALHOST_STUN_SERVERS)
1361 .build(client_kernel1)
1362 .unwrap();
1363 let clients = futures::future::try_join(client0, client1);
1364
1365 let task = async move {
1366 tokio::select! {
1367 server_res = server => Err(citadel_io::error!(citadel_io::ErrorCode::PrefabServerEndedPrematurely, citadel_io::Dbg(server_res.map(|_| ())))),
1368 client_res = clients => client_res.map(|_| ())
1369 }
1370 };
1371
1372 tokio::time::timeout(Duration::from_secs(120), task)
1373 .await
1374 .unwrap()
1375 .unwrap();
1376
1377 assert!(!peer_0_error_received.load(Ordering::SeqCst));
1378 assert!(!peer_1_error_received.load(Ordering::SeqCst));
1379 }
1380
1381 async fn handle_peer_connect_successes<F, Fut, R: Ratchet>(
1382 mut conn_rx: Receiver<Result<PeerConnectSuccess<R>, NetworkError>>,
1383 session_cid: u64,
1384 peer_count: usize,
1385 udp_mode: UdpMode,
1386 checks: F,
1387 ) -> Vec<PeerConnectSuccess<R>>
1388 where
1389 F: Fn(PeerConnectSuccess<R>) -> Fut + Send + Clone + 'static,
1390 Fut: Future<Output = PeerConnectSuccess<R>> + Send,
1391 {
1392 let (finished_tx, finished_rx) = tokio::sync::oneshot::channel();
1393
1394 let task = async move {
1395 let (done_tx, mut done_rx) = tokio::sync::mpsc::unbounded_channel();
1396 let mut conns = vec![];
1397 while let Some(conn) = conn_rx.recv().await {
1398 conns.push(conn);
1399 if conns.len() == peer_count - 1 {
1400 break;
1401 }
1402 }
1403
1404 log::info!(target: "citadel", "~~~*** Peer {session_cid} has {} connections to other peers ***~~~", conns.len());
1405
1406 for conn in conns {
1407 let conn = conn.expect("Error receiving peer connection");
1408 handle_peer_connect_success(
1409 conn,
1410 done_tx.clone(),
1411 session_cid,
1412 udp_mode,
1413 checks.clone(),
1414 );
1415 }
1416
1417 let mut ret = vec![];
1419 while let Some(done) = done_rx.recv().await {
1420 ret.push(done);
1421 if ret.len() == peer_count - 1 {
1422 break;
1423 }
1424 }
1425
1426 finished_tx
1427 .send(ret)
1428 .expect("Error sending finished signal in handle_peer_connect_successes");
1429 };
1430
1431 drop(tokio::task::spawn(task));
1432 let ret = finished_rx
1433 .await
1434 .expect("Error receiving finished signal in handle_peer_connect_successes");
1435
1436 assert_eq!(ret.len(), peer_count - 1);
1437 ret
1438 }
1439
1440 fn handle_peer_connect_success<F, Fut, R: Ratchet>(
1441 mut conn: PeerConnectSuccess<R>,
1442 done_tx: UnboundedSender<PeerConnectSuccess<R>>,
1443 session_cid: u64,
1444 udp_mode: UdpMode,
1445 checks: F,
1446 ) where
1447 F: Fn(PeerConnectSuccess<R>) -> Fut + Send + Clone + 'static,
1448 Fut: Future<Output = PeerConnectSuccess<R>> + Send,
1449 {
1450 let task = async move {
1451 if udp_mode == UdpMode::Enabled {
1452 let path = conn.channel.ensure_direct().await;
1454 assert!(path.is_ok(), "no direct path: {path:?}");
1455 }
1456 let chan = conn.udp_channel_rx.take();
1457 crate::test_common::p2p_assertions(session_cid, &conn).await;
1458 crate::test_common::udp_mode_assertions(udp_mode, chan).await;
1459 let conn = checks(conn).await;
1460 done_tx
1461 .send(conn)
1462 .expect("Error sending done signal in handle_peer_connect_success");
1463 };
1464
1465 drop(tokio::task::spawn(task));
1466 }
1467}