backup.rs (18409B)
1 use radroots_storage::{ 2 Error, 3 backup::{ 4 BackupFormatVersion, BackupId, BackupManifest, BackupMember, BackupMemberKind, 5 BackupOperation, BackupPlan, BackupSecretPolicy, BackupStage, BackupTransition, 6 MemberDigest, MemberVerification, ReliabilityRevision, RestoreMemberStatus, 7 RestoreOperation, RestorePlan, RestoreStage, RestoreTransition, StorageReliability, 8 }, 9 status::{ 10 IntegrityHealth, IntegrityStatus, ShutdownState, StorageBackend, StorageOpenMode, 11 StorageStatus, WriterPolicy, 12 }, 13 }; 14 15 fn member(path: &str, kind: BackupMemberKind, byte: u8) -> BackupMember { 16 BackupMember::new(path, kind, 100, MemberDigest::new([byte; 32])).expect("backup member") 17 } 18 19 fn manifest(policy: BackupSecretPolicy) -> BackupManifest { 20 let mut members = vec![member( 21 "runtime/runtime.sqlite", 22 BackupMemberKind::Runtime, 23 1, 24 )]; 25 if policy == BackupSecretPolicy::IncludeProtectedStorage { 26 members.push(member( 27 "private/private.sqlite", 28 BackupMemberKind::Protected, 29 2, 30 )); 31 } 32 BackupManifest::new( 33 BackupFormatVersion::V1, 34 BackupId::new([7; 16]).expect("backup id"), 35 100, 36 policy, 37 members, 38 ) 39 .expect("backup manifest") 40 } 41 42 #[test] 43 fn manifests_reject_unsafe_duplicate_and_policy_violating_members() { 44 assert_eq!( 45 BackupMember::new( 46 "../runtime.sqlite", 47 BackupMemberKind::Runtime, 48 1, 49 MemberDigest::new([1; 32]), 50 ), 51 Err(Error::InvalidBackupMemberPath) 52 ); 53 let runtime = member("runtime.sqlite", BackupMemberKind::Runtime, 1); 54 assert_eq!( 55 BackupManifest::new( 56 BackupFormatVersion::V1, 57 BackupId::new([1; 16]).expect("id"), 58 1, 59 BackupSecretPolicy::ExcludeProtectedStorage, 60 vec![runtime.clone(), runtime], 61 ), 62 Err(Error::DuplicateBackupMember) 63 ); 64 assert_eq!( 65 BackupManifest::new( 66 BackupFormatVersion::V1, 67 BackupId::new([1; 16]).expect("id"), 68 1, 69 BackupSecretPolicy::ExcludeProtectedStorage, 70 vec![member("private.sqlite", BackupMemberKind::Protected, 2)], 71 ), 72 Err(Error::BackupSecretPolicyViolation) 73 ); 74 } 75 76 #[test] 77 fn backup_requires_capture_verification_before_atomic_finalization() { 78 let plan = BackupPlan::new( 79 BackupId::new([7; 16]).expect("backup id"), 80 BackupFormatVersion::V1, 81 BackupSecretPolicy::IncludeProtectedStorage, 82 90, 83 ) 84 .expect("backup plan"); 85 let planned = BackupOperation::planned(plan); 86 assert_eq!( 87 planned.transition( 88 ReliabilityRevision::INITIAL, 89 BackupTransition::Finalize, 90 100, 91 ), 92 Err(Error::InvalidBackupTransition) 93 ); 94 let captured = planned 95 .transition( 96 ReliabilityRevision::INITIAL, 97 BackupTransition::Captured(manifest(BackupSecretPolicy::IncludeProtectedStorage)), 98 100, 99 ) 100 .expect("captured backup"); 101 let verified = captured 102 .transition(captured.revision(), BackupTransition::Verified, 110) 103 .expect("verified backup"); 104 let finalized = verified 105 .transition(verified.revision(), BackupTransition::Finalize, 120) 106 .expect("finalized backup"); 107 assert_eq!(finalized.stage(), BackupStage::Finalized); 108 assert_eq!(finalized.manifest().expect("manifest").total_bytes(), 200); 109 assert_eq!( 110 finalized.transition(finalized.revision(), BackupTransition::Fail, 130), 111 Err(Error::ReliabilityOperationTerminal) 112 ); 113 } 114 115 #[test] 116 fn restore_stages_and_verifies_every_member_before_replacement() { 117 let manifest = manifest(BackupSecretPolicy::IncludeProtectedStorage); 118 assert_eq!( 119 RestorePlan::new( 120 manifest.clone(), 121 BackupSecretPolicy::ExcludeProtectedStorage, 122 200, 123 ), 124 Err(Error::BackupSecretPolicyViolation) 125 ); 126 let plan = RestorePlan::new( 127 manifest.clone(), 128 BackupSecretPolicy::IncludeProtectedStorage, 129 200, 130 ) 131 .expect("restore plan"); 132 let staging = RestoreOperation::staging(plan); 133 let verifying = staging 134 .transition(ReliabilityRevision::INITIAL, RestoreTransition::Staged, 210) 135 .expect("staged restore"); 136 let failed_evidence = vec![ 137 RestoreMemberStatus::new("runtime/runtime.sqlite", MemberVerification::Verified) 138 .expect("status"), 139 RestoreMemberStatus::new("private/private.sqlite", MemberVerification::HashMismatch) 140 .expect("status"), 141 ]; 142 assert_eq!( 143 verifying.transition( 144 verifying.revision(), 145 RestoreTransition::Verified(failed_evidence), 146 220, 147 ), 148 Err(Error::RestoreMemberVerificationFailed) 149 ); 150 let verified = manifest 151 .members() 152 .iter() 153 .map(|member| { 154 RestoreMemberStatus::new(member.relative_path(), MemberVerification::Verified) 155 .expect("verified status") 156 }) 157 .collect(); 158 let finalizing = verifying 159 .transition( 160 verifying.revision(), 161 RestoreTransition::Verified(verified), 162 220, 163 ) 164 .expect("verified restore"); 165 let finalized = finalizing 166 .transition(finalizing.revision(), RestoreTransition::Finalize, 230) 167 .expect("finalized restore"); 168 assert_eq!(finalized.stage(), RestoreStage::Finalized); 169 } 170 171 #[test] 172 fn integrity_and_storage_status_reject_inconsistent_runtime_claims() { 173 let integrity = 174 IntegrityStatus::new(IntegrityHealth::Healthy, Some(100), 2, 0).expect("integrity status"); 175 let status = StorageStatus::new( 176 StorageBackend::Sqlite, 177 StorageOpenMode::ReadWriteExisting, 178 WriterPolicy::AdvisoryProcessLock, 179 ShutdownState::Open, 180 integrity, 181 true, 182 5_000, 183 ) 184 .expect("storage status"); 185 assert!(status.wal_enabled()); 186 assert_eq!(status.busy_timeout_ms(), 5_000); 187 assert_eq!( 188 IntegrityStatus::new(IntegrityHealth::Healthy, Some(100), 1, 1), 189 Err(Error::InvalidIntegrityStatus) 190 ); 191 assert_eq!( 192 StorageStatus::new( 193 StorageBackend::Sqlite, 194 StorageOpenMode::ReadWriteExisting, 195 WriterPolicy::NoWriter, 196 ShutdownState::Open, 197 integrity, 198 true, 199 5_000, 200 ), 201 Err(Error::InvalidStorageStatus) 202 ); 203 } 204 205 #[test] 206 fn reliability_spi_is_dyn_compatible_and_versions_are_independent() { 207 fn accepts_dyn(_: Option<&dyn StorageReliability>) {} 208 accepts_dyn(None); 209 assert_eq!(BackupFormatVersion::V1.get(), 1); 210 assert_eq!( 211 BackupFormatVersion::new(0), 212 Err(Error::InvalidBackupVersion) 213 ); 214 assert_eq!(BackupId::new([0; 16]), Err(Error::InvalidBackupId)); 215 } 216 217 #[test] 218 fn backup_json_cannot_bypass_constructor_invariants() { 219 let valid = serde_json::to_value(manifest(BackupSecretPolicy::ExcludeProtectedStorage)) 220 .expect("serialize manifest"); 221 222 let mut unsafe_path = valid.clone(); 223 unsafe_path["members"][0]["relative_path"] = serde_json::json!("../runtime.sqlite"); 224 assert!(serde_json::from_value::<BackupManifest>(unsafe_path).is_err()); 225 226 let mut forged_total = valid.clone(); 227 forged_total["total_bytes"] = serde_json::json!(101); 228 assert!(serde_json::from_value::<BackupManifest>(forged_total).is_err()); 229 230 let mut duplicate = valid.clone(); 231 duplicate["members"] = 232 serde_json::json!([valid["members"][0].clone(), valid["members"][0].clone()]); 233 duplicate["total_bytes"] = serde_json::json!(200); 234 assert!(serde_json::from_value::<BackupManifest>(duplicate).is_err()); 235 236 assert!(serde_json::from_str::<BackupId>("[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]").is_err()); 237 assert!(serde_json::from_str::<BackupFormatVersion>("0").is_err()); 238 assert!(serde_json::from_str::<ReliabilityRevision>("0").is_err()); 239 } 240 241 #[test] 242 fn restore_json_cannot_bypass_policy_or_timestamp_invariants() { 243 let restore = RestorePlan::new( 244 manifest(BackupSecretPolicy::IncludeProtectedStorage), 245 BackupSecretPolicy::IncludeProtectedStorage, 246 200, 247 ) 248 .expect("restore plan"); 249 let valid = serde_json::to_value(restore).expect("serialize restore plan"); 250 251 let mut rejected_policy = valid.clone(); 252 rejected_policy["accepted_secret_policy"] = serde_json::json!("exclude_protected_storage"); 253 assert!(serde_json::from_value::<RestorePlan>(rejected_policy).is_err()); 254 255 let mut zero_timestamp = valid; 256 zero_timestamp["requested_at_unix_ms"] = serde_json::json!(0); 257 assert!(serde_json::from_value::<RestorePlan>(zero_timestamp).is_err()); 258 259 let unsafe_status = serde_json::json!({ 260 "relative_path": "runtime/../private.sqlite", 261 "verification": "verified" 262 }); 263 assert!(serde_json::from_value::<RestoreMemberStatus>(unsafe_status).is_err()); 264 } 265 266 #[test] 267 fn backup_value_models_cover_all_bounds_and_accessors() { 268 let backup_id = BackupId::new([1; 16]).unwrap(); 269 assert_eq!(backup_id.as_bytes(), &[1; 16]); 270 let digest = MemberDigest::new([2; 32]); 271 assert_eq!(digest.as_bytes(), &[2; 32]); 272 for path in [ 273 "", 274 "/absolute", 275 "../escape", 276 "a/../b", 277 "a/./b", 278 "a//b", 279 "a\\b", 280 " leading", 281 "bad\npath", 282 ] { 283 assert_eq!( 284 BackupMember::new(path, BackupMemberKind::Runtime, 1, digest), 285 Err(Error::InvalidBackupMemberPath) 286 ); 287 assert_eq!( 288 RestoreMemberStatus::new(path, MemberVerification::Verified), 289 Err(Error::InvalidBackupMemberPath) 290 ); 291 } 292 assert_eq!( 293 BackupMember::new("runtime.sqlite", BackupMemberKind::Runtime, 0, digest), 294 Err(Error::InvalidBackupMemberLength) 295 ); 296 let runtime = 297 BackupMember::new("runtime.sqlite", BackupMemberKind::Metadata, 10, digest).unwrap(); 298 assert_eq!(runtime.relative_path(), "runtime.sqlite"); 299 assert_eq!(runtime.kind(), BackupMemberKind::Metadata); 300 assert_eq!(runtime.byte_length(), 10); 301 assert_eq!(runtime.sha256(), digest); 302 for (created, members) in [(0, vec![runtime.clone()]), (1, vec![])] { 303 assert_eq!( 304 BackupManifest::new( 305 BackupFormatVersion::V1, 306 backup_id, 307 created, 308 BackupSecretPolicy::ExcludeProtectedStorage, 309 members, 310 ), 311 Err(Error::InvalidBackupManifest) 312 ); 313 } 314 let huge = BackupMember::new("huge", BackupMemberKind::Runtime, u64::MAX, digest).unwrap(); 315 let one = BackupMember::new("one", BackupMemberKind::Runtime, 1, digest).unwrap(); 316 assert_eq!( 317 BackupManifest::new( 318 BackupFormatVersion::V1, 319 backup_id, 320 1, 321 BackupSecretPolicy::ExcludeProtectedStorage, 322 vec![huge, one], 323 ), 324 Err(Error::InvalidBackupManifest) 325 ); 326 let manifest = manifest(BackupSecretPolicy::IncludeProtectedStorage); 327 assert_eq!(manifest.format_version(), BackupFormatVersion::V1); 328 assert_eq!(manifest.backup_id().as_bytes(), &[7; 16]); 329 assert_eq!(manifest.created_at_unix_ms(), 100); 330 assert_eq!( 331 manifest.secret_policy(), 332 BackupSecretPolicy::IncludeProtectedStorage 333 ); 334 assert_eq!(manifest.members().len(), 2); 335 assert!(manifest.member("runtime/runtime.sqlite").is_some()); 336 assert!(manifest.member("missing").is_none()); 337 338 assert_eq!( 339 BackupPlan::new( 340 backup_id, 341 BackupFormatVersion::V1, 342 BackupSecretPolicy::ExcludeProtectedStorage, 343 0, 344 ), 345 Err(Error::InvalidBackupTimestamp) 346 ); 347 let plan = BackupPlan::new( 348 backup_id, 349 BackupFormatVersion::V1, 350 BackupSecretPolicy::ExcludeProtectedStorage, 351 10, 352 ) 353 .unwrap(); 354 assert_eq!(plan.backup_id(), backup_id); 355 assert_eq!(plan.format_version(), BackupFormatVersion::V1); 356 assert_eq!( 357 plan.secret_policy(), 358 BackupSecretPolicy::ExcludeProtectedStorage 359 ); 360 assert_eq!(plan.requested_at_unix_ms(), 10); 361 assert_eq!( 362 ReliabilityRevision::new(0), 363 Err(Error::InvalidReliabilityRevision) 364 ); 365 assert_eq!(ReliabilityRevision::new(2).unwrap().get(), 2); 366 } 367 368 #[test] 369 fn backup_transition_matrix_rejects_revision_time_and_manifest_mismatch() { 370 let plan = BackupPlan::new( 371 BackupId::new([7; 16]).unwrap(), 372 BackupFormatVersion::V1, 373 BackupSecretPolicy::IncludeProtectedStorage, 374 90, 375 ) 376 .unwrap(); 377 let planned = BackupOperation::planned(plan.clone()); 378 assert_eq!(planned.plan(), &plan); 379 assert_eq!(planned.revision(), ReliabilityRevision::INITIAL); 380 assert_eq!(planned.stage(), BackupStage::Planned); 381 assert!(planned.manifest().is_none()); 382 assert_eq!(planned.updated_at_unix_ms(), 90); 383 assert_eq!( 384 planned.transition( 385 ReliabilityRevision::new(2).unwrap(), 386 BackupTransition::Fail, 387 100 388 ), 389 Err(Error::ReliabilityRevisionConflict) 390 ); 391 assert_eq!( 392 planned.transition(planned.revision(), BackupTransition::Fail, 89), 393 Err(Error::InvalidBackupTimestamp) 394 ); 395 let wrong_id = BackupManifest::new( 396 BackupFormatVersion::V1, 397 BackupId::new([8; 16]).unwrap(), 398 100, 399 BackupSecretPolicy::IncludeProtectedStorage, 400 vec![member("runtime", BackupMemberKind::Runtime, 1)], 401 ) 402 .unwrap(); 403 assert_eq!( 404 planned.transition( 405 planned.revision(), 406 BackupTransition::Captured(wrong_id), 407 100 408 ), 409 Err(Error::BackupManifestPlanMismatch) 410 ); 411 let wrong_version = BackupManifest::new( 412 BackupFormatVersion::new(2).unwrap(), 413 plan.backup_id(), 414 100, 415 plan.secret_policy(), 416 vec![member("runtime", BackupMemberKind::Runtime, 1)], 417 ) 418 .unwrap(); 419 assert_eq!( 420 planned.transition( 421 planned.revision(), 422 BackupTransition::Captured(wrong_version), 423 100 424 ), 425 Err(Error::BackupManifestPlanMismatch) 426 ); 427 let wrong_policy = manifest(BackupSecretPolicy::ExcludeProtectedStorage); 428 assert_eq!( 429 planned.transition( 430 planned.revision(), 431 BackupTransition::Captured(wrong_policy), 432 100 433 ), 434 Err(Error::BackupManifestPlanMismatch) 435 ); 436 assert_eq!( 437 planned.transition(planned.revision(), BackupTransition::Verified, 100), 438 Err(Error::InvalidBackupTransition) 439 ); 440 let failed = planned 441 .transition(planned.revision(), BackupTransition::Fail, 100) 442 .unwrap(); 443 assert_eq!(failed.stage(), BackupStage::Failed); 444 assert_eq!( 445 failed.transition(failed.revision(), BackupTransition::Fail, 101), 446 Err(Error::ReliabilityOperationTerminal) 447 ); 448 } 449 450 #[test] 451 fn restore_transition_and_member_verification_matrix_is_complete() { 452 let backup_manifest = manifest(BackupSecretPolicy::IncludeProtectedStorage); 453 assert_eq!( 454 RestorePlan::new( 455 backup_manifest.clone(), 456 BackupSecretPolicy::IncludeProtectedStorage, 457 0, 458 ), 459 Err(Error::InvalidRestoreTimestamp) 460 ); 461 let plan = RestorePlan::new( 462 backup_manifest.clone(), 463 BackupSecretPolicy::IncludeProtectedStorage, 464 200, 465 ) 466 .unwrap(); 467 assert_eq!(plan.manifest(), &backup_manifest); 468 assert_eq!( 469 plan.accepted_secret_policy(), 470 BackupSecretPolicy::IncludeProtectedStorage 471 ); 472 assert_eq!(plan.requested_at_unix_ms(), 200); 473 let staging = RestoreOperation::staging(plan.clone()); 474 assert_eq!(staging.plan(), &plan); 475 assert_eq!(staging.revision(), ReliabilityRevision::INITIAL); 476 assert_eq!(staging.stage(), RestoreStage::Staging); 477 assert!(staging.member_status().is_empty()); 478 assert_eq!( 479 staging.transition( 480 ReliabilityRevision::new(2).unwrap(), 481 RestoreTransition::Staged, 482 201 483 ), 484 Err(Error::ReliabilityRevisionConflict) 485 ); 486 assert_eq!( 487 staging.transition(staging.revision(), RestoreTransition::Staged, 199), 488 Err(Error::InvalidRestoreTimestamp) 489 ); 490 assert_eq!( 491 staging.transition(staging.revision(), RestoreTransition::Finalize, 201), 492 Err(Error::InvalidRestoreTransition) 493 ); 494 let verifying = staging 495 .transition(staging.revision(), RestoreTransition::Staged, 201) 496 .unwrap(); 497 for statuses in [ 498 vec![], 499 vec![ 500 RestoreMemberStatus::new("runtime/runtime.sqlite", MemberVerification::Verified) 501 .unwrap(), 502 RestoreMemberStatus::new("runtime/runtime.sqlite", MemberVerification::Verified) 503 .unwrap(), 504 ], 505 vec![ 506 RestoreMemberStatus::new("runtime/runtime.sqlite", MemberVerification::Verified) 507 .unwrap(), 508 RestoreMemberStatus::new("foreign", MemberVerification::Verified).unwrap(), 509 ], 510 vec![ 511 RestoreMemberStatus::new("runtime/runtime.sqlite", MemberVerification::Verified) 512 .unwrap(), 513 RestoreMemberStatus::new("private/private.sqlite", MemberVerification::Missing) 514 .unwrap(), 515 ], 516 ] { 517 assert_eq!( 518 verifying.transition( 519 verifying.revision(), 520 RestoreTransition::Verified(statuses), 521 202 522 ), 523 Err(Error::RestoreMemberVerificationFailed) 524 ); 525 } 526 let status = 527 RestoreMemberStatus::new("runtime/runtime.sqlite", MemberVerification::Verified).unwrap(); 528 assert_eq!(status.relative_path(), "runtime/runtime.sqlite"); 529 assert_eq!(status.verification(), MemberVerification::Verified); 530 let failed = verifying 531 .transition(verifying.revision(), RestoreTransition::Fail, 202) 532 .unwrap(); 533 assert_eq!(failed.stage(), RestoreStage::Failed); 534 assert_eq!( 535 failed.transition(failed.revision(), RestoreTransition::Fail, 203), 536 Err(Error::ReliabilityOperationTerminal) 537 ); 538 }