From 86f793a11e2644bf4acdfa68a33ea6f836d4d310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakob=20M=C3=B6ller?= <jakob.moeller@sap.com> Date: Mon, 14 Sep 2020 17:14:29 +0200 Subject: [PATCH] Version Checker with Branching for Checks on provideDiagnosisKeys below v16 of ENF (EXPOSUREAPP-2213) (#1150) * Add Version Check Signed-off-by: d067928 <jakob.moeller@sap.com> * Version Checker Update Signed-off-by: d067928 <jakob.moeller@sap.com> * Testing for Version Check Signed-off-by: d067928 <jakob.moeller@sap.com> * Remove Expiremental Annotation for method and add on Class and pull tearDown below setUp Signed-off-by: d067928 <jakob.moeller@sap.com> * add GoogleAPIVersion via DI Signed-off-by: d067928 <jakob.moeller@sap.com> Co-authored-by: Matthias Urhahn <matthias.urhahn@sap.com> --- .../InternalExposureNotificationClient.kt | 9 +++ .../RetrieveDiagnosisInjectionHelper.kt | 4 +- .../RetrieveDiagnosisKeysTransaction.kt | 25 ++++-- .../coronawarnapp/util/GoogleAPIVersion.kt | 38 ++++++++++ .../RetrieveDiagnosisKeysTransactionTest.kt | 7 +- .../util/GoogleAPIVersionTest.kt | 76 +++++++++++++++++++ 6 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt index 091162128..020dd1d40 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/InternalExposureNotificationClient.kt @@ -92,6 +92,15 @@ object InternalExposureNotificationClient { } } + suspend fun getVersion(): Long = suspendCoroutine { cont -> + exposureNotificationClient.version + .addOnSuccessListener { + cont.resume(it) + }.addOnFailureListener { + cont.resumeWithException(it) + } + } + /** * Takes an ExposureConfiguration object. Inserts a list of files that contain key * information into the on-device database. Provide the keys of confirmed cases retrieved diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisInjectionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisInjectionHelper.kt index ee6c36d9f..69598eedc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisInjectionHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisInjectionHelper.kt @@ -1,10 +1,12 @@ package de.rki.coronawarnapp.transaction +import de.rki.coronawarnapp.util.GoogleAPIVersion import javax.inject.Inject import javax.inject.Singleton // TODO Remove once we have refactored the transaction and it's no longer a singleton @Singleton data class RetrieveDiagnosisInjectionHelper @Inject constructor( - val transactionScope: TransactionCoroutineScope + val transactionScope: TransactionCoroutineScope, + val googleAPIVersion: GoogleAPIVersion ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransaction.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransaction.kt index 1cc35b328..bd9279432 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransaction.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransaction.kt @@ -35,6 +35,7 @@ import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.Retriev import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.rollback import de.rki.coronawarnapp.transaction.RetrieveDiagnosisKeysTransaction.start import de.rki.coronawarnapp.util.CachedKeyFileHolder +import de.rki.coronawarnapp.util.GoogleAPIVersion import de.rki.coronawarnapp.util.GoogleQuotaCalculator import de.rki.coronawarnapp.util.QuotaCalculator import de.rki.coronawarnapp.util.di.AppInjector @@ -141,6 +142,10 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { quotaChronology = GJChronology.getInstanceUTC() ) + private val googleAPIVersion: GoogleAPIVersion by lazy { + AppInjector.component.transRetrieveKeysInjection.googleAPIVersion + } + suspend fun startWithConstraints() { val currentDate = DateTime(Instant.now(), DateTimeZone.UTC) val lastFetch = DateTime( @@ -316,11 +321,21 @@ object RetrieveDiagnosisKeysTransaction : Transaction() { exportFiles: Collection<File>, exposureConfiguration: ExposureConfiguration? ) = executeState(API_SUBMISSION) { - InternalExposureNotificationClient.asyncProvideDiagnosisKeys( - exportFiles, - exposureConfiguration, - token - ) + if (googleAPIVersion.isAbove(GoogleAPIVersion.V16)) { + InternalExposureNotificationClient.asyncProvideDiagnosisKeys( + exportFiles, + exposureConfiguration, + token + ) + } else { + exportFiles.forEach { batch -> + InternalExposureNotificationClient.asyncProvideDiagnosisKeys( + listOf(batch), + exposureConfiguration, + token + ) + } + } Timber.tag(TAG).d("Diagnosis Keys provided successfully, Token: $token") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt new file mode 100644 index 000000000..9b1998f66 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/GoogleAPIVersion.kt @@ -0,0 +1,38 @@ +package de.rki.coronawarnapp.util + +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.common.api.CommonStatusCodes +import dagger.Reusable +import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient +import javax.inject.Inject +import kotlin.math.abs + +@Reusable +class GoogleAPIVersion @Inject constructor() { + /** + * Indicates if the client runs above a certain version + * + * @return isAboveVersion, if connected to an old unsupported version, return false + */ + suspend fun isAbove(compareVersion: Long): Boolean { + if (!compareVersion.isCorrectVersionLength) { + throw IllegalArgumentException("given version has incorrect length") + } + return try { + val currentVersion = InternalExposureNotificationClient.getVersion() + currentVersion >= compareVersion + } catch (apiException: ApiException) { + if (apiException.statusCode == CommonStatusCodes.API_NOT_CONNECTED) false + else throw apiException + } + } + + // 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 + const val V16 = 16000000L + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransactionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransactionTest.kt index 3736f9a07..c2029628c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransactionTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/transaction/RetrieveDiagnosisKeysTransactionTest.kt @@ -4,6 +4,7 @@ import com.google.android.gms.nearby.exposurenotification.ExposureConfiguration import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.service.applicationconfiguration.ApplicationConfigurationService import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.util.GoogleAPIVersion import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.ApplicationComponent import io.mockk.Runs @@ -34,7 +35,8 @@ class RetrieveDiagnosisKeysTransactionTest { mockkObject(AppInjector) val appComponent = mockk<ApplicationComponent>().apply { every { transRetrieveKeysInjection } returns RetrieveDiagnosisInjectionHelper( - TransactionCoroutineScope() + TransactionCoroutineScope(), + GoogleAPIVersion() ) } every { AppInjector.component } returns appComponent @@ -52,6 +54,9 @@ class RetrieveDiagnosisKeysTransactionTest { any() ) } returns mockk() + coEvery { + InternalExposureNotificationClient.getVersion() + } returns 17000000L coEvery { ApplicationConfigurationService.asyncRetrieveExposureConfiguration() } returns mockk() every { LocalData.googleApiToken(any()) } just Runs every { LocalData.lastTimeDiagnosisKeysFromServerFetch() } returns Date() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt new file mode 100644 index 000000000..7667cb604 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/GoogleAPIVersionTest.kt @@ -0,0 +1,76 @@ +package de.rki.coronawarnapp.util + +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.Status +import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient +import io.kotest.matchers.shouldBe +import io.mockk.Called +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockkObject +import io.mockk.unmockkObject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +@ExperimentalCoroutinesApi +internal class GoogleAPIVersionTest { + + private lateinit var classUnderTest: GoogleAPIVersion + + @BeforeEach + fun setUp() { + mockkObject(InternalExposureNotificationClient) + classUnderTest = GoogleAPIVersion() + } + + @AfterEach + fun tearDown() { + unmockkObject(InternalExposureNotificationClient) + } + + @Test + fun `isAbove API v16 is true for v17`() { + coEvery { InternalExposureNotificationClient.getVersion() } returns 17000000L + + runBlockingTest { + classUnderTest.isAbove(GoogleAPIVersion.V16) shouldBe true + } + + } + + @Test + fun `isAbove API v16 is false for v15`() { + coEvery { InternalExposureNotificationClient.getVersion() } returns 15000000L + + runBlockingTest { + classUnderTest.isAbove(GoogleAPIVersion.V16) shouldBe false + } + } + + @Test + fun `isAbove API v16 throws IllegalArgument for invalid version`() { + assertThrows<IllegalArgumentException> { + runBlockingTest { + classUnderTest.isAbove(1L) + } + coVerify { + InternalExposureNotificationClient.getVersion() wasNot Called + } + } + } + + @Test + fun `isAbove API v16 false when APIException for too low version`() { + coEvery { InternalExposureNotificationClient.getVersion() } throws + ApiException(Status(API_NOT_CONNECTED)) + + runBlockingTest { + classUnderTest.isAbove(GoogleAPIVersion.V16) shouldBe false + } + } +} -- GitLab