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_schedule.rs (3738B)


      1 use crate::{TeraAppError, TeraRuntime};
      2 use tera_core::runtime::product_surface::recovery_schedule::NativeRecoverySchedule;
      3 
      4 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)]
      5 pub struct FfiNativeRecoverySchedule {
      6     pub schema_version: u16,
      7     pub author: String,
      8     pub revision: u64,
      9     pub after: Option<String>,
     10 }
     11 
     12 impl From<NativeRecoverySchedule> for FfiNativeRecoverySchedule {
     13     fn from(value: NativeRecoverySchedule) -> Self {
     14         Self {
     15             schema_version: value.schema_version,
     16             author: hex::encode(value.author),
     17             revision: value.revision,
     18             after: value.after.map(hex::encode),
     19         }
     20     }
     21 }
     22 
     23 fn digest(value: &str) -> Result<[u8; 32], TeraAppError> {
     24     let invalid = || TeraAppError::invalid_argument("invalid_recovery_schedule");
     25     if value.len() != 64
     26         || !value
     27             .bytes()
     28             .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
     29     {
     30         return Err(invalid());
     31     }
     32     let mut bytes = [0; 32];
     33     hex::decode_to_slice(value, &mut bytes).map_err(|_| invalid())?;
     34     Ok(bytes)
     35 }
     36 
     37 impl TryFrom<FfiNativeRecoverySchedule> for NativeRecoverySchedule {
     38     type Error = TeraAppError;
     39     fn try_from(value: FfiNativeRecoverySchedule) -> Result<Self, Self::Error> {
     40         if value.schema_version != 1
     41             || value.revision > i64::MAX as u64
     42             || (value.revision == 0 && value.after.is_some())
     43         {
     44             return Err(TeraAppError::invalid_argument("invalid_recovery_schedule"));
     45         }
     46         Ok(Self {
     47             schema_version: 1,
     48             author: digest(&value.author)?,
     49             revision: value.revision,
     50             after: value.after.as_deref().map(digest).transpose()?,
     51         })
     52     }
     53 }
     54 
     55 #[cfg_attr(not(coverage_nightly), uniffi::export(async_runtime = "tokio"))]
     56 impl TeraRuntime {
     57     pub async fn native_recovery_schedule(
     58         &self,
     59         schema_version: u16,
     60     ) -> Result<FfiNativeRecoverySchedule, TeraAppError> {
     61         if schema_version != 1 {
     62             return Err(TeraAppError::invalid_argument("invalid_recovery_schedule"));
     63         }
     64         self.inner
     65             .native_recovery_schedule()
     66             .await
     67             .map(Into::into)
     68             .map_err(Into::into)
     69     }
     70 
     71     pub async fn advance_native_recovery_schedule(
     72         &self,
     73         expected: FfiNativeRecoverySchedule,
     74         after: Option<String>,
     75     ) -> Result<FfiNativeRecoverySchedule, TeraAppError> {
     76         self.inner
     77             .advance_native_recovery_schedule(
     78                 expected.try_into()?,
     79                 after.as_deref().map(digest).transpose()?,
     80             )
     81             .await
     82             .map(Into::into)
     83             .map_err(Into::into)
     84     }
     85 }
     86 
     87 #[cfg(test)]
     88 mod tests {
     89     use super::*;
     90     #[test]
     91     fn validates_versioned_bounded_canonical_cursors() {
     92         let initial = FfiNativeRecoverySchedule {
     93             schema_version: 1,
     94             author: "ab".repeat(32),
     95             revision: 0,
     96             after: None,
     97         };
     98         assert!(NativeRecoverySchedule::try_from(initial.clone()).is_ok());
     99         for value in ["", &"A".repeat(64), &"0".repeat(65), &"é".repeat(32)] {
    100             assert!(digest(value).is_err());
    101         }
    102         for value in [
    103             FfiNativeRecoverySchedule {
    104                 schema_version: 2,
    105                 ..initial.clone()
    106             },
    107             FfiNativeRecoverySchedule {
    108                 revision: u64::MAX,
    109                 ..initial.clone()
    110             },
    111             FfiNativeRecoverySchedule {
    112                 after: Some("00".repeat(32)),
    113                 ..initial
    114             },
    115         ] {
    116             assert!(NativeRecoverySchedule::try_from(value).is_err());
    117         }
    118     }
    119 }