field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

recovery_status.rs (4605B)


      1 use crate::{MOBILE_FFI_SCHEMA_VERSION, TeraAppError, TeraRuntime};
      2 use tera_core::runtime::product_surface::recovery_status::{
      3     NativeRecoveryReason, NativeRecoveryStatus,
      4 };
      5 
      6 #[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
      7 pub enum FfiNativeRecoveryReason {
      8     MissingParent,
      9     InvalidParent,
     10     AssociationMismatch,
     11     OutcomeUnconfirmed,
     12     Resolved,
     13 }
     14 
     15 impl From<FfiNativeRecoveryReason> for NativeRecoveryReason {
     16     fn from(value: FfiNativeRecoveryReason) -> Self {
     17         match value {
     18             FfiNativeRecoveryReason::MissingParent => Self::MissingParent,
     19             FfiNativeRecoveryReason::InvalidParent => Self::InvalidParent,
     20             FfiNativeRecoveryReason::AssociationMismatch => Self::AssociationMismatch,
     21             FfiNativeRecoveryReason::OutcomeUnconfirmed => Self::OutcomeUnconfirmed,
     22             FfiNativeRecoveryReason::Resolved => Self::Resolved,
     23         }
     24     }
     25 }
     26 
     27 impl From<NativeRecoveryReason> for FfiNativeRecoveryReason {
     28     fn from(value: NativeRecoveryReason) -> Self {
     29         match value {
     30             NativeRecoveryReason::MissingParent => Self::MissingParent,
     31             NativeRecoveryReason::InvalidParent => Self::InvalidParent,
     32             NativeRecoveryReason::AssociationMismatch => Self::AssociationMismatch,
     33             NativeRecoveryReason::OutcomeUnconfirmed => Self::OutcomeUnconfirmed,
     34             NativeRecoveryReason::Resolved => Self::Resolved,
     35         }
     36     }
     37 }
     38 
     39 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)]
     40 pub struct FfiNativeRecoveryStatus {
     41     pub schema_version: u16,
     42     pub key: String,
     43     pub reason: FfiNativeRecoveryReason,
     44     pub revision: u64,
     45     pub first_observed_unix_ms: u64,
     46     pub updated_at_unix_ms: u64,
     47 }
     48 
     49 impl From<NativeRecoveryStatus> for FfiNativeRecoveryStatus {
     50     fn from(value: NativeRecoveryStatus) -> Self {
     51         Self {
     52             schema_version: MOBILE_FFI_SCHEMA_VERSION,
     53             key: hex::encode(value.key),
     54             reason: value.reason.into(),
     55             revision: value.revision,
     56             first_observed_unix_ms: value.first_observed_unix_ms,
     57             updated_at_unix_ms: value.updated_at_unix_ms,
     58         }
     59     }
     60 }
     61 
     62 fn key(schema_version: u16, value: &str) -> Result<[u8; 32], TeraAppError> {
     63     if schema_version != MOBILE_FFI_SCHEMA_VERSION
     64         || value.len() != 64
     65         || !value
     66             .bytes()
     67             .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
     68     {
     69         return Err(TeraAppError::invalid_argument(
     70             "invalid_native_recovery_status",
     71         ));
     72     }
     73     let mut result = [0; 32];
     74     hex::decode_to_slice(value, &mut result)
     75         .map_err(|_| TeraAppError::invalid_argument("invalid_native_recovery_status"))?;
     76     Ok(result)
     77 }
     78 
     79 #[cfg_attr(not(coverage_nightly), uniffi::export(async_runtime = "tokio"))]
     80 impl TeraRuntime {
     81     pub async fn native_recovery_status(
     82         &self,
     83         schema_version: u16,
     84         transfer_key: String,
     85     ) -> Result<Option<FfiNativeRecoveryStatus>, TeraAppError> {
     86         self.inner
     87             .native_recovery_status(key(schema_version, &transfer_key)?)
     88             .await
     89             .map(|value| value.map(Into::into))
     90             .map_err(Into::into)
     91     }
     92 
     93     pub async fn report_native_recovery_status(
     94         &self,
     95         schema_version: u16,
     96         transfer_key: String,
     97         reason: FfiNativeRecoveryReason,
     98     ) -> Result<Option<FfiNativeRecoveryStatus>, TeraAppError> {
     99         self.inner
    100             .report_native_recovery_status(key(schema_version, &transfer_key)?, reason.into())
    101             .await
    102             .map(|value| value.map(Into::into))
    103             .map_err(Into::into)
    104     }
    105 }
    106 
    107 #[cfg(test)]
    108 mod tests {
    109     use super::*;
    110 
    111     #[test]
    112     fn status_key_admission_is_versioned_canonical_and_bounded() {
    113         assert_eq!(key(1, &"ab".repeat(32)).unwrap(), [0xab; 32]);
    114         for value in [
    115             "",
    116             &"A".repeat(64),
    117             &"0".repeat(63),
    118             &"0".repeat(65),
    119             &"é".repeat(32),
    120         ] {
    121             assert!(key(1, value).is_err());
    122         }
    123         assert!(key(2, &"0".repeat(64)).is_err());
    124         for reason in [
    125             FfiNativeRecoveryReason::MissingParent,
    126             FfiNativeRecoveryReason::InvalidParent,
    127             FfiNativeRecoveryReason::AssociationMismatch,
    128             FfiNativeRecoveryReason::OutcomeUnconfirmed,
    129             FfiNativeRecoveryReason::Resolved,
    130         ] {
    131             assert_eq!(
    132                 FfiNativeRecoveryReason::from(NativeRecoveryReason::from(reason)),
    133                 reason
    134             );
    135         }
    136     }
    137 }