diff --git a/Example/Podfile b/Example/Podfile index 396887bb..08010e5e 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -1,4 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '10.3' use_frameworks! target 'xDripG5_Example' do diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 9d30a5a1..606de109 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: ../ SPEC CHECKSUMS: - xDripG5: 8779a4f495fd8eb81a3d75457afe9b95fb52f61d + xDripG5: 92588b5d1d31b337673af62bb433e503001356ae -PODFILE CHECKSUM: 6b30cba971694d5258509315fb52eb645c9bc5e3 +PODFILE CHECKSUM: c62f3fcb344335d79abdcccea268615000e8801b -COCOAPODS: 1.1.0.rc.2 +COCOAPODS: 1.3.1 diff --git a/Example/xDripG5.xcodeproj/project.pbxproj b/Example/xDripG5.xcodeproj/project.pbxproj index 02b5616c..cf8a06c5 100644 --- a/Example/xDripG5.xcodeproj/project.pbxproj +++ b/Example/xDripG5.xcodeproj/project.pbxproj @@ -202,13 +202,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-xDripG5_Example-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 2FC4228A9369221D367ED688 /* [CP] Copy Pods Resources */ = { @@ -232,9 +235,12 @@ files = ( ); inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-xDripG5_Example/Pods-xDripG5_Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/xDripG5/xDripG5.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/xDripG5.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -381,9 +387,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = F3147B948B4F90A741304461 /* Pods-xDripG5_Example.debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = xDripG5/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.xDripG5-Example"; @@ -397,9 +405,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = CFBD776BFE02F42998A8820B /* Pods-xDripG5_Example.release.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)"; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = xDripG5/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.xDripG5-Example"; diff --git a/Example/xDripG5/AppDelegate.swift b/Example/xDripG5/AppDelegate.swift index f230f6d9..c988adc5 100644 --- a/Example/xDripG5/AppDelegate.swift +++ b/Example/xDripG5/AppDelegate.swift @@ -73,24 +73,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate, TransmitterDelegate { }() func transmitter(_ transmitter: Transmitter, didError error: Error) { - if let vc = window?.rootViewController as? TransmitterDelegate { - DispatchQueue.main.async { + DispatchQueue.main.async { + if let vc = self.window?.rootViewController as? TransmitterDelegate { vc.transmitter(transmitter, didError: error) } } } func transmitter(_ transmitter: Transmitter, didRead glucose: Glucose) { - if let vc = window?.rootViewController as? TransmitterDelegate { - DispatchQueue.main.async { + DispatchQueue.main.async { + if let vc = self.window?.rootViewController as? TransmitterDelegate { vc.transmitter(transmitter, didRead: glucose) } } } func transmitter(_ transmitter: Transmitter, didReadUnknownData data: Data) { - if let vc = window?.rootViewController as? TransmitterDelegate { - DispatchQueue.main.async { + DispatchQueue.main.async { + if let vc = self.window?.rootViewController as? TransmitterDelegate { vc.transmitter(transmitter, didReadUnknownData: data) } } diff --git a/Example/xDripG5/ViewController.swift b/Example/xDripG5/ViewController.swift index faef5334..35c27737 100644 --- a/Example/xDripG5/ViewController.swift +++ b/Example/xDripG5/ViewController.swift @@ -68,9 +68,9 @@ class ViewController: UIViewController, TransmitterDelegate, UITextFieldDelegate if let text = textField.text { let newString = text.replacingCharacters(in: range.rangeOfString(text), with: string) - if newString.characters.count > 6 { + if newString.count > 6 { return false - } else if newString.characters.count == 6 { + } else if newString.count == 6 { AppDelegate.sharedDelegate.transmitter?.ID = newString UserDefaults.standard.transmitterID = newString @@ -86,7 +86,7 @@ class ViewController: UIViewController, TransmitterDelegate, UITextFieldDelegate } func textFieldDidEndEditing(_ textField: UITextField) { - if textField.text?.characters.count != 6 { + if textField.text?.count != 6 { textField.text = UserDefaults.standard.transmitterID } } @@ -129,8 +129,8 @@ class ViewController: UIViewController, TransmitterDelegate, UITextFieldDelegate private extension NSRange { func rangeOfString(_ string: String) -> Range { - let startIndex = string.characters.index(string.startIndex, offsetBy: location) - let endIndex = string.characters.index(startIndex, offsetBy: length) + let startIndex = string.index(string.startIndex, offsetBy: location) + let endIndex = string.index(startIndex, offsetBy: length) return startIndex.. "loudnate@gmail.com" } - s.source = { :git => "https://github.com/LoopKit/xDripG5.git", :tag => s.version.to_s } + s.source = { :git => "https://github.com/LoopKit/xDripG5.git", :tag => "v" + s.version.to_s } - s.platform = :ios, '9.3' + s.platform = :ios, '10.3' s.requires_arc = true s.source_files = ['xDripG5/**/*.swift', 'xDripG5/AESCrypt.{h,m}', 'Pod/*.h'] diff --git a/xDripG5/AESCrypt.m b/xDripG5/AESCrypt.m index 1c3850b6..1a0df4a1 100644 --- a/xDripG5/AESCrypt.m +++ b/xDripG5/AESCrypt.m @@ -13,7 +13,7 @@ @implementation AESCrypt + (NSData *)encryptData:(NSData *)data usingKey:(NSData *)key error:(NSError * _Nullable __autoreleasing *)error { - NSMutableData *dataOut = [[NSMutableData alloc] initWithCapacity:16]; + NSMutableData *dataOut = [NSMutableData dataWithLength: data.length + kCCBlockSizeAES128]; CCCryptorStatus status = CCCrypt(kCCEncrypt, kCCAlgorithmAES, diff --git a/xDripG5/BluetoothManager.swift b/xDripG5/BluetoothManager.swift index e1f133c1..debde275 100644 --- a/xDripG5/BluetoothManager.swift +++ b/xDripG5/BluetoothManager.swift @@ -175,7 +175,7 @@ class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate /// Any error surfaced during the active operation private var operationError: Error? - func readValueForCharacteristicAndWait(_ UUID: CGMServiceCharacteristicUUID, timeout: TimeInterval = 2, expectingFirstByte firstByte: UInt8? = nil) throws -> Data { + func readValueForCharacteristicAndWait(_ UUID: CGMServiceCharacteristicUUID, timeout: TimeInterval = 10, expectingFirstByte firstByte: UInt8? = nil) throws -> Data { guard manager.state == .poweredOn && operationConditions.isEmpty, let peripheral = peripheral else { throw BluetoothManagerError.notReady } @@ -206,7 +206,7 @@ class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate return characteristic.value ?? Data() } - func setNotifyEnabledAndWait(_ enabled: Bool, forCharacteristicUUID UUID: CGMServiceCharacteristicUUID, timeout: TimeInterval = 2) throws { + func setNotifyEnabledAndWait(_ enabled: Bool, forCharacteristicUUID UUID: CGMServiceCharacteristicUUID, timeout: TimeInterval = 10) throws { guard manager.state == .poweredOn && operationConditions.isEmpty, let peripheral = peripheral else { throw BluetoothManagerError.notReady } @@ -264,7 +264,7 @@ class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate return characteristic.value ?? Data() } - func writeValueAndWait(_ value: Data, forCharacteristicUUID UUID: CGMServiceCharacteristicUUID, timeout: TimeInterval = 2, expectingFirstByte firstByte: UInt8? = nil) throws -> Data { + func writeValueAndWait(_ value: Data, forCharacteristicUUID UUID: CGMServiceCharacteristicUUID, timeout: TimeInterval = 10, expectingFirstByte firstByte: UInt8? = nil) throws -> Data { guard manager.state == .poweredOn && operationConditions.isEmpty, let peripheral = peripheral else { throw BluetoothManagerError.notReady } @@ -299,6 +299,22 @@ class BluetoothManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate return characteristic.value ?? Data() } + func waitForTime(timeout: TimeInterval = 10) throws { + guard manager.state == .poweredOn && operationConditions.isEmpty, let peripheral = peripheral else { + throw BluetoothManagerError.notReady + } + + operationLock.lock() + + operationLock.wait(until: Date(timeIntervalSinceNow: timeout)) + + defer { + operationLock.unlock() + } + + return + } + // MARK: - Accessors var isScanning: Bool { diff --git a/xDripG5/Messages/AuthRequestTxMessage.swift b/xDripG5/Messages/AuthRequestTxMessage.swift index 8b27c5c5..30b163b4 100644 --- a/xDripG5/Messages/AuthRequestTxMessage.swift +++ b/xDripG5/Messages/AuthRequestTxMessage.swift @@ -19,7 +19,7 @@ struct AuthRequestTxMessage: TransmitterTxMessage { NSUUID().getBytes(&UUIDBytes) - singleUseToken = Data(bytes: UUIDBytes) + singleUseToken = Data(bytes: UUIDBytes[0..<8]) } var byteSequence: [Any] { diff --git a/xDripG5/Messages/SensorRxMessage.swift b/xDripG5/Messages/SensorRxMessage.swift new file mode 100644 index 00000000..d41bc2fd --- /dev/null +++ b/xDripG5/Messages/SensorRxMessage.swift @@ -0,0 +1,9 @@ +// +// SensorRxMessage.swift +// xDripG5 +// +// Created by Renee on 04/02/2018. +// Copyright © 2018 LoopKit Authors. All rights reserved. +// + +import Foundation diff --git a/xDripG5/Messages/SensorTxMessage.swift b/xDripG5/Messages/SensorTxMessage.swift new file mode 100644 index 00000000..0799d82c --- /dev/null +++ b/xDripG5/Messages/SensorTxMessage.swift @@ -0,0 +1,9 @@ +// +// SensorTxMessage.swift +// xDripG5 +// +// Created by Renee on 04/02/2018. +// Copyright © 2018 LoopKit Authors. All rights reserved. +// + +import Foundation diff --git a/xDripG5/Transmitter.swift b/xDripG5/Transmitter.swift index 1fe3f65a..4147bab6 100644 --- a/xDripG5/Transmitter.swift +++ b/xDripG5/Transmitter.swift @@ -168,79 +168,75 @@ public final class Transmitter: BluetoothManagerDelegate { // MARK: - Helpers private func authenticate() throws { - if let data = try? bluetoothManager.readValueForCharacteristicAndWait(.Authentication), - let status = AuthStatusRxMessage(data: data), status.authenticated == 1 && status.bonded == 1 - { - NSLog("Transmitter already authenticated.") - } else { - do { - try bluetoothManager.setNotifyEnabledAndWait(true, forCharacteristicUUID: .Authentication) - } catch let error { - throw TransmitterError.authenticationError("Error enabling notification: \(error)") - } + let authMessage = AuthRequestTxMessage() + let data: Data - let authMessage = AuthRequestTxMessage() - let data: Data + do { + _ = try bluetoothManager.writeValueAndWait(authMessage.data, forCharacteristicUUID: .Authentication) + data = try bluetoothManager.readValueForCharacteristicAndWait(.Authentication, expectingFirstByte: AuthChallengeRxMessage.opcode) + } catch let error { + throw TransmitterError.authenticationError("Error writing transmitter challenge: \(error)") + } + guard let response = AuthChallengeRxMessage(data: data) else { + throw TransmitterError.authenticationError("Unable to parse auth challenge: \(data)") + } + + guard response.tokenHash == self.calculateHash(authMessage.singleUseToken) else { + throw TransmitterError.authenticationError("Transmitter failed auth challenge") + } + + if let challengeHash = self.calculateHash(response.challenge) { + let data: Data do { - data = try bluetoothManager.writeValueAndWait(authMessage.data, forCharacteristicUUID: .Authentication, expectingFirstByte: AuthChallengeRxMessage.opcode) + _ = try bluetoothManager.writeValueAndWait(AuthChallengeTxMessage(challengeHash: challengeHash).data, forCharacteristicUUID: .Authentication) + data = try bluetoothManager.readValueForCharacteristicAndWait(.Authentication, expectingFirstByte: AuthStatusRxMessage.opcode) } catch let error { - throw TransmitterError.authenticationError("Error writing transmitter challenge: \(error)") + throw TransmitterError.authenticationError("Error writing challenge response: \(error)") } - guard let response = AuthChallengeRxMessage(data: data) else { - throw TransmitterError.authenticationError("Unable to parse auth challenge: \(data)") + guard let response = AuthStatusRxMessage(data: data) else { + throw TransmitterError.authenticationError("Unable to parse auth status: \(data)") } - guard response.tokenHash == self.calculateHash(authMessage.singleUseToken) else { - throw TransmitterError.authenticationError("Transmitter failed auth challenge") + guard response.authenticated == 1 else { + throw TransmitterError.authenticationError("Transmitter rejected auth challenge") } - if let challengeHash = self.calculateHash(response.challenge) { + if response.bonded != 0x1 { + do { + _ = try bluetoothManager.writeValueAndWait(KeepAliveTxMessage(time: 25).data, forCharacteristicUUID: .Authentication) + } catch let error { + throw TransmitterError.authenticationError("Error writing keep-alive for bond: \(error)") + } + let data: Data do { - data = try bluetoothManager.writeValueAndWait(AuthChallengeTxMessage(challengeHash: challengeHash).data, forCharacteristicUUID: .Authentication, expectingFirstByte: AuthStatusRxMessage.opcode) + _ = try bluetoothManager.writeValueAndWait(BondRequestTxMessage().data, forCharacteristicUUID: .Authentication) } catch let error { - throw TransmitterError.authenticationError("Error writing challenge response: \(error)") + throw TransmitterError.authenticationError("Error writing bond request: \(error)") } - guard let response = AuthStatusRxMessage(data: data) else { - throw TransmitterError.authenticationError("Unable to parse auth status: \(data)") + // Wait for the OS dialog to pop-up before continuing. + do { + try bluetoothManager.waitForTime(timeout: 5) + } catch let error { + throw TransmitterError.authenticationError("Error waiting for bond request response: \(error)") } - guard response.authenticated == 1 else { - throw TransmitterError.authenticationError("Transmitter rejected auth challenge") + do { + data = try bluetoothManager.readValueForCharacteristicAndWait(.Authentication, expectingFirstByte: AuthStatusRxMessage.opcode) + } catch let error { + throw TransmitterError.authenticationError("Error reading status after bond request: \(error)") } - if response.bonded != 0x1 { - do { - _ = try bluetoothManager.writeValueAndWait(KeepAliveTxMessage(time: 25).data, forCharacteristicUUID: .Authentication) - } catch let error { - throw TransmitterError.authenticationError("Error writing keep-alive for bond: \(error)") - } - - let data: Data - do { - // Wait for the OS dialog to pop-up before continuing. - data = try bluetoothManager.writeValueAndWait(BondRequestTxMessage().data, forCharacteristicUUID: .Authentication, timeout: 15, expectingFirstByte: AuthStatusRxMessage.opcode) - } catch let error { - throw TransmitterError.authenticationError("Error writing bond request: \(error)") - } - - guard let response = AuthStatusRxMessage(data: data) else { - throw TransmitterError.authenticationError("Unable to parse auth status: \(data)") - } - - guard response.bonded == 0x1 else { - throw TransmitterError.authenticationError("Transmitter failed to bond") - } + guard let response = AuthStatusRxMessage(data: data) else { + throw TransmitterError.authenticationError("Unable to parse auth status: \(data)") } - } - do { - try bluetoothManager.setNotifyEnabledAndWait(false, forCharacteristicUUID: .Authentication) - } catch let error { - throw TransmitterError.authenticationError("Error disabling notification: \(error)") + guard response.bonded == 0x1 else { + throw TransmitterError.authenticationError("Transmitter failed to bond") + } } } }