Skip to content

Commit

Permalink
Merge pull request #206 from DP-3T/develop
Browse files Browse the repository at this point in the history
1.2.0 SDK Version
  • Loading branch information
stmitt authored Aug 26, 2020
2 parents a65a9f0 + 9452e5d commit b526c66
Show file tree
Hide file tree
Showing 20 changed files with 304 additions and 57 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ jobs:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

- name: Switch to Xcode 11.5
run: sudo xcode-select --switch /Applications/Xcode_11.5.app
- name: Switch to Xcode 11.6
run: sudo xcode-select --switch /Applications/Xcode_11.6.app

# Generate xcode project
- name: Generate xcodeproj
Expand All @@ -32,8 +32,8 @@ jobs:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

- name: Switch to Xcode 11.5
run: sudo xcode-select --switch /Applications/Xcode_11.5.app
- name: Switch to Xcode 11.6
run: sudo xcode-select --switch /Applications/Xcode_11.6.app

# Compile sample app for iOS Simulator (no signing)
- name: Compile and run tests
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy_to_cocoapods.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ jobs:
steps:
- uses: actions/checkout@v1

- name: Switch to Xcode 11.5
run: sudo xcode-select --switch /Applications/Xcode_11.5.app
- name: Switch to Xcode 11.6
run: sudo xcode-select --switch /Applications/Xcode_11.6.app

- name: Install Cocoapods
run: gem install cocoapods
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/distribute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: distribute

on:
push:
branches: [ master, master-alpha ]
branches: [ master, master-alpha, develop ]
jobs:
appcenter:
runs-on: macOS-latest
Expand All @@ -11,8 +11,8 @@ jobs:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

- name: Switch to Xcode 11.5
run: sudo xcode-select --switch /Applications/Xcode_11.5.app
- name: Switch to Xcode 11.6
run: sudo xcode-select --switch /Applications/Xcode_11.6.app

- name: Run fastlane build
env:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog for DP3T-SDK iOS

## Version 1.2.0 (26.08.2020)
- resolves keychain issue with iOS 14
- adds iOS 14 info.plist entries for calibration app
- submitted keys are now always filled up to 30 instead of 14
- resolves detection issue for iOS 14 beta 5

## Version 1.1.1 (13.08.2020)
- DP3TNetworkingError.HTTPFailureResponse includes raw data

Expand Down
2 changes: 1 addition & 1 deletion DP3TSDK.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Pod::Spec.new do |spec|

spec.name = "DP3TSDK"
spec.version = ENV['LIB_VERSION'] || '1.1.1'
spec.version = ENV['LIB_VERSION'] || '1.2.0'
spec.summary = "Open protocol for COVID-19 proximity tracing using Bluetooth Low Energy on mobile devices"

spec.description = <<-DESC
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,19 @@ DP3T-SDK is available through [Cocoapods](https://cocoapods.org/)

```ruby

pod 'DP3TSDK', => '1.1.1'
pod 'DP3TSDK', => '1.2.0'

```

This version points to the HEAD of the `develop` branch and will always fetch the latest development status. Future releases will be made available using semantic versioning to ensure stability for depending projects.

## Using the SDK

In order to use the SDK with iOS 14 you need to specify the region for which the app works and the version of the [Exposure Notification](https://developer.apple.com/documentation/exposurenotification) Framework which should be used.

This is done by adding [`ENDeveloperRegion`](https://developer.apple.com/documentation/bundleresources/information_property_list/endeveloperregion) as an `Info.plist` property with the according ISO 3166-1 country code as its value.
The SDK currently works with EN Framework version 1 and therefore we need to specify [`ENAPIVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/enapiversion) with a value of 1 in the `Info.plist`.

### Initialization

In your AppDelegate in the `didFinishLaunchingWithOptions` function you have to initialize the SDK.
Expand Down
43 changes: 36 additions & 7 deletions SampleApp/DP3TSampleApp/ControlViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ class ControlViewController: UIViewController {
uploadKeysButton.setTitleColor(.blue, for: .normal)
uploadKeysButton.setTitleColor(.black, for: .highlighted)
}
uploadKeysButton.setTitle("Upload Keys for Debugging", for: .normal)
uploadKeysButton.setTitle("Upload Keys for Experiment", for: .normal)
uploadKeysButton.addTarget(self, action: #selector(uploadKeys), for: .touchUpInside)
stackView.addArrangedSubview(uploadKeysButton)
}
Expand Down Expand Up @@ -340,16 +340,45 @@ class ControlViewController: UIViewController {
}

@objc func uploadKeys() {
let alert = UIAlertController(title: "Upload Keys", message: "Enter debug device name", preferredStyle: .alert)
let alert = UIAlertController(title: "Upload Keys", message: "Enter experiment name and device name", preferredStyle: .alert)

alert.addTextField { textField in
textField.placeholder = "debug device name"
alert.addTextField { (textField) in
textField.placeholder = "experiment Name"
textField.text = ""
textField.tag = 1
}

alert.addAction(UIAlertAction(title: "Upload", style: .default, handler: { [weak alert] _ in
let textField = alert?.textFields![0]
self.uploadHelper.uploadDebugKeys(debugName: textField?.text ?? "noName") { result in
alert.addTextField { textField in
textField.placeholder = "debug device name"
textField.text = UIDevice.current.name
textField.tag = 0
}

alert.addAction(UIAlertAction(title: "Upload", style: .default, handler: { [weak alert, weak self] _ in
guard let self = self,
let deviceNameTextField = alert?.textFields?.first(where: { $0.tag == 0 }),
let experimentNameTextField = alert?.textFields?.first(where: { $0.tag == 1 }) else { return }
guard let deviceName = deviceNameTextField.text, !deviceName.isEmpty,
let experimentName = experimentNameTextField.text, !experimentName.isEmpty else {
var errors: [String] = []
if deviceNameTextField.text == nil ||
deviceNameTextField.text!.isEmpty {
errors.append("device name")
}
if experimentNameTextField.text == nil ||
experimentNameTextField.text!.isEmpty {
errors.append("experiment name")
}

let errorAlert = UIAlertController(title: "Error", message: "Please provide \(errors.joined(separator: " and "))", preferredStyle: .alert)
errorAlert.addAction(.init(title: "OK", style: .default) { _ in
self.uploadKeys()
})
self.present(errorAlert, animated: true, completion: nil)
return
}
let name = "experiment_\(experimentName)_\(deviceName)";
self.uploadHelper.uploadDebugKeys(debugName: name) { result in
print(result)
}
}))
Expand Down
4 changes: 4 additions & 0 deletions SampleApp/DP3TSampleApp/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ENAPIVersion</key>
<string>1</string>
<key>ENDeveloperRegion</key>
<string>CH</string>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>org.dpppt.exposure-notification</string>
Expand Down
117 changes: 103 additions & 14 deletions SampleApp/DP3TSampleApp/KeysViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,28 @@ import ExposureNotification
import UIKit
import ZIPFoundation

class KeyDiffableDataSource: UITableViewDiffableDataSource<Date, NetworkingHelper.DebugZips> {
struct KeySection: Hashable {
let date: Date
let experimentName: String?

var title: String {
let dateString = Self.formatter.string(from: date)
if let experimentName = experimentName {
return "\(dateString) - Experiment: \(experimentName)"
}
return "\(dateString) - Single Device"
}

static var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
return formatter
}()
}

class KeyDiffableDataSource: UITableViewDiffableDataSource<KeySection, NetworkingHelper.DebugZips> {
override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
return Self.formatter.string(from: snapshot().sectionIdentifiers[section])
return snapshot().sectionIdentifiers[section].title
}
}

Expand All @@ -35,6 +48,10 @@ class KeysViewController: UIViewController {

private let networkingHelper = NetworkingHelper()

private let activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))

private let nameRegex = try? NSRegularExpression(pattern: "key_export_experiment_([a-zA-Z0-9]+)_(.+)", options: .caseInsensitive)

init() {
super.init(nibName: nil, bundle: nil)
title = "Keys"
Expand Down Expand Up @@ -69,30 +86,76 @@ class KeysViewController: UIViewController {
tableView.dataSource = dataSource
tableView.delegate = self

let date = Date().addingTimeInterval(60 * 60 * 24)
self.dataSource.apply(NSDiffableDataSourceSnapshot<KeySection, NetworkingHelper.DebugZips>(), animatingDifferences: true)


activityIndicator.hidesWhenStopped = true
let barButton = UIBarButtonItem(customView: activityIndicator)
self.navigationItem.setRightBarButton(barButton, animated: true)
activityIndicator.stopAnimating()

let ts = Date().timeIntervalSince1970
let roundendTs = Date(timeIntervalSince1970: ts - ts.truncatingRemainder(dividingBy: 60 * 60 * 24))
let date = roundendTs.addingTimeInterval(60 * 60 * 24)
datePicker.setDate(date, animated: false)
networkingHelper.getDebugKeys(day: date) { [weak self] result in
var snapshot = NSDiffableDataSourceSnapshot<Date, NetworkingHelper.DebugZips>()
snapshot.appendSections([date])
snapshot.appendItems(result, toSection: date)
self?.dataSource.apply(snapshot, animatingDifferences: true)
loadKeys(for: date)
}

private func parseZipName(name: String) -> (experimentName: String?, deviceName: String?) {
guard let regex = nameRegex else { return (nil, nil) }
var experimentName: String?
var deviceName: String?

if let match = regex.firstMatch(in: name, options: [], range: NSRange(location: 0, length: name.utf16.count)) {
if let experimentRange = Range(match.range(at: 1), in: name) {
experimentName = String(name[experimentRange])
}

if let deviceRange = Range(match.range(at: 2), in: name) {
deviceName = String(name[deviceRange])
}
}
return (experimentName, deviceName)
}

@objc func datePickerDidChange() {
let date = datePicker.date
guard !dataSource.snapshot().sectionIdentifiers.contains(date) else { return }
private func groupZips(zips: [NetworkingHelper.DebugZips], date: Date) -> [KeySection: [NetworkingHelper.DebugZips]] {
return zips.reduce([KeySection: [NetworkingHelper.DebugZips]]()) { (result, zip) -> [KeySection : [NetworkingHelper.DebugZips]] in
let (experimentName, _) = self.parseZipName(name: zip.name)
let section = KeySection(date: date, experimentName: experimentName)
var mutatingResult = result
mutatingResult[section, default: []].append(zip)
return mutatingResult
}
}

func loadKeys(for date: Date) {
activityIndicator.startAnimating()
networkingHelper.getDebugKeys(day: date) { [weak self] result in
guard let self = self else { return }
defer { self.activityIndicator.stopAnimating() }
var snapshot = self.dataSource.snapshot()
if !snapshot.sectionIdentifiers.contains(date) {
snapshot.appendSections([date])

let grouped = self.groupZips(zips: result, date: date)

for groupedItem in grouped {
let section = groupedItem.key
snapshot.insert(section: section, items: groupedItem.value)
}

if grouped.isEmpty {
let section = KeySection(date: date, experimentName: nil)
snapshot.insert(section: section, items: [])
}
snapshot.appendItems(result, toSection: date)

self.dataSource.apply(snapshot, animatingDifferences: true)
}
}

@objc func datePickerDidChange() {
let date = datePicker.date
loadKeys(for: date)
}

func makeDataSource() -> KeyDiffableDataSource {
let reuseIdentifier = cellReuseIdentifier

Expand Down Expand Up @@ -171,3 +234,29 @@ extension ENExposureConfiguration {
return configuration
}
}

extension NSDiffableDataSourceSnapshot where SectionIdentifierType == KeySection,ItemIdentifierType == NetworkingHelper.DebugZips {
mutating func insert(section: KeySection, items: [NetworkingHelper.DebugZips]) {
if !sectionIdentifiers.contains(section) {
var inserted = false
for s in sectionIdentifiers {
if !inserted,
section.date > s.date,
(section.experimentName == nil || s.experimentName == nil) || section.experimentName! > s.experimentName! {
insertSections([section], beforeSection: s)
inserted = true
break
}
}
if !inserted {
appendSections([section])
}
}
let existingNames = itemIdentifiers(inSection: section).map(\.name)
for zip in items {
if !existingNames.contains(zip.name) {
appendItems([zip], toSection: section)
}
}
}
}
3 changes: 2 additions & 1 deletion SampleApp/DP3TSampleApp/NetworkingHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import CommonCrypto
import ExposureNotification
import UIKit
import ZIPFoundation
import DP3TSDK

struct CodableDiagnosisKey: Codable, Equatable, Hashable {
let keyData: Data
Expand Down Expand Up @@ -161,7 +162,7 @@ class NetworkingHelper {
}

var keys = keys?.map(CodableDiagnosisKey.init(key:)) ?? []
while keys.count < 14 {
while keys.count < DP3TTracing.parameters.crypto.numberOfKeysToSubmit {
let ts = Date().timeIntervalSince1970
let day = ts - ts.truncatingRemainder(dividingBy: 60 * 60 * 24)
keys.append(.init(keyData: Crypto.generateRandomKey(lenght: 16),
Expand Down
6 changes: 5 additions & 1 deletion Sources/DP3TSDK/Cryptography/DiagnosisKeysProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ extension ENManager: DiagnosisKeysProvider {
completionHandler(.failure(.exposureNotificationError(error: error)))
} else if let keys = keys {
logger.log("received %d keys", keys.count)
var filteredKeys = keys

let oldestDate = DayDate(date: Date().addingTimeInterval(-Default.shared.parameters.crypto.maxAgeOfKeyToRetreive)).dayMin

// make sure to never retreive keys older than maxNumberOfDaysToRetreive even if the onset date is older
var filteredKeys = keys.filter { $0.date > oldestDate }

// if a onsetDate was passed we filter the keys using it
if let onsetDate = onsetDate {
Expand Down
8 changes: 5 additions & 3 deletions Sources/DP3TSDK/DP3TParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import CoreBluetooth
import Foundation

public struct DP3TParameters: Codable {
static let parameterVersion: Int = 12
static let parameterVersion: Int = 15

let version: Int

Expand All @@ -31,9 +31,11 @@ public struct DP3TParameters: Codable {

public var timeZone: TimeZone = TimeZone(identifier: "UTC")!

public var numberOfDaysToKeepMatchedContacts = 10
public var numberOfDaysToKeepMatchedContacts = 14

public var numberOfKeysToSubmit: Int = 14
public var maxAgeOfKeyToRetreive: TimeInterval = .day * 14

public var numberOfKeysToSubmit: Int = 30
}

public struct Networking: Codable {
Expand Down
2 changes: 1 addition & 1 deletion Sources/DP3TSDK/DP3TTracing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ private var instance: DP3TSDK!
/// DP3TTracing
public enum DP3TTracing {
/// The current version of the SDK
public static let frameworkVersion: String = "1.1.1"
public static let frameworkVersion: String = "1.2.0"

/// sets global parameter values which are used throughout the sdk
public static var parameters: DP3TParameters {
Expand Down
Loading

0 comments on commit b526c66

Please sign in to comment.