1use std::{
4 ffi::OsString,
5 fmt,
6 path::PathBuf,
7 sync::{
8 Arc, Mutex, MutexGuard,
9 atomic::{AtomicU8, AtomicU64, Ordering},
10 },
11};
12
13use aster_forge_cloud_files_core::HydrationCoordinator;
14use aster_forge_cloud_files_core::{
15 Alignment, ByteRange, CloudBackendErrorKind, ContentReadRange, ContentReadResponse,
16 ContentRevision, HydrationError, HydrationRequest, SessionGeneration, SessionState,
17};
18use bytes::Bytes;
19
20use crate::{
21 Result, WindowsCloudFilesError, WindowsFetchDataWaiterRegistry, WindowsFileIdentity,
22 WindowsSyncRootIdentity,
23};
24
25pub const CFAPI_MAX_PRIORITY_HINT: u8 = 15;
27pub const CFAPI_TRANSFER_ALIGNMENT_BYTES: u64 = 4096;
29
30#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
32pub struct WindowsSyncRootConnectOptions {
33 pub require_process_info: bool,
35 pub require_full_file_path: bool,
37 pub block_self_implicit_hydration: bool,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub struct WindowsConnectionKey(i64);
47
48impl WindowsConnectionKey {
49 #[must_use]
51 pub const fn new(value: i64) -> Self {
52 Self(value)
53 }
54
55 #[must_use]
57 pub const fn get(self) -> i64 {
58 self.0
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub struct WindowsTransferKey(i64);
65
66impl WindowsTransferKey {
67 #[must_use]
69 pub const fn new(value: i64) -> Self {
70 Self(value)
71 }
72
73 #[must_use]
75 pub const fn get(self) -> i64 {
76 self.0
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82pub struct WindowsRequestKey(i64);
83
84impl WindowsRequestKey {
85 #[must_use]
87 pub const fn new(value: i64) -> Self {
88 Self(value)
89 }
90
91 #[must_use]
93 pub const fn get(self) -> i64 {
94 self.0
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub enum WindowsCallbackRangeLength {
101 Exact(u64),
103 ToEnd,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub struct WindowsCallbackRange {
110 offset: u64,
111 length: WindowsCallbackRangeLength,
112}
113
114impl WindowsCallbackRange {
115 pub fn exact(offset: u64, length: u64) -> Result<Self> {
121 if offset > i64::MAX as u64 {
122 return Err(invalid_callback(
123 "callback range offset exceeds signed CFAPI boundary",
124 ));
125 }
126 if length == 0 {
127 return Err(invalid_callback("callback range length must be non-zero"));
128 }
129 if length > i64::MAX as u64 {
130 return Err(invalid_callback(
131 "callback range length exceeds signed CFAPI boundary",
132 ));
133 }
134 if offset
135 .checked_add(length)
136 .is_none_or(|end| end > i64::MAX as u64)
137 {
138 return Err(invalid_callback(
139 "callback range end exceeds signed CFAPI boundary",
140 ));
141 }
142 Ok(Self {
143 offset,
144 length: WindowsCallbackRangeLength::Exact(length),
145 })
146 }
147
148 pub fn to_end(offset: u64) -> Result<Self> {
154 if offset > i64::MAX as u64 {
155 return Err(invalid_callback(
156 "callback range offset exceeds signed CFAPI boundary",
157 ));
158 }
159 Ok(Self {
160 offset,
161 length: WindowsCallbackRangeLength::ToEnd,
162 })
163 }
164
165 pub fn from_cfapi(offset: i64, length: i64) -> Result<Self> {
171 let offset = u64::try_from(offset)
172 .map_err(|_| invalid_callback("callback range offset must be non-negative"))?;
173 match length {
174 -1 => Self::to_end(offset),
175 1.. => Self::exact(
176 offset,
177 u64::try_from(length)
178 .map_err(|_| invalid_callback("callback range length exceeds u64"))?,
179 ),
180 _ => Err(invalid_callback(
181 "callback range length must be positive or CF_EOF",
182 )),
183 }
184 }
185
186 #[must_use]
188 pub const fn offset(self) -> u64 {
189 self.offset
190 }
191
192 #[must_use]
194 pub const fn length(self) -> WindowsCallbackRangeLength {
195 self.length
196 }
197
198 #[must_use]
200 pub const fn end_exclusive(self) -> Option<u64> {
201 match self.length {
202 WindowsCallbackRangeLength::Exact(length) => Some(self.offset + length),
203 WindowsCallbackRangeLength::ToEnd => None,
204 }
205 }
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct WindowsProcessInfoSnapshot {
211 pub process_id: u32,
213 pub image_path: Option<PathBuf>,
215 pub package_name: Option<OsString>,
217 pub application_id: Option<OsString>,
219 pub command_line: Option<OsString>,
221 pub session_id: u32,
223}
224
225#[derive(Clone, PartialEq, Eq)]
227pub struct WindowsCallbackInfoSnapshot {
228 generation: SessionGeneration,
229 connection_key: WindowsConnectionKey,
230 transfer_key: WindowsTransferKey,
231 request_key: WindowsRequestKey,
232 volume_guid_name: Option<OsString>,
233 volume_dos_name: Option<OsString>,
234 volume_serial_number: u32,
235 sync_root_file_id: i64,
236 sync_root_identity: WindowsSyncRootIdentity,
237 file_id: i64,
238 file_size: u64,
239 file_identity: WindowsFileIdentity,
240 normalized_path: Option<PathBuf>,
241 priority_hint: u8,
242 process: Option<WindowsProcessInfoSnapshot>,
243}
244
245impl WindowsCallbackInfoSnapshot {
246 #[expect(
248 clippy::too_many_arguments,
249 reason = "the constructor mirrors the flat, versioned CF_CALLBACK_INFO ownership boundary"
250 )]
251 pub fn new(
256 generation: SessionGeneration,
257 connection_key: WindowsConnectionKey,
258 transfer_key: WindowsTransferKey,
259 request_key: WindowsRequestKey,
260 volume_guid_name: Option<OsString>,
261 volume_dos_name: Option<OsString>,
262 volume_serial_number: u32,
263 sync_root_file_id: i64,
264 sync_root_identity: WindowsSyncRootIdentity,
265 file_id: i64,
266 file_size: u64,
267 file_identity: WindowsFileIdentity,
268 normalized_path: Option<PathBuf>,
269 priority_hint: u8,
270 process: Option<WindowsProcessInfoSnapshot>,
271 ) -> Result<Self> {
272 if priority_hint > CFAPI_MAX_PRIORITY_HINT {
273 return Err(invalid_callback("priority hint exceeds CFAPI maximum"));
274 }
275 if file_size > i64::MAX as u64 {
276 return Err(invalid_callback(
277 "callback file size exceeds signed CFAPI boundary",
278 ));
279 }
280 let scope = sync_root_identity.decode()?;
281 let item_key = file_identity.decode()?;
282 if item_key.scope() != &scope {
283 return Err(invalid_callback(
284 "file identity belongs to another sync-root scope",
285 ));
286 }
287 Ok(Self {
288 generation,
289 connection_key,
290 transfer_key,
291 request_key,
292 volume_guid_name,
293 volume_dos_name,
294 volume_serial_number,
295 sync_root_file_id,
296 sync_root_identity,
297 file_id,
298 file_size,
299 file_identity,
300 normalized_path,
301 priority_hint,
302 process,
303 })
304 }
305
306 #[must_use]
308 pub fn generation(&self) -> SessionGeneration {
309 self.generation
310 }
311
312 #[must_use]
314 pub const fn connection_key(&self) -> WindowsConnectionKey {
315 self.connection_key
316 }
317
318 #[must_use]
320 pub const fn transfer_key(&self) -> WindowsTransferKey {
321 self.transfer_key
322 }
323
324 #[must_use]
326 pub const fn request_key(&self) -> WindowsRequestKey {
327 self.request_key
328 }
329
330 #[must_use]
332 pub fn volume_guid_name(&self) -> Option<&std::ffi::OsStr> {
333 self.volume_guid_name.as_deref()
334 }
335
336 #[must_use]
338 pub fn volume_dos_name(&self) -> Option<&std::ffi::OsStr> {
339 self.volume_dos_name.as_deref()
340 }
341
342 #[must_use]
344 pub const fn volume_serial_number(&self) -> u32 {
345 self.volume_serial_number
346 }
347
348 #[must_use]
350 pub const fn sync_root_file_id(&self) -> i64 {
351 self.sync_root_file_id
352 }
353
354 #[must_use]
356 pub const fn sync_root_identity(&self) -> &WindowsSyncRootIdentity {
357 &self.sync_root_identity
358 }
359
360 #[must_use]
362 pub const fn file_id(&self) -> i64 {
363 self.file_id
364 }
365
366 #[must_use]
368 pub const fn file_size(&self) -> u64 {
369 self.file_size
370 }
371
372 #[must_use]
374 pub const fn file_identity(&self) -> &WindowsFileIdentity {
375 &self.file_identity
376 }
377
378 #[must_use]
380 pub fn normalized_path(&self) -> Option<&std::path::Path> {
381 self.normalized_path.as_deref()
382 }
383
384 #[must_use]
386 pub const fn priority_hint(&self) -> u8 {
387 self.priority_hint
388 }
389
390 #[must_use]
392 pub const fn process(&self) -> Option<&WindowsProcessInfoSnapshot> {
393 self.process.as_ref()
394 }
395}
396
397impl fmt::Debug for WindowsCallbackInfoSnapshot {
398 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
399 formatter
400 .debug_struct("WindowsCallbackInfoSnapshot")
401 .field("generation", &self.generation)
402 .field("connection_key", &self.connection_key)
403 .field("transfer_key", &self.transfer_key)
404 .field("request_key", &self.request_key)
405 .field("volume_serial_number", &self.volume_serial_number)
406 .field("sync_root_file_id", &self.sync_root_file_id)
407 .field("sync_root_identity", &self.sync_root_identity)
408 .field("file_id", &self.file_id)
409 .field("file_size", &self.file_size)
410 .field("file_identity", &self.file_identity)
411 .field("priority_hint", &self.priority_hint)
412 .field("has_process_info", &self.process.is_some())
413 .finish_non_exhaustive()
414 }
415}
416
417#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
419pub struct WindowsFetchDataFlags(u32);
420
421impl WindowsFetchDataFlags {
422 pub const RECOVERY: Self = Self(0x1);
424 pub const EXPLICIT_HYDRATION: Self = Self(0x2);
426
427 #[must_use]
429 pub const fn from_bits_retain(bits: u32) -> Self {
430 Self(bits)
431 }
432
433 #[must_use]
435 pub const fn bits(self) -> u32 {
436 self.0
437 }
438
439 #[must_use]
441 pub const fn contains(self, other: Self) -> bool {
442 self.0 & other.0 == other.0
443 }
444}
445
446#[derive(Debug, Clone, PartialEq, Eq)]
448pub struct WindowsFetchDataSnapshot {
449 info: WindowsCallbackInfoSnapshot,
450 flags: WindowsFetchDataFlags,
451 required_range: WindowsCallbackRange,
452 optional_range: Option<WindowsCallbackRange>,
453 last_dehydration_time: i64,
454 last_dehydration_reason: u32,
455}
456
457impl WindowsFetchDataSnapshot {
458 #[must_use]
460 pub const fn new(
461 info: WindowsCallbackInfoSnapshot,
462 flags: WindowsFetchDataFlags,
463 required_range: WindowsCallbackRange,
464 optional_range: Option<WindowsCallbackRange>,
465 last_dehydration_time: i64,
466 last_dehydration_reason: u32,
467 ) -> Self {
468 Self {
469 info,
470 flags,
471 required_range,
472 optional_range,
473 last_dehydration_time,
474 last_dehydration_reason,
475 }
476 }
477
478 #[must_use]
480 pub const fn info(&self) -> &WindowsCallbackInfoSnapshot {
481 &self.info
482 }
483
484 #[must_use]
486 pub const fn flags(&self) -> WindowsFetchDataFlags {
487 self.flags
488 }
489
490 #[must_use]
494 pub const fn is_recovery(&self) -> bool {
495 self.flags.contains(WindowsFetchDataFlags::RECOVERY)
496 }
497
498 #[must_use]
500 pub const fn required_range(&self) -> WindowsCallbackRange {
501 self.required_range
502 }
503
504 #[must_use]
506 pub const fn optional_range(&self) -> Option<WindowsCallbackRange> {
507 self.optional_range
508 }
509
510 #[must_use]
512 pub const fn last_dehydration_time(&self) -> i64 {
513 self.last_dehydration_time
514 }
515
516 #[must_use]
518 pub const fn last_dehydration_reason(&self) -> u32 {
519 self.last_dehydration_reason
520 }
521}
522
523#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
525pub struct WindowsCancelFetchDataFlags(u32);
526
527impl WindowsCancelFetchDataFlags {
528 pub const IO_TIMEOUT: Self = Self(0x1);
530 pub const IO_ABORTED: Self = Self(0x2);
532
533 #[must_use]
535 pub const fn from_bits_retain(bits: u32) -> Self {
536 Self(bits)
537 }
538
539 #[must_use]
541 pub const fn bits(self) -> u32 {
542 self.0
543 }
544
545 #[must_use]
547 pub const fn contains(self, other: Self) -> bool {
548 self.0 & other.0 == other.0
549 }
550}
551
552#[derive(Debug, Clone, PartialEq, Eq)]
554pub struct WindowsCancelFetchDataSnapshot {
555 info: WindowsCallbackInfoSnapshot,
556 flags: WindowsCancelFetchDataFlags,
557 range: WindowsCallbackRange,
558}
559
560impl WindowsCancelFetchDataSnapshot {
561 #[must_use]
563 pub const fn new(
564 info: WindowsCallbackInfoSnapshot,
565 flags: WindowsCancelFetchDataFlags,
566 range: WindowsCallbackRange,
567 ) -> Self {
568 Self { info, flags, range }
569 }
570
571 #[must_use]
573 pub const fn info(&self) -> &WindowsCallbackInfoSnapshot {
574 &self.info
575 }
576
577 #[must_use]
579 pub const fn flags(&self) -> WindowsCancelFetchDataFlags {
580 self.flags
581 }
582
583 #[must_use]
585 pub const fn range(&self) -> WindowsCallbackRange {
586 self.range
587 }
588}
589
590#[derive(Debug)]
592pub struct WindowsFetchDataRequest {
593 snapshot: WindowsFetchDataSnapshot,
594 lease: WindowsCallbackLease,
595 terminal: FetchTerminalGate,
596 #[cfg(windows)]
597 completion_authority: NativeCompletionAuthority,
598}
599
600#[derive(Debug, Clone, Default, PartialEq, Eq)]
606pub struct WindowsRestartHydration {
607 metadata: Option<crate::WindowsPlaceholderMetadata>,
608 identity: Option<WindowsFileIdentity>,
609 mark_in_sync: bool,
610}
611
612impl WindowsRestartHydration {
613 #[must_use]
615 pub const fn new() -> Self {
616 Self {
617 metadata: None,
618 identity: None,
619 mark_in_sync: false,
620 }
621 }
622
623 #[must_use]
625 pub const fn with_metadata(mut self, metadata: crate::WindowsPlaceholderMetadata) -> Self {
626 self.metadata = Some(metadata);
627 self
628 }
629
630 #[must_use]
632 pub fn with_identity(mut self, identity: WindowsFileIdentity) -> Self {
633 self.identity = Some(identity);
634 self
635 }
636
637 #[must_use]
639 pub const fn mark_in_sync(mut self) -> Self {
640 self.mark_in_sync = true;
641 self
642 }
643
644 #[must_use]
646 pub const fn metadata(&self) -> Option<crate::WindowsPlaceholderMetadata> {
647 self.metadata
648 }
649
650 #[must_use]
652 pub const fn identity(&self) -> Option<&WindowsFileIdentity> {
653 self.identity.as_ref()
654 }
655
656 #[must_use]
658 pub const fn should_mark_in_sync(&self) -> bool {
659 self.mark_in_sync
660 }
661
662 #[cfg(windows)]
663 fn validate_for(&self, info: &WindowsCallbackInfoSnapshot) -> Result<()> {
664 if let Some(identity) = &self.identity
665 && identity.decode()? != info.file_identity().decode()?
666 {
667 return Err(WindowsCloudFilesError::IdentityItemMismatch);
668 }
669 Ok(())
670 }
671}
672
673#[derive(Debug, Clone)]
676pub struct WindowsFetchDataProgressReporter {
677 correlation: WindowsFetchDataCorrelation,
678 #[cfg(windows)]
679 completion_authority: NativeCompletionAuthority,
680}
681
682impl WindowsFetchDataProgressReporter {
683 #[must_use]
685 pub const fn correlation(&self) -> WindowsFetchDataCorrelation {
686 self.correlation
687 }
688
689 #[cfg(windows)]
696 pub fn report(
697 &self,
698 registry: &WindowsFetchDataWaiterRegistry,
699 progress: crate::WindowsFetchDataProgress,
700 now: std::time::Instant,
701 ) -> Result<usize> {
702 self.completion_authority.require_native()?;
703 let updated = registry.report_progress_correlation(self.correlation, progress, now)?;
704 if updated != 0 {
705 crate::native_connection::report_fetch_progress(self.correlation, progress)?;
706 }
707 Ok(updated)
708 }
709}
710
711#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
716pub struct WindowsFetchDataCorrelation {
717 generation: SessionGeneration,
718 connection_key: WindowsConnectionKey,
719 transfer_key: WindowsTransferKey,
720 request_key: WindowsRequestKey,
721 file_id: i64,
722}
723
724impl WindowsFetchDataCorrelation {
725 pub(crate) fn from_info(info: &WindowsCallbackInfoSnapshot) -> Self {
726 Self {
727 generation: info.generation(),
728 connection_key: info.connection_key(),
729 transfer_key: info.transfer_key(),
730 request_key: info.request_key(),
731 file_id: info.file_id(),
732 }
733 }
734
735 #[must_use]
737 pub const fn generation(self) -> SessionGeneration {
738 self.generation
739 }
740
741 #[must_use]
743 pub const fn connection_key(self) -> WindowsConnectionKey {
744 self.connection_key
745 }
746
747 #[must_use]
749 pub const fn transfer_key(self) -> WindowsTransferKey {
750 self.transfer_key
751 }
752
753 #[must_use]
755 pub const fn request_key(self) -> WindowsRequestKey {
756 self.request_key
757 }
758
759 #[must_use]
761 pub const fn file_id(self) -> i64 {
762 self.file_id
763 }
764}
765
766#[derive(Debug, Clone)]
767pub(crate) struct FetchTerminalGate {
768 state: Arc<AtomicU8>,
769}
770
771impl Default for FetchTerminalGate {
772 fn default() -> Self {
773 Self {
774 state: Arc::new(AtomicU8::new(FetchTerminalState::Pending as u8)),
775 }
776 }
777}
778
779#[repr(u8)]
780#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
781enum FetchTerminalState {
782 #[default]
783 Pending,
784 NativeAttempted,
785 PlatformCancelled,
786 WatchdogTimedOut,
787}
788
789#[cfg(windows)]
790#[derive(Debug, Clone, Copy, PartialEq, Eq)]
791enum NativeCompletionAuthority {
792 Detached,
793 Native,
794}
795
796#[cfg(windows)]
797impl NativeCompletionAuthority {
798 fn require_native(self) -> Result<()> {
799 match self {
800 Self::Native => Ok(()),
801 Self::Detached => Err(WindowsCloudFilesError::MissingNativeCompletionAuthority),
802 }
803 }
804}
805
806impl FetchTerminalGate {
807 fn state(&self) -> FetchTerminalState {
808 match self.state.load(Ordering::Acquire) {
809 value if value == FetchTerminalState::NativeAttempted as u8 => {
810 FetchTerminalState::NativeAttempted
811 }
812 value if value == FetchTerminalState::PlatformCancelled as u8 => {
813 FetchTerminalState::PlatformCancelled
814 }
815 value if value == FetchTerminalState::WatchdogTimedOut as u8 => {
816 FetchTerminalState::WatchdogTimedOut
817 }
818 _ => FetchTerminalState::Pending,
819 }
820 }
821
822 fn attempted(&self) -> bool {
823 self.state() == FetchTerminalState::NativeAttempted
824 }
825
826 pub(crate) fn platform_cancelled(&self) -> bool {
827 self.state() == FetchTerminalState::PlatformCancelled
828 }
829
830 pub(crate) fn watchdog_timed_out(&self) -> bool {
831 self.state() == FetchTerminalState::WatchdogTimedOut
832 }
833
834 #[cfg(any(windows, test))]
835 pub(crate) fn begin_native(&self) -> bool {
836 self.transition(
837 FetchTerminalState::Pending,
838 FetchTerminalState::NativeAttempted,
839 ) || self.transition(
840 FetchTerminalState::WatchdogTimedOut,
841 FetchTerminalState::NativeAttempted,
842 )
843 }
844
845 pub(crate) fn cancel_by_platform(&self) -> bool {
846 self.transition(
847 FetchTerminalState::Pending,
848 FetchTerminalState::PlatformCancelled,
849 ) || self.transition(
850 FetchTerminalState::WatchdogTimedOut,
851 FetchTerminalState::PlatformCancelled,
852 )
853 }
854
855 pub(crate) fn cancel_by_watchdog(&self) -> bool {
856 self.transition(
857 FetchTerminalState::Pending,
858 FetchTerminalState::WatchdogTimedOut,
859 )
860 }
861
862 fn transition(&self, from: FetchTerminalState, to: FetchTerminalState) -> bool {
863 self.state
864 .compare_exchange(from as u8, to as u8, Ordering::AcqRel, Ordering::Acquire)
865 .is_ok()
866 }
867}
868
869impl WindowsFetchDataRequest {
870 #[must_use]
872 pub const fn snapshot(&self) -> &WindowsFetchDataSnapshot {
873 &self.snapshot
874 }
875
876 #[must_use]
878 pub const fn lease(&self) -> &WindowsCallbackLease {
879 &self.lease
880 }
881
882 #[must_use]
884 pub fn terminal_attempted(&self) -> bool {
885 self.terminal.attempted()
886 }
887
888 #[must_use]
891 pub fn progress_reporter(&self) -> WindowsFetchDataProgressReporter {
892 WindowsFetchDataProgressReporter {
893 correlation: WindowsFetchDataCorrelation::from_info(self.snapshot.info()),
894 #[cfg(windows)]
895 completion_authority: self.completion_authority,
896 }
897 }
898
899 #[cfg(windows)]
908 pub fn restart_hydration(self, replacement: &WindowsRestartHydration) -> Result<()> {
909 self.completion_authority.require_native()?;
910 replacement.validate_for(self.snapshot.info())?;
911 if !self.terminal.begin_native() {
912 return Ok(());
913 }
914 crate::native_connection::restart_fetch_hydration(&self.snapshot, replacement)
915 }
916
917 pub fn hydration_request(
926 &self,
927 revision: ContentRevision,
928 alignment: Alignment,
929 ) -> Result<HydrationRequest> {
930 let info = self.snapshot.info();
931 let key = info.file_identity().decode()?;
932 let range = hydration_byte_range(self.snapshot.required_range(), info.file_size())?;
933 let platform_alignment = Alignment::new(CFAPI_TRANSFER_ALIGNMENT_BYTES)
934 .ok_or_else(|| invalid_transfer("CFAPI transfer alignment must be non-zero"))?;
935 let alignment = platform_alignment.intersection(alignment)?;
936 Ok(HydrationRequest::range(
937 key,
938 revision,
939 info.file_size(),
940 range,
941 alignment,
942 info.generation(),
943 ))
944 }
945
946 pub async fn prepare_transfer(
956 &self,
957 coordinator: &HydrationCoordinator,
958 revision: ContentRevision,
959 alignment: Alignment,
960 ) -> Result<WindowsFetchDataTransfer> {
961 let hydration = self.hydration_request(revision.clone(), alignment)?;
962 let waiter = coordinator.request(hydration)?;
963 let response = waiter.wait().await?;
964 WindowsFetchDataTransfer::from_response(&self.snapshot, &revision, response)
965 }
966
967 pub async fn prepare_registered_transfer(
977 &mut self,
978 coordinator: &HydrationCoordinator,
979 revision: ContentRevision,
980 alignment: Alignment,
981 registry: &WindowsFetchDataWaiterRegistry,
982 ) -> Result<WindowsFetchDataPreparation> {
983 if self.terminal.platform_cancelled() {
984 return Ok(WindowsFetchDataPreparation::Cancelled);
985 }
986 if self.terminal.watchdog_timed_out() {
987 return Ok(WindowsFetchDataPreparation::TimedOut);
988 }
989 let hydration = self.hydration_request(revision.clone(), alignment)?;
990 let range = match hydration.read().read_range() {
991 ContentReadRange::Range(range) => range,
992 ContentReadRange::Whole => {
993 return Err(invalid_transfer(
994 "CFAPI fetch hydration must use one logical range",
995 ));
996 }
997 };
998 let waiter = coordinator.request(hydration)?;
999 let registration = registry.register(
1000 &self.snapshot,
1001 range,
1002 waiter.cancellation_handle(),
1003 self.terminal.clone(),
1004 )?;
1005 let response = waiter.wait().await;
1006 let platform_cancelled = registration.platform_cancelled();
1007 let watchdog_timed_out = registration.watchdog_timed_out();
1008 drop(registration);
1009 if platform_cancelled {
1010 return Ok(WindowsFetchDataPreparation::Cancelled);
1011 }
1012 if watchdog_timed_out {
1013 return Ok(WindowsFetchDataPreparation::TimedOut);
1014 }
1015 match response {
1016 Ok(response) => {
1017 WindowsFetchDataTransfer::from_response(&self.snapshot, &revision, response)
1018 .map(WindowsFetchDataPreparation::Transfer)
1019 }
1020 Err(error) => Err(error.into()),
1021 }
1022 }
1023
1024 #[cfg(windows)]
1035 pub async fn hydrate(
1036 mut self,
1037 coordinator: &HydrationCoordinator,
1038 revision: ContentRevision,
1039 alignment: Alignment,
1040 registry: &WindowsFetchDataWaiterRegistry,
1041 ) -> Result<()> {
1042 match self
1043 .prepare_registered_transfer(coordinator, revision, alignment, registry)
1044 .await
1045 {
1046 Ok(WindowsFetchDataPreparation::Transfer(transfer)) => {
1047 self.complete_transfer_inner(&transfer)
1048 }
1049 Ok(WindowsFetchDataPreparation::Cancelled) => Ok(()),
1050 Ok(WindowsFetchDataPreparation::TimedOut) => {
1051 self.finish_request_error(WindowsCloudFilesError::FetchDataWatchdogTimeout)
1052 }
1053 Err(error) => self.finish_request_error(error),
1054 }
1055 }
1056
1057 #[cfg(windows)]
1064 pub fn complete(
1065 mut self,
1066 revision: &ContentRevision,
1067 response: ContentReadResponse,
1068 ) -> Result<()> {
1069 self.complete_inner(revision, response)
1070 }
1071
1072 #[cfg(windows)]
1079 pub fn fail(mut self, failure: WindowsFetchDataFailure) -> Result<()> {
1080 self.fail_inner(failure)
1081 }
1082
1083 #[cfg(windows)]
1084 pub(crate) fn fail_inner(&mut self, failure: WindowsFetchDataFailure) -> Result<()> {
1085 self.completion_authority.require_native()?;
1086 if !self.terminal.begin_native() {
1087 return Ok(());
1088 }
1089 crate::native_connection::complete_fetch_failure(&self.snapshot, failure)
1090 }
1091
1092 #[cfg(windows)]
1093 fn complete_inner(
1094 &mut self,
1095 revision: &ContentRevision,
1096 response: ContentReadResponse,
1097 ) -> Result<()> {
1098 if self.terminal.attempted() {
1099 return Ok(());
1100 }
1101 let transfer =
1102 match WindowsFetchDataTransfer::from_response(&self.snapshot, revision, response) {
1103 Ok(transfer) => transfer,
1104 Err(error) => return self.finish_request_error(error),
1105 };
1106 self.complete_transfer_inner(&transfer)
1107 }
1108
1109 #[cfg(windows)]
1110 fn complete_transfer_inner(&mut self, transfer: &WindowsFetchDataTransfer) -> Result<()> {
1111 self.completion_authority.require_native()?;
1112 if !self.terminal.begin_native() {
1113 return Ok(());
1114 }
1115 crate::native_connection::complete_fetch_success(&self.snapshot, transfer)
1116 }
1117
1118 #[cfg(windows)]
1119 fn finish_request_error(&mut self, error: WindowsCloudFilesError) -> Result<()> {
1120 let failure = match &error {
1121 WindowsCloudFilesError::Hydration(error) => {
1122 WindowsFetchDataFailure::from_hydration_error(error)
1123 }
1124 _ => WindowsFetchDataFailure::InvalidRequest,
1125 };
1126 match self.fail_inner(failure) {
1127 Ok(()) => Err(error),
1128 Err(completion_error) => Err(completion_error),
1129 }
1130 }
1131}
1132
1133#[derive(Debug, Clone, PartialEq, Eq)]
1135pub enum WindowsFetchDataPreparation {
1136 Transfer(WindowsFetchDataTransfer),
1138 Cancelled,
1140 TimedOut,
1142}
1143
1144#[derive(Debug, Clone, PartialEq, Eq)]
1146pub struct WindowsFetchDataTransfer {
1147 offset: i64,
1148 length: i64,
1149 bytes: Bytes,
1150}
1151
1152impl WindowsFetchDataTransfer {
1153 pub fn from_response(
1159 snapshot: &WindowsFetchDataSnapshot,
1160 revision: &ContentRevision,
1161 response: ContentReadResponse,
1162 ) -> Result<Self> {
1163 let info = snapshot.info();
1164 let expected = hydration_byte_range(snapshot.required_range(), info.file_size())?;
1165 let request = aster_forge_cloud_files_core::ContentReadRequest::range(
1166 info.file_identity().decode()?,
1167 revision.clone(),
1168 info.file_size(),
1169 expected,
1170 );
1171 request.validate_response(&response)?;
1172 let (_, offset, bytes, total_size) = response.into_parts();
1173 if total_size != info.file_size() {
1174 return Err(invalid_transfer(
1175 "hydrated response size does not match callback file size",
1176 ));
1177 }
1178 let offset = i64::try_from(offset)
1179 .map_err(|_| invalid_transfer("transfer offset exceeds signed CFAPI boundary"))?;
1180 if bytes.is_empty() {
1181 return Err(invalid_transfer("successful transfer must contain bytes"));
1182 }
1183 let length = i64::try_from(bytes.len())
1184 .map_err(|_| invalid_transfer("transfer length exceeds signed CFAPI boundary"))?;
1185 Ok(Self {
1186 offset,
1187 length,
1188 bytes,
1189 })
1190 }
1191
1192 pub const fn offset(&self) -> i64 {
1194 self.offset
1195 }
1196
1197 pub fn bytes(&self) -> &[u8] {
1199 &self.bytes
1200 }
1201
1202 pub const fn length(&self) -> i64 {
1204 self.length
1205 }
1206}
1207
1208impl Drop for WindowsFetchDataRequest {
1209 fn drop(&mut self) {
1210 #[cfg(windows)]
1211 {
1212 if self.completion_authority == NativeCompletionAuthority::Native {
1213 let _ = self.fail_inner(WindowsFetchDataFailure::ProviderTerminated);
1214 }
1215 }
1216 }
1217}
1218
1219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1221pub enum WindowsFetchDataFailure {
1222 InvalidRequest,
1224 InsufficientResources,
1226 ProviderTerminated,
1228 AuthenticationFailed,
1230 AccessDenied,
1232 NotInSync,
1234 NetworkUnavailable,
1236 NotSupported,
1238 Cancelled,
1240 Unsuccessful,
1242}
1243
1244#[derive(Debug, Clone, PartialEq, Eq)]
1246pub enum WindowsObservedNotification {
1247 DehydrateCompleted {
1249 info: WindowsCallbackInfoSnapshot,
1251 flags: u32,
1253 reason: u32,
1255 },
1256 DeleteCompleted {
1258 info: WindowsCallbackInfoSnapshot,
1260 flags: u32,
1262 },
1263 RenameCompleted {
1265 info: WindowsCallbackInfoSnapshot,
1267 flags: u32,
1269 source_path: Option<PathBuf>,
1271 },
1272}
1273
1274impl WindowsObservedNotification {
1275 #[must_use]
1277 pub const fn info(&self) -> &WindowsCallbackInfoSnapshot {
1278 match self {
1279 Self::DehydrateCompleted { info, .. }
1280 | Self::DeleteCompleted { info, .. }
1281 | Self::RenameCompleted { info, .. } => info,
1282 }
1283 }
1284}
1285
1286#[derive(Debug, Clone, PartialEq, Eq)]
1288pub enum WindowsPreflightSnapshot {
1289 ValidateData {
1291 info: WindowsCallbackInfoSnapshot,
1293 flags: u32,
1295 range: WindowsCallbackRange,
1297 },
1298 Dehydrate {
1300 info: WindowsCallbackInfoSnapshot,
1302 flags: u32,
1304 reason: u32,
1306 },
1307 Delete {
1309 info: WindowsCallbackInfoSnapshot,
1311 flags: u32,
1313 },
1314 Rename {
1316 info: WindowsCallbackInfoSnapshot,
1318 flags: u32,
1320 target_path: Option<PathBuf>,
1322 },
1323}
1324
1325impl WindowsPreflightSnapshot {
1326 #[must_use]
1328 pub const fn info(&self) -> &WindowsCallbackInfoSnapshot {
1329 match self {
1330 Self::ValidateData { info, .. }
1331 | Self::Dehydrate { info, .. }
1332 | Self::Delete { info, .. }
1333 | Self::Rename { info, .. } => info,
1334 }
1335 }
1336}
1337
1338#[derive(Debug)]
1340pub struct WindowsPreflightRequest {
1341 snapshot: WindowsPreflightSnapshot,
1342 lease: WindowsCallbackLease,
1343 acknowledged: bool,
1344 #[cfg(windows)]
1345 completion_authority: NativeCompletionAuthority,
1346}
1347
1348impl WindowsPreflightRequest {
1349 #[must_use]
1351 pub const fn snapshot(&self) -> &WindowsPreflightSnapshot {
1352 &self.snapshot
1353 }
1354
1355 #[must_use]
1357 pub const fn lease(&self) -> &WindowsCallbackLease {
1358 &self.lease
1359 }
1360
1361 #[must_use]
1363 pub const fn acknowledged(&self) -> bool {
1364 self.acknowledged
1365 }
1366
1367 #[cfg(windows)]
1374 pub fn approve(mut self) -> Result<()> {
1375 self.acknowledge_inner(WindowsFetchDataFailure::Unsuccessful, true)
1376 }
1377
1378 #[cfg(windows)]
1385 pub fn deny(mut self, failure: WindowsFetchDataFailure) -> Result<()> {
1386 self.acknowledge_inner(failure, false)
1387 }
1388
1389 #[cfg(windows)]
1390 pub(crate) fn acknowledge_inner(
1391 &mut self,
1392 failure: WindowsFetchDataFailure,
1393 approved: bool,
1394 ) -> Result<()> {
1395 self.completion_authority.require_native()?;
1396 if self.acknowledged {
1397 return Ok(());
1398 }
1399 self.acknowledged = true;
1400 crate::native_connection::complete_preflight(&self.snapshot, failure, approved)
1401 }
1402}
1403
1404impl Drop for WindowsPreflightRequest {
1405 fn drop(&mut self) {
1406 #[cfg(windows)]
1407 {
1408 if self.completion_authority == NativeCompletionAuthority::Native {
1409 let _ = self.acknowledge_inner(WindowsFetchDataFailure::ProviderTerminated, false);
1410 }
1411 }
1412 }
1413}
1414
1415#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1417pub struct WindowsCallbackQueueMetrics {
1418 accepted_callbacks: u64,
1419 queued_fetch_data: u64,
1420 queued_cancel_observations: u64,
1421 queued_preflights: u64,
1422 queued_observations: u64,
1423 fetch_queue_full: u64,
1424 cancel_observation_queue_full: u64,
1425 preflight_queue_full: u64,
1426 observation_queue_full: u64,
1427 receiver_disconnected: u64,
1428 closing_rejections: u64,
1429 invalid_snapshot_rejections: u64,
1430 panic_failures: u64,
1431}
1432
1433impl WindowsCallbackQueueMetrics {
1434 #[must_use]
1435 pub const fn accepted_callbacks(self) -> u64 {
1436 self.accepted_callbacks
1437 }
1438 #[must_use]
1439 pub const fn queued_fetch_data(self) -> u64 {
1440 self.queued_fetch_data
1441 }
1442 #[must_use]
1443 pub const fn queued_cancel_observations(self) -> u64 {
1444 self.queued_cancel_observations
1445 }
1446 #[must_use]
1448 pub const fn queued_preflights(self) -> u64 {
1449 self.queued_preflights
1450 }
1451 #[must_use]
1453 pub const fn queued_observations(self) -> u64 {
1454 self.queued_observations
1455 }
1456 #[must_use]
1457 pub const fn fetch_queue_full(self) -> u64 {
1458 self.fetch_queue_full
1459 }
1460 #[must_use]
1461 pub const fn cancel_observation_queue_full(self) -> u64 {
1462 self.cancel_observation_queue_full
1463 }
1464 #[must_use]
1466 pub const fn preflight_queue_full(self) -> u64 {
1467 self.preflight_queue_full
1468 }
1469 #[must_use]
1471 pub const fn observation_queue_full(self) -> u64 {
1472 self.observation_queue_full
1473 }
1474 #[must_use]
1475 pub const fn receiver_disconnected(self) -> u64 {
1476 self.receiver_disconnected
1477 }
1478 #[must_use]
1479 pub const fn closing_rejections(self) -> u64 {
1480 self.closing_rejections
1481 }
1482 #[must_use]
1483 pub const fn invalid_snapshot_rejections(self) -> u64 {
1484 self.invalid_snapshot_rejections
1485 }
1486 #[must_use]
1487 pub const fn panic_failures(self) -> u64 {
1488 self.panic_failures
1489 }
1490}
1491
1492impl WindowsFetchDataFailure {
1493 #[must_use]
1495 pub fn from_hydration_error(error: &HydrationError) -> Self {
1496 match error {
1497 HydrationError::Cancelled => Self::Cancelled,
1498 HydrationError::Contract(_) => Self::InvalidRequest,
1499 HydrationError::InFlightLimitExceeded { .. } => Self::InsufficientResources,
1500 HydrationError::Backend(error) => match error.kind() {
1501 CloudBackendErrorKind::AuthenticationRequired => Self::AuthenticationFailed,
1502 CloudBackendErrorKind::PermissionDenied => Self::AccessDenied,
1503 CloudBackendErrorKind::NotFound
1504 | CloudBackendErrorKind::Conflict
1505 | CloudBackendErrorKind::PreconditionFailed => Self::NotInSync,
1506 CloudBackendErrorKind::InvalidRequest => Self::InvalidRequest,
1507 CloudBackendErrorKind::RateLimited
1508 | CloudBackendErrorKind::TemporarilyUnavailable => Self::NetworkUnavailable,
1509 CloudBackendErrorKind::Unsupported => Self::NotSupported,
1510 CloudBackendErrorKind::InvalidResponse | CloudBackendErrorKind::Internal => {
1511 Self::Unsuccessful
1512 }
1513 },
1514 }
1515 }
1516}
1517
1518#[derive(Debug)]
1520pub struct WindowsCancelFetchDataRequest {
1521 snapshot: WindowsCancelFetchDataSnapshot,
1522 lease: WindowsCallbackLease,
1523}
1524
1525impl WindowsCancelFetchDataRequest {
1526 #[must_use]
1528 pub const fn snapshot(&self) -> &WindowsCancelFetchDataSnapshot {
1529 &self.snapshot
1530 }
1531
1532 #[must_use]
1534 pub const fn lease(&self) -> &WindowsCallbackLease {
1535 &self.lease
1536 }
1537}
1538
1539#[derive(Debug)]
1541pub enum WindowsCallbackRequest {
1542 FetchData(WindowsFetchDataRequest),
1544 CancelFetchData(WindowsCancelFetchDataRequest),
1546 Preflight(WindowsPreflightRequest),
1548 Observation {
1550 notification: WindowsObservedNotification,
1552 lease: WindowsCallbackLease,
1554 },
1555}
1556
1557impl WindowsCallbackRequest {
1558 #[must_use]
1560 pub const fn info(&self) -> &WindowsCallbackInfoSnapshot {
1561 match self {
1562 Self::FetchData(request) => request.snapshot.info(),
1563 Self::CancelFetchData(request) => request.snapshot.info(),
1564 Self::Preflight(request) => request.snapshot.info(),
1565 Self::Observation { notification, .. } => notification.info(),
1566 }
1567 }
1568
1569 #[must_use]
1571 pub fn generation(&self) -> SessionGeneration {
1572 self.info().generation()
1573 }
1574
1575 pub fn detached_fetch_data(
1582 snapshot: WindowsFetchDataSnapshot,
1583 lease: WindowsCallbackLease,
1584 ) -> Result<Self> {
1585 validate_request_generation(snapshot.info().generation(), lease.generation())?;
1586 Ok(Self::FetchData(WindowsFetchDataRequest {
1587 snapshot,
1588 lease,
1589 terminal: FetchTerminalGate::default(),
1590 #[cfg(windows)]
1591 completion_authority: NativeCompletionAuthority::Detached,
1592 }))
1593 }
1594
1595 #[cfg(windows)]
1596 pub(crate) fn native_fetch_data(
1597 snapshot: WindowsFetchDataSnapshot,
1598 lease: WindowsCallbackLease,
1599 ) -> Result<Self> {
1600 validate_request_generation(snapshot.info().generation(), lease.generation())?;
1601 Ok(Self::FetchData(WindowsFetchDataRequest {
1602 snapshot,
1603 lease,
1604 terminal: FetchTerminalGate::default(),
1605 completion_authority: NativeCompletionAuthority::Native,
1606 }))
1607 }
1608
1609 pub fn cancel_fetch_data(
1615 snapshot: WindowsCancelFetchDataSnapshot,
1616 lease: WindowsCallbackLease,
1617 ) -> Result<Self> {
1618 validate_request_generation(snapshot.info().generation(), lease.generation())?;
1619 Ok(Self::CancelFetchData(WindowsCancelFetchDataRequest {
1620 snapshot,
1621 lease,
1622 }))
1623 }
1624
1625 pub fn detached_preflight(
1632 snapshot: WindowsPreflightSnapshot,
1633 lease: WindowsCallbackLease,
1634 ) -> Result<Self> {
1635 validate_request_generation(snapshot.info().generation(), lease.generation())?;
1636 Ok(Self::Preflight(WindowsPreflightRequest {
1637 snapshot,
1638 lease,
1639 acknowledged: false,
1640 #[cfg(windows)]
1641 completion_authority: NativeCompletionAuthority::Detached,
1642 }))
1643 }
1644
1645 #[cfg(windows)]
1646 pub(crate) fn native_preflight(
1647 snapshot: WindowsPreflightSnapshot,
1648 lease: WindowsCallbackLease,
1649 ) -> Result<Self> {
1650 validate_request_generation(snapshot.info().generation(), lease.generation())?;
1651 Ok(Self::Preflight(WindowsPreflightRequest {
1652 snapshot,
1653 lease,
1654 acknowledged: false,
1655 completion_authority: NativeCompletionAuthority::Native,
1656 }))
1657 }
1658
1659 pub fn observation(
1665 notification: WindowsObservedNotification,
1666 lease: WindowsCallbackLease,
1667 ) -> Result<Self> {
1668 validate_request_generation(notification.info().generation(), lease.generation())?;
1669 Ok(Self::Observation {
1670 notification,
1671 lease,
1672 })
1673 }
1674}
1675
1676#[derive(Debug)]
1677struct ConnectionLifecycle {
1678 state: SessionState,
1679 active_callbacks: usize,
1680}
1681
1682#[derive(Debug, Default)]
1683struct AtomicWindowsCallbackQueueMetrics {
1684 accepted_callbacks: AtomicU64,
1685 queued_fetch_data: AtomicU64,
1686 queued_cancel_observations: AtomicU64,
1687 queued_preflights: AtomicU64,
1688 queued_observations: AtomicU64,
1689 fetch_queue_full: AtomicU64,
1690 cancel_observation_queue_full: AtomicU64,
1691 preflight_queue_full: AtomicU64,
1692 observation_queue_full: AtomicU64,
1693 receiver_disconnected: AtomicU64,
1694 closing_rejections: AtomicU64,
1695 invalid_snapshot_rejections: AtomicU64,
1696 panic_failures: AtomicU64,
1697}
1698
1699impl AtomicWindowsCallbackQueueMetrics {
1700 fn snapshot(&self) -> WindowsCallbackQueueMetrics {
1701 WindowsCallbackQueueMetrics {
1702 accepted_callbacks: self.accepted_callbacks.load(Ordering::Relaxed),
1703 queued_fetch_data: self.queued_fetch_data.load(Ordering::Relaxed),
1704 queued_cancel_observations: self.queued_cancel_observations.load(Ordering::Relaxed),
1705 queued_preflights: self.queued_preflights.load(Ordering::Relaxed),
1706 queued_observations: self.queued_observations.load(Ordering::Relaxed),
1707 fetch_queue_full: self.fetch_queue_full.load(Ordering::Relaxed),
1708 cancel_observation_queue_full: self
1709 .cancel_observation_queue_full
1710 .load(Ordering::Relaxed),
1711 preflight_queue_full: self.preflight_queue_full.load(Ordering::Relaxed),
1712 observation_queue_full: self.observation_queue_full.load(Ordering::Relaxed),
1713 receiver_disconnected: self.receiver_disconnected.load(Ordering::Relaxed),
1714 closing_rejections: self.closing_rejections.load(Ordering::Relaxed),
1715 invalid_snapshot_rejections: self.invalid_snapshot_rejections.load(Ordering::Relaxed),
1716 panic_failures: self.panic_failures.load(Ordering::Relaxed),
1717 }
1718 }
1719
1720 #[cfg(any(windows, test))]
1721 fn increment(&self, event: QueueMetricEvent) {
1722 let counter = match event {
1723 QueueMetricEvent::Accepted => &self.accepted_callbacks,
1724 QueueMetricEvent::QueuedFetch => &self.queued_fetch_data,
1725 QueueMetricEvent::QueuedCancel => &self.queued_cancel_observations,
1726 QueueMetricEvent::QueuedPreflight => &self.queued_preflights,
1727 QueueMetricEvent::QueuedObservation => &self.queued_observations,
1728 QueueMetricEvent::FetchFull => &self.fetch_queue_full,
1729 QueueMetricEvent::CancelFull => &self.cancel_observation_queue_full,
1730 QueueMetricEvent::PreflightFull => &self.preflight_queue_full,
1731 QueueMetricEvent::ObservationFull => &self.observation_queue_full,
1732 QueueMetricEvent::Disconnected => &self.receiver_disconnected,
1733 QueueMetricEvent::Closing => &self.closing_rejections,
1734 QueueMetricEvent::Invalid => &self.invalid_snapshot_rejections,
1735 QueueMetricEvent::Panic => &self.panic_failures,
1736 };
1737 let _ = counter.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
1738 Some(value.saturating_add(1))
1739 });
1740 }
1741}
1742
1743#[derive(Debug)]
1744struct ConnectionSessionInner {
1745 generation: SessionGeneration,
1746 lifecycle: Mutex<ConnectionLifecycle>,
1747 queue_metrics: AtomicWindowsCallbackQueueMetrics,
1748}
1749
1750#[derive(Debug, Clone)]
1752pub struct WindowsConnectionSession {
1753 inner: Arc<ConnectionSessionInner>,
1754}
1755
1756impl WindowsConnectionSession {
1757 #[must_use]
1759 pub fn new(generation: SessionGeneration) -> Self {
1760 Self {
1761 inner: Arc::new(ConnectionSessionInner {
1762 generation,
1763 lifecycle: Mutex::new(ConnectionLifecycle {
1764 state: SessionState::Accepting,
1765 active_callbacks: 0,
1766 }),
1767 queue_metrics: AtomicWindowsCallbackQueueMetrics::default(),
1768 }),
1769 }
1770 }
1771
1772 #[must_use]
1774 pub fn generation(&self) -> SessionGeneration {
1775 self.inner.generation
1776 }
1777
1778 #[must_use]
1780 pub fn state(&self) -> SessionState {
1781 lock_lifecycle(&self.inner).state
1782 }
1783
1784 #[must_use]
1786 pub fn active_callbacks(&self) -> usize {
1787 lock_lifecycle(&self.inner).active_callbacks
1788 }
1789
1790 #[must_use]
1792 pub fn queue_metrics(&self) -> WindowsCallbackQueueMetrics {
1793 self.inner.queue_metrics.snapshot()
1794 }
1795
1796 #[cfg(any(windows, test))]
1797 fn record_queue_event(&self, event: QueueMetricEvent) {
1798 self.inner.queue_metrics.increment(event);
1799 }
1800
1801 #[cfg(any(windows, test))]
1802 pub(crate) fn record_accepted_callback(&self) {
1803 self.record_queue_event(QueueMetricEvent::Accepted);
1804 }
1805 #[cfg(any(windows, test))]
1806 pub(crate) fn record_queued_fetch(&self) {
1807 self.record_queue_event(QueueMetricEvent::QueuedFetch);
1808 }
1809 #[cfg(any(windows, test))]
1810 pub(crate) fn record_queued_cancel(&self) {
1811 self.record_queue_event(QueueMetricEvent::QueuedCancel);
1812 }
1813 #[cfg(any(windows, test))]
1814 pub(crate) fn record_fetch_queue_full(&self) {
1815 self.record_queue_event(QueueMetricEvent::FetchFull);
1816 }
1817 #[cfg(any(windows, test))]
1818 pub(crate) fn record_cancel_queue_full(&self) {
1819 self.record_queue_event(QueueMetricEvent::CancelFull);
1820 }
1821 #[cfg(any(windows, test))]
1822 pub(crate) fn record_queued_preflight(&self) {
1823 self.record_queue_event(QueueMetricEvent::QueuedPreflight);
1824 }
1825 #[cfg(any(windows, test))]
1826 pub(crate) fn record_queued_observation(&self) {
1827 self.record_queue_event(QueueMetricEvent::QueuedObservation);
1828 }
1829 #[cfg(any(windows, test))]
1830 pub(crate) fn record_preflight_queue_full(&self) {
1831 self.record_queue_event(QueueMetricEvent::PreflightFull);
1832 }
1833 #[cfg(any(windows, test))]
1834 pub(crate) fn record_observation_queue_full(&self) {
1835 self.record_queue_event(QueueMetricEvent::ObservationFull);
1836 }
1837 #[cfg(any(windows, test))]
1838 pub(crate) fn record_receiver_disconnected(&self) {
1839 self.record_queue_event(QueueMetricEvent::Disconnected);
1840 }
1841 #[cfg(any(windows, test))]
1842 pub(crate) fn record_closing_rejection(&self) {
1843 self.record_queue_event(QueueMetricEvent::Closing);
1844 }
1845 #[cfg(any(windows, test))]
1846 pub(crate) fn record_invalid_snapshot(&self) {
1847 self.record_queue_event(QueueMetricEvent::Invalid);
1848 }
1849 #[cfg(any(windows, test))]
1850 pub(crate) fn record_panic_failure(&self) {
1851 self.record_queue_event(QueueMetricEvent::Panic);
1852 }
1853
1854 pub fn begin_callback(
1860 &self,
1861 callback_generation: SessionGeneration,
1862 ) -> Result<WindowsCallbackLease> {
1863 if callback_generation != self.inner.generation {
1864 return Err(WindowsCloudFilesError::StaleConnectionGeneration {
1865 expected: self.inner.generation.get(),
1866 actual: callback_generation.get(),
1867 });
1868 }
1869 let mut lifecycle = lock_lifecycle(&self.inner);
1870 if lifecycle.state != SessionState::Accepting {
1871 return Err(WindowsCloudFilesError::ConnectionNotAccepting {
1872 state: lifecycle.state,
1873 });
1874 }
1875 lifecycle.active_callbacks = lifecycle
1876 .active_callbacks
1877 .checked_add(1)
1878 .ok_or(WindowsCloudFilesError::ActiveCallbackCountOverflow)?;
1879 drop(lifecycle);
1880 Ok(WindowsCallbackLease {
1881 session: self.inner.clone(),
1882 released: false,
1883 })
1884 }
1885
1886 #[must_use]
1890 pub fn begin_closing(&self) -> bool {
1891 let mut lifecycle = lock_lifecycle(&self.inner);
1892 if lifecycle.state == SessionState::Accepting {
1893 lifecycle.state = SessionState::Closing;
1894 true
1895 } else {
1896 false
1897 }
1898 }
1899
1900 pub fn mark_disconnected(&self) -> Result<()> {
1909 let mut lifecycle = lock_lifecycle(&self.inner);
1910 match lifecycle.state {
1911 SessionState::Closing => {
1912 lifecycle.state = if lifecycle.active_callbacks == 0 {
1913 SessionState::Closed
1914 } else {
1915 SessionState::Draining
1916 };
1917 Ok(())
1918 }
1919 SessionState::Draining | SessionState::Closed => Ok(()),
1920 SessionState::Accepting => Err(WindowsCloudFilesError::InvalidConnectionTransition {
1921 from: SessionState::Accepting,
1922 to: SessionState::Draining,
1923 }),
1924 }
1925 }
1926}
1927
1928#[cfg(any(windows, test))]
1929#[derive(Debug, Clone, Copy)]
1930enum QueueMetricEvent {
1931 Accepted,
1932 QueuedFetch,
1933 QueuedCancel,
1934 QueuedPreflight,
1935 QueuedObservation,
1936 FetchFull,
1937 CancelFull,
1938 PreflightFull,
1939 ObservationFull,
1940 Disconnected,
1941 Closing,
1942 Invalid,
1943 Panic,
1944}
1945
1946pub struct WindowsCallbackLease {
1948 session: Arc<ConnectionSessionInner>,
1949 released: bool,
1950}
1951
1952impl WindowsCallbackLease {
1953 #[must_use]
1955 pub fn generation(&self) -> SessionGeneration {
1956 self.session.generation
1957 }
1958
1959 pub fn release(mut self) {
1961 self.release_inner();
1962 }
1963
1964 #[must_use]
1966 pub fn accepts_completion(&self, session: &WindowsConnectionSession) -> bool {
1967 !self.released
1968 && Arc::ptr_eq(&self.session, &session.inner)
1969 && lock_lifecycle(&self.session).state != SessionState::Closed
1970 }
1971
1972 fn release_inner(&mut self) {
1973 if self.released {
1974 return;
1975 }
1976 let mut lifecycle = lock_lifecycle(&self.session);
1977 lifecycle.active_callbacks = lifecycle.active_callbacks.saturating_sub(1);
1978 if lifecycle.state == SessionState::Draining && lifecycle.active_callbacks == 0 {
1979 lifecycle.state = SessionState::Closed;
1980 }
1981 self.released = true;
1982 }
1983}
1984
1985impl fmt::Debug for WindowsCallbackLease {
1986 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1987 formatter
1988 .debug_struct("WindowsCallbackLease")
1989 .field("generation", &self.session.generation)
1990 .field("released", &self.released)
1991 .finish()
1992 }
1993}
1994
1995impl Drop for WindowsCallbackLease {
1996 fn drop(&mut self) {
1997 self.release_inner();
1998 }
1999}
2000
2001fn lock_lifecycle(inner: &ConnectionSessionInner) -> MutexGuard<'_, ConnectionLifecycle> {
2002 match inner.lifecycle.lock() {
2003 Ok(guard) => guard,
2004 Err(poisoned) => poisoned.into_inner(),
2005 }
2006}
2007
2008fn invalid_callback(reason: &'static str) -> WindowsCloudFilesError {
2009 WindowsCloudFilesError::InvalidCallbackSnapshot { reason }
2010}
2011
2012fn invalid_transfer(reason: &'static str) -> WindowsCloudFilesError {
2013 WindowsCloudFilesError::InvalidFetchTransfer { reason }
2014}
2015
2016fn hydration_byte_range(range: WindowsCallbackRange, file_size: u64) -> Result<ByteRange> {
2017 if !range
2018 .offset()
2019 .is_multiple_of(CFAPI_TRANSFER_ALIGNMENT_BYTES)
2020 {
2021 return Err(invalid_transfer(
2022 "CFAPI transfer offset must be aligned to 4096 bytes",
2023 ));
2024 }
2025 let length = match range.length() {
2026 WindowsCallbackRangeLength::Exact(length) => length,
2027 WindowsCallbackRangeLength::ToEnd => file_size
2028 .checked_sub(range.offset())
2029 .ok_or_else(|| invalid_transfer("CF_EOF transfer offset exceeds callback file size"))?,
2030 };
2031 if length == 0 {
2032 return Err(invalid_transfer(
2033 "CFAPI fetch range resolves to an empty successful transfer",
2034 ));
2035 }
2036 let Some(end) = range.offset().checked_add(length) else {
2037 return Err(invalid_transfer("CFAPI fetch range end exceeds u64"));
2038 };
2039 if end > file_size {
2040 return Err(invalid_transfer(
2041 "CFAPI fetch range exceeds callback file size",
2042 ));
2043 }
2044 if end != file_size && !length.is_multiple_of(CFAPI_TRANSFER_ALIGNMENT_BYTES) {
2045 return Err(invalid_transfer(
2046 "CFAPI transfer length must be aligned to 4096 bytes unless it reaches end-of-file",
2047 ));
2048 }
2049 ByteRange::new(range.offset(), length).map_err(Into::into)
2050}
2051
2052fn validate_request_generation(
2053 snapshot: SessionGeneration,
2054 lease: SessionGeneration,
2055) -> Result<()> {
2056 if snapshot == lease {
2057 return Ok(());
2058 }
2059 Err(WindowsCloudFilesError::StaleConnectionGeneration {
2060 expected: lease.get(),
2061 actual: snapshot.get(),
2062 })
2063}
2064
2065#[cfg(test)]
2066mod tests {
2067 use std::sync::atomic::Ordering;
2068
2069 use super::{FetchTerminalGate, SessionGeneration, WindowsConnectionSession};
2070
2071 #[test]
2072 fn fetch_terminal_gate_allows_exactly_one_attempt() {
2073 let gate = FetchTerminalGate::default();
2074 assert!(!gate.attempted());
2075 assert!(gate.begin_native());
2076 assert!(gate.attempted());
2077 assert!(!gate.begin_native());
2078 assert!(gate.attempted());
2079 assert!(!gate.cancel_by_platform());
2080 }
2081
2082 #[test]
2083 fn platform_cancellation_suppresses_every_native_attempt() {
2084 let gate = FetchTerminalGate::default();
2085 assert!(gate.cancel_by_platform());
2086 assert!(!gate.attempted());
2087 assert!(!gate.cancel_by_platform());
2088 assert!(!gate.begin_native());
2089 assert!(!gate.attempted());
2090 }
2091
2092 #[test]
2093 fn watchdog_timeout_preserves_one_native_failure_attempt() {
2094 let gate = FetchTerminalGate::default();
2095 assert!(gate.cancel_by_watchdog());
2096 assert!(gate.watchdog_timed_out());
2097 assert!(!gate.attempted());
2098 assert!(gate.begin_native());
2099 assert!(gate.attempted());
2100 assert!(!gate.begin_native());
2101 }
2102
2103 #[test]
2104 fn callback_queue_metrics_count_each_bounded_ingress_outcome() {
2105 let generation = SessionGeneration::new(1).expect("generation fixture should be valid");
2106 let session = WindowsConnectionSession::new(generation);
2107 session.record_accepted_callback();
2108 session.record_queued_fetch();
2109 session.record_queued_cancel();
2110 session.record_queued_preflight();
2111 session.record_queued_observation();
2112 session.record_fetch_queue_full();
2113 session.record_cancel_queue_full();
2114 session.record_preflight_queue_full();
2115 session.record_observation_queue_full();
2116 session.record_receiver_disconnected();
2117 session.record_closing_rejection();
2118 session.record_invalid_snapshot();
2119 session.record_panic_failure();
2120 let metrics = session.queue_metrics();
2121 assert_eq!(metrics.accepted_callbacks(), 1);
2122 assert_eq!(metrics.queued_fetch_data(), 1);
2123 assert_eq!(metrics.queued_cancel_observations(), 1);
2124 assert_eq!(metrics.queued_preflights(), 1);
2125 assert_eq!(metrics.queued_observations(), 1);
2126 assert_eq!(metrics.fetch_queue_full(), 1);
2127 assert_eq!(metrics.cancel_observation_queue_full(), 1);
2128 assert_eq!(metrics.preflight_queue_full(), 1);
2129 assert_eq!(metrics.observation_queue_full(), 1);
2130 assert_eq!(metrics.receiver_disconnected(), 1);
2131 assert_eq!(metrics.closing_rejections(), 1);
2132 assert_eq!(metrics.invalid_snapshot_rejections(), 1);
2133 assert_eq!(metrics.panic_failures(), 1);
2134
2135 session
2136 .inner
2137 .queue_metrics
2138 .accepted_callbacks
2139 .store(u64::MAX, Ordering::Relaxed);
2140 session.record_accepted_callback();
2141 assert_eq!(session.queue_metrics().accepted_callbacks(), u64::MAX);
2142 }
2143
2144 #[test]
2145 fn callback_queue_metrics_do_not_lose_concurrent_updates() {
2146 let generation = SessionGeneration::new(1).expect("generation fixture should be valid");
2147 let session = WindowsConnectionSession::new(generation);
2148 std::thread::scope(|scope| {
2149 for _ in 0..8 {
2150 let session = session.clone();
2151 scope.spawn(move || {
2152 for _ in 0..1_000 {
2153 session.record_accepted_callback();
2154 }
2155 });
2156 }
2157 });
2158 assert_eq!(session.queue_metrics().accepted_callbacks(), 8_000);
2159 }
2160}