├── Resources └── Screenshot.png ├── BLE-Viewer ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── BLE_Viewer.entitlements ├── HandoffModels │ ├── ScanModel.swift │ ├── HandoffBLE.swift │ ├── HandoffAdvertisement.swift │ └── HandoffActivity.swift ├── ContentView.swift ├── Info.plist ├── Extensions │ ├── HexToDataExtensions.swift │ └── DataIntExtensions.swift ├── AppDelegate.swift ├── Logging │ └── Log.swift ├── BLE │ └── BLEReceiver.swift ├── Crypto │ ├── BLEDecryptor.swift │ └── KeychainAccess.swift ├── BLEScannerVC.swift └── Base.lproj │ └── Main.storyboard ├── BLE-Viewer.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcuserdata │ └── Alex.xcuserdatad │ │ ├── xcschemes │ │ └── xcschememanagement.plist │ │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist └── project.pbxproj ├── BLE-ViewerTests ├── Info.plist └── BLE_ViewerTests.swift ├── LICENSE └── README.md /Resources/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sn0wfreezeDev/Handoff-BLE-Viewer/HEAD/Resources/Screenshot.png -------------------------------------------------------------------------------- /BLE-Viewer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BLE-Viewer/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BLE-Viewer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BLE-Viewer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BLE-Viewer/BLE_Viewer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.device.bluetooth 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /BLE-Viewer.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "CryptoSwift", 6 | "repositoryURL": "https://github.com/krzyzanowskim/CryptoSwift.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "3a2acbb32ab68215ee1596ee6004da8e90c3721b", 10 | "version": "1.0.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /BLE-Viewer.xcodeproj/xcuserdata/Alex.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BLE-Viewer.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /BLE-Viewer/HandoffModels/ScanModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReceivedAdvertisements.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 26.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreBluetooth 11 | 12 | class ScanModel { 13 | let peripheral: CBPeripheral 14 | var advertisements: [HandoffAdvertisement] = [] 15 | 16 | init(peripheral: CBPeripheral, advertisements: [HandoffAdvertisement] = []) { 17 | self.peripheral = peripheral 18 | self.advertisements = advertisements 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BLE-Viewer/ContentView.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// ContentView.swift 3 | //// BLE-Viewer 4 | //// 5 | //// Created by Alexander Heinrich on 24.09.19. 6 | //// Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | //// 8 | // 9 | // 10 | //import SwiftUI 11 | // 12 | //@available(macOS 10.15, *) 13 | //struct ContentView: View { 14 | //// var body: some View { 15 | // Text("Hello World") 16 | // .frame(maxWidth: .infinity, maxHeight: .infinity) 17 | // } 18 | //} 19 | // 20 | //@available(macOS 10.15, *) 21 | //struct ContentView_Previews: PreviewProvider { 22 | // static var previews: some View { 23 | // ContentView() 24 | // } 25 | //} 26 | -------------------------------------------------------------------------------- /BLE-ViewerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alexander Heinrich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /BLE-Viewer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /BLE-Viewer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2019 Alexander Heinrich. All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | NSSupportsAutomaticTermination 34 | 35 | NSSupportsSuddenTermination 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /BLE-Viewer/Extensions/HexToDataExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HexToDataExtensions.swift 3 | // ThesisTools 4 | // 5 | // Created by Alexander Heinrich on 08.05.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | /// Create `Data` from hexadecimal string representation 14 | /// 15 | /// This creates a `Data` object from hex string. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed. 16 | /// 17 | /// - returns: Data represented by this hexadecimal string. 18 | 19 | var hexadecimal: Data? { 20 | var data = Data(capacity: count / 2) 21 | 22 | let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive) 23 | regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in 24 | let byteString = (self as NSString).substring(with: match!.range) 25 | let num = UInt8(byteString, radix: 16)! 26 | data.append(num) 27 | } 28 | 29 | guard data.count > 0 else { return nil } 30 | 31 | return data 32 | } 33 | 34 | } 35 | 36 | extension Data { 37 | 38 | /// Hexadecimal string representation of `Data` object. 39 | 40 | var hexadecimal: String { 41 | return map { String(format: "%02x", $0) } 42 | .joined() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BLE-Viewer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 24.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | //#if canImport(SwiftUI) 11 | //import SwiftUI 12 | //#endif 13 | 14 | @NSApplicationMain 15 | class AppDelegate: NSObject, NSApplicationDelegate { 16 | 17 | var window: NSWindow! 18 | 19 | 20 | func applicationDidFinishLaunching(_ aNotification: Notification) { 21 | // Create the SwiftUI view that provides the window contents. 22 | window = NSWindow( 23 | contentRect: NSRect(x: 0, y: 0, width: 700, height: 480), 24 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], 25 | backing: .buffered, defer: false) 26 | 27 | if #available(macOS 10.15, *) { 28 | 29 | 30 | window.center() 31 | window.setFrameAutosaveName("Main Window") 32 | window.makeKeyAndOrderFront(nil) 33 | // window.contentView = NSHostingView(rootView: ContentView()) 34 | }else { 35 | let vc = NSStoryboard(name: "Main", bundle: nil).instantiateInitialController() as! NSViewController 36 | 37 | window.contentViewController = vc 38 | } 39 | 40 | 41 | } 42 | 43 | 44 | func applicationWillTerminate(_ aNotification: Notification) { 45 | // Insert code here to tear down your application 46 | } 47 | 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Handoff BLE-Viewer 2 | 3 | This project is part of my Master Thesis @ SEEMOO (TU Darmstadt). 4 | With iOS 8 Apple has released a feature called *Handoff*. It allows a user to start an activity on one device and continue on another device. For example you could start writing an email on your iPhone and continue the writing at the same spot with the same content on your Mac. All you need to do is to press one little button. 5 | Researching how this feature is implemented by Apple is my main goal of my Thesis. 6 | 7 | ![Screenshot](Resources/Screenshot.png) 8 | 9 | One big part *Handoff* is Bluetooth Low Energy. With every *Handoff* activity (`NSUserActivity` for devs) the OS will automatically send out a BLE advertising packet. The BLE communication has been researched by Martin et. al. ([Paper]) in *Handoff All Your Privacy: A Review of Apple's Bluetooth Low Energy Continuity Protocol*. The paper does only cover the encrypted parts of the messages and does not actually decrypt any packets. 10 | Luckily most *Handoff* messages are encrypted, but this tool enables you to view the content of a *Handoff* BLE advertising packet. 11 | 12 | I may insert parts of my Thesis here later for further explanations. 13 | 14 | ## Installation 15 | 16 | * Use Xcode 11+ 17 | * Make sure to use the CryptoSwift Swift Package 18 | * Run 19 | 20 | ## Privacy 21 | 22 | The App will request certain keys from your Keychain. This is necessary to decrypt BLE advertising packets. None of the keys will be uploaded somewhere. They will also not be saved outside of your keychain. They will stay in memory as long as the app is running. 23 | Checkout the code if you don't belive me. 24 | 25 | 26 | 27 | 28 | [Paper]: https://arxiv.org/abs/1904.10600 29 | -------------------------------------------------------------------------------- /BLE-Viewer/HandoffModels/HandoffBLE.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HandoffBLE.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 25.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// This struct parses BLE data with Handoff to a format which can than be processed further 13 | /// It is used for parsing raw advertisements which are encrypted 14 | struct HandoffBLE { 15 | let statusByte: UInt8 16 | let counter: UInt16 17 | let counterIV: Array 18 | let authTag: Array 19 | let encryptedData: Data 20 | 21 | init(handoffData: Data) throws { 22 | guard handoffData.count >= 3 else {throw HandoffError.invalidFormat} 23 | 24 | let isAppleData = handoffData[handoffData.startIndex] == 0x4c 25 | let isHandoffData = handoffData[handoffData.startIndex.advanced(by: 2)] == 0x0c 26 | 27 | guard isAppleData && isHandoffData else {throw HandoffError.invalidFormat} 28 | 29 | let handoffContentLength = Int(handoffData[handoffData.startIndex.advanced(by: 3)]) 30 | let contentStartIdx = handoffData.startIndex.advanced(by: 4) 31 | let handoffContent = handoffData[contentStartIdx...(contentStartIdx+handoffContentLength-1)] 32 | statusByte = handoffContent[handoffContent.startIndex] 33 | 34 | let counterData = (handoffContent[handoffContent.startIndex.advanced(by: 1)...handoffContent.startIndex.advanced(by: 2)]) 35 | counter = counterData.uint16 36 | counterIV = Array(counterData) 37 | 38 | let authTagByte = handoffContent[handoffContent.startIndex.advanced(by: 3)] 39 | authTag = Array([authTagByte]) 40 | 41 | encryptedData = handoffContent[handoffContent.startIndex.advanced(by: 4)...] 42 | } 43 | 44 | enum HandoffError: Error { 45 | case invalidFormat 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BLE-Viewer/Extensions/DataIntExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataExtensions.swift 3 | // 4 | // Created by Boris Polania on 2/16/18. 5 | // 6 | 7 | import Foundation 8 | 9 | extension Data { 10 | 11 | var uint8: UInt8 { 12 | get { 13 | var number: UInt8 = 0 14 | self.copyBytes(to:&number, count: MemoryLayout.size) 15 | return number 16 | } 17 | } 18 | 19 | var uint16: UInt16 { 20 | get { 21 | let i16array = self.withUnsafeBytes { 22 | UnsafeBufferPointer(start: $0, count: self.count/2).map(UInt16.init(littleEndian:)) 23 | } 24 | return i16array[0] 25 | } 26 | } 27 | 28 | var uint32: UInt32 { 29 | get { 30 | let i32array = self.withUnsafeBytes { 31 | UnsafeBufferPointer(start: $0, count: self.count/2).map(UInt32.init(littleEndian:)) 32 | } 33 | return i32array[0] 34 | } 35 | } 36 | 37 | var uuid: NSUUID? { 38 | get { 39 | var bytes = [UInt8](repeating: 0, count: self.count) 40 | self.copyBytes(to:&bytes, count: self.count * MemoryLayout.size) 41 | return NSUUID(uuidBytes: bytes) 42 | } 43 | } 44 | var stringASCII: String? { 45 | get { 46 | return NSString(data: self, encoding: String.Encoding.ascii.rawValue) as String? 47 | } 48 | } 49 | 50 | var stringUTF8: String? { 51 | get { 52 | return NSString(data: self, encoding: String.Encoding.utf8.rawValue) as String? 53 | } 54 | } 55 | 56 | struct HexEncodingOptions: OptionSet { 57 | let rawValue: Int 58 | static let upperCase = HexEncodingOptions(rawValue: 1 << 0) 59 | } 60 | 61 | func hexEncodedString(options: HexEncodingOptions = []) -> String { 62 | let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx" 63 | return map { String(format: format, $0) }.joined() 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /BLE-Viewer/Logging/Log.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logging.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 25.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import OSLog 11 | @_exported import os.log 12 | 13 | struct Log { 14 | static func log(log: OSLog, type: OSLogType, message: StaticString,_ a: [CVarArg]) { 15 | switch a.count { 16 | case 5: os_log(message, log: log, type: type, a[0], a[1], a[2], a[3], a[4]) 17 | case 4: os_log(message, log: log, type: type, a[0], a[1], a[2], a[3]) 18 | case 3: os_log(message, log: log, type: type, a[0], a[1], a[2]) 19 | case 2: os_log(message, log: log, type: type, a[0], a[1]) 20 | case 1: os_log(message, log: log, type: type, a[0]) 21 | case 0: os_log(message, log: log, type: type) 22 | default: os_log(message, log: log, type: type, a) 23 | } 24 | } 25 | 26 | static func info(system: LogSystem, message: StaticString,_ args: CVarArg...) { 27 | log(log: system.osLog, type: .info, message: message, args) 28 | } 29 | 30 | static func `default`(system: LogSystem, message: StaticString,_ args: CVarArg...) { 31 | log(log: system.osLog, type: .default, message: message, args) 32 | } 33 | 34 | static func debug(system: LogSystem, message: StaticString,_ args: CVarArg...) { 35 | log(log: system.osLog, type: .debug, message: message, args) 36 | } 37 | 38 | static func error(system: LogSystem, message: StaticString,_ args: CVarArg...) { 39 | log(log: system.osLog, type: .error, message: message, args) 40 | } 41 | 42 | struct LogSystem { 43 | let osLog: OSLog 44 | 45 | init(_ osLog: OSLog) { 46 | self.osLog = osLog 47 | } 48 | 49 | static let ble = LogSystem(OSLog(subsystem: "de.heinrich.alexander.BLE-Viewer", category: "BLE")) 50 | static let crypto = LogSystem(OSLog(subsystem: "de.heinrich.alexander.BLE-Viewer", category: "Crypto")) 51 | 52 | static let app = LogSystem(OSLog(subsystem: "de.heinrich.alexander.BLE-Viewer", category: "App")) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /BLE-Viewer/BLE/BLEReceiver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BLEReceiver.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 25.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreBluetooth 11 | import os 12 | 13 | protocol BLEReceiverDelegate { 14 | func didReceive(handoffData: Data, fromDevice device: CBPeripheral) 15 | } 16 | 17 | class BLEReceiver: NSObject { 18 | var centralManager: CBCentralManager! 19 | var delegate: BLEReceiverDelegate? 20 | 21 | private var shouldScanForAdvertisements = false 22 | 23 | override init() { 24 | super.init() 25 | self.centralManager = CBCentralManager(delegate: self, queue: nil) 26 | } 27 | 28 | func scanForAdvertisements() { 29 | if self.centralManager.state == .poweredOn { 30 | self.centralManager.scanForPeripherals(withServices: nil, options: nil) 31 | }else { 32 | self.shouldScanForAdvertisements = true 33 | } 34 | 35 | } 36 | 37 | func isHandoff(data: Data) -> Bool { 38 | guard data.count >= 3 else {return false} 39 | let isAppleData = data[data.startIndex] == 0x4c 40 | let isHandoffData = data[data.startIndex.advanced(by: 2)] == 0x0c 41 | 42 | return isAppleData && isHandoffData 43 | } 44 | } 45 | 46 | extension BLEReceiver: CBCentralManagerDelegate { 47 | func centralManagerDidUpdateState(_ central: CBCentralManager) { 48 | print("CBCentralManager did Update state \(central.state)") 49 | if central.state == .poweredOn && self.shouldScanForAdvertisements { 50 | self.scanForAdvertisements() 51 | self.shouldScanForAdvertisements = false 52 | } 53 | } 54 | 55 | func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { 56 | 57 | 58 | // Log.debug(system: .ble, message: "Received advertisement from %@", peripheral.identifier.uuidString) 59 | // Log.debug(system: .ble, message: "Advertisement data %@", advertisementData) 60 | guard let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data, 61 | self.isHandoff(data: manufacturerData) else { return } 62 | 63 | self.delegate?.didReceive(handoffData: manufacturerData, fromDevice: peripheral) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /BLE-Viewer/Crypto/BLEDecryptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BLEDecryptor.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 25.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CryptoSwift 11 | 12 | struct BLEDecryptor { 13 | var keys: [HandoffKey] 14 | 15 | init() throws { 16 | self.keys = try KeychainAccess.fetchHandoffKeys() 17 | } 18 | 19 | func decrypt(handoffBLE: HandoffBLE) throws -> Data { 20 | keys.forEach({ (key) in 21 | 22 | }) 23 | 24 | for key in keys { 25 | do { 26 | let decrypted = try decrypt(handoffBLE: handoffBLE, withKey: key) 27 | // Log.info(system: .crypto, 28 | // message: 29 | // """ 30 | // Successfully decrypted handoff message 31 | // Key used: %@ 32 | // Decrypted message: 33 | // %@ 34 | // """, 35 | // key.keyIdentifier.uuidString, decrypted.hexadecimal ) 36 | 37 | return decrypted 38 | 39 | }catch (_) { 40 | //Should fail quite often 41 | // Log.debug(system: .crypto, message: "Failed decrypting with key %@.\nError %@", key.keyIdentifier.uuidString, error.localizedDescription) 42 | } 43 | } 44 | 45 | throw DecryptionError.noKeyFound 46 | } 47 | 48 | func decrypt(handoffBLE: HandoffBLE, withKey key: HandoffKey) throws -> Data { 49 | // The status byte is included in the GCM algorithm as part of the authendicated data 50 | let statusByte: [UInt8] = [handoffBLE.statusByte] 51 | 52 | let gcm = GCM(iv: handoffBLE.counterIV, authenticationTag: handoffBLE.authTag, additionalAuthenticatedData: statusByte, mode: .detached) 53 | 54 | gcm.authenticationTag = handoffBLE.authTag 55 | 56 | let keyArray = Array(key.keyData) 57 | let aes = try AES(key: keyArray, blockMode: gcm, padding: .noPadding) 58 | 59 | let encryptedData = Array(handoffBLE.encryptedData) 60 | 61 | let decryptedArray = try aes.decrypt(encryptedData) 62 | return Data(decryptedArray) 63 | 64 | } 65 | 66 | enum DecryptionError: Error { 67 | case noKeyFound 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /BLE-Viewer/Crypto/KeychainAccess.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeychainAccess.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 24.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct KeychainAccess { 12 | 13 | static func fetchHandoffKeys() throws -> [HandoffKey] { 14 | let query: [CFString: Any] = [ 15 | kSecClass: kSecClassGenericPassword, 16 | kSecAttrService: "com.apple.continuity.encryption", 17 | kSecReturnAttributes: true, 18 | kSecMatchLimit: kSecMatchLimitAll 19 | ] 20 | 21 | var item: CFTypeRef? 22 | let status = SecItemCopyMatching(query as CFDictionary, &item) 23 | 24 | guard status == errSecSuccess else {throw KeychainError.secKeychain(err: NSError(domain: NSOSStatusErrorDomain, code: Int(status), userInfo: nil))} 25 | 26 | guard let itemArray = item as? [[CFString: Any]] else {throw KeychainError.keychainResponse} 27 | 28 | var responseArray = Array() 29 | 30 | for item in itemArray { 31 | let fetchedData = try fetchItem(withAttributes: item) 32 | responseArray.append(fetchedData) 33 | } 34 | 35 | //Fetch the NSDictionary 36 | let dictionaryResponses = try responseArray.compactMap({try PropertyListSerialization.propertyList(from: $0, options: [], format: nil) as? [String: Any]}) 37 | 38 | return try dictionaryResponses.map({try HandoffKey(withDictionary: $0)}) 39 | } 40 | 41 | static func fetchItem(withAttributes item: [CFString: Any]) throws -> Data { 42 | let itemQuery : [CFString: Any] = [ 43 | kSecClass: kSecClassGenericPassword, 44 | kSecAttrAccount : item[kSecAttrAccount]!, 45 | kSecAttrLabel : item[kSecAttrLabel]!, 46 | kSecAttrService: item[kSecAttrService]!, 47 | kSecReturnData : true 48 | ] 49 | 50 | var fetchedItem: CFTypeRef? 51 | let status = SecItemCopyMatching(itemQuery as CFDictionary, &fetchedItem) 52 | 53 | guard status == errSecSuccess, 54 | let fetchedData = fetchedItem as? Data else {throw KeychainError.secKeychain(err: NSError(domain: NSOSStatusErrorDomain, code: Int(status), userInfo: nil))} 55 | 56 | return fetchedData 57 | } 58 | } 59 | 60 | enum KeychainError: Error { 61 | case secKeychain(err: NSError) 62 | case keychainResponse 63 | case keyParsing 64 | } 65 | 66 | struct HandoffKey { 67 | let dateCreated: Date 68 | let isWrappedKey: Bool 69 | let keyData: Data 70 | let keyIdentifier: UUID 71 | let lastUsedCounter: Int 72 | 73 | init(withDictionary dict: [String: Any]) throws { 74 | guard let dC = dict["dateCreated"] as? Date, 75 | let wrapped = dict["isWrappedKey"] as? Bool, 76 | let kD = dict["keyData"] as? Data, 77 | let keyId = dict["keyIdentifier"] as? String, 78 | let keyUUID = UUID(uuidString: keyId), 79 | let counter = dict["lastUsedCounter"] as? Int else { 80 | throw KeychainError.keyParsing 81 | } 82 | 83 | dateCreated = dC 84 | isWrappedKey = wrapped 85 | keyData = kD 86 | keyIdentifier = keyUUID 87 | lastUsedCounter = counter 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /BLE-Viewer/HandoffModels/HandoffAdvertisement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HandoffAdvertisement.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 26.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | #if os(macOS) 11 | import AppKit 12 | #endif 13 | 14 | 15 | /// This struct is used for parsing decrypted Handoff BLE Advertisements. 16 | /// It will reflect the content of the advertisement 17 | struct HandoffAdvertisement { 18 | let statusByte: UInt8 19 | let unknownByte: UInt8 20 | let handoffHash: Data 21 | private let flagByte: UInt8 22 | let flags: [HandoffFlags] 23 | let handoffActivity: HandoffActivity 24 | 25 | let dateReceived = Date() 26 | 27 | init(withRawAdvertisement handoffBLE: HandoffBLE, decryptedAdvertisementData advertisementData: Data) { 28 | self.statusByte = advertisementData[advertisementData.startIndex] 29 | self.handoffHash = advertisementData[advertisementData.startIndex.advanced(by: 1) ... advertisementData.startIndex.advanced(by: 7)] 30 | self.flagByte = advertisementData[advertisementData.startIndex.advanced(by: 8)] 31 | self.unknownByte = advertisementData[advertisementData.startIndex.advanced(by: 9)] 32 | 33 | var flags = [HandoffFlags]() 34 | 35 | //Parse the flags 36 | if flagByte & 0x1 != 0 { 37 | flags.append(.url) 38 | } 39 | 40 | if flagByte & 0x2 != 0 { 41 | flags.append(.fileProviderUrl) 42 | } 43 | 44 | if flagByte & 0x4 != 0 { 45 | flags.append(.cloudDocs) 46 | } 47 | 48 | if flagByte & 0x8 != 0 { 49 | flags.append(.pasteboardAvailable) 50 | } 51 | 52 | if flagByte & 0x10 != 0 { 53 | flags.append(.pasteboardVersionBit(versionBit: flagByte & 0x10)) 54 | } 55 | 56 | if flagByte & 0x20 != 0 { 57 | flags.append(.activityAutoPullOnReceiver) 58 | } 59 | 60 | self.flags = flags 61 | 62 | //Match activity 63 | if flags.contains(.url) { 64 | handoffActivity = .viewWebpage 65 | }else { 66 | handoffActivity = HandoffActivity(withHash: handoffHash) 67 | } 68 | 69 | } 70 | 71 | func description() -> String { 72 | return( 73 | """ 74 | Handoff Advertisement: 75 | Actvity: \(self.handoffActivity.activityName()) 76 | App: \(self.handoffActivity.appBundleId()) 77 | """ 78 | ) 79 | } 80 | 81 | #if os(macOS) 82 | func attributedDescription() -> NSAttributedString { 83 | let description = NSMutableAttributedString() 84 | description.append(NSAttributedString(string: "Activity:\t\t\t\t")) 85 | description.append(NSAttributedString(string: handoffActivity.activityName(), attributes: [NSAttributedString.Key.font : NSFont.boldSystemFont(ofSize: 13.0)])) 86 | description.append(NSAttributedString(string: "\n")) 87 | 88 | description.append(NSAttributedString(string: "App:\t\t\t\t")) 89 | description.append(NSAttributedString(string: handoffActivity.appBundleId(), attributes: [NSAttributedString.Key.font : NSFont.boldSystemFont(ofSize: 13.0)])) 90 | description.append(NSAttributedString(string: "\n")) 91 | 92 | description.append(NSAttributedString(string: "Contains URL:\t\t")) 93 | description.append(NSAttributedString(string: flags.contains(.url) ? "true" : "false", attributes: [NSAttributedString.Key.font : NSFont.boldSystemFont(ofSize: 13.0)])) 94 | description.append(NSAttributedString(string: "\n")) 95 | 96 | description.append(NSAttributedString(string: "Clipboard Available:\t")) 97 | description.append(NSAttributedString(string: flags.contains(.pasteboardAvailable) ? "true" : "false", attributes: [NSAttributedString.Key.font : NSFont.boldSystemFont(ofSize: 13.0)])) 98 | 99 | return description 100 | } 101 | #endif 102 | } 103 | 104 | enum HandoffFlags: Equatable { 105 | case url 106 | case fileProviderUrl 107 | case cloudDocs 108 | case pasteboardAvailable 109 | case pasteboardVersionBit(versionBit: UInt8) 110 | case activityAutoPullOnReceiver 111 | 112 | } 113 | -------------------------------------------------------------------------------- /BLE-Viewer/HandoffModels/HandoffActivity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HandoffActivity.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 26.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum HandoffActivity: CaseIterable { 12 | case notesEditNote 13 | case mailViewMailbox 14 | case mailViewMessage 15 | case mailComposeMessage 16 | case applePodcast 17 | case keynoteEditPresentation 18 | case pagesEditDocument 19 | case numbersEditDocument 20 | case twodoSelecedList 21 | case twodoEditTask 22 | case viewWebpage 23 | 24 | case calendarDateSelection 25 | case calendarEventSelection 26 | 27 | case messages 28 | 29 | case home 30 | 31 | case unknown 32 | 33 | init(withHash hash: Data) { 34 | for activity in HandoffActivity.allCases { 35 | if activity.hash() == hash { 36 | self = activity 37 | return 38 | } 39 | } 40 | self = .unknown 41 | } 42 | 43 | func hash() -> Data { 44 | switch self { 45 | case .notesEditNote: 46 | return "88085c342dc9ed".hexadecimal! 47 | case .mailViewMailbox: 48 | return "9d98584545c05e".hexadecimal! 49 | case .mailViewMessage: 50 | return "86f6a0732b418e".hexadecimal! 51 | case .mailComposeMessage: 52 | return "a564c399758208".hexadecimal! 53 | case .applePodcast: 54 | return "a37b6b65fc4f5f".hexadecimal! 55 | case .keynoteEditPresentation: 56 | return "90ec0d1c7a00f7".hexadecimal! 57 | case .pagesEditDocument: 58 | return "a2f08c1c87dc98".hexadecimal! 59 | case .numbersEditDocument: 60 | return "855912d27f7828".hexadecimal! 61 | case .twodoSelecedList: 62 | return "99bee8c360dd13".hexadecimal! 63 | case .twodoEditTask: 64 | return "b2a2abdd925ef5".hexadecimal! 65 | case .viewWebpage: 66 | return "58c3379233c70b".hexadecimal! 67 | case .calendarDateSelection: 68 | return "b64729d1a4296b".hexadecimal! 69 | case .calendarEventSelection: 70 | return "88acfe99cad770".hexadecimal! 71 | case .messages: 72 | return "a32a9f48308fdc".hexadecimal! 73 | case .home: 74 | return "00000000000000".hexadecimal! 75 | case .unknown: 76 | return Data() 77 | } 78 | } 79 | 80 | func activityName() -> String { 81 | switch self { 82 | case .notesEditNote: 83 | return "com.apple.notes.activity.edit-note" 84 | case .mailViewMailbox: 85 | return "com.apple.mail.mailbox" 86 | case .mailViewMessage: 87 | return "com.apple.mail.message" 88 | case .mailComposeMessage: 89 | return "com.apple.mail.compose" 90 | case .applePodcast: 91 | //TODO: Check in Catalina with Podcasts app 92 | return "unknown" 93 | case .keynoteEditPresentation: 94 | return "com.apple.keynote.documentEditing" 95 | case .pagesEditDocument: 96 | return "com.apple.pages.documentEditing" 97 | case .numbersEditDocument: 98 | return "com.apple.numbers.documentEditing" 99 | case .twodoSelecedList: 100 | return "com.guidedways.2Do.SelectedList" 101 | case .twodoEditTask: 102 | return "com.guidedways.2Do.TaskEditing" 103 | case .viewWebpage: 104 | return "NSUserActivityTypeBrowsingWeb" 105 | case .calendarDateSelection: 106 | return "com.apple.calendar.continuity.date_selection" 107 | case .calendarEventSelection: 108 | return "com.apple.calendar.continuity.event_selection" 109 | case .messages: 110 | return "com.apple.Messages" 111 | case .home: 112 | return "Clear last Activity state" 113 | case .unknown: 114 | return "unkown" 115 | } 116 | } 117 | 118 | func appBundleId() -> String { 119 | switch self { 120 | case .notesEditNote: 121 | return "com.apple.Notes" 122 | case .mailViewMailbox, .mailViewMessage, .mailComposeMessage: 123 | return "com.apple.mail" 124 | case .applePodcast: 125 | return "com.apple.podcasts" 126 | case .keynoteEditPresentation: 127 | return "com.apple.iWork.Keynote" 128 | case .pagesEditDocument: 129 | return "com.apple.iWork.Pages" 130 | case .numbersEditDocument: 131 | return "com.apple.iWork.Numbers" 132 | case .twodoSelecedList, .twodoEditTask: 133 | return "com.guidedways.TodoMac" 134 | case .viewWebpage: 135 | return "com.apple.Safari" 136 | case .calendarDateSelection, .calendarEventSelection: 137 | return "com.apple.iCal" 138 | case .messages: 139 | return "com.apple.iChat" 140 | case .home: 141 | return "No app" 142 | case .unknown: 143 | return "Unkown" 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /BLE-Viewer/BLEScannerVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BLEScannerVC.swift 3 | // BLE-Viewer 4 | // 5 | // Created by Alexander Heinrich on 25.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import CoreBluetooth 11 | 12 | class BLEScannerVC: NSViewController { 13 | var decryptor: BLEDecryptor! 14 | var receiver: BLEReceiver! 15 | 16 | var scannedDevices = [ScanModel]() 17 | var selectedDevice: ScanModel? { 18 | didSet { 19 | self.contentTableView.reloadData() 20 | } 21 | } 22 | 23 | @IBOutlet weak var startButton: NSButton! 24 | @IBOutlet weak var scanOverlay: NSView! 25 | @IBOutlet weak var sidebar: NSScrollView! 26 | @IBOutlet weak var outlineView: NSOutlineView! 27 | @IBOutlet weak var contentTableView: NSTableView! 28 | 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | // Do view setup here. 33 | 34 | outlineView.delegate = self 35 | outlineView.dataSource = self 36 | 37 | contentTableView.delegate = self 38 | contentTableView.dataSource = self 39 | 40 | scanOverlay.isHidden = false 41 | } 42 | @IBAction func startScanning(_ sender: Any) { 43 | do { 44 | self.decryptor = try BLEDecryptor() 45 | self.receiver = BLEReceiver() 46 | 47 | self.receiver.delegate = self 48 | self.receiver.scanForAdvertisements() 49 | 50 | //Dismiss the scan overlay 51 | self.scanOverlay.isHidden = true 52 | 53 | }catch (let error) { 54 | Log.error(system: .app, message: "Error thrown during setup %@", error.localizedDescription) 55 | } 56 | 57 | } 58 | 59 | func addAdvertisement(_ advertisement: HandoffAdvertisement, fromDevice peripheral: CBPeripheral) { 60 | if var scanModel = self.scannedDevices.first(where: {$0.peripheral.identifier == peripheral.identifier}) { 61 | 62 | scanModel.advertisements.append(advertisement) 63 | guard scanModel.peripheral.identifier == self.selectedDevice?.peripheral.identifier else {return} 64 | 65 | contentTableView.insertRows(at: IndexSet(integer: scanModel.advertisements.count-1), withAnimation: .effectFade) 66 | 67 | }else { 68 | let scanModel = ScanModel(peripheral: peripheral, advertisements: [advertisement]) 69 | self.scannedDevices.append(scanModel) 70 | outlineView.insertItems(at: IndexSet(integer: scannedDevices.count-1), inParent: nil, withAnimation: .slideLeft) 71 | } 72 | 73 | } 74 | 75 | } 76 | 77 | extension BLEScannerVC: BLEReceiverDelegate { 78 | func didReceive(handoffData: Data, fromDevice device: CBPeripheral) { 79 | 80 | do { 81 | //Decrypt the advertisements 82 | let handoff = try HandoffBLE(handoffData: handoffData) 83 | let handoffAdvData = try self.decryptor.decrypt(handoffBLE: handoff) 84 | 85 | let handoffAdv = HandoffAdvertisement(withRawAdvertisement: handoff, decryptedAdvertisementData: handoffAdvData) 86 | 87 | Log.info(system: .app, message: "\nRaw status byte %02x \nDecrypted status byte %02x", handoff.statusByte, handoffAdv.statusByte) 88 | Log.info(system: .app, message: "\nUnknown byte %02x", handoffAdv.unknownByte) 89 | 90 | Log.info(system: .app, message: "\nReceived advertisement from device: %@", device.name ?? device.identifier.uuidString) 91 | Log.info(system: .app, message: "%@", handoffAdv.description()) 92 | 93 | self.addAdvertisement(handoffAdv, fromDevice: device) 94 | 95 | 96 | }catch (let error) { 97 | // Log.error(system: .app, message: "Failed parsing Handoff advertisement: \n%@", error.localizedDescription) 98 | } 99 | 100 | } 101 | } 102 | 103 | extension BLEScannerVC: NSOutlineViewDataSource { 104 | func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { 105 | return scannedDevices[index] 106 | } 107 | 108 | func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { 109 | return false 110 | } 111 | 112 | func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { 113 | guard item == nil else {return 0} 114 | 115 | return scannedDevices.count 116 | } 117 | 118 | // func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? { 119 | // 120 | // } 121 | } 122 | 123 | extension BLEScannerVC: NSOutlineViewDelegate { 124 | func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { 125 | var view: NSTableCellView? 126 | 127 | guard let scan = item as? ScanModel else {return view} 128 | 129 | view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DeviceCell"), owner: self) as? NSTableCellView 130 | 131 | view?.textField?.stringValue = scan.peripheral.name ?? scan.peripheral.identifier.uuidString 132 | view?.textField?.sizeToFit() 133 | 134 | return view 135 | } 136 | 137 | func outlineViewSelectionDidChange(_ notification: Notification) { 138 | guard let outlineView = notification.object as? NSOutlineView else { 139 | return 140 | } 141 | 142 | let selectedIndex = outlineView.selectedRow 143 | 144 | guard let scannedDevice = outlineView.item(atRow: selectedIndex) as? ScanModel else {return} 145 | 146 | self.selectedDevice = scannedDevice 147 | 148 | } 149 | } 150 | 151 | extension BLEScannerVC: NSTableViewDataSource { 152 | func numberOfRows(in tableView: NSTableView) -> Int { 153 | return self.selectedDevice?.advertisements.count ?? 0 154 | } 155 | 156 | // func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { 157 | // return self.selectedDevice?.advertisements[row] ?? nil 158 | // } 159 | 160 | } 161 | 162 | extension BLEScannerVC: NSTableViewDelegate { 163 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 164 | let adv = selectedDevice!.advertisements[row] 165 | 166 | if tableColumn?.identifier.rawValue == "DateColumn" { 167 | let view = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DateCell"), owner: self) as? NSTableCellView 168 | 169 | let df = DateFormatter() 170 | df.dateFormat = "HH:mm:ss dd.MM.yy" 171 | 172 | view?.textField?.stringValue = df.string(from: adv.dateReceived) 173 | view?.textField?.sizeToFit() 174 | return view 175 | }else { 176 | let view = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "ActivityCell"), owner: self) as? NSTableCellView 177 | 178 | view?.textField?.attributedStringValue = adv.attributedDescription() 179 | 180 | return view 181 | } 182 | } 183 | 184 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 185 | return 68 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /BLE-ViewerTests/BLE_ViewerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BLE_ViewerTests.swift 3 | // BLE-ViewerTests 4 | // 5 | // Created by Alexander Heinrich on 24.09.19. 6 | // Copyright © 2019 Alexander Heinrich. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import BLE_Viewer 11 | import CryptoSwift 12 | import CoreBluetooth 13 | 14 | class BLE_ViewerTests: XCTestCase { 15 | var bleExpect: XCTestExpectation? 16 | 17 | override func setUp() { 18 | // Put setup code here. This method is called before the invocation of each test method in the class. 19 | } 20 | 21 | override func tearDown() { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testLoadingKeys() throws { 26 | do { 27 | let handoffKeys = try KeychainAccess.fetchHandoffKeys() 28 | 29 | XCTAssertGreaterThanOrEqual(handoffKeys.count, 1) 30 | 31 | var keyMatch = false 32 | handoffKeys.forEach { (key) in 33 | if key.keyData == "9cdbb22f 8dfc0198 bbf9b31e 7d64438e 1755094b ad224d32 d897c634 e71ffb9c".hexadecimal! { 34 | keyMatch = true 35 | } 36 | print(key.keyData.hexEncodedString()) 37 | } 38 | 39 | XCTAssertTrue(keyMatch) 40 | 41 | }catch (let err) { 42 | print(err) 43 | XCTFail("Error thrown") 44 | } 45 | 46 | } 47 | 48 | func testBLE() throws { 49 | let receiver = BLEReceiver() 50 | receiver.scanForAdvertisements() 51 | receiver.delegate = self 52 | 53 | let expect = self.expectation(description: "Advertisements") 54 | self.bleExpect = expect 55 | 56 | self.wait(for: [expect], timeout: 10.0) 57 | } 58 | 59 | func testIsHandoff() { 60 | let handoffData = "<4c000c0e 0057cd3b 3cbd48f2 9e6bf563 f0921006 5b1e1d49 e249>".hexadecimal! 61 | let receiver = BLEReceiver() 62 | 63 | XCTAssert(receiver.isHandoff(data: handoffData), "Failed") 64 | 65 | } 66 | 67 | func testParsing() throws { 68 | let handoffData = "<4c000c0e 0057cd3b 3cbd48f2 9e6bf563 f0921006 5b1e1d49 e249>".hexadecimal! 69 | let handoffBLE = try HandoffBLE(handoffData: handoffData) 70 | 71 | XCTAssertEqual(handoffBLE.counter, UInt16(52567)) 72 | XCTAssertEqual(handoffBLE.authTag, [0x3b]) 73 | XCTAssertEqual(Array(handoffBLE.encryptedData), Array("3cbd48f2 9e6bf563 f092".hexadecimal!)) 74 | } 75 | 76 | func testDecryption() throws { 77 | do { 78 | let handoffData = " <4c000c0e 00a9cd77 c546bb00 79902fbf 09371006 5b1ec335 0d71>".hexadecimal! 79 | let decryptor = try BLEDecryptor() 80 | let handoffBLE = try HandoffBLE(handoffData: handoffData) 81 | XCTAssertEqual(Array(handoffBLE.encryptedData), Array("".hexadecimal!)) 82 | 83 | let decrypted = try decryptor.decrypt(handoffBLE: handoffBLE) 84 | 85 | XCTAssertNotNil(decrypted) 86 | XCTAssertEqual(decrypted, "<0088085c342dc9ed408e>".hexadecimal!) 87 | 88 | let u = NSUserActivity(activityType: "de.macoun.handoff") 89 | u.isEligibleForHandoff = true 90 | 91 | }catch(let error) { 92 | XCTFail(error.localizedDescription) 93 | } 94 | 95 | 96 | } 97 | 98 | 99 | func testDecryption2() { 100 | let encryptedData = Array("3d ce a5 d2 17 21 0f ec 20 3b".hexadecimal!) 101 | let counter: [UInt8] = [0xf2, 0xcd] 102 | let tag: [UInt8] = [0xfb] 103 | let version: [UInt8] = [0x0] 104 | let key = Array("9cdbb22f 8dfc0198 bbf9b31e 7d64438e 1755094b ad224d32 d897c634 e71ffb9c".hexadecimal!) 105 | 106 | let gcm = GCM(iv: counter, authenticationTag: tag, additionalAuthenticatedData: version, mode: .detached) 107 | 108 | do { 109 | let aes = try AES(key: key, blockMode: gcm, padding: .noPadding) 110 | let decrypted = try aes.decrypt(encryptedData) 111 | 112 | XCTAssertNotNil(decrypted) 113 | }catch { 114 | XCTFail() 115 | } 116 | 117 | 118 | } 119 | 120 | func testHandoffAdvertisementParsing() throws { 121 | let decryptedAdv = "0088085c342dc9ed198a".hexadecimal! 122 | let rawAdvertisement = "<4c000c0e 0057cd3b 3cbd48f2 9e6bf563 f0921006 5b1e1d49 e249>".hexadecimal! 123 | let handoffBLE = try HandoffBLE(handoffData: rawAdvertisement) 124 | let parsedAdv = HandoffAdvertisement(withRawAdvertisement: handoffBLE, decryptedAdvertisementData: decryptedAdv) 125 | 126 | XCTAssertEqual(parsedAdv.statusByte, 0x00) 127 | XCTAssertEqual(Array(parsedAdv.handoffHash), Array("88085c342dc9ed".hexadecimal!)) 128 | XCTAssertEqual(parsedAdv.flags, [.url, .pasteboardAvailable ,.pasteboardVersionBit(versionBit: 0x10)]) 129 | XCTAssertEqual(parsedAdv.handoffActivity, HandoffActivity.notesEditNote) 130 | } 131 | 132 | func testBleRandomNumbers() { 133 | var randomTable = Array(repeating: false, count: 0xffff + 1) 134 | for i in 0...0xffff { 135 | let random = arc4random_uniform(0xffff) 136 | XCTAssertFalse(randomTable[Int(random)], "Found doubled value") 137 | if randomTable[Int(random)] == true { 138 | print("Failed at \(random)") 139 | break 140 | } 141 | randomTable[Int(random)] = true 142 | } 143 | } 144 | 145 | var xs: UInt16 = 1 146 | func xorShift() -> UInt16 { 147 | xs ^= xs << 7; 148 | xs ^= xs >> 9; 149 | xs ^= xs << 8; 150 | return xs; 151 | } 152 | 153 | func testXorShift() { 154 | var randomTable = Array(repeating: false, count: 0xffff + 1) 155 | for i in 1...0xffff { 156 | let random = xorShift() 157 | if randomTable[Int(random)] == true { 158 | print("Failed at \(i) with \(random)") 159 | XCTFail("Found doubled value") 160 | break 161 | } 162 | randomTable[Int(random)] = true 163 | } 164 | } 165 | 166 | 167 | func xorShiftMul(x: UInt16, key: UInt16) -> UInt16 { 168 | var y = x 169 | y ^= y >> 7 170 | y ^= y << 9 171 | y ^= y << 8 172 | 173 | let z: UInt64 = UInt64(y) * UInt64(key) 174 | 175 | y = UInt16(z % 0x10000) 176 | 177 | return y 178 | } 179 | 180 | func testXorShiftMul() { 181 | var randomTable = Array(repeating: false, count: 0xffff + 1) 182 | var x: UInt16 = 1 183 | let key: UInt16 = 0x2362 184 | 185 | for i in 1...0xffff { 186 | let random = xorShiftMul(x: x, key: key) 187 | if randomTable[Int(random)] == true { 188 | print("Failed at \(i) with \(random)") 189 | XCTFail("Found doubled value") 190 | break 191 | } 192 | randomTable[Int(random)] = true 193 | x = random 194 | } 195 | } 196 | 197 | 198 | } 199 | 200 | extension BLE_ViewerTests: BLEReceiverDelegate { 201 | func didReceive(handoffData: Data, fromDevice device: CBPeripheral) { 202 | self.bleExpect?.fulfill() 203 | } 204 | 205 | func didReceive(handoffData: Data) { 206 | self.bleExpect?.fulfill() 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /BLE-Viewer.xcodeproj/xcuserdata/Alex.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 21 | 22 | 23 | 25 | 37 | 38 | 39 | 41 | 53 | 54 | 55 | 57 | 69 | 70 | 84 | 85 | 99 | 100 | 101 | 102 | 103 | 105 | 117 | 118 | 119 | 121 | 133 | 134 | 135 | 137 | 149 | 150 | 151 | 153 | 165 | 166 | 167 | 169 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /BLE-Viewer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FA058A0E2366DB2D002DB46D /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = FA058A0D2366DB2D002DB46D /* README.md */; }; 11 | FA058A112366DE02002DB46D /* Screenshot.png in Resources */ = {isa = PBXBuildFile; fileRef = FA058A102366DE02002DB46D /* Screenshot.png */; }; 12 | FA4D9E3F233CA654003E5D8F /* HandoffAdvertisement.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4D9E3E233CA654003E5D8F /* HandoffAdvertisement.swift */; }; 13 | FA4D9E41233CBF0A003E5D8F /* HandoffActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4D9E40233CBF0A003E5D8F /* HandoffActivity.swift */; }; 14 | FA4D9E43233CDF36003E5D8F /* ScanModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4D9E42233CDF36003E5D8F /* ScanModel.swift */; }; 15 | FA83551D233A756F00E4F46B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA83551C233A756F00E4F46B /* AppDelegate.swift */; }; 16 | FA83551F233A756F00E4F46B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA83551E233A756F00E4F46B /* ContentView.swift */; }; 17 | FA835521233A757100E4F46B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA835520233A757100E4F46B /* Assets.xcassets */; }; 18 | FA835524233A757100E4F46B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FA835523233A757100E4F46B /* Preview Assets.xcassets */; }; 19 | FA835527233A757100E4F46B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA835525233A757100E4F46B /* Main.storyboard */; }; 20 | FA835533233A757100E4F46B /* BLE_ViewerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA835532233A757100E4F46B /* BLE_ViewerTests.swift */; }; 21 | FA83553F233A75C700E4F46B /* KeychainAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA83553E233A75C700E4F46B /* KeychainAccess.swift */; }; 22 | FA835541233B5F4900E4F46B /* BLEDecryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA835540233B5F4900E4F46B /* BLEDecryptor.swift */; }; 23 | FA835544233B5F7400E4F46B /* BLEReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA835543233B5F7400E4F46B /* BLEReceiver.swift */; }; 24 | FA835547233B62C500E4F46B /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA835546233B62C500E4F46B /* Log.swift */; }; 25 | FA835549233B690800E4F46B /* HexToDataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA835548233B690800E4F46B /* HexToDataExtensions.swift */; }; 26 | FA83554B233B6FAA00E4F46B /* HandoffBLE.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA83554A233B6FAA00E4F46B /* HandoffBLE.swift */; }; 27 | FA83554F233B718600E4F46B /* DataIntExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA83554E233B718600E4F46B /* DataIntExtensions.swift */; }; 28 | FA835552233B82C800E4F46B /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FA835551233B82C800E4F46B /* CryptoSwift */; }; 29 | FA835554233B86D900E4F46B /* BLEScannerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA835553233B86D900E4F46B /* BLEScannerVC.swift */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | FA83552F233A757100E4F46B /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = FA835511233A756F00E4F46B /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = FA835518233A756F00E4F46B; 38 | remoteInfo = "BLE-Viewer"; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | FA058A0D2366DB2D002DB46D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 44 | FA058A102366DE02002DB46D /* Screenshot.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Screenshot.png; sourceTree = ""; }; 45 | FA4D9E3E233CA654003E5D8F /* HandoffAdvertisement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandoffAdvertisement.swift; sourceTree = ""; }; 46 | FA4D9E40233CBF0A003E5D8F /* HandoffActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandoffActivity.swift; sourceTree = ""; }; 47 | FA4D9E42233CDF36003E5D8F /* ScanModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanModel.swift; sourceTree = ""; }; 48 | FA835519233A756F00E4F46B /* BLE-Viewer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "BLE-Viewer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | FA83551C233A756F00E4F46B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | FA83551E233A756F00E4F46B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 51 | FA835520233A757100E4F46B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | FA835523233A757100E4F46B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 53 | FA835526233A757100E4F46B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | FA835528233A757100E4F46B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 55 | FA835529233A757100E4F46B /* BLE_Viewer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BLE_Viewer.entitlements; sourceTree = ""; }; 56 | FA83552E233A757100E4F46B /* BLE-ViewerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "BLE-ViewerTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | FA835532233A757100E4F46B /* BLE_ViewerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLE_ViewerTests.swift; sourceTree = ""; }; 58 | FA835534233A757100E4F46B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | FA83553E233A75C700E4F46B /* KeychainAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainAccess.swift; sourceTree = ""; }; 60 | FA835540233B5F4900E4F46B /* BLEDecryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEDecryptor.swift; sourceTree = ""; }; 61 | FA835543233B5F7400E4F46B /* BLEReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEReceiver.swift; sourceTree = ""; }; 62 | FA835546233B62C500E4F46B /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; 63 | FA835548233B690800E4F46B /* HexToDataExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HexToDataExtensions.swift; sourceTree = ""; }; 64 | FA83554A233B6FAA00E4F46B /* HandoffBLE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HandoffBLE.swift; sourceTree = ""; }; 65 | FA83554E233B718600E4F46B /* DataIntExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataIntExtensions.swift; sourceTree = ""; }; 66 | FA835553233B86D900E4F46B /* BLEScannerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEScannerVC.swift; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | FA835516233A756F00E4F46B /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | FA835552233B82C800E4F46B /* CryptoSwift in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | FA83552B233A757100E4F46B /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | FA058A0F2366DDEB002DB46D /* Resources */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | FA058A102366DE02002DB46D /* Screenshot.png */, 92 | ); 93 | path = Resources; 94 | sourceTree = ""; 95 | }; 96 | FA4D9E3D233CA61A003E5D8F /* HandoffModels */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | FA4D9E42233CDF36003E5D8F /* ScanModel.swift */, 100 | FA83554A233B6FAA00E4F46B /* HandoffBLE.swift */, 101 | FA4D9E3E233CA654003E5D8F /* HandoffAdvertisement.swift */, 102 | FA4D9E40233CBF0A003E5D8F /* HandoffActivity.swift */, 103 | ); 104 | path = HandoffModels; 105 | sourceTree = ""; 106 | }; 107 | FA835510233A756F00E4F46B = { 108 | isa = PBXGroup; 109 | children = ( 110 | FA058A0D2366DB2D002DB46D /* README.md */, 111 | FA83551B233A756F00E4F46B /* BLE-Viewer */, 112 | FA835531233A757100E4F46B /* BLE-ViewerTests */, 113 | FA058A0F2366DDEB002DB46D /* Resources */, 114 | FA83551A233A756F00E4F46B /* Products */, 115 | ); 116 | sourceTree = ""; 117 | }; 118 | FA83551A233A756F00E4F46B /* Products */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | FA835519233A756F00E4F46B /* BLE-Viewer.app */, 122 | FA83552E233A757100E4F46B /* BLE-ViewerTests.xctest */, 123 | ); 124 | name = Products; 125 | sourceTree = ""; 126 | }; 127 | FA83551B233A756F00E4F46B /* BLE-Viewer */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | FA83551C233A756F00E4F46B /* AppDelegate.swift */, 131 | FA83551E233A756F00E4F46B /* ContentView.swift */, 132 | FA835553233B86D900E4F46B /* BLEScannerVC.swift */, 133 | FA835520233A757100E4F46B /* Assets.xcassets */, 134 | FA835525233A757100E4F46B /* Main.storyboard */, 135 | FA835528233A757100E4F46B /* Info.plist */, 136 | FA835529233A757100E4F46B /* BLE_Viewer.entitlements */, 137 | FA4D9E3D233CA61A003E5D8F /* HandoffModels */, 138 | FA83554D233B717700E4F46B /* Extensions */, 139 | FA835545233B62B700E4F46B /* Logging */, 140 | FA835542233B5F6200E4F46B /* BLE */, 141 | FA83553D233A757F00E4F46B /* Crypto */, 142 | FA835522233A757100E4F46B /* Preview Content */, 143 | ); 144 | path = "BLE-Viewer"; 145 | sourceTree = ""; 146 | }; 147 | FA835522233A757100E4F46B /* Preview Content */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | FA835523233A757100E4F46B /* Preview Assets.xcassets */, 151 | ); 152 | path = "Preview Content"; 153 | sourceTree = ""; 154 | }; 155 | FA835531233A757100E4F46B /* BLE-ViewerTests */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | FA835532233A757100E4F46B /* BLE_ViewerTests.swift */, 159 | FA835534233A757100E4F46B /* Info.plist */, 160 | ); 161 | path = "BLE-ViewerTests"; 162 | sourceTree = ""; 163 | }; 164 | FA83553D233A757F00E4F46B /* Crypto */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | FA83553E233A75C700E4F46B /* KeychainAccess.swift */, 168 | FA835540233B5F4900E4F46B /* BLEDecryptor.swift */, 169 | ); 170 | path = Crypto; 171 | sourceTree = ""; 172 | }; 173 | FA835542233B5F6200E4F46B /* BLE */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | FA835543233B5F7400E4F46B /* BLEReceiver.swift */, 177 | ); 178 | path = BLE; 179 | sourceTree = ""; 180 | }; 181 | FA835545233B62B700E4F46B /* Logging */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | FA835546233B62C500E4F46B /* Log.swift */, 185 | ); 186 | path = Logging; 187 | sourceTree = ""; 188 | }; 189 | FA83554D233B717700E4F46B /* Extensions */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | FA835548233B690800E4F46B /* HexToDataExtensions.swift */, 193 | FA83554E233B718600E4F46B /* DataIntExtensions.swift */, 194 | ); 195 | path = Extensions; 196 | sourceTree = ""; 197 | }; 198 | /* End PBXGroup section */ 199 | 200 | /* Begin PBXNativeTarget section */ 201 | FA835518233A756F00E4F46B /* BLE-Viewer */ = { 202 | isa = PBXNativeTarget; 203 | buildConfigurationList = FA835537233A757100E4F46B /* Build configuration list for PBXNativeTarget "BLE-Viewer" */; 204 | buildPhases = ( 205 | FA835515233A756F00E4F46B /* Sources */, 206 | FA835516233A756F00E4F46B /* Frameworks */, 207 | FA835517233A756F00E4F46B /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | ); 213 | name = "BLE-Viewer"; 214 | packageProductDependencies = ( 215 | FA835551233B82C800E4F46B /* CryptoSwift */, 216 | ); 217 | productName = "BLE-Viewer"; 218 | productReference = FA835519233A756F00E4F46B /* BLE-Viewer.app */; 219 | productType = "com.apple.product-type.application"; 220 | }; 221 | FA83552D233A757100E4F46B /* BLE-ViewerTests */ = { 222 | isa = PBXNativeTarget; 223 | buildConfigurationList = FA83553A233A757100E4F46B /* Build configuration list for PBXNativeTarget "BLE-ViewerTests" */; 224 | buildPhases = ( 225 | FA83552A233A757100E4F46B /* Sources */, 226 | FA83552B233A757100E4F46B /* Frameworks */, 227 | FA83552C233A757100E4F46B /* Resources */, 228 | ); 229 | buildRules = ( 230 | ); 231 | dependencies = ( 232 | FA835530233A757100E4F46B /* PBXTargetDependency */, 233 | ); 234 | name = "BLE-ViewerTests"; 235 | productName = "BLE-ViewerTests"; 236 | productReference = FA83552E233A757100E4F46B /* BLE-ViewerTests.xctest */; 237 | productType = "com.apple.product-type.bundle.unit-test"; 238 | }; 239 | /* End PBXNativeTarget section */ 240 | 241 | /* Begin PBXProject section */ 242 | FA835511233A756F00E4F46B /* Project object */ = { 243 | isa = PBXProject; 244 | attributes = { 245 | LastSwiftUpdateCheck = 1100; 246 | LastUpgradeCheck = 1100; 247 | ORGANIZATIONNAME = "Alexander Heinrich"; 248 | TargetAttributes = { 249 | FA835518233A756F00E4F46B = { 250 | CreatedOnToolsVersion = 11.0; 251 | }; 252 | FA83552D233A757100E4F46B = { 253 | CreatedOnToolsVersion = 11.0; 254 | TestTargetID = FA835518233A756F00E4F46B; 255 | }; 256 | }; 257 | }; 258 | buildConfigurationList = FA835514233A756F00E4F46B /* Build configuration list for PBXProject "BLE-Viewer" */; 259 | compatibilityVersion = "Xcode 9.3"; 260 | developmentRegion = en; 261 | hasScannedForEncodings = 0; 262 | knownRegions = ( 263 | en, 264 | Base, 265 | ); 266 | mainGroup = FA835510233A756F00E4F46B; 267 | packageReferences = ( 268 | FA835550233B82C800E4F46B /* XCRemoteSwiftPackageReference "CryptoSwift" */, 269 | ); 270 | productRefGroup = FA83551A233A756F00E4F46B /* Products */; 271 | projectDirPath = ""; 272 | projectRoot = ""; 273 | targets = ( 274 | FA835518233A756F00E4F46B /* BLE-Viewer */, 275 | FA83552D233A757100E4F46B /* BLE-ViewerTests */, 276 | ); 277 | }; 278 | /* End PBXProject section */ 279 | 280 | /* Begin PBXResourcesBuildPhase section */ 281 | FA835517233A756F00E4F46B /* Resources */ = { 282 | isa = PBXResourcesBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | FA835527233A757100E4F46B /* Main.storyboard in Resources */, 286 | FA058A112366DE02002DB46D /* Screenshot.png in Resources */, 287 | FA835524233A757100E4F46B /* Preview Assets.xcassets in Resources */, 288 | FA058A0E2366DB2D002DB46D /* README.md in Resources */, 289 | FA835521233A757100E4F46B /* Assets.xcassets in Resources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | FA83552C233A757100E4F46B /* Resources */ = { 294 | isa = PBXResourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | /* End PBXResourcesBuildPhase section */ 301 | 302 | /* Begin PBXSourcesBuildPhase section */ 303 | FA835515233A756F00E4F46B /* Sources */ = { 304 | isa = PBXSourcesBuildPhase; 305 | buildActionMask = 2147483647; 306 | files = ( 307 | FA835544233B5F7400E4F46B /* BLEReceiver.swift in Sources */, 308 | FA835554233B86D900E4F46B /* BLEScannerVC.swift in Sources */, 309 | FA4D9E43233CDF36003E5D8F /* ScanModel.swift in Sources */, 310 | FA4D9E3F233CA654003E5D8F /* HandoffAdvertisement.swift in Sources */, 311 | FA835547233B62C500E4F46B /* Log.swift in Sources */, 312 | FA835541233B5F4900E4F46B /* BLEDecryptor.swift in Sources */, 313 | FA83551F233A756F00E4F46B /* ContentView.swift in Sources */, 314 | FA83554B233B6FAA00E4F46B /* HandoffBLE.swift in Sources */, 315 | FA83553F233A75C700E4F46B /* KeychainAccess.swift in Sources */, 316 | FA83554F233B718600E4F46B /* DataIntExtensions.swift in Sources */, 317 | FA83551D233A756F00E4F46B /* AppDelegate.swift in Sources */, 318 | FA835549233B690800E4F46B /* HexToDataExtensions.swift in Sources */, 319 | FA4D9E41233CBF0A003E5D8F /* HandoffActivity.swift in Sources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | FA83552A233A757100E4F46B /* Sources */ = { 324 | isa = PBXSourcesBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | FA835533233A757100E4F46B /* BLE_ViewerTests.swift in Sources */, 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | /* End PBXSourcesBuildPhase section */ 332 | 333 | /* Begin PBXTargetDependency section */ 334 | FA835530233A757100E4F46B /* PBXTargetDependency */ = { 335 | isa = PBXTargetDependency; 336 | target = FA835518233A756F00E4F46B /* BLE-Viewer */; 337 | targetProxy = FA83552F233A757100E4F46B /* PBXContainerItemProxy */; 338 | }; 339 | /* End PBXTargetDependency section */ 340 | 341 | /* Begin PBXVariantGroup section */ 342 | FA835525233A757100E4F46B /* Main.storyboard */ = { 343 | isa = PBXVariantGroup; 344 | children = ( 345 | FA835526233A757100E4F46B /* Base */, 346 | ); 347 | name = Main.storyboard; 348 | sourceTree = ""; 349 | }; 350 | /* End PBXVariantGroup section */ 351 | 352 | /* Begin XCBuildConfiguration section */ 353 | FA835535233A757100E4F46B /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ALWAYS_SEARCH_USER_PATHS = NO; 357 | CLANG_ANALYZER_NONNULL = YES; 358 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 359 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 360 | CLANG_CXX_LIBRARY = "libc++"; 361 | CLANG_ENABLE_MODULES = YES; 362 | CLANG_ENABLE_OBJC_ARC = YES; 363 | CLANG_ENABLE_OBJC_WEAK = YES; 364 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 365 | CLANG_WARN_BOOL_CONVERSION = YES; 366 | CLANG_WARN_COMMA = YES; 367 | CLANG_WARN_CONSTANT_CONVERSION = YES; 368 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 369 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 370 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 371 | CLANG_WARN_EMPTY_BODY = YES; 372 | CLANG_WARN_ENUM_CONVERSION = YES; 373 | CLANG_WARN_INFINITE_RECURSION = YES; 374 | CLANG_WARN_INT_CONVERSION = YES; 375 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 376 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 377 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 378 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 379 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 380 | CLANG_WARN_STRICT_PROTOTYPES = YES; 381 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 382 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | COPY_PHASE_STRIP = NO; 386 | DEBUG_INFORMATION_FORMAT = dwarf; 387 | ENABLE_STRICT_OBJC_MSGSEND = YES; 388 | ENABLE_TESTABILITY = YES; 389 | GCC_C_LANGUAGE_STANDARD = gnu11; 390 | GCC_DYNAMIC_NO_PIC = NO; 391 | GCC_NO_COMMON_BLOCKS = YES; 392 | GCC_OPTIMIZATION_LEVEL = 0; 393 | GCC_PREPROCESSOR_DEFINITIONS = ( 394 | "DEBUG=1", 395 | "$(inherited)", 396 | ); 397 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 398 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 399 | GCC_WARN_UNDECLARED_SELECTOR = YES; 400 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 401 | GCC_WARN_UNUSED_FUNCTION = YES; 402 | GCC_WARN_UNUSED_VARIABLE = YES; 403 | MACOSX_DEPLOYMENT_TARGET = 10.14; 404 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 405 | MTL_FAST_MATH = YES; 406 | ONLY_ACTIVE_ARCH = YES; 407 | SDKROOT = macosx; 408 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 409 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 410 | }; 411 | name = Debug; 412 | }; 413 | FA835536233A757100E4F46B /* Release */ = { 414 | isa = XCBuildConfiguration; 415 | buildSettings = { 416 | ALWAYS_SEARCH_USER_PATHS = NO; 417 | CLANG_ANALYZER_NONNULL = YES; 418 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 419 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 420 | CLANG_CXX_LIBRARY = "libc++"; 421 | CLANG_ENABLE_MODULES = YES; 422 | CLANG_ENABLE_OBJC_ARC = YES; 423 | CLANG_ENABLE_OBJC_WEAK = YES; 424 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 425 | CLANG_WARN_BOOL_CONVERSION = YES; 426 | CLANG_WARN_COMMA = YES; 427 | CLANG_WARN_CONSTANT_CONVERSION = YES; 428 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 429 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 430 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 431 | CLANG_WARN_EMPTY_BODY = YES; 432 | CLANG_WARN_ENUM_CONVERSION = YES; 433 | CLANG_WARN_INFINITE_RECURSION = YES; 434 | CLANG_WARN_INT_CONVERSION = YES; 435 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 436 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 437 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 438 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 439 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 440 | CLANG_WARN_STRICT_PROTOTYPES = YES; 441 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 442 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 443 | CLANG_WARN_UNREACHABLE_CODE = YES; 444 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 445 | COPY_PHASE_STRIP = NO; 446 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 447 | ENABLE_NS_ASSERTIONS = NO; 448 | ENABLE_STRICT_OBJC_MSGSEND = YES; 449 | GCC_C_LANGUAGE_STANDARD = gnu11; 450 | GCC_NO_COMMON_BLOCKS = YES; 451 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 452 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 453 | GCC_WARN_UNDECLARED_SELECTOR = YES; 454 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 455 | GCC_WARN_UNUSED_FUNCTION = YES; 456 | GCC_WARN_UNUSED_VARIABLE = YES; 457 | MACOSX_DEPLOYMENT_TARGET = 10.14; 458 | MTL_ENABLE_DEBUG_INFO = NO; 459 | MTL_FAST_MATH = YES; 460 | SDKROOT = macosx; 461 | SWIFT_COMPILATION_MODE = wholemodule; 462 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 463 | }; 464 | name = Release; 465 | }; 466 | FA835538233A757100E4F46B /* Debug */ = { 467 | isa = XCBuildConfiguration; 468 | buildSettings = { 469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 470 | CODE_SIGN_ENTITLEMENTS = "BLE-Viewer/BLE_Viewer.entitlements"; 471 | CODE_SIGN_IDENTITY = "Apple Development"; 472 | CODE_SIGN_STYLE = Automatic; 473 | COMBINE_HIDPI_IMAGES = YES; 474 | DEVELOPMENT_ASSET_PATHS = "\"BLE-Viewer/Preview Content\""; 475 | DEVELOPMENT_TEAM = A5U65N39SD; 476 | ENABLE_HARDENED_RUNTIME = YES; 477 | ENABLE_PREVIEWS = YES; 478 | INFOPLIST_FILE = "BLE-Viewer/Info.plist"; 479 | LD_RUNPATH_SEARCH_PATHS = ( 480 | "$(inherited)", 481 | "@executable_path/../Frameworks", 482 | ); 483 | MACOSX_DEPLOYMENT_TARGET = 10.14; 484 | PRODUCT_BUNDLE_IDENTIFIER = "de.heinrich.alexander.BLE-Viewer"; 485 | PRODUCT_NAME = "$(TARGET_NAME)"; 486 | PROVISIONING_PROFILE_SPECIFIER = ""; 487 | SWIFT_VERSION = 5.0; 488 | }; 489 | name = Debug; 490 | }; 491 | FA835539233A757100E4F46B /* Release */ = { 492 | isa = XCBuildConfiguration; 493 | buildSettings = { 494 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 495 | CODE_SIGN_ENTITLEMENTS = "BLE-Viewer/BLE_Viewer.entitlements"; 496 | CODE_SIGN_IDENTITY = "Apple Development"; 497 | CODE_SIGN_STYLE = Automatic; 498 | COMBINE_HIDPI_IMAGES = YES; 499 | DEVELOPMENT_ASSET_PATHS = "\"BLE-Viewer/Preview Content\""; 500 | DEVELOPMENT_TEAM = A5U65N39SD; 501 | ENABLE_HARDENED_RUNTIME = YES; 502 | ENABLE_PREVIEWS = YES; 503 | INFOPLIST_FILE = "BLE-Viewer/Info.plist"; 504 | LD_RUNPATH_SEARCH_PATHS = ( 505 | "$(inherited)", 506 | "@executable_path/../Frameworks", 507 | ); 508 | MACOSX_DEPLOYMENT_TARGET = 10.14; 509 | PRODUCT_BUNDLE_IDENTIFIER = "de.heinrich.alexander.BLE-Viewer"; 510 | PRODUCT_NAME = "$(TARGET_NAME)"; 511 | PROVISIONING_PROFILE_SPECIFIER = ""; 512 | SWIFT_VERSION = 5.0; 513 | }; 514 | name = Release; 515 | }; 516 | FA83553B233A757100E4F46B /* Debug */ = { 517 | isa = XCBuildConfiguration; 518 | buildSettings = { 519 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 520 | BUNDLE_LOADER = "$(TEST_HOST)"; 521 | CODE_SIGN_IDENTITY = "Mac Developer"; 522 | CODE_SIGN_STYLE = Automatic; 523 | COMBINE_HIDPI_IMAGES = YES; 524 | DEVELOPMENT_TEAM = A5U65N39SD; 525 | INFOPLIST_FILE = "BLE-ViewerTests/Info.plist"; 526 | LD_RUNPATH_SEARCH_PATHS = ( 527 | "$(inherited)", 528 | "@executable_path/../Frameworks", 529 | "@loader_path/../Frameworks", 530 | ); 531 | MACOSX_DEPLOYMENT_TARGET = 10.15; 532 | PRODUCT_BUNDLE_IDENTIFIER = "de.heinrich.alexander.BLE-ViewerTests"; 533 | PRODUCT_NAME = "$(TARGET_NAME)"; 534 | SWIFT_VERSION = 5.0; 535 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BLE-Viewer.app/Contents/MacOS/BLE-Viewer"; 536 | }; 537 | name = Debug; 538 | }; 539 | FA83553C233A757100E4F46B /* Release */ = { 540 | isa = XCBuildConfiguration; 541 | buildSettings = { 542 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 543 | BUNDLE_LOADER = "$(TEST_HOST)"; 544 | CODE_SIGN_IDENTITY = "Mac Developer"; 545 | CODE_SIGN_STYLE = Automatic; 546 | COMBINE_HIDPI_IMAGES = YES; 547 | DEVELOPMENT_TEAM = A5U65N39SD; 548 | INFOPLIST_FILE = "BLE-ViewerTests/Info.plist"; 549 | LD_RUNPATH_SEARCH_PATHS = ( 550 | "$(inherited)", 551 | "@executable_path/../Frameworks", 552 | "@loader_path/../Frameworks", 553 | ); 554 | MACOSX_DEPLOYMENT_TARGET = 10.15; 555 | PRODUCT_BUNDLE_IDENTIFIER = "de.heinrich.alexander.BLE-ViewerTests"; 556 | PRODUCT_NAME = "$(TARGET_NAME)"; 557 | SWIFT_VERSION = 5.0; 558 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/BLE-Viewer.app/Contents/MacOS/BLE-Viewer"; 559 | }; 560 | name = Release; 561 | }; 562 | /* End XCBuildConfiguration section */ 563 | 564 | /* Begin XCConfigurationList section */ 565 | FA835514233A756F00E4F46B /* Build configuration list for PBXProject "BLE-Viewer" */ = { 566 | isa = XCConfigurationList; 567 | buildConfigurations = ( 568 | FA835535233A757100E4F46B /* Debug */, 569 | FA835536233A757100E4F46B /* Release */, 570 | ); 571 | defaultConfigurationIsVisible = 0; 572 | defaultConfigurationName = Release; 573 | }; 574 | FA835537233A757100E4F46B /* Build configuration list for PBXNativeTarget "BLE-Viewer" */ = { 575 | isa = XCConfigurationList; 576 | buildConfigurations = ( 577 | FA835538233A757100E4F46B /* Debug */, 578 | FA835539233A757100E4F46B /* Release */, 579 | ); 580 | defaultConfigurationIsVisible = 0; 581 | defaultConfigurationName = Release; 582 | }; 583 | FA83553A233A757100E4F46B /* Build configuration list for PBXNativeTarget "BLE-ViewerTests" */ = { 584 | isa = XCConfigurationList; 585 | buildConfigurations = ( 586 | FA83553B233A757100E4F46B /* Debug */, 587 | FA83553C233A757100E4F46B /* Release */, 588 | ); 589 | defaultConfigurationIsVisible = 0; 590 | defaultConfigurationName = Release; 591 | }; 592 | /* End XCConfigurationList section */ 593 | 594 | /* Begin XCRemoteSwiftPackageReference section */ 595 | FA835550233B82C800E4F46B /* XCRemoteSwiftPackageReference "CryptoSwift" */ = { 596 | isa = XCRemoteSwiftPackageReference; 597 | repositoryURL = "https://github.com/krzyzanowskim/CryptoSwift.git"; 598 | requirement = { 599 | kind = upToNextMajorVersion; 600 | minimumVersion = 1.0.0; 601 | }; 602 | }; 603 | /* End XCRemoteSwiftPackageReference section */ 604 | 605 | /* Begin XCSwiftPackageProductDependency section */ 606 | FA835551233B82C800E4F46B /* CryptoSwift */ = { 607 | isa = XCSwiftPackageProductDependency; 608 | package = FA835550233B82C800E4F46B /* XCRemoteSwiftPackageReference "CryptoSwift" */; 609 | productName = CryptoSwift; 610 | }; 611 | /* End XCSwiftPackageProductDependency section */ 612 | }; 613 | rootObject = FA835511233A756F00E4F46B /* Project object */; 614 | } 615 | -------------------------------------------------------------------------------- /BLE-Viewer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | Default 531 | 532 | 533 | 534 | 535 | 536 | 537 | Left to Right 538 | 539 | 540 | 541 | 542 | 543 | 544 | Right to Left 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | Default 556 | 557 | 558 | 559 | 560 | 561 | 562 | Left to Right 563 | 564 | 565 | 566 | 567 | 568 | 569 | Right to Left 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 788 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 886 | 890 | 891 | 892 | 893 | 894 | 895 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | --------------------------------------------------------------------------------