restore.rs (4440B)
1 //! Native-owned recovery files and typed application restore commands. 2 use crate::{FfiBackupManifest, FfiBackupRequest, TeraAppError}; 3 use tera_core::runtime::restore::{ApplicationRestoreGuard, RestoreError, RestoreRequest}; 4 5 mod adapter; 6 pub(crate) use adapter::RestoreHostAdapter; 7 mod records; 8 pub use records::*; 9 10 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)] 11 pub struct FfiRestoreRequest { 12 pub attempt_id: String, 13 pub backup: FfiBackupRequest, 14 pub requested_at_ms: u64, 15 } 16 17 impl FfiRestoreRequest { 18 pub(crate) fn decode(&self) -> Result<RestoreRequest, RestoreError> { 19 RestoreRequest::new( 20 bytes(&self.attempt_id)?, 21 self.backup.decode()?, 22 self.requested_at_ms, 23 ) 24 } 25 } 26 27 impl From<&RestoreRequest> for FfiRestoreRequest { 28 fn from(value: &RestoreRequest) -> Self { 29 Self { 30 attempt_id: hex::encode(value.attempt_id()), 31 backup: value.backup().into(), 32 requested_at_ms: value.requested_at_ms(), 33 } 34 } 35 } 36 37 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)] 38 pub struct FfiRestoreGuard { 39 pub request: FfiRestoreRequest, 40 pub relative_path: String, 41 pub bytes: Vec<u8>, 42 } 43 44 impl TryFrom<ApplicationRestoreGuard> for FfiRestoreGuard { 45 type Error = RestoreError; 46 fn try_from(value: ApplicationRestoreGuard) -> Result<Self, Self::Error> { 47 Ok(Self { 48 request: value.request().into(), 49 relative_path: relative_path(value.request().backup().author()), 50 bytes: value.encode()?, 51 }) 52 } 53 } 54 55 #[uniffi::export] 56 pub fn application_restore_guard_limit() -> u64 { 57 tera_core::runtime::restore::RESTORE_GUARD_MAX_BYTES as u64 58 } 59 60 #[uniffi::export] 61 pub fn application_restore_guard_path(public_key: String) -> Result<String, TeraAppError> { 62 let author = bytes::<32>(&public_key)?; 63 // Reuse validated account construction without touching the filesystem. 64 crate::backup::validate_application_backup_request(FfiBackupRequest { 65 schema_version: tera_core::runtime::backup::APPLICATION_BACKUP_VERSION, 66 backup_id: hex::encode([1; 16]), 67 public_key, 68 source_generation: hex::encode([1; 32]), 69 requested_at_unix_ms: 1, 70 maximum_bytes: 4096, 71 })?; 72 Ok(relative_path(author)) 73 } 74 75 fn relative_path(author: [u8; 32]) -> String { 76 format!( 77 "radroots/users/{}/{}", 78 hex::encode(author), 79 tera_core::runtime::store::RESTORE_GUARD_FILENAME 80 ) 81 } 82 83 #[uniffi::export] 84 pub fn validate_application_restore_guard(bytes: Vec<u8>) -> Result<FfiRestoreGuard, TeraAppError> { 85 ApplicationRestoreGuard::decode(&bytes)? 86 .try_into() 87 .map_err(Into::into) 88 } 89 90 #[uniffi::export(callback_interface)] 91 #[async_trait::async_trait] 92 pub trait TeraRestoreHost: Send + Sync { 93 async fn require_quiescent(&self) -> Result<(), TeraAppError>; 94 async fn load_completed(&self, request: FfiRestoreRequest) -> Result<Vec<u8>, TeraAppError>; 95 async fn restore_media(&self, manifest: FfiBackupManifest) -> Result<(), TeraAppError>; 96 async fn arm_guard(&self, guard: FfiRestoreGuard) -> Result<(), TeraAppError>; 97 } 98 99 pub(crate) fn bytes<const N: usize>(value: &str) -> Result<[u8; N], RestoreError> { 100 if value.len() != N * 2 101 || !value 102 .bytes() 103 .all(|v| v.is_ascii_digit() || (b'a'..=b'f').contains(&v)) 104 { 105 return Err(RestoreError::InvalidRequest); 106 } 107 let mut bytes = [0; N]; 108 hex::decode_to_slice(value, &mut bytes).map_err(|_| RestoreError::InvalidRequest)?; 109 Ok(bytes) 110 } 111 112 impl From<RestoreError> for TeraAppError { 113 fn from(value: RestoreError) -> Self { 114 Self::failure( 115 value.code(), 116 "restore", 117 matches!(value, RestoreError::Busy | RestoreError::Unavailable), 118 &["review_restore"], 119 &value.to_string(), 120 ) 121 } 122 } 123 124 fn host_error(value: TeraAppError) -> RestoreError { 125 match value.report().code.as_str() { 126 "restore_busy" => RestoreError::Busy, 127 "restore_identity_mismatch" => RestoreError::IdentityMismatch, 128 "restore_generation_mismatch" => RestoreError::GenerationMismatch, 129 "restore_media_unavailable" => RestoreError::MediaUnavailable, 130 "restore_capacity_exceeded" => RestoreError::CapacityExceeded, 131 "restore_conflict" => RestoreError::Conflict, 132 _ => RestoreError::RecoveryRequired, 133 } 134 }