Asyncify SVR2

This commit is contained in:
Max Radermacher 2026-05-20 01:25:06 -05:00 committed by GitHub
parent ee6cd21fb0
commit 1105ac39a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 439 additions and 528 deletions

View File

@ -2216,7 +2216,7 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
) async -> RegistrationStep {
let maxAutomaticRetries = Constants.networkErrorRetries
let result = await deps.svr.restoreKeys(pin: pin, authMethod: .svrAuth(credential, backup: nil)).awaitable()
let result = await deps.svr.restoreKeys(pin: pin, authMethod: .svrAuth(credential, backup: nil))
switch result {
case .success(let masterKey):
@ -3443,7 +3443,7 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
let result = await deps.svr.restoreKeys(
pin: pin,
authMethod: .svrAuth(svrAuthCredential, backup: nil),
).awaitable()
)
switch result {
case .success(let masterKey):
@ -3929,7 +3929,7 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
let result = await deps.svr.restoreKeys(
pin: pin,
authMethod: authMethod,
).awaitable()
)
switch result {
case .success(let masterKey):
@ -4022,7 +4022,7 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
pin: pin,
masterKey: masterKey,
authMethod: authMethod,
).awaitable()
)
inMemoryState.hasBackedUpToSVR = true
await db.awaitableWrite { tx in

View File

@ -446,7 +446,6 @@ extension AppSetup.GlobalsContinuation {
credentialStorage: svrCredentialStorage,
db: db,
accountKeyStore: accountKeyStore,
scheduler: DispatchQueue(label: "org.signal.svr2", qos: .userInitiated),
storageServiceManager: storageServiceManager,
svrLocalStorage: svrLocalStorage,
tsAccountManager: tsAccountManager,

View File

@ -33,14 +33,14 @@ public class SecureValueRecoveryMock: SecureValueRecovery {
public var backupMasterKeyMock: ((_ pin: String, _ masterKey: MasterKey, _ authMethod: SVR.AuthMethod) -> Promise<MasterKey>)?
public func backupMasterKey(pin: String, masterKey: MasterKey, authMethod: SVR.AuthMethod) -> Promise<MasterKey> {
return backupMasterKeyMock!(pin, masterKey, authMethod)
public func backupMasterKey(pin: String, masterKey: MasterKey, authMethod: SVR.AuthMethod) async throws -> MasterKey {
return try await backupMasterKeyMock!(pin, masterKey, authMethod).awaitable()
}
public var restoreKeysMock: ((_ pin: String, _ authMethod: SVR.AuthMethod) -> Guarantee<SVR.RestoreKeysResult>)?
public func restoreKeys(pin: String, authMethod: SVR.AuthMethod) -> Guarantee<SVR.RestoreKeysResult> {
return restoreKeysMock!(pin, authMethod)
public func restoreKeys(pin: String, authMethod: SVR.AuthMethod) async -> SVR.RestoreKeysResult {
return await restoreKeysMock!(pin, authMethod).awaitable()
}
public func warmCaches() {

View File

@ -9,23 +9,20 @@ import Foundation
public class MockSgxWebsocketConnectionFactory: SgxWebsocketConnectionFactory {
private var onConnectAndPerformHandshakeHandlers = [String: (Any) -> Promise<Any>]()
private var onConnectAndPerformHandshakeHandlers = [String: (Any) async throws -> Any]()
public func setOnConnectAndPerformHandshake<Configurator: SgxWebsocketConfigurator>(
_ block: @escaping (Configurator) -> Promise<SgxWebsocketConnection<Configurator>>,
_ block: @escaping (Configurator) async throws -> SgxWebsocketConnection<Configurator>,
) {
let key = String(describing: Configurator.self)
self.onConnectAndPerformHandshakeHandlers[key] = {
return block($0 as! Configurator).map(on: SyncScheduler()) { $0 }
}
self.onConnectAndPerformHandshakeHandlers[key] = { try await block($0 as! Configurator) }
}
public func connectAndPerformHandshake<Configurator: SgxWebsocketConfigurator>(
configurator: Configurator,
on scheduler: Scheduler,
) -> Promise<SgxWebsocketConnection<Configurator>> {
) async throws -> SgxWebsocketConnection<Configurator> {
let key = String(describing: Configurator.self)
return onConnectAndPerformHandshakeHandlers[key]!(configurator).map(on: scheduler) { $0 as! SgxWebsocketConnection<Configurator> }
return try await onConnectAndPerformHandshakeHandlers[key]!(configurator) as! SgxWebsocketConnection<Configurator>
}
}

View File

@ -30,12 +30,12 @@ public class SgxWebsocketConnection<Configurator: SgxWebsocketConfigurator> {
public var auth: RemoteAttestation.Auth { fatalError("Concrete subclass must implement") }
// Subclasses must implement.
func sendRequestAndReadResponse(_ request: Configurator.Request) -> Promise<Configurator.Response> {
func sendRequestAndReadResponse(_ request: Configurator.Request) async throws -> Configurator.Response {
fatalError("Concrete subclass must implement")
}
// Subclasses must implement.
func sendRequestAndReadAllResponses(_ request: Configurator.Request) -> Promise<[Configurator.Response]> {
func sendRequestAndReadAllResponses(_ request: Configurator.Request) async throws -> [Configurator.Response] {
fatalError("Concrete subclass must implement")
}
@ -51,20 +51,17 @@ public class SgxWebsocketConnectionImpl<Configurator: SgxWebsocketConfigurator>:
private let configurator: Configurator
private let _client: Configurator.Client
private let _auth: RemoteAttestation.Auth
private let scheduler: Scheduler
private init(
webSocket: WebSocketPromise,
configurator: Configurator,
client: Configurator.Client,
auth: RemoteAttestation.Auth,
scheduler: Scheduler,
) {
self.webSocket = webSocket
self.configurator = configurator
self._client = client
self._auth = auth
self.scheduler = scheduler
super.init()
}
@ -72,38 +69,29 @@ public class SgxWebsocketConnectionImpl<Configurator: SgxWebsocketConfigurator>:
configurator: Configurator,
auth: RemoteAttestation.Auth,
websocketFactory: WebSocketFactory,
scheduler: Scheduler,
) throws -> Promise<SgxWebsocketConnection<Configurator>> {
) async throws -> SgxWebsocketConnection<Configurator> {
let webSocket = try buildSocket(
configurator: configurator,
auth: auth,
websocketFactory: websocketFactory,
scheduler: scheduler,
)
return firstly(on: scheduler) {
webSocket.waitForResponse()
}.then(on: scheduler) { attestationMessage -> Promise<Configurator.Client> in
do {
let attestationMessage = try await webSocket.waitForResponse().awaitable()
let client = try Configurator.client(
mrenclave: configurator.mrenclave,
attestationMessage: attestationMessage,
currentDate: Date(),
)
return firstly {
webSocket.send(data: client.initialRequest())
return webSocket.waitForResponse()
}.map(on: scheduler) { handshakeResponse -> Configurator.Client in
try client.completeHandshake(handshakeResponse)
return client
}
}.map(on: scheduler) { client -> SgxWebsocketConnection<Configurator> in
webSocket.send(data: client.initialRequest())
let handshakeResponse = try await webSocket.waitForResponse().awaitable()
try client.completeHandshake(handshakeResponse)
return SgxWebsocketConnectionImpl<Configurator>(
webSocket: webSocket,
configurator: configurator,
client: client,
auth: auth,
scheduler: scheduler,
)
}.recover(on: scheduler) { error -> Promise<SgxWebsocketConnection<Configurator>> in
} catch {
Logger.warn("\(type(of: configurator).loggingName): Disconnecting socket after failed handshake: \(error)")
webSocket.disconnect(code: .invalidFramePayloadData)
throw error
@ -114,7 +102,6 @@ public class SgxWebsocketConnectionImpl<Configurator: SgxWebsocketConfigurator>:
configurator: Configurator,
auth: RemoteAttestation.Auth,
websocketFactory: WebSocketFactory,
scheduler: Scheduler,
) throws -> WebSocketPromise {
let authHeaderValue = HttpHeaders.authHeaderValue(username: auth.username, password: auth.password)
let request = WebSocketRequest(
@ -123,7 +110,7 @@ public class SgxWebsocketConnectionImpl<Configurator: SgxWebsocketConfigurator>:
urlQueryItems: nil,
extraHeaders: [HttpHeaders.authHeaderKey: authHeaderValue],
)
guard let webSocketPromise = websocketFactory.webSocketPromise(request: request, callbackScheduler: scheduler) else {
guard let webSocketPromise = websocketFactory.webSocketPromise(request: request, callbackScheduler: DispatchQueue.global()) else {
throw OWSAssertionError("We should always be able to get a web socket from this API.")
}
return webSocketPromise
@ -137,27 +124,21 @@ public class SgxWebsocketConnectionImpl<Configurator: SgxWebsocketConfigurator>:
override public func sendRequestAndReadResponse(
_ request: Configurator.Request,
) -> Promise<Configurator.Response> {
firstly(on: scheduler) { () -> Promise<Data> in
try self.encryptAndSendRequest(request.serializedData())
return self.webSocket.waitForResponse()
}.map(on: scheduler) { encryptedResponse in
let data = try self.decryptResponse(encryptedResponse)
return try Configurator.Response(serializedBytes: data)
}
) async throws -> Configurator.Response {
try self.encryptAndSendRequest(request.serializedData())
let encryptedResponse = try await self.webSocket.waitForResponse().awaitable()
let data = try self.decryptResponse(encryptedResponse)
return try Configurator.Response(serializedBytes: data)
}
override public func sendRequestAndReadAllResponses(
_ request: Configurator.Request,
) -> Promise<[Configurator.Response]> {
firstly(on: scheduler) { () -> Promise<[Data]> in
try self.encryptAndSendRequest(request.serializedData())
return self.webSocket.waitForAllResponses()
}.map(on: scheduler) { encryptedResponses in
try encryptedResponses.map {
let data = try self.decryptResponse($0)
return try Configurator.Response(serializedBytes: data)
}
) async throws -> [Configurator.Response] {
try self.encryptAndSendRequest(request.serializedData())
let encryptedResponses = try await self.webSocket.waitForAllResponses().awaitable()
return try encryptedResponses.map {
let data = try self.decryptResponse($0)
return try Configurator.Response(serializedBytes: data)
}
}
@ -199,16 +180,16 @@ public class MockSgxWebsocketConnection<Configurator: SgxWebsocketConfigurator>:
override public func sendRequestAndReadResponse(
_ request: Configurator.Request,
) -> Promise<Configurator.Response> {
onSendRequestAndReadResponse!(request)
) async throws -> Configurator.Response {
try await onSendRequestAndReadResponse!(request).awaitable()
}
public var onSendRequestAndReadAllResponses: ((Configurator.Request) -> Promise<[Configurator.Response]>)?
override public func sendRequestAndReadAllResponses(
_ request: Configurator.Request,
) -> Promise<[Configurator.Response]> {
onSendRequestAndReadAllResponses!(request)
) async throws -> [Configurator.Response] {
try await onSendRequestAndReadAllResponses!(request).awaitable()
}
public var onDisconnect: (() -> Void)?

View File

@ -20,8 +20,7 @@ public protocol SgxWebsocketConnectionFactory {
/// returned connection is properly disconnected.
func connectAndPerformHandshake<Configurator: SgxWebsocketConfigurator>(
configurator: Configurator,
on scheduler: Scheduler,
) -> Promise<SgxWebsocketConnection<Configurator>>
) async throws -> SgxWebsocketConnection<Configurator>
}
final class SgxWebsocketConnectionFactoryImpl: SgxWebsocketConnectionFactory {
@ -34,18 +33,13 @@ final class SgxWebsocketConnectionFactoryImpl: SgxWebsocketConnectionFactory {
func connectAndPerformHandshake<Configurator: SgxWebsocketConfigurator>(
configurator: Configurator,
on scheduler: Scheduler,
) -> Promise<SgxWebsocketConnection<Configurator>> {
) async throws -> SgxWebsocketConnection<Configurator> {
let websocketFactory = self.websocketFactory
return Promise.wrapAsync {
return try await configurator.fetchAuth()
}.then(on: scheduler) { auth -> Promise<SgxWebsocketConnection<Configurator>> in
return try SgxWebsocketConnectionImpl<Configurator>.connectAndPerformHandshake(
configurator: configurator,
auth: auth,
websocketFactory: websocketFactory,
scheduler: scheduler,
)
}
let auth = try await configurator.fetchAuth()
return try await SgxWebsocketConnectionImpl<Configurator>.connectAndPerformHandshake(
configurator: configurator,
auth: auth,
websocketFactory: websocketFactory,
)
}
}

View File

@ -123,11 +123,12 @@ public protocol SecureValueRecovery {
func currentPinType(transaction: DBReadTransaction) -> SVR.PinType?
/// Loads the users key, if any, from the SVR into the database.
func restoreKeys(pin: String, authMethod: SVR.AuthMethod) -> Guarantee<SVR.RestoreKeysResult>
func restoreKeys(pin: String, authMethod: SVR.AuthMethod) async -> SVR.RestoreKeysResult
/// Backs up the user's master key to SVR.
func backupMasterKey(pin: String, masterKey: MasterKey, authMethod: SVR.AuthMethod) -> Promise<MasterKey>
func backupMasterKey(pin: String, masterKey: MasterKey, authMethod: SVR.AuthMethod) async throws -> MasterKey
@MainActor
func warmCaches()
/// Removes the SVR keys locally from the device, they can still be

View File

@ -239,7 +239,7 @@ public class OWS2FAManager {
pin: pin,
masterKey: masterKey,
authMethod: .implicit,
).awaitable()
)
await db.awaitableWrite { tx in
markEnabled(pin: pin, resetReminderInterval: true, transaction: tx)

View File

@ -15,7 +15,6 @@ struct SVR2ConcurrencyTests {
private let svr: SecureValueRecovery2Impl
private let credentialStorage: SVRAuthCredentialStorageMock
private let queue = DispatchQueue(label: "SVR2ConcurrencyTestsQueue")
private let mockConnectionFactory: MockSgxWebsocketConnectionFactory
private let mockConnection: MockSgxWebsocketConnection<SVR2WebsocketConfigurator>
@ -24,6 +23,7 @@ struct SVR2ConcurrencyTests {
self.credentialStorage = SVRAuthCredentialStorageMock()
mockConnection = MockSgxWebsocketConnection<SVR2WebsocketConfigurator>()
mockConnection.mockEnclave = TSConstants.shared.svr2Enclave
mockConnection.mockAuth = RemoteAttestation.Auth(username: "username", password: "password")
mockConnectionFactory = MockSgxWebsocketConnectionFactory()
@ -43,7 +43,6 @@ struct SVR2ConcurrencyTests {
credentialStorage: credentialStorage,
db: db,
accountKeyStore: accountKeyStore,
scheduler: queue,
storageServiceManager: FakeStorageServiceManager(),
svrLocalStorage: localStorage,
tsAccountManager: MockTSAccountManager(),
@ -58,7 +57,7 @@ struct SVR2ConcurrencyTests {
mockConnectionFactory.setOnConnectAndPerformHandshake { (_: SVR2WebsocketConfigurator) in
#expect(!hasOpenedConnection)
hasOpenedConnection = true
return .value(self.mockConnection)
return self.mockConnection
}
let closeContinuation = CancellableContinuation<Void>()
@ -104,7 +103,7 @@ struct SVR2ConcurrencyTests {
}
let firstMasterKey = MasterKey()
async let firstBackupResult = svr.backupMasterKey(pin: "1234", masterKey: firstMasterKey, authMethod: .implicit).awaitable()
async let firstBackupResult = svr.backupMasterKey(pin: "1234", masterKey: firstMasterKey, authMethod: .implicit)
// Let the first backup succeed and start the expose, then make the second request.
firstBackupFuture.resolve(backupResponse())
@ -113,7 +112,7 @@ struct SVR2ConcurrencyTests {
try await madeRequestContinuations[1].wait()
let secondMasterKey = MasterKey()
async let secondBackupResult = svr.backupMasterKey(pin: "abcd", masterKey: secondMasterKey, authMethod: .implicit).awaitable()
async let secondBackupResult = svr.backupMasterKey(pin: "abcd", masterKey: secondMasterKey, authMethod: .implicit)
// There is a race condition here because the connection closes itself
// after 100ms of inactivity. If the second `backupMasterKey` call is
@ -144,7 +143,7 @@ struct SVR2ConcurrencyTests {
mockConnectionFactory.setOnConnectAndPerformHandshake { (_: SVR2WebsocketConfigurator) in
#expect(!hasOpenedConnection)
hasOpenedConnection = true
return .value(self.mockConnection)
return self.mockConnection
}
let closeContinuation = CancellableContinuation<Void>()
@ -187,10 +186,10 @@ struct SVR2ConcurrencyTests {
}
let firstMasterKey = MasterKey()
async let firstBackupResult = svr.backupMasterKey(pin: "1234", masterKey: firstMasterKey, authMethod: .implicit).awaitable()
async let firstBackupResult = svr.backupMasterKey(pin: "1234", masterKey: firstMasterKey, authMethod: .implicit)
let secondMasterKey = MasterKey()
async let secondBackupResult = svr.backupMasterKey(pin: "abcd", masterKey: secondMasterKey, authMethod: .implicit).awaitable()
async let secondBackupResult = svr.backupMasterKey(pin: "abcd", masterKey: secondMasterKey, authMethod: .implicit)
// In addition to the race condition described above related to the 100ms
// disconnection timeout, there is non-determinism and another race
@ -228,9 +227,11 @@ struct SVR2ConcurrencyTests {
func testWebsocketConnectionFailure() async throws {
let firstMockConnection = MockSgxWebsocketConnection<SVR2WebsocketConfigurator>()
firstMockConnection.mockEnclave = TSConstants.shared.svr2Enclave
firstMockConnection.mockAuth = RemoteAttestation.Auth(username: "username", password: "password")
let secondMockConnection = MockSgxWebsocketConnection<SVR2WebsocketConfigurator>()
secondMockConnection.mockEnclave = TSConstants.shared.svr2Enclave
secondMockConnection.mockAuth = RemoteAttestation.Auth(username: "username2", password: "password2")
var numOpenedConnections = 0
@ -238,12 +239,12 @@ struct SVR2ConcurrencyTests {
numOpenedConnections += 1
switch numOpenedConnections {
case 1:
return .value(firstMockConnection)
return firstMockConnection
case 2:
return .value(secondMockConnection)
return secondMockConnection
default:
Issue.record("Unexpected number of opened connections")
return .init(error: OWSAssertionError(""))
throw OWSAssertionError("")
}
}
@ -253,11 +254,10 @@ struct SVR2ConcurrencyTests {
}
let (firstBackupPromise, firstBackupFuture) = Promise<SVR2Proto_Response>.pending()
// We won't make a first expose request; it will get cancelled because of the failure.
// We also won't make a second backup or expose request.
let (secondBackupPromise, secondBackupFuture) = Promise<SVR2Proto_Response>.pending()
var firstConnectionRequestCount = 0
let madeRequestContinuations = (0..<1).map { i in
let madeRequestContinuations = (0..<2).map { i in
return CancellableContinuation<Void>()
}
firstMockConnection.onSendRequestAndReadResponse = { request in
@ -270,6 +270,10 @@ struct SVR2ConcurrencyTests {
// First backup.
#expect(request.hasBackup)
return firstBackupPromise
case 1:
// Second backup.
#expect(request.hasBackup)
return secondBackupPromise
default:
Issue.record("Unexpected request")
return .init(error: OWSAssertionError(""))
@ -277,19 +281,21 @@ struct SVR2ConcurrencyTests {
}
let firstMasterKey = MasterKey()
async let firstBackupResult = svr.backupMasterKey(pin: "1234", masterKey: firstMasterKey, authMethod: .implicit).awaitable()
async let firstBackupResult = svr.backupMasterKey(pin: "1234", masterKey: firstMasterKey, authMethod: .implicit)
let secondMasterKey = MasterKey()
async let secondBackupResult = svr.backupMasterKey(pin: "abcd", masterKey: secondMasterKey, authMethod: .implicit).awaitable()
async let secondBackupResult = svr.backupMasterKey(pin: "abcd", masterKey: secondMasterKey, authMethod: .implicit)
// See above. In this case, we want both `backupMasterKey` calls to acquire
// the same shared connection so they see the same errors.
try await Task.sleep(nanoseconds: 3.clampedNanoseconds)
let firstBackupError = WebSocketError.closeError(statusCode: 400, closeReason: nil)
firstBackupFuture.reject(firstBackupError)
let backupError = WebSocketError.closeError(statusCode: 400, closeReason: nil)
firstBackupFuture.reject(backupError)
secondBackupFuture.reject(backupError)
try await madeRequestContinuations[0].wait()
try await madeRequestContinuations[1].wait()
do {
_ = try await firstBackupResult
@ -324,7 +330,7 @@ struct SVR2ConcurrencyTests {
}
let thirdMasterKey = MasterKey()
_ = try await svr.backupMasterKey(pin: "zzzz", masterKey: thirdMasterKey, authMethod: .implicit).awaitable()
_ = try await svr.backupMasterKey(pin: "zzzz", masterKey: thirdMasterKey, authMethod: .implicit)
#expect(numOpenedConnections == 2)
}

View File

@ -50,7 +50,6 @@ class SecureValueRecovery2Tests: XCTestCase {
credentialStorage: credentialStorage,
db: db,
accountKeyStore: accountKeyStore,
scheduler: DispatchQueue(label: ""),
storageServiceManager: FakeStorageServiceManager(),
svrLocalStorage: localStorage,
tsAccountManager: mockTSAccountManager,
@ -65,19 +64,21 @@ class SecureValueRecovery2Tests: XCTestCase {
let mockAuth = RemoteAttestation.Auth(username: "username", password: "password")
let oldEnclave = MrEnclave("0000000000000000000000000000000000000000000000000000000000000000")
let oldEnclaveConnection = MockSgxWebsocketConnection<SVR2WebsocketConfigurator>()
oldEnclaveConnection.mockEnclave = oldEnclave
oldEnclaveConnection.mockAuth = mockAuth
let newEnclave = MrEnclave("0101010101010101010101010101010101010101010101010101010101010101")
let newEnclaveConnection = MockSgxWebsocketConnection<SVR2WebsocketConfigurator>()
newEnclaveConnection.mockEnclave = newEnclave
newEnclaveConnection.mockAuth = mockAuth
mockConnectionFactory.setOnConnectAndPerformHandshake { (config: SVR2WebsocketConfigurator) in
switch config.mrenclave.stringValue {
case oldEnclave.stringValue:
return .value(oldEnclaveConnection)
return oldEnclaveConnection
case newEnclave.stringValue:
return .value(newEnclaveConnection)
return newEnclaveConnection
default:
XCTFail("Unexpected enclave connection")
return .init(error: OWSAssertionError(""))
throw OWSAssertionError("")
}
}
@ -148,7 +149,7 @@ class SecureValueRecovery2Tests: XCTestCase {
}
// Kick off the migration.
_ = try await svr.performStartupMigrationsIfNecessary().awaitable()
_ = try await svr.performStartupMigrationsIfNecessary()
XCTAssertEqual(newEnclaveRequestCount, 2)
XCTAssertEqual(oldEnclaveRequestCount, 1)
@ -158,7 +159,7 @@ class SecureValueRecovery2Tests: XCTestCase {
}
// If we try to migrate again, it does nothing because we are at the newest enclave.
_ = try await svr.performStartupMigrationsIfNecessary().awaitable()
_ = try await svr.performStartupMigrationsIfNecessary()
XCTAssertEqual(newEnclaveRequestCount, 2)
XCTAssertEqual(oldEnclaveRequestCount, 1)
}
@ -169,17 +170,19 @@ class SecureValueRecovery2Tests: XCTestCase {
let mockAuth = RemoteAttestation.Auth(username: "username", password: "password")
let oldEnclave = MrEnclave("0000000000000000000000000000000000000000000000000000000000000000")
let oldEnclaveConnection = MockSgxWebsocketConnection<SVR2WebsocketConfigurator>()
oldEnclaveConnection.mockEnclave = oldEnclave
oldEnclaveConnection.mockAuth = mockAuth
let newEnclave = MrEnclave("0101010101010101010101010101010101010101010101010101010101010101")
let newEnclaveConnection = MockSgxWebsocketConnection<SVR2WebsocketConfigurator>()
newEnclaveConnection.mockEnclave = newEnclave
newEnclaveConnection.mockAuth = mockAuth
mockConnectionFactory.setOnConnectAndPerformHandshake { (config: SVR2WebsocketConfigurator) in
switch config.mrenclave.stringValue {
case newEnclave.stringValue:
return .value(newEnclaveConnection)
return newEnclaveConnection
default:
XCTFail("Unexpected enclave connection")
return .init(error: OWSAssertionError(""))
throw OWSAssertionError("")
}
}
@ -234,7 +237,7 @@ class SecureValueRecovery2Tests: XCTestCase {
// NOTE: the old enclave should get no requests, its considered dead.
// Kick off the migration.
_ = try await svr.performStartupMigrationsIfNecessary().awaitable()
_ = try await svr.performStartupMigrationsIfNecessary()
XCTAssertEqual(newEnclaveRequestCount, 2)
@ -243,7 +246,7 @@ class SecureValueRecovery2Tests: XCTestCase {
}
// If we try to migrate again, it does nothing because we are at the newest enclave.
_ = try await svr.performStartupMigrationsIfNecessary().awaitable()
_ = try await svr.performStartupMigrationsIfNecessary()
XCTAssertEqual(newEnclaveRequestCount, 2)
}