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().build(client_kernel).unwrap();
737 client_kernels.push(async move { client.await.map(|_| ()) });
738 }
739
740 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
741
742 assert!(futures::future::try_select(server, clients).await.is_ok());
743
744 assert_eq!(client_success.load(Ordering::Relaxed), peer_count);
745 }
746
747 #[rstest]
748 #[case(2, HeaderObfuscatorSettings::default())]
749 #[case(2, HeaderObfuscatorSettings::Enabled)]
750 #[case(2, HeaderObfuscatorSettings::EnabledWithKey(12345))]
751 #[case(3, HeaderObfuscatorSettings::default())]
752 #[timeout(Duration::from_secs(90))]
753 #[tokio::test(flavor = "multi_thread")]
754 async fn peer_to_peer_connect_transient(
755 #[case] peer_count: usize,
756 #[case] header_obfuscator_settings: HeaderObfuscatorSettings,
757 ) -> Result<(), Box<dyn std::error::Error>> {
758 assert!(peer_count > 1);
759 citadel_logging::setup_log();
760 TestBarrier::setup(peer_count);
761 let udp_mode = UdpMode::Enabled;
762
763 let do_deregister = peer_count == 2;
764
765 let client_success = &AtomicUsize::new(0);
766 let (server, server_addr) = server_info::<StackedRatchet>();
767
768 let client_kernels = FuturesUnordered::new();
769 let total_peers = (0..peer_count)
770 .map(|_| Uuid::new_v4())
771 .collect::<Vec<Uuid>>();
772
773 for idx in 0..peer_count {
774 let uuid = total_peers.get(idx).cloned().unwrap();
775 let peers = total_peers
776 .clone()
777 .into_iter()
778 .filter(|r| r != &uuid)
779 .map(UserIdentifier::from)
780 .collect::<Vec<UserIdentifier>>();
781
782 let mut agg = PeerConnectionSetupAggregator::default();
783
784 for peer in peers {
785 let security_settings = SessionSecuritySettings {
786 header_obfuscator_settings,
787 ..Default::default()
788 };
789 agg = agg
790 .with_peer_custom(peer)
791 .with_udp_mode(udp_mode)
792 .ensure_registered()
793 .with_session_security_settings(security_settings)
794 .add();
795 }
796
797 let server_connection_settings =
798 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
799 .build()
800 .unwrap();
801
802 let client_kernel = PeerConnectionKernel::new(
803 server_connection_settings,
804 agg,
805 move |results, remote| async move {
806 log::info!(target: "citadel", "***PEER {uuid} CONNECTED***");
807 let session_cid = remote.conn_type.get_session_cid();
808
809 let check = move |conn: PeerConnectSuccess<_>| async move {
810 if do_deregister {
811 conn.remote
812 .deregister()
813 .await
814 .expect("Deregistration failed");
815 assert!(!conn
816 .remote
817 .inner
818 .account_manager()
819 .get_persistence_handler()
820 .hyperlan_peer_exists(session_cid, conn.channel.get_peer_cid())
821 .await
822 .unwrap());
823 }
824 conn
825 };
826
827 let _ = handle_peer_connect_successes(
828 results,
829 session_cid,
830 peer_count,
831 udp_mode,
832 check,
833 )
834 .await;
835
836 log::info!(target: "citadel", "***PEER {uuid} finished all checks***");
837 let _ = client_success.fetch_add(1, Ordering::Relaxed);
838 wait_for_peers().await;
839 remote.shutdown_kernel().await
840 },
841 );
842
843 let client = DefaultNodeBuilder::default().build(client_kernel)?;
844 client_kernels.push(async move { client.await.map(|_| ()) });
845 }
846
847 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
848
849 if let Err(err) = futures::future::try_select(server, clients).await {
850 return match err {
851 futures::future::Either::Left(res) => Err(res.0.into_string().into()),
852 futures::future::Either::Right(res) => Err(res.0.into_string().into()),
853 };
854 }
855
856 assert_eq!(client_success.load(Ordering::Relaxed), peer_count);
857 Ok(())
858 }
859
860 #[rstest]
861 #[case(2)]
862 #[case(3)]
863 #[timeout(std::time::Duration::from_secs(180))]
864 #[tokio::test(flavor = "multi_thread")]
865 async fn test_peer_to_peer_file_transfer(
866 #[case] peer_count: usize,
867 ) -> Result<(), Box<dyn std::error::Error>> {
868 assert!(peer_count > 1);
869 citadel_logging::setup_log();
870 TestBarrier::setup(peer_count);
871 let udp_mode = UdpMode::Enabled;
872
873 let sender_success = &AtomicBool::new(false);
874 let receiver_success = &AtomicBool::new(false);
875
876 let (server, server_addr) = server_info::<StackedRatchet>();
877
878 let client_kernels = FuturesUnordered::new();
879 let total_peers = (0..peer_count)
880 .map(|_| Uuid::new_v4())
881 .collect::<Vec<Uuid>>();
882
883 let sender_uuid = total_peers[0];
884
885 for idx in 0..peer_count {
886 let uuid = total_peers.get(idx).cloned().unwrap();
887 let mut peers = total_peers
888 .clone()
889 .into_iter()
890 .filter(|r| r != &uuid)
891 .map(UserIdentifier::from)
892 .collect::<Vec<UserIdentifier>>();
893 if idx != 0 {
899 peers = vec![sender_uuid.into()];
900 }
901
902 let mut agg = PeerConnectionSetupAggregator::default();
903
904 for peer in peers {
905 agg = agg
906 .with_peer_custom(peer)
907 .ensure_registered()
908 .with_udp_mode(udp_mode)
909 .with_session_security_settings(SessionSecuritySettings::default())
910 .add();
911 }
912
913 let server_connection_settings =
914 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
915 .build()
916 .unwrap();
917
918 let client_kernel = PeerConnectionKernel::new(
919 server_connection_settings,
920 agg,
921 move |results, remote| async move {
922 log::info!(target: "citadel", "***PEER {uuid} CONNECTED***");
923 wait_for_peers().await;
924 let session_cid = remote.conn_type.get_session_cid();
925 let is_sender = idx == 0; let check = move |mut conn: PeerConnectSuccess<_>| async move {
927 if is_sender {
928 conn.remote
929 .send_file_with_custom_opts(
930 "../resources/TheBridge.pdf",
931 32 * 1024,
932 TransferType::FileTransfer,
933 )
934 .await
935 .expect("Failed to send file");
936 } else {
937 const HANDLE_BUDGET: std::time::Duration =
954 std::time::Duration::from_secs(60);
955 const STATUS_BUDGET: std::time::Duration =
956 std::time::Duration::from_secs(60);
957
958 let mut handles = conn.incoming_object_transfer_handles.take().unwrap();
959 let mut handle = match citadel_io::tokio::time::timeout(
960 HANDLE_BUDGET,
961 handles.recv(),
962 )
963 .await
964 {
965 Ok(Some(handle)) => handle,
966 Ok(None) => panic!(
967 "the incoming-transfer channel closed before any handle arrived"
968 ),
969 Err(_) => panic!(
970 "no incoming file-transfer handle within {HANDLE_BUDGET:?}: the sender never started, or its request never routed here"
971 ),
972 };
973 handle.accept().unwrap();
974
975 use citadel_types::proto::ObjectTransferStatus;
976 use futures::StreamExt;
977 let mut path = None;
978 while let Some(status) = match citadel_io::tokio::time::timeout(
979 STATUS_BUDGET,
980 handle.next(),
981 )
982 .await
983 {
984 Ok(status) => status,
985 Err(_) => panic!(
986 "the transfer stalled: no status within {STATUS_BUDGET:?}, last seen {path:?}"
987 ),
988 } {
989 match status {
990 ObjectTransferStatus::ReceptionComplete => {
991 let cmp =
992 include_bytes!("../../../../resources/TheBridge.pdf");
993 let streamed_data =
994 citadel_io::tokio::fs::read(path.clone().unwrap())
995 .await
996 .unwrap();
997 assert_eq!(
998 cmp,
999 streamed_data.as_slice(),
1000 "Original data and streamed data does not match"
1001 );
1002
1003 log::info!(target: "citadel", "Peer has finished receiving and verifying the file!");
1004 break;
1005 }
1006
1007 ObjectTransferStatus::ReceptionBeginning(file_path, vfm) => {
1008 path = Some(file_path);
1009 assert_eq!(vfm.name, "TheBridge.pdf")
1010 }
1011
1012 _ => {}
1013 }
1014 }
1015 }
1016
1017 conn
1018 };
1019 let peer_count = if idx == 0 { peer_count } else { 2 };
1022 let _ = handle_peer_connect_successes(
1023 results,
1024 session_cid,
1025 peer_count,
1026 udp_mode,
1027 check,
1028 )
1029 .await;
1030
1031 if is_sender {
1032 sender_success.store(true, Ordering::Relaxed);
1033 } else {
1034 receiver_success.store(true, Ordering::Relaxed);
1035 }
1036
1037 log::info!(target: "citadel", "***PEER {uuid} (is_sender: {is_sender}) finished all checks***");
1038 wait_for_peers().await;
1039 log::info!(target: "citadel", "***PEER {uuid} (is_sender: {is_sender}) shutting down***");
1040 remote.shutdown_kernel().await
1041 },
1042 );
1043
1044 let client = DefaultNodeBuilder::default().build(client_kernel).unwrap();
1045 client_kernels.push(async move { client.await.map(|_| ()) });
1046 }
1047
1048 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
1049
1050 if let Err(err) = futures::future::try_select(server, clients).await {
1051 return match err {
1052 futures::future::Either::Left(res) => Err(res.0.into_string().into()),
1053 futures::future::Either::Right(res) => Err(res.0.into_string().into()),
1054 };
1055 }
1056
1057 assert!(sender_success.load(Ordering::Relaxed));
1058 assert!(receiver_success.load(Ordering::Relaxed));
1059 Ok(())
1060 }
1061
1062 #[rstest]
1063 #[case(2)]
1064 #[timeout(std::time::Duration::from_secs(90))]
1065 #[tokio::test(flavor = "multi_thread")]
1066 async fn test_peer_to_peer_rekey(
1067 #[case] peer_count: usize,
1068 ) -> Result<(), Box<dyn std::error::Error>> {
1069 assert!(peer_count > 1);
1070 citadel_logging::setup_log();
1071 TestBarrier::setup(peer_count);
1072 let udp_mode = UdpMode::Enabled;
1073
1074 let client_success = &AtomicUsize::new(0);
1075 let (server, server_addr) = server_info::<StackedRatchet>();
1076
1077 let client_kernels = FuturesUnordered::new();
1078 let total_peers = (0..peer_count)
1079 .map(|_| Uuid::new_v4())
1080 .collect::<Vec<Uuid>>();
1081
1082 for idx in 0..peer_count {
1083 let uuid = total_peers.get(idx).cloned().unwrap();
1084 let peers = total_peers
1085 .clone()
1086 .into_iter()
1087 .filter(|r| r != &uuid)
1088 .map(UserIdentifier::from)
1089 .collect::<Vec<UserIdentifier>>();
1090
1091 let mut agg = PeerConnectionSetupAggregator::default();
1092
1093 for peer in peers {
1094 agg = agg
1095 .with_peer_custom(peer)
1096 .ensure_registered()
1097 .with_udp_mode(udp_mode)
1098 .with_session_security_settings(SessionSecuritySettings::default())
1099 .add();
1100 }
1101
1102 let server_connection_settings =
1103 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
1104 .build()
1105 .unwrap();
1106
1107 let client_kernel = PeerConnectionKernel::new(
1108 server_connection_settings,
1109 agg,
1110 move |results, remote| async move {
1111 log::info!(target: "citadel", "***PEER {uuid} CONNECTED***");
1112 let session_cid = remote.conn_type.get_session_cid();
1113
1114 let check = move |conn: PeerConnectSuccess<_>| async move {
1115 if idx == 0 {
1116 for x in 1..10 {
1117 assert_eq!(
1118 conn.remote.rekey().await.expect("Failed to rekey"),
1119 Some(x)
1120 );
1121 }
1122 }
1123
1124 conn
1125 };
1126
1127 let results = handle_peer_connect_successes(
1128 results,
1129 session_cid,
1130 peer_count,
1131 udp_mode,
1132 check,
1133 )
1134 .await;
1135
1136 log::info!(target: "citadel", "***PEER {uuid} finished all check (count: {})s***", results.len());
1137 let _ = client_success.fetch_add(1, Ordering::Relaxed);
1138 wait_for_peers().await;
1139 remote.shutdown_kernel().await
1140 },
1141 );
1142
1143 let client = DefaultNodeBuilder::default().build(client_kernel)?;
1144 client_kernels.push(async move { client.await.map(|_| ()) });
1145 }
1146
1147 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
1148
1149 if let Err(err) = futures::future::try_select(server, clients).await {
1150 return match err {
1151 futures::future::Either::Left(res) => Err(res.0.into_string().into()),
1152 futures::future::Either::Right(res) => Err(res.0.into_string().into()),
1153 };
1154 }
1155
1156 assert_eq!(client_success.load(Ordering::Relaxed), peer_count);
1157 Ok(())
1158 }
1159
1160 #[rstest]
1161 #[case(2)]
1162 #[timeout(std::time::Duration::from_secs(90))]
1163 #[tokio::test(flavor = "multi_thread")]
1164 async fn test_peer_to_peer_disconnect(
1165 #[case] peer_count: usize,
1166 ) -> Result<(), Box<dyn std::error::Error>> {
1167 assert!(peer_count > 1);
1168 citadel_logging::setup_log();
1169 TestBarrier::setup(peer_count);
1170 let udp_mode = UdpMode::Enabled;
1171
1172 let client_success = &AtomicUsize::new(0);
1173 let (server, server_addr) = server_info::<StackedRatchet>();
1174
1175 let client_kernels = FuturesUnordered::new();
1176 let total_peers = (0..peer_count)
1177 .map(|_| Uuid::new_v4())
1178 .collect::<Vec<Uuid>>();
1179
1180 for idx in 0..peer_count {
1181 let uuid = total_peers.get(idx).cloned().unwrap();
1182 let peers = total_peers
1183 .clone()
1184 .into_iter()
1185 .filter(|r| r != &uuid)
1186 .map(UserIdentifier::from)
1187 .collect::<Vec<UserIdentifier>>();
1188
1189 let mut agg = PeerConnectionSetupAggregator::default();
1190
1191 for peer in peers {
1192 agg = agg
1193 .with_peer_custom(peer)
1194 .ensure_registered()
1195 .with_udp_mode(udp_mode)
1196 .with_session_security_settings(SessionSecuritySettings::default())
1197 .add();
1198 }
1199
1200 let server_connection_settings =
1201 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
1202 .build()
1203 .unwrap();
1204
1205 let client_kernel = PeerConnectionKernel::new(
1206 server_connection_settings,
1207 agg,
1208 move |results, remote| async move {
1209 log::info!(target: "citadel", "***PEER {uuid} CONNECTED***");
1210 wait_for_peers().await;
1211 let session_cid = remote.conn_type.get_session_cid();
1212
1213 let check = move |conn: PeerConnectSuccess<_>| async move {
1214 conn.remote
1215 .disconnect()
1216 .await
1217 .expect("Failed to p2p disconnect");
1218 conn
1219 };
1220 let _ = handle_peer_connect_successes(
1221 results,
1222 session_cid,
1223 peer_count,
1224 udp_mode,
1225 check,
1226 )
1227 .await;
1228 log::info!(target: "citadel", "***PEER {uuid} finished all checks***");
1229
1230 let _ = client_success.fetch_add(1, Ordering::Relaxed);
1231 wait_for_peers().await;
1232 remote.shutdown_kernel().await
1233 },
1234 );
1235
1236 let client = DefaultNodeBuilder::default().build(client_kernel)?;
1237 client_kernels.push(async move { client.await.map(|_| ()) });
1238 }
1239
1240 let clients = Box::pin(async move { client_kernels.try_collect::<()>().await.map(|_| ()) });
1241
1242 if let Err(err) = futures::future::try_select(server, clients).await {
1243 return match err {
1244 futures::future::Either::Left(res) => Err(res.0.into_string().into()),
1245 futures::future::Either::Right(res) => Err(res.0.into_string().into()),
1246 };
1247 }
1248
1249 assert_eq!(client_success.load(Ordering::Relaxed), peer_count);
1250 Ok(())
1251 }
1252
1253 #[rstest]
1254 #[case(SecrecyMode::BestEffort, Some("test-p2p-password"))]
1255 #[timeout(std::time::Duration::from_secs(240))]
1256 #[citadel_io::tokio::test(flavor = "multi_thread")]
1257 async fn test_p2p_wrong_session_password(
1258 #[case] secrecy_mode: SecrecyMode,
1259 #[case] p2p_password: Option<&'static str>,
1260 #[values(KemAlgorithm::MlKem)] kem: KemAlgorithm,
1261 #[values(EncryptionAlgorithm::AES_GCM_256)] enx: EncryptionAlgorithm,
1262 ) {
1263 citadel_logging::setup_log_no_panic_hook();
1264 TestBarrier::setup(2);
1265 let (server, server_addr) = server_info::<StackedRatchet>();
1266 let peer_0_error_received = &AtomicBool::new(false);
1267 let peer_1_error_received = &AtomicBool::new(false);
1268
1269 let uuid0 = Uuid::new_v4();
1270 let uuid1 = Uuid::new_v4();
1271 let session_security = SessionSecuritySettingsBuilder::default()
1272 .with_secrecy_mode(secrecy_mode)
1273 .with_crypto_params(kem + enx)
1274 .build()
1275 .unwrap();
1276
1277 let mut peer0_agg = PeerConnectionSetupAggregator::default()
1278 .with_peer_custom(uuid1)
1279 .ensure_registered()
1280 .with_session_security_settings(session_security);
1281
1282 if let Some(password) = p2p_password {
1283 peer0_agg = peer0_agg.with_session_password(password);
1284 }
1285
1286 let peer0_connection = peer0_agg.add();
1287
1288 let mut peer1_agg = PeerConnectionSetupAggregator::default()
1289 .with_peer_custom(uuid0)
1290 .ensure_registered()
1291 .with_session_security_settings(session_security);
1292
1293 if let Some(_password) = p2p_password {
1294 peer1_agg = peer1_agg.with_session_password("wrong password");
1295 }
1296
1297 let peer1_connection = peer1_agg.add();
1298
1299 let server_connection_settings0 =
1300 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid0)
1301 .with_udp_mode(UdpMode::Enabled)
1302 .with_session_security_settings(session_security)
1303 .build()
1304 .unwrap();
1305
1306 let server_connection_settings1 =
1307 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid1)
1308 .with_udp_mode(UdpMode::Enabled)
1309 .with_session_security_settings(session_security)
1310 .build()
1311 .unwrap();
1312
1313 let client_kernel0 = PeerConnectionKernel::new(
1314 server_connection_settings0,
1315 peer0_connection,
1316 move |mut connection, remote| async move {
1317 wait_for_peers().await;
1318 let conn = connection.recv().await.unwrap();
1319 log::trace!(target: "citadel", "Peer 0 {} received: {:?}", remote.conn_type.get_session_cid(), conn);
1320 if conn.is_ok() {
1321 peer_0_error_received.store(true, Ordering::SeqCst);
1322 }
1323 wait_for_peers().await;
1324 remote.shutdown_kernel().await
1325 },
1326 );
1327
1328 let client_kernel1 = PeerConnectionKernel::new(
1329 server_connection_settings1,
1330 peer1_connection,
1331 move |mut connection, remote| async move {
1332 wait_for_peers().await;
1333 let conn = connection.recv().await.unwrap();
1334 log::trace!(target: "citadel", "Peer 1 {} received: {:?}", remote.conn_type.get_session_cid(), conn);
1335 if conn.is_ok() {
1336 peer_1_error_received.store(true, Ordering::SeqCst);
1337 }
1338 wait_for_peers().await;
1339 remote.shutdown_kernel().await
1340 },
1341 );
1342
1343 let client0 = DefaultNodeBuilder::default().build(client_kernel0).unwrap();
1344 let client1 = DefaultNodeBuilder::default().build(client_kernel1).unwrap();
1345 let clients = futures::future::try_join(client0, client1);
1346
1347 let task = async move {
1348 tokio::select! {
1349 server_res = server => Err(citadel_io::error!(citadel_io::ErrorCode::PrefabServerEndedPrematurely, citadel_io::Dbg(server_res.map(|_| ())))),
1350 client_res = clients => client_res.map(|_| ())
1351 }
1352 };
1353
1354 tokio::time::timeout(Duration::from_secs(120), task)
1355 .await
1356 .unwrap()
1357 .unwrap();
1358
1359 assert!(!peer_0_error_received.load(Ordering::SeqCst));
1360 assert!(!peer_1_error_received.load(Ordering::SeqCst));
1361 }
1362
1363 async fn handle_peer_connect_successes<F, Fut, R: Ratchet>(
1364 mut conn_rx: Receiver<Result<PeerConnectSuccess<R>, NetworkError>>,
1365 session_cid: u64,
1366 peer_count: usize,
1367 udp_mode: UdpMode,
1368 checks: F,
1369 ) -> Vec<PeerConnectSuccess<R>>
1370 where
1371 F: Fn(PeerConnectSuccess<R>) -> Fut + Send + Clone + 'static,
1372 Fut: Future<Output = PeerConnectSuccess<R>> + Send,
1373 {
1374 let (finished_tx, finished_rx) = tokio::sync::oneshot::channel();
1375
1376 let task = async move {
1377 let (done_tx, mut done_rx) = tokio::sync::mpsc::unbounded_channel();
1378 let mut conns = vec![];
1379 while let Some(conn) = conn_rx.recv().await {
1380 conns.push(conn);
1381 if conns.len() == peer_count - 1 {
1382 break;
1383 }
1384 }
1385
1386 log::info!(target: "citadel", "~~~*** Peer {session_cid} has {} connections to other peers ***~~~", conns.len());
1387
1388 for conn in conns {
1389 let conn = conn.expect("Error receiving peer connection");
1390 handle_peer_connect_success(
1391 conn,
1392 done_tx.clone(),
1393 session_cid,
1394 udp_mode,
1395 checks.clone(),
1396 );
1397 }
1398
1399 let mut ret = vec![];
1401 while let Some(done) = done_rx.recv().await {
1402 ret.push(done);
1403 if ret.len() == peer_count - 1 {
1404 break;
1405 }
1406 }
1407
1408 finished_tx
1409 .send(ret)
1410 .expect("Error sending finished signal in handle_peer_connect_successes");
1411 };
1412
1413 drop(tokio::task::spawn(task));
1414 let ret = finished_rx
1415 .await
1416 .expect("Error receiving finished signal in handle_peer_connect_successes");
1417
1418 assert_eq!(ret.len(), peer_count - 1);
1419 ret
1420 }
1421
1422 fn handle_peer_connect_success<F, Fut, R: Ratchet>(
1423 mut conn: PeerConnectSuccess<R>,
1424 done_tx: UnboundedSender<PeerConnectSuccess<R>>,
1425 session_cid: u64,
1426 udp_mode: UdpMode,
1427 checks: F,
1428 ) where
1429 F: Fn(PeerConnectSuccess<R>) -> Fut + Send + Clone + 'static,
1430 Fut: Future<Output = PeerConnectSuccess<R>> + Send,
1431 {
1432 let task = async move {
1433 let chan = conn.udp_channel_rx.take();
1434 crate::test_common::p2p_assertions(session_cid, &conn).await;
1435 crate::test_common::udp_mode_assertions(udp_mode, chan).await;
1436 let conn = checks(conn).await;
1437 done_tx
1438 .send(conn)
1439 .expect("Error sending done signal in handle_peer_connect_success");
1440 };
1441
1442 drop(tokio::task::spawn(task));
1443 }
1444}