1use std::{fmt, mem::size_of, ops::BitOr};
4
5use aster_forge_cloud_files_core::{CloudNamespaceId, CloudRootId, CloudScope};
6
7use crate::{Result, WindowsCloudFilesError, WindowsFileIdentity};
8
9pub const CFAPI_SYNC_ROOT_IDENTITY_MAX_BYTES: usize = 64 * 1024;
11
12const MAGIC: &[u8; 4] = b"AFSR";
13const FORMAT_VERSION: u8 = 1;
14const HEADER_LEN: usize = MAGIC.len() + 1 + 2 * size_of::<u32>();
15const MAX_PROVIDER_TEXT_UTF16_UNITS: usize = 255;
16const PLACEHOLDER_MANAGEMENT_MIN_INTEGRATION: u32 = 0x310;
17const FULL_RESTART_HYDRATION_MIN_INTEGRATION: u32 = 0x500;
18
19#[derive(Clone, Copy, PartialEq, Eq, Hash)]
21pub struct WindowsProviderId(u128);
22
23impl WindowsProviderId {
24 pub fn new(value: u128) -> Result<Self> {
30 if value == 0 {
31 return Err(WindowsCloudFilesError::EmptyProviderId);
32 }
33 Ok(Self(value))
34 }
35
36 #[must_use]
38 pub const fn as_u128(self) -> u128 {
39 self.0
40 }
41}
42
43impl fmt::Debug for WindowsProviderId {
44 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(formatter, "WindowsProviderId({:032x})", self.0)
46 }
47}
48
49#[derive(Clone, PartialEq, Eq, Hash)]
51pub struct WindowsSyncRootIdentity(Vec<u8>);
52
53impl WindowsSyncRootIdentity {
54 pub fn encode(scope: &CloudScope) -> Result<Self> {
60 let fields = [
61 scope.namespace_id().as_str().as_bytes(),
62 scope.root_id().as_str().as_bytes(),
63 ];
64 let payload_len = fields
65 .iter()
66 .try_fold(0usize, |total, field| total.checked_add(field.len()))
67 .ok_or(WindowsCloudFilesError::SyncRootIdentityTooLarge {
68 actual: usize::MAX,
69 maximum: CFAPI_SYNC_ROOT_IDENTITY_MAX_BYTES,
70 })?;
71 let total_len = HEADER_LEN.checked_add(payload_len).ok_or(
72 WindowsCloudFilesError::SyncRootIdentityTooLarge {
73 actual: usize::MAX,
74 maximum: CFAPI_SYNC_ROOT_IDENTITY_MAX_BYTES,
75 },
76 )?;
77 validate_sync_root_identity_size(total_len)?;
78
79 let mut encoded = Vec::with_capacity(total_len);
80 encoded.extend_from_slice(MAGIC);
81 encoded.push(FORMAT_VERSION);
82 for field in fields {
83 let length = u32::try_from(field.len()).map_err(|_| {
84 WindowsCloudFilesError::SyncRootIdentityTooLarge {
85 actual: field.len(),
86 maximum: CFAPI_SYNC_ROOT_IDENTITY_MAX_BYTES,
87 }
88 })?;
89 encoded.extend_from_slice(&length.to_le_bytes());
90 }
91 for field in fields {
92 encoded.extend_from_slice(field);
93 }
94 Ok(Self(encoded))
95 }
96
97 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
103 validate_sync_root_identity_size(bytes.len())?;
104 decode_sync_root_identity(&bytes)?;
105 Ok(Self(bytes))
106 }
107
108 pub fn decode(&self) -> Result<CloudScope> {
114 decode_sync_root_identity(&self.0)
115 }
116
117 #[must_use]
119 pub fn as_bytes(&self) -> &[u8] {
120 &self.0
121 }
122
123 #[must_use]
125 pub fn len(&self) -> usize {
126 self.0.len()
127 }
128
129 #[must_use]
131 pub fn is_empty(&self) -> bool {
132 self.0.is_empty()
133 }
134
135 #[must_use]
137 pub fn into_bytes(self) -> Vec<u8> {
138 self.0
139 }
140}
141
142impl fmt::Debug for WindowsSyncRootIdentity {
143 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
144 formatter
145 .debug_struct("WindowsSyncRootIdentity")
146 .field("format_version", &FORMAT_VERSION)
147 .field("byte_len", &self.0.len())
148 .finish()
149 }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub enum WindowsHydrationPolicyPrimary {
155 Progressive,
157 Full,
159 AlwaysFull,
161}
162
163#[expect(
165 clippy::struct_excessive_bools,
166 reason = "these fields map independent native hydration policy modifiers"
167)]
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct WindowsHydrationPolicy {
170 pub primary: WindowsHydrationPolicyPrimary,
172 pub validation_required: bool,
174 pub streaming_allowed: bool,
176 pub auto_dehydration_allowed: bool,
178 pub allow_full_restart_hydration: bool,
180}
181
182impl WindowsHydrationPolicy {
183 pub fn validate(self, platform: WindowsPlatformVersion) -> Result<()> {
189 if self.validation_required && self.streaming_allowed {
190 return Err(invalid_policy(
191 "validation-required and streaming-allowed are mutually exclusive",
192 ));
193 }
194 if self.allow_full_restart_hydration
195 && platform.integration < FULL_RESTART_HYDRATION_MIN_INTEGRATION
196 {
197 return Err(WindowsCloudFilesError::UnsupportedPlatformIntegration {
198 feature: "allow-full-restart-hydration",
199 required: FULL_RESTART_HYDRATION_MIN_INTEGRATION,
200 actual: platform.integration,
201 });
202 }
203 Ok(())
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub enum WindowsPopulationPolicy {
210 Full,
212 AlwaysFull,
214}
215
216#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
218pub struct WindowsInSyncPolicy(u32);
219
220impl WindowsInSyncPolicy {
221 pub const NONE: Self = Self(0);
223 pub const FILE_CREATION_TIME: Self = Self(0x0000_0001);
225 pub const FILE_READ_ONLY: Self = Self(0x0000_0002);
227 pub const FILE_HIDDEN: Self = Self(0x0000_0004);
229 pub const FILE_SYSTEM: Self = Self(0x0000_0008);
231 pub const DIRECTORY_CREATION_TIME: Self = Self(0x0000_0010);
233 pub const DIRECTORY_READ_ONLY: Self = Self(0x0000_0020);
235 pub const DIRECTORY_HIDDEN: Self = Self(0x0000_0040);
237 pub const DIRECTORY_SYSTEM: Self = Self(0x0000_0080);
239 pub const FILE_LAST_WRITE_TIME: Self = Self(0x0000_0100);
241 pub const DIRECTORY_LAST_WRITE_TIME: Self = Self(0x0000_0200);
243 pub const PRESERVE_FOR_SYNC_ENGINE: Self = Self(0x8000_0000);
245
246 #[must_use]
248 pub const fn bits(self) -> u32 {
249 self.0
250 }
251
252 #[must_use]
254 pub const fn contains(self, other: Self) -> bool {
255 self.0 & other.0 == other.0
256 }
257}
258
259impl BitOr for WindowsInSyncPolicy {
260 type Output = Self;
261
262 fn bitor(self, other: Self) -> Self::Output {
263 Self(self.0 | other.0)
264 }
265}
266
267#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
269pub enum WindowsHardLinkPolicy {
270 #[default]
272 Forbidden,
273 Allowed,
275}
276
277#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
279pub struct WindowsPlaceholderManagementPolicy {
280 pub create_unrestricted: bool,
282 pub convert_unrestricted: bool,
284 pub update_unrestricted: bool,
286}
287
288impl WindowsPlaceholderManagementPolicy {
289 #[must_use]
291 pub const fn is_unrestricted(self) -> bool {
292 self.create_unrestricted || self.convert_unrestricted || self.update_unrestricted
293 }
294}
295
296#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub struct WindowsSyncRootPolicies {
299 pub hydration: WindowsHydrationPolicy,
301 pub population: WindowsPopulationPolicy,
303 pub in_sync: WindowsInSyncPolicy,
305 pub hard_links: WindowsHardLinkPolicy,
307 pub placeholder_management: WindowsPlaceholderManagementPolicy,
309}
310
311impl WindowsSyncRootPolicies {
312 pub fn validate(self, platform: WindowsPlatformVersion) -> Result<()> {
318 self.hydration.validate(platform)?;
319 if self.placeholder_management.is_unrestricted()
320 && platform.integration < PLACEHOLDER_MANAGEMENT_MIN_INTEGRATION
321 {
322 return Err(WindowsCloudFilesError::UnsupportedPlatformIntegration {
323 feature: "unrestricted-placeholder-management",
324 required: PLACEHOLDER_MANAGEMENT_MIN_INTEGRATION,
325 actual: platform.integration,
326 });
327 }
328 Ok(())
329 }
330}
331
332#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
334pub struct WindowsSyncRootRegistrationOptions {
335 pub update_existing: bool,
337 pub disable_on_demand_population_on_root: bool,
339 pub mark_in_sync_on_root: bool,
341}
342
343#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
345pub struct WindowsPlatformVersion {
346 pub build: u32,
348 pub revision: u32,
350 pub integration: u32,
352}
353
354#[derive(Debug, Clone, PartialEq, Eq)]
356pub struct WindowsSyncRootRegistration {
357 provider_name: String,
358 provider_version: String,
359 provider_id: WindowsProviderId,
360 sync_root_identity: WindowsSyncRootIdentity,
361 root_file_identity: WindowsFileIdentity,
362 policies: WindowsSyncRootPolicies,
363 options: WindowsSyncRootRegistrationOptions,
364}
365
366impl WindowsSyncRootRegistration {
367 pub fn new(
373 provider_name: impl Into<String>,
374 provider_version: impl Into<String>,
375 provider_id: WindowsProviderId,
376 sync_root_identity: WindowsSyncRootIdentity,
377 root_file_identity: WindowsFileIdentity,
378 policies: WindowsSyncRootPolicies,
379 options: WindowsSyncRootRegistrationOptions,
380 ) -> Result<Self> {
381 let provider_name = provider_name.into();
382 let provider_version = provider_version.into();
383 validate_registration_text("provider name", &provider_name)?;
384 validate_registration_text("provider version", &provider_version)?;
385 if root_file_identity.decode()?.scope() != &sync_root_identity.decode()? {
386 return Err(WindowsCloudFilesError::RootFileIdentityScopeMismatch);
387 }
388 Ok(Self {
389 provider_name,
390 provider_version,
391 provider_id,
392 sync_root_identity,
393 root_file_identity,
394 policies,
395 options,
396 })
397 }
398
399 pub fn validate_for_platform(&self, platform: WindowsPlatformVersion) -> Result<()> {
405 self.policies.validate(platform)
406 }
407
408 #[must_use]
410 pub fn provider_name(&self) -> &str {
411 &self.provider_name
412 }
413
414 #[must_use]
416 pub fn provider_version(&self) -> &str {
417 &self.provider_version
418 }
419
420 #[must_use]
422 pub const fn provider_id(&self) -> WindowsProviderId {
423 self.provider_id
424 }
425
426 #[must_use]
428 pub const fn sync_root_identity(&self) -> &WindowsSyncRootIdentity {
429 &self.sync_root_identity
430 }
431
432 #[must_use]
434 pub const fn root_file_identity(&self) -> &WindowsFileIdentity {
435 &self.root_file_identity
436 }
437
438 #[must_use]
440 pub const fn policies(&self) -> WindowsSyncRootPolicies {
441 self.policies
442 }
443
444 #[must_use]
446 pub const fn options(&self) -> WindowsSyncRootRegistrationOptions {
447 self.options
448 }
449
450 #[must_use]
452 pub fn into_parts(
453 self,
454 ) -> (
455 String,
456 String,
457 WindowsProviderId,
458 WindowsSyncRootIdentity,
459 WindowsFileIdentity,
460 WindowsSyncRootPolicies,
461 WindowsSyncRootRegistrationOptions,
462 ) {
463 (
464 self.provider_name,
465 self.provider_version,
466 self.provider_id,
467 self.sync_root_identity,
468 self.root_file_identity,
469 self.policies,
470 self.options,
471 )
472 }
473}
474
475fn validate_sync_root_identity_size(actual: usize) -> Result<()> {
476 if actual > CFAPI_SYNC_ROOT_IDENTITY_MAX_BYTES {
477 return Err(WindowsCloudFilesError::SyncRootIdentityTooLarge {
478 actual,
479 maximum: CFAPI_SYNC_ROOT_IDENTITY_MAX_BYTES,
480 });
481 }
482 Ok(())
483}
484
485fn decode_sync_root_identity(bytes: &[u8]) -> Result<CloudScope> {
486 if bytes.len() < HEADER_LEN {
487 return Err(invalid_sync_root_identity("identity envelope is truncated"));
488 }
489 if bytes.get(..MAGIC.len()) != Some(MAGIC.as_slice()) {
490 return Err(invalid_sync_root_identity(
491 "identity envelope magic does not match",
492 ));
493 }
494 if bytes[MAGIC.len()] != FORMAT_VERSION {
495 return Err(invalid_sync_root_identity(
496 "identity envelope version is unsupported",
497 ));
498 }
499
500 let lengths_start = MAGIC.len() + 1;
501 let mut lengths = [0usize; 2];
502 for (index, length) in lengths.iter_mut().enumerate() {
503 let start = lengths_start + index * size_of::<u32>();
504 let end = start + size_of::<u32>();
505 let raw: [u8; 4] = bytes[start..end]
506 .try_into()
507 .map_err(|_| invalid_sync_root_identity("identity length table is truncated"))?;
508 *length = usize::try_from(u32::from_le_bytes(raw)).map_err(|_| {
509 invalid_sync_root_identity("identity field length cannot be represented")
510 })?;
511 }
512 let expected_len = lengths.iter().try_fold(HEADER_LEN, |total, length| {
513 total
514 .checked_add(*length)
515 .ok_or_else(|| invalid_sync_root_identity("identity field lengths overflow"))
516 })?;
517 if expected_len != bytes.len() {
518 return Err(invalid_sync_root_identity(
519 "identity field lengths do not match the envelope",
520 ));
521 }
522
523 let namespace_end = HEADER_LEN + lengths[0];
524 let namespace = std::str::from_utf8(&bytes[HEADER_LEN..namespace_end])
525 .map_err(|_| invalid_sync_root_identity("identity field is not UTF-8"))?;
526 let root = std::str::from_utf8(&bytes[namespace_end..])
527 .map_err(|_| invalid_sync_root_identity("identity field is not UTF-8"))?;
528 Ok(CloudScope::new(
529 CloudNamespaceId::new(namespace)?,
530 CloudRootId::new(root)?,
531 ))
532}
533
534fn validate_registration_text(field: &'static str, value: &str) -> Result<()> {
535 if value.is_empty() {
536 return Err(invalid_registration_text(field, "value is empty"));
537 }
538 if value.encode_utf16().any(|unit| unit == 0) {
539 return Err(invalid_registration_text(field, "value contains a NUL"));
540 }
541 if value.encode_utf16().count() > MAX_PROVIDER_TEXT_UTF16_UNITS {
542 return Err(invalid_registration_text(
543 field,
544 "value exceeds 255 UTF-16 code units",
545 ));
546 }
547 Ok(())
548}
549
550const fn invalid_sync_root_identity(reason: &'static str) -> WindowsCloudFilesError {
551 WindowsCloudFilesError::InvalidSyncRootIdentity { reason }
552}
553
554const fn invalid_registration_text(
555 field: &'static str,
556 reason: &'static str,
557) -> WindowsCloudFilesError {
558 WindowsCloudFilesError::InvalidRegistrationString { field, reason }
559}
560
561const fn invalid_policy(reason: &'static str) -> WindowsCloudFilesError {
562 WindowsCloudFilesError::InvalidSyncRootPolicy { reason }
563}