Convert rate limit apis to WebSocket.

This commit is contained in:
Cody Henthorne
2025-03-11 15:22:50 -04:00
committed by Greyson Parrelli
parent 86b2fe9742
commit 61a8636217
14 changed files with 149 additions and 63 deletions

View File

@@ -51,6 +51,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations
import org.whispersystems.signalservice.api.keys.KeysApi
import org.whispersystems.signalservice.api.link.LinkDeviceApi
import org.whispersystems.signalservice.api.payments.PaymentsApi
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi
import org.whispersystems.signalservice.api.registration.RegistrationApi
import org.whispersystems.signalservice.api.services.DonationsService
import org.whispersystems.signalservice.api.services.ProfileService
@@ -326,6 +327,9 @@ object AppDependencies {
val cdsApi: CdsApi
get() = networkModule.cdsApi
val rateLimitChallengeApi: RateLimitChallengeApi
get() = networkModule.rateLimitChallengeApi
@JvmStatic
val okHttpClient: OkHttpClient
get() = networkModule.okHttpClient
@@ -401,5 +405,6 @@ object AppDependencies {
fun provideCallingApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket, pushServiceSocket: PushServiceSocket): CallingApi
fun providePaymentsApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): PaymentsApi
fun provideCdsApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): CdsApi
fun provideRateLimitChallengeApi(authWebSocket: SignalWebSocket.AuthenticatedWebSocket): RateLimitChallengeApi
}
}

View File

@@ -92,6 +92,7 @@ import org.whispersystems.signalservice.api.link.LinkDeviceApi;
import org.whispersystems.signalservice.api.payments.PaymentsApi;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi;
import org.whispersystems.signalservice.api.registration.RegistrationApi;
import org.whispersystems.signalservice.api.services.DonationsService;
import org.whispersystems.signalservice.api.services.ProfileService;
@@ -516,6 +517,11 @@ public class ApplicationDependencyProvider implements AppDependencies.Provider {
return new CdsApi(authWebSocket);
}
@Override
public @NonNull RateLimitChallengeApi provideRateLimitChallengeApi(@NonNull SignalWebSocket.AuthenticatedWebSocket authWebSocket) {
return new RateLimitChallengeApi(authWebSocket);
}
@VisibleForTesting
static class DynamicCredentialsProvider implements CredentialsProvider {

View File

@@ -36,6 +36,7 @@ import org.whispersystems.signalservice.api.keys.KeysApi
import org.whispersystems.signalservice.api.link.LinkDeviceApi
import org.whispersystems.signalservice.api.payments.PaymentsApi
import org.whispersystems.signalservice.api.push.TrustStore
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi
import org.whispersystems.signalservice.api.registration.RegistrationApi
import org.whispersystems.signalservice.api.services.DonationsService
import org.whispersystems.signalservice.api.services.ProfileService
@@ -177,6 +178,10 @@ class NetworkDependenciesModule(
provider.provideCdsApi(authWebSocket)
}
val rateLimitChallengeApi: RateLimitChallengeApi by lazy {
provider.provideRateLimitChallengeApi(authWebSocket)
}
val okHttpClient: OkHttpClient by lazy {
OkHttpClient.Builder()
.addInterceptor(StandardUserAgentInterceptor())

View File

@@ -4,13 +4,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.SignalNetwork;
import org.thoughtcrime.securesms.ratelimit.RateLimitUtil;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.thoughtcrime.securesms.util.ExceptionHelper;
import org.whispersystems.signalservice.api.NetworkResultUtil;
import java.util.concurrent.TimeUnit;
@@ -51,7 +52,7 @@ public final class SubmitRateLimitPushChallengeJob extends BaseJob {
@Override
protected void onRun() throws Exception {
AppDependencies.getSignalServiceAccountManager().submitRateLimitPushChallenge(challenge);
NetworkResultUtil.toBasicLegacy(SignalNetwork.rateLimitChallenge().submitPushChallenge(challenge));
SignalStore.rateLimit().onProofAccepted();
EventBus.getDefault().post(new SuccessEvent());
RateLimitUtil.retryAllRateLimitedMessages(context);
@@ -59,7 +60,7 @@ public final class SubmitRateLimitPushChallengeJob extends BaseJob {
@Override
protected boolean onShouldRetry(@NonNull Exception e) {
return e instanceof PushNetworkException;
return ExceptionHelper.isRetryableIOException(e);
}
@Override

View File

@@ -14,6 +14,7 @@ import org.whispersystems.signalservice.api.cds.CdsApi
import org.whispersystems.signalservice.api.keys.KeysApi
import org.whispersystems.signalservice.api.link.LinkDeviceApi
import org.whispersystems.signalservice.api.payments.PaymentsApi
import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi
import org.whispersystems.signalservice.api.storage.StorageServiceApi
import org.whispersystems.signalservice.api.username.UsernameApi
@@ -51,6 +52,11 @@ object SignalNetwork {
val payments: PaymentsApi
get() = AppDependencies.paymentsApi
@JvmStatic
@get:JvmName("rateLimitChallenge")
val rateLimitChallenge: RateLimitChallengeApi
get() = AppDependencies.rateLimitChallengeApi
val storageService: StorageServiceApi
get() = AppDependencies.storageServiceApi

View File

@@ -16,11 +16,11 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobs.SubmitRateLimitPushChallengeJob.SuccessEvent
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.SignalNetwork
import org.thoughtcrime.securesms.notifications.v2.ConversationId
import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException
import java.io.IOException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.time.Duration
@@ -42,25 +42,26 @@ object ProofRequiredExceptionHandler {
fun handle(context: Context, proofRequired: ProofRequiredException, recipient: Recipient?, threadId: Long, messageId: Long): Result {
Log.w(TAG, "[Proof Required] Options: ${proofRequired.options}")
try {
if (ProofRequiredException.Option.PUSH_CHALLENGE in proofRequired.options) {
AppDependencies.signalServiceAccountManager.requestRateLimitPushChallenge()
Log.i(TAG, "[Proof Required] Successfully requested a challenge. Waiting up to $PUSH_CHALLENGE_TIMEOUT ms.")
if (ProofRequiredException.Option.PUSH_CHALLENGE in proofRequired.options) {
when (val result = SignalNetwork.rateLimitChallenge.requestPushChallenge()) {
is NetworkResult.Success -> {
Log.i(TAG, "[Proof Required] Successfully requested a challenge. Waiting up to $PUSH_CHALLENGE_TIMEOUT ms.")
val success = PushChallengeRequest(PUSH_CHALLENGE_TIMEOUT).blockUntilSuccess()
val success = PushChallengeRequest(PUSH_CHALLENGE_TIMEOUT).blockUntilSuccess()
if (success) {
Log.i(TAG, "Successfully responded to a push challenge. Retrying message send.")
return Result.RETRY_NOW
} else {
Log.w(TAG, "Failed to respond to the push challeng in time. Falling back.")
if (success) {
Log.i(TAG, "Successfully responded to a push challenge. Retrying message send.")
return Result.RETRY_NOW
} else {
Log.w(TAG, "Failed to respond to the push challeng in time. Falling back.")
}
}
is NetworkResult.StatusCodeError -> Log.w(TAG, "[Proof Required] Could not request a push challenge (${result.code}). Falling back.", result.exception)
is NetworkResult.NetworkError -> {
Log.w(TAG, "[Proof Required] Network error when requesting push challenge. Retrying later.")
return Result.RETRY_LATER
}
is NetworkResult.ApplicationError -> throw result.throwable
}
} catch (e: NonSuccessfulResponseCodeException) {
Log.w(TAG, "[Proof Required] Could not request a push challenge (${e.code}). Falling back.", e)
} catch (e: IOException) {
Log.w(TAG, "[Proof Required] Network error when requesting push challenge. Retrying later.")
return Result.RETRY_LATER
}
if (messageId > 0) {

View File

@@ -19,12 +19,13 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.SignalNetwork;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ExceptionHelper;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
import org.whispersystems.signalservice.api.NetworkResultUtil;
import java.io.IOException;
@@ -98,12 +99,16 @@ public class RecaptchaProofActivity extends PassphraseRequiredActivity {
try {
for (int i = 0; i < 3; i++) {
try {
AppDependencies.getSignalServiceAccountManager().submitRateLimitRecaptchaChallenge(challenge, token);
NetworkResultUtil.toBasicLegacy(SignalNetwork.rateLimitChallenge().submitCaptchaChallenge(challenge, token));
RateLimitUtil.retryAllRateLimitedMessages(this);
Log.i(TAG, "Successfully completed reCAPTCHA.");
return new TokenResult(true, true);
} catch (PushNetworkException e) {
Log.w(TAG, "Network error during submission. Retrying.", e);
} catch (IOException e) {
if (ExceptionHelper.isRetryableIOException(e)) {
Log.w(TAG, "Network error during submission. Retrying.", e);
} else {
throw e;
}
}
}
} catch (IOException e) {

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
@file:JvmName("ExceptionHelper")
package org.thoughtcrime.securesms.util
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
import java.io.IOException
/**
* Returns true if this exception is a retryable I/O Exception. Helpful for jobs.
*/
fun Throwable.isRetryableIOException(): Boolean {
return this is IOException && this !is NonSuccessfulResponseCodeException
}