Asyncify SVR2
This commit is contained in:
parent
ee6cd21fb0
commit
1105ac39a3
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)?
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user