@capacitor-community/bluetooth-le 8.0.0-0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorCommunityBluetoothLe.podspec +17 -17
- package/LICENSE +21 -21
- package/Package.swift +27 -27
- package/README.md +5 -3
- package/android/build.gradle +73 -71
- package/android/src/main/AndroidManifest.xml +22 -22
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt +1094 -1094
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Conversion.kt +51 -51
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Device.kt +771 -771
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceList.kt +28 -28
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt +189 -189
- package/dist/docs.json +43 -43
- package/dist/esm/bleClient.d.ts +278 -278
- package/dist/esm/bleClient.js +361 -361
- package/dist/esm/bleClient.js.map +1 -1
- package/dist/esm/config.d.ts +53 -53
- package/dist/esm/config.js +2 -2
- package/dist/esm/conversion.d.ts +56 -56
- package/dist/esm/conversion.js +134 -134
- package/dist/esm/conversion.js.map +1 -1
- package/dist/esm/definitions.d.ts +352 -352
- package/dist/esm/definitions.js +42 -42
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +5 -5
- package/dist/esm/index.js +5 -5
- package/dist/esm/plugin.d.ts +2 -2
- package/dist/esm/plugin.js +4 -4
- package/dist/esm/queue.d.ts +3 -3
- package/dist/esm/queue.js +17 -17
- package/dist/esm/queue.js.map +1 -1
- package/dist/esm/timeout.d.ts +1 -1
- package/dist/esm/timeout.js +9 -9
- package/dist/esm/validators.d.ts +1 -1
- package/dist/esm/validators.js +11 -11
- package/dist/esm/validators.js.map +1 -1
- package/dist/esm/web.d.ts +57 -57
- package/dist/esm/web.js +403 -403
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +964 -964
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +964 -964
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/BluetoothLe/Conversion.swift +83 -83
- package/ios/Sources/BluetoothLe/Device.swift +423 -423
- package/ios/Sources/BluetoothLe/DeviceListView.swift +121 -121
- package/ios/Sources/BluetoothLe/DeviceManager.swift +415 -503
- package/ios/Sources/BluetoothLe/Logging.swift +8 -8
- package/ios/Sources/BluetoothLe/Plugin.swift +763 -775
- package/ios/Sources/BluetoothLe/ScanFilters.swift +114 -0
- package/ios/Sources/BluetoothLe/ThreadSafeDictionary.swift +15 -15
- package/ios/Tests/BluetoothLeTests/ConversionTests.swift +55 -55
- package/ios/Tests/BluetoothLeTests/PluginTests.swift +27 -27
- package/ios/Tests/BluetoothLeTests/ScanFiltersTests.swift +153 -0
- package/package.json +114 -115
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreBluetooth
|
|
3
|
+
|
|
4
|
+
struct ManufacturerDataFilter {
|
|
5
|
+
let companyIdentifier: UInt16
|
|
6
|
+
let dataPrefix: Data?
|
|
7
|
+
let mask: Data?
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
struct ServiceDataFilter {
|
|
11
|
+
let serviceUuid: CBUUID
|
|
12
|
+
let dataPrefix: Data?
|
|
13
|
+
let mask: Data?
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class ScanFilterUtils {
|
|
17
|
+
|
|
18
|
+
static func passesManufacturerDataFilter(_ advertisementData: [String: Any], filters: [ManufacturerDataFilter]?) -> Bool {
|
|
19
|
+
guard let filters = filters, !filters.isEmpty else {
|
|
20
|
+
return true // No filters means everything passes
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
guard let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data,
|
|
24
|
+
manufacturerData.count >= 2 else {
|
|
25
|
+
return false // If there's no valid manufacturer data, fail
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let companyIdentifier = manufacturerData.prefix(2).withUnsafeBytes {
|
|
29
|
+
$0.load(as: UInt16.self).littleEndian // Manufacturer ID is little-endian
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let payload = Data(manufacturerData.dropFirst(2))
|
|
33
|
+
|
|
34
|
+
for filter in filters {
|
|
35
|
+
if filter.companyIdentifier != companyIdentifier {
|
|
36
|
+
continue // Skip if company ID does not match
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if let dataPrefix = filter.dataPrefix {
|
|
40
|
+
if payload.count < dataPrefix.count {
|
|
41
|
+
continue // Payload too short, does not match
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if let mask = filter.mask {
|
|
45
|
+
// Validate that mask length matches dataPrefix length
|
|
46
|
+
if mask.count != dataPrefix.count {
|
|
47
|
+
continue // Skip this filter if mask length is invalid
|
|
48
|
+
}
|
|
49
|
+
var matches = true
|
|
50
|
+
for i in 0..<dataPrefix.count {
|
|
51
|
+
if (payload[i] & mask[i]) != (dataPrefix[i] & mask[i]) {
|
|
52
|
+
matches = false
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if matches {
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
} else if payload.starts(with: dataPrefix) {
|
|
60
|
+
return true
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
return true // Company ID matched, and no dataPrefix required
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return false // If none matched, return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static func passesServiceDataFilter(_ advertisementData: [String: Any], filters: [ServiceDataFilter]?) -> Bool {
|
|
71
|
+
guard let filters = filters, !filters.isEmpty else {
|
|
72
|
+
return true // No filters means everything passes
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
guard let serviceDataDict = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] else {
|
|
76
|
+
return false // If there's no service data, fail
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for filter in filters {
|
|
80
|
+
guard let serviceData = serviceDataDict[filter.serviceUuid] else {
|
|
81
|
+
continue // Skip if service UUID does not match
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if let dataPrefix = filter.dataPrefix {
|
|
85
|
+
if serviceData.count < dataPrefix.count {
|
|
86
|
+
continue // Service data too short, does not match
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if let mask = filter.mask {
|
|
90
|
+
// Validate that mask length matches dataPrefix length
|
|
91
|
+
if mask.count != dataPrefix.count {
|
|
92
|
+
continue // Skip this filter if mask length is invalid
|
|
93
|
+
}
|
|
94
|
+
var matches = true
|
|
95
|
+
for i in 0..<dataPrefix.count {
|
|
96
|
+
if (serviceData[i] & mask[i]) != (dataPrefix[i] & mask[i]) {
|
|
97
|
+
matches = false
|
|
98
|
+
break
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if matches {
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
} else if serviceData.starts(with: dataPrefix) {
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
return true // Service UUID matched, and no dataPrefix required
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return false // If none matched, return false
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
class ThreadSafeDictionary<K: Hashable, T> {
|
|
4
|
-
private var dictionary: [K: T] = [:]
|
|
5
|
-
private let queue = DispatchQueue(label: "threadSafeDictionaryQueue", attributes: .concurrent)
|
|
6
|
-
|
|
7
|
-
subscript(key: K) -> T? {
|
|
8
|
-
get {
|
|
9
|
-
return queue.sync { dictionary[key] }
|
|
10
|
-
}
|
|
11
|
-
set {
|
|
12
|
-
queue.async(flags: .barrier) { self.dictionary[key] = newValue }
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
class ThreadSafeDictionary<K: Hashable, T> {
|
|
4
|
+
private var dictionary: [K: T] = [:]
|
|
5
|
+
private let queue = DispatchQueue(label: "threadSafeDictionaryQueue", attributes: .concurrent)
|
|
6
|
+
|
|
7
|
+
subscript(key: K) -> T? {
|
|
8
|
+
get {
|
|
9
|
+
return queue.sync { dictionary[key] }
|
|
10
|
+
}
|
|
11
|
+
set {
|
|
12
|
+
queue.async(flags: .barrier) { self.dictionary[key] = newValue }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import CoreBluetooth
|
|
3
|
-
import XCTest
|
|
4
|
-
@testable import BluetoothLe
|
|
5
|
-
|
|
6
|
-
class ConversionTests: XCTestCase {
|
|
7
|
-
|
|
8
|
-
func testDataToString() throws {
|
|
9
|
-
let input = Data([0xA1, 0x2E, 0x38, 0xD4, 0x89, 0xC3])
|
|
10
|
-
let output = dataToString(input)
|
|
11
|
-
XCTAssertEqual(output, "a12e38d489c3")
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
func testStringToData() throws {
|
|
15
|
-
let input = "a12e38d489c3"
|
|
16
|
-
let output = stringToData(input)
|
|
17
|
-
let expected = Data([0xA1, 0x2E, 0x38, 0xD4, 0x89, 0xC3])
|
|
18
|
-
for (index, byte) in output.enumerated() {
|
|
19
|
-
XCTAssertEqual(byte, expected[index])
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
func testEmptyStringToData() throws {
|
|
24
|
-
let input = ""
|
|
25
|
-
let output = stringToData(input)
|
|
26
|
-
XCTAssertEqual(output, Data([]))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
func testCbuuidToString() throws {
|
|
30
|
-
XCTAssertEqual(cbuuidToString(CBUUID(string: "180D")), "0000180d-0000-1000-8000-00805f9b34fb")
|
|
31
|
-
XCTAssertEqual(cbuuidToString(CBUUID(string: "AAAA180D")), "aaaa180d-0000-1000-8000-00805f9b34fb")
|
|
32
|
-
XCTAssertEqual(cbuuidToString(CBUUID(string: "fb005c80-02e7-f387-1cad-8acd2d8df0c8")), "fb005c80-02e7-f387-1cad-8acd2d8df0c8")
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
func testCbuuidToStringUppercase() throws {
|
|
36
|
-
XCTAssertEqual(cbuuidToStringUppercase(CBUUID(string: "180D")), "0000180D-0000-1000-8000-00805F9B34FB")
|
|
37
|
-
XCTAssertEqual(cbuuidToStringUppercase(CBUUID(string: "fb005c80-02e7-f387-1cad-8acd2d8df0c8")), "FB005C80-02E7-F387-1CAD-8ACD2D8DF0C8")
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
func testOptionalStringConversion() throws {
|
|
41
|
-
let str: String? = "180D"
|
|
42
|
-
XCTAssertEqual("\(str)", "Optional(\"180D\")")
|
|
43
|
-
XCTAssertEqual("\(str!)", "180D")
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
func testDescriptorValueToString() throws {
|
|
47
|
-
XCTAssertEqual(descriptorValueToString("Hello"), "48656c6c6f")
|
|
48
|
-
XCTAssertEqual(descriptorValueToString(Data([0, 5, 255])), "0005ff")
|
|
49
|
-
XCTAssertEqual(descriptorValueToString(UInt16(258)), "0201")
|
|
50
|
-
XCTAssertEqual(descriptorValueToString(UInt16(1)), "0100")
|
|
51
|
-
XCTAssertEqual(descriptorValueToString(NSNumber(1)), "0100")
|
|
52
|
-
XCTAssertEqual(descriptorValueToString(0), "")
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
}
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreBluetooth
|
|
3
|
+
import XCTest
|
|
4
|
+
@testable import BluetoothLe
|
|
5
|
+
|
|
6
|
+
class ConversionTests: XCTestCase {
|
|
7
|
+
|
|
8
|
+
func testDataToString() throws {
|
|
9
|
+
let input = Data([0xA1, 0x2E, 0x38, 0xD4, 0x89, 0xC3])
|
|
10
|
+
let output = dataToString(input)
|
|
11
|
+
XCTAssertEqual(output, "a12e38d489c3")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func testStringToData() throws {
|
|
15
|
+
let input = "a12e38d489c3"
|
|
16
|
+
let output = stringToData(input)
|
|
17
|
+
let expected = Data([0xA1, 0x2E, 0x38, 0xD4, 0x89, 0xC3])
|
|
18
|
+
for (index, byte) in output.enumerated() {
|
|
19
|
+
XCTAssertEqual(byte, expected[index])
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func testEmptyStringToData() throws {
|
|
24
|
+
let input = ""
|
|
25
|
+
let output = stringToData(input)
|
|
26
|
+
XCTAssertEqual(output, Data([]))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func testCbuuidToString() throws {
|
|
30
|
+
XCTAssertEqual(cbuuidToString(CBUUID(string: "180D")), "0000180d-0000-1000-8000-00805f9b34fb")
|
|
31
|
+
XCTAssertEqual(cbuuidToString(CBUUID(string: "AAAA180D")), "aaaa180d-0000-1000-8000-00805f9b34fb")
|
|
32
|
+
XCTAssertEqual(cbuuidToString(CBUUID(string: "fb005c80-02e7-f387-1cad-8acd2d8df0c8")), "fb005c80-02e7-f387-1cad-8acd2d8df0c8")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
func testCbuuidToStringUppercase() throws {
|
|
36
|
+
XCTAssertEqual(cbuuidToStringUppercase(CBUUID(string: "180D")), "0000180D-0000-1000-8000-00805F9B34FB")
|
|
37
|
+
XCTAssertEqual(cbuuidToStringUppercase(CBUUID(string: "fb005c80-02e7-f387-1cad-8acd2d8df0c8")), "FB005C80-02E7-F387-1CAD-8ACD2D8DF0C8")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func testOptionalStringConversion() throws {
|
|
41
|
+
let str: String? = "180D"
|
|
42
|
+
XCTAssertEqual("\(str)", "Optional(\"180D\")")
|
|
43
|
+
XCTAssertEqual("\(str!)", "180D")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func testDescriptorValueToString() throws {
|
|
47
|
+
XCTAssertEqual(descriptorValueToString("Hello"), "48656c6c6f")
|
|
48
|
+
XCTAssertEqual(descriptorValueToString(Data([0, 5, 255])), "0005ff")
|
|
49
|
+
XCTAssertEqual(descriptorValueToString(UInt16(258)), "0201")
|
|
50
|
+
XCTAssertEqual(descriptorValueToString(UInt16(1)), "0100")
|
|
51
|
+
XCTAssertEqual(descriptorValueToString(NSNumber(1)), "0100")
|
|
52
|
+
XCTAssertEqual(descriptorValueToString(0), "")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import XCTest
|
|
2
|
-
import Capacitor
|
|
3
|
-
@testable import BluetoothLe
|
|
4
|
-
|
|
5
|
-
class PluginTests: XCTestCase {
|
|
6
|
-
|
|
7
|
-
func testEcho() {
|
|
8
|
-
// This is an example of a functional test case for a plugin.
|
|
9
|
-
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
|
10
|
-
|
|
11
|
-
let value = "Hello, World!"
|
|
12
|
-
XCTAssertEqual(1, 1)
|
|
13
|
-
|
|
14
|
-
// let plugin = MyPlugin()
|
|
15
|
-
//
|
|
16
|
-
// let call = CAPPluginCall(callbackId: "test", options: [
|
|
17
|
-
// "value": value
|
|
18
|
-
// ], success: { (result, _) in
|
|
19
|
-
// let resultValue = result!.data["value"] as? String
|
|
20
|
-
// XCTAssertEqual(value, resultValue)
|
|
21
|
-
// }, error: { (_) in
|
|
22
|
-
// XCTFail("Error shouldn't have been called")
|
|
23
|
-
// })
|
|
24
|
-
//
|
|
25
|
-
// plugin.echo(call!)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
import XCTest
|
|
2
|
+
import Capacitor
|
|
3
|
+
@testable import BluetoothLe
|
|
4
|
+
|
|
5
|
+
class PluginTests: XCTestCase {
|
|
6
|
+
|
|
7
|
+
func testEcho() {
|
|
8
|
+
// This is an example of a functional test case for a plugin.
|
|
9
|
+
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
|
10
|
+
|
|
11
|
+
let value = "Hello, World!"
|
|
12
|
+
XCTAssertEqual(1, 1)
|
|
13
|
+
|
|
14
|
+
// let plugin = MyPlugin()
|
|
15
|
+
//
|
|
16
|
+
// let call = CAPPluginCall(callbackId: "test", options: [
|
|
17
|
+
// "value": value
|
|
18
|
+
// ], success: { (result, _) in
|
|
19
|
+
// let resultValue = result!.data["value"] as? String
|
|
20
|
+
// XCTAssertEqual(value, resultValue)
|
|
21
|
+
// }, error: { (_) in
|
|
22
|
+
// XCTFail("Error shouldn't have been called")
|
|
23
|
+
// })
|
|
24
|
+
//
|
|
25
|
+
// plugin.echo(call!)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
import CoreBluetooth
|
|
3
|
+
@testable import BluetoothLe
|
|
4
|
+
|
|
5
|
+
class ScanFiltersTests: XCTestCase {
|
|
6
|
+
|
|
7
|
+
// MARK: - Manufacturer Data Filter Tests
|
|
8
|
+
|
|
9
|
+
func testManufacturerDataFilter_InvalidMaskLength() {
|
|
10
|
+
// Test that when mask.count != dataPrefix.count, the filter is skipped
|
|
11
|
+
// and returns false (no match)
|
|
12
|
+
|
|
13
|
+
// Create manufacturer data: 2 bytes company ID + 4 bytes payload
|
|
14
|
+
var manufacturerData = Data()
|
|
15
|
+
manufacturerData.append(contentsOf: [0x4C, 0x00]) // Apple company ID (0x004C in little-endian)
|
|
16
|
+
manufacturerData.append(contentsOf: [0x01, 0x02, 0x03, 0x04]) // 4 bytes payload
|
|
17
|
+
|
|
18
|
+
let advertisementData: [String: Any] = [
|
|
19
|
+
CBAdvertisementDataManufacturerDataKey: manufacturerData
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
// Create filter with dataPrefix of 4 bytes but mask of only 2 bytes
|
|
23
|
+
// This should be skipped due to invalid mask length
|
|
24
|
+
let dataPrefix = Data([0x01, 0x02, 0x03, 0x04]) // 4 bytes
|
|
25
|
+
let mask = Data([0xFF, 0xFF]) // Only 2 bytes - invalid!
|
|
26
|
+
|
|
27
|
+
let filter = ManufacturerDataFilter(
|
|
28
|
+
companyIdentifier: 0x004C,
|
|
29
|
+
dataPrefix: dataPrefix,
|
|
30
|
+
mask: mask
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
// Should return false because the filter is skipped due to invalid mask
|
|
34
|
+
let result = ScanFilterUtils.passesManufacturerDataFilter(advertisementData, filters: [filter])
|
|
35
|
+
XCTAssertFalse(result, "Should return false when mask length doesn't match dataPrefix length")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func testManufacturerDataFilter_ValidMaskLength() {
|
|
39
|
+
// Test the valid case where mask and dataPrefix have the same length
|
|
40
|
+
var manufacturerData = Data()
|
|
41
|
+
manufacturerData.append(contentsOf: [0x4C, 0x00]) // Apple company ID
|
|
42
|
+
manufacturerData.append(contentsOf: [0x01, 0x02, 0x03, 0x04])
|
|
43
|
+
|
|
44
|
+
let advertisementData: [String: Any] = [
|
|
45
|
+
CBAdvertisementDataManufacturerDataKey: manufacturerData
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
// Mask and dataPrefix have the same length - should work correctly
|
|
49
|
+
let dataPrefix = Data([0x01, 0x02, 0x03, 0x04])
|
|
50
|
+
let mask = Data([0xFF, 0xFF, 0xFF, 0xFF]) // Same length as dataPrefix
|
|
51
|
+
|
|
52
|
+
let filter = ManufacturerDataFilter(
|
|
53
|
+
companyIdentifier: 0x004C,
|
|
54
|
+
dataPrefix: dataPrefix,
|
|
55
|
+
mask: mask
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
let result = ScanFilterUtils.passesManufacturerDataFilter(advertisementData, filters: [filter])
|
|
59
|
+
XCTAssertTrue(result, "Should match when mask and dataPrefix have same length")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func testManufacturerDataFilter_NoMask() {
|
|
63
|
+
// Test without a mask - should use simple prefix matching
|
|
64
|
+
var manufacturerData = Data()
|
|
65
|
+
manufacturerData.append(contentsOf: [0x4C, 0x00])
|
|
66
|
+
manufacturerData.append(contentsOf: [0x01, 0x02, 0x03, 0x04])
|
|
67
|
+
|
|
68
|
+
let advertisementData: [String: Any] = [
|
|
69
|
+
CBAdvertisementDataManufacturerDataKey: manufacturerData
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
let dataPrefix = Data([0x01, 0x02])
|
|
73
|
+
let filter = ManufacturerDataFilter(
|
|
74
|
+
companyIdentifier: 0x004C,
|
|
75
|
+
dataPrefix: dataPrefix,
|
|
76
|
+
mask: nil // No mask
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
let result = ScanFilterUtils.passesManufacturerDataFilter(advertisementData, filters: [filter])
|
|
80
|
+
XCTAssertTrue(result, "Should match with prefix matching when no mask is provided")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// MARK: - Service Data Filter Tests
|
|
84
|
+
|
|
85
|
+
func testServiceDataFilter_InvalidMaskLength() {
|
|
86
|
+
// Test that when mask.count != dataPrefix.count, the filter is skipped
|
|
87
|
+
// and returns false (no match)
|
|
88
|
+
|
|
89
|
+
let serviceUUID = CBUUID(string: "1234")
|
|
90
|
+
let serviceData = Data([0x01, 0x02, 0x03, 0x04]) // 4 bytes
|
|
91
|
+
|
|
92
|
+
let advertisementData: [String: Any] = [
|
|
93
|
+
CBAdvertisementDataServiceDataKey: [serviceUUID: serviceData]
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
// Create filter with dataPrefix of 4 bytes but mask of only 2 bytes
|
|
97
|
+
// This should be skipped due to invalid mask length
|
|
98
|
+
let dataPrefix = Data([0x01, 0x02, 0x03, 0x04]) // 4 bytes
|
|
99
|
+
let mask = Data([0xFF, 0xFF]) // Only 2 bytes - invalid!
|
|
100
|
+
|
|
101
|
+
let filter = ServiceDataFilter(
|
|
102
|
+
serviceUuid: serviceUUID,
|
|
103
|
+
dataPrefix: dataPrefix,
|
|
104
|
+
mask: mask
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// Should return false because the filter is skipped due to invalid mask
|
|
108
|
+
let result = ScanFilterUtils.passesServiceDataFilter(advertisementData, filters: [filter])
|
|
109
|
+
XCTAssertFalse(result, "Should return false when mask length doesn't match dataPrefix length")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func testServiceDataFilter_ValidMaskLength() {
|
|
113
|
+
// Test the valid case where mask and dataPrefix have the same length
|
|
114
|
+
let serviceUUID = CBUUID(string: "1234")
|
|
115
|
+
let serviceData = Data([0x01, 0x02, 0x03, 0x04])
|
|
116
|
+
|
|
117
|
+
let advertisementData: [String: Any] = [
|
|
118
|
+
CBAdvertisementDataServiceDataKey: [serviceUUID: serviceData]
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
let dataPrefix = Data([0x01, 0x02, 0x03, 0x04])
|
|
122
|
+
let mask = Data([0xFF, 0xFF, 0xFF, 0xFF]) // Same length
|
|
123
|
+
|
|
124
|
+
let filter = ServiceDataFilter(
|
|
125
|
+
serviceUuid: serviceUUID,
|
|
126
|
+
dataPrefix: dataPrefix,
|
|
127
|
+
mask: mask
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
let result = ScanFilterUtils.passesServiceDataFilter(advertisementData, filters: [filter])
|
|
131
|
+
XCTAssertTrue(result, "Should match when mask and dataPrefix have same length")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
func testServiceDataFilter_NoMask() {
|
|
135
|
+
// Test without a mask
|
|
136
|
+
let serviceUUID = CBUUID(string: "1234")
|
|
137
|
+
let serviceData = Data([0x01, 0x02, 0x03, 0x04])
|
|
138
|
+
|
|
139
|
+
let advertisementData: [String: Any] = [
|
|
140
|
+
CBAdvertisementDataServiceDataKey: [serviceUUID: serviceData]
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
let dataPrefix = Data([0x01, 0x02])
|
|
144
|
+
let filter = ServiceDataFilter(
|
|
145
|
+
serviceUuid: serviceUUID,
|
|
146
|
+
dataPrefix: dataPrefix,
|
|
147
|
+
mask: nil
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
let result = ScanFilterUtils.passesServiceDataFilter(advertisementData, filters: [filter])
|
|
151
|
+
XCTAssertTrue(result, "Should match with prefix matching when no mask is provided")
|
|
152
|
+
}
|
|
153
|
+
}
|