Initial multi device support refactoring.

1) Store account data as a json type, which includes all
   devices in a single object.

2) Simplify message delivery logic.

3) Make federated calls a pass through to standard controllers.

4) Simplify key retrieval logic.
This commit is contained in:
Moxie Marlinspike
2014-01-18 23:45:07 -08:00
parent 6f9226dcf9
commit 74f71fd8a6
47 changed files with 961 additions and 1211 deletions

View File

@@ -17,6 +17,7 @@
package org.whispersystems.textsecuregcm.federation;
import com.google.common.base.Optional;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
@@ -34,14 +35,19 @@ import org.whispersystems.textsecuregcm.entities.AccountCount;
import org.whispersystems.textsecuregcm.entities.AttachmentUri;
import org.whispersystems.textsecuregcm.entities.ClientContact;
import org.whispersystems.textsecuregcm.entities.ClientContacts;
import org.whispersystems.textsecuregcm.entities.IncomingMessage;
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
import org.whispersystems.textsecuregcm.entities.MessageResponse;
import org.whispersystems.textsecuregcm.entities.PreKey;
import org.whispersystems.textsecuregcm.entities.RelayMessage;
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
import org.whispersystems.textsecuregcm.util.Base64;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -54,6 +60,7 @@ import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
public class FederatedClient {
@@ -61,8 +68,9 @@ public class FederatedClient {
private static final String USER_COUNT_PATH = "/v1/federation/user_count";
private static final String USER_TOKENS_PATH = "/v1/federation/user_tokens/%d";
private static final String RELAY_MESSAGE_PATH = "/v1/federation/message";
private static final String RELAY_MESSAGE_PATH = "/v1/federation/messages/%s/%s";
private static final String PREKEY_PATH = "/v1/federation/key/%s";
private static final String PREKEY_PATH_DEVICE = "/v1/federation/key/%s/%s";
private static final String ATTACHMENT_URI_PATH = "/v1/federation/attachment/%d";
private final FederatedPeer peer;
@@ -98,15 +106,27 @@ public class FederatedClient {
}
}
public UnstructuredPreKeyList getKeys(String destination) {
public Optional<PreKey> getKey(String destination) {
try {
WebResource resource = client.resource(peer.getUrl()).path(String.format(PREKEY_PATH, destination));
return resource.accept(MediaType.APPLICATION_JSON)
.header("Authorization", authorizationHeader)
.get(UnstructuredPreKeyList.class);
return Optional.of(resource.accept(MediaType.APPLICATION_JSON)
.header("Authorization", authorizationHeader)
.get(PreKey.class));
} catch (UniformInterfaceException | ClientHandlerException e) {
logger.warn("PreKey", e);
return null;
return Optional.absent();
}
}
public Optional<UnstructuredPreKeyList> getKeys(String destination, String device) {
try {
WebResource resource = client.resource(peer.getUrl()).path(String.format(PREKEY_PATH_DEVICE, destination, device));
return Optional.of(resource.accept(MediaType.APPLICATION_JSON)
.header("Authorization", authorizationHeader)
.get(UnstructuredPreKeyList.class));
} catch (UniformInterfaceException | ClientHandlerException e) {
logger.warn("PreKey", e);
return Optional.absent();
}
}
@@ -138,21 +158,19 @@ public class FederatedClient {
}
}
public MessageResponse sendMessages(List<RelayMessage> messages)
public void sendMessages(String source, String destination, IncomingMessageList messages)
throws IOException
{
try {
WebResource resource = client.resource(peer.getUrl()).path(RELAY_MESSAGE_PATH);
WebResource resource = client.resource(peer.getUrl()).path(String.format(RELAY_MESSAGE_PATH, source, destination));
ClientResponse response = resource.type(MediaType.APPLICATION_JSON)
.header("Authorization", authorizationHeader)
.entity(messages)
.put(ClientResponse.class);
if (response.getStatus() != 200 && response.getStatus() != 204) {
throw new IOException("Bad response: " + response.getStatus());
throw new WebApplicationException(clientResponseToResponse(response));
}
return response.getEntity(MessageResponse.class);
} catch (UniformInterfaceException | ClientHandlerException e) {
logger.warn("sendMessage", e);
throw new IOException(e);
@@ -203,6 +221,19 @@ public class FederatedClient {
}
}
private Response clientResponseToResponse(ClientResponse r) {
Response.ResponseBuilder rb = Response.status(r.getStatus());
for (Map.Entry<String, List<String>> entry : r.getHeaders().entrySet()) {
for (String value : entry.getValue()) {
rb.header(entry.getKey(), value);
}
}
rb.entity(r.getEntityInputStream());
return rb.build();
}
public String getPeerName() {
return peer.getName();
}

View File

@@ -0,0 +1,32 @@
package org.whispersystems.textsecuregcm.federation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.base.Optional;
import org.whispersystems.textsecuregcm.storage.Account;
public class NonLimitedAccount extends Account {
@JsonIgnore
private final String number;
@JsonIgnore
private final String relay;
public NonLimitedAccount(String number, String relay) {
this.number = number;
this.relay = relay;
}
public String getNumber() {
return number;
}
public boolean isRateLimited() {
return false;
}
public Optional<String> getRelay() {
return Optional.of(relay);
}
}