@mentra/bluetooth-sdk 3.4.0-dev.996 → 3.4.0-dev.998
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/MIGRATION.md +61 -0
- package/README.md +47 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +6 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +8 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +48 -4
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +21 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +2 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/connection/ConnectionModels.kt +5 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/GlassesAudioSetupPolicy.kt +64 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +788 -134
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLiveOwnerLossPolicy.kt +227 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +19 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingReadinessPolicy.kt +94 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingStateMachine.kt +411 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingStateStore.kt +124 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/status/DeviceStatus.kt +50 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/DeviceManagerOwnerLossTest.kt +146 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/GlassesStatusAudioSetupTest.kt +51 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/GlassesStatusClassicAudioTest.kt +2 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/GlassesAudioSetupPolicyTest.kt +65 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveAudioSetupReadinessTest.kt +231 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveOwnerLossPolicyTest.kt +368 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +20 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingReadinessPolicyTest.kt +136 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingStateMachineTest.kt +346 -0
- package/build/BluetoothSdk.types.d.ts +101 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +1 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +8 -2
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +11 -3
- package/ios/Source/DeviceManager.swift +80 -6
- package/ios/Source/DeviceStore.swift +27 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +10 -4
- package/ios/Source/errors/BluetoothSdkError.swift +5 -1
- package/ios/Source/sgcs/GlassesAudioSetupPolicy.swift +181 -0
- package/ios/Source/sgcs/MentraLive.swift +1053 -166
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +260 -1
- package/ios/Source/sgcs/MentraLivePairingStateMachine.swift +385 -0
- package/ios/Source/sgcs/MentraLivePairingStateStore.swift +121 -0
- package/ios/Source/sgcs/SGCManager.swift +7 -0
- package/ios/Source/sgcs/Simulated.swift +2 -0
- package/ios/Source/status/DeviceStatus.swift +35 -0
- package/ios/Source/types/DeviceModels.swift +8 -1
- package/ios/Source/utils/AudioSessionMonitor.swift +158 -5
- package/ios/Tests/BluetoothAudioRouteTests.swift +82 -0
- package/ios/Tests/GlassesAudioSetupPolicyTests.swift +191 -0
- package/ios/Tests/MentraLiveAudioSetupTests.swift +215 -0
- package/ios/Tests/MentraLiveConnectionOptionsTests.swift +55 -0
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +327 -0
- package/ios/Tests/MentraLivePairingStateMachineTests.swift +402 -0
- package/ios/Tests/MentraLiveSessionReadinessTests.swift +328 -26
- package/package.json +3 -2
- package/src/BluetoothSdk.types.ts +97 -0
- package/src/_private/BluetoothSdkModule.ts +1 -0
- package/src/generated/releaseMetadata.ts +5 -5
- package/src/index.ts +10 -0
|
@@ -1,8 +1,57 @@
|
|
|
1
|
+
import CoreBluetooth
|
|
1
2
|
import Foundation
|
|
2
3
|
@testable import MentraBluetoothSDK
|
|
3
4
|
import XCTest
|
|
4
5
|
|
|
5
6
|
final class MentraLivePairingAdvertisementTests: XCTestCase {
|
|
7
|
+
func testKnownConnectUsesThePeripheralFoundDuringPairing() {
|
|
8
|
+
XCTAssertEqual(
|
|
9
|
+
MentraLiveKnownConnectRoute.decide(
|
|
10
|
+
bluetoothPoweredOn: false,
|
|
11
|
+
hasCachedPeripheral: true,
|
|
12
|
+
hasSavedIdentifier: true,
|
|
13
|
+
systemConnectedMatch: true
|
|
14
|
+
),
|
|
15
|
+
.waitForPower
|
|
16
|
+
)
|
|
17
|
+
XCTAssertEqual(
|
|
18
|
+
MentraLiveKnownConnectRoute.decide(
|
|
19
|
+
bluetoothPoweredOn: true,
|
|
20
|
+
hasCachedPeripheral: true,
|
|
21
|
+
hasSavedIdentifier: true,
|
|
22
|
+
systemConnectedMatch: false
|
|
23
|
+
),
|
|
24
|
+
.cachedPeripheral
|
|
25
|
+
)
|
|
26
|
+
XCTAssertEqual(
|
|
27
|
+
MentraLiveKnownConnectRoute.decide(
|
|
28
|
+
bluetoothPoweredOn: true,
|
|
29
|
+
hasCachedPeripheral: false,
|
|
30
|
+
hasSavedIdentifier: true,
|
|
31
|
+
systemConnectedMatch: false
|
|
32
|
+
),
|
|
33
|
+
.savedIdentifier
|
|
34
|
+
)
|
|
35
|
+
XCTAssertEqual(
|
|
36
|
+
MentraLiveKnownConnectRoute.decide(
|
|
37
|
+
bluetoothPoweredOn: true,
|
|
38
|
+
hasCachedPeripheral: false,
|
|
39
|
+
hasSavedIdentifier: false,
|
|
40
|
+
systemConnectedMatch: true
|
|
41
|
+
),
|
|
42
|
+
.alreadyConnected
|
|
43
|
+
)
|
|
44
|
+
XCTAssertEqual(
|
|
45
|
+
MentraLiveKnownConnectRoute.decide(
|
|
46
|
+
bluetoothPoweredOn: true,
|
|
47
|
+
hasCachedPeripheral: false,
|
|
48
|
+
hasSavedIdentifier: false,
|
|
49
|
+
systemConnectedMatch: false
|
|
50
|
+
),
|
|
51
|
+
.scan
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
6
55
|
func testConnectedPairingRecoveryRequiresExplicitPendingTarget() {
|
|
7
56
|
XCTAssertFalse(
|
|
8
57
|
MentraLivePendingPairingTarget.matches(
|
|
@@ -139,6 +188,188 @@ final class MentraLivePairingAdvertisementTests: XCTestCase {
|
|
|
139
188
|
}
|
|
140
189
|
}
|
|
141
190
|
|
|
191
|
+
func testCapabilityFlagDoesNotConfirmOwnership() {
|
|
192
|
+
XCTAssertFalse(MentraLiveConnectionAttemptPolicy.pairingInfoConfirmsOwner(ownerCommitted: false))
|
|
193
|
+
XCTAssertTrue(MentraLiveConnectionAttemptPolicy.pairingInfoConfirmsOwner(ownerCommitted: true))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func testStaleIosBondPromptsOnceUntilTheCooldownPasses() {
|
|
197
|
+
typealias Policy = MentraLiveConnectionAttemptPolicy
|
|
198
|
+
XCTAssertTrue(
|
|
199
|
+
Policy.shouldPromptToForgetStaleIosBond(peerRemovedPairingInformation: true, promptedAt: 0, now: 100)
|
|
200
|
+
)
|
|
201
|
+
XCTAssertFalse(
|
|
202
|
+
Policy.shouldPromptToForgetStaleIosBond(peerRemovedPairingInformation: true, promptedAt: 90, now: 100)
|
|
203
|
+
)
|
|
204
|
+
XCTAssertTrue(
|
|
205
|
+
Policy.shouldPromptToForgetStaleIosBond(
|
|
206
|
+
peerRemovedPairingInformation: true,
|
|
207
|
+
promptedAt: 100,
|
|
208
|
+
now: 100 + Policy.staleIosBondPromptCooldownSeconds
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
XCTAssertFalse(
|
|
212
|
+
Policy.shouldPromptToForgetStaleIosBond(peerRemovedPairingInformation: false, promptedAt: 0, now: 100)
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func testOnlySecureFirmwareOutsideExplicitPairingRejectsAnOwner() {
|
|
217
|
+
typealias Policy = MentraLiveConnectionAttemptPolicy
|
|
218
|
+
XCTAssertTrue(Policy.isOwnerRejection(isAuthFailure: true, explicitPairing: false, secureFirmware: true))
|
|
219
|
+
XCTAssertFalse(Policy.isOwnerRejection(isAuthFailure: true, explicitPairing: true, secureFirmware: true))
|
|
220
|
+
XCTAssertFalse(Policy.isOwnerRejection(isAuthFailure: true, explicitPairing: false, secureFirmware: false))
|
|
221
|
+
XCTAssertFalse(Policy.isOwnerRejection(isAuthFailure: false, explicitPairing: false, secureFirmware: true))
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
func testASingleRejectionDoesNotStandDown() {
|
|
225
|
+
var streak = MentraLiveOwnerRejectionStreak()
|
|
226
|
+
XCTAssertFalse(streak.record(nowMs: 0))
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
func testQuickRejectionsWaitForTheStreakToSpanAboutAMinute() {
|
|
230
|
+
var streak = MentraLiveOwnerRejectionStreak()
|
|
231
|
+
XCTAssertFalse(streak.record(nowMs: 0))
|
|
232
|
+
XCTAssertFalse(streak.record(nowMs: 3_000))
|
|
233
|
+
// Third attempt, but a glasses reboot also rejects for a few seconds.
|
|
234
|
+
XCTAssertFalse(streak.record(nowMs: 8_000))
|
|
235
|
+
XCTAssertFalse(streak.record(nowMs: 24_000))
|
|
236
|
+
XCTAssertTrue(streak.record(nowMs: MentraLiveOwnerRejectionStreak.minSpanMs))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func testASuccessfulSessionEndsTheStreak() {
|
|
240
|
+
var streak = MentraLiveOwnerRejectionStreak()
|
|
241
|
+
_ = streak.record(nowMs: 0)
|
|
242
|
+
_ = streak.record(nowMs: 10_000)
|
|
243
|
+
streak.reset()
|
|
244
|
+
XCTAssertFalse(streak.record(nowMs: 50_000))
|
|
245
|
+
XCTAssertFalse(streak.record(nowMs: 60_000))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
func testRejectionsFarApartAreNotOneStreak() {
|
|
249
|
+
var streak = MentraLiveOwnerRejectionStreak()
|
|
250
|
+
_ = streak.record(nowMs: 0)
|
|
251
|
+
_ = streak.record(nowMs: 10_000)
|
|
252
|
+
_ = streak.record(nowMs: 20_000)
|
|
253
|
+
let later = 20_000 + MentraLiveOwnerRejectionStreak.streakGapMs + 1
|
|
254
|
+
XCTAssertFalse(streak.record(nowMs: later))
|
|
255
|
+
XCTAssertFalse(streak.record(nowMs: later + 10_000))
|
|
256
|
+
XCTAssertFalse(streak.record(nowMs: later + 20_000))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
func testOneAttemptReportingTwiceCountsOnce() {
|
|
260
|
+
var streak = MentraLiveOwnerRejectionStreak()
|
|
261
|
+
let span = MentraLiveOwnerRejectionStreak.minSpanMs
|
|
262
|
+
// didFailToConnect then didDisconnect for each of two attempts.
|
|
263
|
+
XCTAssertFalse(streak.record(nowMs: 0))
|
|
264
|
+
XCTAssertFalse(streak.record(nowMs: 400))
|
|
265
|
+
XCTAssertFalse(streak.record(nowMs: span))
|
|
266
|
+
XCTAssertFalse(streak.record(nowMs: span + 400))
|
|
267
|
+
// A genuine third attempt completes the streak.
|
|
268
|
+
XCTAssertTrue(streak.record(nowMs: span + 10_000))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
func testAutomaticReconnectStandsDownWhileTheSavedGlassesAdvertisePairingMode() {
|
|
272
|
+
typealias Policy = MentraLiveConnectionAttemptPolicy
|
|
273
|
+
XCTAssertTrue(Policy.shouldStandDownInsteadOfReconnect(
|
|
274
|
+
pairingMode: true, savedGlasses: true, explicitPairing: false
|
|
275
|
+
))
|
|
276
|
+
XCTAssertFalse(Policy.shouldStandDownInsteadOfReconnect(
|
|
277
|
+
pairingMode: true, savedGlasses: true, explicitPairing: true
|
|
278
|
+
))
|
|
279
|
+
XCTAssertFalse(Policy.shouldStandDownInsteadOfReconnect(
|
|
280
|
+
pairingMode: false, savedGlasses: true, explicitPairing: false
|
|
281
|
+
))
|
|
282
|
+
XCTAssertFalse(Policy.shouldStandDownInsteadOfReconnect(
|
|
283
|
+
pairingMode: true, savedGlasses: false, explicitPairing: false
|
|
284
|
+
))
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
func testPauseIsTheWindowPlusTheCloseMarginWithNo300sClamp() {
|
|
288
|
+
typealias Policy = MentraLiveConnectionAttemptPolicy
|
|
289
|
+
XCTAssertEqual(Policy.yieldMs(windowMs: 120_000), 150_000)
|
|
290
|
+
XCTAssertEqual(Policy.yieldMs(windowMs: 900_000), 930_000)
|
|
291
|
+
XCTAssertEqual(Policy.yieldMs(windowMs: 5_000), 35_000)
|
|
292
|
+
// A missing window falls back to the spec default, not to a floor.
|
|
293
|
+
XCTAssertEqual(Policy.yieldMs(windowMs: 0), 150_000)
|
|
294
|
+
XCTAssertEqual(Policy.yieldMs(windowMs: -1), 150_000)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
func testARestartedAppKeepsOnlyALiveDeadlineWithinThePersistedPause() {
|
|
298
|
+
typealias Policy = MentraLiveConnectionAttemptPolicy
|
|
299
|
+
let now = 1_000_000
|
|
300
|
+
let pause = 150_000
|
|
301
|
+
XCTAssertEqual(Policy.persistedYieldRemainingMs(untilWallMs: 0, nowWallMs: now, pauseMs: pause), 0)
|
|
302
|
+
XCTAssertEqual(Policy.persistedYieldRemainingMs(untilWallMs: now + 50_000, nowWallMs: now, pauseMs: pause), 50_000)
|
|
303
|
+
XCTAssertEqual(Policy.persistedYieldRemainingMs(untilWallMs: now - 1, nowWallMs: now, pauseMs: pause), 0)
|
|
304
|
+
XCTAssertEqual(Policy.persistedYieldRemainingMs(untilWallMs: now + pause, nowWallMs: now, pauseMs: pause), pause)
|
|
305
|
+
// Longer than the pause persisted with it: a clock jump, not a deadline.
|
|
306
|
+
XCTAssertEqual(Policy.persistedYieldRemainingMs(untilWallMs: now + pause + 1, nowWallMs: now, pauseMs: pause), 0)
|
|
307
|
+
// A long window is not clamped to the old 300 s bound.
|
|
308
|
+
XCTAssertEqual(
|
|
309
|
+
Policy.persistedYieldRemainingMs(untilWallMs: now + 600_000, nowWallMs: now, pauseMs: 930_000),
|
|
310
|
+
600_000
|
|
311
|
+
)
|
|
312
|
+
XCTAssertEqual(Policy.persistedYieldRemainingMs(untilWallMs: now + 10, nowWallMs: now, pauseMs: 0), 0)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
func testPairSelectGoesOnlyWithAnExplicitAttemptToSecureGlasses() {
|
|
316
|
+
typealias Policy = MentraLiveConnectionAttemptPolicy
|
|
317
|
+
XCTAssertTrue(Policy.shouldSendPairSelect(explicitPairing: true, securePairingTarget: true))
|
|
318
|
+
XCTAssertFalse(Policy.shouldSendPairSelect(explicitPairing: false, securePairingTarget: true))
|
|
319
|
+
XCTAssertFalse(Policy.shouldSendPairSelect(explicitPairing: true, securePairingTarget: false))
|
|
320
|
+
XCTAssertFalse(Policy.shouldSendPairSelect(explicitPairing: false, securePairingTarget: false))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
func testParkedSecureReadinessAlwaysHasABondWaitDeadline() {
|
|
324
|
+
typealias Policy = MentraLiveConnectionAttemptPolicy
|
|
325
|
+
// glasses_ready parks exactly when the attempt is explicit and the target secure; the
|
|
326
|
+
// wait must then always run so the pair cannot hang.
|
|
327
|
+
XCTAssertTrue(Policy.shouldRunSecurePairingWait(explicitPairing: true, securePairingTarget: true))
|
|
328
|
+
XCTAssertFalse(Policy.shouldRunSecurePairingWait(explicitPairing: false, securePairingTarget: true))
|
|
329
|
+
XCTAssertFalse(Policy.shouldRunSecurePairingWait(explicitPairing: true, securePairingTarget: false))
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
func testTheStaleBondCodeMatchesTheAppContract() {
|
|
333
|
+
XCTAssertEqual(MentraLiveConnectionAttemptPolicy.iosForgetPairingCode, "ios_forget_pairing")
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
func testAuthFailureClassifierCoversError14AndInsufficientSecurityOnly() {
|
|
337
|
+
XCTAssertTrue(MentraLive.isPairingAuthFailure(
|
|
338
|
+
NSError(domain: CBErrorDomain, code: CBError.peerRemovedPairingInformation.rawValue)
|
|
339
|
+
))
|
|
340
|
+
XCTAssertTrue(MentraLive.isPairingAuthFailure(
|
|
341
|
+
NSError(domain: CBATTErrorDomain, code: CBATTError.insufficientAuthentication.rawValue)
|
|
342
|
+
))
|
|
343
|
+
XCTAssertTrue(MentraLive.isPairingAuthFailure(
|
|
344
|
+
NSError(domain: CBATTErrorDomain, code: CBATTError.insufficientEncryption.rawValue)
|
|
345
|
+
))
|
|
346
|
+
// A plain remote disconnect (the glasses' 0x14 refusal) and timeouts are not.
|
|
347
|
+
XCTAssertFalse(MentraLive.isPairingAuthFailure(
|
|
348
|
+
NSError(domain: CBErrorDomain, code: CBError.peripheralDisconnected.rawValue)
|
|
349
|
+
))
|
|
350
|
+
XCTAssertFalse(MentraLive.isPairingAuthFailure(
|
|
351
|
+
NSError(domain: CBErrorDomain, code: CBError.connectionTimeout.rawValue)
|
|
352
|
+
))
|
|
353
|
+
XCTAssertFalse(MentraLive.isPairingAuthFailure(nil))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
func testReadsTheOwnerChangedFlagAndWindowIdFromTheV4Trailer() {
|
|
357
|
+
var payload = securePayload(pairingFlag: 0)
|
|
358
|
+
payload[2 + 7] = 0x01 | 0x04
|
|
359
|
+
payload[2 + 12] = 9
|
|
360
|
+
let result = MentraLivePairingAdvertisement.parse(coreBluetoothManufacturerData: payload)
|
|
361
|
+
XCTAssertEqual(result?.ownerChanged, true)
|
|
362
|
+
XCTAssertEqual(result?.pairingGeneration, 9)
|
|
363
|
+
XCTAssertEqual(result?.pairingMode, false)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
func testOwnerChangedIsFalseWithoutTheFlag() {
|
|
367
|
+
let result = MentraLivePairingAdvertisement.parse(
|
|
368
|
+
coreBluetoothManufacturerData: securePayload(pairingFlag: 1)
|
|
369
|
+
)
|
|
370
|
+
XCTAssertEqual(result?.ownerChanged, false)
|
|
371
|
+
}
|
|
372
|
+
|
|
142
373
|
private func legacyPayload() -> Data {
|
|
143
374
|
var data = Data(repeating: 0, count: 29)
|
|
144
375
|
data[0] = 0x22
|
|
@@ -157,4 +388,100 @@ final class MentraLivePairingAdvertisementTests: XCTestCase {
|
|
|
157
388
|
data[2 + 11] = 0x50
|
|
158
389
|
return data
|
|
159
390
|
}
|
|
391
|
+
|
|
392
|
+
func testAPacketWithoutTheTrailerRightAfterOneWithItIsPartialNotLegacy() {
|
|
393
|
+
let t0 = 1_000_000
|
|
394
|
+
XCTAssertTrue(
|
|
395
|
+
MentraLiveDiscoveryPolicy.isPartialAdvertisement(secureCapable: false, lastSecureTrailerAtMs: t0, nowMs: t0 + 500)
|
|
396
|
+
)
|
|
397
|
+
XCTAssertTrue(
|
|
398
|
+
MentraLiveDiscoveryPolicy.isPartialAdvertisement(
|
|
399
|
+
secureCapable: false, lastSecureTrailerAtMs: t0, nowMs: t0 + MentraLiveDiscoveryPolicy.secureTrailerMemoryMs
|
|
400
|
+
)
|
|
401
|
+
)
|
|
402
|
+
XCTAssertFalse(
|
|
403
|
+
MentraLiveDiscoveryPolicy.isPartialAdvertisement(secureCapable: false, lastSecureTrailerAtMs: nil, nowMs: t0),
|
|
404
|
+
"legacy firmware never carries the trailer and is still listed as pairable"
|
|
405
|
+
)
|
|
406
|
+
XCTAssertFalse(
|
|
407
|
+
MentraLiveDiscoveryPolicy.isPartialAdvertisement(secureCapable: true, lastSecureTrailerAtMs: t0, nowMs: t0 + 500)
|
|
408
|
+
)
|
|
409
|
+
XCTAssertFalse(
|
|
410
|
+
MentraLiveDiscoveryPolicy.isPartialAdvertisement(secureCapable: true, lastSecureTrailerAtMs: nil, nowMs: t0)
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
func testTheSecureTrailerMemoryIsTenSeconds() {
|
|
415
|
+
XCTAssertEqual(MentraLiveDiscoveryPolicy.secureTrailerMemoryMs, 10_000)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
func testARolledBackUnitListsAsLegacyOnceTheShortMemoryExpires() {
|
|
419
|
+
var memory = MentraLiveSecureTrailerMemory()
|
|
420
|
+
let unit = UUID()
|
|
421
|
+
let t0 = 5_000_000
|
|
422
|
+
memory.recordSecureTrailer(unit, nowMs: t0)
|
|
423
|
+
XCTAssertTrue(memory.isPartialAdvertisement(unit, secureCapable: false, nowMs: t0 + 2_000))
|
|
424
|
+
// Rolled back to legacy firmware: no trailer any more. After the memory it is legacy.
|
|
425
|
+
XCTAssertFalse(memory.isPartialAdvertisement(unit, secureCapable: false, nowMs: t0 + 10_001))
|
|
426
|
+
XCTAssertFalse(memory.hasRecentSecureTrailer(unit, nowMs: t0 + 10_001))
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
func testASameNamedLegacyUnitIsNotHidden() {
|
|
430
|
+
var memory = MentraLiveSecureTrailerMemory()
|
|
431
|
+
let secureUnit = UUID()
|
|
432
|
+
let legacyUnitWithTheSameName = UUID()
|
|
433
|
+
let t0 = 5_000_000
|
|
434
|
+
memory.recordSecureTrailer(secureUnit, nowMs: t0)
|
|
435
|
+
XCTAssertTrue(memory.isPartialAdvertisement(secureUnit, secureCapable: false, nowMs: t0 + 100))
|
|
436
|
+
XCTAssertFalse(memory.isPartialAdvertisement(legacyUnitWithTheSameName, secureCapable: false, nowMs: t0 + 100))
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
func testForgetClearsTheSecureTrailerMemory() {
|
|
440
|
+
var memory = MentraLiveSecureTrailerMemory()
|
|
441
|
+
let unit = UUID()
|
|
442
|
+
memory.recordSecureTrailer(unit, nowMs: 1_000)
|
|
443
|
+
memory.clear()
|
|
444
|
+
XCTAssertFalse(memory.isPartialAdvertisement(unit, secureCapable: false, nowMs: 1_100))
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
func testSecurePairingTargetFollowsOnlyTheSelectedRow() {
|
|
448
|
+
typealias Policy = MentraLiveConnectionAttemptPolicy
|
|
449
|
+
XCTAssertTrue(Policy.isSecurePairingTarget(selectedCapability: true, securePairingInfoThisSession: false))
|
|
450
|
+
XCTAssertFalse(
|
|
451
|
+
Policy.isSecurePairingTarget(selectedCapability: false, securePairingInfoThisSession: true),
|
|
452
|
+
"a row listed as legacy stays legacy"
|
|
453
|
+
)
|
|
454
|
+
XCTAssertFalse(Policy.isSecurePairingTarget(selectedCapability: nil, securePairingInfoThisSession: false))
|
|
455
|
+
XCTAssertTrue(
|
|
456
|
+
Policy.isSecurePairingTarget(selectedCapability: nil, securePairingInfoThisSession: true),
|
|
457
|
+
"an unknown row follows this session's secure pairing_info"
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
func testGlassesReadyReadinessForEachTargetCapability() {
|
|
462
|
+
typealias Policy = MentraLiveConnectionAttemptPolicy
|
|
463
|
+
func readiness(
|
|
464
|
+
explicit: Bool = true, row: Bool?, securePairingInfo: Bool = false, confirmed: Bool = false
|
|
465
|
+
) -> Policy.Readiness {
|
|
466
|
+
Policy.readinessAtGlassesReady(
|
|
467
|
+
explicitPairing: explicit,
|
|
468
|
+
selectedCapability: row,
|
|
469
|
+
securePairingInfoThisSession: securePairingInfo,
|
|
470
|
+
securePairingConfirmed: confirmed
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
// Automatic reconnect never waits.
|
|
474
|
+
XCTAssertEqual(readiness(explicit: false, row: nil), .ready)
|
|
475
|
+
XCTAssertEqual(readiness(explicit: false, row: true), .ready)
|
|
476
|
+
// Known rows.
|
|
477
|
+
XCTAssertEqual(readiness(row: false), .ready)
|
|
478
|
+
XCTAssertEqual(readiness(row: true), .awaitOwnerCommit)
|
|
479
|
+
XCTAssertEqual(readiness(row: true, confirmed: true), .ready)
|
|
480
|
+
// Unknown row: wait (bounded) for pairing_info unless one already decided it.
|
|
481
|
+
XCTAssertEqual(readiness(row: nil), .awaitPairingInfo)
|
|
482
|
+
XCTAssertEqual(readiness(row: nil, securePairingInfo: true), .awaitOwnerCommit)
|
|
483
|
+
XCTAssertEqual(readiness(row: nil, securePairingInfo: true, confirmed: true), .ready)
|
|
484
|
+
XCTAssertEqual(Policy.unknownCapabilityPairingInfoWaitMs, 5_000)
|
|
485
|
+
XCTAssertEqual(Policy.secureFirmwareRefreshMs, 15_000)
|
|
486
|
+
}
|
|
160
487
|
}
|