Implement Apple Watch TOTP interface (#57)
This commit is contained in:
@@ -10,7 +10,8 @@ use std::{
|
||||
};
|
||||
|
||||
use ironstorage::mobile_watch::{
|
||||
WatchPersistenceAction as StoragePersistenceAction, WatchRuntime as StorageWatchRuntime,
|
||||
WatchPersistenceAction as StoragePersistenceAction, WatchPresentation as StoragePresentation,
|
||||
WatchPresentationState as StoragePresentationState, WatchRuntime as StorageWatchRuntime,
|
||||
WatchSnapshotApply as StorageSnapshotApply, WatchSnapshotError as StorageWatchError,
|
||||
WatchSnapshotUpdate as StorageSnapshotUpdate, WatchTotpRecord as StorageTotpRecord,
|
||||
};
|
||||
@@ -83,10 +84,11 @@ pub struct WatchTotpRecord {
|
||||
pub code: String,
|
||||
pub period: u64,
|
||||
pub valid_until: u64,
|
||||
pub remaining: u64,
|
||||
}
|
||||
|
||||
impl From<StorageTotpRecord> for WatchTotpRecord {
|
||||
fn from(value: StorageTotpRecord) -> Self {
|
||||
impl From<&StorageTotpRecord> for WatchTotpRecord {
|
||||
fn from(value: &StorageTotpRecord) -> Self {
|
||||
Self {
|
||||
path: value.path().to_owned(),
|
||||
issuer: value.issuer().map(str::to_owned),
|
||||
@@ -95,6 +97,51 @@ impl From<StorageTotpRecord> for WatchTotpRecord {
|
||||
.expect("storage-generated TOTP codes are ASCII"),
|
||||
period: value.period(),
|
||||
valid_until: value.valid_until(),
|
||||
remaining: value.remaining(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)]
|
||||
pub enum WatchPresentationState {
|
||||
Ready,
|
||||
Empty,
|
||||
Syncing,
|
||||
Stale,
|
||||
Locked,
|
||||
Unavailable,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl From<StoragePresentationState> for WatchPresentationState {
|
||||
fn from(value: StoragePresentationState) -> Self {
|
||||
match value {
|
||||
StoragePresentationState::Ready => Self::Ready,
|
||||
StoragePresentationState::Empty => Self::Empty,
|
||||
StoragePresentationState::Syncing => Self::Syncing,
|
||||
StoragePresentationState::Stale => Self::Stale,
|
||||
StoragePresentationState::Locked => Self::Locked,
|
||||
StoragePresentationState::Unavailable => Self::Unavailable,
|
||||
StoragePresentationState::Error => Self::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)]
|
||||
pub struct WatchPresentation {
|
||||
pub state: WatchPresentationState,
|
||||
pub title: String,
|
||||
pub detail: String,
|
||||
pub records: Vec<WatchTotpRecord>,
|
||||
}
|
||||
|
||||
impl From<StoragePresentation> for WatchPresentation {
|
||||
fn from(value: StoragePresentation) -> Self {
|
||||
Self {
|
||||
state: value.state().into(),
|
||||
title: value.title().to_owned(),
|
||||
detail: value.detail().to_owned(),
|
||||
records: value.records().iter().map(WatchTotpRecord::from).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,15 +185,44 @@ impl WatchCore {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn records_at(&self, unix_seconds: u64) -> Result<Vec<WatchTotpRecord>, WatchFfiError> {
|
||||
pub fn presentation_at(&self, unix_seconds: u64) -> Result<WatchPresentation, WatchFfiError> {
|
||||
self.runtime
|
||||
.lock()
|
||||
.map_err(|_| lock_error())?
|
||||
.records_at(unix_seconds)
|
||||
.map(|records| records.into_iter().map(Into::into).collect())
|
||||
.presentation_at(unix_seconds)
|
||||
.map(Into::into)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn sync_started(&self) -> Result<(), WatchFfiError> {
|
||||
self.runtime
|
||||
.lock()
|
||||
.map_err(|_| lock_error())?
|
||||
.sync_started();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sync_finished(&self) -> Result<(), WatchFfiError> {
|
||||
self.runtime
|
||||
.lock()
|
||||
.map_err(|_| lock_error())?
|
||||
.sync_finished();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sync_unavailable(&self) -> Result<(), WatchFfiError> {
|
||||
self.runtime
|
||||
.lock()
|
||||
.map_err(|_| lock_error())?
|
||||
.sync_unavailable();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn sync_failed(&self) -> Result<(), WatchFfiError> {
|
||||
self.runtime.lock().map_err(|_| lock_error())?.sync_failed();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn protected_data_unavailable(&self) -> Result<(), WatchFfiError> {
|
||||
self.runtime
|
||||
.lock()
|
||||
@@ -183,8 +259,16 @@ mod tests {
|
||||
fn bridge_masks_records_when_protected_data_is_unavailable() {
|
||||
let core = super::watch_core();
|
||||
core.no_persisted_snapshot().expect("available Keychain");
|
||||
assert!(core.records_at(59).expect("empty snapshot").is_empty());
|
||||
assert!(
|
||||
core.presentation_at(59)
|
||||
.expect("empty snapshot")
|
||||
.records
|
||||
.is_empty()
|
||||
);
|
||||
core.protected_data_unavailable().expect("lock transition");
|
||||
assert!(core.records_at(59).is_err());
|
||||
assert_eq!(
|
||||
core.presentation_at(59).expect("locked presentation").state,
|
||||
super::WatchPresentationState::Locked
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user