Skip to content

Commit

Permalink
adds methods to ask for user consent seperate from sending the keys t…
Browse files Browse the repository at this point in the history
…o the backend
  • Loading branch information
stmitt committed May 18, 2021
1 parent e0f2149 commit 7adaf16
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 66 deletions.
157 changes: 92 additions & 65 deletions Sources/DP3TSDK/DP3TSDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,93 +236,120 @@ class DP3TSDK {
}
}

/// Request the user permission to obtain all TEK's
/// This results with a IWasExposedState object which later can be used to transmit the filtered TEK's to the backend
/// - Parameter callback: a handler which receives the state object or an error
func requestTEKPermission(callback: @escaping (Result<IWasExposedState, DP3TTracingError>) -> Void) {
log.trace()
if case .infected = state.infectionStatus {
callback(.failure(DP3TTracingError.userAlreadyMarkedAsInfected))
return
}
diagnosisKeysProvider.getDiagnosisKeys(onsetDate: nil, appDesc: applicationDescriptor, disableExposureNotificationAfterCompletion: false) { result in
switch result {
case let .success(keys):
callback(.success(.init(keys: keys, isFake: false)))
case let .failure(error):
callback(.failure(error))
}
}
}

/// tell the SDK that the user was exposed
/// This will stop tracing
/// - Parameters:
/// - onset: Start date of the exposure
/// - authString: Authentication string for the exposure change
/// - authentication: Authentication method
/// - isFakeRequest: indicates if the request should be a fake one. This method should be called regulary so people sniffing the networking traffic can no figure out if somebody is marking themself actually as exposed
/// - callback: callback
func iWasExposed(onset: Date,
authentication: ExposeeAuthMethod,
isFakeRequest: Bool = false,
callback: @escaping (Result<DP3TTracing.IWasExposedResult, DP3TTracingError>) -> Void) {
func sendTEKs(onset: Date,
iWasExposedState: IWasExposedState,
authentication: ExposeeAuthMethod,
callback: @escaping (Result<DP3TTracing.IWasExposedResult, DP3TTracingError>) -> Void) {
log.trace()
if !isFakeRequest,
if !iWasExposedState.isFake,
case .infected = state.infectionStatus {
callback(.failure(DP3TTracingError.userAlreadyMarkedAsInfected))
return
}
var keys: [CodableDiagnosisKey] = iWasExposedState.keys.filter { $0.date > onset }

let group = DispatchGroup()
// always make sure we fill up the keys to defaults.parameters.crypto.numberOfKeysToSubmit
let fakeKeyCount = self.defaults.parameters.networking.numberOfKeysToSubmit - keys.count

var diagnosisKeysResult: Result<[CodableDiagnosisKey], DP3TTracingError> = .success([])
let oldestRollingStartNumber = keys.min { (a, b) -> Bool in a.rollingStartNumber < b.rollingStartNumber }?.rollingStartNumber ?? DayDate(date: .init(timeIntervalSinceNow: -.day)).period

if isFakeRequest {
group.enter()
diagnosisKeysProvider.getFakeDiagnosisKeys { result in
diagnosisKeysResult = result
group.leave()
}
} else {
group.enter()
diagnosisKeysProvider.getDiagnosisKeys(onsetDate: onset, appDesc: applicationDescriptor, disableExposureNotificationAfterCompletion: false) { result in
diagnosisKeysResult = result
group.leave()
}
}
let startingFrom = Date(timeIntervalSince1970: Double(oldestRollingStartNumber) * 10 * .minute - .day)

group.notify(queue: .main) { [weak self] in
guard let self = self else { return }
switch diagnosisKeysResult {
case let .failure(error):
callback(.failure(error))
case let .success(keys):
keys.append(contentsOf: self.diagnosisKeysProvider.getFakeKeys(count: fakeKeyCount, startingFrom: startingFrom))

var mutableKeys = keys
// always make sure we fill up the keys to defaults.parameters.crypto.numberOfKeysToSubmit
let fakeKeyCount = self.defaults.parameters.networking.numberOfKeysToSubmit - mutableKeys.count
let withFederationGateway: Bool?
switch self.federationGateway {
case .yes:
withFederationGateway = true
case .no:
withFederationGateway = false
case .unspecified:
withFederationGateway = nil
}

let oldestRollingStartNumber = keys.min { (a, b) -> Bool in a.rollingStartNumber < b.rollingStartNumber }?.rollingStartNumber ?? DayDate(date: .init(timeIntervalSinceNow: -.day)).period
var oldestNonFakeKeyDate: Date? = nil
if !iWasExposedState.isFake {
oldestNonFakeKeyDate = Date(timeIntervalSince1970: Double(oldestRollingStartNumber) * 10 * .minute)
}

let startingFrom = Date(timeIntervalSince1970: Double(oldestRollingStartNumber) * 10 * .minute - .day)
let model = ExposeeListModel(gaenKeys: keys,
withFederationGateway: withFederationGateway,
fake: iWasExposedState.isFake)

mutableKeys.append(contentsOf: self.diagnosisKeysProvider.getFakeKeys(count: fakeKeyCount, startingFrom: startingFrom))
self.service.addExposeeList(model, authentication: authentication) { [weak self] result in
guard let self = self else { return }
DispatchQueue.main.async {
switch result {
case .success:
if !iWasExposedState.isFake {
self.state.infectionStatus = .infected
self.tracer.setEnabled(false, completionHandler: nil)
}

let withFederationGateway: Bool?
switch self.federationGateway {
case .yes:
withFederationGateway = true
case .no:
withFederationGateway = false
case .unspecified:
withFederationGateway = nil
callback(.success(.init(oldestKeyDate: oldestNonFakeKeyDate)))
case let .failure(error):
callback(.failure(.networkingError(error: error)))
}
}
}
}

var oldestNonFakeKeyDate: Date? = nil
if !isFakeRequest {
oldestNonFakeKeyDate = Date(timeIntervalSince1970: Double(oldestRollingStartNumber) * 10 * .minute)
}
/// tell the SDK that the user was exposed
/// This is a convenience method that for not fake requests internally first calls requestTEKPermission and then sendTEKs
/// This will stop tracing
/// - Parameters:
/// - onset: Start date of the exposure
/// - authString: Authentication string for the exposure change
/// - isFakeRequest: indicates if the request should be a fake one. This method should be called regulary so people sniffing the networking traffic can no figure out if somebody is marking themself actually as exposed
/// - callback: callback
func iWasExposed(onset: Date,
authentication: ExposeeAuthMethod,
isFakeRequest: Bool = false,
callback: @escaping (Result<DP3TTracing.IWasExposedResult, DP3TTracingError>) -> Void) {
log.trace()

let model = ExposeeListModel(gaenKeys: mutableKeys,
withFederationGateway: withFederationGateway,
fake: isFakeRequest)

self.service.addExposeeList(model, authentication: authentication) { [weak self] result in
guard let self = self else { return }
DispatchQueue.main.async {
switch result {
case .success:
if !isFakeRequest {
self.state.infectionStatus = .infected
self.tracer.setEnabled(false, completionHandler: nil)
}

callback(.success(.init(oldestKeyDate: oldestNonFakeKeyDate)))
case let .failure(error):
callback(.failure(.networkingError(error: error)))
}
}
let handle: (_ state: IWasExposedState) -> Void = { [weak self] state in
guard let self = self else { return }
self.sendTEKs(onset: onset,
iWasExposedState: state,
authentication: authentication,
callback: callback)
}

if isFakeRequest {
handle(.init(keys: [], isFake: true))
} else {
requestTEKPermission { result in
switch result {
case let .success(state):
handle(state)
case let .failure(error):
callback(.failure(error))
}
}
}
Expand Down
26 changes: 26 additions & 0 deletions Sources/DP3TSDK/DP3TTracing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,34 @@ public enum DP3TTracing {
public struct IWasExposedResult {
public let oldestKeyDate: Date?
}

/// Request the user permission to obtain all TEK's
/// This results with a IWasExposedState object which later can be used to transmit the filtered TEK's to the backend by calling sendTEKs
/// - Parameter callback: a handler which receives the state object or an error
@available(iOS 12.5, *)
public static func requestTEKPermission(callback: @escaping (Result<IWasExposedState, DP3TTracingError>) -> Void) {
instancePrecondition()
instance.requestTEKPermission(callback: callback)
}

/// Send the obtained keys to the backend. First a valid state has to be obtained by calling requestTEKPermission
/// - Parameters:
/// - onset: Start date of the exposure
/// - iWasExposedState: state object obtained by calling requestTEKPermission
/// - authentication: Authentication method
/// - callback: callback
@available(iOS 12.5, *)
public static func sendTEKs(onset: Date,
iWasExposedState: IWasExposedState,
authentication: ExposeeAuthMethod,
callback: @escaping (Result<IWasExposedResult, DP3TTracingError>) -> Void) {
instancePrecondition()
instance.sendTEKs(onset: onset, iWasExposedState: iWasExposedState, authentication: authentication, callback: callback)
}

/// tell the SDK that the user was exposed
/// This is a convenience method that for not fake requests internally first calls requestTEKPermission and then sendTEKs
/// This will stop tracing
/// - Parameters:
/// - onset: Start date of the exposure
/// - authentication: Authentication method
Expand Down
9 changes: 9 additions & 0 deletions Sources/DP3TSDK/DP3TTracingState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,12 @@ public enum SyncResult: Equatable {
}
}
}

public struct IWasExposedState {
internal let keys: [CodableDiagnosisKey]
internal let isFake: Bool

public static var fake: Self {
return .init(keys: [], isFake: true)
}
}
2 changes: 1 addition & 1 deletion Tests/DP3TSDKTests/DP3TSDKTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class DP3TSDKTests: XCTestCase {
.init(keyData: Data(count: 16), rollingPeriod: 144, rollingStartNumber: DayDate(date: oldestDate).period, transmissionRiskLevel: 0, fake: 0),
.init(keyData: Data(count: 16), rollingPeriod: 144, rollingStartNumber: DayDate(date: oldestDate.addingTimeInterval(.day * 2)).period, transmissionRiskLevel: 0, fake: 0),
]
sdk.iWasExposed(onset: .init(timeIntervalSinceNow: -.day), authentication: .none) { (result) in
sdk.iWasExposed(onset: .distantPast, authentication: .none) { (result) in
if case let Result.success(wrapper) = result {
XCTAssertEqual(wrapper.oldestKeyDate, DayDate(date: oldestDate).dayMin)
} else {
Expand Down

0 comments on commit 7adaf16

Please sign in to comment.