diff --git a/CTRTests/Infrastrucure/Services/ClockDeviationManagerTests.swift b/CTRTests/Infrastrucure/Services/ClockDeviationManagerTests.swift index b4f825737..b230cbe1a 100644 --- a/CTRTests/Infrastrucure/Services/ClockDeviationManagerTests.swift +++ b/CTRTests/Infrastrucure/Services/ClockDeviationManagerTests.swift @@ -23,7 +23,7 @@ class ClockDeviationManagerTests: XCTestCase { sut = ClockDeviationManager( remoteConfigManager: remoteConfigManagerSpy, - currentSystemUptime: { 1 * hour }, + currentSystemUptime: { 1 * 3600 }, now: { now } ) } diff --git a/Localizations/CoronaCheck holder/en/Localizable.strings b/Localizations/CoronaCheck holder/en/Localizable.strings index 3048f21a3..f8ab01ee2 100644 --- a/Localizations/CoronaCheck holder/en/Localizable.strings +++ b/Localizations/CoronaCheck holder/en/Localizable.strings @@ -349,7 +349,7 @@ "holder.dashboard.empty.domestic.title" = "Find your Dutch certificate here later"; "holder.dashboard.empty.domestic.message" = "

You can add a certificate if you're vaccinated, if you've had corona or by taking a test at an affiliated test location. The app generates a certificate for the Netherlands and one for other countries.

"; "holder.dashboard.empty.international.title" = "Find your international certificate here later"; -"holder.dashboard.empty.international.message" = "

Are you abroad, or planning to cross the border? Then always use your international certificate. Please check before leaving which certificate you need.


Which certificate is valid where I’m heading?

"; +"holder.dashboard.empty.international.message" = "

Are you abroad, or planning to cross the border? Then always use your international certificate. Please check before leaving which certificate you need.

Which certificate is valid where I’m heading?

"; /* MARK: - Holder Enlarged */ "holder.enlarged.screenshot.title" = "A screenshot is not a valid entrance ticket."; "holder.enlarged.screenshot.message" = "A screenshot causes issues when scanning. You can only use the QR code in the app as a valid entrance ticket."; diff --git a/Localizations/CoronaCheck holder/nl/Localizable.strings b/Localizations/CoronaCheck holder/nl/Localizable.strings index fd5482eb3..1491c6023 100644 --- a/Localizations/CoronaCheck holder/nl/Localizable.strings +++ b/Localizations/CoronaCheck holder/nl/Localizable.strings @@ -311,7 +311,7 @@ /* MARK: - Holder Dashboard */ "holder.dashboard.title" = "Mijn bewijzen"; "holder.dashboard.intro.domestic" = "Laat de QR-code en je identiteitsbewijs zien bij de ingang van de locatie die je bezoekt."; -"holder.dashboard.intro.international" = "

Ben je in het buitenland of ga je de grens over? Gebruik dan altijd de internationale QR-code hieronder. Controleer voor vertrek welk bewijs je nodig hebt.


Is mijn bewijs geldig op mijn bestemming?

"; +"holder.dashboard.intro.international" = "

Ben je in het buitenland of ga je de grens over? Gebruik dan altijd de internationale QR-code hieronder. Controleer voor vertrek welk bewijs je nodig hebt.

Is mijn bewijs geldig op mijn bestemming?

"; "holder.dashboard.qr.expired" = "Je QR-code is verlopen."; "holder.dashboard.qr.hour" = "uur"; "holder.dashboard.qr.minute" = "min"; @@ -349,7 +349,7 @@ "holder.dashboard.empty.domestic.title" = "Hier komt jouw Nederlandse bewijs"; "holder.dashboard.empty.domestic.message" = "

Je kunt een bewijs toevoegen als je bent gevaccineerd of hersteld. Of als je bent getest bij een aangesloten testlocatie. De app maakt een Nederlands en een internationaal bewijs.

"; "holder.dashboard.empty.international.title" = "Hier komt jouw internationale bewijs"; -"holder.dashboard.empty.international.message" = "

Ben je in het buitenland of ga je de grens over? Gebruik dan altijd jouw internationale bewijs. Controleer voor vertrek welk bewijs je nodig hebt.


Welk bewijs is geldig op mijn bestemming?

"; +"holder.dashboard.empty.international.message" = "

Ben je in het buitenland of ga je de grens over? Gebruik dan altijd jouw internationale bewijs. Controleer voor vertrek welk bewijs je nodig hebt.

Welk bewijs is geldig op mijn bestemming?

"; /* MARK: - Holder Enlarged */ "holder.enlarged.screenshot.title" = "Een schermafbeelding is geen geldig toegangsbewijs"; "holder.enlarged.screenshot.message" = "Een schermafbeelding zorgt voor problemen bij het scannen. Alleen de QR-code in de app is een geldig toegangsbewijs."; diff --git a/Sources/CTR/Infrastructure/Services/ClockDeviationManager.swift b/Sources/CTR/Infrastructure/Services/ClockDeviationManager.swift index ab35dceca..d3f4f6571 100644 --- a/Sources/CTR/Infrastructure/Services/ClockDeviationManager.swift +++ b/Sources/CTR/Infrastructure/Services/ClockDeviationManager.swift @@ -13,7 +13,7 @@ protocol ClockDeviationManaging: AnyObject { init() func update(serverHeaderDate: String) - func update(serverResponseDateTime: Date, localResponseDatetime: Date, localResponseSystemUptime: TimeInterval) + func update(serverResponseDateTime: Date, localResponseDateTime: Date, localResponseSystemUptime: __darwin_time_t) func appendDeviationChangeObserver(_ observer: @escaping (Bool) -> Void) -> ClockDeviationManager.ObserverToken func removeDeviationChangeObserver(token: ClockDeviationManager.ObserverToken) @@ -24,31 +24,33 @@ class ClockDeviationManager: ClockDeviationManaging, Logging { var hasSignificantDeviation: Bool? { guard let serverResponseDateTime = serverResponseDateTime, - let localResponseDatetime = localResponseDatetime, + let localResponseDatetime = localResponseDateTime, let localResponseSystemUptime = localResponseSystemUptime, - let clockDeviationThresholdSeconds = clockDeviationThresholdSeconds + let clockDeviationThresholdSeconds = clockDeviationThresholdSeconds, + let systemUptime = currentSystemUptime() else { return nil } let result = ClockDeviationManager.hasSignificantDeviation( serverResponseDateTime: serverResponseDateTime, - localResponseDatetime: localResponseDatetime, - clockDeviationThresholdSeconds: clockDeviationThresholdSeconds, - localSystemUptime: localResponseSystemUptime, - systemUptime: currentSystemUptime(), - now: now() + localResponseDateTime: localResponseDatetime, + localResponseSystemUptime: localResponseSystemUptime, + currentDate: now(), + currentSystemUptime: systemUptime, + clockDeviationThresholdSeconds: clockDeviationThresholdSeconds ) + return result } private var serverResponseDateTime: Date? - private var localResponseDatetime: Date? - private var localResponseSystemUptime: TimeInterval? + private var localResponseDateTime: Date? + private var localResponseSystemUptime: __darwin_time_t? private var clockDeviationThresholdSeconds: Double? { remoteConfigManager.getConfiguration().clockDeviationThresholdSeconds.map { Double($0) } } private let remoteConfigManager: RemoteConfigManaging - private let currentSystemUptime: () -> TimeInterval + private let currentSystemUptime: () -> __darwin_time_t? private let now: () -> Date private var deviationChangeObservers = [ObserverToken: (Bool) -> Void]() private lazy var serverHeaderDateFormatter: DateFormatter = { @@ -61,14 +63,14 @@ class ClockDeviationManager: ClockDeviationManaging, Logging { required convenience init() { self.init( remoteConfigManager: Services.remoteConfigManager, - currentSystemUptime: { ProcessInfo.processInfo.systemUptime }, + currentSystemUptime: { ClockDeviationManager.currentSystemUptime() }, now: { Date() } ) } required init( remoteConfigManager: RemoteConfigManaging = Services.remoteConfigManager, - currentSystemUptime: @escaping () -> TimeInterval = { ProcessInfo.processInfo.systemUptime }, + currentSystemUptime: @escaping () -> __darwin_time_t? = { ClockDeviationManager.currentSystemUptime() }, now: @escaping () -> Date = { Date() } ) { self.remoteConfigManager = remoteConfigManager @@ -92,18 +94,20 @@ class ClockDeviationManager: ClockDeviationManaging, Logging { /// Update using the Server Response Header string /// e.g. "Sat, 07 Aug 2021 12:12:57 GMT" func update(serverHeaderDate: String) { - guard let serverDate = serverHeaderDateFormatter.date(from: serverHeaderDate) else { return } + guard let serverDate = serverHeaderDateFormatter.date(from: serverHeaderDate), + let systemUptime = currentSystemUptime() + else { return } update( serverResponseDateTime: serverDate, - localResponseDatetime: now(), - localResponseSystemUptime: currentSystemUptime() + localResponseDateTime: now(), + localResponseSystemUptime: systemUptime ) } - func update(serverResponseDateTime: Date, localResponseDatetime: Date, localResponseSystemUptime: TimeInterval) { + func update(serverResponseDateTime: Date, localResponseDateTime: Date, localResponseSystemUptime: __darwin_time_t) { self.serverResponseDateTime = serverResponseDateTime - self.localResponseDatetime = localResponseDatetime + self.localResponseDateTime = localResponseDateTime self.localResponseSystemUptime = localResponseSystemUptime notifyObservers() } @@ -138,19 +142,34 @@ class ClockDeviationManager: ClockDeviationManaging, Logging { /// - now: the current local time private static func hasSignificantDeviation( serverResponseDateTime: Date, - localResponseDatetime: Date, - clockDeviationThresholdSeconds: Double, - localSystemUptime: TimeInterval, - systemUptime: TimeInterval, - now: Date = Date() + localResponseDateTime: Date, + localResponseSystemUptime: __darwin_time_t, + currentDate: Date = Date(), + currentSystemUptime: __darwin_time_t, + clockDeviationThresholdSeconds: Double ) -> Bool { - let responseSystemStartDatetime = localResponseDatetime.timeIntervalSince1970 - localSystemUptime - let currentSystemStartDateTime = now.timeIntervalSince1970 - systemUptime + let responseSystemStartDatetime = localResponseDateTime.timeIntervalSince1970 - TimeInterval(localResponseSystemUptime) + let currentSystemStartDateTime = currentDate.timeIntervalSince1970 - TimeInterval(currentSystemUptime) let systemUptimeDelta = currentSystemStartDateTime - responseSystemStartDatetime - let responseTimeDelta = localResponseDatetime.timeIntervalSince1970 - serverResponseDateTime.timeIntervalSince1970 + let responseTimeDelta = localResponseDateTime.timeIntervalSince1970 - serverResponseDateTime.timeIntervalSince1970 let hasDeviation = abs(responseTimeDelta + systemUptimeDelta) >= clockDeviationThresholdSeconds return hasDeviation } + + private static func currentSystemUptime() -> __darwin_time_t? { + + var uptime = timespec() + + // `CLOCK_MONOTONIC` represents the absolute elapsed wall-clock time since some arbitrary, + // fixed point in the past. It isn't affected by changes in the system time-of-day clock. + + // Check response is 0 else there was an error: + guard clock_gettime(CLOCK_MONOTONIC, &uptime) == 0 else { + return nil + } + + return uptime.tv_sec + } } diff --git a/Sources/CTR/Interface/Holder/Dashboard/HolderDashboardViewModel.swift b/Sources/CTR/Interface/Holder/Dashboard/HolderDashboardViewModel.swift index 959a0120e..31f682268 100644 --- a/Sources/CTR/Interface/Holder/Dashboard/HolderDashboardViewModel.swift +++ b/Sources/CTR/Interface/Holder/Dashboard/HolderDashboardViewModel.swift @@ -115,7 +115,8 @@ final class HolderDashboardViewModel: Logging { myQRCards: [], expiredGreenCards: [], showCreateCard: true, - isRefreshingStrippen: false + isRefreshingStrippen: false, + deviceHasClockDeviation: Services.clockDeviationManager.hasSignificantDeviation ?? false ) self.datasource.didUpdate = { [weak self] (qrCardDataItems: [MyQRCard], expiredGreenCards: [ExpiredQR]) in @@ -294,7 +295,7 @@ final class HolderDashboardViewModel: Logging { ] } - if state.deviceHasClockDeviation && validityRegion == .domestic && !regionFilteredMyQRCards.isEmpty { + if state.deviceHasClockDeviation && !allQRCards.isEmpty { viewControllerCards += [ .deviceHasClockDeviation(message: L.holderDashboardClockDeviationDetectedMessage(), didTapMoreInfo: { coordinatorDelegate.userWishesMoreInfoAboutClockDeviation()