citadel_sdk/
fs.rs

1//! Remote Encrypted Virtual Filesystem (RE-VFS)
2//!
3//! This module provides high-level operations for interacting with the Remote Encrypted
4//! Virtual Filesystem in the Citadel Protocol. RE-VFS enables secure storage and
5//! retrieval of files with end-to-end encryption.
6//!
7//! # Features
8//! - Secure file storage and retrieval
9//! - Configurable security levels
10//! - Automatic encryption handling
11//! - Virtual path management
12//! - File deletion support
13//! - Take operations for atomic reads
14//!
15//! # Example
16//! ```rust
17//! use citadel_sdk::prelude::*;
18//! use citadel_sdk::fs;
19//!
20//! async fn store_file<R: Ratchet>(remote: &impl TargetLockedRemote<R>) -> Result<(), NetworkError> {
21//!     // Write a file to RE-VFS
22//!     fs::write(remote, "/local/file.txt", "/virtual/file.txt").await?;
23//!
24//!     // Read the file back
25//!     let local_path = fs::read(remote, "/virtual/file.txt").await?;
26//!
27//!     // Delete the file
28//!     fs::delete(remote, "/virtual/file.txt").await?;
29//!
30//!     Ok(())
31//! }
32//! ```
33//!
34//! # Important Notes
35//! - All operations are end-to-end encrypted
36//! - Security levels are configurable per operation
37//! - Take operations atomically read and delete
38//! - Virtual paths are independent of local paths
39//!
40//! # Related Components
41//! - [`TargetLockedRemote`]: Remote connection interface
42//! - [`SecurityLevel`]: Encryption strength configuration
43//! - [`ObjectSource`]: File source abstraction
44//! - [`NetworkError`]: Error handling
45//!
46//! [`TargetLockedRemote`]: crate::prelude::TargetLockedRemote
47//! [`SecurityLevel`]: crate::prelude::SecurityLevel
48//! [`ObjectSource`]: crate::prelude::ObjectSource
49//! [`NetworkError`]: crate::prelude::NetworkError
50//!
51
52use 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
59/// Writes a file or BytesSource to the Remote Encrypted Virtual Filesystem
60pub 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
68/// Writes a file or BytesSource to the Remote Encrypted Virtual Filesystem with a custom security level.
69pub 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
80/// Reads a file from the Remote Encrypted Virtual Filesystem
81pub 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
88/// Reads a file from the Remote Encrypted Virtual Filesystem with a custom transport security level
89pub 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
99/// Takes a file from the Remote Encrypted Virtual Filesystem
100pub 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
109/// Takes a file from the Remote Encrypted Virtual Filesystem with a custom security level.
110pub 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
120/// Deletes a file from the Remote Encrypted Virtual Filesystem
121pub 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;
136    #[cfg(feature = "localhost-testing")]
137    use crate::prefabs::client::peer_connection::PeerConnectionKernel;
138    use crate::prefabs::client::DefaultServerConnectionSettingsBuilder;
139    use crate::prelude::*;
140    #[cfg(feature = "localhost-testing")]
141    use crate::test_common::wait_for_peers;
142    use citadel_io::tokio;
143    use futures::StreamExt;
144    use rstest::rstest;
145    use std::net::SocketAddr;
146    use std::path::PathBuf;
147    use std::sync::atomic::{AtomicBool, Ordering};
148    use std::time::Duration;
149    use uuid::Uuid;
150
151    pub fn server_info<'a, R: Ratchet>() -> (NodeFuture<'a, AcceptFileTransferKernel<R>>, SocketAddr)
152    {
153        crate::test_common::server_test_node(AcceptFileTransferKernel::<R>::default(), |_| {})
154    }
155
156    #[rstest]
157    #[case(
158        EncryptionAlgorithm::AES_GCM_256,
159        KemAlgorithm::Kyber,
160        SigAlgorithm::None
161    )]
162    #[case(
163        EncryptionAlgorithm::KyberHybrid,
164        KemAlgorithm::Kyber,
165        SigAlgorithm::Dilithium65
166    )]
167    #[timeout(Duration::from_secs(90))]
168    #[citadel_io::tokio::test]
169    async fn test_c2s_file_transfer_revfs(
170        #[case] enx: EncryptionAlgorithm,
171        #[case] kem: KemAlgorithm,
172        #[case] sig: SigAlgorithm,
173        #[values(SecurityLevel::Standard, SecurityLevel::Reinforced)] security_level: SecurityLevel,
174    ) {
175        citadel_logging::setup_log();
176        let client_success = &AtomicBool::new(false);
177        let (server, server_addr) = server_info::<StackedRatchet>();
178        let uuid = Uuid::new_v4();
179
180        let source_dir = PathBuf::from("../resources/TheBridge.pdf");
181
182        let session_security_settings = SessionSecuritySettingsBuilder::default()
183            .with_crypto_params(enx + kem + sig)
184            .with_security_level(security_level)
185            .build()
186            .unwrap();
187
188        let server_connection_settings =
189            DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
190                .disable_udp()
191                .with_session_security_settings(session_security_settings)
192                .build()
193                .unwrap();
194
195        let client_kernel = SingleClientServerConnectionKernel::new(
196            server_connection_settings,
197            |connection| async move {
198                log::trace!(target: "citadel", "***CLIENT LOGIN SUCCESS :: File transfer next ***");
199
200                // Test list_sessions() - verify we can list active sessions with correct data
201                let sessions = connection.remote.list_sessions().await?;
202                log::info!(target: "citadel", "***CLIENT list_sessions() returned {} sessions***", sessions.sessions.len());
203                assert!(
204                    !sessions.sessions.is_empty(),
205                    "Should have at least one active session"
206                );
207                // Find our session by CID
208                let our_session = sessions.sessions.iter().find(|s| s.cid == connection.cid);
209                assert!(our_session.is_some(), "Should find our session in the list");
210                let our_session = our_session.unwrap();
211                assert!(
212                    !our_session.connections.is_empty(),
213                    "Session should have at least one connection"
214                );
215                // For C2S, peer_cid should be None
216                let c2s_conn = our_session
217                    .connections
218                    .iter()
219                    .find(|c| c.peer_cid.is_none());
220                assert!(
221                    c2s_conn.is_some(),
222                    "Should have a C2S connection (peer_cid = None)"
223                );
224                let c2s_conn = c2s_conn.unwrap();
225                assert!(c2s_conn.connected, "C2S connection should be active");
226                log::info!(target: "citadel", "***CLIENT list_sessions() verification PASSED***");
227
228                let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
229                // write to file to the RE-VFS
230                crate::fs::write_with_security_level(
231                    &connection.remote,
232                    source_dir.clone(),
233                    security_level,
234                    &virtual_path,
235                )
236                .await?;
237                log::info!(target: "citadel", "***CLIENT FILE TRANSFER SUCCESS***");
238                // now, pull it
239                let save_dir = crate::fs::read(&connection.remote, virtual_path).await?;
240                // now, compare bytes
241                log::info!(target: "citadel", "***CLIENT REVFS PULL SUCCESS");
242                let original_bytes = citadel_io::tokio::fs::read(&source_dir).await.unwrap();
243                let revfs_pulled_bytes = citadel_io::tokio::fs::read(&save_dir).await.unwrap();
244                assert_eq!(original_bytes, revfs_pulled_bytes);
245                log::info!(target: "citadel", "***CLIENT REVFS PULL COMPARE SUCCESS");
246                client_success.store(true, Ordering::Relaxed);
247                connection.shutdown_kernel().await
248            },
249        );
250
251        let client = DefaultNodeBuilder::default().build(client_kernel).unwrap();
252
253        let result = citadel_io::tokio::select! {
254            res0 = client => res0.map(|_| ()),
255            res1 = server => res1.map(|_| ())
256        };
257
258        result.unwrap();
259
260        assert!(client_success.load(Ordering::Relaxed));
261    }
262
263    #[rstest]
264    #[case(
265        EncryptionAlgorithm::AES_GCM_256,
266        KemAlgorithm::Kyber,
267        SigAlgorithm::None
268    )]
269    #[timeout(std::time::Duration::from_secs(90))]
270    #[citadel_io::tokio::test]
271    async fn test_c2s_file_transfer_revfs_take(
272        #[case] enx: EncryptionAlgorithm,
273        #[case] kem: KemAlgorithm,
274        #[case] sig: SigAlgorithm,
275        #[values(SecurityLevel::Standard)] security_level: SecurityLevel,
276    ) {
277        citadel_logging::setup_log();
278        let client_success = &AtomicBool::new(false);
279        let (server, server_addr) = server_info::<StackedRatchet>();
280        let uuid = Uuid::new_v4();
281
282        let source_dir = PathBuf::from("../resources/TheBridge.pdf");
283
284        let session_security_settings = SessionSecuritySettingsBuilder::default()
285            .with_crypto_params(enx + kem + sig)
286            .with_security_level(security_level)
287            .build()
288            .unwrap();
289
290        let server_connection_settings =
291            DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
292                .disable_udp()
293                .with_session_security_settings(session_security_settings)
294                .build()
295                .unwrap();
296
297        let client_kernel = SingleClientServerConnectionKernel::new(
298            server_connection_settings,
299            |connection| async move {
300                log::trace!(target: "citadel", "***CLIENT LOGIN SUCCESS :: File transfer next ***");
301                let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
302                // write to file to the RE-VFS
303                crate::fs::write_with_security_level(
304                    &connection.remote,
305                    source_dir.clone(),
306                    security_level,
307                    &virtual_path,
308                )
309                .await?;
310                log::trace!(target: "citadel", "***CLIENT FILE TRANSFER SUCCESS***");
311                // now, pull it
312                let save_dir = crate::fs::take(&connection.remote, &virtual_path).await?;
313                // now, compare bytes
314                log::trace!(target: "citadel", "***CLIENT REVFS PULL SUCCESS");
315                let original_bytes = citadel_io::tokio::fs::read(&source_dir).await.unwrap();
316                let revfs_pulled_bytes = citadel_io::tokio::fs::read(&save_dir).await.unwrap();
317                assert_eq!(original_bytes, revfs_pulled_bytes);
318                log::trace!(target: "citadel", "***CLIENT REVFS PULL COMPARE SUCCESS");
319                // prove we can no longer read from this virtual file
320                assert!(crate::fs::read(&connection.remote, &virtual_path)
321                    .await
322                    .is_err());
323                client_success.store(true, Ordering::Relaxed);
324                connection.shutdown_kernel().await
325            },
326        );
327
328        let client = DefaultNodeBuilder::default().build(client_kernel).unwrap();
329
330        let result = citadel_io::tokio::select! {
331            res0 = client => res0.map(|_| ()),
332            res1 = server => res1.map(|_| ())
333        };
334
335        result.unwrap();
336
337        assert!(client_success.load(Ordering::Relaxed));
338    }
339
340    #[rstest]
341    #[case(
342        EncryptionAlgorithm::AES_GCM_256,
343        KemAlgorithm::Kyber,
344        SigAlgorithm::None
345    )]
346    #[timeout(std::time::Duration::from_secs(90))]
347    #[citadel_io::tokio::test]
348    async fn test_c2s_file_transfer_revfs_delete(
349        #[case] enx: EncryptionAlgorithm,
350        #[case] kem: KemAlgorithm,
351        #[case] sig: SigAlgorithm,
352        #[values(SecurityLevel::Standard)] security_level: SecurityLevel,
353    ) {
354        citadel_logging::setup_log();
355        let client_success = &AtomicBool::new(false);
356        let (server, server_addr) = server_info::<StackedRatchet>();
357        let uuid = Uuid::new_v4();
358
359        let source_dir = PathBuf::from("../resources/TheBridge.pdf");
360
361        let session_security_settings = SessionSecuritySettingsBuilder::default()
362            .with_crypto_params(enx + kem + sig)
363            .with_security_level(security_level)
364            .build()
365            .unwrap();
366
367        let server_connection_settings =
368            DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid)
369                .disable_udp()
370                .with_session_security_settings(session_security_settings)
371                .build()
372                .unwrap();
373
374        let client_kernel = SingleClientServerConnectionKernel::new(
375            server_connection_settings,
376            |connection| async move {
377                log::trace!(target: "citadel", "***CLIENT LOGIN SUCCESS :: File transfer next ***");
378                let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
379                // write to file to the RE-VFS
380                crate::fs::write_with_security_level(
381                    &connection.remote,
382                    source_dir.clone(),
383                    security_level,
384                    &virtual_path,
385                )
386                .await?;
387                log::trace!(target: "citadel", "***CLIENT FILE TRANSFER SUCCESS***");
388                // now, pull it
389                let save_dir = crate::fs::read(&connection.remote, &virtual_path).await?;
390                // now, compare bytes
391                log::trace!(target: "citadel", "***CLIENT REVFS PULL SUCCESS");
392                let original_bytes = citadel_io::tokio::fs::read(&source_dir).await.unwrap();
393                let revfs_pulled_bytes = citadel_io::tokio::fs::read(&save_dir).await.unwrap();
394                assert_eq!(original_bytes, revfs_pulled_bytes);
395                log::trace!(target: "citadel", "***CLIENT REVFS PULL COMPARE SUCCESS");
396                crate::fs::delete(&connection.remote, &virtual_path).await?;
397                // prove we can no longer read from this virtual file since it was just deleted
398                assert!(crate::fs::read(&connection.remote, &virtual_path)
399                    .await
400                    .is_err());
401                client_success.store(true, Ordering::Relaxed);
402                connection.shutdown_kernel().await
403            },
404        );
405
406        let client = DefaultNodeBuilder::default().build(client_kernel).unwrap();
407
408        let result = citadel_io::tokio::select! {
409            res0 = client => res0.map(|_| ()),
410            res1 = server => res1.map(|_| ())
411        };
412
413        result.unwrap();
414
415        assert!(client_success.load(Ordering::Relaxed));
416    }
417
418    #[rstest]
419    #[case(SecrecyMode::BestEffort)]
420    #[timeout(Duration::from_secs(60))]
421    #[cfg(feature = "localhost-testing")]
422    #[citadel_io::tokio::test(flavor = "multi_thread")]
423    async fn test_p2p_file_transfer_revfs(
424        #[case] secrecy_mode: SecrecyMode,
425        #[values(KemAlgorithm::Kyber)] kem: KemAlgorithm,
426        #[values(EncryptionAlgorithm::AES_GCM_256)] enx: EncryptionAlgorithm,
427    ) {
428        citadel_logging::setup_log();
429        crate::test_common::TestBarrier::setup(2);
430        let client0_success = &AtomicBool::new(false);
431        let client1_success = &AtomicBool::new(false);
432
433        let (server, server_addr) = crate::test_common::server_info::<StackedRatchet>();
434
435        let uuid0 = Uuid::new_v4();
436        let uuid1 = Uuid::new_v4();
437        let session_security = SessionSecuritySettingsBuilder::default()
438            .with_secrecy_mode(secrecy_mode)
439            .with_crypto_params(kem + enx)
440            .build()
441            .unwrap();
442
443        let security_level = SecurityLevel::Standard;
444
445        let source_dir = &PathBuf::from("../resources/TheBridge.pdf");
446
447        let server_connection_settings =
448            DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid0)
449                .disable_udp()
450                .with_session_security_settings(session_security)
451                .build()
452                .unwrap();
453
454        let peer_conn_0 = PeerConnectionSetupAggregator::default()
455            .with_peer_custom(uuid1)
456            .ensure_registered()
457            .with_session_security_settings(session_security)
458            .enable_udp()
459            .add();
460
461        // TODO: SinglePeerConnectionKernel
462        let client_kernel0 = PeerConnectionKernel::new(
463            server_connection_settings,
464            peer_conn_0,
465            move |mut connection, remote_outer| async move {
466                wait_for_peers().await;
467                let mut connection = connection.recv().await.unwrap()?;
468                let cid = connection.channel.get_session_cid();
469                wait_for_peers().await;
470                // The other peer will send the file first
471                log::info!(target: "citadel", "***CLIENT A {cid} LOGIN SUCCESS :: File transfer next ***");
472                let remote = connection.remote.clone();
473                let handle_orig = connection.incoming_object_transfer_handles.take().unwrap();
474                let _file_transfer_task = accept_all(handle_orig);
475
476                let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
477                // write the file to the RE-VFS
478                crate::fs::write_with_security_level(
479                    &remote,
480                    source_dir.clone(),
481                    security_level,
482                    &virtual_path,
483                )
484                .await?;
485                log::info!(target: "citadel", "***CLIENT A {cid} FILE TRANSFER SUCCESS***");
486                tokio::time::sleep(Duration::from_secs(1)).await;
487                wait_for_peers().await;
488                // now, pull it
489                let save_dir = crate::fs::read(&remote, virtual_path).await?;
490                // now, compare bytes
491                log::info!(target: "citadel", "***CLIENT A {cid} REVFS PULL SUCCESS");
492                let original_bytes = tokio::fs::read(&source_dir).await.unwrap();
493                let revfs_pulled_bytes = tokio::fs::read(&save_dir).await.unwrap();
494                assert_eq!(original_bytes, revfs_pulled_bytes);
495                log::info!(target: "citadel", "***CLIENT A {cid} REVFS PULL COMPARE SUCCESS");
496                wait_for_peers().await;
497                client0_success.store(true, Ordering::Relaxed);
498                remote_outer.shutdown_kernel().await
499            },
500        );
501
502        let server_connection_settings =
503            DefaultServerConnectionSettingsBuilder::transient_with_id(server_addr, uuid1)
504                .disable_udp()
505                .with_session_security_settings(session_security)
506                .build()
507                .unwrap();
508
509        let peer_conn_1 = PeerConnectionSetupAggregator::default()
510            .with_peer_custom(uuid0)
511            .ensure_registered()
512            .with_session_security_settings(session_security)
513            .enable_udp()
514            .add();
515
516        let client_kernel1 = PeerConnectionKernel::new(
517            server_connection_settings,
518            peer_conn_1,
519            move |mut connection, remote_outer| async move {
520                wait_for_peers().await;
521                let mut connection = connection.recv().await.unwrap()?;
522                let cid = connection.channel.get_session_cid();
523                wait_for_peers().await;
524                let remote = connection.remote.clone();
525                let handle_orig = connection.incoming_object_transfer_handles.take().unwrap();
526                let _file_transfer_task = accept_all(handle_orig);
527                log::info!(target: "citadel", "***CLIENT B {cid} LOGIN SUCCESS :: File transfer next ***");
528                let virtual_path = PathBuf::from("/home/john.doe/TheBridge.pdf");
529                // write the file to the RE-VFS
530                crate::fs::write_with_security_level(
531                    &remote,
532                    source_dir.clone(),
533                    security_level,
534                    &virtual_path,
535                )
536                .await?;
537                log::info!(target: "citadel", "***CLIENT B {cid} FILE TRANSFER SUCCESS***");
538                // Wait some time for the file to synchronize
539                tokio::time::sleep(Duration::from_secs(1)).await;
540                wait_for_peers().await;
541                // now, pull it
542                let save_dir = crate::fs::read(&remote, virtual_path).await?;
543                // now, compare bytes
544                log::info!(target: "citadel", "***CLIENT B {cid} REVFS PULL SUCCESS");
545                let original_bytes = citadel_io::tokio::fs::read(&source_dir).await.unwrap();
546                let revfs_pulled_bytes = citadel_io::tokio::fs::read(&save_dir).await.unwrap();
547                assert_eq!(original_bytes, revfs_pulled_bytes);
548                log::info!(target: "citadel", "***CLIENT B {cid} REVFS PULL COMPARE SUCCESS");
549                wait_for_peers().await;
550                client1_success.store(true, Ordering::Relaxed);
551                remote_outer.shutdown_kernel().await
552            },
553        );
554
555        let client0 = DefaultNodeBuilder::default().build(client_kernel0).unwrap();
556        let client1 = DefaultNodeBuilder::default().build(client_kernel1).unwrap();
557        let clients = futures::future::try_join(client0, client1);
558
559        let task = async move {
560            citadel_io::tokio::select! {
561                server_res = server => Err(NetworkError::msg(format!("Server ended prematurely: {:?}", server_res.map(|_| ())))),
562                client_res = clients => client_res.map(|_| ())
563            }
564        };
565
566        let _ = citadel_io::tokio::time::timeout(Duration::from_secs(120), task)
567            .await
568            .unwrap();
569
570        assert!(client0_success.load(Ordering::Relaxed));
571        assert!(client1_success.load(Ordering::Relaxed));
572    }
573
574    #[allow(dead_code)]
575    fn accept_all(mut rx: FileTransferHandleRx) -> citadel_io::tokio::task::JoinHandle<()> {
576        citadel_io::tokio::task::spawn(async move {
577            while let Some(mut handle) = rx.recv().await {
578                if let Err(err) = handle.accept() {
579                    log::error!(target: "citadel", "Failed to accept file transfer: {err:?}");
580                    continue;
581                }
582
583                // Wait for this transfer to complete before accepting the next one
584                exhaust_file_transfer_async(handle).await;
585            }
586        })
587    }
588
589    #[allow(dead_code)]
590    async fn exhaust_file_transfer_async(mut handle: ObjectTransferHandler) {
591        while let Some(evt) = handle.next().await {
592            log::info!(target: "citadel", "File Transfer Event: {evt:?}");
593            if let ObjectTransferStatus::Fail(err) = &evt {
594                log::error!(target: "citadel", "File Transfer Failed: {err:?}");
595                break;
596            } else if let ObjectTransferStatus::TransferComplete = &evt {
597                break;
598            } else if let ObjectTransferStatus::ReceptionComplete = &evt {
599                break;
600            }
601        }
602    }
603}