mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Add UX for handling CDS rate limits.
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package org.whispersystems.signalservice.api.push.exceptions;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that something about our request was wrong. Could be:
|
||||
* - Over 50k new contacts
|
||||
* - Missing version byte prefix
|
||||
* - Missing credentials
|
||||
* - E164s are not a multiple of 8 bytes
|
||||
* - Something else?
|
||||
*/
|
||||
public class CdsiInvalidArgumentException extends NonSuccessfulResponseCodeException {
|
||||
public CdsiInvalidArgumentException() {
|
||||
super(4003);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.whispersystems.signalservice.api.push.exceptions;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that you provided a bad token to CDSI.
|
||||
*/
|
||||
public class CdsiInvalidTokenException extends NonSuccessfulResponseCodeException {
|
||||
public CdsiInvalidTokenException() {
|
||||
super(4101);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.whispersystems.signalservice.api.push.exceptions;
|
||||
|
||||
/**
|
||||
* A 4008 responses from CDSI indicating we've exhausted our quota.
|
||||
*/
|
||||
public class CdsiResourceExhaustedException extends NonSuccessfulResponseCodeException {
|
||||
|
||||
private final int retryAfterSeconds;
|
||||
|
||||
public CdsiResourceExhaustedException(int retryAfterSeconds) {
|
||||
super(4008);
|
||||
this.retryAfterSeconds = retryAfterSeconds;
|
||||
}
|
||||
|
||||
public int getRetryAfterSeconds() {
|
||||
return retryAfterSeconds;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,10 @@ import org.signal.libsignal.cds2.Cds2CommunicationFailureException;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.signal.libsignal.protocol.util.Pair;
|
||||
import org.whispersystems.signalservice.api.push.TrustStore;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidArgumentException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException;
|
||||
import org.whispersystems.signalservice.api.util.Tls12SocketFactory;
|
||||
import org.whispersystems.signalservice.api.util.TlsProxySocketFactory;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
|
||||
@@ -22,9 +25,9 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -90,6 +93,9 @@ final class CdsiSocket {
|
||||
.addHeader("Authorization", basicAuth(username, password))
|
||||
.build();
|
||||
|
||||
AtomicInteger retryAfterSeconds = new AtomicInteger(0);
|
||||
|
||||
|
||||
WebSocket webSocket = okhttp.newWebSocket(request, new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
@@ -128,6 +134,12 @@ final class CdsiSocket {
|
||||
|
||||
case WAITING_FOR_TOKEN:
|
||||
ClientResponse tokenResponse = ClientResponse.parseFrom(client.establishedRecv(bytes.toByteArray()));
|
||||
|
||||
if (tokenResponse.getRetryAfterSecs() > 0) {
|
||||
Log.w(TAG, "Got a retry-after (" + tokenResponse.getRetryAfterSecs() + "), meaning we hit the rate limit. (WAITING_FOR_TOKEN)");
|
||||
throw new CdsiResourceExhaustedException(tokenResponse.getRetryAfterSecs());
|
||||
}
|
||||
|
||||
if (tokenResponse.getToken().isEmpty()) {
|
||||
throw new IOException("No token! Cannot continue!");
|
||||
}
|
||||
@@ -143,6 +155,13 @@ final class CdsiSocket {
|
||||
break;
|
||||
|
||||
case WAITING_FOR_RESPONSE:
|
||||
ClientResponse dataResponse = ClientResponse.parseFrom(client.establishedRecv(bytes.toByteArray()));
|
||||
|
||||
if (dataResponse.getRetryAfterSecs() > 0) {
|
||||
Log.w(TAG, "Got a retry-after (" + dataResponse.getRetryAfterSecs() + "), meaning we hit the rate limit. (WAITING_FOR_RESPONSE)");
|
||||
throw new CdsiResourceExhaustedException(dataResponse.getRetryAfterSecs());
|
||||
}
|
||||
|
||||
emitter.onNext(ClientResponse.parseFrom(client.establishedRecv(bytes.toByteArray())));
|
||||
break;
|
||||
|
||||
@@ -172,7 +191,13 @@ final class CdsiSocket {
|
||||
Log.w(TAG, "Remote side is closing with non-normal code " + code);
|
||||
webSocket.close(1000, "Remote closed with code " + code);
|
||||
stage.set(Stage.FAILED);
|
||||
emitter.tryOnError(new NonSuccessfulResponseCodeException(code));
|
||||
if (code == 4003) {
|
||||
emitter.tryOnError(new CdsiInvalidArgumentException());
|
||||
} else if (code == 4101) {
|
||||
emitter.tryOnError(new CdsiInvalidTokenException());
|
||||
} else {
|
||||
emitter.tryOnError(new NonSuccessfulResponseCodeException(code));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,14 +40,14 @@ public final class CdsiV2Service {
|
||||
private static final UUID EMPTY_UUID = new UUID(0, 0);
|
||||
private static final int RESPONSE_ITEM_SIZE = 8 + 16 + 16; // 1 uint64 + 2 UUIDs
|
||||
|
||||
private final CdsiSocket cdshSocket;
|
||||
private final CdsiSocket cdsiSocket;
|
||||
|
||||
public CdsiV2Service(SignalServiceConfiguration configuration, String mrEnclave) {
|
||||
this.cdshSocket = new CdsiSocket(configuration, mrEnclave);
|
||||
this.cdsiSocket = new CdsiSocket(configuration, mrEnclave);
|
||||
}
|
||||
|
||||
public Single<ServiceResponse<Response>> getRegisteredUsers(String username, String password, Request request, Consumer<byte[]> tokenSaver) {
|
||||
return cdshSocket
|
||||
return cdsiSocket
|
||||
.connect(username, password, buildClientRequest(request), tokenSaver)
|
||||
.map(CdsiV2Service::parseEntries)
|
||||
.collect(Collectors.toList())
|
||||
|
||||
Reference in New Issue
Block a user