parent
4711fa2a9a
commit
fa4e492d1c
5
pom.xml
5
pom.xml
|
@ -91,11 +91,6 @@
|
||||||
<artifactId>gcm-server</artifactId>
|
<artifactId>gcm-server</artifactId>
|
||||||
<version>1.0.2</version>
|
<version>1.0.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.code.gson</groupId>
|
|
||||||
<artifactId>gson</artifactId>
|
|
||||||
<version>2.2.2</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.spy</groupId>
|
<groupId>net.spy</groupId>
|
||||||
<artifactId>spymemcached</artifactId>
|
<artifactId>spymemcached</artifactId>
|
||||||
|
|
|
@ -18,12 +18,8 @@ package org.whispersystems.textsecuregcm.configuration;
|
||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FederationConfiguration {
|
public class FederationConfiguration {
|
||||||
|
@ -34,31 +30,7 @@ public class FederationConfiguration {
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String herokuPeers;
|
|
||||||
|
|
||||||
public List<FederatedPeer> getPeers() {
|
public List<FederatedPeer> getPeers() {
|
||||||
if (peers != null) {
|
|
||||||
return peers;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (herokuPeers != null) {
|
|
||||||
List<FederatedPeer> peers = new LinkedList<>();
|
|
||||||
JsonElement root = new JsonParser().parse(herokuPeers);
|
|
||||||
JsonArray peerElements = root.getAsJsonArray();
|
|
||||||
|
|
||||||
for (JsonElement peer : peerElements) {
|
|
||||||
String name = peer.getAsJsonObject().get("name").getAsString();
|
|
||||||
String url = peer.getAsJsonObject().get("url").getAsString();
|
|
||||||
String authenticationToken = peer.getAsJsonObject().get("authenticationToken").getAsString();
|
|
||||||
String certificate = peer.getAsJsonObject().get("certificate").getAsString();
|
|
||||||
|
|
||||||
peers.add(new FederatedPeer(name, url, authenticationToken, certificate));
|
|
||||||
}
|
|
||||||
|
|
||||||
return peers;
|
|
||||||
}
|
|
||||||
|
|
||||||
return peers;
|
return peers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,15 +18,10 @@ package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
import com.google.gson.Gson;
|
|
||||||
import org.whispersystems.textsecuregcm.util.Base64;
|
|
||||||
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
import org.whispersystems.textsecuregcm.util.ByteArrayAdapter;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
|
||||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||||
|
@ -73,9 +68,9 @@ public class ClientContact {
|
||||||
this.inactive = inactive;
|
this.inactive = inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
// public String toString() {
|
||||||
return new Gson().toJson(this);
|
// return new Gson().toJson(this);
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
|
|
|
@ -16,14 +16,19 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecuregcm.storage;
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.gson.Gson;
|
import org.slf4j.Logger;
|
||||||
import com.google.gson.annotations.SerializedName;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||||
import org.whispersystems.textsecuregcm.util.IterablePair;
|
import org.whispersystems.textsecuregcm.util.IterablePair;
|
||||||
import org.whispersystems.textsecuregcm.util.Pair;
|
import org.whispersystems.textsecuregcm.util.Pair;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -34,12 +39,17 @@ import redis.clients.jedis.Response;
|
||||||
|
|
||||||
public class DirectoryManager {
|
public class DirectoryManager {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(DirectoryManager.class);
|
||||||
|
|
||||||
private static final byte[] DIRECTORY_KEY = {'d', 'i', 'r', 'e', 'c', 't', 'o', 'r', 'y'};
|
private static final byte[] DIRECTORY_KEY = {'d', 'i', 'r', 'e', 'c', 't', 'o', 'r', 'y'};
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
private final JedisPool redisPool;
|
private final JedisPool redisPool;
|
||||||
|
|
||||||
public DirectoryManager(JedisPool redisPool) {
|
public DirectoryManager(JedisPool redisPool) {
|
||||||
this.redisPool = redisPool;
|
this.redisPool = redisPool;
|
||||||
|
this.objectMapper = new ObjectMapper();
|
||||||
|
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove(String number) {
|
public void remove(String number) {
|
||||||
|
@ -63,45 +73,48 @@ public class DirectoryManager {
|
||||||
|
|
||||||
public void add(ClientContact contact) {
|
public void add(ClientContact contact) {
|
||||||
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
||||||
Jedis jedis = redisPool.getResource();
|
|
||||||
|
|
||||||
jedis.hset(DIRECTORY_KEY, contact.getToken(), new Gson().toJson(tokenValue).getBytes());
|
try (Jedis jedis = redisPool.getResource()) {
|
||||||
redisPool.returnResource(jedis);
|
jedis.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
logger.warn("JSON Serialization", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(BatchOperationHandle handle, ClientContact contact) {
|
public void add(BatchOperationHandle handle, ClientContact contact) {
|
||||||
|
try {
|
||||||
Pipeline pipeline = handle.pipeline;
|
Pipeline pipeline = handle.pipeline;
|
||||||
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
TokenValue tokenValue = new TokenValue(contact.getRelay(), contact.isSupportsSms());
|
||||||
|
|
||||||
pipeline.hset(DIRECTORY_KEY, contact.getToken(), new Gson().toJson(tokenValue).getBytes());
|
pipeline.hset(DIRECTORY_KEY, contact.getToken(), objectMapper.writeValueAsBytes(tokenValue));
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
logger.warn("JSON Serialization", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PendingClientContact get(BatchOperationHandle handle, byte[] token) {
|
public PendingClientContact get(BatchOperationHandle handle, byte[] token) {
|
||||||
Pipeline pipeline = handle.pipeline;
|
Pipeline pipeline = handle.pipeline;
|
||||||
return new PendingClientContact(token, pipeline.hget(DIRECTORY_KEY, token));
|
return new PendingClientContact(objectMapper, token, pipeline.hget(DIRECTORY_KEY, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ClientContact> get(byte[] token) {
|
public Optional<ClientContact> get(byte[] token) {
|
||||||
Jedis jedis = redisPool.getResource();
|
try (Jedis jedis = redisPool.getResource()) {
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] result = jedis.hget(DIRECTORY_KEY, token);
|
byte[] result = jedis.hget(DIRECTORY_KEY, token);
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenValue tokenValue = new Gson().fromJson(new String(result), TokenValue.class);
|
TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class);
|
||||||
return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.supportsSms));
|
return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.supportsSms));
|
||||||
} finally {
|
} catch (IOException e) {
|
||||||
redisPool.returnResource(jedis);
|
logger.warn("JSON Error", e);
|
||||||
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ClientContact> get(List<byte[]> tokens) {
|
public List<ClientContact> get(List<byte[]> tokens) {
|
||||||
Jedis jedis = redisPool.getResource();
|
try (Jedis jedis = redisPool.getResource()) {
|
||||||
|
|
||||||
try {
|
|
||||||
Pipeline pipeline = jedis.pipelined();
|
Pipeline pipeline = jedis.pipelined();
|
||||||
List<Response<byte[]>> futures = new LinkedList<>();
|
List<Response<byte[]>> futures = new LinkedList<>();
|
||||||
List<ClientContact> results = new LinkedList<>();
|
List<ClientContact> results = new LinkedList<>();
|
||||||
|
@ -117,17 +130,19 @@ public class DirectoryManager {
|
||||||
IterablePair<byte[], Response<byte[]>> lists = new IterablePair<>(tokens, futures);
|
IterablePair<byte[], Response<byte[]>> lists = new IterablePair<>(tokens, futures);
|
||||||
|
|
||||||
for (Pair<byte[], Response<byte[]>> pair : lists) {
|
for (Pair<byte[], Response<byte[]>> pair : lists) {
|
||||||
|
try {
|
||||||
if (pair.second().get() != null) {
|
if (pair.second().get() != null) {
|
||||||
TokenValue tokenValue = new Gson().fromJson(new String(pair.second().get()), TokenValue.class);
|
TokenValue tokenValue = objectMapper.readValue(pair.second().get(), TokenValue.class);
|
||||||
ClientContact clientContact = new ClientContact(pair.first(), tokenValue.relay, tokenValue.supportsSms);
|
ClientContact clientContact = new ClientContact(pair.first(), tokenValue.relay, tokenValue.supportsSms);
|
||||||
|
|
||||||
results.add(clientContact);
|
results.add(clientContact);
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Deserialization Problem: ", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
} finally {
|
|
||||||
redisPool.returnResource(jedis);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,10 +171,10 @@ public class DirectoryManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TokenValue {
|
private static class TokenValue {
|
||||||
@SerializedName("r")
|
@JsonProperty(value = "r")
|
||||||
private String relay;
|
private String relay;
|
||||||
|
|
||||||
@SerializedName("s")
|
@JsonProperty(value = "s")
|
||||||
private boolean supportsSms;
|
private boolean supportsSms;
|
||||||
|
|
||||||
public TokenValue(String relay, boolean supportsSms) {
|
public TokenValue(String relay, boolean supportsSms) {
|
||||||
|
@ -169,22 +184,24 @@ public class DirectoryManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PendingClientContact {
|
public static class PendingClientContact {
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
private final byte[] token;
|
private final byte[] token;
|
||||||
private final Response<byte[]> response;
|
private final Response<byte[]> response;
|
||||||
|
|
||||||
PendingClientContact(byte[] token, Response<byte[]> response) {
|
PendingClientContact(ObjectMapper objectMapper, byte[] token, Response<byte[]> response) {
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.response = response;
|
this.response = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ClientContact> get() {
|
public Optional<ClientContact> get() throws IOException {
|
||||||
byte[] result = response.get();
|
byte[] result = response.get();
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
TokenValue tokenValue = new Gson().fromJson(new String(result), TokenValue.class);
|
TokenValue tokenValue = objectMapper.readValue(result, TokenValue.class);
|
||||||
return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.supportsSms));
|
return Optional.of(new ClientContact(token, tokenValue.relay, tokenValue.supportsSms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.whispersystems.textsecuregcm.storage.DirectoryManager.BatchOperationH
|
||||||
import org.whispersystems.textsecuregcm.util.Base64;
|
import org.whispersystems.textsecuregcm.util.Base64;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -53,9 +54,12 @@ public class DirectoryUpdater {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateFromLocalDatabase() {
|
public void updateFromLocalDatabase() {
|
||||||
|
int contactsAdded = 0;
|
||||||
|
int contactsRemoved = 0;
|
||||||
BatchOperationHandle batchOperation = directory.startBatchOperation();
|
BatchOperationHandle batchOperation = directory.startBatchOperation();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
logger.info("Updating from local DB.");
|
||||||
Iterator<Account> accounts = accountsManager.getAll();
|
Iterator<Account> accounts = accountsManager.getAll();
|
||||||
|
|
||||||
if (accounts == null)
|
if (accounts == null)
|
||||||
|
@ -69,17 +73,17 @@ public class DirectoryUpdater {
|
||||||
ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
|
ClientContact clientContact = new ClientContact(token, null, account.getSupportsSms());
|
||||||
|
|
||||||
directory.add(batchOperation, clientContact);
|
directory.add(batchOperation, clientContact);
|
||||||
|
contactsAdded++;
|
||||||
logger.debug("Adding local token: " + Base64.encodeBytesWithoutPadding(token));
|
|
||||||
} else {
|
} else {
|
||||||
directory.remove(batchOperation, account.getNumber());
|
directory.remove(batchOperation, account.getNumber());
|
||||||
|
contactsRemoved++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
directory.stopBatchOperation(batchOperation);
|
directory.stopBatchOperation(batchOperation);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Local directory is updated.");
|
logger.info(String.format("Local directory is updated (%d added, %d removed).", contactsAdded, contactsRemoved));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateFromPeers() {
|
public void updateFromPeers() {
|
||||||
|
@ -121,6 +125,7 @@ public class DirectoryUpdater {
|
||||||
Iterator<PendingClientContact> localContactIterator = localContacts.iterator();
|
Iterator<PendingClientContact> localContactIterator = localContacts.iterator();
|
||||||
|
|
||||||
while (remoteContactIterator.hasNext() && localContactIterator.hasNext()) {
|
while (remoteContactIterator.hasNext() && localContactIterator.hasNext()) {
|
||||||
|
try {
|
||||||
ClientContact remoteContact = remoteContactIterator.next();
|
ClientContact remoteContact = remoteContactIterator.next();
|
||||||
Optional<ClientContact> localContact = localContactIterator.next().get();
|
Optional<ClientContact> localContact = localContactIterator.next().get();
|
||||||
|
|
||||||
|
@ -135,6 +140,9 @@ public class DirectoryUpdater {
|
||||||
directory.remove(handle, remoteContact.getToken());
|
directory.remove(handle, remoteContact.getToken());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("JSON Serialization Failed: ", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
directory.stopBatchOperation(handle);
|
directory.stopBatchOperation(handle);
|
||||||
|
|
Loading…
Reference in New Issue