backup.rs (25331B)
1 //! Versioned backup, staged restore, and member-integrity contracts. 2 3 use radroots_transport::BoxFuture; 4 use std::collections::BTreeSet; 5 6 use crate::{ 7 Error, 8 status::{IntegrityStatus, StorageStatus}, 9 }; 10 11 mod capability; 12 pub use capability::BackupCapabilityError; 13 mod restore_capability; 14 pub use restore_capability::RestoreCapabilityError; 15 16 pub const BACKUP_MEMBER_PATH_MAX_BYTES: usize = 512; 17 pub const BACKUP_MEMBER_MAX: usize = 1_024; 18 19 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 20 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 21 pub struct BackupId([u8; 16]); 22 23 #[cfg(feature = "serde")] 24 impl<'de> serde::Deserialize<'de> for BackupId { 25 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 26 where 27 D: serde::Deserializer<'de>, 28 { 29 let bytes = <[u8; 16] as serde::Deserialize>::deserialize(deserializer)?; 30 Self::new(bytes).map_err(serde::de::Error::custom) 31 } 32 } 33 34 impl BackupId { 35 pub const fn new(bytes: [u8; 16]) -> Result<Self, Error> { 36 if bytes_are_zero(&bytes) { 37 return Err(Error::InvalidBackupId); 38 } 39 Ok(Self(bytes)) 40 } 41 pub const fn as_bytes(&self) -> &[u8; 16] { 42 &self.0 43 } 44 } 45 46 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 47 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 48 pub struct BackupFormatVersion(u16); 49 50 #[cfg(feature = "serde")] 51 impl<'de> serde::Deserialize<'de> for BackupFormatVersion { 52 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 53 where 54 D: serde::Deserializer<'de>, 55 { 56 Self::new(<u16 as serde::Deserialize>::deserialize(deserializer)?) 57 .map_err(serde::de::Error::custom) 58 } 59 } 60 61 impl BackupFormatVersion { 62 pub const V1: Self = Self(1); 63 pub const fn new(value: u16) -> Result<Self, Error> { 64 if value == 0 { 65 Err(Error::InvalidBackupVersion) 66 } else { 67 Ok(Self(value)) 68 } 69 } 70 pub const fn get(self) -> u16 { 71 self.0 72 } 73 } 74 75 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 76 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 77 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 78 pub enum BackupSecretPolicy { 79 ExcludeProtectedStorage, 80 IncludeProtectedStorage, 81 } 82 83 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 84 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 85 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 86 pub enum BackupMemberKind { 87 Runtime, 88 Protected, 89 Metadata, 90 } 91 92 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 93 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 94 pub struct MemberDigest([u8; 32]); 95 96 impl MemberDigest { 97 pub const fn new(bytes: [u8; 32]) -> Self { 98 Self(bytes) 99 } 100 pub const fn as_bytes(&self) -> &[u8; 32] { 101 &self.0 102 } 103 } 104 105 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 106 #[derive(Clone, Debug, Eq, PartialEq)] 107 pub struct BackupMember { 108 relative_path: String, 109 kind: BackupMemberKind, 110 byte_length: u64, 111 sha256: MemberDigest, 112 } 113 114 #[cfg(feature = "serde")] 115 impl<'de> serde::Deserialize<'de> for BackupMember { 116 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 117 where 118 D: serde::Deserializer<'de>, 119 { 120 #[derive(serde::Deserialize)] 121 struct Wire { 122 relative_path: String, 123 kind: BackupMemberKind, 124 byte_length: u64, 125 sha256: MemberDigest, 126 } 127 128 let wire = <Wire as serde::Deserialize>::deserialize(deserializer)?; 129 Self::new(wire.relative_path, wire.kind, wire.byte_length, wire.sha256) 130 .map_err(serde::de::Error::custom) 131 } 132 } 133 134 impl BackupMember { 135 pub fn new( 136 relative_path: impl Into<String>, 137 kind: BackupMemberKind, 138 byte_length: u64, 139 sha256: MemberDigest, 140 ) -> Result<Self, Error> { 141 let relative_path = relative_path.into(); 142 if !valid_member_path(relative_path.as_str()) { 143 return Err(Error::InvalidBackupMemberPath); 144 } 145 if byte_length == 0 { 146 return Err(Error::InvalidBackupMemberLength); 147 } 148 Ok(Self { 149 relative_path, 150 kind, 151 byte_length, 152 sha256, 153 }) 154 } 155 pub fn relative_path(&self) -> &str { 156 self.relative_path.as_str() 157 } 158 pub const fn kind(&self) -> BackupMemberKind { 159 self.kind 160 } 161 pub const fn byte_length(&self) -> u64 { 162 self.byte_length 163 } 164 pub const fn sha256(&self) -> MemberDigest { 165 self.sha256 166 } 167 } 168 169 /// Self-contained immutable inventory of one backup bundle. 170 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 171 #[derive(Clone, Debug, Eq, PartialEq)] 172 pub struct BackupManifest { 173 format_version: BackupFormatVersion, 174 backup_id: BackupId, 175 created_at_unix_ms: u64, 176 secret_policy: BackupSecretPolicy, 177 total_bytes: u64, 178 members: Vec<BackupMember>, 179 } 180 181 #[cfg(feature = "serde")] 182 impl<'de> serde::Deserialize<'de> for BackupManifest { 183 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 184 where 185 D: serde::Deserializer<'de>, 186 { 187 #[derive(serde::Deserialize)] 188 struct Wire { 189 format_version: BackupFormatVersion, 190 backup_id: BackupId, 191 created_at_unix_ms: u64, 192 secret_policy: BackupSecretPolicy, 193 total_bytes: u64, 194 members: Vec<BackupMember>, 195 } 196 197 let wire = <Wire as serde::Deserialize>::deserialize(deserializer)?; 198 let expected_total_bytes = wire.total_bytes; 199 let manifest = Self::new( 200 wire.format_version, 201 wire.backup_id, 202 wire.created_at_unix_ms, 203 wire.secret_policy, 204 wire.members, 205 ) 206 .map_err(serde::de::Error::custom)?; 207 if manifest.total_bytes() != expected_total_bytes { 208 return Err(serde::de::Error::custom(Error::InvalidBackupManifest)); 209 } 210 Ok(manifest) 211 } 212 } 213 214 impl BackupManifest { 215 pub fn new( 216 format_version: BackupFormatVersion, 217 backup_id: BackupId, 218 created_at_unix_ms: u64, 219 secret_policy: BackupSecretPolicy, 220 members: Vec<BackupMember>, 221 ) -> Result<Self, Error> { 222 if created_at_unix_ms == 0 || members.is_empty() || members.len() > BACKUP_MEMBER_MAX { 223 return Err(Error::InvalidBackupManifest); 224 } 225 let mut paths = BTreeSet::new(); 226 let mut total_bytes = 0_u64; 227 for member in &members { 228 if !paths.insert(member.relative_path()) { 229 return Err(Error::DuplicateBackupMember); 230 } 231 if member.kind() == BackupMemberKind::Protected 232 && secret_policy == BackupSecretPolicy::ExcludeProtectedStorage 233 { 234 return Err(Error::BackupSecretPolicyViolation); 235 } 236 total_bytes = total_bytes 237 .checked_add(member.byte_length()) 238 .ok_or(Error::InvalidBackupManifest)?; 239 } 240 Ok(Self { 241 format_version, 242 backup_id, 243 created_at_unix_ms, 244 secret_policy, 245 total_bytes, 246 members, 247 }) 248 } 249 pub const fn format_version(&self) -> BackupFormatVersion { 250 self.format_version 251 } 252 pub const fn backup_id(&self) -> BackupId { 253 self.backup_id 254 } 255 pub const fn created_at_unix_ms(&self) -> u64 { 256 self.created_at_unix_ms 257 } 258 pub const fn secret_policy(&self) -> BackupSecretPolicy { 259 self.secret_policy 260 } 261 pub const fn total_bytes(&self) -> u64 { 262 self.total_bytes 263 } 264 pub fn members(&self) -> &[BackupMember] { 265 self.members.as_slice() 266 } 267 pub fn member(&self, path: &str) -> Option<&BackupMember> { 268 self.members 269 .iter() 270 .find(|member| member.relative_path() == path) 271 } 272 } 273 274 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 275 #[derive(Clone, Debug, Eq, PartialEq)] 276 pub struct BackupPlan { 277 backup_id: BackupId, 278 format_version: BackupFormatVersion, 279 secret_policy: BackupSecretPolicy, 280 requested_at_unix_ms: u64, 281 } 282 283 #[cfg(feature = "serde")] 284 impl<'de> serde::Deserialize<'de> for BackupPlan { 285 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 286 where 287 D: serde::Deserializer<'de>, 288 { 289 #[derive(serde::Deserialize)] 290 struct Wire { 291 backup_id: BackupId, 292 format_version: BackupFormatVersion, 293 secret_policy: BackupSecretPolicy, 294 requested_at_unix_ms: u64, 295 } 296 297 let wire = <Wire as serde::Deserialize>::deserialize(deserializer)?; 298 Self::new( 299 wire.backup_id, 300 wire.format_version, 301 wire.secret_policy, 302 wire.requested_at_unix_ms, 303 ) 304 .map_err(serde::de::Error::custom) 305 } 306 } 307 308 impl BackupPlan { 309 pub const fn new( 310 backup_id: BackupId, 311 format_version: BackupFormatVersion, 312 secret_policy: BackupSecretPolicy, 313 requested_at_unix_ms: u64, 314 ) -> Result<Self, Error> { 315 if requested_at_unix_ms == 0 { 316 return Err(Error::InvalidBackupTimestamp); 317 } 318 Ok(Self { 319 backup_id, 320 format_version, 321 secret_policy, 322 requested_at_unix_ms, 323 }) 324 } 325 pub const fn backup_id(&self) -> BackupId { 326 self.backup_id 327 } 328 pub const fn format_version(&self) -> BackupFormatVersion { 329 self.format_version 330 } 331 pub const fn secret_policy(&self) -> BackupSecretPolicy { 332 self.secret_policy 333 } 334 pub const fn requested_at_unix_ms(&self) -> u64 { 335 self.requested_at_unix_ms 336 } 337 } 338 339 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 340 #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] 341 pub struct ReliabilityRevision(u64); 342 343 #[cfg(feature = "serde")] 344 impl<'de> serde::Deserialize<'de> for ReliabilityRevision { 345 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 346 where 347 D: serde::Deserializer<'de>, 348 { 349 Self::new(<u64 as serde::Deserialize>::deserialize(deserializer)?) 350 .map_err(serde::de::Error::custom) 351 } 352 } 353 354 impl ReliabilityRevision { 355 pub const INITIAL: Self = Self(1); 356 pub const fn new(value: u64) -> Result<Self, Error> { 357 if value == 0 { 358 Err(Error::InvalidReliabilityRevision) 359 } else { 360 Ok(Self(value)) 361 } 362 } 363 pub const fn get(self) -> u64 { 364 self.0 365 } 366 fn next(self) -> Result<Self, Error> { 367 self.0 368 .checked_add(1) 369 .map(Self) 370 .ok_or(Error::CorruptReliabilityOperation) 371 } 372 } 373 374 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 375 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 376 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 377 pub enum BackupStage { 378 Planned, 379 Captured, 380 Verified, 381 Finalized, 382 Failed, 383 } 384 385 #[derive(Clone, Debug, Eq, PartialEq)] 386 pub struct BackupOperation { 387 plan: BackupPlan, 388 revision: ReliabilityRevision, 389 stage: BackupStage, 390 manifest: Option<BackupManifest>, 391 updated_at_unix_ms: u64, 392 } 393 394 impl BackupOperation { 395 pub const fn planned(plan: BackupPlan) -> Self { 396 let at = plan.requested_at_unix_ms; 397 Self { 398 plan, 399 revision: ReliabilityRevision::INITIAL, 400 stage: BackupStage::Planned, 401 manifest: None, 402 updated_at_unix_ms: at, 403 } 404 } 405 pub const fn plan(&self) -> &BackupPlan { 406 &self.plan 407 } 408 pub const fn revision(&self) -> ReliabilityRevision { 409 self.revision 410 } 411 pub const fn stage(&self) -> BackupStage { 412 self.stage 413 } 414 pub const fn manifest(&self) -> Option<&BackupManifest> { 415 self.manifest.as_ref() 416 } 417 pub const fn updated_at_unix_ms(&self) -> u64 { 418 self.updated_at_unix_ms 419 } 420 421 pub fn transition( 422 &self, 423 expected_revision: ReliabilityRevision, 424 transition: BackupTransition, 425 at_unix_ms: u64, 426 ) -> Result<Self, Error> { 427 if expected_revision != self.revision { 428 return Err(Error::ReliabilityRevisionConflict); 429 } 430 if at_unix_ms < self.updated_at_unix_ms { 431 return Err(Error::InvalidBackupTimestamp); 432 } 433 let (stage, manifest) = match (self.stage, transition) { 434 (BackupStage::Planned, BackupTransition::Captured(manifest)) => { 435 if manifest.backup_id() != self.plan.backup_id() 436 || manifest.format_version() != self.plan.format_version() 437 || manifest.secret_policy() != self.plan.secret_policy() 438 { 439 return Err(Error::BackupManifestPlanMismatch); 440 } 441 (BackupStage::Captured, Some(manifest)) 442 } 443 (BackupStage::Captured, BackupTransition::Verified) => { 444 (BackupStage::Verified, self.manifest.clone()) 445 } 446 (BackupStage::Verified, BackupTransition::Finalize) => { 447 (BackupStage::Finalized, self.manifest.clone()) 448 } 449 ( 450 BackupStage::Planned | BackupStage::Captured | BackupStage::Verified, 451 BackupTransition::Fail, 452 ) => (BackupStage::Failed, self.manifest.clone()), 453 (BackupStage::Finalized | BackupStage::Failed, _) => { 454 return Err(Error::ReliabilityOperationTerminal); 455 } 456 _ => return Err(Error::InvalidBackupTransition), 457 }; 458 Ok(Self { 459 plan: self.plan.clone(), 460 revision: self.revision.next()?, 461 stage, 462 manifest, 463 updated_at_unix_ms: at_unix_ms, 464 }) 465 } 466 } 467 468 #[derive(Clone, Debug, Eq, PartialEq)] 469 pub enum BackupTransition { 470 Captured(BackupManifest), 471 Verified, 472 Finalize, 473 Fail, 474 } 475 476 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 477 #[derive(Clone, Debug, Eq, PartialEq)] 478 pub struct RestorePlan { 479 manifest: BackupManifest, 480 accepted_secret_policy: BackupSecretPolicy, 481 requested_at_unix_ms: u64, 482 } 483 484 #[cfg(feature = "serde")] 485 impl<'de> serde::Deserialize<'de> for RestorePlan { 486 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 487 where 488 D: serde::Deserializer<'de>, 489 { 490 #[derive(serde::Deserialize)] 491 struct Wire { 492 manifest: BackupManifest, 493 accepted_secret_policy: BackupSecretPolicy, 494 requested_at_unix_ms: u64, 495 } 496 497 let wire = <Wire as serde::Deserialize>::deserialize(deserializer)?; 498 Self::new( 499 wire.manifest, 500 wire.accepted_secret_policy, 501 wire.requested_at_unix_ms, 502 ) 503 .map_err(serde::de::Error::custom) 504 } 505 } 506 507 impl RestorePlan { 508 pub fn new( 509 manifest: BackupManifest, 510 accepted_secret_policy: BackupSecretPolicy, 511 requested_at_unix_ms: u64, 512 ) -> Result<Self, Error> { 513 if requested_at_unix_ms == 0 { 514 return Err(Error::InvalidRestoreTimestamp); 515 } 516 if manifest.secret_policy() == BackupSecretPolicy::IncludeProtectedStorage 517 && accepted_secret_policy != BackupSecretPolicy::IncludeProtectedStorage 518 { 519 return Err(Error::BackupSecretPolicyViolation); 520 } 521 Ok(Self { 522 manifest, 523 accepted_secret_policy, 524 requested_at_unix_ms, 525 }) 526 } 527 pub const fn manifest(&self) -> &BackupManifest { 528 &self.manifest 529 } 530 pub const fn accepted_secret_policy(&self) -> BackupSecretPolicy { 531 self.accepted_secret_policy 532 } 533 pub const fn requested_at_unix_ms(&self) -> u64 { 534 self.requested_at_unix_ms 535 } 536 } 537 538 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 539 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 540 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 541 pub enum MemberVerification { 542 Verified, 543 Missing, 544 HashMismatch, 545 LengthMismatch, 546 UnsafePath, 547 Unexpected, 548 } 549 550 #[cfg_attr(feature = "serde", derive(serde::Serialize))] 551 #[derive(Clone, Debug, Eq, PartialEq)] 552 pub struct RestoreMemberStatus { 553 relative_path: String, 554 verification: MemberVerification, 555 } 556 557 #[cfg(feature = "serde")] 558 impl<'de> serde::Deserialize<'de> for RestoreMemberStatus { 559 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 560 where 561 D: serde::Deserializer<'de>, 562 { 563 #[derive(serde::Deserialize)] 564 struct Wire { 565 relative_path: String, 566 verification: MemberVerification, 567 } 568 569 let wire = <Wire as serde::Deserialize>::deserialize(deserializer)?; 570 Self::new(wire.relative_path, wire.verification).map_err(serde::de::Error::custom) 571 } 572 } 573 574 impl RestoreMemberStatus { 575 pub fn new( 576 relative_path: impl Into<String>, 577 verification: MemberVerification, 578 ) -> Result<Self, Error> { 579 let relative_path = relative_path.into(); 580 if !valid_member_path(relative_path.as_str()) { 581 return Err(Error::InvalidBackupMemberPath); 582 } 583 Ok(Self { 584 relative_path, 585 verification, 586 }) 587 } 588 pub fn relative_path(&self) -> &str { 589 self.relative_path.as_str() 590 } 591 pub const fn verification(&self) -> MemberVerification { 592 self.verification 593 } 594 } 595 596 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 597 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 598 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 599 pub enum RestoreStage { 600 Staging, 601 Verifying, 602 Finalizing, 603 Finalized, 604 Failed, 605 } 606 607 #[derive(Clone, Debug, Eq, PartialEq)] 608 pub struct RestoreOperation { 609 plan: RestorePlan, 610 revision: ReliabilityRevision, 611 stage: RestoreStage, 612 member_status: Vec<RestoreMemberStatus>, 613 updated_at_unix_ms: u64, 614 } 615 616 impl RestoreOperation { 617 pub const fn staging(plan: RestorePlan) -> Self { 618 let at = plan.requested_at_unix_ms; 619 Self { 620 plan, 621 revision: ReliabilityRevision::INITIAL, 622 stage: RestoreStage::Staging, 623 member_status: Vec::new(), 624 updated_at_unix_ms: at, 625 } 626 } 627 pub const fn plan(&self) -> &RestorePlan { 628 &self.plan 629 } 630 pub const fn revision(&self) -> ReliabilityRevision { 631 self.revision 632 } 633 pub const fn stage(&self) -> RestoreStage { 634 self.stage 635 } 636 pub fn member_status(&self) -> &[RestoreMemberStatus] { 637 self.member_status.as_slice() 638 } 639 640 pub fn transition( 641 &self, 642 expected_revision: ReliabilityRevision, 643 transition: RestoreTransition, 644 at_unix_ms: u64, 645 ) -> Result<Self, Error> { 646 if expected_revision != self.revision { 647 return Err(Error::ReliabilityRevisionConflict); 648 } 649 if at_unix_ms < self.updated_at_unix_ms { 650 return Err(Error::InvalidRestoreTimestamp); 651 } 652 let (stage, member_status) = match (self.stage, transition) { 653 (RestoreStage::Staging, RestoreTransition::Staged) => { 654 (RestoreStage::Verifying, Vec::new()) 655 } 656 (RestoreStage::Verifying, RestoreTransition::Verified(statuses)) => { 657 validate_restore_members(self.plan.manifest(), &statuses)?; 658 (RestoreStage::Finalizing, statuses) 659 } 660 (RestoreStage::Finalizing, RestoreTransition::Finalize) => { 661 (RestoreStage::Finalized, self.member_status.clone()) 662 } 663 ( 664 RestoreStage::Staging | RestoreStage::Verifying | RestoreStage::Finalizing, 665 RestoreTransition::Fail, 666 ) => (RestoreStage::Failed, self.member_status.clone()), 667 (RestoreStage::Finalized | RestoreStage::Failed, _) => { 668 return Err(Error::ReliabilityOperationTerminal); 669 } 670 _ => return Err(Error::InvalidRestoreTransition), 671 }; 672 Ok(Self { 673 plan: self.plan.clone(), 674 revision: self.revision.next()?, 675 stage, 676 member_status, 677 updated_at_unix_ms: at_unix_ms, 678 }) 679 } 680 } 681 682 #[derive(Clone, Debug, Eq, PartialEq)] 683 pub enum RestoreTransition { 684 Staged, 685 Verified(Vec<RestoreMemberStatus>), 686 Finalize, 687 Fail, 688 } 689 690 /// Backend-neutral reliability operations. Implementations own staging and 691 /// atomic filesystem replacement; callers receive only typed state. 692 pub trait StorageReliability: Send + Sync { 693 /// Settles writes already admitted by this owner, including work whose 694 /// caller was cancelled. The host must exclude new writes before calling 695 /// this and retain that exclusion through related inventory and capture. 696 /// This is neither a snapshot nor an ongoing maintenance reservation. 697 fn settle_backup_writes(&self) -> BoxFuture<'_, Result<(), BackupCapabilityError>> { 698 Box::pin(async { Err(BackupCapabilityError::Unsupported) }) 699 } 700 701 /// Captures actual owner-produced members. Metadata transitions alone do 702 /// not implement this capability. Hosts must separately coordinate related 703 /// application state and files; a member snapshot is not a global transaction. 704 fn capture_backup( 705 &self, 706 _plan: BackupPlan, 707 ) -> BoxFuture<'_, Result<BackupManifest, BackupCapabilityError>> { 708 Box::pin(async { Err(BackupCapabilityError::Unsupported) }) 709 } 710 711 /// Verifies the exact staged members against the supplied plan and manifest. 712 fn verify_backup( 713 &self, 714 _plan: BackupPlan, 715 _manifest: BackupManifest, 716 ) -> BoxFuture<'_, Result<(), BackupCapabilityError>> { 717 Box::pin(async { Err(BackupCapabilityError::Unsupported) }) 718 } 719 720 /// Verifies and finalizes the exact owner-managed bundle. No filesystem 721 /// pathname or database handle crosses this boundary. 722 fn finalize_backup( 723 &self, 724 _plan: BackupPlan, 725 _manifest: BackupManifest, 726 ) -> BoxFuture<'_, Result<(), BackupCapabilityError>> { 727 Box::pin(async { Err(BackupCapabilityError::Unsupported) }) 728 } 729 730 /// Stages verified retained members without replacing live state. The host 731 /// owns application identity, related media and command exclusion. Existing 732 /// staging is recovery evidence, not permission to overwrite it. 733 fn stage_restore( 734 &self, 735 _plan: RestorePlan, 736 ) -> BoxFuture<'_, Result<Vec<RestoreMemberStatus>, RestoreCapabilityError>> { 737 Box::pin(async { Err(RestoreCapabilityError::Unsupported) }) 738 } 739 740 /// Verifies staging, closes the owner and installs through its recovery 741 /// protocol. After installation begins, callers must reopen the owner even 742 /// if they lose the result. Restore never authorizes historical delivery. 743 fn finalize_restore( 744 &self, 745 _plan: RestorePlan, 746 ) -> BoxFuture<'_, Result<(), RestoreCapabilityError>> { 747 Box::pin(async { Err(RestoreCapabilityError::Unsupported) }) 748 } 749 750 fn begin_backup(&self, plan: BackupPlan) -> BoxFuture<'_, Result<BackupOperation, Error>>; 751 fn transition_backup( 752 &self, 753 backup_id: BackupId, 754 expected_revision: ReliabilityRevision, 755 transition: BackupTransition, 756 at_unix_ms: u64, 757 ) -> BoxFuture<'_, Result<BackupOperation, Error>>; 758 fn begin_restore(&self, plan: RestorePlan) -> BoxFuture<'_, Result<RestoreOperation, Error>>; 759 fn transition_restore( 760 &self, 761 backup_id: BackupId, 762 expected_revision: ReliabilityRevision, 763 transition: RestoreTransition, 764 at_unix_ms: u64, 765 ) -> BoxFuture<'_, Result<RestoreOperation, Error>>; 766 fn integrity(&self) -> BoxFuture<'_, Result<IntegrityStatus, Error>>; 767 fn status(&self) -> BoxFuture<'_, Result<StorageStatus, Error>>; 768 fn close(&self) -> BoxFuture<'_, Result<StorageStatus, Error>>; 769 } 770 771 fn validate_restore_members( 772 manifest: &BackupManifest, 773 statuses: &[RestoreMemberStatus], 774 ) -> Result<(), Error> { 775 if statuses.len() != manifest.members().len() { 776 return Err(Error::RestoreMemberVerificationFailed); 777 } 778 let mut paths = BTreeSet::new(); 779 for status in statuses { 780 if status.verification() != MemberVerification::Verified 781 || !paths.insert(status.relative_path()) 782 || manifest.member(status.relative_path()).is_none() 783 { 784 return Err(Error::RestoreMemberVerificationFailed); 785 } 786 } 787 Ok(()) 788 } 789 790 fn valid_member_path(value: &str) -> bool { 791 !value.is_empty() 792 && value.len() <= BACKUP_MEMBER_PATH_MAX_BYTES 793 && value == value.trim() 794 && !value.starts_with('/') 795 && !value.contains('\\') 796 && value.split('/').all(|part| { 797 !part.is_empty() && part != "." && part != ".." && !part.chars().any(char::is_control) 798 }) 799 } 800 801 const fn bytes_are_zero(bytes: &[u8; 16]) -> bool { 802 let mut index = 0; 803 while index < bytes.len() { 804 if bytes[index] != 0 { 805 return false; 806 } 807 index += 1; 808 } 809 true 810 }