mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-25 05:27:42 +00:00
Retrieve profiles in parallel.
This commit is contained in:
@@ -30,6 +30,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
import org.whispersystems.signalservice.api.kbs.MasterKey;
|
||||
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite;
|
||||
import org.whispersystems.signalservice.api.push.ContactTokenDetails;
|
||||
@@ -78,6 +79,7 @@ import java.security.KeyStore;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SignatureException;
|
||||
import java.sql.Time;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -89,6 +91,9 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static org.whispersystems.signalservice.internal.push.ProvisioningProtos.ProvisionMessage;
|
||||
import static org.whispersystems.signalservice.internal.push.ProvisioningProtos.ProvisioningVersion;
|
||||
@@ -705,9 +710,22 @@ public class SignalServiceAccountManager {
|
||||
}
|
||||
|
||||
public Optional<ProfileKeyCredential> resolveProfileKeyCredential(UUID uuid, ProfileKey profileKey)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, VerificationFailedException
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
return this.pushServiceSocket.retrieveVersionedProfileAndCredential(uuid, profileKey, Optional.absent()).getProfileKeyCredential();
|
||||
try {
|
||||
ProfileAndCredential credential = this.pushServiceSocket.retrieveVersionedProfileAndCredential(uuid, profileKey, Optional.absent()).get(10, TimeUnit.SECONDS);
|
||||
return credential.getProfileKeyCredential();
|
||||
} catch (InterruptedException | TimeoutException e) {
|
||||
throw new PushNetworkException(e);
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof NonSuccessfulResponseCodeException) {
|
||||
throw (NonSuccessfulResponseCodeException) e.getCause();
|
||||
} else if (e.getCause() instanceof PushNetworkException) {
|
||||
throw (PushNetworkException) e.getCause();
|
||||
} else {
|
||||
throw new PushNetworkException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setUsername(String username) throws IOException {
|
||||
|
||||
@@ -23,6 +23,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
|
||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes;
|
||||
import org.whispersystems.signalservice.internal.push.AttachmentV3UploadAttributes;
|
||||
@@ -190,64 +192,63 @@ public class SignalServiceMessagePipe {
|
||||
});
|
||||
}
|
||||
|
||||
public ProfileAndCredential getProfile(SignalServiceAddress address,
|
||||
Optional<ProfileKey> profileKey,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
SignalServiceProfile.RequestType requestType)
|
||||
public ListenableFuture<ProfileAndCredential> getProfile(SignalServiceAddress address,
|
||||
Optional<ProfileKey> profileKey,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
SignalServiceProfile.RequestType requestType)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
List<String> headers = new LinkedList<>();
|
||||
List<String> headers = new LinkedList<>();
|
||||
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
headers.add("Unidentified-Access-Key:" + Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()));
|
||||
}
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
headers.add("Unidentified-Access-Key:" + Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()));
|
||||
}
|
||||
|
||||
Optional<UUID> uuid = address.getUuid();
|
||||
SecureRandom random = new SecureRandom();
|
||||
ProfileKeyCredentialRequestContext requestContext = null;
|
||||
Optional<UUID> uuid = address.getUuid();
|
||||
SecureRandom random = new SecureRandom();
|
||||
ProfileKeyCredentialRequestContext requestContext = null;
|
||||
|
||||
WebSocketRequestMessage.Builder builder = WebSocketRequestMessage.newBuilder()
|
||||
.setId(random.nextLong())
|
||||
.setVerb("GET")
|
||||
.addAllHeaders(headers);
|
||||
WebSocketRequestMessage.Builder builder = WebSocketRequestMessage.newBuilder()
|
||||
.setId(random.nextLong())
|
||||
.setVerb("GET")
|
||||
.addAllHeaders(headers);
|
||||
|
||||
if (uuid.isPresent() && profileKey.isPresent()) {
|
||||
UUID target = uuid.get();
|
||||
ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(target);
|
||||
String version = profileKeyIdentifier.serialize();
|
||||
if (uuid.isPresent() && profileKey.isPresent()) {
|
||||
UUID target = uuid.get();
|
||||
ProfileKeyVersion profileKeyIdentifier = profileKey.get().getProfileKeyVersion(target);
|
||||
String version = profileKeyIdentifier.serialize();
|
||||
|
||||
if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) {
|
||||
requestContext = clientZkProfile.createProfileKeyCredentialRequestContext(random, target, profileKey.get());
|
||||
if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) {
|
||||
requestContext = clientZkProfile.createProfileKeyCredentialRequestContext(random, target, profileKey.get());
|
||||
|
||||
ProfileKeyCredentialRequest request = requestContext.getRequest();
|
||||
String credentialRequest = Hex.toStringCondensed(request.serialize());
|
||||
ProfileKeyCredentialRequest request = requestContext.getRequest();
|
||||
String credentialRequest = Hex.toStringCondensed(request.serialize());
|
||||
|
||||
builder.setPath(String.format("/v1/profile/%s/%s/%s", target, version, credentialRequest));
|
||||
} else {
|
||||
builder.setPath(String.format("/v1/profile/%s/%s", target, version));
|
||||
}
|
||||
builder.setPath(String.format("/v1/profile/%s/%s/%s", target, version, credentialRequest));
|
||||
} else {
|
||||
builder.setPath(String.format("/v1/profile/%s", address.getIdentifier()));
|
||||
builder.setPath(String.format("/v1/profile/%s/%s", target, version));
|
||||
}
|
||||
} else {
|
||||
builder.setPath(String.format("/v1/profile/%s", address.getIdentifier()));
|
||||
}
|
||||
|
||||
WebSocketRequestMessage requestMessage = builder.build();
|
||||
final ProfileKeyCredentialRequestContext finalRequestContext = requestContext;
|
||||
WebSocketRequestMessage requestMessage = builder.build();
|
||||
|
||||
WebsocketResponse response = websocket.sendRequest(requestMessage).get(10, TimeUnit.SECONDS);
|
||||
|
||||
if (response.getStatus() < 200 || response.getStatus() >= 300) {
|
||||
throw new IOException("Non-successful response: " + response.getStatus());
|
||||
return FutureTransformers.map(websocket.sendRequest(requestMessage), response -> {
|
||||
if (response.getStatus() == 404) {
|
||||
throw new NotFoundException("Not found");
|
||||
} else if (response.getStatus() < 200 || response.getStatus() >= 300) {
|
||||
throw new NonSuccessfulResponseCodeException("Non-successful response: " + response.getStatus());
|
||||
}
|
||||
|
||||
SignalServiceProfile signalServiceProfile = JsonUtil.fromJson(response.getBody(), SignalServiceProfile.class);
|
||||
ProfileKeyCredential profileKeyCredential = requestContext != null && signalServiceProfile.getProfileKeyCredentialResponse() != null
|
||||
? clientZkProfile.receiveProfileKeyCredential(requestContext, signalServiceProfile.getProfileKeyCredentialResponse())
|
||||
: null;
|
||||
ProfileKeyCredential profileKeyCredential = finalRequestContext != null && signalServiceProfile.getProfileKeyCredentialResponse() != null
|
||||
? clientZkProfile.receiveProfileKeyCredential(finalRequestContext, signalServiceProfile.getProfileKeyCredentialResponse())
|
||||
: null;
|
||||
|
||||
return new ProfileAndCredential(signalServiceProfile, requestType, Optional.fromNullable(profileKeyCredential));
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException | VerificationFailedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AttachmentV2UploadAttributes getAttachmentV2UploadAttributes() throws IOException {
|
||||
|
||||
@@ -35,6 +35,8 @@ import org.whispersystems.signalservice.internal.push.SignalServiceEnvelopeEntit
|
||||
import org.whispersystems.signalservice.internal.sticker.StickerProtos;
|
||||
import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.signalservice.internal.util.concurrent.FutureTransformers;
|
||||
import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
|
||||
import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -120,11 +122,10 @@ public class SignalServiceMessageReceiver {
|
||||
return retrieveAttachment(pointer, destination, maxSizeBytes, null);
|
||||
}
|
||||
|
||||
public ProfileAndCredential retrieveProfile(SignalServiceAddress address,
|
||||
Optional<ProfileKey> profileKey,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
SignalServiceProfile.RequestType requestType)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, VerificationFailedException
|
||||
public ListenableFuture<ProfileAndCredential> retrieveProfile(SignalServiceAddress address,
|
||||
Optional<ProfileKey> profileKey,
|
||||
Optional<UnidentifiedAccess> unidentifiedAccess,
|
||||
SignalServiceProfile.RequestType requestType)
|
||||
{
|
||||
Optional<UUID> uuid = address.getUuid();
|
||||
|
||||
@@ -132,14 +133,18 @@ public class SignalServiceMessageReceiver {
|
||||
if (requestType == SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL) {
|
||||
return socket.retrieveVersionedProfileAndCredential(uuid.get(), profileKey.get(), unidentifiedAccess);
|
||||
} else {
|
||||
return new ProfileAndCredential(socket.retrieveVersionedProfile(uuid.get(), profileKey.get(), unidentifiedAccess),
|
||||
SignalServiceProfile.RequestType.PROFILE,
|
||||
Optional.absent());
|
||||
return FutureTransformers.map(socket.retrieveVersionedProfile(uuid.get(), profileKey.get(), unidentifiedAccess), profile -> {
|
||||
return new ProfileAndCredential(profile,
|
||||
SignalServiceProfile.RequestType.PROFILE,
|
||||
Optional.absent());
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return new ProfileAndCredential(socket.retrieveProfile(address, unidentifiedAccess),
|
||||
SignalServiceProfile.RequestType.PROFILE,
|
||||
Optional.absent());
|
||||
return FutureTransformers.map(socket.retrieveProfile(address, unidentifiedAccess), profile -> {
|
||||
return new ProfileAndCredential(profile,
|
||||
SignalServiceProfile.RequestType.PROFILE,
|
||||
Optional.absent());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1226,7 +1226,7 @@ public class SignalServiceMessageSender {
|
||||
Log.w(TAG, e);
|
||||
results.add(SendMessageResult.identityFailure(recipient, ((UntrustedIdentityException) e.getCause()).getIdentityKey()));
|
||||
} else if (e.getCause() instanceof UnregisteredUserException) {
|
||||
Log.w(TAG, e);
|
||||
Log.w(TAG, "Found unregistered user.");
|
||||
results.add(SendMessageResult.unregisteredFailure(recipient));
|
||||
} else if (e.getCause() instanceof PushNetworkException) {
|
||||
Log.w(TAG, e);
|
||||
|
||||
@@ -90,6 +90,9 @@ import org.whispersystems.signalservice.internal.util.BlacklistingTrustManager;
|
||||
import org.whispersystems.signalservice.internal.util.Hex;
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil;
|
||||
import org.whispersystems.signalservice.internal.util.Util;
|
||||
import org.whispersystems.signalservice.internal.util.concurrent.FutureTransformers;
|
||||
import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
|
||||
import org.whispersystems.signalservice.internal.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -113,6 +116,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
@@ -120,6 +124,7 @@ import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.ConnectionSpec;
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.Dns;
|
||||
@@ -280,9 +285,9 @@ public class PushServiceSocket {
|
||||
}
|
||||
|
||||
public VerifyAccountResponse verifyAccountCode(String verificationCode, String signalingKey, int registrationId, boolean fetchesMessages,
|
||||
String pin, String registrationLock,
|
||||
byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess,
|
||||
SignalServiceProfile.Capabilities capabilities)
|
||||
String pin, String registrationLock,
|
||||
byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess,
|
||||
SignalServiceProfile.Capabilities capabilities)
|
||||
throws IOException
|
||||
{
|
||||
AccountAttributes signalingKeyEntity = new AccountAttributes(signalingKey, registrationId, fetchesMessages, pin, registrationLock, unidentifiedAccessKey, unrestrictedUnidentifiedAccess, capabilities);
|
||||
@@ -376,6 +381,15 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
public Future<SendMessageResponse> submitMessage(OutgoingPushMessageList bundle, Optional<UnidentifiedAccess> unidentifiedAccess) {
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", JsonUtil.toJson(bundle), NO_HEADERS, unidentifiedAccess);
|
||||
|
||||
return FutureTransformers.map(response, body -> {
|
||||
return body == null ? new SendMessageResponse(false)
|
||||
: JsonUtil.fromJson(body, SendMessageResponse.class);
|
||||
});
|
||||
}
|
||||
|
||||
public List<SignalServiceEnvelopeEntity> getMessages() throws IOException {
|
||||
String responseText = makeServiceRequest(String.format(MESSAGE_PATH, ""), "GET", null);
|
||||
return JsonUtil.fromJson(responseText, SignalServiceEnvelopeEntityList.class).getMessages();
|
||||
@@ -398,17 +412,17 @@ public class PushServiceSocket {
|
||||
|
||||
for (PreKeyRecord record : records) {
|
||||
PreKeyEntity entity = new PreKeyEntity(record.getId(),
|
||||
record.getKeyPair().getPublicKey());
|
||||
record.getKeyPair().getPublicKey());
|
||||
|
||||
entities.add(entity);
|
||||
}
|
||||
|
||||
SignedPreKeyEntity signedPreKeyEntity = new SignedPreKeyEntity(signedPreKey.getId(),
|
||||
signedPreKey.getKeyPair().getPublicKey(),
|
||||
signedPreKey.getSignature());
|
||||
signedPreKey.getKeyPair().getPublicKey(),
|
||||
signedPreKey.getSignature());
|
||||
|
||||
makeServiceRequest(String.format(PREKEY_PATH, ""), "PUT",
|
||||
JsonUtil.toJson(new PreKeyState(entities, signedPreKeyEntity, identityKey)));
|
||||
JsonUtil.toJson(new PreKeyState(entities, signedPreKeyEntity, identityKey)));
|
||||
}
|
||||
|
||||
public int getAvailablePreKeys() throws IOException {
|
||||
@@ -458,8 +472,8 @@ public class PushServiceSocket {
|
||||
}
|
||||
|
||||
bundles.add(new PreKeyBundle(device.getRegistrationId(), device.getDeviceId(), preKeyId,
|
||||
preKey, signedPreKeyId, signedPreKey, signedPreKeySignature,
|
||||
response.getIdentityKey()));
|
||||
preKey, signedPreKeyId, signedPreKey, signedPreKeySignature,
|
||||
response.getIdentityKey()));
|
||||
}
|
||||
|
||||
return bundles;
|
||||
@@ -569,17 +583,17 @@ public class PushServiceSocket {
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
public SignalServiceProfile retrieveProfile(SignalServiceAddress target, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
String response = makeServiceRequest(String.format(PROFILE_PATH, target.getIdentifier()), "GET", null, NO_HEADERS, unidentifiedAccess);
|
||||
public ListenableFuture<SignalServiceProfile> retrieveProfile(SignalServiceAddress target, Optional<UnidentifiedAccess> unidentifiedAccess) {
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, target.getIdentifier()), "GET", null, NO_HEADERS, unidentifiedAccess);
|
||||
|
||||
try {
|
||||
return JsonUtil.fromJson(response, SignalServiceProfile.class);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
|
||||
}
|
||||
return FutureTransformers.map(response, body -> {
|
||||
try {
|
||||
return JsonUtil.fromJson(body, SignalServiceProfile.class);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public SignalServiceProfile retrieveProfileByUsername(String username, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
@@ -595,9 +609,7 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
public ProfileAndCredential retrieveVersionedProfileAndCredential(UUID target, ProfileKey profileKey, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException, VerificationFailedException
|
||||
{
|
||||
public ListenableFuture<ProfileAndCredential> retrieveVersionedProfileAndCredential(UUID target, ProfileKey profileKey, Optional<UnidentifiedAccess> unidentifiedAccess) {
|
||||
ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target);
|
||||
ProfileKeyCredentialRequestContext requestContext = clientZkProfileOperations.createProfileKeyCredentialRequestContext(random, target, profileKey);
|
||||
ProfileKeyCredentialRequest request = requestContext.getRequest();
|
||||
@@ -606,38 +618,47 @@ public class PushServiceSocket {
|
||||
String credentialRequest = Hex.toStringCondensed(request.serialize());
|
||||
String subPath = String.format("%s/%s/%s", target, version, credentialRequest);
|
||||
|
||||
String response = makeServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, NO_HEADERS, unidentifiedAccess);
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, NO_HEADERS, unidentifiedAccess);
|
||||
|
||||
return FutureTransformers.map(response, body -> formatProfileAndCredentialBody(requestContext, body));
|
||||
}
|
||||
|
||||
private ProfileAndCredential formatProfileAndCredentialBody(ProfileKeyCredentialRequestContext requestContext, String body)
|
||||
throws NonSuccessfulResponseCodeException
|
||||
{
|
||||
try {
|
||||
SignalServiceProfile signalServiceProfile = JsonUtil.fromJson(response, SignalServiceProfile.class);
|
||||
SignalServiceProfile signalServiceProfile = JsonUtil.fromJson(body, SignalServiceProfile.class);
|
||||
|
||||
ProfileKeyCredential profileKeyCredential = signalServiceProfile.getProfileKeyCredentialResponse() != null
|
||||
? clientZkProfileOperations.receiveProfileKeyCredential(requestContext, signalServiceProfile.getProfileKeyCredentialResponse())
|
||||
: null;
|
||||
|
||||
return new ProfileAndCredential(signalServiceProfile, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL, Optional.fromNullable(profileKeyCredential));
|
||||
try {
|
||||
ProfileKeyCredential profileKeyCredential = signalServiceProfile.getProfileKeyCredentialResponse() != null
|
||||
? clientZkProfileOperations.receiveProfileKeyCredential(requestContext, signalServiceProfile.getProfileKeyCredentialResponse())
|
||||
: null;
|
||||
return new ProfileAndCredential(signalServiceProfile, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL, Optional.fromNullable(profileKeyCredential));
|
||||
} catch (VerificationFailedException e) {
|
||||
Log.w(TAG, "Failed to verify credential.", e);
|
||||
return new ProfileAndCredential(signalServiceProfile, SignalServiceProfile.RequestType.PROFILE_AND_CREDENTIAL, Optional.absent());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
|
||||
}
|
||||
}
|
||||
|
||||
public SignalServiceProfile retrieveVersionedProfile(UUID target, ProfileKey profileKey, Optional<UnidentifiedAccess> unidentifiedAccess)
|
||||
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||
{
|
||||
public ListenableFuture<SignalServiceProfile> retrieveVersionedProfile(UUID target, ProfileKey profileKey, Optional<UnidentifiedAccess> unidentifiedAccess) {
|
||||
ProfileKeyVersion profileKeyIdentifier = profileKey.getProfileKeyVersion(target);
|
||||
|
||||
String version = profileKeyIdentifier.serialize();
|
||||
String subPath = String.format("%s/%s", target, version);
|
||||
String version = profileKeyIdentifier.serialize();
|
||||
String subPath = String.format("%s/%s", target, version);
|
||||
ListenableFuture<String> response = submitServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, NO_HEADERS, unidentifiedAccess);
|
||||
|
||||
String response = makeServiceRequest(String.format(PROFILE_PATH, subPath), "GET", null, NO_HEADERS, unidentifiedAccess);
|
||||
|
||||
try {
|
||||
return JsonUtil.fromJson(response, SignalServiceProfile.class);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
|
||||
}
|
||||
return FutureTransformers.map(response, body -> {
|
||||
try {
|
||||
return JsonUtil.fromJson(body, SignalServiceProfile.class);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new NonSuccessfulResponseCodeException("Unable to parse entity");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void retrieveProfileAvatar(String path, File destination, long maxSizeBytes)
|
||||
@@ -700,7 +721,7 @@ public class PushServiceSocket {
|
||||
String response = makeServiceRequest(String.format(PROFILE_PATH, ""), "PUT", requestBody);
|
||||
|
||||
if (signalServiceProfileWrite.hasAvatar() && profileAvatar != null) {
|
||||
try {
|
||||
try {
|
||||
formAttributes = JsonUtil.fromJson(response, ProfileAvatarUploadAttributes.class);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
@@ -1323,14 +1344,48 @@ public class PushServiceSocket {
|
||||
|
||||
private static RequestBody jsonRequestBody(String jsonBody) {
|
||||
return jsonBody != null
|
||||
? RequestBody.create(MediaType.parse("application/json"), jsonBody)
|
||||
: null;
|
||||
? RequestBody.create(MediaType.parse("application/json"), jsonBody)
|
||||
: null;
|
||||
}
|
||||
|
||||
private static RequestBody protobufRequestBody(MessageLite protobufBody) {
|
||||
return protobufBody != null
|
||||
? RequestBody.create(MediaType.parse("application/x-protobuf"), protobufBody.toByteArray())
|
||||
: null;
|
||||
? RequestBody.create(MediaType.parse("application/x-protobuf"), protobufBody.toByteArray())
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
private ListenableFuture<String> submitServiceRequest(String urlFragment, String method, String jsonBody, Map<String, String> headers, Optional<UnidentifiedAccess> unidentifiedAccessKey) {
|
||||
OkHttpClient okHttpClient = buildOkHttpClient(unidentifiedAccessKey.isPresent());
|
||||
Call call = okHttpClient.newCall(buildServiceRequest(urlFragment, method, jsonRequestBody(jsonBody), headers, unidentifiedAccessKey));
|
||||
|
||||
synchronized (connections) {
|
||||
connections.add(call);
|
||||
}
|
||||
|
||||
SettableFuture<String> bodyFuture = new SettableFuture<>();
|
||||
|
||||
call.enqueue(new Callback() {
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) {
|
||||
try (ResponseBody body = validateServiceResponse(response)) {
|
||||
try {
|
||||
bodyFuture.set(body.string());
|
||||
} catch (IOException e) {
|
||||
throw new PushNetworkException(e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
bodyFuture.setException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
bodyFuture.setException(e);
|
||||
}
|
||||
});
|
||||
|
||||
return bodyFuture;
|
||||
}
|
||||
|
||||
private ResponseBody makeServiceBodyRequest(String urlFragment,
|
||||
@@ -1343,12 +1398,16 @@ public class PushServiceSocket {
|
||||
{
|
||||
Response response = getServiceConnection(urlFragment, method, body, headers, unidentifiedAccessKey);
|
||||
|
||||
responseCodeHandler.handle(response.code());
|
||||
|
||||
return validateServiceResponse(response);
|
||||
}
|
||||
|
||||
private ResponseBody validateServiceResponse(Response response) throws NonSuccessfulResponseCodeException, PushNetworkException {
|
||||
int responseCode = response.code();
|
||||
String responseMessage = response.message();
|
||||
ResponseBody responseBody = response.body();
|
||||
|
||||
responseCodeHandler.handle(responseCode);
|
||||
|
||||
switch (responseCode) {
|
||||
case 413:
|
||||
throw new RateLimitException("Rate limit exceeded: " + responseCode);
|
||||
@@ -1427,42 +1486,8 @@ public class PushServiceSocket {
|
||||
throws PushNetworkException
|
||||
{
|
||||
try {
|
||||
ServiceConnectionHolder connectionHolder = (ServiceConnectionHolder) getRandom(serviceClients, random);
|
||||
OkHttpClient baseClient = unidentifiedAccess.isPresent() ? connectionHolder.getUnidentifiedClient() : connectionHolder.getClient();
|
||||
OkHttpClient okHttpClient = baseClient.newBuilder()
|
||||
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
|
||||
// Log.d(TAG, "Push service URL: " + connectionHolder.getUrl());
|
||||
// Log.d(TAG, "Opening URL: " + String.format("%s%s", connectionHolder.getUrl(), urlFragment));
|
||||
Log.d(TAG, "Opening URL: <REDACTED>");
|
||||
|
||||
Request.Builder request = new Request.Builder();
|
||||
request.url(String.format("%s%s", connectionHolder.getUrl(), urlFragment));
|
||||
request.method(method, body);
|
||||
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
request.addHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
if (!headers.containsKey("Authorization")) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
request.addHeader("Unidentified-Access-Key", Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()));
|
||||
} else if (credentialsProvider.getPassword() != null) {
|
||||
request.addHeader("Authorization", getAuthorizationHeader(credentialsProvider));
|
||||
}
|
||||
}
|
||||
|
||||
if (signalAgent != null) {
|
||||
request.addHeader("X-Signal-Agent", signalAgent);
|
||||
}
|
||||
|
||||
if (connectionHolder.getHostHeader().isPresent()) {
|
||||
request.addHeader("Host", connectionHolder.getHostHeader().get());
|
||||
}
|
||||
|
||||
Call call = okHttpClient.newCall(request.build());
|
||||
OkHttpClient okHttpClient = buildOkHttpClient(unidentifiedAccess.isPresent());
|
||||
Call call = okHttpClient.newCall(buildServiceRequest(urlFragment, method, body, headers, unidentifiedAccess));
|
||||
|
||||
synchronized (connections) {
|
||||
connections.add(call);
|
||||
@@ -1480,6 +1505,51 @@ public class PushServiceSocket {
|
||||
}
|
||||
}
|
||||
|
||||
private OkHttpClient buildOkHttpClient(boolean unidentified) {
|
||||
ServiceConnectionHolder connectionHolder = (ServiceConnectionHolder) getRandom(serviceClients, random);
|
||||
OkHttpClient baseClient = unidentified ? connectionHolder.getUnidentifiedClient() : connectionHolder.getClient();
|
||||
|
||||
return baseClient.newBuilder()
|
||||
.connectTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
|
||||
.readTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Request buildServiceRequest(String urlFragment, String method, RequestBody body, Map<String, String> headers, Optional<UnidentifiedAccess> unidentifiedAccess) {
|
||||
ServiceConnectionHolder connectionHolder = (ServiceConnectionHolder) getRandom(serviceClients, random);
|
||||
|
||||
// Log.d(TAG, "Push service URL: " + connectionHolder.getUrl());
|
||||
// Log.d(TAG, "Opening URL: " + String.format("%s%s", connectionHolder.getUrl(), urlFragment));
|
||||
Log.d(TAG, "Opening URL: <REDACTED>");
|
||||
|
||||
Request.Builder request = new Request.Builder();
|
||||
request.url(String.format("%s%s", connectionHolder.getUrl(), urlFragment));
|
||||
request.method(method, body);
|
||||
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
request.addHeader(header.getKey(), header.getValue());
|
||||
}
|
||||
|
||||
if (!headers.containsKey("Authorization")) {
|
||||
if (unidentifiedAccess.isPresent()) {
|
||||
request.addHeader("Unidentified-Access-Key", Base64.encodeBytes(unidentifiedAccess.get().getUnidentifiedAccessKey()));
|
||||
} else if (credentialsProvider.getPassword() != null) {
|
||||
request.addHeader("Authorization", getAuthorizationHeader(credentialsProvider));
|
||||
}
|
||||
}
|
||||
|
||||
if (signalAgent != null) {
|
||||
request.addHeader("X-Signal-Agent", signalAgent);
|
||||
}
|
||||
|
||||
if (connectionHolder.getHostHeader().isPresent()) {
|
||||
request.addHeader("Host", connectionHolder.getHostHeader().get());
|
||||
}
|
||||
|
||||
return request.build();
|
||||
}
|
||||
|
||||
|
||||
private ConnectionHolder[] clientsFor(ClientSet clientSet) {
|
||||
switch (clientSet) {
|
||||
case ContactDiscovery:
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.whispersystems.signalservice.internal.util.concurrent;
|
||||
|
||||
import org.whispersystems.libsignal.logging.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* A future that allows you to have multiple ways to compute a result. If one fails, the calculation
|
||||
* will fall back to the next in the list.
|
||||
*
|
||||
* You will only see a failure if the last attempt in the list fails.
|
||||
*/
|
||||
public final class CascadingFuture<T> implements ListenableFuture<T> {
|
||||
|
||||
private static final String TAG = CascadingFuture.class.getSimpleName();
|
||||
|
||||
private SettableFuture<T> result;
|
||||
|
||||
public CascadingFuture(List<Callable<ListenableFuture<T>>> callables, ExceptionChecker exceptionChecker) {
|
||||
if (callables.isEmpty()) {
|
||||
throw new IllegalArgumentException("Must have at least one callable!");
|
||||
}
|
||||
|
||||
this.result = new SettableFuture<>();
|
||||
|
||||
doNext(new ArrayList<>(callables), exceptionChecker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return result.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return result.isCancelled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return result.isDone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() throws ExecutionException, InterruptedException {
|
||||
return result.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
|
||||
return result.get(timeout, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Listener<T> listener) {
|
||||
result.addListener(listener);
|
||||
}
|
||||
|
||||
private void doNext(List<Callable<ListenableFuture<T>>> callables, ExceptionChecker exceptionChecker) {
|
||||
Callable<ListenableFuture<T>> callable = callables.remove(0);
|
||||
try {
|
||||
ListenableFuture<T> future = callable.call();
|
||||
|
||||
future.addListener(new ListenableFuture.Listener<T>() {
|
||||
@Override
|
||||
public void onSuccess(T value) {
|
||||
result.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
if (callables.isEmpty() || !exceptionChecker.shouldContinue(e)) {
|
||||
Log.w(TAG, e);
|
||||
result.setException(e.getCause());
|
||||
} else if (!result.isCancelled()) {
|
||||
doNext(callables, exceptionChecker);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
if (callables.isEmpty() || !exceptionChecker.shouldContinue(e)) {
|
||||
result.setException(e.getCause());
|
||||
} else if (!result.isCancelled()) {
|
||||
Log.w(TAG, e);
|
||||
doNext(callables, exceptionChecker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ExceptionChecker {
|
||||
boolean shouldContinue(Exception e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user