1use crate::prefabs::ClientServerRemote;
64use crate::prelude::results::{PeerConnectSuccess, PeerRegisterStatus};
65use crate::prelude::*;
66use crate::remote_ext::remote_specialization::PeerRemote;
67use crate::remote_ext::results::LocalGroupPeerFullInfo;
68use std::ops::{Deref, DerefMut};
69
70use futures::StreamExt;
71use std::path::PathBuf;
72use std::time::Duration;
73
74pub(crate) mod user_ids {
75 use crate::prelude::*;
76 use std::ops::Deref;
77
78 #[derive(Debug)]
79 pub struct SymmetricIdentifierHandleRef<'a, R: Ratchet> {
81 pub(crate) user: VirtualTargetType,
82 pub(crate) remote: &'a NodeRemote<R>,
83 pub(crate) target_username: Option<String>,
84 }
85
86 impl<R: Ratchet> SymmetricIdentifierHandleRef<'_, R> {
87 pub fn into_owned(self) -> SymmetricIdentifierHandle<R> {
88 SymmetricIdentifierHandle {
89 user: self.user,
90 remote: self.remote.clone(),
91 target_username: self.target_username,
92 }
93 }
94 }
95
96 #[derive(Clone, Debug)]
97 pub struct SymmetricIdentifierHandle<R: Ratchet> {
99 user: VirtualTargetType,
100 remote: NodeRemote<R>,
101 target_username: Option<String>,
102 }
103
104 pub trait TargetLockedRemote<R: Ratchet>: Send + Sync {
105 fn user(&self) -> &VirtualTargetType;
106 fn remote(&self) -> &NodeRemote<R>;
107 fn target_username(&self) -> Option<&str>;
108 fn user_mut(&mut self) -> &mut VirtualTargetType;
109 fn session_security_settings(&self) -> Option<&SessionSecuritySettings>;
110 }
111
112 impl<R: Ratchet> TargetLockedRemote<R> for SymmetricIdentifierHandleRef<'_, R> {
113 fn user(&self) -> &VirtualTargetType {
114 &self.user
115 }
116 fn remote(&self) -> &NodeRemote<R> {
117 self.remote
118 }
119 fn target_username(&self) -> Option<&str> {
120 self.target_username.as_deref()
121 }
122 fn user_mut(&mut self) -> &mut VirtualTargetType {
123 &mut self.user
124 }
125
126 fn session_security_settings(&self) -> Option<&SessionSecuritySettings> {
127 None
128 }
129 }
130
131 impl<R: Ratchet> TargetLockedRemote<R> for SymmetricIdentifierHandle<R> {
132 fn user(&self) -> &VirtualTargetType {
133 &self.user
134 }
135 fn remote(&self) -> &NodeRemote<R> {
136 &self.remote
137 }
138 fn target_username(&self) -> Option<&str> {
139 self.target_username.as_deref()
140 }
141 fn user_mut(&mut self) -> &mut VirtualTargetType {
142 &mut self.user
143 }
144
145 fn session_security_settings(&self) -> Option<&SessionSecuritySettings> {
146 None
147 }
148 }
149
150 impl<R: Ratchet> From<SymmetricIdentifierHandleRef<'_, R>> for SymmetricIdentifierHandle<R> {
151 fn from(this: SymmetricIdentifierHandleRef<'_, R>) -> Self {
152 this.into_owned()
153 }
154 }
155
156 impl<R: Ratchet> Deref for SymmetricIdentifierHandle<R> {
157 type Target = NodeRemote<R>;
158
159 fn deref(&self) -> &Self::Target {
160 &self.remote
161 }
162 }
163
164 impl<R: Ratchet> Deref for SymmetricIdentifierHandleRef<'_, R> {
165 type Target = NodeRemote<R>;
166
167 fn deref(&self) -> &Self::Target {
168 self.remote
169 }
170 }
171}
172
173pub struct CitadelClientServerConnection<R: Ratchet> {
175 pub(crate) channel: Option<PeerChannel<R>>,
177 pub remote: ClientServerRemote<R>,
178 pub udp_channel_rx: Option<citadel_io::tokio::sync::oneshot::Receiver<UdpChannel<R>>>,
180 pub services: ServicesObject,
182 pub cid: u64,
183 pub session_security_settings: SessionSecuritySettings,
184 pub path_policy: Option<PathPolicy>,
188}
189
190impl<R: Ratchet> CitadelClientServerConnection<R> {
191 pub fn split(self) -> (PeerChannelSendHalf<R>, PeerChannelRecvHalf<R>) {
197 self.channel.expect("Channel already taken").split()
198 }
199
200 pub fn take_channel(&mut self) -> Option<PeerChannel<R>> {
201 self.channel.take()
202 }
203}
204
205impl<R: Ratchet> Deref for CitadelClientServerConnection<R> {
206 type Target = ClientServerRemote<R>;
207
208 fn deref(&self) -> &Self::Target {
209 &self.remote
210 }
211}
212
213impl<R: Ratchet> DerefMut for CitadelClientServerConnection<R> {
214 fn deref_mut(&mut self) -> &mut Self::Target {
215 &mut self.remote
216 }
217}
218
219pub struct RegisterSuccess {
221 pub cid: u64,
222 pub recovery_codes: Vec<String>,
225}
226
227async fn await_registration<R: Ratchet, Rem: Remote<R>>(
229 remote: &Rem,
230 register_request: NodeRequest,
231) -> Result<RegisterSuccess, NetworkError> {
232 let mut subscription = remote.send_callback_subscription(register_request).await?;
233 while let Some(status) = subscription.next().await {
234 match status.into_result()? {
235 NodeResult::RegisterOkay(RegisterOkay {
236 cid,
237 recovery_codes,
238 ..
239 }) => {
240 return Ok(RegisterSuccess {
241 cid,
242 recovery_codes,
243 });
244 }
245 NodeResult::RegisterFailure(err) => {
246 return Err(citadel_io::error!(
247 citadel_io::ErrorCode::RemoteRegisterFailure,
248 err.error_message
249 ));
250 }
251 NodeResult::Disconnect(err) => {
252 return Err(citadel_io::error!(
253 citadel_io::ErrorCode::RemoteDisconnected,
254 err.message
255 ));
256 }
257 evt => {
258 log::warn!(target: "citadel", "Invalid NodeResult for Register request received: {evt:?}");
259 }
260 }
261 }
262
263 Err(citadel_io::error!(
264 citadel_io::ErrorCode::RemoteKernelStreamDied,
265 "register"
266 ))
267}
268
269async fn registration_credentials<
272 P: Into<String> + Send,
273 V: Into<String> + Send,
274 K: Into<SecBuffer>,
275>(
276 full_name: P,
277 username: V,
278 proposed_password: K,
279) -> Result<(ProposedCredentials, SecBuffer), NetworkError> {
280 let password: SecBuffer = proposed_password.into();
281 let trimmed = ProposedCredentials::registration_password(&password);
282 let creds = ProposedCredentials::new_register(full_name, username, password).await?;
283 Ok((creds, trimmed))
284}
285
286#[async_trait]
287pub trait ProtocolRemoteExt<R: Ratchet>: Remote<R> {
289 async fn register<
292 T: std::net::ToSocketAddrs + Send,
293 P: Into<String> + Send,
294 V: Into<String> + Send,
295 K: Into<SecBuffer> + Send,
296 >(
297 &self,
298 addr: T,
299 full_name: P,
300 username: V,
301 proposed_password: K,
302 default_security_settings: SessionSecuritySettings,
303 server_password: Option<PreSharedKey>,
304 ) -> Result<RegisterSuccess, NetworkError> {
305 self.register_admitted(
306 addr,
307 full_name,
308 username,
309 proposed_password,
310 default_security_settings,
311 server_password,
312 None,
313 )
314 .await
315 }
316
317 #[allow(clippy::too_many_arguments)]
321 async fn register_admitted<
322 T: std::net::ToSocketAddrs + Send,
323 P: Into<String> + Send,
324 V: Into<String> + Send,
325 K: Into<SecBuffer> + Send,
326 >(
327 &self,
328 addr: T,
329 full_name: P,
330 username: V,
331 proposed_password: K,
332 default_security_settings: SessionSecuritySettings,
333 server_password: Option<PreSharedKey>,
334 admission: Option<String>,
335 ) -> Result<RegisterSuccess, NetworkError> {
336 let (creds, password) =
337 registration_credentials(full_name, username, proposed_password).await?;
338 let register_request = NodeRequest::RegisterToHypernode(RegisterToHypernode {
339 remote_addr: addr.to_socket_addrs()?.next().ok_or(citadel_io::error!(
340 citadel_io::ErrorCode::RemoteInvalidSocketAddr
341 ))?,
342 proposed_credentials: creds,
343 static_security_settings: default_security_settings,
344 session_password: server_password.unwrap_or_default(),
345 endpoint: None,
346 password: Some(password),
347 admission,
348 });
349
350 await_registration(self, register_request).await
351 }
352
353 #[cfg(not(target_family = "wasm"))]
358 async fn register_to_endpoint<
359 P: Into<String> + Send,
360 V: Into<String> + Send,
361 K: Into<SecBuffer> + Send,
362 >(
363 &self,
364 endpoint: citadel_io::WebSocketEndpoint,
365 full_name: P,
366 username: V,
367 proposed_password: K,
368 default_security_settings: SessionSecuritySettings,
369 server_password: Option<PreSharedKey>,
370 ) -> Result<RegisterSuccess, NetworkError> {
371 self.register_to_endpoint_admitted(
372 endpoint,
373 full_name,
374 username,
375 proposed_password,
376 default_security_settings,
377 server_password,
378 None,
379 )
380 .await
381 }
382
383 #[cfg(not(target_family = "wasm"))]
385 #[allow(clippy::too_many_arguments)]
386 async fn register_to_endpoint_admitted<
387 P: Into<String> + Send,
388 V: Into<String> + Send,
389 K: Into<SecBuffer> + Send,
390 >(
391 &self,
392 endpoint: citadel_io::WebSocketEndpoint,
393 full_name: P,
394 username: V,
395 proposed_password: K,
396 default_security_settings: SessionSecuritySettings,
397 server_password: Option<PreSharedKey>,
398 admission: Option<String>,
399 ) -> Result<RegisterSuccess, NetworkError> {
400 let remote_addr = endpoint.resolve().await?;
401 let (creds, password) =
402 registration_credentials(full_name, username, proposed_password).await?;
403 let register_request = NodeRequest::RegisterToHypernode(RegisterToHypernode {
404 remote_addr,
405 proposed_credentials: creds,
406 static_security_settings: default_security_settings,
407 session_password: server_password.unwrap_or_default(),
408 endpoint: Some(endpoint),
409 password: Some(password),
410 admission,
411 });
412
413 await_registration(self, register_request).await
414 }
415
416 async fn server_endpoint(
421 &self,
422 cid: u64,
423 ) -> Result<Option<citadel_io::WebSocketEndpoint>, NetworkError> {
424 citadel_proto::prelude::load_server_endpoint(self.account_manager(), cid).await
425 }
426
427 async fn register_with_defaults<
430 T: std::net::ToSocketAddrs + Send,
431 P: Into<String> + Send,
432 V: Into<String> + Send,
433 K: Into<SecBuffer> + Send,
434 >(
435 &self,
436 addr: T,
437 full_name: P,
438 username: V,
439 proposed_password: K,
440 ) -> Result<RegisterSuccess, NetworkError> {
441 self.register(
442 addr,
443 full_name,
444 username,
445 proposed_password,
446 Default::default(),
447 Default::default(),
448 )
449 .await
450 }
451
452 async fn connect(
455 &self,
456 auth: AuthenticationRequest,
457 connect_mode: ConnectMode,
458 udp_mode: UdpMode,
459 keep_alive_timeout: Option<Duration>,
460 session_security_settings: SessionSecuritySettings,
461 server_password: Option<PreSharedKey>,
462 ) -> Result<CitadelClientServerConnection<R>, NetworkError> {
463 let connect_request = NodeRequest::ConnectToHypernode(ConnectToHypernode {
464 auth_request: auth,
465 connect_mode,
466 udp_mode,
467 keep_alive_timeout: keep_alive_timeout.map(|r| r.as_secs()),
468 session_security_settings,
469 session_password: server_password.unwrap_or_default(),
470 });
471
472 let mut subscription = self.send_callback_subscription(connect_request).await?;
473 let status = subscription.next().await.ok_or(citadel_io::error!(
474 citadel_io::ErrorCode::RemoteKernelStreamDied,
475 "connect"
476 ))?;
477
478 return match status.into_result()? {
479 NodeResult::ConnectSuccess(ConnectSuccess {
480 ticket: _,
481 session_cid: cid,
482 remote_addr: _,
483 is_personal: _,
484 v_conn_type,
485 services,
486 welcome_message: _,
487 channel,
488 udp_rx_opt: udp_channel_rx,
489 session_security_settings,
490 path_policy,
491 }) => Ok(CitadelClientServerConnection {
492 remote: ClientServerRemote::new(
493 v_conn_type,
494 self.remote_ref().clone(),
495 session_security_settings,
496 None,
497 None,
498 ),
499 channel: Some(*channel),
500 udp_channel_rx,
501 services,
502 cid,
503 session_security_settings,
504 path_policy,
505 }),
506 NodeResult::ConnectFail(ConnectFail {
507 error_message: err, ..
508 }) => Err(citadel_io::error!(
509 citadel_io::ErrorCode::RemoteConnectFailed,
510 err
511 )),
512 NodeResult::Disconnect(err) => {
513 return Err(citadel_io::error!(
514 citadel_io::ErrorCode::RemoteDisconnected,
515 err.message
516 ));
517 }
518 res => Err(citadel_io::error!(
519 citadel_io::ErrorCode::RemoteConnectUnexpectedResponse,
520 citadel_io::Dbg(res)
521 )),
522 };
523 }
524
525 async fn connect_with_defaults(
532 &self,
533 auth: AuthenticationRequest,
534 ) -> Result<CitadelClientServerConnection<R>, NetworkError> {
535 self.connect(
536 auth,
537 ConnectMode::Standard { force_login: false },
538 Default::default(),
539 None,
540 Default::default(),
541 Default::default(),
542 )
543 .await
544 }
545
546 async fn find_target<T: Into<UserIdentifier> + Send, P: Into<UserIdentifier> + Send>(
559 &self,
560 local_user: T,
561 peer: P,
562 ) -> Result<SymmetricIdentifierHandleRef<'_, R>, NetworkError> {
563 let account_manager = self.account_manager();
564 account_manager
565 .find_target_information(local_user, peer)
566 .await?
567 .map(move |(cid, peer)| {
568 if peer.parent_icid != 0 {
569 SymmetricIdentifierHandleRef {
570 user: VirtualTargetType::ExternalGroupPeer {
571 session_cid: cid,
572 interserver_cid: peer.parent_icid,
573 peer_cid: peer.cid,
574 },
575 remote: self.remote_ref(),
576 target_username: None,
577 }
578 } else {
579 SymmetricIdentifierHandleRef {
580 user: VirtualTargetType::LocalGroupPeer {
581 session_cid: cid,
582 peer_cid: peer.cid,
583 },
584 remote: self.remote_ref(),
585 target_username: None,
586 }
587 }
588 })
589 .ok_or_else(|| citadel_io::error!(citadel_io::ErrorCode::RemoteTargetPairNotFound))
590 }
591
592 async fn propose_target<T: Into<UserIdentifier> + Send, P: Into<UserIdentifier> + Send>(
595 &self,
596 local_user: T,
597 peer: P,
598 ) -> Result<SymmetricIdentifierHandleRef<'_, R>, NetworkError> {
599 let local_cid = self.get_session_cid(local_user).await?;
600 match peer.into() {
601 UserIdentifier::ID(peer_cid) => Ok(SymmetricIdentifierHandleRef {
602 user: VirtualTargetType::LocalGroupPeer {
603 session_cid: local_cid,
604 peer_cid,
605 },
606 remote: self.remote_ref(),
607 target_username: None,
608 }),
609 UserIdentifier::Username(uname) => {
610 let peer_cid = self
611 .remote_ref()
612 .account_manager()
613 .find_target_information(local_cid, uname.clone())
614 .await?
615 .map(|r| r.1.cid)
616 .unwrap_or(0);
617 Ok(SymmetricIdentifierHandleRef {
618 user: VirtualTargetType::LocalGroupPeer {
619 session_cid: local_cid,
620 peer_cid,
621 },
622 remote: self.remote_ref(),
623 target_username: Some(uname),
624 })
625 }
626 }
627 }
628
629 async fn get_local_group_peers<T: Into<UserIdentifier> + Send>(
632 &self,
633 local_user: T,
634 limit: Option<usize>,
635 ) -> Result<Vec<LocalGroupPeerFullInfo>, NetworkError> {
636 let local_cid = self.get_session_cid(local_user).await?;
637 let command = NodeRequest::PeerCommand(PeerCommand {
638 session_cid: local_cid,
639 command: PeerSignal::GetRegisteredPeers {
640 peer_conn_type: ClientConnectionType::Server {
641 session_cid: local_cid,
642 },
643 response: None,
644 limit: limit.map(|r| r as i32),
645 },
646 });
647
648 let mut stream = self.send_callback_subscription(command).await?;
649
650 while let Some(status) = stream.next().await {
651 if let NodeResult::PeerEvent(PeerEvent {
652 event:
653 PeerSignal::GetRegisteredPeers {
654 peer_conn_type: _,
655 response: Some(PeerResponse::RegisteredCids(peer_info, is_onlines)),
656 limit: _,
657 },
658 ..
659 }) = status.into_result()?
660 {
661 return Ok(peer_info
662 .into_iter()
663 .zip(is_onlines)
664 .filter_map(|(peer_info, is_online)| {
665 peer_info.map(|info| LocalGroupPeerFullInfo {
666 cid: info.cid,
667 username: Some(info.username),
668 full_name: Some(info.full_name),
669 is_online,
670 })
671 })
672 .collect());
673 }
674 }
675
676 Err(citadel_io::error!(
677 citadel_io::ErrorCode::RemoteKernelStreamDied,
678 "get_local_group_peers"
679 ))
680 }
681
682 async fn get_local_group_mutual_peers<T: Into<UserIdentifier> + Send>(
684 &self,
685 local_user: T,
686 ) -> Result<Vec<LocalGroupPeerFullInfo>, NetworkError> {
687 let local_cid = self.get_session_cid(local_user).await?;
688 let command = NodeRequest::PeerCommand(PeerCommand {
689 session_cid: local_cid,
690 command: PeerSignal::GetMutuals {
691 v_conn_type: ClientConnectionType::Server {
692 session_cid: local_cid,
693 },
694 response: None,
695 },
696 });
697
698 let mut stream = self.send_callback_subscription(command).await?;
699
700 while let Some(status) = stream.next().await {
701 if let NodeResult::PeerEvent(PeerEvent {
702 event:
703 PeerSignal::GetMutuals {
704 v_conn_type: _,
705 response: Some(PeerResponse::RegisteredCids(peer_info, is_onlines)),
706 },
707 ..
708 }) = status.into_result()?
709 {
710 return Ok(peer_info
711 .into_iter()
712 .zip(is_onlines)
713 .filter_map(|(peer_info, is_online)| {
714 peer_info.map(|info| LocalGroupPeerFullInfo {
715 cid: info.cid,
716 username: Some(info.username),
717 full_name: Some(info.full_name),
718 is_online,
719 })
720 })
721 .collect());
722 }
723 }
724
725 Err(citadel_io::error!(
726 citadel_io::ErrorCode::RemoteSessionStreamDied
727 ))
728 }
729
730 async fn probe_server(&self, cid: u64, timeout: Duration) -> ServerProbeOutcome {
736 crate::server_probe::probe_server(self, cid, timeout).await
737 }
738
739 async fn abandon_session(&self, cid: u64) -> Result<(), NetworkError> {
745 crate::server_probe::abandon_session(self, cid).await
746 }
747
748 async fn sessions(&self) -> Result<ActiveSessions, NetworkError> {
751 match self
752 .send_callback_subscription(NodeRequest::GetActiveSessions)
753 .await
754 {
755 Ok(mut stream) => {
756 while let Some(result) = stream.next().await {
757 match result.into_result()? {
758 NodeResult::SessionList(res) => return Ok(res.sessions),
759 res => {
760 citadel_logging::warn!("Received unexpected result: {res:?}");
761 }
762 }
763 }
764
765 citadel_logging::warn!("Failed to receive response from SDK (stream died)");
766 return Err(citadel_io::error!(
767 citadel_io::ErrorCode::RemoteKernelStreamDied,
768 "get_active_sessions"
769 ));
770 }
771 Err(e) => {
772 citadel_logging::warn!(target: "citadel", "Failed to query SDK sessions: {e}");
773 }
774 }
775
776 Err(citadel_io::error!(
777 citadel_io::ErrorCode::RemoteQuerySessionsFailed
778 ))
779 }
780
781 fn rebind_local(&self) -> Result<RebindReport, NetworkError> {
786 self.remote_ref().rebind_local()
787 }
788
789 #[doc(hidden)]
790 fn remote_ref(&self) -> &NodeRemote<R>;
791
792 #[doc(hidden)]
793 async fn get_session_cid<T: Into<UserIdentifier> + Send>(
794 &self,
795 local_user: T,
796 ) -> Result<u64, NetworkError> {
797 let account_manager = self.account_manager();
798 Ok(account_manager
799 .find_local_user_information(local_user)
800 .await?
801 .ok_or(citadel_io::error!(
802 citadel_io::ErrorCode::RemoteUserDoesNotExist
803 ))?)
804 }
805}
806
807impl<R: Ratchet> ProtocolRemoteExt<R> for NodeRemote<R> {
808 fn remote_ref(&self) -> &NodeRemote<R> {
809 self
810 }
811}
812
813impl<R: Ratchet> ProtocolRemoteExt<R> for ClientServerRemote<R> {
814 fn remote_ref(&self) -> &NodeRemote<R> {
815 &self.inner
816 }
817}
818
819#[async_trait]
820pub trait ProtocolRemoteTargetExt<R: Ratchet>: TargetLockedRemote<R> {
822 async fn send_file_with_custom_opts<T: ObjectSource>(
824 &self,
825 source: T,
826 chunk_size: usize,
827 transfer_type: TransferType,
828 ) -> Result<(), NetworkError> {
829 let chunk_size = if chunk_size == 0 {
830 None
831 } else {
832 Some(chunk_size)
833 };
834 let session_cid = self.user().get_session_cid();
835 let user = *self.user();
836 let remote = self.remote();
837
838 let mut stream = remote
839 .send_callback_subscription(NodeRequest::SendObject(SendObject {
840 source: Box::new(source),
841 chunk_size,
842 session_cid,
843 v_conn_type: user,
844 transfer_type,
845 }))
846 .await?;
847
848 while let Some(event) = stream.next().await {
849 match event.into_result()? {
850 NodeResult::ObjectTransferHandle(ObjectTransferHandle { mut handle, .. }) => {
851 return handle.transfer_file().await.map_err(|err| {
852 citadel_io::error!(
853 citadel_io::ErrorCode::RemoteFileTransferFailed,
854 err.into_string()
855 )
856 });
857 }
858
859 NodeResult::PeerEvent(PeerEvent {
860 event: PeerSignal::SignalReceived { .. },
861 ..
862 }) => {}
863
864 res => {
865 log::warn!(target: "citadel", "Invalid NodeResult for FileTransfer request received: {res:?}")
866 }
867 }
868 }
869
870 Err(citadel_io::error!(
871 citadel_io::ErrorCode::RemoteFileTransferStreamDied
872 ))
873 }
874
875 async fn send_file<T: ObjectSource>(&self, source: T) -> Result<(), NetworkError> {
877 self.send_file_with_custom_opts(source, 0, TransferType::FileTransfer)
878 .await
879 }
880
881 async fn remote_encrypted_virtual_filesystem_push_custom_chunking<
884 T: ObjectSource,
885 P: Into<PathBuf> + Send,
886 >(
887 &self,
888 source: T,
889 virtual_directory: P,
890 chunk_size: usize,
891 security_level: SecurityLevel,
892 ) -> Result<(), NetworkError> {
893 self.can_use_revfs()?;
894 let mut virtual_path = virtual_directory.into();
895 virtual_path = prepare_virtual_path(virtual_path);
896 validate_virtual_path(&virtual_path).map_err(|err| {
897 citadel_io::error!(
898 citadel_io::ErrorCode::RemoteRevfsInvalidVirtualPath,
899 err.into_string()
900 )
901 })?;
902 let tx_type = TransferType::RemoteEncryptedVirtualFilesystem {
903 virtual_path,
904 security_level,
905 };
906 self.send_file_with_custom_opts(source, chunk_size, tx_type)
907 .await
908 }
909
910 async fn remote_encrypted_virtual_filesystem_push<T: ObjectSource, P: Into<PathBuf> + Send>(
913 &self,
914 source: T,
915 virtual_directory: P,
916 security_level: SecurityLevel,
917 ) -> Result<(), NetworkError> {
918 self.remote_encrypted_virtual_filesystem_push_custom_chunking(
919 source,
920 virtual_directory,
921 0,
922 security_level,
923 )
924 .await
925 }
926
927 async fn remote_encrypted_virtual_filesystem_pull<P: Into<PathBuf> + Send>(
930 &self,
931 virtual_directory: P,
932 transfer_security_level: SecurityLevel,
933 delete_on_pull: bool,
934 ) -> Result<PathBuf, NetworkError> {
935 self.can_use_revfs()?;
936 let request = NodeRequest::PullObject(PullObject {
937 v_conn: *self.user(),
938 virtual_dir: virtual_directory.into(),
939 delete_on_pull,
940 transfer_security_level,
941 });
942
943 let mut stream = self.remote().send_callback_subscription(request).await?;
944
945 while let Some(event) = stream.next().await {
946 match event.into_result()? {
947 NodeResult::ObjectTransferHandle(ObjectTransferHandle { mut handle, .. }) => {
948 return handle.receive_file().await.map_err(|err| {
949 citadel_io::error!(
950 citadel_io::ErrorCode::RemoteFileTransferFailed,
951 err.into_string()
952 )
953 });
954 }
955
956 NodeResult::PeerEvent(PeerEvent {
957 event: PeerSignal::SignalReceived { .. },
958 ..
959 }) => {}
960
961 NodeResult::ReVFS(ReVFSResult {
963 error_message: Some(error),
964 ..
965 }) => {
966 return Err(citadel_io::error!(
967 citadel_io::ErrorCode::RemoteFileTransferFailed,
968 error
969 ));
970 }
971
972 NodeResult::ReVFS(ReVFSResult {
973 error_message: None,
974 ..
975 }) => {}
976
977 res => {
978 log::error!(target: "citadel", "Invalid NodeResult for REVFS FileTransfer request received: {res:?}");
979 return Err(citadel_io::error!(
980 citadel_io::ErrorCode::RemoteRevfsInvalidResponse
981 ));
982 }
983 }
984 }
985
986 Err(citadel_io::error!(
987 citadel_io::ErrorCode::RemoteRevfsFileTransferStreamDied
988 ))
989 }
990
991 async fn remote_encrypted_virtual_filesystem_delete<P: Into<PathBuf> + Send>(
995 &self,
996 virtual_directory: P,
997 ) -> Result<(), NetworkError> {
998 self.can_use_revfs()?;
999 let request = NodeRequest::DeleteObject(DeleteObject {
1000 v_conn: *self.user(),
1001 virtual_dir: virtual_directory.into(),
1002 security_level: Default::default(),
1003 });
1004
1005 let mut stream = self.remote().send_callback_subscription(request).await?;
1006 while let Some(event) = stream.next().await {
1007 match event.into_result()? {
1008 NodeResult::ReVFS(result) => {
1009 return if let Some(error) = result.error_message {
1010 Err(citadel_io::error!(
1011 citadel_io::ErrorCode::RemoteFileTransferFailed,
1012 error
1013 ))
1014 } else {
1015 Ok(())
1016 }
1017 }
1018
1019 evt => {
1020 log::error!(target: "citadel", "Invalid NodeResult for REVFS Delete request received: {evt:?}");
1021 }
1022 }
1023 }
1024
1025 Err(citadel_io::error!(
1026 citadel_io::ErrorCode::RemoteRevfsDeleteStreamDied
1027 ))
1028 }
1029
1030 async fn connect_to_peer_custom(
1032 &self,
1033 session_security_settings: SessionSecuritySettings,
1034 udp_mode: UdpMode,
1035 peer_session_password: Option<PreSharedKey>,
1036 ) -> Result<PeerConnectSuccess<R>, NetworkError> {
1037 use std::time::Duration;
1038
1039 const P2P_CONNECT_TIMEOUT: Duration = Duration::from_secs(60);
1045
1046 let session_cid = self.user().get_session_cid();
1047 let peer_target = self.try_as_peer_connection().await?;
1048
1049 let mut stream = self
1050 .remote()
1051 .send_callback_subscription(NodeRequest::PeerCommand(PeerCommand {
1052 session_cid,
1053 command: PeerSignal::PostConnect {
1054 peer_conn_type: peer_target,
1055 ticket_opt: None,
1056 invitee_response: None,
1057 session_security_settings,
1058 udp_mode,
1059 session_password: peer_session_password,
1060 },
1061 }))
1062 .await?;
1063
1064 let connect_task = async {
1065 while let Some(status) = stream.next().await {
1066 match status.into_result()? {
1067 NodeResult::PeerChannelCreated(PeerChannelCreated {
1068 ticket: _,
1069 channel,
1070 udp_rx_opt,
1071 }) => {
1072 let username = self.target_username().map(ToString::to_string);
1073 let remote = PeerRemote {
1074 inner: self.remote().clone(),
1075 peer: peer_target.as_virtual_connection(),
1076 username,
1077 session_security_settings,
1078 };
1079
1080 return Ok(PeerConnectSuccess {
1081 remote,
1082 channel: *channel,
1083 udp_channel_rx: udp_rx_opt,
1084 incoming_object_transfer_handles: None,
1085 });
1086 }
1087
1088 NodeResult::PeerEvent(PeerEvent {
1089 event:
1090 PeerSignal::PostConnect {
1091 invitee_response, ..
1092 },
1093 ..
1094 }) => match invitee_response {
1095 Some(PeerResponse::Timeout) => {
1096 return Err(citadel_io::error!(
1097 citadel_io::ErrorCode::RemotePeerNoResponse
1098 ))
1099 }
1100 Some(PeerResponse::Decline) => {
1101 return Err(citadel_io::error!(
1102 citadel_io::ErrorCode::RemotePeerDeclined
1103 ))
1104 }
1105 _ => {}
1106 },
1107
1108 NodeResult::PeerEvent(PeerEvent {
1112 event: PeerSignal::SignalError { error, .. },
1113 ..
1114 }) => {
1115 return Err(citadel_io::error!(
1116 citadel_io::ErrorCode::RemoteP2pSignalRoutingFailed,
1117 error
1118 ))
1119 }
1120
1121 _ => {}
1122 }
1123 }
1124
1125 Err(citadel_io::error!(
1126 citadel_io::ErrorCode::RemoteKernelStreamDied,
1127 "connect_to_peer_custom"
1128 ))
1129 };
1130
1131 match citadel_io::time::timeout(P2P_CONNECT_TIMEOUT, connect_task).await {
1132 Ok(result) => result,
1133 Err(_elapsed) => Err(citadel_io::error!(
1134 citadel_io::ErrorCode::RemoteP2pConnectTimeout,
1135 P2P_CONNECT_TIMEOUT.as_secs()
1136 )),
1137 }
1138 }
1139
1140 async fn set_turn_config(
1147 &self,
1148 config: Option<citadel_proto::prelude::TurnRelayConfig>,
1149 ) -> Result<(), NetworkError> {
1150 let session_cid = self.user().get_session_cid();
1151 let peer_cid = self
1152 .try_as_peer_connection()
1153 .await?
1154 .get_original_target_cid();
1155 self.remote()
1156 .send(NodeRequest::SetPeerTurnConfig(SetPeerTurnConfig {
1157 session_cid,
1158 peer_cid,
1159 config,
1160 }))
1161 .await
1162 .map(|_| ())
1163 }
1164
1165 async fn connect_to_peer_with_turn(
1167 &self,
1168 session_security_settings: SessionSecuritySettings,
1169 udp_mode: UdpMode,
1170 peer_session_password: Option<PreSharedKey>,
1171 turn: citadel_proto::prelude::TurnRelayConfig,
1172 ) -> Result<PeerConnectSuccess<R>, NetworkError> {
1173 self.set_turn_config(Some(turn)).await?;
1174 self.connect_to_peer_custom(session_security_settings, udp_mode, peer_session_password)
1175 .await
1176 }
1177
1178 async fn connect_to_peer(&self) -> Result<PeerConnectSuccess<R>, NetworkError> {
1180 self.connect_to_peer_custom(Default::default(), Default::default(), Default::default())
1181 .await
1182 }
1183
1184 async fn register_to_peer(&self) -> Result<PeerRegisterStatus, NetworkError> {
1186 let session_cid = self.user().get_session_cid();
1187 let peer_target = self.try_as_peer_connection().await?;
1188 let local_username = self
1190 .remote()
1191 .account_manager()
1192 .get_username_by_cid(session_cid)
1193 .await?
1194 .ok_or_else(|| citadel_io::error!(citadel_io::ErrorCode::RemoteLocalUsernameMissing))?;
1195 let peer_username_opt = self.target_username().map(ToString::to_string);
1196
1197 let mut stream = self
1198 .remote()
1199 .send_callback_subscription(NodeRequest::PeerCommand(PeerCommand {
1200 session_cid,
1201 command: PeerSignal::PostRegister {
1202 peer_conn_type: peer_target,
1203 inviter_username: local_username,
1204 invitee_username: peer_username_opt,
1205 ticket_opt: None,
1206 invitee_response: None,
1207 },
1208 }))
1209 .await?;
1210
1211 while let Some(status) = stream.next().await {
1212 if let NodeResult::PeerEvent(PeerEvent {
1213 event:
1214 PeerSignal::PostRegister {
1215 peer_conn_type: _,
1216 inviter_username: _,
1217 invitee_username: _,
1218 ticket_opt: _,
1219 invitee_response: Some(resp),
1220 },
1221 ..
1222 }) = status.into_result()?
1223 {
1224 match resp {
1225 PeerResponse::Accept(..) => return Ok(PeerRegisterStatus::Accepted),
1226 PeerResponse::Decline => return Ok(PeerRegisterStatus::Declined),
1227 PeerResponse::Timeout => return Ok(PeerRegisterStatus::Failed { reason: Some("Timeout on register request. Peer did not accept in time. Try again later".to_string()) }),
1228 _ => {}
1229 }
1230 }
1231 }
1232
1233 Err(citadel_io::error!(
1234 citadel_io::ErrorCode::RemoteKernelStreamDied,
1235 format!("register_to_peer: {:?}", stream.callback_key())
1236 ))
1237 }
1238
1239 async fn deregister(&self) -> Result<(), NetworkError> {
1243 if let Ok(peer_conn) = self.try_as_peer_connection().await {
1244 let peer_request = PeerSignal::Deregister {
1245 peer_conn_type: peer_conn,
1246 };
1247 let session_cid = self.user().get_session_cid();
1248 let request = NodeRequest::PeerCommand(PeerCommand {
1249 session_cid,
1250 command: peer_request,
1251 });
1252
1253 let mut subscription = self.remote().send_callback_subscription(request).await?;
1254 while let Some(result) = subscription.next().await {
1255 if let NodeResult::PeerEvent(PeerEvent {
1256 event: PeerSignal::DeregistrationSuccess { .. },
1257 ..
1258 }) = result.into_result()?
1259 {
1260 return Ok(());
1261 }
1262 }
1263 } else {
1264 let cid = self.user().get_session_cid();
1266 let request = NodeRequest::DeregisterFromHypernode(DeregisterFromHypernode {
1267 session_cid: cid,
1268 v_conn_type: *self.user(),
1269 });
1270 let mut subscription = self.remote().send_callback_subscription(request).await?;
1271 while let Some(result) = subscription.next().await {
1272 match result.into_result()? {
1273 NodeResult::DeRegistration(DeRegistration {
1274 session_cid: _,
1275 ticket_opt: _,
1276 success: true,
1277 }) => return Ok(()),
1278 NodeResult::DeRegistration(DeRegistration {
1279 session_cid: _,
1280 ticket_opt: _,
1281 success: false,
1282 }) => {
1283 return Err(citadel_io::error!(
1284 citadel_io::ErrorCode::RemoteDeregisterFailed
1285 ))
1286 }
1287
1288 _ => {}
1289 }
1290 }
1291 }
1292
1293 Err(citadel_io::error!(
1294 citadel_io::ErrorCode::RemoteDeregisterEndedUnexpectedly
1295 ))
1296 }
1297
1298 async fn disconnect(&self) -> Result<(), NetworkError> {
1299 if let Ok(peer_conn) = self.try_as_peer_connection().await {
1300 if let PeerConnectionType::LocalGroupPeer {
1301 session_cid,
1302 peer_cid: _,
1303 } = peer_conn
1304 {
1305 let request = NodeRequest::PeerCommand(PeerCommand {
1306 session_cid,
1307 command: PeerSignal::Disconnect {
1308 peer_conn_type: peer_conn,
1309 disconnect_response: None,
1310 disconnect_token: None,
1311 },
1312 });
1313
1314 let mut subscription = self.remote().send_callback_subscription(request).await?;
1315
1316 while let Some(event) = subscription.next().await {
1317 if let NodeResult::PeerEvent(PeerEvent {
1318 event:
1319 PeerSignal::Disconnect {
1320 peer_conn_type: _,
1321 disconnect_response: Some(_),
1322 ..
1323 },
1324 ..
1325 }) = event.into_result()?
1326 {
1327 return Ok(());
1328 }
1329 }
1330
1331 Err(citadel_io::error!(
1332 citadel_io::ErrorCode::RemoteDisconnectEventMissing
1333 ))
1334 } else {
1335 Err(citadel_io::error!(
1336 citadel_io::ErrorCode::RemoteExternalGroupPeerUnsupported
1337 ))
1338 }
1339 } else {
1340 let cid = self.user().get_session_cid();
1342 let request =
1343 NodeRequest::DisconnectFromHypernode(DisconnectFromHypernode { session_cid: cid });
1344
1345 let mut subscription = self.remote().send_callback_subscription(request).await?;
1346 const DISCONNECT_CONFIRMATION_TIMEOUT: Duration = Duration::from_secs(30);
1361 let deadline = citadel_io::time::Instant::now() + DISCONNECT_CONFIRMATION_TIMEOUT;
1362 let mut carried = 0usize;
1379 loop {
1380 let remaining =
1381 deadline.saturating_duration_since(citadel_io::time::Instant::now());
1382 if remaining.is_zero() {
1383 log::warn!(target: "citadel", "[dc-wait] no Disconnect for cid {cid} within {DISCONNECT_CONFIRMATION_TIMEOUT:?}; the subscription carried {carried} other event(s)");
1384 return Err(citadel_io::error!(
1385 citadel_io::ErrorCode::RemoteDisconnectEventMissing
1386 ));
1387 }
1388 match citadel_io::time::timeout(remaining, subscription.next()).await {
1389 Ok(Some(event)) => {
1390 carried += 1;
1391 if let NodeResult::Disconnect(Disconnect {
1392 success, message, ..
1393 }) = event.into_result()?
1394 {
1395 return if success {
1396 Ok(())
1397 } else {
1398 Err(citadel_io::error!(
1399 citadel_io::ErrorCode::RemoteDisconnected,
1400 message
1401 ))
1402 };
1403 }
1404 }
1405 Ok(None) => {
1407 log::warn!(target: "citadel", "[dc-wait] the subscription for cid {cid} closed without a Disconnect; it carried {carried} other event(s)");
1408 break;
1409 }
1410 Err(_elapsed) => {
1411 log::warn!(target: "citadel", "[dc-wait] timed out waiting for a Disconnect for cid {cid}; the subscription carried {carried} other event(s)");
1412 return Err(citadel_io::error!(
1413 citadel_io::ErrorCode::RemoteDisconnectEventMissing
1414 ));
1415 }
1416 }
1417 }
1418
1419 Err(citadel_io::error!(
1420 citadel_io::ErrorCode::RemoteDisconnectEventMissing
1421 ))
1422 }
1423 }
1424
1425 async fn create_group(
1426 &self,
1427 initial_users_to_invite: Option<Vec<UserIdentifier>>,
1428 ) -> Result<GroupChannel, NetworkError> {
1429 self.create_group_with_options(initial_users_to_invite, MessageGroupOptions::default())
1430 .await
1431 }
1432
1433 async fn create_group_with_options(
1438 &self,
1439 initial_users_to_invite: Option<Vec<UserIdentifier>>,
1440 options: MessageGroupOptions,
1441 ) -> Result<GroupChannel, NetworkError> {
1442 let session_cid = self.user().get_session_cid();
1443
1444 let mut initial_users = vec![];
1445 if let Some(initial_users_to_invite) = initial_users_to_invite {
1448 for user in initial_users_to_invite {
1449 initial_users.push(
1450 self.remote()
1451 .account_manager()
1452 .find_target_information(session_cid, user.clone())
1453 .await?
1454 .ok_or_else(|| {
1455 citadel_io::error!(
1456 citadel_io::ErrorCode::RemoteGroupAccountNotFound,
1457 citadel_io::Dbg(user),
1458 citadel_io::Dbg(session_cid)
1459 )
1460 })
1461 .map(|r| r.1.cid)?,
1462 )
1463 }
1464 }
1465
1466 let group_request = GroupBroadcast::Create {
1467 initial_invitees: initial_users,
1468 options,
1469 };
1470 let request = NodeRequest::GroupBroadcastCommand(GroupBroadcastCommand {
1471 session_cid,
1472 command: group_request,
1473 });
1474 let mut subscription = self.remote().send_callback_subscription(request).await?;
1475
1476 match crate::group_create_wait::await_group_creation(&mut subscription).await? {
1489 crate::group_create_wait::GroupCreation::Created(channel) => Ok(channel),
1490 crate::group_create_wait::GroupCreation::Refused => Err(citadel_io::error!(
1491 citadel_io::ErrorCode::Generic,
1492 "The server refused to create the group"
1493 )),
1494 crate::group_create_wait::GroupCreation::Ended => Err(citadel_io::error!(
1495 citadel_io::ErrorCode::RemoteCreateGroupEndedUnexpectedly
1496 )),
1497 }
1498 }
1499
1500 async fn list_owned_groups(&self) -> Result<Vec<MessageGroupKey>, NetworkError> {
1502 let session_cid = self.user().get_session_cid();
1503 let cid_to_check_for = match self.try_as_peer_connection().await {
1504 Ok(res) => res.get_original_target_cid(),
1505 _ => session_cid,
1506 };
1507 let group_request = GroupBroadcast::ListGroupsFor {
1508 cid: cid_to_check_for,
1509 };
1510 let request = NodeRequest::GroupBroadcastCommand(GroupBroadcastCommand {
1511 session_cid,
1512 command: group_request,
1513 });
1514
1515 let mut subscription = self.remote().send_callback_subscription(request).await?;
1516
1517 while let Some(evt) = subscription.next().await {
1518 if let NodeResult::GroupEvent(GroupEvent {
1519 session_cid: _,
1520 ticket: _,
1521 event: GroupBroadcast::ListResponse { groups },
1522 }) = evt.into_result()?
1523 {
1524 return Ok(groups);
1525 }
1526 }
1527
1528 Err(citadel_io::error!(
1529 citadel_io::ErrorCode::RemoteListGroupsEndedUnexpectedly
1530 ))
1531 }
1532
1533 async fn list_sessions(&self) -> Result<ActiveSessions, NetworkError> {
1537 let request = NodeRequest::GetActiveSessions;
1538 let mut subscription = self.remote().send_callback_subscription(request).await?;
1539
1540 if let Some(NodeResult::SessionList(result)) = subscription.next().await {
1541 return Ok(result.sessions);
1542 }
1543
1544 Err(citadel_io::error!(
1545 citadel_io::ErrorCode::RemoteListSessionsEndedUnexpectedly
1546 ))
1547 }
1548
1549 async fn rekey(&self) -> Result<Option<u32>, NetworkError> {
1553 let request = NodeRequest::ReKey(ReKey {
1554 v_conn_type: *self.user(),
1555 });
1556 let mut subscription = self.remote().send_callback_subscription(request).await?;
1557
1558 while let Some(evt) = subscription.next().await {
1559 if let NodeResult::ReKeyResult(result) = evt {
1560 return match result.status {
1561 ReKeyReturnType::Success { version } => Ok(Some(version)),
1562 ReKeyReturnType::AlreadyInProgress => Ok(None),
1563 ReKeyReturnType::Failure { err } => Err(citadel_io::error!(
1564 citadel_io::ErrorCode::RemoteRekeyFailed,
1565 err
1566 )),
1567 };
1568 }
1569 }
1570
1571 Err(citadel_io::error!(
1572 citadel_io::ErrorCode::RemoteRekeyEndedUnexpectedly
1573 ))
1574 }
1575
1576 async fn is_peer_registered(&self) -> Result<bool, NetworkError> {
1578 let target = self.try_as_peer_connection().await?;
1579 if let PeerConnectionType::LocalGroupPeer {
1580 session_cid: local_cid,
1581 peer_cid,
1582 } = target
1583 {
1584 let peers = self.remote().get_local_group_peers(local_cid, None).await?;
1585 citadel_logging::info!(target: "citadel", "Checking to see if {target} is registered in {peers:?}");
1586 Ok(peers.iter().any(|p| p.cid == peer_cid))
1587 } else {
1588 Err(citadel_io::error!(
1589 citadel_io::ErrorCode::RemoteExternalGroupPeerUnsupportedYet
1590 ))
1591 }
1592 }
1593
1594 #[doc(hidden)]
1595 async fn try_as_peer_connection(&self) -> Result<PeerConnectionType, NetworkError> {
1596 let verified_return = |user: &VirtualTargetType| {
1597 user.try_as_peer_connection().ok_or(citadel_io::error!(
1598 citadel_io::ErrorCode::RemoteTargetNotPeer
1599 ))
1600 };
1601
1602 if self.user().get_target_cid() == 0 {
1603 let peer_username = self.target_username().ok_or_else(|| {
1606 citadel_io::error!(citadel_io::ErrorCode::RemoteTargetCidZeroNoUsername)
1607 })?;
1608 let session_cid = self.user().get_session_cid();
1609 let expected_peer_cid = self
1610 .remote()
1611 .account_manager()
1612 .get_persistence_handler()
1613 .get_cid_by_username(peer_username);
1614 let peer_cid = self
1617 .remote()
1618 .account_manager()
1619 .find_target_information(session_cid, peer_username)
1620 .await?
1621 .map(|r| r.1.cid)
1622 .unwrap_or(expected_peer_cid);
1623
1624 let mut user = *self.user();
1625 user.set_target_cid(peer_cid);
1626 verified_return(&user)
1627 } else {
1628 verified_return(self.user())
1629 }
1630 }
1631
1632 #[doc(hidden)]
1633 fn can_use_revfs(&self) -> Result<(), NetworkError> {
1634 if let Some(sess) = self.session_security_settings() {
1635 if sess.crypto_params.kem_algorithm == KemAlgorithm::MlKem {
1636 Ok(())
1637 } else {
1638 Err(citadel_io::error!(
1639 citadel_io::ErrorCode::RemoteRevfsRequiresKyber
1640 ))
1641 }
1642 } else {
1643 Err(citadel_io::error!(
1644 citadel_io::ErrorCode::RemoteRevfsUnsupportedRemote
1645 ))
1646 }
1647 }
1648}
1649
1650impl<T: TargetLockedRemote<R>, R: Ratchet> ProtocolRemoteTargetExt<R> for T {}
1651
1652pub mod results {
1653 use crate::prefabs::client::peer_connection::FileTransferHandleRx;
1654 use crate::prelude::{PeerChannel, UdpChannel};
1655 use crate::remote_ext::remote_specialization::PeerRemote;
1656 use citadel_io::tokio::sync::oneshot::Receiver;
1657 use citadel_proto::prelude::*;
1658 use std::fmt::Debug;
1659
1660 pub struct PeerConnectSuccess<R: Ratchet> {
1661 pub channel: PeerChannel<R>,
1662 pub udp_channel_rx: Option<Receiver<UdpChannel<R>>>,
1663 pub remote: PeerRemote<R>,
1664 pub(crate) incoming_object_transfer_handles: Option<FileTransferHandleRx>,
1667 }
1668
1669 impl<R: Ratchet> Debug for PeerConnectSuccess<R> {
1670 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1671 f.debug_struct("PeerConnectSuccess")
1672 .field("channel", &self.channel)
1673 .field("udp_channel_rx", &self.udp_channel_rx)
1674 .finish()
1675 }
1676 }
1677
1678 impl<R: Ratchet> PeerConnectSuccess<R> {
1679 pub fn get_incoming_file_transfer_handle(
1681 &mut self,
1682 ) -> Result<FileTransferHandleRx, NetworkError> {
1683 self.incoming_object_transfer_handles
1684 .take()
1685 .ok_or(citadel_io::error!(
1686 citadel_io::ErrorCode::RemoteFunctionAlreadyCalled
1687 ))
1688 }
1689 }
1690
1691 #[derive(Clone, Debug, PartialEq, Eq)]
1704 pub enum PeerRegisterStatus {
1705 Accepted,
1706 Declined,
1707 Failed { reason: Option<String> },
1708 }
1709
1710 impl PeerRegisterStatus {
1711 pub fn is_accepted(&self) -> bool {
1713 matches!(self, PeerRegisterStatus::Accepted)
1714 }
1715
1716 pub fn refusal_reason(&self) -> Option<String> {
1718 match self {
1719 PeerRegisterStatus::Accepted => None,
1720 PeerRegisterStatus::Declined => Some("The peer declined the request".to_string()),
1721 PeerRegisterStatus::Failed { reason } => Some(
1722 reason
1723 .clone()
1724 .unwrap_or_else(|| "The registration request failed".to_string()),
1725 ),
1726 }
1727 }
1728 }
1729
1730 #[derive(Clone, Debug)]
1731 pub struct LocalGroupPeer {
1732 pub cid: u64,
1733 pub is_online: bool,
1734 }
1735
1736 #[derive(Clone, Debug)]
1737 pub struct LocalGroupPeerFullInfo {
1738 pub cid: u64,
1739 pub username: Option<String>,
1740 pub full_name: Option<String>,
1741 pub is_online: bool,
1742 }
1743}
1744
1745pub mod remote_specialization {
1746 use crate::prelude::*;
1747 use std::ops::{Deref, DerefMut};
1748
1749 #[derive(Debug, Clone)]
1750 pub struct PeerRemote<R: Ratchet> {
1751 pub(crate) inner: NodeRemote<R>,
1752 pub(crate) peer: VirtualTargetType,
1753 pub(crate) username: Option<String>,
1754 pub(crate) session_security_settings: SessionSecuritySettings,
1755 }
1756
1757 impl<R: Ratchet> Deref for PeerRemote<R> {
1758 type Target = NodeRemote<R>;
1759 fn deref(&self) -> &Self::Target {
1760 &self.inner
1761 }
1762 }
1763
1764 impl<R: Ratchet> DerefMut for PeerRemote<R> {
1765 fn deref_mut(&mut self) -> &mut Self::Target {
1766 &mut self.inner
1767 }
1768 }
1769
1770 impl<R: Ratchet> TargetLockedRemote<R> for PeerRemote<R> {
1771 fn user(&self) -> &VirtualTargetType {
1772 &self.peer
1773 }
1774 fn remote(&self) -> &NodeRemote<R> {
1775 &self.inner
1776 }
1777 fn target_username(&self) -> Option<&str> {
1778 self.username.as_deref()
1779 }
1780 fn user_mut(&mut self) -> &mut VirtualTargetType {
1781 &mut self.peer
1782 }
1783
1784 fn session_security_settings(&self) -> Option<&SessionSecuritySettings> {
1785 Some(&self.session_security_settings)
1786 }
1787 }
1788}
1789
1790#[cfg(all(test, not(target_family = "wasm")))]
1791mod tests {
1792 use crate::prefabs::client::single_connection::SingleClientServerConnectionKernel;
1793 use crate::prefabs::client::DefaultServerConnectionSettingsBuilder;
1794 use crate::prelude::*;
1795 use citadel_io::tokio;
1796 use rstest::rstest;
1797 use std::net::SocketAddr;
1798 use std::sync::atomic::{AtomicBool, Ordering};
1799 use std::sync::Arc;
1800 use uuid::Uuid;
1801
1802 pub struct ReceiverFileTransferKernel<R: Ratchet>(
1803 pub Option<NodeRemote<R>>,
1804 pub Arc<AtomicBool>,
1805 );
1806
1807 #[async_trait]
1808 impl<R: Ratchet> NetKernel<R> for ReceiverFileTransferKernel<R> {
1809 fn load_remote(&mut self, node_remote: NodeRemote<R>) -> Result<(), NetworkError> {
1810 self.0 = Some(node_remote);
1811 Ok(())
1812 }
1813
1814 async fn on_start(&self) -> Result<(), NetworkError> {
1815 Ok(())
1816 }
1817
1818 async fn on_node_event_received(&self, message: NodeResult<R>) -> Result<(), NetworkError> {
1819 log::trace!(target: "citadel", "SERVER received {:?}", message);
1820 if let NodeResult::ObjectTransferHandle(ObjectTransferHandle { mut handle, .. }) =
1821 message.into_result()?
1822 {
1823 let mut path = None;
1824 handle
1826 .accept()
1827 .map_err(|err| NetworkError::msg(err.into_string()))?;
1828
1829 use citadel_types::proto::ObjectTransferStatus;
1830 use futures::StreamExt;
1831 while let Some(status) = handle.next().await {
1832 match status {
1833 ObjectTransferStatus::ReceptionComplete => {
1834 log::trace!(target: "citadel", "Server has finished receiving the file!");
1835 let cmp = include_bytes!("../../resources/TheBridge.pdf");
1836 let streamed_data = citadel_io::tokio::fs::read(path.clone().unwrap())
1837 .await
1838 .unwrap();
1839 assert_eq!(
1840 cmp,
1841 streamed_data.as_slice(),
1842 "Original data and streamed data does not match"
1843 );
1844
1845 self.1.store(true, Ordering::Relaxed);
1846 self.0.clone().unwrap().shutdown().await?;
1847 }
1848
1849 ObjectTransferStatus::ReceptionBeginning(file_path, vfm) => {
1850 path = Some(file_path);
1851 assert_eq!(vfm.name, "TheBridge.pdf")
1852 }
1853
1854 _ => {}
1855 }
1856 }
1857 }
1858
1859 Ok(())
1860 }
1861
1862 async fn on_stop(&mut self) -> Result<(), NetworkError> {
1863 Ok(())
1864 }
1865 }
1866
1867 pub fn server_info<'a, R: Ratchet>(
1868 switch: Arc<AtomicBool>,
1869 ) -> (NodeFuture<'a, ReceiverFileTransferKernel<R>>, SocketAddr) {
1870 crate::test_common::server_test_node(ReceiverFileTransferKernel(None, switch), |_| {})
1871 }
1872
1873 #[rstest]
1874 #[case(
1875 EncryptionAlgorithm::AES_GCM_256,
1876 KemAlgorithm::MlKem,
1877 SigAlgorithm::None
1878 )]
1879 #[case(
1880 EncryptionAlgorithm::MlKemHybrid,
1881 KemAlgorithm::MlKem,
1882 SigAlgorithm::MlDsa65
1883 )]
1884 #[timeout(std::time::Duration::from_secs(90))]
1885 #[tokio::test]
1886 async fn test_c2s_file_transfer(
1887 #[case] enx: EncryptionAlgorithm,
1888 #[case] kem: KemAlgorithm,
1889 #[case] sig: SigAlgorithm,
1890 ) {
1891 citadel_logging::setup_log();
1892 let client_success = &AtomicBool::new(false);
1893 let server_success = &Arc::new(AtomicBool::new(false));
1894 let (server, server_addr) = server_info::<StackedRatchet>(server_success.clone());
1895 let uuid = Uuid::new_v4();
1896
1897 let session_security_settings = SessionSecuritySettingsBuilder::default()
1898 .with_crypto_params(enx + kem + sig)
1899 .build()
1900 .unwrap();
1901
1902 let server_connection_settings =
1903 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
1904 .with_session_security_settings(session_security_settings)
1905 .disable_udp()
1906 .build()
1907 .unwrap();
1908
1909 let client_kernel = SingleClientServerConnectionKernel::new(
1910 server_connection_settings,
1911 |connection| async move {
1912 log::trace!(target: "citadel", "***CLIENT LOGIN SUCCESS :: File transfer next ***");
1913 connection
1914 .send_file_with_custom_opts(
1915 "../resources/TheBridge.pdf",
1916 32 * 1024,
1917 TransferType::FileTransfer,
1918 )
1919 .await
1920 .unwrap();
1921 log::trace!(target: "citadel", "***CLIENT FILE TRANSFER SUCCESS***");
1922 client_success.store(true, Ordering::Relaxed);
1923 connection.shutdown_kernel().await
1924 },
1925 );
1926
1927 let client = DefaultNodeBuilder::default()
1928 .with_stun_servers(crate::test_common::LOCALHOST_STUN_SERVERS)
1929 .build(client_kernel)
1930 .unwrap();
1931
1932 let joined = futures::future::try_join(server, client);
1933
1934 let _ = joined.await.unwrap();
1935
1936 assert!(client_success.load(Ordering::Relaxed));
1937 assert!(server_success.load(Ordering::Relaxed));
1938 }
1939}