This repository has been archived by the owner on Jun 20, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 495
/
RapidAntigenQrCodeExtractor.kt
83 lines (73 loc) · 2.67 KB
/
RapidAntigenQrCodeExtractor.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package de.rki.coronawarnapp.coronatest.qrcode
import com.google.common.io.BaseEncoding
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import de.rki.coronawarnapp.util.serialization.fromJson
import okio.internal.commonToUtf8String
import org.joda.time.Instant
import org.joda.time.LocalDate
import timber.log.Timber
internal class RapidAntigenQrCodeExtractor : QrCodeExtractor {
private val prefix: String = "https://s.coronawarn.app?v=1#"
private val prefix2: String = "https://s.coronawarn.app/?v=1#"
override fun canHandle(rawString: String): Boolean {
return rawString.startsWith(prefix, ignoreCase = true) || rawString.startsWith(prefix2, ignoreCase = true)
}
override fun extract(rawString: String): CoronaTestQRCode.RapidAntigen {
val data = extractData(rawString).validate()
return CoronaTestQRCode.RapidAntigen(
hash = data.hash!!,
createdAt = data.createdAt!!,
firstName = data.firstName,
lastName = data.lastName,
dateOfBirth = data.dateOfBirth
)
}
private fun Payload.validate(): Payload {
if (hash == null || !hash.isSha256Hash()) throw InvalidQRCodeException("Hash is invalid")
if (timestamp == null || timestamp <= 0) throw InvalidQRCodeException("Timestamp is invalid")
createdAt = Instant.ofEpochSecond(timestamp)
dateOfBirth = dob?.let {
try {
LocalDate.parse(it)
} catch (e: Exception) {
Timber.e("Invalid date format")
throw InvalidQRCodeException("Date of birth has wrong format: $it. It should be YYYY-MM-DD")
}
}
return this
}
private fun String.isSha256Hash(): Boolean {
return toByteArray().size == 64
}
private fun extractData(rawString: String): Payload {
return rawString
.removePrefix(prefix)
.removePrefix(prefix2)
.decode()
}
private fun String.decode(): Payload {
val decoded = if (
this.contains("+") ||
this.contains("/") ||
this.contains("=")
) {
BaseEncoding.base64().decode(this)
} else {
BaseEncoding.base64Url().decode(this)
}
return Gson().fromJson(decoded.commonToUtf8String())
}
private data class Payload(
val hash: String?,
val timestamp: Long?,
@SerializedName("fn")
val firstName: String?,
@SerializedName("ln")
val lastName: String?,
val dob: String?
) {
var dateOfBirth: LocalDate? = null
var createdAt: Instant? = null
}
}