Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Prevent matching our own CheckIns (EXPOSUREAPP-6305) #2797

Merged
merged 5 commits into from
Apr 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "5117ed4caaa7ecd70051902d844cc665",
"identityHash": "e23913768d43dc0cb1374df83b2fa78a",
"entities": [
{
"tableName": "checkin",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `traceLocationIdBase64` TEXT NOT NULL, `version` INTEGER NOT NULL, `type` INTEGER NOT NULL, `description` TEXT NOT NULL, `address` TEXT NOT NULL, `traceLocationStart` TEXT, `traceLocationEnd` TEXT, `defaultCheckInLengthInMinutes` INTEGER, `cryptographicSeedBase64` TEXT NOT NULL, `cnPublicKey` TEXT NOT NULL, `checkInStart` TEXT NOT NULL, `checkInEnd` TEXT NOT NULL, `completed` INTEGER NOT NULL, `createJournalEntry` INTEGER NOT NULL)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `traceLocationIdBase64` TEXT NOT NULL, `version` INTEGER NOT NULL, `type` INTEGER NOT NULL, `description` TEXT NOT NULL, `address` TEXT NOT NULL, `traceLocationStart` TEXT, `traceLocationEnd` TEXT, `defaultCheckInLengthInMinutes` INTEGER, `cryptographicSeedBase64` TEXT NOT NULL, `cnPublicKey` TEXT NOT NULL, `checkInStart` TEXT NOT NULL, `checkInEnd` TEXT NOT NULL, `completed` INTEGER NOT NULL, `createJournalEntry` INTEGER NOT NULL, `submitted` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
Expand Down Expand Up @@ -97,6 +97,12 @@
"columnName": "createJournalEntry",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isSubmitted",
"columnName": "submitted",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
Expand Down Expand Up @@ -186,7 +192,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5117ed4caaa7ecd70051902d844cc665')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e23913768d43dc0cb1374df83b2fa78a')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ object CheckInDatabaseData {
checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"),
checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z"),
completed = false,
createJournalEntry = true
createJournalEntry = true,
isSubmitted = false,
)

val testCheckInWithoutCheckOutTime = TraceLocationCheckInEntity(
Expand All @@ -38,6 +39,7 @@ object CheckInDatabaseData {
checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"),
checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z"),
completed = false,
createJournalEntry = true
createJournalEntry = true,
isSubmitted = false,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ data class CheckIn(
val checkInStart: Instant,
val checkInEnd: Instant,
val completed: Boolean,
val createJournalEntry: Boolean
val createJournalEntry: Boolean,
val isSubmitted: Boolean = false,
) {
/**
* Returns SHA-256 hash of [traceLocationId] which itself may also be SHA-256 hash.
Expand All @@ -48,5 +49,6 @@ fun CheckIn.toEntity() = TraceLocationCheckInEntity(
checkInStart = checkInStart,
checkInEnd = checkInEnd,
completed = completed,
createJournalEntry = createJournalEntry
createJournalEntry = createJournalEntry,
isSubmitted = isSubmitted,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.eventregistration.checkins

import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase
import de.rki.coronawarnapp.eventregistration.storage.dao.CheckInDao
import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity
import de.rki.coronawarnapp.eventregistration.storage.entity.toCheckIn
import de.rki.coronawarnapp.util.TimeStamper
import kotlinx.coroutines.NonCancellable
Expand Down Expand Up @@ -64,6 +65,13 @@ class CheckInRepository @Inject constructor(
checkInDao.updateEntityById(checkInId, update)
}

suspend fun markCheckInAsSubmitted(checkInId: Long) {
Timber.d("markCheckInAsSubmitted(checkInId=$checkInId)")
checkInDao.updateEntity(
TraceLocationCheckInEntity.SubmissionUpdate(checkInId = checkInId, isSubmitted = true)
)
}

suspend fun deleteCheckIns(checkIns: Collection<CheckIn>) = withContext(NonCancellable) {
Timber.d("deleteCheckIns(checkIns=%s)", checkIns)
checkInDao.deleteByIds(checkIns.map { it.id })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ interface CheckInDao {
update(updated)
}

@Update(entity = TraceLocationCheckInEntity::class)
suspend fun updateEntity(update: TraceLocationCheckInEntity.SubmissionUpdate)

@Query("DELETE FROM checkin")
suspend fun deleteAll()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@ data class TraceLocationCheckInEntity(
@ColumnInfo(name = "checkInStart") val checkInStart: Instant,
@ColumnInfo(name = "checkInEnd") val checkInEnd: Instant,
@ColumnInfo(name = "completed") val completed: Boolean,
@ColumnInfo(name = "createJournalEntry") val createJournalEntry: Boolean
)
@ColumnInfo(name = "createJournalEntry") val createJournalEntry: Boolean,
@ColumnInfo(name = "submitted") val isSubmitted: Boolean,
) {

@Entity
data class SubmissionUpdate(
@PrimaryKey @ColumnInfo(name = "id") val checkInId: Long,
@ColumnInfo(name = "submitted") val isSubmitted: Boolean,
)
}

fun TraceLocationCheckInEntity.toCheckIn() = CheckIn(
id = id,
Expand All @@ -41,5 +49,6 @@ fun TraceLocationCheckInEntity.toCheckIn() = CheckIn(
checkInStart = checkInStart,
checkInEnd = checkInEnd,
completed = completed,
createJournalEntry = createJournalEntry
createJournalEntry = createJournalEntry,
isSubmitted = isSubmitted
)
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ internal fun CheckIn.calculateOverlap(
return null
}

if (isSubmitted) {
Timber.tag(TAG).d("Overlap with our own CheckIn (%s and %s)", this, warning)
return null
}

return CheckInWarningOverlap(
checkInId = id,
transmissionRiskLevel = warning.transmissionRiskLevel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.submission.task

import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.bugreporting.reportProblem
import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository
import de.rki.coronawarnapp.eventregistration.checkins.CheckInsTransformer
Expand Down Expand Up @@ -167,6 +168,15 @@ class SubmissionTask @Inject constructor(
tekHistoryStorage.clear()
submissionSettings.symptoms.update { null }

Timber.tag(TAG).d("Marking %d submitted CheckIns.", checkIns.size)
checkIns.forEach { checkIn ->
try {
checkInsRepository.markCheckInAsSubmitted(checkIn.id)
} catch (e: Exception) {
e.reportProblem(TAG, "CheckIn $checkIn could not be marked as submitted")
}
}

autoSubmission.updateMode(AutoSubmission.Mode.DISABLED)

setSubmissionFinished()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ class CheckInRepositoryTest : BaseTest() {
checkInStart = time,
checkInEnd = end,
completed = false,
createJournalEntry = false
createJournalEntry = false,
isSubmitted = true
)
)
coVerify {
Expand All @@ -112,7 +113,8 @@ class CheckInRepositoryTest : BaseTest() {
checkInStart = time,
checkInEnd = end,
completed = false,
createJournalEntry = false
createJournalEntry = false,
isSubmitted = true,
)
)
}
Expand Down Expand Up @@ -156,7 +158,8 @@ class CheckInRepositoryTest : BaseTest() {
checkInStart = start,
checkInEnd = end,
completed = false,
createJournalEntry = false
createJournalEntry = false,
isSubmitted = true,
)
)
runBlockingTest {
Expand All @@ -176,7 +179,8 @@ class CheckInRepositoryTest : BaseTest() {
checkInStart = start,
checkInEnd = end,
completed = false,
createJournalEntry = false
createJournalEntry = false,
isSubmitted = true,
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,59 @@ class CheckInWarningMatcherTest : BaseTest() {
}
}
}

@Test
fun `we do not match our own CheckIns`() {
val checkIn1 = createCheckIn(
id = 2L,
traceLocationId = "fe84394e73838590cc7707aba0350c130f6d0fb6f0f2535f9735f481dee61871",
startDateStr = "2021-03-04T10:15+01:00",
endDateStr = "2021-03-04T10:17+01:00"
)
val checkIn2 = createCheckIn(
id = 3L,
traceLocationId = "69eb427e1a48133970486244487e31b3f1c5bde47415db9b52cc5a2ece1e0060",
startDateStr = "2021-03-04T09:15+01:00",
endDateStr = "2021-03-04T10:12+01:00",
isSubmitted = true
)

val warning1 = createWarning(
traceLocationId = "fe84394e73838590cc7707aba0350c130f6d0fb6f0f2535f9735f481dee61871",
startIntervalDateStr = "2021-03-04T10:00+01:00",
period = 6,
transmissionRiskLevel = 8
)

val warning2 = createWarning(
traceLocationId = "69eb427e1a48133970486244487e31b3f1c5bde47415db9b52cc5a2ece1e0060",
startIntervalDateStr = "2021-03-04T10:00+01:00",
period = 6,
transmissionRiskLevel = 8
)

val warningPackage = object : TraceWarningPackage {
override suspend fun extractWarnings(): List<TraceWarning.TraceTimeIntervalWarning> {
return listOf(warning1, warning2)
}

override val packageId: WarningPackageId
get() = "id"
}

runBlockingTest {
val result = createInstance().process(
checkIns = listOf(checkIn1, checkIn2),
warningPackages = listOf(warningPackage)
)
result.apply {
successful shouldBe true
processedPackages.single().warningPackage shouldBe warningPackage
processedPackages.single().apply {
overlaps.size shouldBe 1
overlaps.any { it.checkInId == 2L } shouldBe true
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,32 @@ class OverlapTest : BaseTest() {
traceWarningPackageId = id
)!!.roundedMinutes shouldBe 5
}

@Test
fun `returns null if it matches our own ssubmitted CheckIn`() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo submitted

createCheckIn(
traceLocationId = locationId,
startDateStr = "2021-03-04T10:15+01:00",
endDateStr = "2021-03-04T10:17+01:00",
isSubmitted = true,
).calculateOverlap(
createWarning(
traceLocationId = locationId,
startIntervalDateStr = "2021-03-04T10:00+01:00",
period = 6,
transmissionRiskLevel = 8
),
traceWarningPackageId = id
) shouldBe null
}
}

fun createCheckIn(
id: Long = 1L,
traceLocationId: String,
startDateStr: String,
endDateStr: String
endDateStr: String,
isSubmitted: Boolean = false,
) = CheckIn(
id = id,
traceLocationId = traceLocationId.decodeHex(),
Expand All @@ -284,7 +303,8 @@ fun createCheckIn(
checkInStart = Instant.parse(startDateStr),
checkInEnd = Instant.parse(endDateStr),
completed = false,
createJournalEntry = false
createJournalEntry = false,
isSubmitted = isSubmitted
)

fun createWarning(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.appconfig.ConfigData
import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
import de.rki.coronawarnapp.eventregistration.checkins.CheckIn
import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository
import de.rki.coronawarnapp.eventregistration.checkins.CheckInsTransformer
import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
Expand Down Expand Up @@ -73,6 +74,25 @@ class SubmissionTaskTest : BaseTest() {

private val settingLastUserActivityUTC: FlowPreference<Instant> = mockFlowPreference(Instant.EPOCH.plus(1))

private val testCheckIn1 = CheckIn(
id = 1L,
traceLocationId = mockk(),
version = 1,
type = 2,
description = "brothers birthday",
address = "Malibu",
traceLocationStart = Instant.EPOCH,
traceLocationEnd = null,
defaultCheckInLengthInMinutes = null,
cryptographicSeed = mockk(),
cnPublicKey = "cnPublicKey",
checkInStart = Instant.EPOCH,
checkInEnd = Instant.EPOCH.plus(9000),
completed = false,
createJournalEntry = false,
isSubmitted = true
)

@BeforeEach
fun setup() {
MockKAnnotations.init(this)
Expand Down Expand Up @@ -113,7 +133,7 @@ class SubmissionTaskTest : BaseTest() {

every { timeStamper.nowUTC } returns Instant.EPOCH.plus(Duration.standardHours(1))

every { checkInRepository.checkInsWithinRetention } returns flowOf(emptyList())
every { checkInRepository.checkInsWithinRetention } returns flowOf(listOf(testCheckIn1))
coEvery { checkInsTransformer.transform(any(), any()) } returns emptyList()
}

Expand Down Expand Up @@ -179,6 +199,8 @@ class SubmissionTaskTest : BaseTest() {
submissionSettings.symptoms
settingSymptomsPreference.update(match { it.invoke(mockk()) == null })

checkInRepository.markCheckInAsSubmitted(testCheckIn1.id)

autoSubmission.updateMode(AutoSubmission.Mode.DISABLED)

backgroundWorkScheduler.stopWorkScheduler()
Expand Down