diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt index c108c17d7e..51a93a7c76 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResetSvrGuessCountJob.kt @@ -9,6 +9,7 @@ import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.BuildConfig import org.thoughtcrime.securesms.dependencies.AppDependencies import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.Job.Result import org.thoughtcrime.securesms.jobmanager.JsonJobData import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.keyvalue.SignalStore @@ -20,6 +21,7 @@ import org.whispersystems.signalservice.api.svr.SecureValueRecovery.PinChangeSes import org.whispersystems.signalservice.internal.push.AuthCredentials import kotlin.concurrent.withLock import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.milliseconds /** * Attempts to reset the guess on the SVR PIN. Intended to be enqueued after a successful restore. @@ -163,6 +165,11 @@ class ResetSvrGuessCountJob private constructor( Log.w(TAG, "Failed to expose the backup. Giving up. $svr") Result.success() } + is BackupResponse.RateLimited -> { + val backoff = response.retryAfter ?: defaultBackoff().milliseconds + Log.w(TAG, "Hit rate limit. Retrying in $backoff") + Result.retry(backoff.inWholeMilliseconds) + } is BackupResponse.NetworkError -> { Log.w(TAG, "Hit a network error. Retrying. $svr", response.exception) Result.retry(defaultBackoff()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt index 7168191d7f..85473a5457 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr2MirrorJob.kt @@ -19,6 +19,7 @@ import org.whispersystems.signalservice.api.svr.SecureValueRecovery.PinChangeSes import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2 import kotlin.concurrent.withLock import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.milliseconds /** * Ensures a user's SVR data is written to SVR2. @@ -109,6 +110,11 @@ class Svr2MirrorJob private constructor(parameters: Parameters, private var seri Log.w(TAG, "Failed to expose the backup. Giving up.") Result.success() } + is BackupResponse.RateLimited -> { + val backoff = response.retryAfter ?: defaultBackoff().milliseconds + Log.w(TAG, "Hit rate limit. Retrying in $backoff") + Result.retry(backoff.inWholeMilliseconds) + } is BackupResponse.NetworkError -> { Log.w(TAG, "Hit a network error. Retrying.", response.exception) Result.retry(defaultBackoff()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr3MirrorJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr3MirrorJob.kt index a2d5b90cf6..c7d69000a8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr3MirrorJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/Svr3MirrorJob.kt @@ -18,6 +18,7 @@ import org.whispersystems.signalservice.api.svr.SecureValueRecovery.PinChangeSes import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV3 import kotlin.concurrent.withLock import kotlin.time.Duration.Companion.days +import kotlin.time.Duration.Companion.milliseconds /** * Ensures a user's SVR data is written to SVR3. @@ -93,6 +94,11 @@ class Svr3MirrorJob private constructor(parameters: Parameters, private var seri Result.retry(defaultBackoff()) } } + is BackupResponse.RateLimited -> { + val backoff = response.retryAfter ?: defaultBackoff().milliseconds + Log.w(TAG, "Hit rate limit. Retrying in $backoff") + Result.retry(backoff.inWholeMilliseconds) + } BackupResponse.EnclaveNotFound -> { Log.w(TAG, "Could not find the enclave. Giving up.") Result.success() diff --git a/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt index 821abbaf30..87221eefc4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/pin/SvrRepository.kt @@ -257,6 +257,7 @@ object SvrRepository { BackupResponse.ExposeFailure -> it is BackupResponse.NetworkError -> it BackupResponse.ServerRejected -> it + is BackupResponse.RateLimited -> it BackupResponse.EnclaveNotFound -> null is BackupResponse.Success -> null } diff --git a/demo/registration/src/main/java/org/signal/registration/sample/dependencies/RealNetworkController.kt b/demo/registration/src/main/java/org/signal/registration/sample/dependencies/RealNetworkController.kt index ed71ff6059..3b9048c3b7 100644 --- a/demo/registration/src/main/java/org/signal/registration/sample/dependencies/RealNetworkController.kt +++ b/demo/registration/src/main/java/org/signal/registration/sample/dependencies/RealNetworkController.kt @@ -23,6 +23,7 @@ import org.signal.registration.NetworkController.RegisterAccountError import org.signal.registration.NetworkController.RegisterAccountResponse import org.signal.registration.NetworkController.RegistrationLockResponse import org.signal.registration.NetworkController.RegistrationNetworkResult +import org.signal.registration.NetworkController.RegistrationNetworkResult.* import org.signal.registration.NetworkController.RequestVerificationCodeError import org.signal.registration.NetworkController.SessionMetadata import org.signal.registration.NetworkController.SubmitVerificationCodeError @@ -465,27 +466,30 @@ class RealNetworkController( when (response) { is BackupResponse.Success -> { Log.i(TAG, "[backupMasterKeyToSvr] Successfully backed up master key to SVR2") - RegistrationNetworkResult.Success(Unit) + Success(Unit) } is BackupResponse.ApplicationError -> { Log.w(TAG, "[backupMasterKeyToSvr] Application error", response.exception) - RegistrationNetworkResult.ApplicationError(response.exception) + ApplicationError(response.exception) } is BackupResponse.NetworkError -> { Log.w(TAG, "[backupMasterKeyToSvr] Network error", response.exception) - RegistrationNetworkResult.NetworkError(response.exception) + NetworkError(response.exception) } is BackupResponse.EnclaveNotFound -> { Log.w(TAG, "[backupMasterKeyToSvr] Enclave not found") - RegistrationNetworkResult.Failure(NetworkController.BackupMasterKeyError.EnclaveNotFound) + Failure(NetworkController.BackupMasterKeyError.EnclaveNotFound) } is BackupResponse.ExposeFailure -> { Log.w(TAG, "[backupMasterKeyToSvr] Expose failure -- per spec, treat as success.") - RegistrationNetworkResult.Success(Unit) + Success(Unit) } is BackupResponse.ServerRejected -> { Log.w(TAG, "[backupMasterKeyToSvr] Server rejected") - RegistrationNetworkResult.NetworkError(IOException("Server rejected backup request")) + NetworkError(IOException("Server rejected backup request")) + } + is BackupResponse.RateLimited -> { + NetworkError(IOException("Rate limited")) } } } catch (e: IOException) { diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecovery.kt b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecovery.kt index 13e3c34046..c18095ff07 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecovery.kt +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecovery.kt @@ -9,6 +9,7 @@ import org.signal.core.models.MasterKey import org.whispersystems.signalservice.internal.push.AuthCredentials import java.io.IOException import kotlin.jvm.Throws +import kotlin.time.Duration interface SecureValueRecovery { @@ -88,6 +89,9 @@ interface SecureValueRecovery { /** There as a network error. Not a bad response, but rather interference or some other inability to make a network request. */ data class NetworkError(val exception: IOException) : BackupResponse() + /** The client request was rate-limited. */ + data class RateLimited(val retryAfter: Duration?) : BackupResponse() + /** Something went wrong when making the request that is related to application logic. */ data class ApplicationError(val exception: Throwable) : BackupResponse() } diff --git a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV2.kt b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV2.kt index b801d0eccf..7094ff968f 100644 --- a/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV2.kt +++ b/lib/libsignal-service/src/main/java/org/whispersystems/signalservice/api/svr/SecureValueRecoveryV2.kt @@ -30,6 +30,7 @@ import org.whispersystems.signalservice.internal.push.AuthCredentials import org.whispersystems.signalservice.internal.util.JsonUtil import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage import java.io.IOException +import kotlin.time.Duration.Companion.seconds import org.signal.svr2.proto.BackupResponse as ProtoBackupResponse import org.signal.svr2.proto.ExposeResponse as ProtoExposeResponse import org.signal.svr2.proto.RestoreResponse as ProtoRestoreResponse @@ -220,10 +221,10 @@ class SecureValueRecoveryV2( } } catch (e: NonSuccessfulResponseCodeException) { Log.w(TAG, "[Set] Failed with a non-successful response code exception!", e) - if (e.code == 404) { - BackupResponse.EnclaveNotFound - } else { - BackupResponse.ApplicationError(e) + when (e.code) { + 404 -> BackupResponse.EnclaveNotFound + 429 -> BackupResponse.RateLimited(e.headers["retry-after"]?.toLongOrNull()?.seconds) + else -> BackupResponse.ApplicationError(e) } } catch (e: IOException) { Log.w(TAG, "[Set] Failed with a network exception!", e)