-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathDiagnosisKeysUploadService.swift
More file actions
148 lines (123 loc) · 5.37 KB
/
DiagnosisKeysUploadService.swift
File metadata and controls
148 lines (123 loc) · 5.37 KB
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//
// DiagnosisKeysUploadService.swift
// safesafe
//
import Moya
import PromiseKit
import ExposureNotification
protocol DiagnosisKeysUploadServiceProtocol {
func upload(usingResponse jsResponse: UploadTemporaryExposureKeysResponse) -> Promise<Void>
}
enum UploadError: Error {
case noInternet(shouldRetry: Bool)
case general(shouldRetry: Bool, code: Int)
case unknown(Error)
}
@available(iOS 13.5, *)
final class DiagnosisKeysUploadService: DiagnosisKeysUploadServiceProtocol {
private enum Constants {
static let dayIntervalSeconds: UInt32 = 86400
static let keyExpirationDays: UInt32 = 14
static let rollingIterationSeconds: UInt32 = 600
}
private enum Validation {
static let keysAtLeast = 1
static let keysMax = 30
static let keysPerDayMax = 3
}
// MARK: - Properties
private let exposureManager: ExposureServiceProtocol
private let deviceCheckService: DeviceCheckServiceProtocol
private let renewableRequest: RenewableRequest<ExposureKeysTarget>
// MARK: - Life Cycle
init(
with exposureManager: ExposureServiceProtocol,
deviceCheckService: DeviceCheckServiceProtocol,
exposureKeysProvider: MoyaProvider<ExposureKeysTarget>
) {
self.exposureManager = exposureManager
self.deviceCheckService = deviceCheckService
self.renewableRequest = .init(provider: exposureKeysProvider, alertManager: NetworkingAlertManager())
}
// MARK: - Exposure Keys
func upload(usingResponse jsResponse: UploadTemporaryExposureKeysResponse) -> Promise<Void> {
var diagnosisKeys: [ENTemporaryExposureKey] = []
return getDiagnosisKeys()
.then (validateKeysAtLeast)
.then (validateKeysMax)
.then(validateKeysPerDayMax)
.then { keys -> Promise<String> in
diagnosisKeys = keys
return self.getToken(usingAuthCode: jsResponse.pin)
}
.then { token -> Promise<Moya.Response> in
let data = TemporaryExposureKeys(
temporaryExposureKeys: diagnosisKeys.map({ TemporaryExposureSingleKey($0) }),
verificationPayload: token
)
let keysData = TemporaryExposureKeysData(
data: data,
isInteroperabilityEnabled: jsResponse.isInteroperabilityEnabled
)
#if !LIVE
File.saveUploadedPayload(keysData)
#endif
return self.renewableRequest.make(target: .post(keysData))
}
.asVoid()
}
private func getDiagnosisKeys(filtered: Bool = true) -> Promise<[ENTemporaryExposureKey]> {
if filtered {
return exposureManager
.getDiagnosisKeys()
.filterValues(discardOldKeys)
} else {
return exposureManager
.getDiagnosisKeys()
}
}
// MARK: - Auth
private func getToken(usingAuthCode authCode: String) -> Promise<String> {
let data = TemporaryExposureKeysAuthData(code: authCode)
return renewableRequest.make(target: .auth(data))
.then { response -> Promise<String> in
do {
let token = try response.map(TemporaryExposureKeysAuthResponse.self).result.accessToken
return .value(token)
} catch {
throw error
}
}
}
private func discardOldKeys(key: ENTemporaryExposureKey) -> Bool {
let startOfDay = UInt32(Calendar.current.startOfDay(for: Date()).timeIntervalSince1970)
let valid = (key.rollingStartNumber * Constants.rollingIterationSeconds) > (startOfDay - Constants.keyExpirationDays * Constants.dayIntervalSeconds)
if !valid { console(">>> Discarded Key> Rolling Start Number: \(key.rollingStartNumber), Rolling Period: \(key.rollingPeriod)", type: .warning) }
return valid
}
private func validateKeysAtLeast(_ keys: [ENTemporaryExposureKey]) -> Promise<[ENTemporaryExposureKey]> {
console("keys count: \(keys.count)")
if keys.count < Validation.keysAtLeast {
UploadValidationAlertManager().show(type: .keysAtLeast) { _ in }
return .init(error: InternalError.uploadValidation)
}
return .value(keys)
}
private func validateKeysMax(_ keys: [ENTemporaryExposureKey]) -> Promise<[ENTemporaryExposureKey]> {
console("keys count: \(keys.count)")
if keys.count > Validation.keysMax {
UploadValidationAlertManager().show(type: .keysMax) { _ in }
return .init(error: InternalError.uploadValidation)
}
return .value(keys)
}
private func validateKeysPerDayMax(_ keys: [ENTemporaryExposureKey]) -> Promise<[ENTemporaryExposureKey]> {
let rollingPeriods = keys.map { ($0.rollingStartNumber, 1) }
let rollingPeriodsCount = Dictionary(rollingPeriods, uniquingKeysWith: +)
if rollingPeriodsCount.filter ({ $1 > Validation.keysPerDayMax }).count > .zero {
UploadValidationAlertManager().show(type: .keysPerDayMax) { _ in }
return .init(error: InternalError.uploadValidation)
}
return .value(keys)
}
}