mirror of
https://github.com/seemoo-lab/openhaystack.git
synced 2026-02-14 17:49:54 +00:00
Mark accessories as online when receiving Bluetooth advertisements
This commit is contained in:
@@ -51,6 +51,10 @@
|
||||
78EC226C25DBC2E40042B775 /* OpenHaystackMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC226B25DBC2E40042B775 /* OpenHaystackMainView.swift */; };
|
||||
78EC227225DBC8CE0042B775 /* Accessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227125DBC8CE0042B775 /* Accessory.swift */; };
|
||||
78EC227725DBDB7E0042B775 /* KeychainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227625DBDB7E0042B775 /* KeychainController.swift */; };
|
||||
F12D5A5A25FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12D5A5925FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift */; };
|
||||
F12D5A6025FA79FA00CBBA09 /* Advertisement.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12D5A5F25FA79FA00CBBA09 /* Advertisement.swift */; };
|
||||
F1647C1625FF6C61004144D6 /* BluetoothTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1647C1525FF6C61004144D6 /* BluetoothTests.swift */; };
|
||||
F1647C1B25FF7954004144D6 /* AccessoryNearbyMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1647C1A25FF7954004144D6 /* AccessoryNearbyMonitor.swift */; };
|
||||
F16BA9E925E7DB2D00238183 /* NIOSSL in Frameworks */ = {isa = PBXBuildFile; productRef = F16BA9E825E7DB2D00238183 /* NIOSSL */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -150,6 +154,10 @@
|
||||
78EC226B25DBC2E40042B775 /* OpenHaystackMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHaystackMainView.swift; sourceTree = "<group>"; };
|
||||
78EC227125DBC8CE0042B775 /* Accessory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessory.swift; sourceTree = "<group>"; };
|
||||
78EC227625DBDB7E0042B775 /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = "<group>"; };
|
||||
F12D5A5925FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothAccessoryScanner.swift; sourceTree = "<group>"; };
|
||||
F12D5A5F25FA79FA00CBBA09 /* Advertisement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advertisement.swift; sourceTree = "<group>"; };
|
||||
F1647C1525FF6C61004144D6 /* BluetoothTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothTests.swift; sourceTree = "<group>"; };
|
||||
F1647C1A25FF7954004144D6 /* AccessoryNearbyMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryNearbyMonitor.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -315,6 +323,7 @@
|
||||
78EC226325DAE0BE0042B775 /* OpenHaystackTests.swift */,
|
||||
78EC226525DAE0BE0042B775 /* Info.plist */,
|
||||
78023CB025F7841F00B083EF /* MicrocontrollerTests.swift */,
|
||||
F1647C1525FF6C61004144D6 /* BluetoothTests.swift */,
|
||||
);
|
||||
path = OpenHaystackTests;
|
||||
sourceTree = "<group>";
|
||||
@@ -322,6 +331,7 @@
|
||||
78EC226E25DBC2FC0042B775 /* HaystackApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F12D5A5E25FA79D600CBBA09 /* Bluetooth */,
|
||||
78023CAC25F7775300B083EF /* Firmwares */,
|
||||
78286D3A25E4017400F65511 /* Mail Plugin */,
|
||||
78EC227025DBC8BB0042B775 /* Views */,
|
||||
@@ -331,6 +341,7 @@
|
||||
787D8AC025DECD3C00148766 /* AccessoryController.swift */,
|
||||
78023CAA25F7767000B083EF /* ESP32Controller.swift */,
|
||||
7821DAD025F7B2C10054DC33 /* FileManager.swift */,
|
||||
F1647C1A25FF7954004144D6 /* AccessoryNearbyMonitor.swift */,
|
||||
);
|
||||
path = HaystackApp;
|
||||
sourceTree = "<group>";
|
||||
@@ -360,6 +371,15 @@
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F12D5A5E25FA79D600CBBA09 /* Bluetooth */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F12D5A5925FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift */,
|
||||
F12D5A5F25FA79FA00CBBA09 /* Advertisement.swift */,
|
||||
);
|
||||
path = Bluetooth;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -575,6 +595,7 @@
|
||||
78D9B80625F7CF60009B9CE8 /* ManageAccessoriesView.swift in Sources */,
|
||||
78486BEF25DD711E0007ED87 /* PopUpAlertView.swift in Sources */,
|
||||
78014A2925DC08580089F6D9 /* MicrobitController.swift in Sources */,
|
||||
F1647C1B25FF7954004144D6 /* AccessoryNearbyMonitor.swift in Sources */,
|
||||
78286D1F25E3D8B800F65511 /* ALTAnisetteData.m in Sources */,
|
||||
781EB3EC25DAD7EA00FEAA19 /* DecryptReports.swift in Sources */,
|
||||
78EC226C25DBC2E40042B775 /* OpenHaystackMainView.swift in Sources */,
|
||||
@@ -587,10 +608,12 @@
|
||||
781EB3F125DAD7EA00FEAA19 /* FindMyKeyDecoder.swift in Sources */,
|
||||
787D8AC125DECD3C00148766 /* AccessoryController.swift in Sources */,
|
||||
78023CAB25F7767000B083EF /* ESP32Controller.swift in Sources */,
|
||||
F12D5A6025FA79FA00CBBA09 /* Advertisement.swift in Sources */,
|
||||
781EB3F225DAD7EA00FEAA19 /* OpenHaystackApp.swift in Sources */,
|
||||
781EB3F325DAD7EA00FEAA19 /* Models.swift in Sources */,
|
||||
781EB3F425DAD7EA00FEAA19 /* FindMyController.swift in Sources */,
|
||||
781EB3F525DAD7EA00FEAA19 /* BoringSSL.m in Sources */,
|
||||
F12D5A5A25FA4F3500CBBA09 /* BluetoothAccessoryScanner.swift in Sources */,
|
||||
78286D5625E401F000F65511 /* MailPluginManager.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -609,6 +632,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78023CB125F7841F00B083EF /* MicrocontrollerTests.swift in Sources */,
|
||||
F1647C1625FF6C61004144D6 /* BluetoothTests.swift in Sources */,
|
||||
78EC226425DAE0BE0042B775 /* OpenHaystackTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -8,3 +8,35 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AccessoryNearbyMonitor: BluetoothAccessoryDelegate {
|
||||
|
||||
var accessoryController: AccessoryController
|
||||
var scanner: BluetoothAccessoryScanner
|
||||
|
||||
init(accessoryController: AccessoryController) {
|
||||
self.accessoryController = accessoryController
|
||||
self.scanner = BluetoothAccessoryScanner()
|
||||
self.initScanner()
|
||||
}
|
||||
|
||||
func initScanner() {
|
||||
self.scanner.delegate = self
|
||||
}
|
||||
|
||||
func received(_ advertisement: Advertisement) {
|
||||
guard let accessory = getAccessoryForAdvertisement(advertisement) else {
|
||||
return
|
||||
}
|
||||
accessory.isOnline = true
|
||||
}
|
||||
|
||||
func getAccessoryForAdvertisement(_ advertisement: Advertisement) -> Accessory? {
|
||||
let accessory =
|
||||
try? self.accessoryController.accessories.first {
|
||||
let accessoryPublicKey = try $0.getAdvertisementKey().advanced(by: 6)
|
||||
return accessoryPublicKey == advertisement.publicKeyPayload
|
||||
} ?? nil
|
||||
return accessory
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
|
||||
//
|
||||
// Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
|
||||
// Copyright © 2021 The Open Wireless Link Project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import CoreBluetooth
|
||||
import Foundation
|
||||
|
||||
struct Advertisement {
|
||||
|
||||
let publicKeyPayload: Data
|
||||
|
||||
init?(fromAdvertisementData: [String: Any]) {
|
||||
guard let manufacturerData = fromAdvertisementData[CBAdvertisementDataManufacturerDataKey] as? Data else {
|
||||
return nil
|
||||
}
|
||||
try? self.init(fromManufacturerData: manufacturerData)
|
||||
}
|
||||
|
||||
init?(fromManufacturerData: Data) {
|
||||
guard let publicKey = Advertisement.extractPublicKeyFromPayload(fromManufacturerData) else {
|
||||
return nil
|
||||
}
|
||||
self.publicKeyPayload = publicKey
|
||||
}
|
||||
|
||||
static let publicKeyPayloadLength = 22
|
||||
|
||||
static func extractPublicKeyFromPayload(_ payload: Data) -> Data? {
|
||||
guard payload.count == 29 else {
|
||||
return nil
|
||||
}
|
||||
// Apple company ID
|
||||
guard payload.subdata(in: 0..<2) == Data([0x4c, 0x00]) else {
|
||||
return nil
|
||||
}
|
||||
// Offline finding sub type
|
||||
guard payload.subdata(in: 2..<3) == Data([0x12]) else {
|
||||
return nil
|
||||
}
|
||||
// Offline finding sub type length
|
||||
guard payload.subdata(in: 3..<4) == Data([0x19]) else {
|
||||
return nil
|
||||
}
|
||||
let publicKey = payload.subdata(in: 5..<5 + publicKeyPayloadLength)
|
||||
guard publicKey.count == publicKeyPayloadLength else {
|
||||
return nil
|
||||
}
|
||||
return publicKey
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// OpenHaystack – Tracking personal Bluetooth devices via Apple's Find My network
|
||||
//
|
||||
// Copyright © 2021 Secure Mobile Networking Lab (SEEMOO)
|
||||
// Copyright © 2021 The Open Wireless Link Project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import CoreBluetooth
|
||||
import Foundation
|
||||
|
||||
protocol BluetoothAccessoryDelegate {
|
||||
func received(_ advertisement: Advertisement)
|
||||
}
|
||||
|
||||
public class BluetoothAccessoryScanner: NSObject, CBCentralManagerDelegate {
|
||||
|
||||
var scanner: CBCentralManager!
|
||||
var delegate: BluetoothAccessoryDelegate?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
scanner = CBCentralManager(delegate: self, queue: DispatchQueue.main)
|
||||
}
|
||||
|
||||
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
||||
startScanning(central)
|
||||
}
|
||||
|
||||
private func startScanning(_ central: CBCentralManager) {
|
||||
guard central.state == .poweredOn else {
|
||||
return
|
||||
}
|
||||
let scanOptions = [
|
||||
CBCentralManagerScanOptionAllowDuplicatesKey: false
|
||||
]
|
||||
scanner.scanForPeripherals(withServices: nil, options: scanOptions)
|
||||
}
|
||||
|
||||
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
||||
guard let adv = Advertisement(fromAdvertisementData: advertisementData) else {
|
||||
return
|
||||
}
|
||||
self.delegate?.received(adv)
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable, Hashable {
|
||||
@Published var lastLocation: CLLocation?
|
||||
@Published var locationTimestamp: Date?
|
||||
@Published var isDeployed: Bool
|
||||
@Published var isOnline: Bool = false
|
||||
|
||||
init(name: String = "New accessory", color: Color = randomColor(), iconName: String = randomIcon()) throws {
|
||||
self.name = name
|
||||
|
||||
@@ -54,6 +54,9 @@ struct AccessoryListEntry: View {
|
||||
}
|
||||
|
||||
Spacer()
|
||||
Circle()
|
||||
.fill(accessory.isOnline ? Color.green : Color.red)
|
||||
.frame(width: 8, height: 8)
|
||||
if !accessory.isDeployed {
|
||||
Button(
|
||||
action: { self.deployAccessoryToMicrobit(accessory) },
|
||||
|
||||
@@ -28,5 +28,7 @@
|
||||
<true/>
|
||||
<key>NSSupportsSuddenTermination</key>
|
||||
<true/>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>OpenHaystack uses Bluetooth to detect the presence of nearby accessories.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -12,6 +12,7 @@ import SwiftUI
|
||||
@main
|
||||
struct OpenHaystackApp: App {
|
||||
@StateObject var accessoryController: AccessoryController
|
||||
var accessoryNearbyMonitor: AccessoryNearbyMonitor
|
||||
|
||||
init() {
|
||||
let accessoryController: AccessoryController
|
||||
@@ -21,6 +22,7 @@ struct OpenHaystackApp: App {
|
||||
accessoryController = AccessoryController()
|
||||
}
|
||||
self._accessoryController = StateObject(wrappedValue: accessoryController)
|
||||
self.accessoryNearbyMonitor = AccessoryNearbyMonitor(accessoryController: accessoryController)
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import CoreBluetooth
|
||||
import XCTest
|
||||
|
||||
@testable import OpenHaystack
|
||||
|
||||
class BluetoothTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
@@ -19,16 +22,40 @@ class BluetoothTests: XCTestCase {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
func testNoManufacturerData() throws {
|
||||
let data: [String: Any] = [
|
||||
"": Data()
|
||||
]
|
||||
let adv = Advertisement(fromAdvertisementData: data)
|
||||
XCTAssertNil(adv)
|
||||
}
|
||||
|
||||
func testPerformanceExample() throws {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
func testEmptyManufacturerData() throws {
|
||||
let data: [String: Any] = [
|
||||
CBAdvertisementDataManufacturerDataKey: Data()
|
||||
]
|
||||
let adv = Advertisement(fromAdvertisementData: data)
|
||||
XCTAssertNil(adv)
|
||||
}
|
||||
|
||||
func testCorrectAdvertisement() throws {
|
||||
let publicKey = "11111111111111111111111111111111111111111111".hexaData
|
||||
let data = "4c00121900111111111111111111111111111111111111111111110100".hexaData
|
||||
let adv = Advertisement(fromManufacturerData: data)
|
||||
XCTAssertNotNil(adv)
|
||||
XCTAssertEqual(adv?.publicKeyPayload, publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
extension StringProtocol {
|
||||
var hexaData: Data { .init(hexa) }
|
||||
var hexaBytes: [UInt8] { .init(hexa) }
|
||||
private var hexa: UnfoldSequence<UInt8, Index> {
|
||||
sequence(state: startIndex) { startIndex in
|
||||
guard startIndex < self.endIndex else { return nil }
|
||||
let endIndex = self.index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex
|
||||
defer { startIndex = endIndex }
|
||||
return UInt8(self[startIndex..<endIndex], radix: 16)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user