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