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())
573 .map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?;
574 }
575 final_body.extend(full_body);
576 left_to_read -= read_in_this_go;
577 }
578
579 Ok((file_size, final_body.freeze()))
580 } else {
581 todo!("Implement non-FFIO file support.")
582 }
583 }
584
585 async fn do_file_write(
586 &self,
587 block_count: u32,
588 block_size: u32,
589 file_descriptor: i32,
590 move_to: Option<MoveToFileLocation>,
591 raw_trusted_data: Bytes,
592 timeout: Option<Duration>,
593 ) -> Result<(), CatBridgeError> {
594 if self.supports_ffio.load(Ordering::Acquire) {
595 let base_req_bytes = Bytes::from(Self::construct(
596 0x7,
597 SataWriteFilePacketBody::new(block_count, block_size, file_descriptor, move_to),
598 ));
599 let mut final_req =
600 BytesMut::with_capacity(base_req_bytes.len() + raw_trusted_data.len());
601 final_req.extend(base_req_bytes);
602 final_req.extend(raw_trusted_data);
603
604 let resp = self
605 .underlying_client
606 .send(final_req, Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)))
607 .await?
608 .2
609 .ok_or(NetworkError::ExpectedData)?
610 .take_body()
611 .ok_or(NetworkError::ExpectedData)?;
612
613 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
614 if sata_resp.body().0 != 0 {
615 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
616 }
617 Ok(())
618 } else {
619 todo!("Implement non-FFIO file support.")
620 }
621 }
622
623 async fn stat_file(
624 &self,
625 file_descriptor: i32,
626 timeout: Option<Duration>,
627 ) -> Result<SataFDInfo, CatBridgeError> {
628 let resp = self
629 .underlying_client
630 .send(
631 Self::construct(0xB, SataStatFilePacketBody::new(file_descriptor)),
632 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
633 )
634 .await?
635 .2
636 .ok_or(NetworkError::ExpectedData)?
637 .take_body()
638 .ok_or(NetworkError::ExpectedData)?;
639
640 let (_header, bytes) = SataResponse::<Bytes>::parse_opaque(resp)?.to_parts();
641 let typed_response = SataQueryResponse::try_from_fd_info(bytes)?;
642 if let SataQueryResponse::ErrorCode(ec) = typed_response {
643 return Err(NetworkParseError::ErrorCode(ec).into());
644 }
645 match typed_response {
646 SataQueryResponse::FDInfo(info) => Ok(info),
647 _ => unreachable!("Not reachable from try_from_fd_info"),
648 }
649 }
650
651 async fn do_file_move(
652 &self,
653 file_descriptor: i32,
654 move_to: MoveToFileLocation,
655 timeout: Option<Duration>,
656 ) -> Result<(), CatBridgeError> {
657 let resp = self
658 .underlying_client
659 .send(
660 Self::construct(
661 0x9,
662 SataSetFilePositionPacketBody::new(file_descriptor, move_to),
663 ),
664 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
665 )
666 .await?
667 .2
668 .ok_or(NetworkError::ExpectedData)?
669 .take_body()
670 .ok_or(NetworkError::ExpectedData)?;
671
672 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
673 if sata_resp.body().0 != 0 {
674 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
675 }
676 Ok(())
677 }
678
679 async fn close_file(
680 &self,
681 file_descriptor: i32,
682 timeout: Option<Duration>,
683 ) -> Result<(), CatBridgeError> {
684 let resp = self
685 .underlying_client
686 .send(
687 Self::construct(0xD, SataCloseFilePacketBody::new(file_descriptor)),
688 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
689 )
690 .await?
691 .2
692 .ok_or(NetworkError::ExpectedData)?
693 .take_body()
694 .ok_or(NetworkError::ExpectedData)?;
695
696 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
697 if sata_resp.body().0 != 0 {
698 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
699 }
700 Ok(())
701 }
702
703 async fn read_folder(
704 &self,
705 folder_descriptor: i32,
706 timeout: Option<Duration>,
707 ) -> Result<Option<(SataFDInfo, String)>, CatBridgeError> {
708 let resp = self
709 .underlying_client
710 .send(
711 Self::construct(0x2, SataReadFolderPacketBody::new(folder_descriptor)),
712 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
713 )
714 .await?
715 .2
716 .ok_or(NetworkError::ExpectedData)?
717 .take_body()
718 .ok_or(NetworkError::ExpectedData)?;
719
720 let sata_resp = SataResponse::<DirectoryItemResponse>::try_from(resp)?;
721 let directory_item = sata_resp.take_body();
722 if !directory_item.is_successful() {
723 return Err(NetworkParseError::ErrorCode(directory_item.return_code()).into());
724 }
725 Ok(directory_item.take_file_info())
726 }
727
728 async fn rewind_folder(
729 &self,
730 folder_descriptor: i32,
731 timeout: Option<Duration>,
732 ) -> Result<(), CatBridgeError> {
733 let resp = self
734 .underlying_client
735 .send(
736 Self::construct(0x3, SataRewindFolderPacketBody::new(folder_descriptor)),
737 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
738 )
739 .await?
740 .2
741 .ok_or(NetworkError::ExpectedData)?
742 .take_body()
743 .ok_or(NetworkError::ExpectedData)?;
744
745 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
746 if sata_resp.body().0 != 0 {
747 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
748 }
749 Ok(())
750 }
751
752 async fn close_folder(
753 &self,
754 folder_descriptor: i32,
755 timeout: Option<Duration>,
756 ) -> Result<(), CatBridgeError> {
757 let resp = self
758 .underlying_client
759 .send(
760 Self::construct(0x4, SataCloseFolderPacketBody::new(folder_descriptor)),
761 Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
762 )
763 .await?
764 .2
765 .ok_or(NetworkError::ExpectedData)?
766 .take_body()
767 .ok_or(NetworkError::ExpectedData)?;
768
769 let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
770 if sata_resp.body().0 != 0 {
771 return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
772 }
773 Ok(())
774 }
775
776 #[must_use]
778 fn construct<InnerTy: Into<Bytes>>(command: u32, body: InnerTy) -> SataRequest<Bytes> {
779 let ci = SataCommandInfo::new((0, 0), (0, 0), command);
780 let body: Bytes = body.into();
781 let mut header = SataPacketHeader::new(0);
782 header.set_data_len(0x14_u32 + u32::try_from(body.len()).unwrap_or(u32::MAX));
783
784 SataRequest::new(header, ci, body)
785 }
786}
787
788#[derive(Debug, Valuable)]
793pub struct SataClientFileHandle<'client> {
794 file_descriptor: i32,
796 underlying_client: &'client SataClient,
798}
799
800impl SataClientFileHandle<'_> {
801 pub async fn close(self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
810 self.underlying_client
811 .close_file(self.file_descriptor, timeout)
812 .await
813 }
814
815 pub async fn read_file(
828 &self,
829 amount: usize,
830 move_to: Option<MoveToFileLocation>,
831 timeout: Option<Duration>,
832 ) -> Result<(usize, Bytes), CatBridgeError> {
833 let (block_size, block_len) = Self::calculate_ideal_block_size_count(amount);
834
835 self.underlying_client
836 .do_file_read(
837 block_len,
838 block_size,
839 self.file_descriptor,
840 move_to,
841 timeout,
842 )
843 .await
844 }
845
846 pub async fn stat(&self, timeout: Option<Duration>) -> Result<SataFDInfo, CatBridgeError> {
855 self.underlying_client
856 .stat_file(self.file_descriptor, timeout)
857 .await
858 }
859
860 pub async fn move_to(
869 &self,
870 move_to: MoveToFileLocation,
871 timeout: Option<Duration>,
872 ) -> Result<(), CatBridgeError> {
873 self.underlying_client
874 .do_file_move(self.file_descriptor, move_to, timeout)
875 .await
876 }
877
878 pub async fn write_file(
893 &self,
894 to_write: Bytes,
895 move_to: Option<MoveToFileLocation>,
896 timeout: Option<Duration>,
897 ) -> Result<(), CatBridgeError> {
898 let (block_size, block_len) = Self::calculate_ideal_block_size_count(to_write.len());
899
900 self.underlying_client
901 .do_file_write(
902 block_len,
903 block_size,
904 self.file_descriptor,
905 move_to,
906 to_write,
907 timeout,
908 )
909 .await
910 }
911
912 fn calculate_ideal_block_size_count(amount: usize) -> (u32, u32) {
913 if amount < 512 {
914 (u32::try_from(amount).expect("unreachable()"), 1)
915 } else if amount.is_multiple_of(512) {
916 (512, u32::try_from(amount / 512).unwrap_or(u32::MAX))
917 } else {
918 let mut count = 511;
919 while !amount.is_multiple_of(count) {
920 count -= 1;
921 }
922
923 (
924 u32::try_from(count).expect("unreachable()"),
925 u32::try_from(amount / count).unwrap_or(u32::MAX),
926 )
927 }
928 }
929}
930
931#[derive(Debug, Valuable)]
936pub struct SataClientFolderHandle<'client> {
937 folder_descriptor: i32,
939 underlying_client: &'client SataClient,
941}
942
943impl SataClientFolderHandle<'_> {
944 pub async fn close(self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
953 self.underlying_client
954 .close_folder(self.folder_descriptor, timeout)
955 .await
956 }
957
958 pub async fn next_in_folder(
970 &self,
971 timeout: Option<Duration>,
972 ) -> Result<Option<(SataFDInfo, String)>, CatBridgeError> {
973 self.underlying_client
974 .read_folder(self.folder_descriptor, timeout)
975 .await
976 }
977
978 pub async fn rewind_iterator(&self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
988 self.underlying_client
989 .rewind_folder(self.folder_descriptor, timeout)
990 .await
991 }
992}
993