Skip to content
Snippets Groups Projects
Unverified Commit 35e77fb5 authored by Matthias Urhahn's avatar Matthias Urhahn Committed by GitHub
Browse files

Refactor ENF version check (#1680)

parent 6ddb44a1
No related branches found
No related tags found
No related merge requests found
...@@ -25,13 +25,9 @@ class DefaultDiagnosisKeyProvider @Inject constructor( ...@@ -25,13 +25,9 @@ class DefaultDiagnosisKeyProvider @Inject constructor(
return true return true
} }
// Check version of ENF // Check version of ENF, WindowMode since v1.5, but version check since v1.6
try { // Will throw if requirement is not satisfied
enfVersion.requireAtLeast(ENFVersion.V16) enfVersion.requireMinimumVersion(ENFVersion.V1_6)
} catch (e: Exception) {
Timber.e(e)
throw e
}
if (!submissionQuota.consumeQuota(1)) { if (!submissionQuota.consumeQuota(1)) {
Timber.w("No key files submitted because not enough quota available.") Timber.w("No key files submitted because not enough quota available.")
......
...@@ -9,7 +9,6 @@ import javax.inject.Singleton ...@@ -9,7 +9,6 @@ import javax.inject.Singleton
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import kotlin.math.abs
@Singleton @Singleton
class DefaultENFVersion @Inject constructor( class DefaultENFVersion @Inject constructor(
...@@ -23,22 +22,19 @@ class DefaultENFVersion @Inject constructor( ...@@ -23,22 +22,19 @@ class DefaultENFVersion @Inject constructor(
null null
} }
override suspend fun requireAtLeast(compareVersion: Long) { override suspend fun requireMinimumVersion(required: Long) {
if (!isAtLeast(compareVersion)) { try {
throw ENFVersion.Companion.UnsupportedENFVersionException() val currentVersion = internalGetENFClientVersion()
} if (currentVersion < required) {
} val error = OutdatedENFVersionException(current = currentVersion, required = required)
Timber.e(error, "Version requirement not satisfied.")
override suspend fun isAtLeast(compareVersion: Long): Boolean { throw error
if (!compareVersion.isCorrectVersionLength) throw IllegalArgumentException("given version has incorrect length") } else {
Timber.d("Version requirement satisfied: current=$currentVersion, required=$required")
return try { }
internalGetENFClientVersion() >= compareVersion
} catch (apiException: ApiException) { } catch (apiException: ApiException) {
if (apiException.statusCode != CommonStatusCodes.API_NOT_CONNECTED) { if (apiException.statusCode != CommonStatusCodes.API_NOT_CONNECTED) {
throw apiException throw apiException
} else {
return false
} }
} }
} }
...@@ -48,12 +44,4 @@ class DefaultENFVersion @Inject constructor( ...@@ -48,12 +44,4 @@ class DefaultENFVersion @Inject constructor(
.addOnSuccessListener { cont.resume(it) } .addOnSuccessListener { cont.resume(it) }
.addOnFailureListener { cont.resumeWithException(it) } .addOnFailureListener { cont.resumeWithException(it) }
} }
// check if a raw long has the correct length to be considered an API version
private val Long.isCorrectVersionLength
get(): Boolean = abs(this).toString().length == GOOGLE_API_VERSION_FIELD_LENGTH
companion object {
private const val GOOGLE_API_VERSION_FIELD_LENGTH = 8
}
} }
package de.rki.coronawarnapp.nearby.modules.version package de.rki.coronawarnapp.nearby.modules.version
interface ENFVersion { interface ENFVersion {
suspend fun getENFClientVersion(): Long?
/** /**
* Indicates if the client runs above a certain version * May return null if the API is currently not connected.
*
* @return isAboveVersion, if connected to an old unsupported version, return false
*/ */
suspend fun isAtLeast(compareVersion: Long): Boolean suspend fun getENFClientVersion(): Long?
/** /**
* Throws an [UnsupportedENFVersionException] if the client runs an old unsupported version of the ENF * Throws an [OutdatedENFVersionException] if the client runs an old unsupported version of the ENF
* If the API is currently not connected, no exception will be thrown, we expect this to only be a temporary state
*/ */
suspend fun requireAtLeast(compareVersion: Long) suspend fun requireMinimumVersion(required: Long)
companion object { companion object {
const val V16 = 16000000L const val V1_6 = 16000000L
const val V15 = 15000000L
class UnsupportedENFVersionException : Exception("The client runs an old unsupported version of the ENF")
} }
} }
package de.rki.coronawarnapp.nearby.modules.version
class OutdatedENFVersionException(
val current: Long,
val required: Long
) : Exception("Client is using an outdated ENF version: current=$current, required=$required")
...@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider ...@@ -2,6 +2,7 @@ package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient
import de.rki.coronawarnapp.nearby.modules.version.ENFVersion import de.rki.coronawarnapp.nearby.modules.version.ENFVersion
import de.rki.coronawarnapp.nearby.modules.version.OutdatedENFVersionException
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.clearAllMocks import io.mockk.clearAllMocks
...@@ -33,7 +34,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { ...@@ -33,7 +34,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() {
coEvery { googleENFClient.provideDiagnosisKeys(any<List<File>>()) } returns MockGMSTask.forValue(null) coEvery { googleENFClient.provideDiagnosisKeys(any<List<File>>()) } returns MockGMSTask.forValue(null)
coEvery { enfVersion.requireAtLeast(any()) } returns Unit coEvery { enfVersion.requireMinimumVersion(any()) } returns Unit
} }
@AfterEach @AfterEach
...@@ -49,11 +50,14 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { ...@@ -49,11 +50,14 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() {
@Test @Test
fun `provide diagnosis keys with outdated ENF versions`() { fun `provide diagnosis keys with outdated ENF versions`() {
coEvery { enfVersion.requireAtLeast(any()) } throws ENFVersion.Companion.UnsupportedENFVersionException() coEvery { enfVersion.requireMinimumVersion(any()) } throws OutdatedENFVersionException(
current = 9000,
required = 5000
)
val provider = createProvider() val provider = createProvider()
assertThrows<ENFVersion.Companion.UnsupportedENFVersionException> { assertThrows<OutdatedENFVersionException> {
runBlockingTest { provider.provideDiagnosisKeys(exampleKeyFiles) } shouldBe false runBlockingTest { provider.provideDiagnosisKeys(exampleKeyFiles) } shouldBe false
} }
......
...@@ -2,21 +2,21 @@ package de.rki.coronawarnapp.nearby.modules.version ...@@ -2,21 +2,21 @@ package de.rki.coronawarnapp.nearby.modules.version
import com.google.android.gms.common.api.ApiException import com.google.android.gms.common.api.ApiException
import com.google.android.gms.common.api.CommonStatusCodes.API_NOT_CONNECTED import com.google.android.gms.common.api.CommonStatusCodes.API_NOT_CONNECTED
import com.google.android.gms.common.api.CommonStatusCodes.INTERNAL_ERROR
import com.google.android.gms.common.api.Status import com.google.android.gms.common.api.Status
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient
import io.kotest.assertions.throwables.shouldNotThrowAny
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.mockk.Called
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.clearAllMocks import io.mockk.clearAllMocks
import io.mockk.every import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import testhelpers.gms.MockGMSTask import testhelpers.gms.MockGMSTask
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
...@@ -39,61 +39,74 @@ internal class DefaultENFVersionTest { ...@@ -39,61 +39,74 @@ internal class DefaultENFVersionTest {
) )
@Test @Test
fun `isAbove API v16 is true for v17`() { fun `current version is newer than the required version`() {
every { client.version } returns MockGMSTask.forValue(17000000L) every { client.version } returns MockGMSTask.forValue(17000000L)
runBlockingTest { runBlockingTest {
createInstance().isAtLeast(ENFVersion.V16) shouldBe true createInstance().apply {
getENFClientVersion() shouldBe 17000000L
shouldNotThrowAny {
requireMinimumVersion(ENFVersion.V1_6)
}
}
} }
} }
@Test @Test
fun `isAbove API v16 is false for v15`() { fun `current version is older than the required version`() {
every { client.version } returns MockGMSTask.forValue(15000000L) every { client.version } returns MockGMSTask.forValue(15000000L)
runBlockingTest { runBlockingTest {
createInstance().isAtLeast(ENFVersion.V16) shouldBe false createInstance().apply {
} getENFClientVersion() shouldBe 15000000L
}
@Test shouldThrow<OutdatedENFVersionException> {
fun `isAbove API v16 throws IllegalArgument for invalid version`() { requireMinimumVersion(ENFVersion.V1_6)
assertThrows<IllegalArgumentException> { }
runBlockingTest {
createInstance().isAtLeast(1L)
} }
verify { client.version wasNot Called }
} }
} }
@Test @Test
fun `isAbove API v16 false when APIException for too low version`() { fun `current version is equal to the required version`() {
every { client.version } returns MockGMSTask.forError(ApiException(Status(API_NOT_CONNECTED))) every { client.version } returns MockGMSTask.forValue(16000000L)
runBlockingTest { runBlockingTest {
createInstance().isAtLeast(ENFVersion.V16) shouldBe false createInstance().apply {
getENFClientVersion() shouldBe ENFVersion.V1_6
shouldNotThrowAny {
requireMinimumVersion(ENFVersion.V1_6)
}
}
} }
} }
@Test @Test
fun `require API v16 throws UnsupportedENFVersionException for v15`() { fun `API_NOT_CONNECTED exceptions are not treated as failures`() {
every { client.version } returns MockGMSTask.forValue(ENFVersion.V15) every { client.version } returns MockGMSTask.forError(ApiException(Status(API_NOT_CONNECTED)))
assertThrows<ENFVersion.Companion.UnsupportedENFVersionException> { runBlockingTest {
runBlockingTest { createInstance().apply {
createInstance().requireAtLeast(ENFVersion.V16) getENFClientVersion() shouldBe null
shouldNotThrowAny {
requireMinimumVersion(ENFVersion.V1_6)
}
} }
} }
} }
@Test @Test
fun `require API v15 does not throw for v16`() { fun `rethrows unexpected exceptions`() {
every { client.version } returns MockGMSTask.forValue(ENFVersion.V16) every { client.version } returns MockGMSTask.forError(ApiException(Status(INTERNAL_ERROR)))
runBlockingTest { runBlockingTest {
createInstance().requireAtLeast(ENFVersion.V15) createInstance().apply {
} getENFClientVersion() shouldBe null
verify { client.version } shouldThrow<ApiException> {
requireMinimumVersion(ENFVersion.V1_6)
}
}
}
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment