Commit 276886a2 authored by stopcovid@lunabee.com's avatar stopcovid@lunabee.com

Initial Robert files

parent 7f8d4d00
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Data+Extension.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 27/04/2020 - for the STOP-COVID project.
//
import UIKit
import CommonCrypto
extension Data {
var bytes: [UInt8] { [UInt8](self) }
func hmac(key: Data) -> Data {
let string: UnsafePointer<UInt8> = (self as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.count)
let stringLength = self.count
let keyString: [CUnsignedChar] = [UInt8](key)
let keyLength: Int = key.bytes.count
var result = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), keyString, keyLength, string, stringLength, &result)
return Data(result)
}
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Date+Extension.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 23/04/2020 - for the STOP-COVID project.
//
import Foundation
extension Date {
var timeIntervalSince1900: Int {
return Int(timeIntervalSince1970) + 2208988800
}
init(timeIntervalSince1900: Int) {
self.init(timeIntervalSince1970: Double(timeIntervalSince1900 - 2208988800))
}
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RobertMessageManager.swift
// COVID-19
//
// Created by Lunabee Studio on 23/04/2020.
// Copyright © 2020 Lunabee Studio. All rights reserved.
//
import Foundation
final class RobertMessageManager {
let storage: RobertStorage
init(storage: RobertStorage) {
self.storage = storage
}
}
// MARK: - Hello Message generation -
extension RobertMessageManager {
func generateCurrentHelloMessage() throws -> Data {
let epoch: RBEpoch = try storage.getCurrentEpoch()
let ntpTimestamp: Int = Int(Date().timeIntervalSince1900)
let key: String = try storage.getKey()
return generateHelloMessage(for: epoch, ntpTimestamp: ntpTimestamp, key: key)
}
func generateHelloMessage(for epoch: RBEpoch, ntpTimestamp: Int, key: String) -> Data {
let message: Data = generateMessage(for: epoch, ntpTimestamp: ntpTimestamp, key: key)
let mac: Data = generateHelloMessageMac(key: key, message: message)
return message + mac
}
private func generateMessage(for epoch: RBEpoch, ntpTimestamp: Int, key: String) -> Data {
let ecc: Data = Data(base64Encoded: epoch.ecc)!
let ebid: Data = Data(base64Encoded: epoch.ebid)!
let time: UInt16 = UInt16(truncating: NSNumber(integerLiteral: ntpTimestamp)).bigEndian
let data: Data = withUnsafeBytes(of: time) { Data($0) }
return ecc + ebid + data
}
private func generateHelloMessageMac(key: String, message: Data) -> Data {
let totalMessage: Data = Data([RobertConstants.Prefix.c1]) + message
let parsedKey: String = String(data: Data(base64Encoded: key)!, encoding: .ascii)!
return totalMessage.hmac(key: parsedKey)[0..<5]
}
}
// MARK: - Status mac generation -
extension RobertMessageManager {
func generateCurrentStatusMessage() throws -> RBStatusMessage {
let epoch: RBEpoch = try storage.getCurrentEpoch()
let ntpTimestamp: Int = Int(Date().timeIntervalSince1900)
let key: String = try storage.getKey()
return try generateStatusMessage(for: epoch, ntpTimestamp: ntpTimestamp, key: key)
}
func generateStatusMessage(for epoch: RBEpoch, ntpTimestamp: Int, key: String) throws -> RBStatusMessage {
let time: UInt16 = UInt16(truncating: NSNumber(integerLiteral: ntpTimestamp)).bigEndian
let timeData: Data = withUnsafeBytes(of: time) { Data($0) }
let mac: Data = try generateStatusMessageMAC(key: key, epoch: epoch, timeData: timeData)
return RBStatusMessage(ebid: epoch.ebid, time: timeData.base64EncodedString(), mac: mac.base64EncodedString())
}
private func generateStatusMessageMAC(key: String, epoch: RBEpoch, timeData: Data) throws -> Data {
guard let ebid = Data(base64Encoded: epoch.ebid) else {
throw NSError.localizedError(message: "Malformed EBID in epoch", code: 0)
}
let totalMessage: Data = Data([RobertConstants.Prefix.c2]) + ebid + timeData
guard let data = Data(base64Encoded: key), let parsedKey = String(data: data, encoding: .ascii) else {
throw NSError.localizedError(message: "Malformed key provided for mac calculation", code: 0)
}
return totalMessage.hmac(key: parsedKey)
}
}
// MARK: - Message parsing -
extension RobertMessageManager {
func parseHelloMessage(_ messageData: Data) {
let eccData: Data = messageData[0..<1]
let ebidData: Data = messageData[1..<9]
let timeData: Data = messageData[9..<11]
let pointer = UnsafePointer(timeData.bytes)
let time: UInt16 = pointer.withMemoryRebound(to: UInt16.self, capacity: 1) { $0.pointee }.bigEndian
print("Parsed ecc: \(eccData.base64EncodedString())")
print("Parsed ebid: \(ebidData.base64EncodedString())")
print("Parsed time: \(time)")
}
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBMessageGenerator.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 23/04/2020 - for the STOP-COVID project.
//
import Foundation
final class RBMessageGenerator {
// MARK: - Hello Message generation -
static func generateHelloMessage(for epoch: RBEpoch, ntpTimestamp: Int, key: Data) throws -> Data {
let message: Data = try generateMessage(for: epoch, ntpTimestamp: ntpTimestamp)
let mac: Data = try generateHelloMessageMac(key: key, message: message)
return message + mac
}
private static func generateMessage(for epoch: RBEpoch, ntpTimestamp: Int) throws -> Data {
guard let ecc = Data(base64Encoded: epoch.ecc) else {
throw NSError.localizedError(message: "Malformed ECC in epoch", code: 0)
}
guard let ebid = Data(base64Encoded: epoch.ebid) else {
throw NSError.localizedError(message: "Malformed EBID in epoch", code: 0)
}
let time: UInt16 = UInt16(truncating: NSNumber(integerLiteral: ntpTimestamp)).bigEndian
let data: Data = withUnsafeBytes(of: time) { Data($0) }
return ecc + ebid + data
}
private static func generateHelloMessageMac(key: Data, message: Data) throws -> Data {
let totalMessage: Data = Data([RBConstants.Prefix.c1]) + message
return totalMessage.hmac(key: key)[0..<5]
}
// MARK: - Status mac generation -
static func generateStatusMessage(for epoch: RBEpoch, ntpTimestamp: Int, key: Data) throws -> RBStatusMessage {
let time: UInt32 = UInt32(truncating: NSNumber(integerLiteral: ntpTimestamp)).bigEndian
let timeData: Data = withUnsafeBytes(of: time) { Data($0) }
let mac: Data = try generateStatusMessageMAC(key: key, epoch: epoch, timeData: timeData)
return RBStatusMessage(ebid: epoch.ebid, time: timeData.base64EncodedString(), mac: mac.base64EncodedString())
}
private static func generateStatusMessageMAC(key: Data, epoch: RBEpoch, timeData: Data) throws -> Data {
guard let ebid = Data(base64Encoded: epoch.ebid) else {
throw NSError.localizedError(message: "Malformed EBID in epoch", code: 0)
}
let totalMessage: Data = Data([RBConstants.Prefix.c2]) + ebid + timeData
return totalMessage.hmac(key: key)
}
// MARK: - Unregister mac generation -
static func generateUnregisterMessage(for epoch: RBEpoch, ntpTimestamp: Int, key: Data) throws -> RBUnregisterMessage {
let time: UInt32 = UInt32(truncating: NSNumber(integerLiteral: ntpTimestamp)).bigEndian
let timeData: Data = withUnsafeBytes(of: time) { Data($0) }
let mac: Data = try generateUnregisterMessageMAC(key: key, epoch: epoch, timeData: timeData)
return RBUnregisterMessage(ebid: epoch.ebid, time: timeData.base64EncodedString(), mac: mac.base64EncodedString())
}
private static func generateUnregisterMessageMAC(key: Data, epoch: RBEpoch, timeData: Data) throws -> Data {
guard let ebid = Data(base64Encoded: epoch.ebid) else {
throw NSError.localizedError(message: "Malformed EBID in epoch", code: 0)
}
let totalMessage: Data = Data([RBConstants.Prefix.c3]) + ebid + timeData
return totalMessage.hmac(key: key)
}
// MARK: - Delete exposure history mac generation -
static func generateDeleteExposureHistoryMessage(for epoch: RBEpoch, ntpTimestamp: Int, key: Data) throws -> RBDeleteExposureHistoryMessage {
let time: UInt32 = UInt32(truncating: NSNumber(integerLiteral: ntpTimestamp)).bigEndian
let timeData: Data = withUnsafeBytes(of: time) { Data($0) }
let mac: Data = try generateDeleteExposureHistoryMessageMAC(key: key, epoch: epoch, timeData: timeData)
return RBDeleteExposureHistoryMessage(ebid: epoch.ebid, time: timeData.base64EncodedString(), mac: mac.base64EncodedString())
}
private static func generateDeleteExposureHistoryMessageMAC(key: Data, epoch: RBEpoch, timeData: Data) throws -> Data {
guard let ebid = Data(base64Encoded: epoch.ebid) else {
throw NSError.localizedError(message: "Malformed EBID in epoch", code: 0)
}
let totalMessage: Data = Data([RBConstants.Prefix.c4]) + ebid + timeData
return totalMessage.hmac(key: key)
}
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBMessageParser.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 23/04/2020 - for the STOP-COVID project.
//
import Foundation
final class RBMessageParser {
static func getEcc(from helloMessage: Data) -> Data? {
guard helloMessage.count >= 1 else { return nil }
return helloMessage[0..<1]
}
static func getEbid(from helloMessage: Data) -> Data? {
guard helloMessage.count >= 9 else { return nil }
return helloMessage[1..<9]
}
static func getTime(from helloMessage: Data) -> UInt16? {
guard helloMessage.count >= 11 else { return nil }
let timeData: Data = helloMessage[9..<11]
let time: UInt16 = timeData.bytes.withUnsafeBufferPointer { $0.baseAddress?.withMemoryRebound(to: UInt16.self, capacity: 1) { $0.pointee }.bigEndian } ?? 0
return time
}
static func getMac(from helloMessage: Data) -> Data? {
guard helloMessage.count >= 16 else { return nil }
return helloMessage[11..<16]
}
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBDeleteExposureHistoryMessage.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 29/04/2020 - for the STOP-COVID project.
//
import UIKit
struct RBDeleteExposureHistoryMessage {
var ebid: String
var time: String
var mac: String
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBEpoch.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 23/04/2020 - for the STOP-COVID project.
//
import UIKit
struct RBEpoch: RBStorable {
var id: Int
var ebid: String
var ecc: String
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBLocalProximity.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 29/04/2020 - for the STOP-COVID project.
//
import UIKit
struct RBLocalProximity {
var ecc: String
var ebid: String
var mac: String
var timeFromHelloMessage: UInt16
var timeCollectedOnDevice: Int
var rssiRaw: Int
var rssiCalibrated: Int
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBReceivedProximity.swift
// COVID-19
//
// Created by Lunabee Studio / Date - 29/04/2020 - for the STOP-COVID project.
//
import UIKit
struct RBReceivedProximity {
var helloMessage: Data
var timeCollectedOnDevice: Int
var rssiRaw: Int
var rssiCalibrated: Int
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBReceivedProximity.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 29/04/2020 - for the STOP-COVID project.
//
import UIKit
struct RBReceivedProximity {
var data: Data
var timeCollectedOnDevice: Int
var rssiRaw: Int
var rssiCalibrated: Int
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBRegisterResponse.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 29/04/2020 - for the STOP-COVID project.
//
import UIKit
struct RBRegisterResponse {
var key: String
var epochs: [RBEpoch]
var timeStart: Int
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBStatusMessage.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 29/04/2020 - for the STOP-COVID project.
//
import UIKit
struct RBStatusMessage {
var ebid: String
var time: String
var mac: String
}
//
// RBStatusResponse.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 29/04/2020 - for the STOP-COVID project.
//
import UIKit
struct RBStatusResponse {
var atRisk: Bool
var lastExposureTimeFrame: Int?
var epochs: [RBEpoch]
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBStorable.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 23/04/2020 - for the STOP-COVID project.
//
import UIKit
protocol RBStorable: Codable {
static func from(data: Data) throws -> Self
func toData() throws -> Data
}
extension RBStorable {
static func from(data: Data) throws -> Self {
return try JSONDecoder().decode(Self.self, from: data)
}
func toData() throws -> Data {
return try JSONEncoder().encode(self)
}
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBUnregisterMessage.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 29/04/2020 - for the STOP-COVID project.
//
import UIKit
struct RBUnregisterMessage {
var ebid: String
var time: String
var mac: String
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBBluetooth.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 30/04/2020 - for the STOP-COVID project.
//
import UIKit
protocol RBBluetooth {
func start(helloMessageCreationHandler: @escaping () -> Data,
ebidExtractionHandler: @escaping (_ data: Data) -> Data,
didReceiveProximity: @escaping (_ proximities: [RBReceivedProximity]) -> ())
func stop()
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBServer.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 27/04/2020 - for the STOP-COVID project.
//
import Foundation
protocol RBServer {
func status(ebid: String, time: String, mac: String, completion: @escaping (_ result: Result<RBStatusResponse, Error>) -> ())
func report(token: String, helloMessages: [RBLocalProximity], completion: @escaping (_ error: Error?) -> ())
func register(captcha: String, completion: @escaping (_ result: Result<RBRegisterResponse, Error>) -> ())
func unregister(ebid: String, time: String, mac: String, completion: @escaping (_ error: Error?) -> ())
func deleteExposureHistory(ebid: String, time: String, mac: String, completion: @escaping (_ error: Error?) -> ())
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBStorage.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 27/04/2020 - for the STOP-COVID project.
//
import Foundation
protocol RBStorage {
func start()
func stop()
// MARK: - Epoch -
func save(epochs: [RBEpoch])
func getCurrentEpoch() -> RBEpoch?
func getEpoch(for id: Int) -> RBEpoch?
func getLastEpoch() -> RBEpoch?
// MARK: - TimeStart -
func save(timeStart: Int) throws
func getTimeStart() throws -> Int
// MARK: - Key -
func save(key: Data)
func getKey() -> Data?
func isKeyStored() -> Bool
// MARK: - Proximity -
func save(proximityActivated: Bool)
func isProximityActivated() -> Bool
// MARK: - Local Proximity -
func save(localProximities: [RBLocalProximity])
func getLocalProximityList() -> [RBLocalProximity]
// MARK: - Status: isAtRisk -
func save(isAtRisk: Bool?)
func isAtRisk() -> Bool?
// MARK: - Status: last exposure time frame -
func save(lastExposureTimeFrame: Int?)
func lastExposureTimeFrame() -> Int?
// MARK: - Status: last status received date -
func saveLastStatusReceivedDate(_ date: Date?)
func lastStatusReceivedDate() -> Date?
// MARK: - Status: Is sick -
func save(isSick: Bool)
func isSick() -> Bool
// MARK: - Positive to symptoms -
func save(positiveToSymptoms: Bool?)
func positiveToSymptoms() -> Bool?
// MARK: - Data cleraing -
func clearLocalEpochs()
func clearLocalProximityList()
func clearAll()
}
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// RBConstants.swift
// STOP-COVID
//
// Created by Lunabee Studio / Date - 23/04/2020 - for the STOP-COVID project.
//
import Foundation
struct RBConstants {
static let epochDurationInSeconds: Int = 15 * 60
struct Prefix {
static let c1: UInt8 = 0b00000001