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

Commit 33fb7af

Browse files
authored
Self Report Submission config param (EXPOSUREAPP-14187) (#5681)
* Self Report Submission config param * Lint * Fix test * Local prerequisites (EXPOSUREAPP-14185) (#5682) * Update SrsLocalChecker.kt * Check duration in hours * Time since submission * Create SrsLocalCheckerTest.kt * lint
1 parent c7f4e99 commit 33fb7af

File tree

9 files changed

+275
-3
lines changed

9 files changed

+275
-3
lines changed

Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/AppConfigModule.kt

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import de.rki.coronawarnapp.appconfig.mapping.ExposureWindowRiskCalculationConfi
1515
import de.rki.coronawarnapp.appconfig.mapping.KeyDownloadParametersMapper
1616
import de.rki.coronawarnapp.appconfig.mapping.LogUploadConfigMapper
1717
import de.rki.coronawarnapp.appconfig.mapping.PresenceTracingConfigMapper
18+
import de.rki.coronawarnapp.appconfig.mapping.SelfReportSubmissionConfigMapper
1819
import de.rki.coronawarnapp.appconfig.mapping.SurveyConfigMapper
1920
import de.rki.coronawarnapp.environment.download.DownloadCDNHttpClient
2021
import de.rki.coronawarnapp.environment.download.DownloadCDNServerUrl
@@ -103,6 +104,11 @@ object AppConfigModule {
103104

104105
@Binds
105106
fun covidCertificateConfigMapper(mapper: CovidCertificateConfigMapper): CovidCertificateConfig.Mapper
107+
108+
@Binds
109+
fun selfReportSubmissionConfigMapper(
110+
mapper: SelfReportSubmissionConfigMapper
111+
): SelfReportSubmissionConfig.Mapper
106112
}
107113
}
108114

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package de.rki.coronawarnapp.appconfig
2+
3+
import de.rki.coronawarnapp.appconfig.mapping.ConfigMapper
4+
import java.time.Duration
5+
6+
interface SelfReportSubmissionConfig {
7+
val common: SelfReportSubmissionCommon
8+
9+
val ppac: SafetyNetRequirements
10+
11+
interface Mapper : ConfigMapper<SelfReportSubmissionConfig>
12+
}
13+
14+
interface SelfReportSubmissionCommon {
15+
val timeSinceOnboardingInHours: Duration
16+
val timeBetweenSubmissionsInDays: Duration
17+
}
18+
19+
data class SelfReportSubmissionCommonContainer(
20+
override val timeSinceOnboardingInHours: Duration = DEFAULT_HOURS,
21+
override val timeBetweenSubmissionsInDays: Duration = DEFAULT_DAYS
22+
) : SelfReportSubmissionCommon {
23+
companion object {
24+
val DEFAULT_HOURS: Duration = Duration.ofHours(24)
25+
val DEFAULT_DAYS: Duration = Duration.ofDays(90)
26+
}
27+
}
28+
29+
data class SelfReportSubmissionConfigContainer(
30+
override val common: SelfReportSubmissionCommon,
31+
override val ppac: SafetyNetRequirements
32+
) : SelfReportSubmissionConfig {
33+
companion object {
34+
val DEFAULT = SelfReportSubmissionConfigContainer(
35+
common = SelfReportSubmissionCommonContainer(),
36+
ppac = SafetyNetRequirementsContainer()
37+
)
38+
}
39+
}

Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigMapping.kt

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig
99
import de.rki.coronawarnapp.appconfig.KeyDownloadConfig
1010
import de.rki.coronawarnapp.appconfig.LogUploadConfig
1111
import de.rki.coronawarnapp.appconfig.PresenceTracingConfig
12+
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfig
1213
import de.rki.coronawarnapp.appconfig.SurveyConfig
1314

1415
interface ConfigMapping :
@@ -23,4 +24,5 @@ interface ConfigMapping :
2324
val presenceTracing: PresenceTracingConfig
2425
val coronaTestParameters: CoronaTestConfig
2526
val covidCertificateParameters: CovidCertificateConfig
27+
val selfReportSubmission: SelfReportSubmissionConfig
2628
}

Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParser.kt

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig
1010
import de.rki.coronawarnapp.appconfig.KeyDownloadConfig
1111
import de.rki.coronawarnapp.appconfig.LogUploadConfig
1212
import de.rki.coronawarnapp.appconfig.PresenceTracingConfig
13+
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfig
1314
import de.rki.coronawarnapp.appconfig.SurveyConfig
1415
import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid
1516
import timber.log.Timber
@@ -28,6 +29,7 @@ class ConfigParser @Inject constructor(
2829
private val presenceTracingConfigMapper: PresenceTracingConfig.Mapper,
2930
private val coronaTestConfigMapper: CoronaTestConfig.Mapper,
3031
private val covidCertificateConfigMapper: CovidCertificateConfig.Mapper,
32+
private val selfReportSubmissionConfigMapper: SelfReportSubmissionConfig.Mapper
3133
) {
3234

3335
fun parse(configBytes: ByteArray): ConfigMapping = try {
@@ -43,6 +45,7 @@ class ConfigParser @Inject constructor(
4345
presenceTracing = presenceTracingConfigMapper.map(it),
4446
coronaTestParameters = coronaTestConfigMapper.map(it),
4547
covidCertificateParameters = covidCertificateConfigMapper.map(it),
48+
selfReportSubmission = selfReportSubmissionConfigMapper.map(it),
4649
)
4750
}
4851
} catch (e: Exception) {

Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/DefaultConfigMapping.kt

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig
99
import de.rki.coronawarnapp.appconfig.KeyDownloadConfig
1010
import de.rki.coronawarnapp.appconfig.LogUploadConfig
1111
import de.rki.coronawarnapp.appconfig.PresenceTracingConfig
12+
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfig
1213
import de.rki.coronawarnapp.appconfig.SurveyConfig
1314

1415
data class DefaultConfigMapping(
@@ -22,6 +23,7 @@ data class DefaultConfigMapping(
2223
override val presenceTracing: PresenceTracingConfig,
2324
override val coronaTestParameters: CoronaTestConfig,
2425
override val covidCertificateParameters: CovidCertificateConfig,
26+
override val selfReportSubmission: SelfReportSubmissionConfig,
2527
) : ConfigMapping,
2628
CWAConfig by cwaConfig,
2729
KeyDownloadConfig by keyDownloadConfig,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package de.rki.coronawarnapp.appconfig.mapping
2+
3+
import dagger.Reusable
4+
import de.rki.coronawarnapp.appconfig.SafetyNetRequirementsContainer
5+
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionCommonContainer
6+
7+
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfig
8+
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfigContainer
9+
import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid
10+
import de.rki.coronawarnapp.server.protocols.internal.v2.PpddSrsParameters
11+
import timber.log.Timber
12+
import java.time.Duration
13+
import javax.inject.Inject
14+
15+
@Reusable
16+
class SelfReportSubmissionConfigMapper @Inject constructor() : SelfReportSubmissionConfig.Mapper {
17+
override fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): SelfReportSubmissionConfig = try {
18+
when {
19+
!rawConfig.hasSelfReportParameters() -> {
20+
Timber.d("No SelfReportParameters -> set to default")
21+
SelfReportSubmissionConfigContainer.DEFAULT
22+
}
23+
else -> rawConfig.selfReportParameters.map()
24+
}
25+
} catch (e: Exception) {
26+
Timber.d(e, "SelfReportSubmissionConfigMapper failed -> returning default")
27+
SelfReportSubmissionConfigContainer.DEFAULT
28+
}
29+
30+
fun PpddSrsParameters.PPDDSelfReportSubmissionParametersAndroid.map(): SelfReportSubmissionConfigContainer {
31+
val common = if (hasCommon()) {
32+
SelfReportSubmissionCommonContainer(
33+
timeSinceOnboardingInHours = if (common.timeSinceOnboardingInHours <= 0) {
34+
Timber.d("Faulty timeSinceOnboardingInHours -> set to default")
35+
SelfReportSubmissionCommonContainer.DEFAULT_HOURS
36+
} else {
37+
Duration.ofHours(common.timeSinceOnboardingInHours.toLong())
38+
},
39+
timeBetweenSubmissionsInDays = if (common.timeBetweenSubmissionsInDays <= 0) {
40+
Timber.d("Faulty timeBetweenSubmissionsInDays -> set to default")
41+
SelfReportSubmissionCommonContainer.DEFAULT_DAYS
42+
} else {
43+
Duration.ofHours(common.timeBetweenSubmissionsInDays.toLong())
44+
}
45+
)
46+
} else {
47+
Timber.d("No Common -> set to default")
48+
SelfReportSubmissionCommonContainer()
49+
}
50+
51+
val ppac = if (hasPpac()) {
52+
SafetyNetRequirementsContainer(
53+
requireBasicIntegrity = ppac.requireBasicIntegrity,
54+
requireCTSProfileMatch = ppac.requireCTSProfileMatch,
55+
requireEvaluationTypeBasic = ppac.requireEvaluationTypeBasic,
56+
requireEvaluationTypeHardwareBacked = ppac.requireEvaluationTypeHardwareBacked
57+
)
58+
} else {
59+
Timber.d("No Ppac -> set to default")
60+
SafetyNetRequirementsContainer()
61+
}
62+
63+
return SelfReportSubmissionConfigContainer(
64+
common = common,
65+
ppac = ppac
66+
)
67+
}
68+
}

Corona-Warn-App/src/main/java/de/rki/coronawarnapp/srs/core/SrsLocalChecker.kt

+56-3
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,74 @@ package de.rki.coronawarnapp.srs.core
22

33
import dagger.Reusable
44
import de.rki.coronawarnapp.appconfig.AppConfigProvider
5+
import de.rki.coronawarnapp.appconfig.ConfigData.DeviceTimeState
6+
import de.rki.coronawarnapp.main.CWASettings
57
import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException
8+
import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException.ErrorCode
69
import de.rki.coronawarnapp.srs.core.storage.SrsSubmissionSettings
10+
import de.rki.coronawarnapp.util.TimeStamper
11+
import kotlinx.coroutines.flow.first
12+
import timber.log.Timber
13+
import java.time.Duration
714
import javax.inject.Inject
815

916
@Reusable
1017
class SrsLocalChecker @Inject constructor(
1118
private val srsSubmissionSettings: SrsSubmissionSettings,
12-
private val appConfigProvider: AppConfigProvider
19+
private val appConfigProvider: AppConfigProvider,
20+
private val cwaSettings: CWASettings,
21+
private val timeStamper: TimeStamper,
1322
) {
1423

1524
/**
25+
* Check SRS local time prerequisites
26+
* throws an error if it fails, otherwise does nothing
1627
* @throws SrsSubmissionException
1728
*/
1829
suspend fun check() {
19-
// TODo
20-
throw SrsSubmissionException(SrsSubmissionException.ErrorCode.SUBMISSION_TOO_EARLY)
30+
val appConfig = appConfigProvider.getAppConfig()
31+
val deviceTimeState = appConfig.deviceTimeState
32+
val selfReportSubmissionCommon = appConfig.selfReportSubmission.common
33+
34+
if (deviceTimeState == DeviceTimeState.INCORRECT) {
35+
Timber.e("DeviceTime INCORRECT")
36+
throw SrsSubmissionException(ErrorCode.DEVICE_TIME_INCORRECT)
37+
}
38+
39+
if (deviceTimeState == DeviceTimeState.ASSUMED_CORRECT) {
40+
Timber.e("DeviceTime ASSUMED_CORRECT")
41+
throw SrsSubmissionException(ErrorCode.DEVICE_TIME_UNVERIFIED)
42+
}
43+
44+
val reliableDuration = Duration.between(
45+
cwaSettings.firstReliableDeviceTime.first(),
46+
timeStamper.nowUTC
47+
)
48+
val onboardingInHours = selfReportSubmissionCommon.timeSinceOnboardingInHours
49+
if (reliableDuration < onboardingInHours) {
50+
Timber.e(
51+
"Time since onboarding is unverified reliableDuration=%s, configDuration=%s",
52+
reliableDuration,
53+
onboardingInHours
54+
)
55+
throw SrsSubmissionException(ErrorCode.TIME_SINCE_ONBOARDING_UNVERIFIED)
56+
}
57+
58+
val durationSinceSubmission = Duration.between(
59+
srsSubmissionSettings.getMostRecentSubmissionTime(),
60+
timeStamper.nowUTC
61+
)
62+
63+
val submissionsInDays = selfReportSubmissionCommon.timeBetweenSubmissionsInDays
64+
if (durationSinceSubmission < submissionsInDays) {
65+
Timber.e(
66+
"Submission is too early durationSinceSubmission=%s, configDuration=%s",
67+
durationSinceSubmission,
68+
submissionsInDays
69+
)
70+
throw SrsSubmissionException(ErrorCode.SUBMISSION_TOO_EARLY)
71+
}
72+
73+
Timber.d("Local prerequisites are met -> Congratulations!")
2174
}
2275
}

Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/mapping/ConfigParserTest.kt

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig
1010
import de.rki.coronawarnapp.appconfig.KeyDownloadConfig
1111
import de.rki.coronawarnapp.appconfig.LogUploadConfig
1212
import de.rki.coronawarnapp.appconfig.PresenceTracingConfig
13+
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfig
1314
import de.rki.coronawarnapp.appconfig.SurveyConfig
1415
import io.kotest.assertions.throwables.shouldThrow
1516
import io.kotest.matchers.shouldBe
@@ -36,6 +37,7 @@ class ConfigParserTest : BaseTest() {
3637
@MockK lateinit var presenceTracingConfigMapper: PresenceTracingConfig.Mapper
3738
@MockK lateinit var coronaTestConfigMapper: CoronaTestConfig.Mapper
3839
@MockK lateinit var covidCertificateConfigMapper: CovidCertificateConfig.Mapper
40+
@MockK lateinit var selfReportSubmissionConfigMapper: SelfReportSubmissionConfig.Mapper
3941

4042
private val appConfig171 = File("src/test/resources/appconfig_1_7_1.bin")
4143
private val appConfig180 = File("src/test/resources/appconfig_1_8_0.bin")
@@ -54,6 +56,7 @@ class ConfigParserTest : BaseTest() {
5456
every { presenceTracingConfigMapper.map(any()) } returns mockk()
5557
every { coronaTestConfigMapper.map(any()) } returns mockk()
5658
every { covidCertificateConfigMapper.map(any()) } returns mockk()
59+
every { selfReportSubmissionConfigMapper.map(any()) } returns mockk()
5760

5861
appConfig171.exists() shouldBe true
5962
appConfig180.exists() shouldBe true
@@ -70,6 +73,7 @@ class ConfigParserTest : BaseTest() {
7073
presenceTracingConfigMapper = presenceTracingConfigMapper,
7174
coronaTestConfigMapper = coronaTestConfigMapper,
7275
covidCertificateConfigMapper = covidCertificateConfigMapper,
76+
selfReportSubmissionConfigMapper = selfReportSubmissionConfigMapper,
7377
)
7478

7579
@Test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package de.rki.coronawarnapp.srs.core
2+
3+
import de.rki.coronawarnapp.appconfig.AppConfigProvider
4+
import de.rki.coronawarnapp.appconfig.ConfigData
5+
import de.rki.coronawarnapp.appconfig.SelfReportSubmissionConfigContainer
6+
import de.rki.coronawarnapp.main.CWASettings
7+
import de.rki.coronawarnapp.srs.core.error.SrsSubmissionException
8+
import de.rki.coronawarnapp.srs.core.storage.SrsSubmissionSettings
9+
import de.rki.coronawarnapp.util.TimeStamper
10+
import io.kotest.assertions.throwables.shouldNotThrow
11+
import io.kotest.assertions.throwables.shouldThrow
12+
import io.kotest.matchers.shouldBe
13+
import io.mockk.MockKAnnotations
14+
import io.mockk.coEvery
15+
import io.mockk.every
16+
import io.mockk.impl.annotations.MockK
17+
import io.mockk.mockk
18+
import kotlinx.coroutines.flow.flowOf
19+
import kotlinx.coroutines.test.runTest
20+
import org.junit.jupiter.api.BeforeEach
21+
import org.junit.jupiter.api.Test
22+
23+
import testhelpers.BaseTest
24+
import java.time.Instant
25+
26+
internal class SrsLocalCheckerTest : BaseTest() {
27+
28+
@MockK lateinit var srsSubmissionSettings: SrsSubmissionSettings
29+
@MockK lateinit var appConfigProvider: AppConfigProvider
30+
@MockK lateinit var cwaSettings: CWASettings
31+
@MockK lateinit var timeStamper: TimeStamper
32+
33+
@BeforeEach
34+
fun setUp() {
35+
MockKAnnotations.init(this)
36+
every { timeStamper.nowUTC } returns Instant.parse("2022-11-02T14:01:22Z")
37+
every { cwaSettings.firstReliableDeviceTime } returns flowOf(Instant.parse("2022-10-02T14:01:22Z"))
38+
coEvery { srsSubmissionSettings.getMostRecentSubmissionTime() } returns
39+
Instant.parse("2022-08-02T14:01:22Z")
40+
coEvery { appConfigProvider.getAppConfig() } returns config()
41+
}
42+
43+
@Test
44+
fun `check pass`() = runTest {
45+
shouldNotThrow<SrsSubmissionException> {
46+
instance().check()
47+
}
48+
}
49+
50+
@Test
51+
fun `device time is incorrect`() = runTest {
52+
coEvery { appConfigProvider.getAppConfig() } returns config(ConfigData.DeviceTimeState.INCORRECT)
53+
shouldThrow<SrsSubmissionException> {
54+
instance().check()
55+
}.errorCode shouldBe SrsSubmissionException.ErrorCode.DEVICE_TIME_INCORRECT
56+
}
57+
58+
@Test
59+
fun `device time is assumed correct`() = runTest {
60+
coEvery { appConfigProvider.getAppConfig() } returns config(ConfigData.DeviceTimeState.ASSUMED_CORRECT)
61+
shouldThrow<SrsSubmissionException> {
62+
instance().check()
63+
}.errorCode shouldBe SrsSubmissionException.ErrorCode.DEVICE_TIME_UNVERIFIED
64+
}
65+
66+
@Test
67+
fun `Time since onboarding is unverified`() = runTest {
68+
every { cwaSettings.firstReliableDeviceTime } returns flowOf(Instant.parse("2022-11-02T10:01:22Z"))
69+
shouldThrow<SrsSubmissionException> {
70+
instance().check()
71+
}.errorCode shouldBe SrsSubmissionException.ErrorCode.TIME_SINCE_ONBOARDING_UNVERIFIED
72+
}
73+
74+
@Test
75+
fun `Time since last submission is too early`() = runTest {
76+
coEvery { srsSubmissionSettings.getMostRecentSubmissionTime() } returns Instant.parse("2022-11-02T10:01:22Z")
77+
shouldThrow<SrsSubmissionException> {
78+
instance().check()
79+
}.errorCode shouldBe SrsSubmissionException.ErrorCode.SUBMISSION_TOO_EARLY
80+
}
81+
82+
private fun instance() = SrsLocalChecker(
83+
srsSubmissionSettings = srsSubmissionSettings,
84+
appConfigProvider = appConfigProvider,
85+
cwaSettings = cwaSettings,
86+
timeStamper = timeStamper,
87+
)
88+
89+
private fun config(
90+
state: ConfigData.DeviceTimeState = ConfigData.DeviceTimeState.CORRECT
91+
) = mockk<ConfigData>().apply {
92+
every { selfReportSubmission } returns SelfReportSubmissionConfigContainer.DEFAULT
93+
every { deviceTimeState } returns state
94+
}
95+
}

0 commit comments

Comments
 (0)