1use crate::{
4 errors::{CatBridgeError, NetworkError, NetworkParseError},
5 fsemul::pcfs::{
6 errors::SataProtocolError,
7 sata::proto::{
8 DirectoryItemResponse, MoveToFileLocation, SataCapabilitiesFlags,
9 SataChangeModePacketBody, SataChangeOwnerPacketBody, SataCloseFilePacketBody,
10 SataCloseFolderPacketBody, SataCommandInfo, SataCreateFolderPacketBody, SataFDInfo,
11 SataFileDescriptorResult, SataGetInfoByQueryPacketBody, SataOpenFilePacketBody,
12 SataPacketHeader, SataPingPacketBody, SataPongBody, SataQueryResponse, SataQueryType,
13 SataReadFilePacketBody, SataReadFolderPacketBody, SataRemovePacketBody, SataRequest,
14 SataResponse, SataResultCode, SataRewindFolderPacketBody,
15 SataSetFilePositionPacketBody, SataStatFilePacketBody, SataWriteFilePacketBody,
16 },
17 },
18 net::{
19 client::TCPClient,
20 models::{Endianness, NagleGuard},
21 },
22};
23use bytes::{Buf, Bytes, BytesMut};
24use std::{
25 sync::{
26 Arc,
27 atomic::{AtomicBool, AtomicU32, Ordering},
28 },
29 time::Duration,
30};
31use tokio::net::ToSocketAddrs;
32use valuable::Valuable;
33
34pub const DEFAULT_CLIENT_TIMEOUT: Duration = Duration::from_secs(30);
36
37#[derive(Debug, Valuable)]
39pub struct SataClient {
40 supports_csr: Arc<AtomicBool>,
42 supports_ffio: Arc<AtomicBool>,
44 first_read_size: Arc<AtomicU32>,
47 first_write_size: Arc<AtomicU32>,
50 underlying_client: TCPClient,
52}
53
54impl SataClient {
55 pub async fn connect<AddrTy: ToSocketAddrs>(
61 address: AddrTy,
62 supports_csr: bool,
63 supports_ffio: bool,
64 first_read_size: u32,
65 first_write_size: u32,
66 trace_io_during_debug: bool,
67 ) -> Result<Self, CatBridgeError> {
68 let client = TCPClient::new(
69 "pcfs-sata",
70 NagleGuard::U32LengthPrefixed(Endianness::Big, None),
71 (None, None),
72 trace_io_during_debug,
73 );
74 client.connect(address).await?;
75
76 let this = Self {
77 supports_csr: Arc::new(AtomicBool::new(supports_csr)),
78 supports_ffio: Arc::new(AtomicBool::new(supports_ffio)),
79 underlying_client: client,
80 first_read_size: Arc::new(AtomicU32::new(first_read_size)),
81 first_write_size: Arc::new(AtomicU32::new(first_write_size)),
82 };
83 this.ping(Some(DEFAULT_CLIENT_TIMEOUT)).await?;
84
85 Ok(this)
86 }
87
88 pub async fn try_set_csr_ffio(&self, csr: bool, ffio: bool) -> Result<(), CatBridgeError> {
97 self.supports_csr.store(csr, Ordering::Release);
98 self.supports_ffio.store(ffio, Ordering::Release);
99 self.ping(None).await?;
100 Ok(())
101 }
102
103 pub async fn try_set_csr(&self, csr: bool) -> Result<(), CatBridgeError> {
112 self.supports_csr.store(csr, Ordering::Release);
113 self.ping(None).await?;
114 Ok(())
115 }
116
117 pub async fn try_set_ffio(&self, ffio: bool) -> Result<(), CatBridgeError> {
126 self.supports_ffio.store(ffio, Ordering::Release);
127 self.ping(None).await?;
128 Ok(())
129 }
130
131 pub async fn ping(&self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
138 let mut flags = SataCapabilitiesFlags::empty();
139 if self.supports_csr.load(Ordering::Acquire) {
140 flags = flags.union(SataCapabilitiesFlags::COMBINED_SEND_RECV_SUPPORTED);
141 }
142 if self.supports_ffio.load(Ordering::Acquire) {
143 flags = flags.union(SataCapabilitiesFlags::FAST_FILE_IO_SUPPORTED);
144 }
145
146 let mut req = Self::construct(0x14, SataPingPacketBody::new());
147 req.command_info_mut().set_user((
148 self.first_read_size.load(Ordering::Acquire),
149 self.first_write_size.load(Ordering::Acquire),
150 ));
151 req.command_info_mut().set_capabilities((u32::MAX, 0));
152 req.header_mut().set_flags(flags.0);
153 let (_stream_id, _req_id, opt_response) = self
154 .underlying_client
155 .send(req, Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)))
156 .await?;
157 let response = opt_response.ok_or(NetworkError::ExpectedData)?;
158 let pong = SataResponse::<SataPongBody>::try_from(
159 response.take_body().ok_or(NetworkError::ExpectedData)?,
160 )?;
161 self.supports_ffio
162 .store(pong.body().ffio_enabled(), Ordering::Release);
163 self.supports_csr
164 .store(pong.body().combined_send_recv_enabled(), Ordering::Release);
165
166 Ok(())
167 }
168
169 pub async fn change_mode(
183 &self,
184 path: String,
185 writable: bool,
186 timeout: Option<Duration>,
187 ) -> Result<(), CatBridgeError> {
188 let resp = self
189 .underlying_client
190 .send(
191 Self::construct(0x13, SataChangeModePacketBody::new(path, writable)?),
192 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
193 )
194 .await?
195 .2
196 .ok_or(NetworkError::ExpectedData)?
197 .take_body()
198 .ok_or(NetworkError::ExpectedData)?;
199
200 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
201 if sata_resp.body().0 != 0 {
202 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
203 }
204 Ok(())
205 }
206
207 pub async fn change_owner(
220 &self,
221 path: String,
222 owner: u32,
223 group: u32,
224 timeout: Option<Duration>,
225 ) -> Result<(), CatBridgeError> {
226 let resp = self
227 .underlying_client
228 .send(
229 Self::construct(0x12, SataChangeOwnerPacketBody::new(path, owner, group)?),
230 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
231 )
232 .await?
233 .2
234 .ok_or(NetworkError::ExpectedData)?
235 .take_body()
236 .ok_or(NetworkError::ExpectedData)?;
237
238 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
239 if sata_resp.body().0 != 0 {
240 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
241 }
242 Ok(())
243 }
244
245 pub async fn create_folder(
255 &self,
256 path: String,
257 writable: bool,
258 timeout: Option<Duration>,
259 ) -> Result<(), CatBridgeError> {
260 let resp = self
261 .underlying_client
262 .send(
263 Self::construct(0x0, SataCreateFolderPacketBody::new(path, writable)?),
264 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
265 )
266 .await?
267 .2
268 .ok_or(NetworkError::ExpectedData)?
269 .take_body()
270 .ok_or(NetworkError::ExpectedData)?;
271
272 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
273 if sata_resp.body().0 != 0 {
274 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
275 }
276 Ok(())
277 }
278
279 pub async fn info_by_query(
293 &self,
294 path: String,
295 query_type: SataQueryType,
296 timeout: Option<Duration>,
297 ) -> Result<SataQueryResponse, CatBridgeError> {
298 let resp = self
299 .underlying_client
300 .send(
301 Self::construct(0x10, SataGetInfoByQueryPacketBody::new(path, query_type)?),
302 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
303 )
304 .await?
305 .2
306 .ok_or(NetworkError::ExpectedData)?
307 .take_body()
308 .ok_or(NetworkError::ExpectedData)?;
309
310 let (_header, bytes) = SataResponse::<Bytes>::parse_opaque(resp)?.to_parts();
311
312 let typed_response = match query_type {
313 SataQueryType::FileCount => SataQueryResponse::try_from_small(bytes)?,
314 SataQueryType::FileDetails => SataQueryResponse::try_from_fd_info(bytes)?,
315 SataQueryType::FreeDiskSpace | SataQueryType::SizeOfFolder => {
316 SataQueryResponse::try_from_large(bytes)?
317 }
318 };
319 if let SataQueryResponse::ErrorCode(ec) = typed_response {
320 return Err(NetworkParseError::ErrorCode(ec).into());
321 }
322
323 Ok(typed_response)
324 }
325
326 pub async fn file_count(
336 &self,
337 path: String,
338 timeout: Option<Duration>,
339 ) -> Result<u32, CatBridgeError> {
340 let final_response = self
341 .info_by_query(path, SataQueryType::FileCount, timeout)
342 .await?;
343
344 match final_response {
345 SataQueryResponse::ErrorCode(_) => unreachable!("Checked in info_by_query"),
346 SataQueryResponse::FDInfo(_) | SataQueryResponse::LargeSize(_) => {
347 Err(SataProtocolError::WrongSataQueryResponse(final_response).into())
348 }
349 SataQueryResponse::SmallSize(smol) => Ok(smol),
350 }
351 }
352
353 pub async fn free_disk_space(
363 &self,
364 path: String,
365 timeout: Option<Duration>,
366 ) -> Result<u64, CatBridgeError> {
367 let final_response = self
368 .info_by_query(path, SataQueryType::FreeDiskSpace, timeout)
369 .await?;
370
371 match final_response {
372 SataQueryResponse::ErrorCode(_) => unreachable!("Checked in info_by_query"),
373 SataQueryResponse::FDInfo(_) | SataQueryResponse::SmallSize(_) => {
374 Err(SataProtocolError::WrongSataQueryResponse(final_response).into())
375 }
376 SataQueryResponse::LargeSize(lorg) => Ok(lorg),
377 }
378 }
379
380 pub async fn folder_size(
390 &self,
391 path: String,
392 timeout: Option<Duration>,
393 ) -> Result<u64, CatBridgeError> {
394 let final_response = self
395 .info_by_query(path, SataQueryType::SizeOfFolder, timeout)
396 .await?;
397
398 match final_response {
399 SataQueryResponse::ErrorCode(_) => unreachable!("Checked in info_by_query"),
400 SataQueryResponse::FDInfo(_) | SataQueryResponse::SmallSize(_) => {
401 Err(SataProtocolError::WrongSataQueryResponse(final_response).into())
402 }
403 SataQueryResponse::LargeSize(lorg) => Ok(lorg),
404 }
405 }
406
407 pub async fn path_info(
417 &self,
418 path: String,
419 timeout: Option<Duration>,
420 ) -> Result<SataFDInfo, CatBridgeError> {
421 let final_response = self
422 .info_by_query(path, SataQueryType::FileDetails, timeout)
423 .await?;
424
425 match final_response {
426 SataQueryResponse::ErrorCode(_) => unreachable!("Checked in info_by_query"),
427 SataQueryResponse::LargeSize(_) | SataQueryResponse::SmallSize(_) => {
428 Err(SataProtocolError::WrongSataQueryResponse(final_response).into())
429 }
430 SataQueryResponse::FDInfo(info) => Ok(info),
431 }
432 }
433
434 pub async fn remove(
444 &self,
445 path: String,
446 timeout: Option<Duration>,
447 ) -> Result<(), CatBridgeError> {
448 let resp = self
449 .underlying_client
450 .send(
451 Self::construct(0xE, SataRemovePacketBody::new(path)?),
452 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
453 )
454 .await?
455 .2
456 .ok_or(NetworkError::ExpectedData)?
457 .take_body()
458 .ok_or(NetworkError::ExpectedData)?;
459
460 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
461 if sata_resp.body().0 != 0 {
462 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
463 }
464 Ok(())
465 }
466
467 pub async fn open_file(
481 &self,
482 path: String,
483 mode_string: String,
484 timeout: Option<Duration>,
485 ) -> Result<SataClientFileHandle<'_>, CatBridgeError> {
486 let resp = self
487 .underlying_client
488 .send(
489 Self::construct(0x5, SataOpenFilePacketBody::new(path, mode_string)?),
490 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
491 )
492 .await?
493 .2
494 .ok_or(NetworkError::ExpectedData)?
495 .take_body()
496 .ok_or(NetworkError::ExpectedData)?;
497 let fd_result = SataResponse::<SataFileDescriptorResult>::try_from(resp)?;
498 let fd = match fd_result.take_body().result() {
499 Ok(fd) => fd,
500 Err(code) => {
501 return Err(NetworkParseError::ErrorCode(code).into());
502 }
503 };
504
505 Ok(SataClientFileHandle {
506 file_descriptor: fd,
507 underlying_client: self,
508 })
509 }
510
511 async fn do_file_read(
522 &self,
523 block_count: u32,
524 block_size: u32,
525 file_descriptor: i32,
526 move_to: Option<MoveToFileLocation>,
527 timeout: Option<Duration>,
528 ) -> Result<(usize, Bytes), CatBridgeError> {
529 if self.supports_ffio.load(Ordering::Acquire) {
530 let mut left_to_read = block_size * block_count;
531
532 let mut file_size = 0_usize;
533 let mut final_body = BytesMut::with_capacity(
534 usize::try_from(left_to_read)
535 .map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?,
536 );
537 while left_to_read > 0 {
538 let read_in_this_go = std::cmp::min(
541 left_to_read,
542 self.first_read_size.load(Ordering::Acquire) - 0x25,
543 );
544 let read_in_this_go_size = usize::try_from(read_in_this_go)
545 .map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?;
546 let (_stream_id, _req_id, opt_response) = self
548 .underlying_client
549 .send_with_read_amount(
550 Self::construct(
551 0x6,
552 SataReadFilePacketBody::new(
553 1,
554 read_in_this_go,
555 file_descriptor,
556 move_to,
557 ),
558 ),
559 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
560 0x20_usize + 0x4_usize + read_in_this_go_size,
562 )
563 .await?;
564
565 let mut full_body = opt_response
566 .ok_or(NetworkError::ExpectedData)?
567 .take_body()
568 .ok_or(NetworkError::ExpectedData)?;
569 full_body.advance(0x20);
571 if file_size == 0 {
572 file_size = usize::try_from(full_body.get_u32()).unwrap_or(usize::MAX);
573 }
574 final_body.extend(full_body);
575 left_to_read -= read_in_this_go;
576 }
577
578 Ok((file_size, final_body.freeze()))
579 } else {
580 todo!("Implement non-FFIO file support.")
581 }
582 }
583
584 async fn do_file_write(
585 &self,
586 block_count: u32,
587 block_size: u32,
588 file_descriptor: i32,
589 move_to: Option<MoveToFileLocation>,
590 raw_trusted_data: Bytes,
591 timeout: Option<Duration>,
592 ) -> Result<(), CatBridgeError> {
593 if self.supports_ffio.load(Ordering::Acquire) {
594 let base_req_bytes = Bytes::from(Self::construct(
595 0x7,
596 SataWriteFilePacketBody::new(block_count, block_size, file_descriptor, move_to),
597 ));
598 let mut final_req =
599 BytesMut::with_capacity(base_req_bytes.len() + raw_trusted_data.len());
600 final_req.extend(base_req_bytes);
601 final_req.extend(raw_trusted_data);
602
603 let resp = self
604 .underlying_client
605 .send(final_req, Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)))
606 .await?
607 .2
608 .ok_or(NetworkError::ExpectedData)?
609 .take_body()
610 .ok_or(NetworkError::ExpectedData)?;
611
612 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
613 if sata_resp.body().0 != 0 {
614 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
615 }
616 Ok(())
617 } else {
618 todo!("Implement non-FFIO file support.")
619 }
620 }
621
622 async fn stat_file(
623 &self,
624 file_descriptor: i32,
625 timeout: Option<Duration>,
626 ) -> Result<SataFDInfo, CatBridgeError> {
627 let resp = self
628 .underlying_client
629 .send(
630 Self::construct(0xB, SataStatFilePacketBody::new(file_descriptor)),
631 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
632 )
633 .await?
634 .2
635 .ok_or(NetworkError::ExpectedData)?
636 .take_body()
637 .ok_or(NetworkError::ExpectedData)?;
638
639 let (_header, bytes) = SataResponse::<Bytes>::parse_opaque(resp)?.to_parts();
640 let typed_response = SataQueryResponse::try_from_fd_info(bytes)?;
641 if let SataQueryResponse::ErrorCode(ec) = typed_response {
642 return Err(NetworkParseError::ErrorCode(ec).into());
643 }
644 match typed_response {
645 SataQueryResponse::FDInfo(info) => Ok(info),
646 _ => unreachable!("Not reachable from try_from_fd_info"),
647 }
648 }
649
650 async fn do_file_move(
651 &self,
652 file_descriptor: i32,
653 move_to: MoveToFileLocation,
654 timeout: Option<Duration>,
655 ) -> Result<(), CatBridgeError> {
656 let resp = self
657 .underlying_client
658 .send(
659 Self::construct(
660 0x9,
661 SataSetFilePositionPacketBody::new(file_descriptor, move_to),
662 ),
663 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
664 )
665 .await?
666 .2
667 .ok_or(NetworkError::ExpectedData)?
668 .take_body()
669 .ok_or(NetworkError::ExpectedData)?;
670
671 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
672 if sata_resp.body().0 != 0 {
673 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
674 }
675 Ok(())
676 }
677
678 async fn close_file(
679 &self,
680 file_descriptor: i32,
681 timeout: Option<Duration>,
682 ) -> Result<(), CatBridgeError> {
683 let resp = self
684 .underlying_client
685 .send(
686 Self::construct(0xD, SataCloseFilePacketBody::new(file_descriptor)),
687 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
688 )
689 .await?
690 .2
691 .ok_or(NetworkError::ExpectedData)?
692 .take_body()
693 .ok_or(NetworkError::ExpectedData)?;
694
695 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
696 if sata_resp.body().0 != 0 {
697 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
698 }
699 Ok(())
700 }
701
702 async fn read_folder(
703 &self,
704 folder_descriptor: i32,
705 timeout: Option<Duration>,
706 ) -> Result<Option<(SataFDInfo, String)>, CatBridgeError> {
707 let resp = self
708 .underlying_client
709 .send(
710 Self::construct(0x2, SataReadFolderPacketBody::new(folder_descriptor)),
711 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
712 )
713 .await?
714 .2
715 .ok_or(NetworkError::ExpectedData)?
716 .take_body()
717 .ok_or(NetworkError::ExpectedData)?;
718
719 let sata_resp = SataResponse::<DirectoryItemResponse>::try_from(resp)?;
720 let directory_item = sata_resp.take_body();
721 if !directory_item.is_successful() {
722 return Err(NetworkParseError::ErrorCode(directory_item.return_code()).into());
723 }
724 Ok(directory_item.take_file_info())
725 }
726
727 async fn rewind_folder(
728 &self,
729 folder_descriptor: i32,
730 timeout: Option<Duration>,
731 ) -> Result<(), CatBridgeError> {
732 let resp = self
733 .underlying_client
734 .send(
735 Self::construct(0x3, SataRewindFolderPacketBody::new(folder_descriptor)),
736 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
737 )
738 .await?
739 .2
740 .ok_or(NetworkError::ExpectedData)?
741 .take_body()
742 .ok_or(NetworkError::ExpectedData)?;
743
744 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
745 if sata_resp.body().0 != 0 {
746 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
747 }
748 Ok(())
749 }
750
751 async fn close_folder(
752 &self,
753 folder_descriptor: i32,
754 timeout: Option<Duration>,
755 ) -> Result<(), CatBridgeError> {
756 let resp = self
757 .underlying_client
758 .send(
759 Self::construct(0x4, SataCloseFolderPacketBody::new(folder_descriptor)),
760 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
761 )
762 .await?
763 .2
764 .ok_or(NetworkError::ExpectedData)?
765 .take_body()
766 .ok_or(NetworkError::ExpectedData)?;
767
768 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
769 if sata_resp.body().0 != 0 {
770 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
771 }
772 Ok(())
773 }
774
775 #[must_use]
777 fn construct<InnerTy: Into<Bytes>>(command: u32, body: InnerTy) -> SataRequest<Bytes> {
778 let ci = SataCommandInfo::new((0, 0), (0, 0), command);
779 let body: Bytes = body.into();
780 let mut header = SataPacketHeader::new(0);
781 header.set_data_len(0x14_u32 + u32::try_from(body.len()).unwrap_or(u32::MAX));
782
783 SataRequest::new(header, ci, body)
784 }
785}
786
787#[derive(Debug, Valuable)]
792pub struct SataClientFileHandle<'client> {
793 file_descriptor: i32,
795 underlying_client: &'client SataClient,
797}
798
799impl SataClientFileHandle<'_> {
800 pub async fn close(self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
809 self.underlying_client
810 .close_file(self.file_descriptor, timeout)
811 .await
812 }
813
814 pub async fn read_file(
827 &self,
828 amount: usize,
829 move_to: Option<MoveToFileLocation>,
830 timeout: Option<Duration>,
831 ) -> Result<(usize, Bytes), CatBridgeError> {
832 let (block_size, block_len) = Self::calculate_ideal_block_size_count(amount);
833
834 self.underlying_client
835 .do_file_read(
836 block_len,
837 block_size,
838 self.file_descriptor,
839 move_to,
840 timeout,
841 )
842 .await
843 }
844
845 pub async fn stat(&self, timeout: Option<Duration>) -> Result<SataFDInfo, CatBridgeError> {
854 self.underlying_client
855 .stat_file(self.file_descriptor, timeout)
856 .await
857 }
858
859 pub async fn move_to(
868 &self,
869 move_to: MoveToFileLocation,
870 timeout: Option<Duration>,
871 ) -> Result<(), CatBridgeError> {
872 self.underlying_client
873 .do_file_move(self.file_descriptor, move_to, timeout)
874 .await
875 }
876
877 pub async fn write_file(
892 &self,
893 to_write: Bytes,
894 move_to: Option<MoveToFileLocation>,
895 timeout: Option<Duration>,
896 ) -> Result<(), CatBridgeError> {
897 let (block_size, block_len) = Self::calculate_ideal_block_size_count(to_write.len());
898
899 self.underlying_client
900 .do_file_write(
901 block_len,
902 block_size,
903 self.file_descriptor,
904 move_to,
905 to_write,
906 timeout,
907 )
908 .await
909 }
910
911 fn calculate_ideal_block_size_count(amount: usize) -> (u32, u32) {
912 if amount < 512 {
913 (u32::try_from(amount).expect("unreachable()"), 1)
914 } else if amount.is_multiple_of(512) {
915 (512, u32::try_from(amount / 512).unwrap_or(u32::MAX))
916 } else {
917 let mut count = 511;
918 while !amount.is_multiple_of(count) {
919 count -= 1;
920 }
921
922 (
923 u32::try_from(count).expect("unreachable()"),
924 u32::try_from(amount / count).unwrap_or(u32::MAX),
925 )
926 }
927 }
928}
929
930#[derive(Debug, Valuable)]
935pub struct SataClientFolderHandle<'client> {
936 folder_descriptor: i32,
938 underlying_client: &'client SataClient,
940}
941
942impl SataClientFolderHandle<'_> {
943 pub async fn close(self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
952 self.underlying_client
953 .close_folder(self.folder_descriptor, timeout)
954 .await
955 }
956
957 pub async fn next_in_folder(
969 &self,
970 timeout: Option<Duration>,
971 ) -> Result<Option<(SataFDInfo, String)>, CatBridgeError> {
972 self.underlying_client
973 .read_folder(self.folder_descriptor, timeout)
974 .await
975 }
976
977 pub async fn rewind_iterator(&self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
987 self.underlying_client
988 .rewind_folder(self.folder_descriptor, timeout)
989 .await
990 }
991}
992