Package the Rust mobile core and generated Kotlin API for Android (#92)
Dependency security audit / rustsec (push) Failing after 24s
Dependency security audit / rustsec (push) Failing after 24s
This commit is contained in:
1 parent
e079b82b15
commit
fcb36a0684
42 files changed
+1586
-172
No files matched your search
+101
@@ -0,0 +1,101 @@
|
||||
package de.rfc1437.ironstorage.core
|
||||
|
||||
import android.content.Context
|
||||
import de.rfc1437.ironstorage.bridge.NativeBridgeException
|
||||
import de.rfc1437.ironstorage.bridge.NativeFailureKind
|
||||
import de.rfc1437.ironstorage.bridge.NativeRuntime
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileShellState
|
||||
import de.rfc1437.ironstorage.bridge.generated.MobileTab
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/** #92's native fixture integration; repository initialization is owned by #90. */
|
||||
class NativeFixtureShellRepository internal constructor(
|
||||
private val scope: CoroutineScope,
|
||||
private val load: suspend (ShellState) -> ShellSnapshot
|
||||
) : ShellRepository {
|
||||
constructor(context: Context, scope: CoroutineScope) : this(scope, nativeLoader(context))
|
||||
private val mutableSnapshot = MutableStateFlow(ShellSnapshot(ShellState.Loading, emptyList()))
|
||||
override val snapshot: StateFlow<ShellSnapshot> = mutableSnapshot
|
||||
private val revision = AtomicLong()
|
||||
private var job: Job? = null
|
||||
|
||||
init {
|
||||
showFixture(ShellState.Empty)
|
||||
}
|
||||
|
||||
override fun selectTab(tab: ShellTab) {
|
||||
mutableSnapshot.update { it.copy(selectedTab = tab) }
|
||||
}
|
||||
|
||||
fun showFixture(state: ShellState) {
|
||||
val operation = revision.incrementAndGet()
|
||||
job?.cancel()
|
||||
job = scope.launch {
|
||||
val result = try {
|
||||
load(state)
|
||||
} catch (error: NativeBridgeException) {
|
||||
ShellSnapshot(ShellState.Error, emptyList(), nativeFailure = error.kind.toFacade())
|
||||
}
|
||||
if (revision.get() == operation) {
|
||||
mutableSnapshot.update { result.copy(selectedTab = it.selectedTab) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun nativeLoader(context: Context): suspend (ShellState) -> ShellSnapshot {
|
||||
val runtime = NativeRuntime(context)
|
||||
return { state ->
|
||||
withContext(Dispatchers.IO) {
|
||||
val shell = runtime.fixture(state.toNative())
|
||||
val pages = shell.pages.map { page ->
|
||||
ShellPage(page.tab.toFacade(), page.title, page.stateTitle, page.stateDetail)
|
||||
}
|
||||
val actual = shell.pages.firstOrNull()?.state
|
||||
?: throw NativeBridgeException(NativeFailureKind.RustFailure)
|
||||
ShellSnapshot(actual.toFacade(), pages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MobileShellState.toFacade(): ShellState = when (this) {
|
||||
MobileShellState.LOADING -> ShellState.Loading
|
||||
MobileShellState.EMPTY -> ShellState.Empty
|
||||
MobileShellState.READY -> ShellState.Ready
|
||||
MobileShellState.LOCKED -> ShellState.Locked
|
||||
MobileShellState.ERROR -> ShellState.Error
|
||||
}
|
||||
|
||||
private fun ShellState.toNative(): MobileShellState = when (this) {
|
||||
ShellState.Loading -> MobileShellState.LOADING
|
||||
ShellState.Empty -> MobileShellState.EMPTY
|
||||
ShellState.Ready -> MobileShellState.READY
|
||||
ShellState.Locked -> MobileShellState.LOCKED
|
||||
ShellState.Error -> MobileShellState.ERROR
|
||||
}
|
||||
|
||||
private fun MobileTab.toFacade(): ShellTab = when (this) {
|
||||
MobileTab.HOME -> ShellTab.Home
|
||||
MobileTab.PASSWORDS -> ShellTab.Passwords
|
||||
MobileTab.SEARCH -> ShellTab.Search
|
||||
MobileTab.TOTP -> ShellTab.Totp
|
||||
MobileTab.PREFERENCES -> ShellTab.Preferences
|
||||
}
|
||||
|
||||
private fun NativeFailureKind.toFacade(): NativeFailure = when (this) {
|
||||
NativeFailureKind.UnsupportedAbi -> NativeFailure.UnsupportedAbi
|
||||
NativeFailureKind.MissingLibrary -> NativeFailure.MissingLibrary
|
||||
NativeFailureKind.ContractMismatch -> NativeFailure.ContractMismatch
|
||||
NativeFailureKind.ChecksumMismatch -> NativeFailure.ChecksumMismatch
|
||||
NativeFailureKind.LoadFailure -> NativeFailure.LoadFailure
|
||||
NativeFailureKind.RustFailure -> NativeFailure.RustFailure
|
||||
NativeFailureKind.MainThreadCall -> NativeFailure.MainThreadCall
|
||||
}
|
||||
@@ -14,9 +14,20 @@ data class ShellPage(
|
||||
data class ShellSnapshot(
|
||||
val state: ShellState,
|
||||
val pages: List<ShellPage>,
|
||||
val selectedTab: ShellTab = ShellTab.Home
|
||||
val selectedTab: ShellTab = ShellTab.Home,
|
||||
val nativeFailure: NativeFailure? = null
|
||||
)
|
||||
|
||||
enum class NativeFailure {
|
||||
UnsupportedAbi,
|
||||
MissingLibrary,
|
||||
ContractMismatch,
|
||||
ChecksumMismatch,
|
||||
LoadFailure,
|
||||
RustFailure,
|
||||
MainThreadCall
|
||||
}
|
||||
|
||||
interface ShellRepository {
|
||||
val snapshot: StateFlow<ShellSnapshot>
|
||||
fun selectTab(tab: ShellTab)
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package de.rfc1437.ironstorage.core
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||
class NativeFixtureShellRepositoryTest {
|
||||
@Test
|
||||
fun cancelledNativeCompletionCannotReplaceNewerStateOrTabSelection() = runTest {
|
||||
val pending = CompletableDeferred<Unit>()
|
||||
val repository = NativeFixtureShellRepository(backgroundScope) { state ->
|
||||
if (state == ShellState.Ready) withContext(NonCancellable) { pending.await() }
|
||||
ShellSnapshot(state, listOf(ShellPage(ShellTab.Home, "Home", state.name, "Fixture")))
|
||||
}
|
||||
runCurrent()
|
||||
repository.showFixture(ShellState.Ready)
|
||||
runCurrent()
|
||||
repository.selectTab(ShellTab.Search)
|
||||
repository.showFixture(ShellState.Locked)
|
||||
runCurrent()
|
||||
pending.complete(Unit)
|
||||
runCurrent()
|
||||
assertEquals(ShellState.Locked, repository.snapshot.value.state)
|
||||
assertEquals(ShellTab.Search, repository.snapshot.value.selectedTab)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user