@mentra/bluetooth-sdk 3.4.0-dev.978 → 3.4.0-dev.979
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/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
- package/build/_private/BluetoothSdkModule.d.ts +3 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/generated/releaseMetadata.js +5 -5
- package/build/generated/releaseMetadata.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +6 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/DeviceManager.swift +10 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
- package/ios/Source/MentraBluetoothSDK.swift +15 -2
- package/ios/Source/sgcs/Nimo.swift +278 -38
- package/ios/Source/sgcs/NimoCanvasCoordinator.swift +77 -18
- package/ios/Source/sgcs/NimoCanvasSession.swift +1 -0
- package/ios/Source/sgcs/NimoMacResourceTransport.swift +217 -0
- package/ios/Source/sgcs/NimoResourceFixture.swift +187 -0
- package/ios/Source/sgcs/NimoResourceSession.swift +340 -0
- package/ios/Tests/DeviceManagerReconnectTests.swift +75 -2
- package/ios/Tests/NimoCanvasTransportTests.swift +147 -0
- package/ios/Tests/NimoResourceSessionTests.swift +348 -0
- package/package.json +2 -2
- package/src/_private/BluetoothSdkModule.ts +3 -0
- package/src/generated/releaseMetadata.ts +5 -5
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
@testable import MentraBluetoothSDK
|
|
3
|
+
import XCTest
|
|
4
|
+
|
|
5
|
+
final class NimoResourceSessionTests: XCTestCase {
|
|
6
|
+
private let uuid = "00000000-0000-4000-8000-000000000001"
|
|
7
|
+
private final class Clock { var value: UInt64 = 100; func next() -> UInt64 { value += 1; return value } }
|
|
8
|
+
private func fixture() -> (NimoResourceSession, NSObject, NSObject, Clock) {
|
|
9
|
+
let clock = Clock(), owner = NSObject(), peer = NSObject()
|
|
10
|
+
let model = NimoResourceSession(captureNonce: uuid, pair: NimoResourcePair(name: "NIMO test", peripheralUuid: uuid), now: clock.next)
|
|
11
|
+
XCTAssertTrue(model.admit(owner, instanceId: uuid, name: "NIMO test"))
|
|
12
|
+
return (model, owner, peer, clock)
|
|
13
|
+
}
|
|
14
|
+
@MainActor
|
|
15
|
+
func testActualNimoReleaseRetiresPendingTimeAckWithoutRadioOrFalseHandshakeFailure() async {
|
|
16
|
+
for cancel in [false, true] {
|
|
17
|
+
for timeoutBeforeEntry in [false, true] {
|
|
18
|
+
let result = await Nimo().packageExercisePendingTimeAck(cancel: cancel, timeoutBeforeEntry: timeoutBeforeEntry)
|
|
19
|
+
XCTAssertTrue(result.errors.isEmpty)
|
|
20
|
+
XCTAssertEqual(result.pendingAcks, 0)
|
|
21
|
+
XCTAssertTrue(result.idle)
|
|
22
|
+
XCTAssertFalse(result.ready)
|
|
23
|
+
XCTAssertTrue(result.sameWriteEvidence)
|
|
24
|
+
XCTAssertTrue(result.ackStillAbsent)
|
|
25
|
+
XCTAssertTrue(result.noRadio)
|
|
26
|
+
XCTAssertFalse(result.physicalClosure)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
@MainActor
|
|
31
|
+
func testActualNimoActiveTimeAckTimeoutRemainsFailure() async {
|
|
32
|
+
let result = await Nimo().packageExercisePendingTimeAck(cancel: true, timeoutBeforeEntry: false, activeTimeout: true)
|
|
33
|
+
XCTAssertTrue(result.errors.contains("handshake-failed"))
|
|
34
|
+
XCTAssertFalse(result.ready)
|
|
35
|
+
XCTAssertTrue(result.noRadio)
|
|
36
|
+
}
|
|
37
|
+
func testReleaseFenceDoesNotTurnHandshakeAbandonmentIntoCommandOrWriterProof() {
|
|
38
|
+
let (model, owner, peer, _) = fixture()
|
|
39
|
+
XCTAssertTrue(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
40
|
+
model.transportConnected(owner, peer); model.observeTws(owner, connected: true)
|
|
41
|
+
let command = model.beginTimeCommand(owner)!
|
|
42
|
+
model.timeWriteStarted(owner, command: command)
|
|
43
|
+
let started = model.timeWriteStartedElapsedMs
|
|
44
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: true, mic: true, canvas: true))
|
|
45
|
+
XCTAssertFalse(model.recordHandshakeFailure(owner))
|
|
46
|
+
model.timeWriteCompleted(owner, command: command); model.timeAck(owner, command: command, status: 0)
|
|
47
|
+
XCTAssertEqual(model.timeWriteStartedElapsedMs, started)
|
|
48
|
+
XCTAssertNil(model.timeWriteCompletedElapsedMs)
|
|
49
|
+
XCTAssertNil(model.timeAckElapsedMs)
|
|
50
|
+
XCTAssertFalse(model.ready)
|
|
51
|
+
XCTAssertFalse(model.commandsSettled)
|
|
52
|
+
var producers = NimoResourceProducers(); producers.writerQueued = 1
|
|
53
|
+
XCTAssertFalse(model.settled(producers: producers))
|
|
54
|
+
XCTAssertTrue(model.errors.isEmpty)
|
|
55
|
+
}
|
|
56
|
+
func testForeignManagerCannotReplaceCaptureUntilOriginalClosure() {
|
|
57
|
+
XCTAssertTrue(NimoResourceManagerState.allowsReplacement(fixtureEnabled: false, originalClosed: false))
|
|
58
|
+
XCTAssertFalse(NimoResourceManagerState.allowsReplacement(fixtureEnabled: true, originalClosed: false))
|
|
59
|
+
XCTAssertTrue(NimoResourceManagerState.allowsReplacement(fixtureEnabled: true, originalClosed: true))
|
|
60
|
+
}
|
|
61
|
+
func testFailedOriginalConnectionCanReleaseWithoutInventingDisconnect() {
|
|
62
|
+
let (model, owner, peer, clock) = fixture()
|
|
63
|
+
XCTAssertTrue(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
64
|
+
model.connectionFailed(owner, NSObject(), receivedElapsedMs: clock.next())
|
|
65
|
+
model.connectionFailed(owner, peer, receivedElapsedMs: model.startedElapsedMs! - 1)
|
|
66
|
+
XCTAssertNil(model.connectionFailedElapsedMs)
|
|
67
|
+
model.connectionFailed(owner, peer, receivedElapsedMs: clock.next())
|
|
68
|
+
XCTAssertNotNil(model.connectionFailedElapsedMs)
|
|
69
|
+
XCTAssertFalse(model.ready)
|
|
70
|
+
XCTAssertFalse(model.settled(producers: NimoResourceProducers()))
|
|
71
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: true, mic: false, canvas: false))
|
|
72
|
+
XCTAssertTrue(model.settled(producers: NimoResourceProducers()))
|
|
73
|
+
XCTAssertNil(model.disconnectCallbackElapsedMs)
|
|
74
|
+
XCTAssertFalse(model.noTransport)
|
|
75
|
+
XCTAssertNotNil(model.original)
|
|
76
|
+
var producers = NimoResourceProducers(); producers.postedCallbacks = 1
|
|
77
|
+
XCTAssertFalse(model.settled(producers: producers))
|
|
78
|
+
model.transportConnected(owner, peer) // A contradictory late callback cannot preserve closure.
|
|
79
|
+
XCTAssertFalse(model.settled(producers: NimoResourceProducers()))
|
|
80
|
+
XCTAssertTrue(model.errors.contains("connect-after-failed-attempt"))
|
|
81
|
+
let (connected, other, otherPeer, otherClock) = fixture()
|
|
82
|
+
XCTAssertTrue(connected.beginTransport(other, peripheral: otherPeer, uuid: uuid))
|
|
83
|
+
connected.transportConnected(other, otherPeer)
|
|
84
|
+
connected.connectionFailed(other, otherPeer, receivedElapsedMs: otherClock.next())
|
|
85
|
+
XCTAssertNil(connected.connectionFailedElapsedMs)
|
|
86
|
+
XCTAssertTrue(connected.beginRelease(nonce: uuid, cancel: true, mic: false, canvas: false))
|
|
87
|
+
XCTAssertFalse(connected.settled(producers: NimoResourceProducers()))
|
|
88
|
+
let (fenced, fencedOwner, fencedPeer, fencedClock) = fixture()
|
|
89
|
+
XCTAssertTrue(fenced.beginTransport(fencedOwner, peripheral: fencedPeer, uuid: uuid))
|
|
90
|
+
XCTAssertTrue(fenced.beginRelease(nonce: uuid, cancel: true, mic: false, canvas: false))
|
|
91
|
+
fenced.transportConnected(fencedOwner, fencedPeer)
|
|
92
|
+
XCTAssertTrue(fenced.connected)
|
|
93
|
+
fenced.connectionFailed(fencedOwner, fencedPeer, receivedElapsedMs: fencedClock.next())
|
|
94
|
+
XCTAssertNil(fenced.connectionFailedElapsedMs)
|
|
95
|
+
XCTAssertFalse(fenced.settled(producers: NimoResourceProducers()))
|
|
96
|
+
}
|
|
97
|
+
func testOrdinaryManagerCleanupPreservesVerifiedOriginalClosureButNotActiveCapture() {
|
|
98
|
+
let (model, owner, _, _) = fixture()
|
|
99
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: true, mic: false, canvas: false))
|
|
100
|
+
model.proveNoTransport()
|
|
101
|
+
let accepted = model.settled(producers: NimoResourceProducers())
|
|
102
|
+
XCTAssertTrue(accepted)
|
|
103
|
+
XCTAssertFalse(model.shouldRunOrdinaryDisconnect(owner, originalClosed: accepted))
|
|
104
|
+
XCTAssertFalse(model.shouldRunOrdinaryDisconnect(owner, originalClosed: accepted))
|
|
105
|
+
XCTAssertTrue(model.errors.isEmpty)
|
|
106
|
+
XCTAssertTrue(model.settled(producers: NimoResourceProducers())) // Fresh status, not old bytes.
|
|
107
|
+
XCTAssertTrue(NimoResourceManagerState.allowsReplacement(fixtureEnabled: true, originalClosed: model.settled(producers: NimoResourceProducers())))
|
|
108
|
+
let (active, other, _, _) = fixture()
|
|
109
|
+
XCTAssertTrue(active.shouldRunOrdinaryDisconnect(other, originalClosed: false))
|
|
110
|
+
XCTAssertTrue(active.errors.contains("ordinary-disconnect-during-capture"))
|
|
111
|
+
XCTAssertFalse(active.settled(producers: NimoResourceProducers()))
|
|
112
|
+
let (failed, third, peer, clock) = fixture()
|
|
113
|
+
XCTAssertTrue(failed.beginTransport(third, peripheral: peer, uuid: uuid))
|
|
114
|
+
failed.connectionFailed(third, peer, receivedElapsedMs: clock.next())
|
|
115
|
+
XCTAssertTrue(failed.beginRelease(nonce: uuid, cancel: true, mic: false, canvas: false))
|
|
116
|
+
XCTAssertTrue(failed.settled(producers: NimoResourceProducers()))
|
|
117
|
+
failed.transportConnected(third, peer) // A late contradictory callback still invalidates closure.
|
|
118
|
+
XCTAssertTrue(failed.shouldRunOrdinaryDisconnect(third, originalClosed: true))
|
|
119
|
+
XCTAssertFalse(failed.settled(producers: NimoResourceProducers()))
|
|
120
|
+
}
|
|
121
|
+
func testManagerEntryRosterScopesOriginalNimoWithoutIgnoringItsCallbacks() {
|
|
122
|
+
let entries = NimoResourceManagerEntries(), foreign = NimoResourceManagerEntries()
|
|
123
|
+
let (model, owner, _, _) = fixture()
|
|
124
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: true, mic: false, canvas: false)); model.proveNoTransport()
|
|
125
|
+
let nimo = entries.begin("NIMO")
|
|
126
|
+
var producers = NimoResourceProducers(); producers.postedCallbacks = entries.originalNimo
|
|
127
|
+
XCTAssertFalse(model.settled(producers: producers))
|
|
128
|
+
let g2 = entries.begin("G2")
|
|
129
|
+
XCTAssertEqual(entries.all, 2); XCTAssertEqual(entries.originalNimo, 1)
|
|
130
|
+
foreign.end(nimo); XCTAssertEqual(entries.originalNimo, 1)
|
|
131
|
+
entries.end(nimo); entries.end(nimo) // Exact one-shot ticket, never an early/duplicate decrement.
|
|
132
|
+
producers.postedCallbacks = entries.originalNimo
|
|
133
|
+
XCTAssertEqual(entries.all, 1); XCTAssertTrue(model.settled(producers: producers))
|
|
134
|
+
XCTAssertFalse(model.shouldRunOrdinaryDisconnect(owner, originalClosed: true))
|
|
135
|
+
entries.end(g2); XCTAssertEqual(entries.all, 0)
|
|
136
|
+
producers.postedCallbacks = 1 // A late actual original callback still holds closure.
|
|
137
|
+
XCTAssertFalse(model.settled(producers: producers))
|
|
138
|
+
}
|
|
139
|
+
func testOriginalClosureCannotClearReplacementManagerSharedState() {
|
|
140
|
+
let (model, owner, _, _) = fixture()
|
|
141
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: false, mic: false, canvas: false))
|
|
142
|
+
model.proveNoTransport()
|
|
143
|
+
XCTAssertTrue(model.settled(producers: NimoResourceProducers()))
|
|
144
|
+
let g2 = NSObject()
|
|
145
|
+
var connected = true
|
|
146
|
+
NimoResourceManagerState.publish(owner: owner, current: g2) { connected = false }
|
|
147
|
+
XCTAssertTrue(connected)
|
|
148
|
+
NimoResourceManagerState.publish(owner: owner, current: nil) { connected = false }
|
|
149
|
+
XCTAssertTrue(connected)
|
|
150
|
+
NimoResourceManagerState.publish(owner: owner, current: owner) { connected = false }
|
|
151
|
+
XCTAssertFalse(connected)
|
|
152
|
+
}
|
|
153
|
+
func testQueuedOldNotificationsKeepReceiveTimeAndCannotSatisfyNewCommands() {
|
|
154
|
+
let (model, owner, peer, clock) = fixture()
|
|
155
|
+
XCTAssertTrue(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
156
|
+
model.transportConnected(owner, peer)
|
|
157
|
+
model.observeTws(owner, connected: true)
|
|
158
|
+
let command = model.beginTimeCommand(owner)!
|
|
159
|
+
let staleAck = clock.value
|
|
160
|
+
model.timeWriteStarted(owner, command: command)
|
|
161
|
+
model.timeAck(owner, command: command, status: 0, receivedElapsedMs: staleAck)
|
|
162
|
+
XCTAssertNil(model.timeAckElapsedMs)
|
|
163
|
+
let actualAck = clock.next()
|
|
164
|
+
model.timeAck(owner, command: command, status: 0, receivedElapsedMs: actualAck)
|
|
165
|
+
XCTAssertFalse(model.ready) // Real ACK may precede didWrite callback.
|
|
166
|
+
model.timeWriteCompleted(owner, command: command)
|
|
167
|
+
XCTAssertTrue(model.ready)
|
|
168
|
+
XCTAssertEqual(model.timeAckElapsedMs, actualAck)
|
|
169
|
+
let staleStop = clock.value
|
|
170
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: false, mic: true, canvas: true))
|
|
171
|
+
model.micStopStarted(owner)
|
|
172
|
+
model.micStopMarker(owner, receivedElapsedMs: staleStop)
|
|
173
|
+
model.exitAcknowledged(owner, status: 0, receivedElapsedMs: staleStop)
|
|
174
|
+
XCTAssertNil(model.micStopMarkerElapsedMs)
|
|
175
|
+
XCTAssertNil(model.exitAckElapsedMs)
|
|
176
|
+
let actualStop = clock.next()
|
|
177
|
+
model.micStopMarker(owner, receivedElapsedMs: actualStop)
|
|
178
|
+
model.micStopWritten(owner)
|
|
179
|
+
XCTAssertEqual(model.micStopMarkerElapsedMs, actualStop)
|
|
180
|
+
}
|
|
181
|
+
func testArmAdmissionPinsOneObjectAndRejectsReplacementWrongNameAndWrongPeripheral() {
|
|
182
|
+
let (model, owner, peer, _) = fixture()
|
|
183
|
+
let admitted = model.admittedElapsedMs
|
|
184
|
+
XCTAssertFalse(model.admit(NSObject(), instanceId: uuid, name: "NIMO test"))
|
|
185
|
+
XCTAssertFalse(model.admit(owner, instanceId: uuid, name: "other"))
|
|
186
|
+
XCTAssertEqual(model.admittedElapsedMs, admitted)
|
|
187
|
+
XCTAssertFalse(model.beginTransport(owner, peripheral: peer, uuid: UUID().uuidString))
|
|
188
|
+
XCTAssertTrue(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
189
|
+
XCTAssertFalse(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
190
|
+
XCTAssertFalse(model.beginTransport(owner, peripheral: NSObject(), uuid: uuid))
|
|
191
|
+
}
|
|
192
|
+
func testReadinessRequiresOriginalConnectedTwsWrittenCommandAndStatusZeroAck() {
|
|
193
|
+
let (model, owner, peer, _) = fixture()
|
|
194
|
+
XCTAssertTrue(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
195
|
+
XCTAssertNil(model.beginTimeCommand(owner))
|
|
196
|
+
model.transportConnected(owner, peer)
|
|
197
|
+
model.observeTws(NSObject(), connected: true)
|
|
198
|
+
XCTAssertNil(model.beginTimeCommand(owner))
|
|
199
|
+
model.observeTws(owner, connected: true)
|
|
200
|
+
let command = model.beginTimeCommand(owner)!
|
|
201
|
+
model.timeAck(owner, command: command, status: 0) // Before actual write start.
|
|
202
|
+
XCTAssertNil(model.timeAckElapsedMs)
|
|
203
|
+
model.timeWriteStarted(owner, command: command)
|
|
204
|
+
model.timeAck(owner, command: command, status: 0) // Early ACK buffers until actual callback.
|
|
205
|
+
XCTAssertFalse(model.ready)
|
|
206
|
+
model.timeWriteCompleted(owner, command: "wrong")
|
|
207
|
+
XCTAssertFalse(model.ready)
|
|
208
|
+
model.timeWriteCompleted(owner, command: command)
|
|
209
|
+
XCTAssertTrue(model.ready)
|
|
210
|
+
let handshake = model.handshakeTwsElapsedMs
|
|
211
|
+
model.observeTws(owner, connected: true)
|
|
212
|
+
XCTAssertEqual(model.handshakeTwsElapsedMs, handshake)
|
|
213
|
+
XCTAssertGreaterThan(model.twsObservedElapsedMs!, model.timeAckElapsedMs!)
|
|
214
|
+
model.observeTws(owner, connected: false)
|
|
215
|
+
XCTAssertFalse(model.ready)
|
|
216
|
+
}
|
|
217
|
+
func testNegativeAckAndReplacementCallbacksNeverEstablishReadiness() {
|
|
218
|
+
let (model, owner, peer, _) = fixture()
|
|
219
|
+
XCTAssertTrue(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
220
|
+
model.transportConnected(owner, NSObject())
|
|
221
|
+
XCTAssertFalse(model.connected)
|
|
222
|
+
model.transportConnected(owner, peer); model.observeTws(owner, connected: true)
|
|
223
|
+
let command = model.beginTimeCommand(owner)!
|
|
224
|
+
model.timeWriteStarted(owner, command: command); model.timeWriteCompleted(owner, command: command)
|
|
225
|
+
model.timeAck(owner, command: command, status: 2)
|
|
226
|
+
XCTAssertFalse(model.ready)
|
|
227
|
+
}
|
|
228
|
+
func testReleaseRequiresRealOriginalCallbackEveryObligationAndActualProducerSettlement() {
|
|
229
|
+
let (model, owner, peer, _) = fixture()
|
|
230
|
+
XCTAssertTrue(model.beginTransport(owner, peripheral: peer, uuid: uuid)); model.transportConnected(owner, peer)
|
|
231
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: false, mic: true, canvas: true))
|
|
232
|
+
let start = model.releaseStartedElapsedMs
|
|
233
|
+
XCTAssertFalse(model.beginRelease(nonce: UUID().uuidString, cancel: false, mic: false, canvas: false))
|
|
234
|
+
XCTAssertEqual(model.releaseStartedElapsedMs, start)
|
|
235
|
+
XCTAssertFalse(model.allowsEntry(owner)); XCTAssertNil(model.beginTimeCommand(owner))
|
|
236
|
+
model.proveNoTransport(); XCTAssertFalse(model.noTransport)
|
|
237
|
+
model.micStopMarker(owner) // Before actual stop write: ignored.
|
|
238
|
+
XCTAssertNil(model.micStopMarkerElapsedMs)
|
|
239
|
+
model.micStopStarted(owner); model.micStopMarker(owner); model.micStopWritten(owner)
|
|
240
|
+
model.exitAcknowledged(owner, status: 7)
|
|
241
|
+
XCTAssertFalse(model.commandsSettled)
|
|
242
|
+
model.exitAcknowledged(owner, status: 0)
|
|
243
|
+
XCTAssertTrue(model.commandsSettled)
|
|
244
|
+
model.disconnectRequested(owner)
|
|
245
|
+
XCTAssertFalse(model.settled(producers: NimoResourceProducers())) // cancel return/nil app ref is not closure.
|
|
246
|
+
model.disconnected(owner, NSObject(), error: nil)
|
|
247
|
+
XCTAssertNil(model.disconnectCallbackElapsedMs)
|
|
248
|
+
model.disconnected(owner, peer, error: nil)
|
|
249
|
+
var producers = NimoResourceProducers(); producers.encoderDelivery = 1
|
|
250
|
+
XCTAssertFalse(model.settled(producers: producers))
|
|
251
|
+
producers.encoderDelivery = 0; producers.writerActive = 1
|
|
252
|
+
XCTAssertFalse(model.settled(producers: producers))
|
|
253
|
+
producers.writerActive = 0; producers.reconnectTask = 1
|
|
254
|
+
XCTAssertFalse(model.settled(producers: producers))
|
|
255
|
+
XCTAssertTrue(model.settled(producers: NimoResourceProducers()))
|
|
256
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: false, mic: false, canvas: false))
|
|
257
|
+
XCTAssertEqual(model.releaseStartedElapsedMs, start)
|
|
258
|
+
XCTAssertTrue(model.micStopRequired); XCTAssertTrue(model.displayExitRequired)
|
|
259
|
+
}
|
|
260
|
+
func testGenZeroCancellationFencesAdmissionAndProducesExplicitNoTransport() {
|
|
261
|
+
let (model, owner, _, _) = fixture()
|
|
262
|
+
XCTAssertEqual(model.sessionDictionary()?["sessionGeneration"] as? UInt64, 0)
|
|
263
|
+
XCTAssertTrue(model.sessionDictionary()?["startedElapsedMs"] is NSNull)
|
|
264
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: true, mic: false, canvas: false))
|
|
265
|
+
model.proveNoTransport()
|
|
266
|
+
XCTAssertTrue(model.settled(producers: NimoResourceProducers()))
|
|
267
|
+
XCTAssertFalse(model.admit(owner, instanceId: uuid, name: "NIMO test"))
|
|
268
|
+
XCTAssertFalse(model.admit(NSObject(), instanceId: uuid, name: "NIMO test"))
|
|
269
|
+
}
|
|
270
|
+
func testDisconnectErrorOrUnrequestedDisconnectNeverProvesIdle() {
|
|
271
|
+
for requested in [true, false] {
|
|
272
|
+
let (model, owner, peer, _) = fixture()
|
|
273
|
+
XCTAssertTrue(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
274
|
+
model.transportConnected(owner, peer)
|
|
275
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: false, mic: false, canvas: false))
|
|
276
|
+
if requested { model.disconnectRequested(owner) }
|
|
277
|
+
model.disconnected(owner, peer, error: "radio error")
|
|
278
|
+
XCTAssertFalse(model.settled(producers: NimoResourceProducers()))
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
func testReleaseDoesNotAdoptLateGenerationOrRewriteReadinessAndClockAnchors() {
|
|
282
|
+
let (model, owner, peer, _) = fixture()
|
|
283
|
+
XCTAssertTrue(model.beginTransport(owner, peripheral: peer, uuid: uuid)); model.transportConnected(owner, peer)
|
|
284
|
+
model.observeTws(owner, connected: true); let command = model.beginTimeCommand(owner)!
|
|
285
|
+
model.timeWriteStarted(owner, command: command); model.timeWriteCompleted(owner, command: command); model.timeAck(owner, command: command, status: 0)
|
|
286
|
+
let observed = model.twsObservedElapsedMs, ack = model.timeAckElapsedMs
|
|
287
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: true, mic: false, canvas: false))
|
|
288
|
+
model.observeTws(owner, connected: false); model.timeAck(owner, command: command, status: 7)
|
|
289
|
+
XCTAssertEqual(model.twsObservedElapsedMs, observed); XCTAssertEqual(model.timeAckElapsedMs, ack)
|
|
290
|
+
XCTAssertFalse(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
291
|
+
XCTAssertFalse(model.ready)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
func testStrictTypedRequestRejectsPacketPathsUnknownFieldsAndMalformedOriginals() throws {
|
|
295
|
+
let pair: [String: Any] = ["name": "NIMO test", "peripheralUuid": uuid]
|
|
296
|
+
let process: [String: Any] = ["pid": 123, "instanceId": uuid]
|
|
297
|
+
let original: [String: Any] = ["instanceId": uuid, "peripheralUuid": uuid, "sessionGeneration": 1]
|
|
298
|
+
let valid: [String: Any] = ["version": 1, "operation": "arm", "requestNonce": uuid, "captureNonce": uuid,
|
|
299
|
+
"planSha256": String(repeating: "a", count: 64), "grantSha256": String(repeating: "b", count: 64),
|
|
300
|
+
"expectedProcess": process, "expectedPair": pair, "original": NSNull()]
|
|
301
|
+
func encoded(_ value: [String: Any]) throws -> Data { try JSONSerialization.data(withJSONObject: value, options: [.sortedKeys]) }
|
|
302
|
+
XCTAssertEqual(try NimoResourceRequest.decode(encoded(valid)).operation, .arm)
|
|
303
|
+
for replacement: [String: Any] in [
|
|
304
|
+
["packet": "52010000"], ["path": "/tmp/other.json"], ["operation": "connect"],
|
|
305
|
+
["requestNonce": "AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA"], ["version": 2], ["grantSha256": "unknown"],
|
|
306
|
+
["expectedPair": pair.merging(["bluetoothAddress": "00:11:22:33:44:55"]) { _, new in new }],
|
|
307
|
+
["expectedProcess": process.merging(["pid": true]) { _, new in new }],
|
|
308
|
+
["expectedPair": pair.merging(["name": String(repeating: "x", count: 129)]) { _, new in new }],
|
|
309
|
+
["original": original.merging(["sessionGeneration": 2]) { _, new in new }],
|
|
310
|
+
["original": original.merging(["command": "reset"]) { _, new in new }],
|
|
311
|
+
["original": original.merging(["instanceId": "replacement"]) { _, new in new }],
|
|
312
|
+
] {
|
|
313
|
+
XCTAssertThrowsError(try NimoResourceRequest.decode(encoded(valid.merging(replacement) { _, new in new })))
|
|
314
|
+
}
|
|
315
|
+
XCTAssertThrowsError(try NimoResourceRequest.decode(Data(repeating: 32, count: 16_385)))
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
func testAdmissionPersistenceFailureRetainsOnlyCancelledOriginalAndNoTransportAuthority() {
|
|
320
|
+
let clock = Clock(), owner = NSObject(), peer = NSObject()
|
|
321
|
+
let model = NimoResourceSession(captureNonce: uuid, pair: NimoResourcePair(name: "NIMO test", peripheralUuid: uuid), now: clock.next)
|
|
322
|
+
XCTAssertFalse(model.admit(owner, instanceId: uuid, name: "NIMO test", persist: { _ in
|
|
323
|
+
XCTAssertNil(model.admittedElapsedMs)
|
|
324
|
+
return false
|
|
325
|
+
}))
|
|
326
|
+
XCTAssertTrue(model.isOwner(owner)); XCTAssertTrue(model.cancelled)
|
|
327
|
+
XCTAssertNil(model.admittedElapsedMs)
|
|
328
|
+
XCTAssertFalse(model.allowsEntry(owner))
|
|
329
|
+
XCTAssertFalse(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
330
|
+
XCTAssertFalse(model.admit(NSObject(), instanceId: uuid, name: "NIMO test"))
|
|
331
|
+
XCTAssertTrue(model.beginRelease(nonce: uuid, cancel: true, mic: false, canvas: false))
|
|
332
|
+
model.proveNoTransport()
|
|
333
|
+
XCTAssertFalse(model.settled(producers: NimoResourceProducers())) // Failed durable authority is not fabricated idle.
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
func testTransportPersistencePrecedesEffectAuthorityAndFailureCannotBeRetried() {
|
|
337
|
+
let (model, owner, peer, _) = fixture()
|
|
338
|
+
XCTAssertFalse(model.beginTransport(owner, peripheral: peer, uuid: uuid, persist: { proposed in
|
|
339
|
+
XCTAssertNil(model.original); XCTAssertNil(model.startedElapsedMs)
|
|
340
|
+
XCTAssertGreaterThan(proposed, model.admittedElapsedMs!)
|
|
341
|
+
return false
|
|
342
|
+
}))
|
|
343
|
+
XCTAssertNil(model.original); XCTAssertNil(model.startedElapsedMs)
|
|
344
|
+
XCTAssertTrue(model.cancelled)
|
|
345
|
+
XCTAssertFalse(model.beginTransport(owner, peripheral: peer, uuid: uuid))
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentra/bluetooth-sdk",
|
|
3
|
-
"version": "3.4.0-dev.
|
|
3
|
+
"version": "3.4.0-dev.979",
|
|
4
4
|
"description": "SDK for communicating with smart glasses",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"registry": "https://registry.npmjs.org/"
|
|
115
115
|
},
|
|
116
116
|
"dependencies": {
|
|
117
|
-
"@mentra/cloud-protocol": "3.4.0-dev.
|
|
117
|
+
"@mentra/cloud-protocol": "3.4.0-dev.979"
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@types/node": "^25.9.3",
|
|
@@ -93,6 +93,9 @@ declare class BluetoothSdkNativeModule extends NativeModule<BluetoothSdkModuleEv
|
|
|
93
93
|
|
|
94
94
|
// Connection Commands
|
|
95
95
|
requestStatus(): Promise<void>
|
|
96
|
+
/** Mac-only explicit fixture launch. Strict nimo-macos-resource-v1 Codable
|
|
97
|
+
* request/receipt JSON; ordinary launches refuse. No packet/path commands. */
|
|
98
|
+
nimoResourceSession?(requestJSON: string): Promise<string>
|
|
96
99
|
connectDefault(options?: ConnectOptions): Promise<void>
|
|
97
100
|
connectDefaultWithOptions(options: Required<ConnectOptions>): Promise<void>
|
|
98
101
|
setDefaultDevice(device: Device | null): Promise<void>
|
|
@@ -12,9 +12,9 @@ export interface BluetoothSdkReleaseMetadata {
|
|
|
12
12
|
export const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({
|
|
13
13
|
"schemaVersion": 1,
|
|
14
14
|
"familyBaseVersion": "3.4.0",
|
|
15
|
-
"releaseIdentity": "3.4.0-dev.
|
|
16
|
-
"releaseSetId": "mentra-3.4.0-dev.
|
|
17
|
-
"sourceCommit": "
|
|
18
|
-
"otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.4.0/mentra-live-ota-3.4.0-dev.
|
|
19
|
-
"otaManifestSha256": "
|
|
15
|
+
"releaseIdentity": "3.4.0-dev.979",
|
|
16
|
+
"releaseSetId": "mentra-3.4.0-dev.979",
|
|
17
|
+
"sourceCommit": "cec2dff0b77fc7963d63602bba43556c690daf9d",
|
|
18
|
+
"otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.4.0/mentra-live-ota-3.4.0-dev.979.json",
|
|
19
|
+
"otaManifestSha256": "667fffda403d22a07c60a0029ef602c556d13ddfa4bf8161fda5f56f54f50f13"
|
|
20
20
|
})
|