3 class OctagonErrorHandlingTests: OctagonTestsBase {
5 func testRecoverFromImmediateTimeoutDuringEstablish() throws {
6 self.startCKAccountStatusMock()
8 let establishExpectation = self.expectation(description: "establishExpectation")
10 self.fakeCuttlefishServer.establishListener = { [unowned self] request in
11 self.fakeCuttlefishServer.establishListener = nil
12 establishExpectation.fulfill()
14 return CKPrettyError(domain: CKErrorDomain,
15 code: CKError.networkFailure.rawValue,
19 _ = self.assertResetAndBecomeTrustedInDefaultContext()
20 self.wait(for: [establishExpectation], timeout: 10)
23 func testRecoverFromRetryableErrorDuringEstablish() throws {
24 self.startCKAccountStatusMock()
26 let establishExpectation = self.expectation(description: "establishExpectation")
28 var t0 = Date.distantPast
30 self.fakeCuttlefishServer.establishListener = { [unowned self] request in
31 self.fakeCuttlefishServer.establishListener = nil
32 establishExpectation.fulfill()
35 return FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .retryableServerFailure)
38 _ = self.assertResetAndBecomeTrustedInDefaultContext()
39 self.wait(for: [establishExpectation], timeout: 10)
41 let d = t0.distance(to: t1)
42 XCTAssertGreaterThanOrEqual(d, 4)
43 // Let slower devices have a few extra seconds: we expect this after 5s, but sometimes they need a bit.
44 XCTAssertLessThanOrEqual(d, 8)
47 func testRecoverFromTransactionalErrorDuringJoinWithVoucher() throws {
48 self.startCKAccountStatusMock()
50 self.assertResetAndBecomeTrustedInDefaultContext()
52 var t0 = Date.distantPast
54 let joinExpectation = self.expectation(description: "joinExpectation")
55 self.fakeCuttlefishServer.joinListener = { [unowned self] _ in
56 self.fakeCuttlefishServer.joinListener = nil
57 joinExpectation.fulfill()
60 return FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .transactionalFailure)
63 let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2)
64 _ = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext)
66 self.wait(for: [joinExpectation], timeout: 10)
68 let d = t0.distance(to: t1)
69 XCTAssertGreaterThanOrEqual(d, 4)
70 // Let slower devices have a few extra seconds: we expect this after 5s, but sometimes they need a bit.
71 XCTAssertLessThanOrEqual(d, 8)
74 func testReceiveUpdateWhileUntrustedAndLocked() {
75 self.startCKAccountStatusMock()
77 self.cuttlefishContext.startOctagonStateMachine()
78 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
80 self.aksLockState = true
81 self.lockStateTracker.recheck()
83 self.sendContainerChange(context: self.cuttlefishContext)
85 XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "state machine should pause")
86 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
89 func testReceiveUpdateWhileReadyAndLocked() {
90 self.startCKAccountStatusMock()
92 self.cuttlefishContext.startOctagonStateMachine()
93 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
96 let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
97 XCTAssertNotNil(clique, "Clique should not be nil")
99 XCTFail("Shouldn't have errored making new friends: \(error)")
102 // Now, we should be in 'ready'
103 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
104 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
105 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
107 self.aksLockState = true
108 self.lockStateTracker.recheck()
110 self.sendContainerChange(context: self.cuttlefishContext)
112 XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "state machine should pause")
113 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
115 self.aksLockState = false
116 self.lockStateTracker.recheck()
117 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
120 self.aksLockState = true
121 self.lockStateTracker.recheck()
122 self.sendContainerChange(context: self.cuttlefishContext)
125 XCTAssertTrue(self.cuttlefishContext.stateMachine.possiblePendingFlags().contains("recd_push"), "Should have recd_push pending flag")
127 let waitForUnlockStateCondition = self.cuttlefishContext.stateMachine.stateConditions[OctagonStateWaitForUnlock] as! CKKSCondition
128 XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "state machine should pause")
129 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
132 // Check that we haven't been spinning
133 let sameWaitForUnlockStateCondition = self.cuttlefishContext.stateMachine.stateConditions[OctagonStateWaitForUnlock] as! CKKSCondition
134 XCTAssert(waitForUnlockStateCondition == sameWaitForUnlockStateCondition, "Conditions should be the same (as the state machine should be halted)")
136 self.aksLockState = false
137 self.lockStateTracker.recheck()
141 XCTAssertEqual(self.cuttlefishContext.stateMachine.possiblePendingFlags(), [], "Should have 0 pending flags")
143 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
146 func testReceiveUpdateWhileReadyAndAuthkitRetry() {
147 self.startCKAccountStatusMock()
149 self.cuttlefishContext.startOctagonStateMachine()
150 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
153 let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
154 XCTAssertNotNil(clique, "Clique should not be nil")
156 XCTFail("Shouldn't have errored making new friends: \(error)")
159 // Now, we should be in 'ready'
160 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
161 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
162 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
164 self.mockAuthKit.machineIDFetchErrors.append(CKPrettyError(domain: CKErrorDomain,
165 code: CKError.networkUnavailable.rawValue,
166 userInfo: [CKErrorRetryAfterKey: 2]))
168 self.sendContainerChange(context: self.cuttlefishContext)
170 XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "state machine should pause")
171 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
173 self.sendContainerChange(context: self.cuttlefishContext)
174 XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "state machine should pause")
175 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
178 func testReceiveUpdateWhileReadyAndLockedAndAuthkitRetry() {
179 self.startCKAccountStatusMock()
181 self.cuttlefishContext.startOctagonStateMachine()
182 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
185 let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
186 XCTAssertNotNil(clique, "Clique should not be nil")
188 XCTFail("Shouldn't have errored making new friends: \(error)")
191 // Now, we should be in 'ready'
192 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
193 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
194 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
196 self.aksLockState = true
197 self.lockStateTracker.recheck()
199 self.mockAuthKit.machineIDFetchErrors.append(CKPrettyError(domain: CKErrorDomain,
200 code: CKError.networkUnavailable.rawValue,
201 userInfo: [CKErrorRetryAfterKey: 2]))
203 self.sendContainerChange(context: self.cuttlefishContext)
205 XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "state machine should pause")
206 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
208 self.sendContainerChange(context: self.cuttlefishContext)
209 XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "state machine should pause")
210 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
214 XCTAssertTrue(self.cuttlefishContext.stateMachine.possiblePendingFlags().contains("recd_push"), "Should have recd_push pending flag")
216 self.aksLockState = false
217 self.lockStateTracker.recheck()
221 XCTAssertEqual(self.cuttlefishContext.stateMachine.possiblePendingFlags(), [], "Should have 0 pending flags")
222 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
225 func testReceiveTransactionErrorDuringUpdate() {
226 self.startCKAccountStatusMock()
228 self.cuttlefishContext.startOctagonStateMachine()
229 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
232 let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
233 XCTAssertNotNil(clique, "Clique should not be nil")
235 XCTFail("Shouldn't have errored making new friends: \(error)")
238 // Now, we should be in 'ready'
239 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
240 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
241 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
243 let pre = self.fakeCuttlefishServer.fetchChangesCalledCount
245 let ckError = FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .transactionalFailure)
246 self.fakeCuttlefishServer.nextFetchErrors.append(ckError)
248 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
252 XCTAssertEqual(0, self.cuttlefishContext.stateMachine.paused.wait(10 * NSEC_PER_SEC), "state machine should pause")
253 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
255 XCTAssertEqual(self.cuttlefishContext.stateMachine.possiblePendingFlags(), [], "Should have zero pending flags after retry")
257 let post = self.fakeCuttlefishServer.fetchChangesCalledCount
258 XCTAssertEqual(post, pre + 2, "should have fetched two times, the first response would have been a transaction error")
261 func testPreapprovedPushWhileLocked() throws {
262 // Peer 1 becomes SOS+Octagon
263 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
264 self.putSelfTLKShares(inCloudKit: self.manateeZoneID)
265 self.saveTLKMaterial(toKeychain: self.manateeZoneID)
267 XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on")
269 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
270 self.startCKAccountStatusMock()
272 self.cuttlefishContext.startOctagonStateMachine()
274 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
275 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
277 let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
278 XCTAssertNotNil(peerID, "Should have a peer ID after making new friends")
280 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
282 // Peer 2 attempts to join via preapprovalh
283 let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID")
284 let peer2contextID = "peer2"
285 let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false)
286 let peer2 = self.manager.context(forContainerName: OTCKContainerName,
287 contextID: peer2contextID,
288 sosAdapter: peer2mockSOS,
289 authKitAdapter: self.mockAuthKit2,
290 lockStateTracker: self.lockStateTracker,
291 accountStateTracker: self.accountStateTracker,
292 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
294 peer2.startOctagonStateMachine()
296 self.assertEnters(context: peer2, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
298 // Now, Peer1 should preapprove Peer2
299 let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
301 self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer)
302 self.mockSOSAdapter.sendTrustedPeerSetChangedUpdate()
304 // Peer1 should upload TLKs for Peer2
305 self.assertAllCKKSViewsUpload(tlkShares: 1)
307 let updateTrustExpectation = self.expectation(description: "updateTrust")
308 self.fakeCuttlefishServer.updateListener = { request in
309 XCTAssertEqual(peerID, request.peerID, "updateTrust request should be for ego peer ID")
310 XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info")
311 let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, sig: request.dynamicInfoAndSig.sig)
312 XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf")
314 XCTAssertEqual(newDynamicInfo!.preapprovals.count, 1, "Should have a single preapproval")
315 XCTAssertTrue(newDynamicInfo!.preapprovals.contains(peer2Preapproval), "Octagon peer should preapprove new SOS peer")
317 // But, since this is an SOS peer and TLK uploads for those peers are currently handled through CK CRUD operations, this update
318 // shouldn't have any TLKShares
319 XCTAssertEqual(0, request.tlkShares.count, "Trust update should not have any new TLKShares")
321 updateTrustExpectation.fulfill()
325 self.verifyDatabaseMocks()
326 self.wait(for: [updateTrustExpectation], timeout: 100)
327 self.fakeCuttlefishServer.updateListener = nil
329 // Now, peer 2 should lock and receive an Octagon push
330 self.aksLockState = true
331 self.lockStateTracker.recheck()
333 // Now, peer2 should receive an Octagon push, try to realize it is preapproved, and get stuck
334 self.sendContainerChange(context: peer2)
335 self.assertEnters(context: peer2, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
338 XCTAssertTrue(peer2.stateMachine.possiblePendingFlags().contains("recd_push"), "Should have recd_push pending flag")
340 self.aksLockState = false
341 self.lockStateTracker.recheck()
345 XCTAssertEqual(self.cuttlefishContext.stateMachine.possiblePendingFlags(), [], "Should have 0 pending flags")
346 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
348 self.assertEnters(context: peer2, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
351 func testReceiveMachineListUpdateWhileReadyAndLocked() throws {
352 // Peer 1 becomes SOS+Octagon
353 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
354 self.putSelfTLKShares(inCloudKit: self.manateeZoneID)
355 self.saveTLKMaterial(toKeychain: self.manateeZoneID)
357 XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on")
359 self.startCKAccountStatusMock()
360 self.cuttlefishContext.startOctagonStateMachine()
364 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
365 XCTAssertNotNil(clique, "Clique should not be nil")
367 XCTFail("Shouldn't have errored making new friends: \(error)")
371 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
372 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
374 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
376 // Peer 2 arrives (with a voucher), but is not on the trusted device list
377 let firstPeerID = clique.cliqueMemberIdentifier
378 XCTAssertNotNil(firstPeerID, "Clique should have a member identifier")
379 let bottle = self.fakeCuttlefishServer.state.bottles[0]
380 let entropy = try self.loadSecret(label: firstPeerID!)
381 XCTAssertNotNil(entropy, "entropy should not be nil")
383 let bNewOTCliqueContext = OTConfigurationContext()
384 bNewOTCliqueContext.context = "restoreB"
385 bNewOTCliqueContext.dsid = self.otcliqueContext.dsid
386 bNewOTCliqueContext.altDSID = self.otcliqueContext.altDSID
387 bNewOTCliqueContext.otControl = self.otcliqueContext.otControl
388 bNewOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottle.bottleID, entropy: entropy!)
390 let deviceBmockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID,
391 machineID: "b-machine-id",
392 otherDevices: [self.mockAuthKit.currentMachineID])
394 let bRestoreContext = self.manager.context(forContainerName: OTCKContainerName,
395 contextID: bNewOTCliqueContext.context!,
396 sosAdapter: OTSOSMissingAdapter(),
397 authKitAdapter: deviceBmockAuthKit,
398 lockStateTracker: self.lockStateTracker,
399 accountStateTracker: self.accountStateTracker,
400 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
402 bRestoreContext.startOctagonStateMachine()
404 self.sendContainerChange(context: bRestoreContext)
405 let bNewClique: OTClique
407 bNewClique = try OTClique.performEscrowRecovery(withContextData: bNewOTCliqueContext, escrowArguments: [:])
408 XCTAssertNotNil(bNewClique, "bNewClique should not be nil")
410 XCTFail("Shouldn't have errored recovering: \(error)")
413 self.assertEnters(context: bRestoreContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
415 // Device A notices, but doesn't update (because B isn't on the device list)
416 self.fakeCuttlefishServer.updateListener = { _ in
417 XCTFail("Should not have updated trust")
421 // Device A locks and gets the device list notification
422 self.aksLockState = true
423 self.lockStateTracker.recheck()
425 self.mockAuthKit.otherDevices.insert(deviceBmockAuthKit.currentMachineID)
427 self.cuttlefishContext.incompleteNotificationOfMachineIDListChange()
428 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForUnlock, within: 10 * NSEC_PER_SEC)
430 let updateTrustExpectation = self.expectation(description: "updateTrust")
431 self.fakeCuttlefishServer.updateListener = { request in
432 XCTAssertEqual(firstPeerID, request.peerID, "updateTrust request should be for ego peer ID")
433 XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info")
434 let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, sig: request.dynamicInfoAndSig.sig)
435 XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf")
437 XCTAssertEqual(newDynamicInfo?.includedPeerIDs.count, 2, "Should trust both peers")
438 updateTrustExpectation.fulfill()
442 self.sendContainerChange(context: self.cuttlefishContext)
443 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForUnlock, within: 10 * NSEC_PER_SEC)
445 // And on unlock, it should handle the update
446 self.aksLockState = false
447 self.lockStateTracker.recheck()
449 self.wait(for: [updateTrustExpectation], timeout: 30)
450 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
453 func testCKKSResetRecoverFromCKKSConflict() throws {
454 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
455 self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID)
456 // But do NOT add them to the keychain
458 // CKKS should get stuck in waitfortlk
459 self.startCKAccountStatusMock()
460 self.cuttlefishContext.startOctagonStateMachine()
461 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
464 let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
465 XCTAssertNotNil(clique, "Clique should not be nil")
467 XCTFail("Shouldn't have errored making new friends: \(error)")
470 // Now, we should be in 'ready', and CKKS should be stuck
471 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
472 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
473 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
475 // Now, CKKS decides to reset the world, but a conflict occurs on hierarchy upload
476 self.silentZoneDeletesAllowed = true
477 var tlkUUIDs : [CKRecordZone.ID:String] = [:]
479 self.silentFetchesAllowed = false
480 self.expectCKFetchAndRun(beforeFinished: {
481 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
482 self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID)
483 self.silentFetchesAllowed = true
485 // Use the commented version below when multi-zone support is readded to the tets
486 tlkUUIDs[self.manateeZoneID!] = (self.keys![self.manateeZoneID!] as? ZoneKeys)?.tlk?.uuid
488 for zoneID in self.ckksZones {
489 tlkUUIDs[zoneID as! CKRecordZone.ID] = (self.keys![zoneID] as? ZoneKeys)?.tlk?.uuid
494 let resetExepctation = self.expectation(description: "reset callback is called")
495 self.cuttlefishContext.viewManager!.rpcResetCloudKit(nil, reason: "unit-test") {
497 XCTAssertNil(error, "should be no error resetting cloudkit")
498 resetExepctation.fulfill()
501 // Deletions should occur, then the fetches, then get stuck (as we don't have the TLK)
502 self.wait(for: [resetExepctation], timeout: 10)
503 self.verifyDatabaseMocks()
505 // all subCKKSes should get stuck in waitfortlk
506 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
507 self.verifyDatabaseMocks()
509 XCTAssertEqual(tlkUUIDs.count, self.ckksZones.count, "Should have the right number of conflicted TLKs")
510 for (zoneID,tlkUUID) in tlkUUIDs {
511 XCTAssertEqual(tlkUUID, (self.keys![zoneID] as? ZoneKeys)?.tlk?.uuid, "TLK should match conflicted version")