feat(android): implement private Rust storage runtime (#90)
Dependency security audit / rustsec (push) Failing after 23s
Dependency security audit / rustsec (push) Failing after 23s
This commit is contained in:
1 parent
180851087b
commit
874c8139a4
36 files changed
+2848
-39
No files matched your search
+75
-6
@@ -5,8 +5,13 @@ import de.rfc1437.ironstorage.bridge.NativeBridgeException
|
||||
import de.rfc1437.ironstorage.bridge.NativeFailureKind
|
||||
import de.rfc1437.ironstorage.bridge.NativeRuntime
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileIcon
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateDirectories
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateRuntime
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateStorageErrorKind
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobilePrivateStorageFfiException
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileShellState
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileTab
|
||||
import de.rfc1437.ironstorage.bridge.generated.mobilePrivateStorageFailure
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -17,12 +22,13 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/** #92's native fixture integration; repository initialization is owned by #90. */
|
||||
/** Rust-owned private initialization; debug controls can switch to explicit fixtures. */
|
||||
class NativeFixtureShellRepository internal constructor(
|
||||
private val scope: CoroutineScope,
|
||||
private val load: suspend (ShellState) -> ShellSnapshot
|
||||
) : ShellRepository {
|
||||
constructor(context: Context, scope: CoroutineScope) : this(scope, nativeLoader(context))
|
||||
constructor(context: Context, scope: CoroutineScope, directories: () -> PrivateDirectories) :
|
||||
this(scope, nativeLoader(context, directories))
|
||||
private val mutableSnapshot = MutableStateFlow(ShellSnapshot(ShellState.Loading, emptyList()))
|
||||
override val snapshot: StateFlow<ShellSnapshot> = mutableSnapshot
|
||||
private val revision = AtomicLong()
|
||||
@@ -46,17 +52,52 @@ class NativeFixtureShellRepository internal constructor(
|
||||
ShellSnapshot(ShellState.Error, emptyList(), nativeFailure = error.kind.toFacade())
|
||||
}
|
||||
if (revision.get() == operation) {
|
||||
mutableSnapshot.update { result.copy(selectedTab = it.selectedTab) }
|
||||
mutableSnapshot.update {
|
||||
result.copy(
|
||||
selectedTab = if (it.pages.isEmpty()) result.selectedTab else it.selectedTab
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun nativeLoader(context: Context): suspend (ShellState) -> ShellSnapshot {
|
||||
private fun nativeLoader(
|
||||
context: Context,
|
||||
directoryProvider: () -> PrivateDirectories
|
||||
): suspend (ShellState) -> ShellSnapshot {
|
||||
val runtime = NativeRuntime(context)
|
||||
var storage: MobilePrivateRuntime? = null
|
||||
var firstLoad = true
|
||||
return { state ->
|
||||
withContext(Dispatchers.IO) {
|
||||
val shell = runtime.fixture(state.toNative())
|
||||
var recovery: StorageRecovery? = null
|
||||
val shell = if (firstLoad) {
|
||||
firstLoad = false
|
||||
try {
|
||||
val directories = directoryProvider()
|
||||
val initialized = storage ?: runtime.privateStorage(
|
||||
MobilePrivateDirectories(
|
||||
directories.files,
|
||||
directories.noBackup,
|
||||
directories.cache
|
||||
)
|
||||
)
|
||||
storage = initialized
|
||||
initialized.activate()
|
||||
recovery = initialized.status().recovery?.toFacade()
|
||||
initialized.shell()
|
||||
} catch (error: MobilePrivateStorageFfiException.Failed) {
|
||||
recovery = error.kind.toFacade()
|
||||
mobilePrivateStorageFailure(error.kind)
|
||||
} catch (_: java.io.IOException) {
|
||||
recovery = StorageRecovery.InvalidRoots
|
||||
runtime.fixture(MobileShellState.ERROR)
|
||||
mobilePrivateStorageFailure(MobilePrivateStorageErrorKind.INVALID_ROOTS)
|
||||
}
|
||||
} else {
|
||||
runtime.fixture(state.toNative())
|
||||
}
|
||||
val pages = shell.pages.map { page ->
|
||||
ShellPage(
|
||||
page.tab.toFacade(),
|
||||
@@ -68,11 +109,39 @@ private fun nativeLoader(context: Context): suspend (ShellState) -> ShellSnapsho
|
||||
}
|
||||
val actual = shell.pages.firstOrNull()?.state
|
||||
?: throw NativeBridgeException(NativeFailureKind.RustFailure)
|
||||
ShellSnapshot(actual.toFacade(), pages)
|
||||
ShellSnapshot(
|
||||
actual.toFacade(),
|
||||
pages,
|
||||
selectedTab = shell.selectedTab.toFacade(),
|
||||
storageRecovery = recovery
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MobilePrivateStorageErrorKind.toFacade(): StorageRecovery = when (this) {
|
||||
MobilePrivateStorageErrorKind.INVALID_ROOTS -> StorageRecovery.InvalidRoots
|
||||
MobilePrivateStorageErrorKind.UNSAFE_PATH -> StorageRecovery.UnsafePath
|
||||
MobilePrivateStorageErrorKind.MISSING_ROOT -> StorageRecovery.MissingRoot
|
||||
MobilePrivateStorageErrorKind.ACCESS_DENIED -> StorageRecovery.AccessDenied
|
||||
MobilePrivateStorageErrorKind.READ_ONLY -> StorageRecovery.ReadOnly
|
||||
MobilePrivateStorageErrorKind.STORAGE_FULL -> StorageRecovery.StorageFull
|
||||
MobilePrivateStorageErrorKind.BUSY -> StorageRecovery.Busy
|
||||
MobilePrivateStorageErrorKind.IO -> StorageRecovery.Io
|
||||
MobilePrivateStorageErrorKind.CORRUPT_LAYOUT -> StorageRecovery.CorruptLayout
|
||||
MobilePrivateStorageErrorKind.MIGRATION_REQUIRED -> StorageRecovery.MigrationRequired
|
||||
MobilePrivateStorageErrorKind.CORRUPT_CONFIGURATION -> StorageRecovery.CorruptConfiguration
|
||||
MobilePrivateStorageErrorKind.CONFIGURATION_MISSING_WITH_DATA ->
|
||||
StorageRecovery.ConfigurationMissingWithData
|
||||
MobilePrivateStorageErrorKind.INTERRUPTED_SETUP -> StorageRecovery.InterruptedSetup
|
||||
MobilePrivateStorageErrorKind.RESTORED_WITHOUT_KEYSTORE ->
|
||||
StorageRecovery.RestoredWithoutKeystore
|
||||
MobilePrivateStorageErrorKind.SECURE_STORAGE_LOCKED -> StorageRecovery.SecureStorageLocked
|
||||
MobilePrivateStorageErrorKind.SECURE_STORAGE_UNAVAILABLE ->
|
||||
StorageRecovery.SecureStorageUnavailable
|
||||
MobilePrivateStorageErrorKind.INVALID_SECURE_BINDING -> StorageRecovery.InvalidSecureBinding
|
||||
}
|
||||
|
||||
private fun MobileShellState.toFacade(): ShellState = when (this) {
|
||||
MobileShellState.LOADING -> ShellState.Loading
|
||||
MobileShellState.EMPTY -> ShellState.Empty
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.rfc1437.ironstorage.core
|
||||
|
||||
data class PrivateDirectories(val files: String, val noBackup: String, val cache: String) {
|
||||
override fun toString(): String = "PrivateDirectories([REDACTED])"
|
||||
}
|
||||
|
||||
enum class StorageRecovery {
|
||||
InvalidRoots,
|
||||
UnsafePath,
|
||||
MissingRoot,
|
||||
AccessDenied,
|
||||
ReadOnly,
|
||||
StorageFull,
|
||||
Busy,
|
||||
Io,
|
||||
CorruptLayout,
|
||||
MigrationRequired,
|
||||
CorruptConfiguration,
|
||||
ConfigurationMissingWithData,
|
||||
InterruptedSetup,
|
||||
RestoredWithoutKeystore,
|
||||
SecureStorageLocked,
|
||||
SecureStorageUnavailable,
|
||||
InvalidSecureBinding
|
||||
}
|
||||
@@ -44,7 +44,8 @@ data class ShellSnapshot(
|
||||
val state: ShellState,
|
||||
val pages: List<ShellPage>,
|
||||
val selectedTab: ShellTab = ShellTab.Home,
|
||||
val nativeFailure: NativeFailure? = null
|
||||
val nativeFailure: NativeFailure? = null,
|
||||
val storageRecovery: StorageRecovery? = null
|
||||
)
|
||||
|
||||
enum class NativeFailure {
|
||||
|
||||
Reference in new issue
Block a user