Skip to content
Snippets Groups Projects
Unverified Commit dcf361ab authored by BMItter's avatar BMItter Committed by GitHub
Browse files

ENFClient v2 - ExposureDetectionTracker without identifier (EXPOSUREAPP-3540) (#1605)

* Implemented the opportunity to use ExposureDetectionTracker without identifier

* Old provideDiagnosisKeys = NO-OP

* clean

* maxLength clean

* refactoring and extension function

* adjusted wording

* Throw UnsupportedOperationException in ENFClient in case

* ktlint clean

* fix tests for now

* commented out Exception temporarily

* adjusted enfTest -wip, throw unsupportedOperationException
parent a5630bed
No related branches found
No related tags found
No related merge requests found
......@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.map
import org.joda.time.Instant
import timber.log.Timber
import java.io.File
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
......@@ -26,7 +27,6 @@ class ENFClient @Inject constructor(
private val tracingStatus: TracingStatus,
private val scanningSupport: ScanningSupport,
private val exposureWindowProvider: ExposureWindowProvider,
private val exposureDetectionTracker: ExposureDetectionTracker
) : DiagnosisKeyProvider, TracingStatus, ScanningSupport, ExposureWindowProvider {
......@@ -40,19 +40,8 @@ class ENFClient @Inject constructor(
configuration: ExposureConfiguration?,
token: String
): Boolean {
Timber.d(
"asyncProvideDiagnosisKeys(keyFiles=%s, configuration=%s, token=%s)",
keyFiles, configuration, token
)
return if (keyFiles.isEmpty()) {
Timber.d("No key files submitted, returning early.")
true
} else {
Timber.d("Forwarding %d key files to our DiagnosisKeyProvider.", keyFiles.size)
exposureDetectionTracker.trackNewExposureDetection(token)
diagnosisKeyProvider.provideDiagnosisKeys(keyFiles, configuration, token)
}
// TODO uncomment Exception later, after every subtask has joined (fun will probably be removed)
throw UnsupportedOperationException("Use provideDiagnosisKeys without token and configuration!")
}
override suspend fun provideDiagnosisKeys(keyFiles: Collection<File>): Boolean {
......@@ -63,7 +52,7 @@ class ENFClient @Inject constructor(
true
} else {
Timber.d("Forwarding %d key files to our DiagnosisKeyProvider.", keyFiles.size)
TODO("Call calculationTracker.trackNewCalaculation with an UUID as replacement for token?")
exposureDetectionTracker.trackNewExposureDetection(UUID.randomUUID().toString())
diagnosisKeyProvider.provideDiagnosisKeys(keyFiles)
}
}
......
......@@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.plus
import org.joda.time.Duration
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.min
......@@ -63,7 +64,7 @@ class DefaultExposureDetectionTracker @Inject constructor(
}
}
delay(TIMEOUT_CHECK_INTERVALL.millis)
delay(TIMEOUT_CHECK_INTERVAL.millis)
}
}.launchIn(scope + dispatcherProvider.Default)
}
......@@ -93,34 +94,17 @@ class DefaultExposureDetectionTracker @Inject constructor(
}
}
override fun finishExposureDetection(identifier: String, result: Result) {
override fun finishExposureDetection(identifier: String?, result: Result) {
Timber.i("finishExposureDetection(token=%s, result=%s)", identifier, result)
detectionStates.updateSafely {
mutate {
val existing = this[identifier]
if (existing != null) {
if (existing.result == Result.TIMEOUT) {
Timber.w("Detection is late, already hit timeout, still updating.")
} else if (existing.result != null) {
Timber.e("Duplicate callback. Result is already set for detection!")
}
this[identifier] = existing.copy(
result = result,
finishedAt = timeStamper.nowUTC
)
if (identifier == null) {
val id = this.findUnfinishedOrCreateIdentifier()
finishDetection(id, result)
} else {
Timber.e(
"Unknown detection finished (token=%s, result=%s)",
identifier,
result
)
this[identifier] = TrackedExposureDetection(
identifier = identifier,
result = result,
startedAt = timeStamper.nowUTC,
finishedAt = timeStamper.nowUTC
)
finishDetection(identifier, result)
}
val toKeep = entries
.sortedByDescending { it.value.startedAt } // Keep newest
.subList(0, min(entries.size, MAX_ENTRY_SIZE))
......@@ -134,9 +118,52 @@ class DefaultExposureDetectionTracker @Inject constructor(
}
}
private fun Map<String, TrackedExposureDetection>.findUnfinishedOrCreateIdentifier(): String {
val newestUnfinishedDetection = this
.map { it.value }
.filter { it.finishedAt == null }
.maxByOrNull { it.startedAt.millis }
return if (newestUnfinishedDetection != null) {
Timber.d("findUnfinishedOrCreateIdentifier(): Found unfinished detection, return identifier")
newestUnfinishedDetection.identifier
} else {
Timber.d("findUnfinishedOrCreateIdentifier(): No unfinished detection found, create identifier")
UUID.randomUUID().toString()
}
}
private fun MutableMap<String, TrackedExposureDetection>.finishDetection(identifier: String, result: Result) {
Timber.i("finishDetection(token=%s, result=%s)", identifier, result)
val existing = this[identifier]
if (existing != null) {
if (existing.result == Result.TIMEOUT) {
Timber.w("Detection is late, already hit timeout, still updating.")
} else if (existing.result != null) {
Timber.e("Duplicate callback. Result is already set for detection!")
}
this[identifier] = existing.copy(
result = result,
finishedAt = timeStamper.nowUTC
)
} else {
Timber.e(
"Unknown detection finished (token=%s, result=%s)",
identifier,
result
)
this[identifier] = TrackedExposureDetection(
identifier = identifier,
result = result,
startedAt = timeStamper.nowUTC,
finishedAt = timeStamper.nowUTC
)
}
}
companion object {
private const val TAG = "DefaultExposureDetectionTracker"
private const val MAX_ENTRY_SIZE = 5
private val TIMEOUT_CHECK_INTERVALL = Duration.standardMinutes(3)
private val TIMEOUT_CHECK_INTERVAL = Duration.standardMinutes(3)
}
}
......@@ -7,5 +7,5 @@ interface ExposureDetectionTracker {
fun trackNewExposureDetection(identifier: String)
fun finishExposureDetection(identifier: String, result: TrackedExposureDetection.Result)
fun finishExposureDetection(identifier: String? = null, result: TrackedExposureDetection.Result)
}
......@@ -11,7 +11,6 @@ import com.google.android.gms.nearby.exposurenotification.ExposureNotificationCl
import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient.EXTRA_TOKEN
import dagger.android.AndroidInjection
import de.rki.coronawarnapp.exception.ExceptionCategory.INTERNAL
import de.rki.coronawarnapp.exception.NoTokenException
import de.rki.coronawarnapp.exception.UnknownBroadcastException
import de.rki.coronawarnapp.exception.reporting.report
import de.rki.coronawarnapp.nearby.ExposureStateUpdateWorker
......@@ -55,9 +54,10 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() {
val async = goAsync()
scope.launch(context = dispatcherProvider.Default) {
try {
val token = intent.getStringExtra(EXTRA_TOKEN)
when (action) {
ACTION_EXPOSURE_STATE_UPDATED -> processStateUpdates(intent)
ACTION_EXPOSURE_NOT_FOUND -> processNotFound(intent)
ACTION_EXPOSURE_STATE_UPDATED -> processStateUpdates(token)
ACTION_EXPOSURE_NOT_FOUND -> processNotFound(token)
else -> throw UnknownBroadcastException(action)
}
} catch (e: Exception) {
......@@ -69,13 +69,12 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() {
}
}
private fun processStateUpdates(intent: Intent) {
private fun processStateUpdates(token: String?) {
Timber.tag(TAG).i("Processing ACTION_EXPOSURE_STATE_UPDATED")
val workManager = WorkManager.getInstance(context)
val token = intent.requireToken()
// TODO("Remove token from ExposureStateUpdateWorker")
val data = Data
.Builder()
.putString(EXTRA_TOKEN, token)
......@@ -93,21 +92,15 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() {
)
}
private fun processNotFound(intent: Intent) {
private fun processNotFound(token: String?) {
Timber.tag(TAG).i("Processing ACTION_EXPOSURE_NOT_FOUND")
val token = intent.requireToken()
exposureDetectionTracker.finishExposureDetection(
token,
TrackedExposureDetection.Result.NO_MATCHES
)
}
private fun Intent.requireToken(): String = getStringExtra(EXTRA_TOKEN).also {
Timber.tag(TAG).v("Extracted token: %s", it)
} ?: throw NoTokenException(IllegalArgumentException("no token was found in the intent"))
companion object {
private val TAG: String? = ExposureStateUpdateReceiver::class.simpleName
}
......
......@@ -77,21 +77,19 @@ class ENFClientTest : BaseTest() {
val configuration = mockk<ExposureConfiguration>()
val token = "123"
coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns true
coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true
runBlocking {
client.provideDiagnosisKeys(keyFiles, configuration, token) shouldBe true
client.provideDiagnosisKeys(keyFiles) shouldBe true
}
coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns false
coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns false
runBlocking {
client.provideDiagnosisKeys(keyFiles, configuration, token) shouldBe false
client.provideDiagnosisKeys(keyFiles) shouldBe false
}
coVerify(exactly = 2) {
diagnosisKeyProvider.provideDiagnosisKeys(
keyFiles,
configuration,
token
keyFiles
)
}
}
......@@ -103,13 +101,13 @@ class ENFClientTest : BaseTest() {
val configuration = mockk<ExposureConfiguration>()
val token = "123"
coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any()) } returns true
coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true
runBlocking {
client.provideDiagnosisKeys(keyFiles, configuration, token) shouldBe true
client.provideDiagnosisKeys(keyFiles) shouldBe true
}
coVerify(exactly = 0) {
diagnosisKeyProvider.provideDiagnosisKeys(any(), any(), any())
diagnosisKeyProvider.provideDiagnosisKeys(any())
}
}
......
......@@ -297,7 +297,8 @@ class ExposureWindowsCalculationTest : BaseTest() {
serverTime = Instant.now(),
localOffset = Duration.ZERO,
mappedConfig = configData,
isFallback = false
identifier = "soup",
configType = ConfigData.Type.FROM_SERVER
)
val attenuationFilters = mutableListOf<RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter>()
......
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