Skip to content

Commit

Permalink
Groundwork to get issuance work in Koltin Multiplatform. (#816)
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Sorotokin <[email protected]>
  • Loading branch information
sorotokin authored Dec 18, 2024
1 parent e75ec3f commit b42a116
Show file tree
Hide file tree
Showing 185 changed files with 2,035 additions and 2,248 deletions.
7 changes: 4 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ androidx-test-junit = "1.1.5"
compose-plugin = "1.7.0-rc01"
faceDetection = "16.1.6"
junit = "4.13.2"
kotlin = "2.0.21"
kotlin = "2.1.0"
kotlinx-coroutines = "1.8.1"
kotlinx-io = "0.4.0"
kotlinx-datetime = "0.6.0"
kotlinx-serialization = "1.7.0"
bouncy-castle = "1.78.1"
tink = "1.13.0"
jetbrainsKotlinJvm = "2.0.21"
ksp = "2.0.21-1.0.26"
jetbrainsKotlinJvm = "2.1.0"
ksp = "2.1.0-1.0.29"
androidx-biometrics = "1.2.0-alpha05"
volley = "1.2.1"
cbor = "0.9"
Expand Down Expand Up @@ -103,6 +103,7 @@ ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "k
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package com.android.identity.android.securearea.cloud

import android.os.Build
import com.android.identity.android.securearea.UserAuthenticationType
import com.android.identity.cbor.DataItem
import com.android.identity.crypto.EcCurve
import com.android.identity.securearea.CreateKeySettings
import com.android.identity.securearea.KeyPurpose
import com.android.identity.securearea.config.SecureAreaConfigurationCloud
import kotlinx.datetime.Instant

/**
Expand Down Expand Up @@ -79,25 +79,15 @@ class CloudCreateKeySettings private constructor(
* @param configuration configuration from a CBOR map.
* @return the builder.
*/
fun applyConfiguration(configuration: DataItem) = apply {
var userAutenticationRequired = false
var userAuthenticationTimeoutMillis = 0L
var userAuthenticationTypes = setOf<UserAuthenticationType>()
for ((key, value) in configuration.asMap) {
when (key.asTstr) {
"purposes" -> setKeyPurposes(KeyPurpose.decodeSet(value.asNumber))
"curve" -> setEcCurve(EcCurve.fromInt(value.asNumber.toInt()))
"useStrongBox" -> setUseStrongBox(value.asBoolean)
"userAuthenticationRequired" -> userAutenticationRequired = value.asBoolean
"userAuthenticationTimeoutMillis" -> userAuthenticationTimeoutMillis = value.asNumber
"userAuthenticationTypes" -> userAuthenticationTypes = UserAuthenticationType.decodeSet(value.asNumber)
"passphraseRequired" -> setPassphraseRequired(value.asBoolean)
}
}
fun applyConfiguration(configuration: SecureAreaConfigurationCloud) = apply {
setKeyPurposes(KeyPurpose.decodeSet(configuration.purposes))
setEcCurve(EcCurve.fromInt(configuration.curve))
setUseStrongBox(configuration.useStrongBox)
setPassphraseRequired(configuration.passphraseRequired)
setUserAuthenticationRequired(
userAutenticationRequired,
userAuthenticationTimeoutMillis,
userAuthenticationTypes
configuration.userAuthenticationRequired,
configuration.userAuthenticationTimeoutMillis,
UserAuthenticationType.decodeSet(configuration.userAuthenticationTypes)
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.android.identity.android.securearea

import android.os.Build
import com.android.identity.cbor.DataItem
import com.android.identity.crypto.EcCurve
import com.android.identity.securearea.CreateKeySettings
import com.android.identity.securearea.KeyPurpose
import com.android.identity.securearea.config.SecureAreaConfigurationAndroidKeystore
import kotlinx.datetime.Instant

/**
Expand Down Expand Up @@ -83,24 +83,14 @@ class AndroidKeystoreCreateKeySettings private constructor(
* @param configuration configuration from a CBOR map.
* @return the builder.
*/
fun applyConfiguration(configuration: DataItem) = apply {
var userAutenticationRequired = false
var userAuthenticationTimeoutMillis = 0L
var userAuthenticationTypes = setOf<UserAuthenticationType>()
for ((key, value) in configuration.asMap) {
when (key.asTstr) {
"purposes" -> setKeyPurposes(KeyPurpose.decodeSet(value.asNumber))
"curve" -> setEcCurve(EcCurve.fromInt(value.asNumber.toInt()))
"useStrongBox" -> setUseStrongBox(value.asBoolean)
"userAuthenticationRequired" -> userAutenticationRequired = value.asBoolean
"userAuthenticationTimeoutMillis" -> userAuthenticationTimeoutMillis = value.asNumber
"userAuthenticationTypes" -> userAuthenticationTypes = UserAuthenticationType.decodeSet(value.asNumber)
}
}
fun applyConfiguration(configuration: SecureAreaConfigurationAndroidKeystore) = apply {
setKeyPurposes(KeyPurpose.decodeSet(configuration.purposes))
setEcCurve(EcCurve.fromInt(configuration.curve))
setUseStrongBox(configuration.useStrongBox)
setUserAuthenticationRequired(
userAutenticationRequired,
userAuthenticationTimeoutMillis,
userAuthenticationTypes
configuration.userAuthenticationRequired,
configuration.userAuthenticationTimeoutMillis,
UserAuthenticationType.decodeSet(configuration.userAuthenticationTypes)
)
}

Expand Down
71 changes: 59 additions & 12 deletions identity-flow/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,28 +1,75 @@
plugins {
id("java-library")
id("org.jetbrains.kotlin.jvm")
alias(libs.plugins.ksp)
alias(libs.plugins.kotlinMultiplatform)
}

val projectVersionCode: Int by rootProject.extra
val projectVersionName: String by rootProject.extra

kotlin {
jvmToolchain(17)

jvm()

listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach {
it.binaries.framework {
baseName = "identity-flow"
isStatic = true
}
}

sourceSets {
val commonMain by getting {
kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
dependencies {
implementation(projects.identity)
implementation(projects.processorAnnotations)
implementation(libs.kotlinx.io.bytestring)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
}
}

val jvmTest by getting {
kotlin.srcDir("build/generated/ksp/jvm/jvmTest/kotlin")
dependencies {
implementation(projects.processorAnnotations)
implementation(libs.kotlin.test)
implementation(libs.kotlinx.coroutine.test)
}
}
}
}

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

dependencies {
implementation(project(":identity"))
implementation(project(":processor-annotations"))
ksp(project(":processor"))
group = "com.android.identity"
version = projectVersionName

implementation(libs.kotlinx.io.bytestring)
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
dependencies {
add("kspCommonMainMetadata", project(":processor"))
add("kspJvmTest", project(":processor"))
}

testImplementation(libs.junit)
testImplementation(libs.kotlinx.coroutine.test)
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().all {
if (name != "kspCommonMainKotlinMetadata") {
dependsOn("kspCommonMainKotlinMetadata")
}
}

tasks["compileKotlinIosX64"].dependsOn("kspCommonMainKotlinMetadata")
tasks["compileKotlinIosArm64"].dependsOn("kspCommonMainKotlinMetadata")
tasks["compileKotlinIosSimulatorArm64"].dependsOn("kspCommonMainKotlinMetadata")

tasks["iosX64SourcesJar"].dependsOn("kspCommonMainKotlinMetadata")
tasks["iosArm64SourcesJar"].dependsOn("kspCommonMainKotlinMetadata")
tasks["iosSimulatorArm64SourcesJar"].dependsOn("kspCommonMainKotlinMetadata")
tasks["jvmSourcesJar"].dependsOn("kspCommonMainKotlinMetadata")
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import kotlin.time.Duration.Companion.milliseconds
* testing/developing in local environment.
*/
class FlowNotificationsLocal(
private val coroutineScope: CoroutineScope,
private val cipher: SimpleCipher
): FlowNotifications, FlowNotifier {
private val lock = Mutex()
Expand All @@ -37,7 +38,7 @@ class FlowNotificationsLocal(
// as the need for the delay is an indication of a race condition somewhere
// Update: it seems that we start listening for notifications a bit later than
// they are emitted.
CoroutineScope(Dispatchers.IO).launch {
coroutineScope.launch {
delay(200.milliseconds)
val flow = lock.withLock { flowMap[ref] };
if (flow == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.android.identity.flow.handler

import com.android.identity.cbor.DataItem
import com.android.identity.flow.transport.HttpTransport
import com.android.identity.util.Logger
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.transform
import kotlinx.io.bytestring.ByteString
import java.util.concurrent.CancellationException
import kotlin.time.Duration.Companion.seconds

/** [FlowNotifier] implementation based on [FlowPoll] interface. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import com.android.identity.flow.annotation.FlowException

@FlowException
@CborSerializable
class InvalidRequestException(message: String?) : RuntimeException(message) {
class InvalidRequestException(override val message: String?) : RuntimeException(message) {
companion object
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ import com.android.identity.flow.handler.FlowPollHttp
import com.android.identity.flow.server.FlowEnvironment
import com.android.identity.flow.handler.HttpHandler
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Test
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.math.sqrt
import kotlin.random.Random
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.fail

// The following data structures are shared between the client and the server. They can be
// used as method parameters and return values.
Expand Down Expand Up @@ -254,7 +259,7 @@ class FlowProcessorTest {
runBlocking {
try {
localFactory().createQuadraticSolver("Foo")
Assert.fail()
fail()
} catch (e: UnknownSolverException) {
// expected
}
Expand All @@ -275,20 +280,20 @@ class FlowProcessorTest {
fun nullable() {
runBlocking {
val factory = remoteFactory()
Assert.assertEquals("Foo", factory.nullableIdentity("Foo"))
Assert.assertNull(factory.nullableIdentity(null))
assertEquals("Foo", factory.nullableIdentity("Foo"))
assertNull(factory.nullableIdentity(null))
}
}

@Test
fun flowParameter() {
runBlocking {
val factory = localFactory()
Assert.assertEquals(
assertEquals(
"MockQuadraticSolverState",
factory.examineSolver(factory.createQuadraticSolver("Mock"))
)
Assert.assertEquals(
assertEquals(
"DirectQuadraticSolverState[0]",
factory.examineSolver(factory.createQuadraticSolver("Direct"))
)
Expand All @@ -300,7 +305,7 @@ class FlowProcessorTest {
runBlocking {
try {
remoteFactory().createQuadraticSolver("Foo")
Assert.fail()
fail()
} catch (e: UnknownSolverException) {
// expected
}
Expand Down Expand Up @@ -333,20 +338,20 @@ class FlowProcessorTest {
fun testFlow(name: String, factory: SolverFactoryFlow) {
runBlocking {
val solver = factory.createQuadraticSolver(name)
Assert.assertEquals(name, solver.getName())
assertEquals(name, solver.getName())
val solution1 = solver.solve(QuadraticEquation(a = 1.0, b = -6.0, c = 9.0))
Assert.assertNotNull(solution1.x1)
Assert.assertEquals(3.0, solution1.x1!!, 1e-10)
Assert.assertNull(solution1.x2)
assertNotNull(solution1.x1)
assertEquals(3.0, solution1.x1, 1e-10)
assertNull(solution1.x2)
try {
solver.solve(QuadraticEquation(a = 1.0, b = 0.0, c = 1.0))
Assert.fail()
fail()
} catch (err: NoSolutionsException) {
// expect to happen
}
Assert.assertEquals(0, factory.getCount())
assertEquals(0, factory.getCount())
solver.complete()
Assert.assertEquals(2, factory.getCount())
assertEquals(2, factory.getCount())
}
}
}
Loading

0 comments on commit b42a116

Please sign in to comment.