lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }