Skip to content
Snippets Groups Projects
Unverified Commit f98ac2dd authored by Fabian-K's avatar Fabian-K Committed by GitHub
Browse files

plausible deniability (#946)

* Added requestPadding property to all VerficationService requests

* Added fake calls to the WebReqeustBuilder

* :construction:

 initial draft to enable plausible deniability

* Switched from SubmissionConstants to KeyType enum everywhere

* basic playbook implementation with fake and real requests

* Playbook
- ensure request pattern for playbooks is always the same

VerificationService
- apply padding to ensure equal request size (header & body)

SecurityHelper
- extract hash256 to HashHelper. This simplifies tests that use only the hash function (and therefore don´t need to initialize SecurityHelper and its dependencies)

* Implemented random chance of dummy playbook execution on app open

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* Playbook
- ignore exceptions for fake requests

SubmissionService
- add padding header to fake request for same header size

WebRequestBuilder
- include fake keys in upload (:construction_site:

)

* DiagnosisKeyService: removed (low value & difficult to test)

SubmissionService & SubmitDiagnosisKeysTransaction
- inline playbook & backgroundNoise property to prevent issues during testing

DiagnosisKeyConstantsTest, SubmissionServiceTest, SubmitDiagnosisKeysTransactionTest & SubmissionViewModelTest
- adjusted to changes

* Dummy playbook will now be repeated and delayed randomly

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* Linting

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* Initial Code for background noise worker

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* First implementation of noise background worker

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* Linting

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* PlaybookImpl
- ensure that fake requests are executed when real requests fail

SubmissionService & VerificationService
- adjust header name for padding

WebRequestBuilder
- add padding to dummy submission

* BackgroundNoise is now trigger-only

PlaybookImpl
- include follow-up executions after every playbook
- logging

SubmissionViewModel.kt, SubmissionService.kt, SubmitDiagnosisKeysTransaction.kt, MainActivity.kt, BackgroundNoisePeriodicWorker.kt, DiagnosisTestResultRetrievalPeriodicWorker.kt
- propagate context for coroutine

VerificationService
- ensure body size of 1000

* WebRequestBuilder.kt
- adjust fake key generation

PlaybookImplTest.kt
- remove unused server.enqueue

SubmissionService.kt 6 SubmitDiagnosisKeysTransaction.kt
- remove commented out code

* revert temporary changes to SubmissionResultPositiveOtherWarningFragment.kt

* Background job scheduling implemented

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* - adjust fake key size
- remove temporary comment

* Moved build work calls to own file to fix linting

Signed-off-by: default avatarKolya Opahle <k.opahle@sap.com>

* - initialize coroutine scope within the playbook, revert passing it from outside
- remove experimental test dependency for coroutines

* - use single endpoint per server for fake requests
- reduce request size from 1000 to 250 for the verification server
- include dummy registration token in fake request to fulfill verification on server side
- prepare for randomized count of submitted keys
- always include headers cwa-authorization & cwa-header-padding for submission server

* - simplify empty header using constant

Co-authored-by: default avatarKolya Opahle <k.opahle@sap.com>
parent 53e457f0
No related branches found
No related tags found
No related merge requests found
Showing
with 679 additions and 104 deletions
package de.rki.coronawarnapp.worker
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler.stop
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import timber.log.Timber
/**
* Periodic background noise worker
*
* @see BackgroundWorkScheduler
*/
class BackgroundNoisePeriodicWorker(
val context: Context,
workerParams: WorkerParameters
) :
CoroutineWorker(context, workerParams) {
companion object {
private val TAG: String? = BackgroundNoisePeriodicWorker::class.simpleName
}
/**
* Work execution
*
* @return Result
*
* @see BackgroundConstants.NUMBER_OF_DAYS_TO_RUN_PLAYBOOK
*/
override suspend fun doWork(): Result {
Timber.d("Background job started. Run attempt: $runAttemptCount")
var result = Result.success()
try {
val initialPairingDate = DateTime(
LocalData.devicePairingSuccessfulTimestamp(),
DateTimeZone.UTC
)
// Check if the numberOfDaysToRunPlaybook are over
if (initialPairingDate.plusDays(BackgroundConstants.NUMBER_OF_DAYS_TO_RUN_PLAYBOOK).isBeforeNow) {
stopWorker()
return result
}
BackgroundWorkScheduler.scheduleBackgroundNoiseOneTimeWork()
} catch (e: Exception) {
result = if (runAttemptCount > BackgroundConstants.WORKER_RETRY_COUNT_THRESHOLD) {
Result.failure()
} else {
Result.retry()
}
}
return result
}
private fun stopWorker() {
BackgroundWorkScheduler.WorkType.BACKGROUND_NOISE_PERIODIC_WORK.stop()
}
}
package de.rki.coronawarnapp.worker
import androidx.work.BackoffPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler.WorkTag
import java.util.concurrent.TimeUnit
/**
* Build diagnosis key periodic work request
* Set "kind delay" for accessibility reason.
* Backoff criteria set to Linear type.
*
* @return PeriodicWorkRequest
*
* @see WorkTag.DIAGNOSIS_KEY_RETRIEVAL_PERIODIC_WORKER
* @see BackgroundConstants.KIND_DELAY
* @see BackgroundConstants.BACKOFF_INITIAL_DELAY
* @see BackoffPolicy.LINEAR
*/
fun buildDiagnosisKeyRetrievalPeriodicWork() =
PeriodicWorkRequestBuilder<DiagnosisKeyRetrievalPeriodicWorker>(
BackgroundWorkHelper.getDiagnosisKeyRetrievalPeriodicWorkTimeInterval(), TimeUnit.MINUTES
)
.addTag(WorkTag.DIAGNOSIS_KEY_RETRIEVAL_PERIODIC_WORKER.tag)
.setInitialDelay(
BackgroundConstants.KIND_DELAY,
TimeUnit.MINUTES
)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
BackgroundConstants.BACKOFF_INITIAL_DELAY,
TimeUnit.MINUTES
)
.build()
/**
* Build diagnosis key one time work request
* Set random initial delay for security reason.
* Backoff criteria set to Linear type.
*
* @return OneTimeWorkRequest
*
* @see WorkTag.DIAGNOSIS_KEY_RETRIEVAL_ONE_TIME_WORKER
* @see buildDiagnosisKeyRetrievalOneTimeWork
* @see BackgroundConstants.BACKOFF_INITIAL_DELAY
* @see BackoffPolicy.LINEAR
*/
fun buildDiagnosisKeyRetrievalOneTimeWork() =
OneTimeWorkRequestBuilder<DiagnosisKeyRetrievalOneTimeWorker>()
.addTag(WorkTag.DIAGNOSIS_KEY_RETRIEVAL_ONE_TIME_WORKER.tag)
.setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork())
.setInitialDelay(
BackgroundConstants.KIND_DELAY,
TimeUnit.MINUTES
)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
BackgroundConstants.BACKOFF_INITIAL_DELAY,
TimeUnit.MINUTES
)
.build()
/**
* Build diagnosis Test Result periodic work request
* Set "kind delay" for accessibility reason.
*
* @return PeriodicWorkRequest
*
* @see WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER
* @see BackgroundConstants.KIND_DELAY
*/
fun buildDiagnosisTestResultRetrievalPeriodicWork() =
PeriodicWorkRequestBuilder<DiagnosisTestResultRetrievalPeriodicWorker>(
BackgroundWorkHelper.getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval(),
TimeUnit.MINUTES
)
.addTag(WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER.tag)
.setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork())
.setInitialDelay(
BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_INITIAL_DELAY,
TimeUnit.SECONDS
).setBackoffCriteria(
BackoffPolicy.LINEAR,
BackgroundConstants.KIND_DELAY,
TimeUnit.MINUTES
)
.build()
/**
* Build background noise one time work request
* Set BackgroundNoiseOneTimeWorkDelay for timing randomness.
*
* @return PeriodicWorkRequest
*
* @see WorkTag.BACKGROUND_NOISE_ONE_TIME_WORKER
* @see BackgroundWorkHelper.getBackgroundNoiseOneTimeWorkDelay
*/
fun buildBackgroundNoiseOneTimeWork() =
OneTimeWorkRequestBuilder<BackgroundNoiseOneTimeWorker>()
.addTag(WorkTag.BACKGROUND_NOISE_ONE_TIME_WORKER.tag)
.setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork())
.setInitialDelay(
BackgroundWorkHelper.getBackgroundNoiseOneTimeWorkDelay(),
TimeUnit.HOURS
).setBackoffCriteria(
BackoffPolicy.LINEAR,
BackgroundConstants.KIND_DELAY,
TimeUnit.MINUTES
)
.build()
/**
* Build background noise periodic work request
* Set "kind delay" for accessibility reason.
*
* @return PeriodicWorkRequest
*
* @see BackgroundConstants.MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION
* @see WorkTag.BACKGROUND_NOISE_PERIODIC_WORKER
* @see BackgroundConstants.KIND_DELAY
*/
fun buildBackgroundNoisePeriodicWork() =
PeriodicWorkRequestBuilder<BackgroundNoisePeriodicWorker>(
BackgroundConstants.MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION,
TimeUnit.HOURS
)
.addTag(WorkTag.BACKGROUND_NOISE_PERIODIC_WORKER.tag)
.setInitialDelay(
BackgroundConstants.KIND_DELAY,
TimeUnit.SECONDS
).setBackoffCriteria(
BackoffPolicy.LINEAR,
BackgroundConstants.KIND_DELAY,
TimeUnit.MINUTES
)
.build()
...@@ -5,6 +5,7 @@ import androidx.work.Constraints ...@@ -5,6 +5,7 @@ import androidx.work.Constraints
import androidx.work.NetworkType import androidx.work.NetworkType
import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.notification.NotificationHelper
import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.LocalData
import kotlin.random.Random
/** /**
* Singleton class for background work helper functions * Singleton class for background work helper functions
...@@ -49,6 +50,22 @@ object BackgroundWorkHelper { ...@@ -49,6 +50,22 @@ object BackgroundWorkHelper {
BackgroundConstants.DIAGNOSIS_KEY_RETRIEVAL_TRIES_PER_DAY BackgroundConstants.DIAGNOSIS_KEY_RETRIEVAL_TRIES_PER_DAY
.coerceAtMost(BackgroundConstants.GOOGLE_API_MAX_CALLS_PER_DAY) .coerceAtMost(BackgroundConstants.GOOGLE_API_MAX_CALLS_PER_DAY)
/**
* Get background noise one time work delay
* The periodic job is already delayed by MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION
* so we only need to delay further by the difference between min and max.
*
* @return Long
*
* @see BackgroundConstants.MAX_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION
* @see BackgroundConstants.MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION
*/
fun getBackgroundNoiseOneTimeWorkDelay() = Random.nextLong(
0,
BackgroundConstants.MAX_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION -
BackgroundConstants.MIN_HOURS_TO_NEXT_BACKGROUND_NOISE_EXECUTION
)
/** /**
* Constraints for diagnosis key one time work * Constraints for diagnosis key one time work
* Requires battery not low and any network connection * Requires battery not low and any network connection
......
package de.rki.coronawarnapp.worker package de.rki.coronawarnapp.worker
import androidx.work.BackoffPolicy
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.Operation import androidx.work.Operation
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkInfo
import de.rki.coronawarnapp.BuildConfig import de.rki.coronawarnapp.BuildConfig
import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.CoronaWarnApplication
import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.LocalData
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
/** /**
* Singleton class for background work handling * Singleton class for background work handling
...@@ -32,11 +28,15 @@ object BackgroundWorkScheduler { ...@@ -32,11 +28,15 @@ object BackgroundWorkScheduler {
* @see BackgroundConstants.DIAGNOSIS_KEY_ONE_TIME_WORKER_TAG * @see BackgroundConstants.DIAGNOSIS_KEY_ONE_TIME_WORKER_TAG
* @see BackgroundConstants.DIAGNOSIS_KEY_PERIODIC_WORKER_TAG * @see BackgroundConstants.DIAGNOSIS_KEY_PERIODIC_WORKER_TAG
* @see BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER_TAG * @see BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER_TAG
* @see BackgroundConstants.BACKGROUND_NOISE_ONE_TIME_WORKER_TAG
* @see BackgroundConstants.BACKGROUND_NOISE_PERIODIC_WORKER_TAG
*/ */
enum class WorkTag(val tag: String) { enum class WorkTag(val tag: String) {
DIAGNOSIS_KEY_RETRIEVAL_ONE_TIME_WORKER(BackgroundConstants.DIAGNOSIS_KEY_ONE_TIME_WORKER_TAG), DIAGNOSIS_KEY_RETRIEVAL_ONE_TIME_WORKER(BackgroundConstants.DIAGNOSIS_KEY_ONE_TIME_WORKER_TAG),
DIAGNOSIS_KEY_RETRIEVAL_PERIODIC_WORKER(BackgroundConstants.DIAGNOSIS_KEY_PERIODIC_WORKER_TAG), DIAGNOSIS_KEY_RETRIEVAL_PERIODIC_WORKER(BackgroundConstants.DIAGNOSIS_KEY_PERIODIC_WORKER_TAG),
DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER(BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER_TAG) DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER(BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER_TAG),
BACKGROUND_NOISE_ONE_TIME_WORKER(BackgroundConstants.BACKGROUND_NOISE_ONE_TIME_WORKER_TAG),
BACKGROUND_NOISE_PERIODIC_WORKER(BackgroundConstants.BACKGROUND_NOISE_PERIODIC_WORKER_TAG)
} }
/** /**
...@@ -47,11 +47,15 @@ object BackgroundWorkScheduler { ...@@ -47,11 +47,15 @@ object BackgroundWorkScheduler {
* @see BackgroundConstants.DIAGNOSIS_KEY_ONE_TIME_WORK_NAME * @see BackgroundConstants.DIAGNOSIS_KEY_ONE_TIME_WORK_NAME
* @see BackgroundConstants.DIAGNOSIS_KEY_PERIODIC_WORK_NAME * @see BackgroundConstants.DIAGNOSIS_KEY_PERIODIC_WORK_NAME
* @see BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORK_NAME * @see BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORK_NAME
* @see BackgroundConstants.BACKGROUND_NOISE_PERIODIC_WORK_NAME
* @see BackgroundConstants.BACKGROUND_NOISE_ONE_TIME_WORK_NAME
*/ */
enum class WorkType(val uniqueName: String) { enum class WorkType(val uniqueName: String) {
DIAGNOSIS_KEY_BACKGROUND_ONE_TIME_WORK(BackgroundConstants.DIAGNOSIS_KEY_ONE_TIME_WORK_NAME), DIAGNOSIS_KEY_BACKGROUND_ONE_TIME_WORK(BackgroundConstants.DIAGNOSIS_KEY_ONE_TIME_WORK_NAME),
DIAGNOSIS_KEY_BACKGROUND_PERIODIC_WORK(BackgroundConstants.DIAGNOSIS_KEY_PERIODIC_WORK_NAME), DIAGNOSIS_KEY_BACKGROUND_PERIODIC_WORK(BackgroundConstants.DIAGNOSIS_KEY_PERIODIC_WORK_NAME),
DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER(BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORK_NAME) DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER(BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_WORK_NAME),
BACKGROUND_NOISE_PERIODIC_WORK(BackgroundConstants.BACKGROUND_NOISE_PERIODIC_WORK_NAME),
BACKGROUND_NOISE_ONE_TIME_WORK(BackgroundConstants.BACKGROUND_NOISE_ONE_TIME_WORK_NAME)
} }
/** /**
...@@ -162,6 +166,24 @@ object BackgroundWorkScheduler { ...@@ -162,6 +166,24 @@ object BackgroundWorkScheduler {
WorkType.DIAGNOSIS_KEY_BACKGROUND_ONE_TIME_WORK.start() WorkType.DIAGNOSIS_KEY_BACKGROUND_ONE_TIME_WORK.start()
} }
/**
* Schedule background noise periodic work
*
* @see WorkType.BACKGROUND_NOISE_PERIODIC_WORK
*/
fun scheduleBackgroundNoisePeriodicWork() {
WorkType.BACKGROUND_NOISE_PERIODIC_WORK.start()
}
/**
* Schedule background noise one time work
*
* @see WorkType.DIAGNOSIS_KEY_BACKGROUND_ONE_TIME_WORK
*/
fun scheduleBackgroundNoiseOneTimeWork() {
WorkType.DIAGNOSIS_KEY_BACKGROUND_ONE_TIME_WORK.start()
}
/** /**
* Enqueue operation for work type defined in WorkType enum class * Enqueue operation for work type defined in WorkType enum class
* *
...@@ -173,6 +195,8 @@ object BackgroundWorkScheduler { ...@@ -173,6 +195,8 @@ object BackgroundWorkScheduler {
WorkType.DIAGNOSIS_KEY_BACKGROUND_PERIODIC_WORK -> enqueueDiagnosisKeyBackgroundPeriodicWork() WorkType.DIAGNOSIS_KEY_BACKGROUND_PERIODIC_WORK -> enqueueDiagnosisKeyBackgroundPeriodicWork()
WorkType.DIAGNOSIS_KEY_BACKGROUND_ONE_TIME_WORK -> enqueueDiagnosisKeyBackgroundOneTimeWork() WorkType.DIAGNOSIS_KEY_BACKGROUND_ONE_TIME_WORK -> enqueueDiagnosisKeyBackgroundOneTimeWork()
WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER -> enqueueDiagnosisTestResultBackgroundPeriodicWork() WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER -> enqueueDiagnosisTestResultBackgroundPeriodicWork()
WorkType.BACKGROUND_NOISE_PERIODIC_WORK -> enqueueBackgroundNoisePeriodicWork()
WorkType.BACKGROUND_NOISE_ONE_TIME_WORK -> enqueueBackgroundNoiseOneTimeWork()
} }
/** /**
...@@ -220,84 +244,34 @@ object BackgroundWorkScheduler { ...@@ -220,84 +244,34 @@ object BackgroundWorkScheduler {
).also { it.logOperationSchedule(WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER) } ).also { it.logOperationSchedule(WorkType.DIAGNOSIS_TEST_RESULT_PERIODIC_WORKER) }
/** /**
* Build diagnosis key periodic work request * Enqueue background noise periodic
* Set "kind delay" for accessibility reason. * Replace with new if older work exists.
* Backoff criteria set to Linear type.
*
* @return PeriodicWorkRequest
*
* @see WorkTag.DIAGNOSIS_KEY_RETRIEVAL_PERIODIC_WORKER
* @see BackgroundConstants.KIND_DELAY
* @see BackgroundConstants.BACKOFF_INITIAL_DELAY
* @see BackoffPolicy.LINEAR
*/
private fun buildDiagnosisKeyRetrievalPeriodicWork() =
PeriodicWorkRequestBuilder<DiagnosisKeyRetrievalPeriodicWorker>(
BackgroundWorkHelper.getDiagnosisKeyRetrievalPeriodicWorkTimeInterval(), TimeUnit.MINUTES
)
.addTag(WorkTag.DIAGNOSIS_KEY_RETRIEVAL_PERIODIC_WORKER.tag)
.setInitialDelay(
BackgroundConstants.KIND_DELAY,
TimeUnit.MINUTES
)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
BackgroundConstants.BACKOFF_INITIAL_DELAY,
TimeUnit.MINUTES
)
.build()
/**
* Build diagnosis key one time work request
* Set random initial delay for security reason.
* Backoff criteria set to Linear type.
* *
* @return OneTimeWorkRequest * @return Operation
* *
* @see WorkTag.DIAGNOSIS_KEY_RETRIEVAL_ONE_TIME_WORKER * @see WorkType.BACKGROUND_NOISE_PERIODIC_WORK
* @see buildDiagnosisKeyRetrievalOneTimeWork
* @see BackgroundConstants.BACKOFF_INITIAL_DELAY
* @see BackoffPolicy.LINEAR
*/ */
private fun buildDiagnosisKeyRetrievalOneTimeWork() = private fun enqueueBackgroundNoisePeriodicWork() =
OneTimeWorkRequestBuilder<DiagnosisKeyRetrievalOneTimeWorker>() workManager.enqueueUniquePeriodicWork(
.addTag(WorkTag.DIAGNOSIS_KEY_RETRIEVAL_ONE_TIME_WORKER.tag) WorkType.BACKGROUND_NOISE_PERIODIC_WORK.uniqueName,
.setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork()) ExistingPeriodicWorkPolicy.REPLACE,
.setInitialDelay( buildBackgroundNoisePeriodicWork()
BackgroundConstants.KIND_DELAY, ).also { it.logOperationSchedule(WorkType.BACKGROUND_NOISE_PERIODIC_WORK) }
TimeUnit.MINUTES
)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
BackgroundConstants.BACKOFF_INITIAL_DELAY,
TimeUnit.MINUTES
)
.build()
/** /**
* Build diagnosis Test Result periodic work request * Enqueue background noise one time
* Set "kind delay" for accessibility reason. * Replace with new if older work exists.
* *
* @return PeriodicWorkRequest * @return Operation
* *
* @see WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER * @see WorkType.BACKGROUND_NOISE_ONE_TIME_WORK
* @see BackgroundConstants.KIND_DELAY
*/ */
private fun buildDiagnosisTestResultRetrievalPeriodicWork() = private fun enqueueBackgroundNoiseOneTimeWork() =
PeriodicWorkRequestBuilder<DiagnosisTestResultRetrievalPeriodicWorker>( workManager.enqueueUniqueWork(
BackgroundWorkHelper.getDiagnosisTestResultRetrievalPeriodicWorkTimeInterval(), TimeUnit.MINUTES WorkType.BACKGROUND_NOISE_ONE_TIME_WORK.uniqueName,
) ExistingWorkPolicy.REPLACE,
.addTag(WorkTag.DIAGNOSIS_TEST_RESULT_RETRIEVAL_PERIODIC_WORKER.tag) buildBackgroundNoiseOneTimeWork()
.setConstraints(BackgroundWorkHelper.getConstraintsForDiagnosisKeyOneTimeBackgroundWork()) ).also { it.logOperationSchedule(WorkType.BACKGROUND_NOISE_ONE_TIME_WORK) }
.setInitialDelay(
BackgroundConstants.DIAGNOSIS_TEST_RESULT_PERIODIC_INITIAL_DELAY,
TimeUnit.SECONDS
).setBackoffCriteria(
BackoffPolicy.LINEAR,
BackgroundConstants.KIND_DELAY,
TimeUnit.MINUTES
)
.build()
/** /**
* Log operation schedule * Log operation schedule
......
package de.rki.coronawarnapp.http.playbook
import de.rki.coronawarnapp.exception.http.InternalServerErrorException
import de.rki.coronawarnapp.service.submission.KeyType
import de.rki.coronawarnapp.util.newWebRequestBuilder
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.hamcrest.Matchers.equalTo
import org.junit.Assert.fail
import org.junit.Test
class PlaybookImplTest {
@Test
fun hasRequestPattern_initialRegistration(): Unit = runBlocking {
val server = MockWebServer()
server.start()
server.enqueue(MockResponse().setBody("""{"registrationToken":"response"}"""))
server.enqueue(MockResponse().setBody("{}"))
server.enqueue(MockResponse().setBody("{}"))
PlaybookImpl(server.newWebRequestBuilder())
.initialRegistration("9A3B578UMG", KeyType.TELETAN)
// ensure request order is 2x verification and 1x submission
assertRequestPattern(server)
}
@Test
fun hasRequestPattern_submission(): Unit = runBlocking {
val server = MockWebServer()
server.start()
server.enqueue(MockResponse().setBody("""{"tan":"response"}"""))
server.enqueue(MockResponse().setBody("{}"))
server.enqueue(MockResponse().setBody("{}"))
PlaybookImpl(server.newWebRequestBuilder())
.submission("token", listOf())
// ensure request order is 2x verification and 1x submission
assertRequestPattern(server)
}
@Test
fun hasRequestPattern_testResult(): Unit = runBlocking {
val server = MockWebServer()
server.start()
server.enqueue(MockResponse().setBody("""{"testResult":0}"""))
server.enqueue(MockResponse().setBody("{}"))
server.enqueue(MockResponse().setBody("{}"))
PlaybookImpl(server.newWebRequestBuilder())
.testResult("token")
// ensure request order is 2x verification and 1x submission
assertRequestPattern(server)
}
@Test
fun hasRequestPattern_dummy(): Unit = runBlocking {
val server = MockWebServer()
server.start()
server.enqueue(MockResponse().setBody("{}"))
server.enqueue(MockResponse().setBody("{}"))
server.enqueue(MockResponse().setBody("{}"))
PlaybookImpl(server.newWebRequestBuilder())
.dummy()
// ensure request order is 2x verification and 1x submission
assertRequestPattern(server)
}
@Test
fun shouldIgnoreFailuresForDummyRequests(): Unit = runBlocking {
val server = MockWebServer()
server.start()
val expectedRegistrationToken = "token"
server.enqueue(MockResponse().setBody("""{"registrationToken":"$expectedRegistrationToken"}"""))
server.enqueue(MockResponse().setResponseCode(500))
server.enqueue(MockResponse().setResponseCode(500))
val registrationToken = PlaybookImpl(server.newWebRequestBuilder())
.initialRegistration("key", KeyType.GUID)
assertThat(registrationToken, equalTo(expectedRegistrationToken))
}
@Test
fun hasRequestPatternWhenRealRequestFails_initialRegistration(): Unit = runBlocking {
val server = MockWebServer()
server.start()
server.enqueue(MockResponse().setResponseCode(500))
server.enqueue(MockResponse().setBody("{}"))
server.enqueue(MockResponse().setBody("{}"))
try {
PlaybookImpl(server.newWebRequestBuilder())
.initialRegistration("9A3B578UMG", KeyType.TELETAN)
fail("exception propagation expected")
} catch (e: InternalServerErrorException) {
}
// ensure request order is 2x verification and 1x submission
assertRequestPattern(server)
}
@Test
fun hasRequestPatternWhenRealRequestFails_testResult(): Unit = runBlocking {
val server = MockWebServer()
server.start()
server.enqueue(MockResponse().setResponseCode(500))
server.enqueue(MockResponse().setBody("{}"))
server.enqueue(MockResponse().setBody("{}"))
try {
PlaybookImpl(server.newWebRequestBuilder())
.testResult("token")
fail("exception propagation expected")
} catch (e: InternalServerErrorException) {
}
// ensure request order is 2x verification and 1x submission
assertRequestPattern(server)
}
@Test
fun hasRequestPatternWhenRealRequestFails_submission(): Unit = runBlocking {
val server = MockWebServer()
server.start()
server.enqueue(MockResponse().setResponseCode(500))
server.enqueue(MockResponse().setBody("{}"))
server.enqueue(MockResponse().setBody("{}"))
try {
PlaybookImpl(server.newWebRequestBuilder())
.submission("token", listOf())
fail("exception propagation expected")
} catch (e: InternalServerErrorException) {
}
// ensure request order is 2x verification and 1x submission
assertRequestPattern(server)
}
private fun assertRequestPattern(server: MockWebServer) {
assertThat(server.takeRequest().path, Matchers.startsWith("/verification/"))
assertThat(server.takeRequest().path, Matchers.startsWith("/verification/"))
assertThat(server.takeRequest().path, Matchers.startsWith("/submission/"))
}
}
\ No newline at end of file
package de.rki.coronawarnapp.http.service
import de.rki.coronawarnapp.util.headerSizeIgnoringContentLength
import de.rki.coronawarnapp.util.newWebRequestBuilder
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.Assert
import org.junit.Test
class SubmissionServiceTest {
@Test
fun allRequestHaveSameFootprintForPlausibleDeniability(): Unit = runBlocking {
val server = MockWebServer()
server.start()
val webRequestBuilder = server.newWebRequestBuilder()
val authCodeExample = "39ec4930-7a1f-4d5d-921f-bfad3b6f1269"
server.enqueue(MockResponse().setBody("{}"))
webRequestBuilder.asyncSubmitKeysToServer(authCodeExample, listOf())
server.enqueue(MockResponse().setBody("{}"))
webRequestBuilder.asyncFakeSubmission()
val requests = listOf(
server.takeRequest(),
server.takeRequest()
)
// ensure all request have same size (header & body)
requests.zipWithNext().forEach { (a, b) ->
Assert.assertEquals(
"Header size mismatch: ",
a.headerSizeIgnoringContentLength(),
b.headerSizeIgnoringContentLength()
)
}
}
}
package de.rki.coronawarnapp.http.service
import de.rki.coronawarnapp.service.submission.KeyType
import de.rki.coronawarnapp.util.headerSizeIgnoringContentLength
import de.rki.coronawarnapp.util.newWebRequestBuilder
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.Test
class VerificationServiceTest {
@Test
fun allRequestHaveSameFootprintForPlausibleDeniability(): Unit = runBlocking {
val server = MockWebServer()
server.start()
val webRequestBuilder = server.newWebRequestBuilder()
val guidExample = "3BF1D4-1C6003DD-733D-41F1-9F30-F85FA7406BF7"
val teletanExample = "9A3B578UMG"
val registrationTokenExample = "63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f"
server.enqueue(MockResponse().setBody("{}"))
webRequestBuilder.asyncGetRegistrationToken(guidExample, KeyType.GUID)
server.enqueue(MockResponse().setBody("{}"))
webRequestBuilder.asyncGetRegistrationToken(teletanExample, KeyType.TELETAN)
server.enqueue(MockResponse().setBody("{}"))
webRequestBuilder.asyncGetTestResult(registrationTokenExample)
server.enqueue(MockResponse().setBody("{}"))
webRequestBuilder.asyncGetTan(registrationTokenExample)
server.enqueue(MockResponse().setBody("{}"))
webRequestBuilder.asyncFakeVerification()
val requests = listOf(
server.takeRequest(),
server.takeRequest(),
server.takeRequest(),
server.takeRequest(),
server.takeRequest()
)
// ensure all request have same size (header & body)
requests.forEach { assertThat(it.bodySize, equalTo(250L)) }
requests.zipWithNext().forEach { (a, b) ->
assertThat(
a.headerSizeIgnoringContentLength(),
equalTo(b.headerSizeIgnoringContentLength())
)
}
}
}
\ No newline at end of file
...@@ -9,15 +9,15 @@ class DiagnosisKeyConstantsTest { ...@@ -9,15 +9,15 @@ class DiagnosisKeyConstantsTest {
fun allDiagnosisKeyConstants() { fun allDiagnosisKeyConstants() {
Assert.assertEquals(DiagnosisKeyConstants.HOUR, "hour") Assert.assertEquals(DiagnosisKeyConstants.HOUR, "hour")
Assert.assertEquals(DiagnosisKeyConstants.SERVER_ERROR_CODE_403, 403) Assert.assertEquals(DiagnosisKeyConstants.SERVER_ERROR_CODE_403, 403)
Assert.assertEquals(DiagnosisKeyConstants.INDEX_DOWNLOAD_URL, "/version/v1/index.txt") Assert.assertEquals(DiagnosisKeyConstants.INDEX_DOWNLOAD_URL, "version/v1/index.txt")
Assert.assertEquals(DiagnosisKeyConstants.DIAGNOSIS_KEYS_DOWNLOAD_URL, "/version/v1/diagnosis-keys") Assert.assertEquals(DiagnosisKeyConstants.DIAGNOSIS_KEYS_DOWNLOAD_URL, "version/v1/diagnosis-keys")
Assert.assertEquals(DiagnosisKeyConstants.DIAGNOSIS_KEYS_SUBMISSION_URL, "/version/v1/diagnosis-keys") Assert.assertEquals(DiagnosisKeyConstants.DIAGNOSIS_KEYS_SUBMISSION_URL, "version/v1/diagnosis-keys")
Assert.assertEquals(DiagnosisKeyConstants.PARAMETERS_COUNTRY_DOWNLOAD_URL, "/version/v1/parameters/country") Assert.assertEquals(DiagnosisKeyConstants.PARAMETERS_COUNTRY_DOWNLOAD_URL, "version/v1/parameters/country")
Assert.assertEquals(DiagnosisKeyConstants.APPCONFIG_COUNTRY_DOWNLOAD_URL, "/version/v1/configuration/country") Assert.assertEquals(DiagnosisKeyConstants.APPCONFIG_COUNTRY_DOWNLOAD_URL, "version/v1/configuration/country")
Assert.assertEquals( Assert.assertEquals(
DiagnosisKeyConstants.COUNTRY_APPCONFIG_DOWNLOAD_URL, DiagnosisKeyConstants.COUNTRY_APPCONFIG_DOWNLOAD_URL,
"/version/v1/configuration/country/DE/app_config" "version/v1/configuration/country/DE/app_config"
) )
Assert.assertEquals(DiagnosisKeyConstants.AVAILABLE_DATES_URL, "/version/v1/diagnosis-keys/country/DE/date") Assert.assertEquals(DiagnosisKeyConstants.AVAILABLE_DATES_URL, "version/v1/diagnosis-keys/country/DE/date")
} }
} }
...@@ -7,8 +7,10 @@ class SubmissionConstantsTest { ...@@ -7,8 +7,10 @@ class SubmissionConstantsTest {
@Test @Test
fun allSubmissionConstants() { fun allSubmissionConstants() {
Assert.assertEquals(SubmissionConstants.QR_CODE_KEY_TYPE, "GUID") // TODO: Should we really keep these now?
Assert.assertEquals(SubmissionConstants.TELE_TAN_KEY_TYPE, "TELETAN") Assert.assertEquals(KeyType.GUID.name, "GUID")
Assert.assertEquals(KeyType.TELETAN.name, "TELETAN")
Assert.assertEquals(SubmissionConstants.REGISTRATION_TOKEN_URL, "version/v1/registrationToken") Assert.assertEquals(SubmissionConstants.REGISTRATION_TOKEN_URL, "version/v1/registrationToken")
Assert.assertEquals(SubmissionConstants.TEST_RESULT_URL, "version/v1/testresult") Assert.assertEquals(SubmissionConstants.TEST_RESULT_URL, "version/v1/testresult")
Assert.assertEquals(SubmissionConstants.TAN_REQUEST_URL, "version/v1/tan") Assert.assertEquals(SubmissionConstants.TAN_REQUEST_URL, "version/v1/tan")
...@@ -18,5 +20,11 @@ class SubmissionConstantsTest { ...@@ -18,5 +20,11 @@ class SubmissionConstantsTest {
Assert.assertEquals(SubmissionConstants.GUID_SEPARATOR, '?') Assert.assertEquals(SubmissionConstants.GUID_SEPARATOR, '?')
Assert.assertEquals(SubmissionConstants.SERVER_ERROR_CODE_400, 400) Assert.assertEquals(SubmissionConstants.SERVER_ERROR_CODE_400, 400)
// dummy token passes server verification
Assert.assertTrue(
Regex("^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}\$")
.matches(SubmissionConstants.DUMMY_REGISTRATION_TOKEN)
)
} }
} }
...@@ -3,12 +3,15 @@ package de.rki.coronawarnapp.service.submission ...@@ -3,12 +3,15 @@ package de.rki.coronawarnapp.service.submission
import de.rki.coronawarnapp.exception.NoGUIDOrTANSetException import de.rki.coronawarnapp.exception.NoGUIDOrTANSetException
import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
import de.rki.coronawarnapp.http.WebRequestBuilder import de.rki.coronawarnapp.http.WebRequestBuilder
import de.rki.coronawarnapp.http.playbook.BackgroundNoise
import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction import de.rki.coronawarnapp.transaction.SubmitDiagnosisKeysTransaction
import de.rki.coronawarnapp.util.formatter.TestResult import de.rki.coronawarnapp.util.formatter.TestResult
import io.mockk.MockKAnnotations
import io.mockk.Runs import io.mockk.Runs
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.every import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just import io.mockk.just
import io.mockk.mockkObject import io.mockk.mockkObject
import io.mockk.verify import io.mockk.verify
...@@ -22,11 +25,23 @@ class SubmissionServiceTest { ...@@ -22,11 +25,23 @@ class SubmissionServiceTest {
private val guid = "123456-12345678-1234-4DA7-B166-B86D85475064" private val guid = "123456-12345678-1234-4DA7-B166-B86D85475064"
private val registrationToken = "asdjnskjfdniuewbheboqudnsojdff" private val registrationToken = "asdjnskjfdniuewbheboqudnsojdff"
@MockK
private lateinit var webRequestBuilder: WebRequestBuilder
@MockK
private lateinit var backgroundNoise: BackgroundNoise
@Before @Before
fun setUp() { fun setUp() {
mockkObject(LocalData) MockKAnnotations.init(this)
mockkObject(WebRequestBuilder) mockkObject(WebRequestBuilder.Companion)
every { WebRequestBuilder.getInstance() } returns webRequestBuilder
mockkObject(BackgroundNoise.Companion)
every { BackgroundNoise.getInstance() } returns backgroundNoise
mockkObject(SubmitDiagnosisKeysTransaction) mockkObject(SubmitDiagnosisKeysTransaction)
mockkObject(LocalData)
every { LocalData.teletan() } returns null every { LocalData.teletan() } returns null
every { LocalData.testGUID() } returns null every { LocalData.testGUID() } returns null
...@@ -49,9 +64,9 @@ class SubmissionServiceTest { ...@@ -49,9 +64,9 @@ class SubmissionServiceTest {
every { LocalData.devicePairingSuccessfulTimestamp(any()) } just Runs every { LocalData.devicePairingSuccessfulTimestamp(any()) } just Runs
coEvery { coEvery {
WebRequestBuilder.getInstance() webRequestBuilder.asyncGetRegistrationToken(any(), KeyType.GUID)
.asyncGetRegistrationToken(any(), SubmissionConstants.QR_CODE_KEY_TYPE)
} returns registrationToken } returns registrationToken
every { backgroundNoise.scheduleDummyPattern() } just Runs
runBlocking { runBlocking {
SubmissionService.asyncRegisterDevice() SubmissionService.asyncRegisterDevice()
...@@ -61,6 +76,7 @@ class SubmissionServiceTest { ...@@ -61,6 +76,7 @@ class SubmissionServiceTest {
LocalData.registrationToken(registrationToken) LocalData.registrationToken(registrationToken)
LocalData.devicePairingSuccessfulTimestamp(any()) LocalData.devicePairingSuccessfulTimestamp(any())
LocalData.testGUID(null) LocalData.testGUID(null)
backgroundNoise.scheduleDummyPattern()
} }
} }
...@@ -73,9 +89,9 @@ class SubmissionServiceTest { ...@@ -73,9 +89,9 @@ class SubmissionServiceTest {
every { LocalData.devicePairingSuccessfulTimestamp(any()) } just Runs every { LocalData.devicePairingSuccessfulTimestamp(any()) } just Runs
coEvery { coEvery {
WebRequestBuilder.getInstance() webRequestBuilder.asyncGetRegistrationToken(any(), KeyType.TELETAN)
.asyncGetRegistrationToken(any(), SubmissionConstants.TELE_TAN_KEY_TYPE)
} returns registrationToken } returns registrationToken
every { backgroundNoise.scheduleDummyPattern() } just Runs
runBlocking { runBlocking {
SubmissionService.asyncRegisterDevice() SubmissionService.asyncRegisterDevice()
...@@ -85,6 +101,7 @@ class SubmissionServiceTest { ...@@ -85,6 +101,7 @@ class SubmissionServiceTest {
LocalData.registrationToken(registrationToken) LocalData.registrationToken(registrationToken)
LocalData.devicePairingSuccessfulTimestamp(any()) LocalData.devicePairingSuccessfulTimestamp(any())
LocalData.teletan(null) LocalData.teletan(null)
backgroundNoise.scheduleDummyPattern()
} }
} }
...@@ -98,9 +115,7 @@ class SubmissionServiceTest { ...@@ -98,9 +115,7 @@ class SubmissionServiceTest {
@Test @Test
fun requestTestResultSucceeds() { fun requestTestResultSucceeds() {
every { LocalData.registrationToken() } returns registrationToken every { LocalData.registrationToken() } returns registrationToken
coEvery { coEvery { webRequestBuilder.asyncGetTestResult(registrationToken) } returns TestResult.NEGATIVE.value
WebRequestBuilder.getInstance().asyncGetTestResult(registrationToken)
} returns TestResult.NEGATIVE.value
runBlocking { runBlocking {
assertThat(SubmissionService.asyncRequestTestResult(), equalTo(TestResult.NEGATIVE)) assertThat(SubmissionService.asyncRequestTestResult(), equalTo(TestResult.NEGATIVE))
......
...@@ -2,15 +2,18 @@ package de.rki.coronawarnapp.transaction ...@@ -2,15 +2,18 @@ package de.rki.coronawarnapp.transaction
import KeyExportFormat import KeyExportFormat
import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey
import de.rki.coronawarnapp.http.WebRequestBuilder
import de.rki.coronawarnapp.http.playbook.BackgroundNoise
import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient
import de.rki.coronawarnapp.service.diagnosiskey.DiagnosisKeyService
import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.service.submission.SubmissionService
import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.worker.BackgroundWorkScheduler import de.rki.coronawarnapp.worker.BackgroundWorkScheduler
import io.mockk.MockKAnnotations
import io.mockk.Runs import io.mockk.Runs
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.coVerifyOrder import io.mockk.coVerifyOrder
import io.mockk.every import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just import io.mockk.just
import io.mockk.mockkObject import io.mockk.mockkObject
import io.mockk.slot import io.mockk.slot
...@@ -23,30 +26,45 @@ import org.junit.Before ...@@ -23,30 +26,45 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
class SubmitDiagnosisKeysTransactionTest { class SubmitDiagnosisKeysTransactionTest {
@MockK
private lateinit var webRequestBuilder: WebRequestBuilder
@MockK
private lateinit var backgroundNoise: BackgroundNoise
private val authString = "authString" private val authString = "authString"
private val registrationToken = "123"
@Before @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this)
mockkObject(WebRequestBuilder.Companion)
every { WebRequestBuilder.getInstance() } returns webRequestBuilder
mockkObject(BackgroundNoise.Companion)
every { BackgroundNoise.getInstance() } returns backgroundNoise
mockkObject(LocalData) mockkObject(LocalData)
mockkObject(SubmissionService) mockkObject(SubmissionService)
mockkObject(InternalExposureNotificationClient) mockkObject(InternalExposureNotificationClient)
mockkObject(DiagnosisKeyService)
mockkObject(BackgroundWorkScheduler) mockkObject(BackgroundWorkScheduler)
every { BackgroundWorkScheduler.stopWorkScheduler() } just Runs every { BackgroundWorkScheduler.stopWorkScheduler() } just Runs
every { LocalData.numberOfSuccessfulSubmissions(any()) } just Runs every { LocalData.numberOfSuccessfulSubmissions(any()) } just Runs
coEvery { SubmissionService.asyncRequestAuthCode(any()) } returns authString coEvery { webRequestBuilder.asyncGetTan(registrationToken) } returns authString
} }
@Test @Test
fun testTransactionNoKeys() { fun testTransactionNoKeys() {
coEvery { InternalExposureNotificationClient.asyncGetTemporaryExposureKeyHistory() } returns listOf() coEvery { InternalExposureNotificationClient.asyncGetTemporaryExposureKeyHistory() } returns listOf()
coEvery { DiagnosisKeyService.asyncSubmitKeys(authString, listOf()) } just Runs coEvery { webRequestBuilder.asyncSubmitKeysToServer(authString, listOf()) } just Runs
runBlocking { runBlocking {
SubmitDiagnosisKeysTransaction.start("123", listOf()) SubmitDiagnosisKeysTransaction.start(registrationToken, listOf())
coVerifyOrder { coVerifyOrder {
DiagnosisKeyService.asyncSubmitKeys(authString, listOf()) webRequestBuilder.asyncSubmitKeysToServer(authString, listOf())
SubmissionService.submissionSuccessful() SubmissionService.submissionSuccessful()
} }
} }
...@@ -64,13 +82,15 @@ class SubmitDiagnosisKeysTransactionTest { ...@@ -64,13 +82,15 @@ class SubmitDiagnosisKeysTransactionTest {
coEvery { InternalExposureNotificationClient.asyncGetTemporaryExposureKeyHistory() } returns listOf( coEvery { InternalExposureNotificationClient.asyncGetTemporaryExposureKeyHistory() } returns listOf(
key key
) )
coEvery { DiagnosisKeyService.asyncSubmitKeys(authString, capture(testList)) } just Runs coEvery {
webRequestBuilder.asyncSubmitKeysToServer(authString, capture(testList))
} just Runs
runBlocking { runBlocking {
SubmitDiagnosisKeysTransaction.start("123", listOf(key)) SubmitDiagnosisKeysTransaction.start(registrationToken, listOf(key))
coVerifyOrder { coVerifyOrder {
DiagnosisKeyService.asyncSubmitKeys(authString, any()) webRequestBuilder.asyncSubmitKeysToServer(authString, any())
SubmissionService.submissionSuccessful() SubmissionService.submissionSuccessful()
} }
assertThat(testList.isCaptured, `is`(true)) assertThat(testList.isCaptured, `is`(true))
......
package de.rki.coronawarnapp.ui.viewmodel package de.rki.coronawarnapp.ui.viewmodel
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import de.rki.coronawarnapp.http.WebRequestBuilder
import de.rki.coronawarnapp.http.playbook.BackgroundNoise
import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.LocalData
import de.rki.coronawarnapp.ui.submission.ScanStatus import de.rki.coronawarnapp.ui.submission.ScanStatus
import io.mockk.MockKAnnotations
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just import io.mockk.just
import io.mockk.mockkObject import io.mockk.mockkObject
import org.junit.Assert import org.junit.Assert
...@@ -19,10 +23,24 @@ class SubmissionViewModelTest { ...@@ -19,10 +23,24 @@ class SubmissionViewModelTest {
@Rule @Rule
var instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule() var instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()
@MockK
private lateinit var webRequestBuilder: WebRequestBuilder
@MockK
private lateinit var backgroundNoise: BackgroundNoise
@Before @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this)
mockkObject(LocalData) mockkObject(LocalData)
every { LocalData.testGUID(any()) } just Runs every { LocalData.testGUID(any()) } just Runs
mockkObject(WebRequestBuilder.Companion)
every { WebRequestBuilder.getInstance() } returns webRequestBuilder
mockkObject(BackgroundNoise.Companion)
every { BackgroundNoise.getInstance() } returns backgroundNoise
} }
@Test @Test
......
package de.rki.coronawarnapp.util
import de.rki.coronawarnapp.http.HttpErrorParser
import de.rki.coronawarnapp.http.WebRequestBuilder
import de.rki.coronawarnapp.http.interceptor.RetryInterceptor
import de.rki.coronawarnapp.http.service.DistributionService
import de.rki.coronawarnapp.http.service.SubmissionService
import de.rki.coronawarnapp.http.service.VerificationService
import de.rki.coronawarnapp.util.security.VerificationKeys
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import okio.utf8Size
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.converter.protobuf.ProtoConverterFactory
fun MockWebServer.newWebRequestBuilder(): WebRequestBuilder {
val httpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.addInterceptor(RetryInterceptor())
.addInterceptor(HttpErrorParser())
.build()
val retrofit = Retrofit.Builder()
.client(httpClient)
.addConverterFactory(ProtoConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
return WebRequestBuilder(
retrofit.baseUrl(this.url("/distribution/")).build()
.create(DistributionService::class.java),
retrofit.baseUrl(this.url("/verification/")).build()
.create(VerificationService::class.java),
retrofit.baseUrl(this.url("/submission/")).build()
.create(SubmissionService::class.java),
VerificationKeys()
)
}
fun RecordedRequest.requestHeaderWithoutContentLength() =
listOf(this.requestLine)
.plus(
this.headers.filter { (k, _) -> k != "Content-Length" }.toString()
)
.joinToString("\n")
fun RecordedRequest.headerSizeIgnoringContentLength() =
requestHeaderWithoutContentLength().utf8Size()
\ No newline at end of file
...@@ -29,6 +29,7 @@ message SignatureInfo { ...@@ -29,6 +29,7 @@ message SignatureInfo {
message SubmissionPayload { message SubmissionPayload {
repeated TemporaryExposureKey keys = 1; repeated TemporaryExposureKey keys = 1;
optional bytes padding = 2;
} }
message TemporaryExposureKey { message TemporaryExposureKey {
......
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