1use crate::prelude::{ObjectSource, ProtocolRemoteTargetExt, TargetLockedRemote};
53
54use citadel_proto::prelude::NetworkError;
55use citadel_proto::prelude::*;
56use citadel_types::crypto::SecurityLevel;
57use std::path::PathBuf;
58
59pub async fn write<T: ObjectSource, P: Into<PathBuf> + Send, R: Ratchet>(
61 remote: &impl TargetLockedRemote<R>,
62 source: T,
63 virtual_path: P,
64) -> Result<(), NetworkError> {
65 write_with_security_level(remote, source, Default::default(), virtual_path).await
66}
67
68pub async fn write_with_security_level<T: ObjectSource, P: Into<PathBuf> + Send, R: Ratchet>(
70 remote: &impl TargetLockedRemote<R>,
71 source: T,
72 security_level: SecurityLevel,
73 virtual_path: P,
74) -> Result<(), NetworkError> {
75 remote
76 .remote_encrypted_virtual_filesystem_push(source, virtual_path, security_level)
77 .await
78}
79
80pub async fn read<P: Into<PathBuf> + Send, R: Ratchet>(
82 remote: &impl TargetLockedRemote<R>,
83 virtual_path: P,
84) -> Result<PathBuf, NetworkError> {
85 read_with_security_level(remote, Default::default(), virtual_path).await
86}
87
88pub async fn read_with_security_level<P: Into<PathBuf> + Send, R: Ratchet>(
90 remote: &impl TargetLockedRemote<R>,
91 transfer_security_level: SecurityLevel,
92 virtual_path: P,
93) -> Result<PathBuf, NetworkError> {
94 remote
95 .remote_encrypted_virtual_filesystem_pull(virtual_path, transfer_security_level, false)
96 .await
97}
98
99pub async fn take<P: Into<PathBuf> + Send, R: Ratchet>(
101 remote: &impl TargetLockedRemote<R>,
102 virtual_path: P,
103) -> Result<PathBuf, NetworkError> {
104 remote
105 .remote_encrypted_virtual_filesystem_pull(virtual_path, Default::default(), true)
106 .await
107}
108
109pub async fn take_with_security_level<P: Into<PathBuf> + Send, R: Ratchet>(
111 remote: &impl TargetLockedRemote<R>,
112 transfer_security_level: SecurityLevel,
113 virtual_path: P,
114) -> Result<PathBuf, NetworkError> {
115 remote
116 .remote_encrypted_virtual_filesystem_pull(virtual_path, transfer_security_level, true)
117 .await
118}
119
120pub async fn delete<P: Into<PathBuf> + Send, R: Ratchet>(
122 remote: &impl TargetLockedRemote<R>,
123 virtual_path: P,
124) -> Result<(), NetworkError> {
125 remote
126 .remote_encrypted_virtual_filesystem_delete(virtual_path)
127 .await
128}
129
130#[cfg(test)]
131mod tests {
132 use crate::prefabs::client::single_connection::SingleClientServerConnectionKernel;
133 use crate::prefabs::server::accept_file_transfer_kernel::AcceptFileTransferKernel;
134
135 use crate::prefabs::client::peer_connection::{FileTransferHandleRx, PeerConnectionKernel};
136 use crate::prefabs::client::DefaultServerConnectionSettingsBuilder;
137 use crate::prelude::*;
138 use crate::test_common::wait_for_peers;
139 use citadel_io::tokio;
140 use futures::StreamExt;
141 use rstest::rstest;
142 use std::net::SocketAddr;
143 use std::path::PathBuf;
144 use std::sync::atomic::{AtomicBool, Ordering};
145 use std::time::Duration;
146 use uuid::Uuid;
147
148 pub fn server_info<'a, R: Ratchet>() -> (NodeFuture<'a, AcceptFileTransferKernel<R>>, SocketAddr)
149 {
150 crate::test_common::server_test_node(AcceptFileTransferKernel::<R>::default(), |_| {})
151 }
152
153 #[rstest]
154 #[case(
155 EncryptionAlgorithm::AES_GCM_256,
156 KemAlgorithm::Kyber,
157 SigAlgorithm::None
158 )]
159 #[case(
160 EncryptionAlgorithm::KyberHybrid,
161 KemAlgorithm::Kyber,
162 SigAlgorithm::Dilithium65
163 )]
164 #[timeout(Duration::from_secs(90))]
165 #[citadel_io::tokio::test]
166 async fn test_c2s_file_transfer_revfs(
167 #[case] enx: EncryptionAlgorithm,
168 #[case] kem: KemAlgorithm,
169 #[case] sig: SigAlgorithm,
170 #[values(SecurityLevel::Standard, SecurityLevel::Reinforced)] security_level: SecurityLevel,
171 ) {
172 citadel_logging::setup_log();
173 let client_success = &AtomicBool::new(false);
174 let (server, server_addr) = server_info::<StackedRatchet>();
175 let uuid = Uuid::new_v4();
176
177 let source_dir = PathBuf::from("../resources/TheBridge.pdf");
178
179 let session_security_settings = SessionSecuritySettingsBuilder::default()
180 .with_crypto_params(enx + kem + sig)
181 .with_security_level(security_level)
182 .build()
183 .unwrap();
184
185 let server_connection_settings =
186 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
187 .disable_udp()
188 .with_session_security_settings(session_security_settings)
189 .build()
190 .unwrap();
191
192 let client_kernel = SingleClientServerConnectionKernel::new(
193 server_connection_settings,
194 |connection| async move {
195 log::trace!(target: "citadel", "***CLIENT LOGIN SUCCESS :: File transfer next ***");
196 let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
197 crate::fs::write_with_security_level(
199 &connection.remote,
200 source_dir.clone(),
201 security_level,
202 &virtual_path,
203 )
204 .await?;
205 log::info!(target: "citadel", "***CLIENT FILE TRANSFER SUCCESS***");
206 let save_dir = crate::fs::read(&connection.remote, virtual_path).await?;
208 log::info!(target: "citadel", "***CLIENT REVFS PULL SUCCESS");
210 let original_bytes = citadel_io::tokio::fs::read(&source_dir).await.unwrap();
211 let revfs_pulled_bytes = citadel_io::tokio::fs::read(&save_dir).await.unwrap();
212 assert_eq!(original_bytes, revfs_pulled_bytes);
213 log::info!(target: "citadel", "***CLIENT REVFS PULL COMPARE SUCCESS");
214 client_success.store(true, Ordering::Relaxed);
215 connection.shutdown_kernel().await
216 },
217 );
218
219 let client = DefaultNodeBuilder::default().build(client_kernel).unwrap();
220
221 let result = citadel_io::tokio::select! {
222 res0 = client => res0.map(|_| ()),
223 res1 = server => res1.map(|_| ())
224 };
225
226 result.unwrap();
227
228 assert!(client_success.load(Ordering::Relaxed));
229 }
230
231 #[rstest]
232 #[case(
233 EncryptionAlgorithm::AES_GCM_256,
234 KemAlgorithm::Kyber,
235 SigAlgorithm::None
236 )]
237 #[timeout(std::time::Duration::from_secs(90))]
238 #[citadel_io::tokio::test]
239 async fn test_c2s_file_transfer_revfs_take(
240 #[case] enx: EncryptionAlgorithm,
241 #[case] kem: KemAlgorithm,
242 #[case] sig: SigAlgorithm,
243 #[values(SecurityLevel::Standard)] security_level: SecurityLevel,
244 ) {
245 citadel_logging::setup_log();
246 let client_success = &AtomicBool::new(false);
247 let (server, server_addr) = server_info::<StackedRatchet>();
248 let uuid = Uuid::new_v4();
249
250 let source_dir = PathBuf::from("../resources/TheBridge.pdf");
251
252 let session_security_settings = SessionSecuritySettingsBuilder::default()
253 .with_crypto_params(enx + kem + sig)
254 .with_security_level(security_level)
255 .build()
256 .unwrap();
257
258 let server_connection_settings =
259 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
260 .disable_udp()
261 .with_session_security_settings(session_security_settings)
262 .build()
263 .unwrap();
264
265 let client_kernel = SingleClientServerConnectionKernel::new(
266 server_connection_settings,
267 |connection| async move {
268 log::trace!(target: "citadel", "***CLIENT LOGIN SUCCESS :: File transfer next ***");
269 let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
270 crate::fs::write_with_security_level(
272 &connection.remote,
273 source_dir.clone(),
274 security_level,
275 &virtual_path,
276 )
277 .await?;
278 log::trace!(target: "citadel", "***CLIENT FILE TRANSFER SUCCESS***");
279 let save_dir = crate::fs::take(&connection.remote, &virtual_path).await?;
281 log::trace!(target: "citadel", "***CLIENT REVFS PULL SUCCESS");
283 let original_bytes = citadel_io::tokio::fs::read(&source_dir).await.unwrap();
284 let revfs_pulled_bytes = citadel_io::tokio::fs::read(&save_dir).await.unwrap();
285 assert_eq!(original_bytes, revfs_pulled_bytes);
286 log::trace!(target: "citadel", "***CLIENT REVFS PULL COMPARE SUCCESS");
287 assert!(crate::fs::read(&connection.remote, &virtual_path)
289 .await
290 .is_err());
291 client_success.store(true, Ordering::Relaxed);
292 connection.shutdown_kernel().await
293 },
294 );
295
296 let client = DefaultNodeBuilder::default().build(client_kernel).unwrap();
297
298 let result = citadel_io::tokio::select! {
299 res0 = client => res0.map(|_| ()),
300 res1 = server => res1.map(|_| ())
301 };
302
303 result.unwrap();
304
305 assert!(client_success.load(Ordering::Relaxed));
306 }
307
308 #[rstest]
309 #[case(
310 EncryptionAlgorithm::AES_GCM_256,
311 KemAlgorithm::Kyber,
312 SigAlgorithm::None
313 )]
314 #[timeout(std::time::Duration::from_secs(90))]
315 #[citadel_io::tokio::test]
316 async fn test_c2s_file_transfer_revfs_delete(
317 #[case] enx: EncryptionAlgorithm,
318 #[case] kem: KemAlgorithm,
319 #[case] sig: SigAlgorithm,
320 #[values(SecurityLevel::Standard)] security_level: SecurityLevel,
321 ) {
322 citadel_logging::setup_log();
323 let client_success = &AtomicBool::new(false);
324 let (server, server_addr) = server_info::<StackedRatchet>();
325 let uuid = Uuid::new_v4();
326
327 let source_dir = PathBuf::from("../resources/TheBridge.pdf");
328
329 let session_security_settings = SessionSecuritySettingsBuilder::default()
330 .with_crypto_params(enx + kem + sig)
331 .with_security_level(security_level)
332 .build()
333 .unwrap();
334
335 let server_connection_settings =
336 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
337 .disable_udp()
338 .with_session_security_settings(session_security_settings)
339 .build()
340 .unwrap();
341
342 let client_kernel = SingleClientServerConnectionKernel::new(
343 server_connection_settings,
344 |connection| async move {
345 log::trace!(target: "citadel", "***CLIENT LOGIN SUCCESS :: File transfer next ***");
346 let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
347 crate::fs::write_with_security_level(
349 &connection.remote,
350 source_dir.clone(),
351 security_level,
352 &virtual_path,
353 )
354 .await?;
355 log::trace!(target: "citadel", "***CLIENT FILE TRANSFER SUCCESS***");
356 let save_dir = crate::fs::read(&connection.remote, &virtual_path).await?;
358 log::trace!(target: "citadel", "***CLIENT REVFS PULL SUCCESS");
360 let original_bytes = citadel_io::tokio::fs::read(&source_dir).await.unwrap();
361 let revfs_pulled_bytes = citadel_io::tokio::fs::read(&save_dir).await.unwrap();
362 assert_eq!(original_bytes, revfs_pulled_bytes);
363 log::trace!(target: "citadel", "***CLIENT REVFS PULL COMPARE SUCCESS");
364 crate::fs::delete(&connection.remote, &virtual_path).await?;
365 assert!(crate::fs::read(&connection.remote, &virtual_path)
367 .await
368 .is_err());
369 client_success.store(true, Ordering::Relaxed);
370 connection.shutdown_kernel().await
371 },
372 );
373
374 let client = DefaultNodeBuilder::default().build(client_kernel).unwrap();
375
376 let result = citadel_io::tokio::select! {
377 res0 = client => res0.map(|_| ()),
378 res1 = server => res1.map(|_| ())
379 };
380
381 result.unwrap();
382
383 assert!(client_success.load(Ordering::Relaxed));
384 }
385
386 #[rstest]
387 #[case(SecrecyMode::BestEffort)]
388 #[timeout(Duration::from_secs(60))]
389 #[citadel_io::tokio::test(flavor = "multi_thread")]
390 async fn test_p2p_file_transfer_revfs(
391 #[case] secrecy_mode: SecrecyMode,
392 #[values(KemAlgorithm::Kyber)] kem: KemAlgorithm,
393 #[values(EncryptionAlgorithm::AES_GCM_256)] enx: EncryptionAlgorithm,
394 ) {
395 citadel_logging::setup_log();
396 crate::test_common::TestBarrier::setup(2);
397 let client0_success = &AtomicBool::new(false);
398 let client1_success = &AtomicBool::new(false);
399
400 let (server, server_addr) = crate::test_common::server_info::<StackedRatchet>();
401
402 let uuid0 = Uuid::new_v4();
403 let uuid1 = Uuid::new_v4();
404 let session_security = SessionSecuritySettingsBuilder::default()
405 .with_secrecy_mode(secrecy_mode)
406 .with_crypto_params(kem + enx)
407 .build()
408 .unwrap();
409
410 let security_level = SecurityLevel::Standard;
411
412 let source_dir = &PathBuf::from("../resources/TheBridge.pdf");
413
414 let server_connection_settings =
415 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid0)
416 .disable_udp()
417 .with_session_security_settings(session_security)
418 .build()
419 .unwrap();
420
421 let peer_conn_0 = PeerConnectionSetupAggregator::default()
422 .with_peer_custom(uuid1)
423 .ensure_registered()
424 .with_session_security_settings(session_security)
425 .enable_udp()
426 .add();
427
428 let client_kernel0 = PeerConnectionKernel::new(
430 server_connection_settings,
431 peer_conn_0,
432 move |mut connection, remote_outer| async move {
433 wait_for_peers().await;
434 let mut connection = connection.recv().await.unwrap()?;
435 let cid = connection.channel.get_session_cid();
436 wait_for_peers().await;
437 log::info!(target: "citadel", "***CLIENT A {cid} LOGIN SUCCESS :: File transfer next ***");
439 let remote = connection.remote.clone();
440 let handle_orig = connection.incoming_object_transfer_handles.take().unwrap();
441 let _file_transfer_task = accept_all(handle_orig);
442
443 let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
444 crate::fs::write_with_security_level(
446 &remote,
447 source_dir.clone(),
448 security_level,
449 &virtual_path,
450 )
451 .await?;
452 log::info!(target: "citadel", "***CLIENT A {cid} FILE TRANSFER SUCCESS***");
453 tokio::time::sleep(Duration::from_secs(1)).await;
454 wait_for_peers().await;
455 let save_dir = crate::fs::read(&remote, virtual_path).await?;
457 log::info!(target: "citadel", "***CLIENT A {cid} REVFS PULL SUCCESS");
459 let original_bytes = tokio::fs::read(&source_dir).await.unwrap();
460 let revfs_pulled_bytes = tokio::fs::read(&save_dir).await.unwrap();
461 assert_eq!(original_bytes, revfs_pulled_bytes);
462 log::info!(target: "citadel", "***CLIENT A {cid} REVFS PULL COMPARE SUCCESS");
463 wait_for_peers().await;
464 client0_success.store(true, Ordering::Relaxed);
465 remote_outer.shutdown_kernel().await
466 },
467 );
468
469 let server_connection_settings =
470 DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid1)
471 .disable_udp()
472 .with_session_security_settings(session_security)
473 .build()
474 .unwrap();
475
476 let peer_conn_1 = PeerConnectionSetupAggregator::default()
477 .with_peer_custom(uuid0)
478 .ensure_registered()
479 .with_session_security_settings(session_security)
480 .enable_udp()
481 .add();
482
483 let client_kernel1 = PeerConnectionKernel::new(
484 server_connection_settings,
485 peer_conn_1,
486 move |mut connection, remote_outer| async move {
487 wait_for_peers().await;
488 let mut connection = connection.recv().await.unwrap()?;
489 let cid = connection.channel.get_session_cid();
490 wait_for_peers().await;
491 let remote = connection.remote.clone();
492 let handle_orig = connection.incoming_object_transfer_handles.take().unwrap();
493 let _file_transfer_task = accept_all(handle_orig);
494 log::info!(target: "citadel", "***CLIENT B {cid} LOGIN SUCCESS :: File transfer next ***");
495 let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
496 crate::fs::write_with_security_level(
498 &remote,
499 source_dir.clone(),
500 security_level,
501 &virtual_path,
502 )
503 .await?;
504 log::info!(target: "citadel", "***CLIENT B {cid} FILE TRANSFER SUCCESS***");
505 tokio::time::sleep(Duration::from_secs(1)).await;
507 wait_for_peers().await;
508 let save_dir = crate::fs::read(&remote, virtual_path).await?;
510 log::info!(target: "citadel", "***CLIENT B {cid} REVFS PULL SUCCESS");
512 let original_bytes = citadel_io::tokio::fs::read(&source_dir).await.unwrap();
513 let revfs_pulled_bytes = citadel_io::tokio::fs::read(&save_dir).await.unwrap();
514 assert_eq!(original_bytes, revfs_pulled_bytes);
515 log::info!(target: "citadel", "***CLIENT B {cid} REVFS PULL COMPARE SUCCESS");
516 wait_for_peers().await;
517 client1_success.store(true, Ordering::Relaxed);
518 remote_outer.shutdown_kernel().await
519 },
520 );
521
522 let client0 = DefaultNodeBuilder::default().build(client_kernel0).unwrap();
523 let client1 = DefaultNodeBuilder::default().build(client_kernel1).unwrap();
524 let clients = futures::future::try_join(client0, client1);
525
526 let task = async move {
527 citadel_io::tokio::select! {
528 server_res = server => Err(NetworkError::msg(format!("Server ended prematurely: {:?}", server_res.map(|_| ())))),
529 client_res = clients => client_res.map(|_| ())
530 }
531 };
532
533 let _ = citadel_io::tokio::time::timeout(Duration::from_secs(120), task)
534 .await
535 .unwrap();
536
537 assert!(client0_success.load(Ordering::Relaxed));
538 assert!(client1_success.load(Ordering::Relaxed));
539 }
540
541 fn accept_all(mut rx: FileTransferHandleRx) -> citadel_io::tokio::task::JoinHandle<()> {
542 citadel_io::tokio::task::spawn(async move {
543 while let Some(mut handle) = rx.recv().await {
544 if let Err(err) = handle.accept() {
545 log::error!(target: "citadel", "Failed to accept file transfer: {err:?}");
546 continue;
547 }
548
549 exhaust_file_transfer_async(handle).await;
551 }
552 })
553 }
554
555 async fn exhaust_file_transfer_async(mut handle: ObjectTransferHandler) {
556 while let Some(evt) = handle.next().await {
557 log::info!(target: "citadel", "File Transfer Event: {evt:?}");
558 if let ObjectTransferStatus::Fail(err) = &evt {
559 log::error!(target: "citadel", "File Transfer Failed: {err:?}");
560 break;
561 } else if let ObjectTransferStatus::TransferComplete = &evt {
562 break;
563 } else if let ObjectTransferStatus::ReceptionComplete = &evt {
564 break;
565 }
566 }
567 }
568}