Split Account into Device and Account definitions.
This commit is contained in:
parent
ce9d3548e4
commit
d3830a7fd4
|
@ -135,7 +135,7 @@ public class WhisperServerService extends Service<WhisperServerConfiguration> {
|
||||||
environment.addResource(new FederationController(keys, accountsManager, pushSender, urlSigner));
|
environment.addResource(new FederationController(keys, accountsManager, pushSender, urlSigner));
|
||||||
|
|
||||||
environment.addServlet(new MessageController(rateLimiters, deviceAuthenticator,
|
environment.addServlet(new MessageController(rateLimiters, deviceAuthenticator,
|
||||||
pushSender, federatedClientManager),
|
pushSender, accountsManager, federatedClientManager),
|
||||||
MessageController.PATH);
|
MessageController.PATH);
|
||||||
|
|
||||||
environment.addHealthCheck(new RedisHealthCheck(redisClient));
|
environment.addHealthCheck(new RedisHealthCheck(redisClient));
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||||
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
|
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
||||||
|
@ -53,6 +54,7 @@ import javax.ws.rs.core.Response;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
@Path("/v1/accounts")
|
@Path("/v1/accounts")
|
||||||
public class AccountController {
|
public class AccountController {
|
||||||
|
@ -142,7 +144,7 @@ public class AccountController {
|
||||||
device.setFetchesMessages(accountAttributes.getFetchesMessages());
|
device.setFetchesMessages(accountAttributes.getFetchesMessages());
|
||||||
device.setDeviceId(0);
|
device.setDeviceId(0);
|
||||||
|
|
||||||
accounts.createResetNumber(device);
|
accounts.create(new Account(number, accountAttributes.getSupportsSms(), device));
|
||||||
|
|
||||||
pendingAccounts.remove(number);
|
pendingAccounts.remove(number);
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ public class DeviceController {
|
||||||
device.setSupportsSms(accountAttributes.getSupportsSms());
|
device.setSupportsSms(accountAttributes.getSupportsSms());
|
||||||
device.setFetchesMessages(accountAttributes.getFetchesMessages());
|
device.setFetchesMessages(accountAttributes.getFetchesMessages());
|
||||||
|
|
||||||
accounts.createAccountOnExistingNumber(device);
|
accounts.provisionDevice(device);
|
||||||
|
|
||||||
pendingDevices.remove(number);
|
pendingDevices.remove(number);
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.whispersystems.textsecuregcm.controllers;
|
package org.whispersystems.textsecuregcm.controllers;
|
||||||
|
|
||||||
import com.amazonaws.HttpMethod;
|
import com.amazonaws.HttpMethod;
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import com.yammer.dropwizard.auth.Auth;
|
import com.yammer.dropwizard.auth.Auth;
|
||||||
import com.yammer.metrics.annotation.Timed;
|
import com.yammer.metrics.annotation.Timed;
|
||||||
|
@ -32,6 +33,7 @@ import org.whispersystems.textsecuregcm.entities.RelayMessage;
|
||||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||||
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
import org.whispersystems.textsecuregcm.federation.FederatedPeer;
|
||||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||||
|
@ -95,13 +97,13 @@ public class FederationController {
|
||||||
public UnstructuredPreKeyList getKey(@Auth FederatedPeer peer,
|
public UnstructuredPreKeyList getKey(@Auth FederatedPeer peer,
|
||||||
@PathParam("number") String number)
|
@PathParam("number") String number)
|
||||||
{
|
{
|
||||||
UnstructuredPreKeyList preKeys = keys.get(number, accounts.getAllByNumber(number));
|
Optional<Account> account = accounts.getAccount(number);
|
||||||
|
UnstructuredPreKeyList keyList = null;
|
||||||
if (preKeys == null) {
|
if (account.isPresent())
|
||||||
|
keyList = keys.get(number, account.get());
|
||||||
|
if (!account.isPresent() || keyList.getKeys().isEmpty())
|
||||||
throw new WebApplicationException(Response.status(404).build());
|
throw new WebApplicationException(Response.status(404).build());
|
||||||
}
|
return keyList;
|
||||||
|
|
||||||
return preKeys;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Timed
|
@Timed
|
||||||
|
@ -113,37 +115,38 @@ public class FederationController {
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Map<String, Pair<Boolean, Set<Long>>> destinations = new HashMap<>();
|
Map<String, Set<Long>> localDestinations = new HashMap<>();
|
||||||
|
|
||||||
for (RelayMessage message : messages) {
|
for (RelayMessage message : messages) {
|
||||||
Pair<Boolean, Set<Long>> deviceIds = destinations.get(message.getDestination());
|
Set<Long> deviceIds = localDestinations.get(message.getDestination());
|
||||||
if (deviceIds == null) {
|
if (deviceIds == null) {
|
||||||
deviceIds = new Pair<Boolean, Set<Long>>(true, new HashSet<Long>());
|
deviceIds = new HashSet<>();
|
||||||
destinations.put(message.getDestination(), deviceIds);
|
localDestinations.put(message.getDestination(), deviceIds);
|
||||||
}
|
}
|
||||||
deviceIds.second().add(message.getDestinationDeviceId());
|
deviceIds.add(message.getDestinationDeviceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<Pair<String, Long>, Device> accountCache = new HashMap<>();
|
Pair<Map<String, Account>, List<String>> accountsForDevices = accounts.getAccountsForDevices(localDestinations);
|
||||||
List<String> numbersMissingDevices = new LinkedList<>();
|
|
||||||
pushSender.fillLocalAccountsCache(destinations, accountCache, numbersMissingDevices);
|
|
||||||
|
|
||||||
|
Map<String, Account> localAccounts = accountsForDevices.first();
|
||||||
|
List<String> numbersMissingDevices = accountsForDevices.second();
|
||||||
List<String> success = new LinkedList<>();
|
List<String> success = new LinkedList<>();
|
||||||
List<String> failure = new LinkedList<>(numbersMissingDevices);
|
List<String> failure = new LinkedList<>(numbersMissingDevices);
|
||||||
|
|
||||||
for (RelayMessage message : messages) {
|
for (RelayMessage message : messages) {
|
||||||
Device device = accountCache.get(new Pair<>(message.getDestination(), message.getDestinationDeviceId()));
|
Account destinationAccount = localAccounts.get(message.getDestination());
|
||||||
if (device == null)
|
if (destinationAccount == null)
|
||||||
continue;
|
continue;
|
||||||
|
Device device = destinationAccount.getDevice(message.getDestinationDeviceId());
|
||||||
OutgoingMessageSignal signal = OutgoingMessageSignal.parseFrom(message.getOutgoingMessageSignal())
|
OutgoingMessageSignal signal = OutgoingMessageSignal.parseFrom(message.getOutgoingMessageSignal())
|
||||||
.toBuilder()
|
.toBuilder()
|
||||||
.setRelay(peer.getName())
|
.setRelay(peer.getName())
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
pushSender.sendMessage(device, signal);
|
pushSender.sendMessage(device, signal);
|
||||||
|
success.add(device.getBackwardsCompatibleNumberEncoding());
|
||||||
} catch (NoSuchUserException e) {
|
} catch (NoSuchUserException e) {
|
||||||
logger.info("No such user", e);
|
logger.info("No such user", e);
|
||||||
failure.add(message.getDestination());
|
failure.add(device.getBackwardsCompatibleNumberEncoding());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +172,7 @@ public class FederationController {
|
||||||
public ClientContacts getUserTokens(@Auth FederatedPeer peer,
|
public ClientContacts getUserTokens(@Auth FederatedPeer peer,
|
||||||
@PathParam("offset") int offset)
|
@PathParam("offset") int offset)
|
||||||
{
|
{
|
||||||
List<Device> numberList = accounts.getAllMasterAccounts(offset, ACCOUNT_CHUNK_SIZE);
|
List<Device> numberList = accounts.getAllMasterDevices(offset, ACCOUNT_CHUNK_SIZE);
|
||||||
List<ClientContact> clientContacts = new LinkedList<>();
|
List<ClientContact> clientContacts = new LinkedList<>();
|
||||||
|
|
||||||
for (Device device : numberList) {
|
for (Device device : numberList) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||||
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||||
|
@ -78,7 +79,11 @@ public class KeysController {
|
||||||
UnstructuredPreKeyList keyList;
|
UnstructuredPreKeyList keyList;
|
||||||
|
|
||||||
if (relay == null) {
|
if (relay == null) {
|
||||||
keyList = keys.get(number, accountsManager.getAllByNumber(number));
|
Optional<Account> account = accountsManager.getAccount(number);
|
||||||
|
if (account.isPresent())
|
||||||
|
keyList = keys.get(number, account.get());
|
||||||
|
else
|
||||||
|
throw new WebApplicationException(Response.status(404).build());
|
||||||
} else {
|
} else {
|
||||||
keyList = federatedClientManager.getClient(relay).getKeys(number);
|
keyList = federatedClientManager.getClient(relay).getKeys(number);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,8 @@ import org.whispersystems.textsecuregcm.federation.FederatedClientManager;
|
||||||
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
import org.whispersystems.textsecuregcm.federation.NoSuchPeerException;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.util.Base64;
|
import org.whispersystems.textsecuregcm.util.Base64;
|
||||||
import org.whispersystems.textsecuregcm.util.IterablePair;
|
import org.whispersystems.textsecuregcm.util.IterablePair;
|
||||||
|
@ -52,10 +54,12 @@ import javax.servlet.AsyncContext;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -79,20 +83,34 @@ public class MessageController extends HttpServlet {
|
||||||
private final FederatedClientManager federatedClientManager;
|
private final FederatedClientManager federatedClientManager;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
|
private final AccountsManager accountsManager;
|
||||||
|
|
||||||
public MessageController(RateLimiters rateLimiters,
|
public MessageController(RateLimiters rateLimiters,
|
||||||
DeviceAuthenticator deviceAuthenticator,
|
DeviceAuthenticator deviceAuthenticator,
|
||||||
PushSender pushSender,
|
PushSender pushSender,
|
||||||
|
AccountsManager accountsManager,
|
||||||
FederatedClientManager federatedClientManager)
|
FederatedClientManager federatedClientManager)
|
||||||
{
|
{
|
||||||
this.rateLimiters = rateLimiters;
|
this.rateLimiters = rateLimiters;
|
||||||
this.deviceAuthenticator = deviceAuthenticator;
|
this.deviceAuthenticator = deviceAuthenticator;
|
||||||
this.pushSender = pushSender;
|
this.pushSender = pushSender;
|
||||||
|
this.accountsManager = accountsManager;
|
||||||
this.federatedClientManager = federatedClientManager;
|
this.federatedClientManager = federatedClientManager;
|
||||||
this.objectMapper = new ObjectMapper();
|
this.objectMapper = new ObjectMapper();
|
||||||
this.executor = Executors.newFixedThreadPool(10);
|
this.executor = Executors.newFixedThreadPool(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LocalOrRemoteDevice {
|
||||||
|
Device device;
|
||||||
|
String relay, number; long deviceId;
|
||||||
|
LocalOrRemoteDevice(Device device) {
|
||||||
|
this.device = device; this.number = device.getNumber(); this.deviceId = device.getDeviceId();
|
||||||
|
}
|
||||||
|
LocalOrRemoteDevice(String relay, String number, long deviceId) {
|
||||||
|
this.relay = relay; this.number = number; this.deviceId = deviceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
|
||||||
TimerContext timerContext = timer.time();
|
TimerContext timerContext = timer.time();
|
||||||
|
@ -103,20 +121,12 @@ public class MessageController extends HttpServlet {
|
||||||
|
|
||||||
rateLimiters.getMessagesLimiter().validate(sender.getNumber());
|
rateLimiters.getMessagesLimiter().validate(sender.getNumber());
|
||||||
|
|
||||||
|
|
||||||
Map<Pair<String, Long>, Device> deviceCache = new HashMap<>();
|
|
||||||
List<String> numbersMissingDevices = new LinkedList<>();
|
List<String> numbersMissingDevices = new LinkedList<>();
|
||||||
|
|
||||||
List<IncomingMessage> incomingMessages = messages.getMessages();
|
List<Pair<LocalOrRemoteDevice, OutgoingMessageSignal>> outgoingMessages =
|
||||||
List<OutgoingMessageSignal> outgoingMessages = getOutgoingMessageSignals(sender.getNumber(),
|
getOutgoingMessageSignals(sender.getNumber(), messages.getMessages(), numbersMissingDevices);
|
||||||
incomingMessages,
|
|
||||||
deviceCache,
|
|
||||||
numbersMissingDevices);
|
|
||||||
|
|
||||||
IterablePair<IncomingMessage, OutgoingMessageSignal> listPair = new IterablePair<>(incomingMessages,
|
handleAsyncDelivery(timerContext, req.startAsync(), outgoingMessages, numbersMissingDevices);
|
||||||
outgoingMessages);
|
|
||||||
|
|
||||||
handleAsyncDelivery(timerContext, req.startAsync(), listPair, deviceCache, numbersMissingDevices);
|
|
||||||
} catch (AuthenticationException e) {
|
} catch (AuthenticationException e) {
|
||||||
failureMeter.mark();
|
failureMeter.mark();
|
||||||
timerContext.stop();
|
timerContext.stop();
|
||||||
|
@ -139,8 +149,7 @@ public class MessageController extends HttpServlet {
|
||||||
|
|
||||||
private void handleAsyncDelivery(final TimerContext timerContext,
|
private void handleAsyncDelivery(final TimerContext timerContext,
|
||||||
final AsyncContext context,
|
final AsyncContext context,
|
||||||
final IterablePair<IncomingMessage, OutgoingMessageSignal> listPair,
|
final List<Pair<LocalOrRemoteDevice, OutgoingMessageSignal>> listPair,
|
||||||
final Map<Pair<String, Long>, Device> deviceCache,
|
|
||||||
final List<String> numbersMissingDevices)
|
final List<String> numbersMissingDevices)
|
||||||
{
|
{
|
||||||
executor.submit(new Runnable() {
|
executor.submit(new Runnable() {
|
||||||
|
@ -151,38 +160,37 @@ public class MessageController extends HttpServlet {
|
||||||
HttpServletResponse response = (HttpServletResponse) context.getResponse();
|
HttpServletResponse response = (HttpServletResponse) context.getResponse();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, Set<Pair<IncomingMessage, OutgoingMessageSignal>>> relayMessages = new HashMap<>();
|
Map<String, Set<Pair<LocalOrRemoteDevice, OutgoingMessageSignal>>> relayMessages = new HashMap<>();
|
||||||
for (Pair<IncomingMessage, OutgoingMessageSignal> messagePair : listPair) {
|
for (Pair<LocalOrRemoteDevice, OutgoingMessageSignal> messagePair : listPair) {
|
||||||
String destination = messagePair.first().getDestination();
|
String relay = messagePair.first().relay;
|
||||||
long destinationDeviceId = messagePair.first().getDestinationDeviceId();
|
|
||||||
String relay = messagePair.first().getRelay();
|
|
||||||
|
|
||||||
if (Util.isEmpty(relay)) {
|
if (Util.isEmpty(relay)) {
|
||||||
|
String encodedId = messagePair.first().device.getBackwardsCompatibleNumberEncoding();
|
||||||
try {
|
try {
|
||||||
pushSender.sendMessage(deviceCache.get(new Pair<>(destination, destinationDeviceId)), messagePair.second());
|
pushSender.sendMessage(messagePair.first().device, messagePair.second());
|
||||||
|
success.add(encodedId);
|
||||||
} catch (NoSuchUserException e) {
|
} catch (NoSuchUserException e) {
|
||||||
logger.debug("No such user", e);
|
logger.debug("No such user", e);
|
||||||
failure.add(destination);
|
failure.add(encodedId);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Set<Pair<IncomingMessage, OutgoingMessageSignal>> messageSet = relayMessages.get(relay);
|
Set<Pair<LocalOrRemoteDevice, OutgoingMessageSignal>> messageSet = relayMessages.get(relay);
|
||||||
if (messageSet == null) {
|
if (messageSet == null) {
|
||||||
messageSet = new HashSet<>();
|
messageSet = new HashSet<>();
|
||||||
relayMessages.put(relay, messageSet);
|
relayMessages.put(relay, messageSet);
|
||||||
}
|
}
|
||||||
messageSet.add(messagePair);
|
messageSet.add(messagePair);
|
||||||
}
|
}
|
||||||
success.add(destination);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, Set<Pair<IncomingMessage, OutgoingMessageSignal>>> messagesForRelay : relayMessages.entrySet()) {
|
for (Map.Entry<String, Set<Pair<LocalOrRemoteDevice, OutgoingMessageSignal>>> messagesForRelay : relayMessages.entrySet()) {
|
||||||
try {
|
try {
|
||||||
FederatedClient client = federatedClientManager.getClient(messagesForRelay.getKey());
|
FederatedClient client = federatedClientManager.getClient(messagesForRelay.getKey());
|
||||||
|
|
||||||
List<RelayMessage> messages = new LinkedList<>();
|
List<RelayMessage> messages = new LinkedList<>();
|
||||||
for (Pair<IncomingMessage, OutgoingMessageSignal> message : messagesForRelay.getValue()) {
|
for (Pair<LocalOrRemoteDevice, OutgoingMessageSignal> message : messagesForRelay.getValue()) {
|
||||||
messages.add(new RelayMessage(message.first().getDestination(),
|
messages.add(new RelayMessage(message.first().number,
|
||||||
message.first().getDestinationDeviceId(),
|
message.first().deviceId,
|
||||||
message.second().toByteArray()));
|
message.second().toByteArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,8 +203,8 @@ public class MessageController extends HttpServlet {
|
||||||
numbersMissingDevices.add(string);
|
numbersMissingDevices.add(string);
|
||||||
} catch (NoSuchPeerException e) {
|
} catch (NoSuchPeerException e) {
|
||||||
logger.info("No such peer", e);
|
logger.info("No such peer", e);
|
||||||
for (Pair<IncomingMessage, OutgoingMessageSignal> messagePair : messagesForRelay.getValue())
|
for (Pair<LocalOrRemoteDevice, OutgoingMessageSignal> messagePair : messagesForRelay.getValue())
|
||||||
failure.add(messagePair.first().getDestination());
|
failure.add(messagePair.first().number);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +218,11 @@ public class MessageController extends HttpServlet {
|
||||||
failureMeter.mark();
|
failureMeter.mark();
|
||||||
response.setStatus(501);
|
response.setStatus(501);
|
||||||
context.complete();
|
context.complete();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Unknown error sending message", e);
|
||||||
|
failureMeter.mark();
|
||||||
|
response.setStatus(500);
|
||||||
|
context.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
timerContext.stop();
|
timerContext.stop();
|
||||||
|
@ -217,28 +230,32 @@ public class MessageController extends HttpServlet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param deviceCache is a map from Pair<number, deviceId> to the account
|
|
||||||
*/
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private List<OutgoingMessageSignal> getOutgoingMessageSignals(String sourceNumber,
|
private List<Pair<LocalOrRemoteDevice, OutgoingMessageSignal>> getOutgoingMessageSignals(String sourceNumber,
|
||||||
List<IncomingMessage> incomingMessages,
|
List<IncomingMessage> incomingMessages,
|
||||||
Map<Pair<String, Long>, Device> deviceCache,
|
|
||||||
List<String> numbersMissingDevices)
|
List<String> numbersMissingDevices)
|
||||||
{
|
{
|
||||||
List<OutgoingMessageSignal> outgoingMessages = new LinkedList<>();
|
List<Pair<LocalOrRemoteDevice, OutgoingMessageSignal>> outgoingMessages = new LinkedList<>();
|
||||||
// # local deviceIds
|
Map<String, Set<Long>> localDestinations = new HashMap<>();
|
||||||
Map<String, Pair<Boolean, Set<Long>>> destinations = new HashMap<>();
|
Set<String> destinationNumbers = new HashSet<>();
|
||||||
for (IncomingMessage incoming : incomingMessages) {
|
for (IncomingMessage incoming : incomingMessages) {
|
||||||
Pair<Boolean, Set<Long>> deviceIds = destinations.get(incoming.getDestination());
|
destinationNumbers.add(incoming.getDestination());
|
||||||
|
if (!Util.isEmpty(incoming.getRelay()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Set<Long> deviceIds = localDestinations.get(incoming.getDestination());
|
||||||
if (deviceIds == null) {
|
if (deviceIds == null) {
|
||||||
deviceIds = new Pair<Boolean, Set<Long>>(Util.isEmpty(incoming.getRelay()), new HashSet<Long>());
|
deviceIds = new HashSet<>();
|
||||||
destinations.put(incoming.getDestination(), deviceIds);
|
localDestinations.put(incoming.getDestination(), deviceIds);
|
||||||
}
|
}
|
||||||
deviceIds.second().add(incoming.getDestinationDeviceId());
|
deviceIds.add(incoming.getDestinationDeviceId());
|
||||||
}
|
}
|
||||||
|
|
||||||
pushSender.fillLocalAccountsCache(destinations, deviceCache, numbersMissingDevices);
|
Pair<Map<String, Account>, List<String>> accountsForDevices = accountsManager.getAccountsForDevices(localDestinations);
|
||||||
|
|
||||||
|
Map<String, Account> localAccounts = accountsForDevices.first();
|
||||||
|
for (String number : accountsForDevices.second())
|
||||||
|
numbersMissingDevices.add(number);
|
||||||
|
|
||||||
for (IncomingMessage incoming : incomingMessages) {
|
for (IncomingMessage incoming : incomingMessages) {
|
||||||
OutgoingMessageSignal.Builder outgoingMessage = OutgoingMessageSignal.newBuilder();
|
OutgoingMessageSignal.Builder outgoingMessage = OutgoingMessageSignal.newBuilder();
|
||||||
|
@ -255,13 +272,22 @@ public class MessageController extends HttpServlet {
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
|
|
||||||
for (String destination : destinations.keySet()) {
|
for (String destination : destinationNumbers) {
|
||||||
if (!destination.equals(incoming.getDestination())) {
|
if (!destination.equals(incoming.getDestination()))
|
||||||
outgoingMessage.setDestinations(index++, destination);
|
outgoingMessage.setDestinations(index++, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocalOrRemoteDevice device = null;
|
||||||
|
if (!Util.isEmpty(incoming.getRelay()))
|
||||||
|
device = new LocalOrRemoteDevice(incoming.getRelay(), incoming.getDestination(), incoming.getDestinationDeviceId());
|
||||||
|
else {
|
||||||
|
Account destination = localAccounts.get(incoming.getDestination());
|
||||||
|
if (destination != null)
|
||||||
|
device = new LocalOrRemoteDevice(destination.getDevice(incoming.getDestinationDeviceId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
outgoingMessages.add(outgoingMessage.build());
|
if (device != null)
|
||||||
|
outgoingMessages.add(new Pair<>(device, outgoingMessage.build()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return outgoingMessages;
|
return outgoingMessages;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecuregcm.push;
|
package org.whispersystems.textsecuregcm.push;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
|
||||||
|
@ -23,6 +24,7 @@ import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.controllers.NoSuchUserException;
|
import org.whispersystems.textsecuregcm.controllers.NoSuchUserException;
|
||||||
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
|
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
|
||||||
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||||
|
@ -61,35 +63,6 @@ public class PushSender {
|
||||||
this.apnSender = new APNSender(apnConfiguration.getCertificate(), apnConfiguration.getKey());
|
this.apnSender = new APNSender(apnConfiguration.getCertificate(), apnConfiguration.getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* For each local destination in destinations, either adds all its accounts to accountCache or adds the number to
|
|
||||||
* numbersMissingDevices, if the deviceIds list don't match what is required.
|
|
||||||
* @param destinations Map from number to Pair<localNumber, Set<deviceIds>>
|
|
||||||
* @param accountCache Map from <number, deviceId> to account
|
|
||||||
* @param numbersMissingDevices list of numbers missing devices
|
|
||||||
*/
|
|
||||||
public void fillLocalAccountsCache(Map<String, Pair<Boolean, Set<Long>>> destinations, Map<Pair<String, Long>, Device> accountCache, List<String> numbersMissingDevices) {
|
|
||||||
for (Map.Entry<String, Pair<Boolean, Set<Long>>> destination : destinations.entrySet()) {
|
|
||||||
if (destination.getValue().first()) {
|
|
||||||
String number = destination.getKey();
|
|
||||||
List<Device> deviceList = accounts.getAllByNumber(number);
|
|
||||||
Set<Long> deviceIdsIncluded = destination.getValue().second();
|
|
||||||
if (deviceList.size() != deviceIdsIncluded.size())
|
|
||||||
numbersMissingDevices.add(number);
|
|
||||||
else {
|
|
||||||
for (Device device : deviceList) {
|
|
||||||
if (!deviceIdsIncluded.contains(device.getDeviceId())) {
|
|
||||||
numbersMissingDevices.add(number);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (Device device : deviceList)
|
|
||||||
accountCache.put(new Pair<>(number, device.getDeviceId()), device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMessage(Device device, MessageProtos.OutgoingMessageSignal outgoingMessage)
|
public void sendMessage(Device device, MessageProtos.OutgoingMessageSignal outgoingMessage)
|
||||||
throws IOException, NoSuchUserException
|
throws IOException, NoSuchUserException
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2013 Open WhisperSystems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
|
|
||||||
|
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
||||||
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class Account implements Serializable {
|
||||||
|
private String number;
|
||||||
|
private boolean supportsSms;
|
||||||
|
private Map<Long, Device> devices = new HashMap<>();
|
||||||
|
|
||||||
|
private Account(String number, boolean supportsSms) {
|
||||||
|
this.number = number;
|
||||||
|
this.supportsSms = supportsSms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account(String number, boolean supportsSms, Device onlyDevice) {
|
||||||
|
this(number, supportsSms);
|
||||||
|
this.devices.put(onlyDevice.getDeviceId(), onlyDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account(String number, boolean supportsSms, List<Device> devices) {
|
||||||
|
this(number, supportsSms);
|
||||||
|
for (Device device : devices)
|
||||||
|
this.devices.put(device.getDeviceId(), device);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumber(String number) {
|
||||||
|
this.number = number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSupportsSms() {
|
||||||
|
return supportsSms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSupportsSms(boolean supportsSms) {
|
||||||
|
this.supportsSms = supportsSms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isActive() {
|
||||||
|
Device masterDevice = devices.get((long) 1);
|
||||||
|
return masterDevice != null && masterDevice.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Device> getDevices() {
|
||||||
|
return devices.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Device getDevice(long destinationDeviceId) {
|
||||||
|
return devices.get(destinationDeviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAllDeviceIds(Set<Long> deviceIds) {
|
||||||
|
if (devices.size() != deviceIds.size())
|
||||||
|
return false;
|
||||||
|
for (long deviceId : devices.keySet()) {
|
||||||
|
if (!deviceIds.contains(deviceId))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,8 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -78,22 +80,22 @@ public abstract class Accounts {
|
||||||
"WHERE " + NUMBER + " = :number AND " + DEVICE_ID + " = :device_id")
|
"WHERE " + NUMBER + " = :number AND " + DEVICE_ID + " = :device_id")
|
||||||
abstract void update(@AccountBinder Device device);
|
abstract void update(@AccountBinder Device device);
|
||||||
|
|
||||||
@Mapper(AccountMapper.class)
|
@Mapper(DeviceMapper.class)
|
||||||
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number AND " + DEVICE_ID + " = :device_id")
|
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number AND " + DEVICE_ID + " = :device_id")
|
||||||
abstract Device get(@Bind("number") String number, @Bind("device_id") long deviceId);
|
abstract Device get(@Bind("number") String number, @Bind("device_id") long deviceId);
|
||||||
|
|
||||||
@SqlQuery("SELECT COUNT(DISTINCT " + NUMBER + ") from accounts")
|
@SqlQuery("SELECT COUNT(DISTINCT " + NUMBER + ") from accounts")
|
||||||
abstract long getNumberCount();
|
abstract long getNumberCount();
|
||||||
|
|
||||||
@Mapper(AccountMapper.class)
|
@Mapper(DeviceMapper.class)
|
||||||
@SqlQuery("SELECT * FROM accounts WHERE " + DEVICE_ID + " = 1 OFFSET :offset LIMIT :limit")
|
@SqlQuery("SELECT * FROM accounts WHERE " + DEVICE_ID + " = 1 OFFSET :offset LIMIT :limit")
|
||||||
abstract List<Device> getAllFirstAccounts(@Bind("offset") int offset, @Bind("limit") int length);
|
abstract List<Device> getAllMasterDevices(@Bind("offset") int offset, @Bind("limit") int length);
|
||||||
|
|
||||||
@Mapper(AccountMapper.class)
|
@Mapper(DeviceMapper.class)
|
||||||
@SqlQuery("SELECT * FROM accounts WHERE " + DEVICE_ID + " = 1")
|
@SqlQuery("SELECT * FROM accounts WHERE " + DEVICE_ID + " = 1")
|
||||||
public abstract Iterator<Device> getAllFirstAccounts();
|
public abstract Iterator<Device> getAllMasterDevices();
|
||||||
|
|
||||||
@Mapper(AccountMapper.class)
|
@Mapper(DeviceMapper.class)
|
||||||
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number")
|
@SqlQuery("SELECT * FROM accounts WHERE " + NUMBER + " = :number")
|
||||||
public abstract List<Device> getAllByNumber(@Bind("number") String number);
|
public abstract List<Device> getAllByNumber(@Bind("number") String number);
|
||||||
|
|
||||||
|
@ -104,8 +106,7 @@ public abstract class Accounts {
|
||||||
return insertStep(device);
|
return insertStep(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AccountMapper implements ResultSetMapper<Device> {
|
public static class DeviceMapper implements ResultSetMapper<Device> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Device map(int i, ResultSet resultSet, StatementContext statementContext)
|
public Device map(int i, ResultSet resultSet, StatementContext statementContext)
|
||||||
throws SQLException
|
throws SQLException
|
||||||
|
|
|
@ -20,10 +20,17 @@ package org.whispersystems.textsecuregcm.storage;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import net.spy.memcached.MemcachedClient;
|
import net.spy.memcached.MemcachedClient;
|
||||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||||
|
import org.whispersystems.textsecuregcm.util.Pair;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
import sun.util.logging.resources.logging_zh_CN;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class AccountsManager {
|
public class AccountsManager {
|
||||||
|
|
||||||
|
@ -44,16 +51,17 @@ public class AccountsManager {
|
||||||
return accounts.getNumberCount();
|
return accounts.getNumberCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Device> getAllMasterAccounts(int offset, int length) {
|
public List<Device> getAllMasterDevices(int offset, int length) {
|
||||||
return accounts.getAllFirstAccounts(offset, length);
|
return accounts.getAllMasterDevices(offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator<Device> getAllMasterAccounts() {
|
public Iterator<Device> getAllMasterDevices() {
|
||||||
return accounts.getAllFirstAccounts();
|
return accounts.getAllMasterDevices();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a new Device and NumberData, clearing all existing accounts/data on the given number */
|
/** Creates a new Account (WITH ONE DEVICE), clearing all existing devices on the given number */
|
||||||
public void createResetNumber(Device device) {
|
public void create(Account account) {
|
||||||
|
Device device = account.getDevices().iterator().next();
|
||||||
long id = accounts.insertClearingNumber(device);
|
long id = accounts.insertClearingNumber(device);
|
||||||
device.setId(id);
|
device.setId(id);
|
||||||
|
|
||||||
|
@ -64,8 +72,8 @@ public class AccountsManager {
|
||||||
updateDirectory(device);
|
updateDirectory(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a new Device for an existing NumberData (setting the deviceId) */
|
/** Creates a new Device for an existing Account */
|
||||||
public void createAccountOnExistingNumber(Device device) {
|
public void provisionDevice(Device device) {
|
||||||
long id = accounts.insert(device);
|
long id = accounts.insert(device);
|
||||||
device.setId(id);
|
device.setId(id);
|
||||||
|
|
||||||
|
@ -104,8 +112,43 @@ public class AccountsManager {
|
||||||
else return Optional.absent();
|
else return Optional.absent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Device> getAllByNumber(String number) {
|
public Optional<Account> getAccount(String number) {
|
||||||
return accounts.getAllByNumber(number);
|
List<Device> devices = accounts.getAllByNumber(number);
|
||||||
|
if (devices.isEmpty())
|
||||||
|
return Optional.absent();
|
||||||
|
return Optional.of(new Account(number, devices.get(0).getSupportsSms(), devices));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Account> getAllAccounts(Set<String> numbers) {
|
||||||
|
//TODO: ONE QUERY
|
||||||
|
Map<String, Account> result = new HashMap<>();
|
||||||
|
for (String number : numbers) {
|
||||||
|
Optional<Account> account = getAccount(number);
|
||||||
|
if (account.isPresent())
|
||||||
|
result.put(number, account.get());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pair<Map<String, Account>, List<String>> getAccountsForDevices(Map<String, Set<Long>> destinations) {
|
||||||
|
List<String> numbersMissingDevices = new LinkedList<>();
|
||||||
|
Map<String, Account> localAccounts = getAllAccounts(destinations.keySet());
|
||||||
|
|
||||||
|
for (String number : destinations.keySet()) {
|
||||||
|
if (localAccounts.get(number) == null)
|
||||||
|
numbersMissingDevices.add(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<Account> localAccountIterator = localAccounts.values().iterator();
|
||||||
|
while (localAccountIterator.hasNext()) {
|
||||||
|
Account account = localAccountIterator.next();
|
||||||
|
if (!account.hasAllDeviceIds(destinations.get(account.getNumber()))) {
|
||||||
|
numbersMissingDevices.add(account.getNumber());
|
||||||
|
localAccountIterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Pair<>(localAccounts, numbersMissingDevices);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDirectory(Device device) {
|
private void updateDirectory(Device device) {
|
||||||
|
|
|
@ -125,15 +125,19 @@ public class Device implements Serializable {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFetchesMessages(boolean fetchesMessages) {
|
public boolean isActive() {
|
||||||
this.fetchesMessages = fetchesMessages;
|
return fetchesMessages || !Util.isEmpty(getApnRegistrationId()) || !Util.isEmpty(getGcmRegistrationId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getFetchesMessages() {
|
public boolean getFetchesMessages() {
|
||||||
return fetchesMessages;
|
return fetchesMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isActive() {
|
public void setFetchesMessages(boolean fetchesMessages) {
|
||||||
return getFetchesMessages() || !Util.isEmpty(getApnRegistrationId()) || !Util.isEmpty(getGcmRegistrationId());
|
this.fetchesMessages = fetchesMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBackwardsCompatibleNumberEncoding() {
|
||||||
|
return deviceId == 1 ? number : (number + "." + deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,9 +79,9 @@ public abstract class Keys {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
@Transaction(TransactionIsolationLevel.SERIALIZABLE)
|
||||||
public UnstructuredPreKeyList get(String number, List<Device> devices) {
|
public UnstructuredPreKeyList get(String number, Account account) {
|
||||||
List<PreKey> preKeys = new LinkedList<>();
|
List<PreKey> preKeys = new LinkedList<>();
|
||||||
for (Device device : devices) {
|
for (Device device : account.getDevices()) {
|
||||||
PreKey preKey = retrieveFirst(number, device.getDeviceId());
|
PreKey preKey = retrieveFirst(number, device.getDeviceId());
|
||||||
if (preKey != null)
|
if (preKey != null)
|
||||||
preKeys.add(preKey);
|
preKeys.add(preKey);
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class DirectoryUpdater {
|
||||||
BatchOperationHandle batchOperation = directory.startBatchOperation();
|
BatchOperationHandle batchOperation = directory.startBatchOperation();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Iterator<Device> accounts = accountsManager.getAllMasterAccounts();
|
Iterator<Device> accounts = accountsManager.getAllMasterDevices();
|
||||||
|
|
||||||
if (accounts == null)
|
if (accounts == null)
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
||||||
|
@ -80,7 +81,7 @@ public class AccountControllerTest extends ResourceTest {
|
||||||
((Device)invocation.getArguments()[0]).setDeviceId(2);
|
((Device)invocation.getArguments()[0]).setDeviceId(2);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).when(accountsManager).createAccountOnExistingNumber(any(Device.class));
|
}).when(accountsManager).provisionDevice(any(Device.class));
|
||||||
|
|
||||||
addResource(new DumbVerificationAccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender));
|
addResource(new DumbVerificationAccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender));
|
||||||
}
|
}
|
||||||
|
@ -107,7 +108,7 @@ public class AccountControllerTest extends ResourceTest {
|
||||||
|
|
||||||
assertThat(response.getStatus()).isEqualTo(204);
|
assertThat(response.getStatus()).isEqualTo(204);
|
||||||
|
|
||||||
verify(accountsManager).createResetNumber(isA(Device.class));
|
verify(accountsManager).create(isA(Account.class));
|
||||||
|
|
||||||
ArgumentCaptor<String> number = ArgumentCaptor.forClass(String.class);
|
ArgumentCaptor<String> number = ArgumentCaptor.forClass(String.class);
|
||||||
verify(pendingAccountsManager).remove(number.capture());
|
verify(pendingAccountsManager).remove(number.capture());
|
||||||
|
|
|
@ -78,7 +78,7 @@ public class DeviceControllerTest extends ResourceTest {
|
||||||
((Device) invocation.getArguments()[0]).setDeviceId(2);
|
((Device) invocation.getArguments()[0]).setDeviceId(2);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).when(accountsManager).createAccountOnExistingNumber(any(Device.class));
|
}).when(accountsManager).provisionDevice(any(Device.class));
|
||||||
|
|
||||||
addResource(new DumbVerificationDeviceController(pendingDevicesManager, accountsManager, rateLimiters));
|
addResource(new DumbVerificationDeviceController(pendingDevicesManager, accountsManager, rateLimiters));
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ public class DeviceControllerTest extends ResourceTest {
|
||||||
assertThat(deviceId).isNotEqualTo(AuthHelper.DEFAULT_DEVICE_ID);
|
assertThat(deviceId).isNotEqualTo(AuthHelper.DEFAULT_DEVICE_ID);
|
||||||
|
|
||||||
ArgumentCaptor<Device> newAccount = ArgumentCaptor.forClass(Device.class);
|
ArgumentCaptor<Device> newAccount = ArgumentCaptor.forClass(Device.class);
|
||||||
verify(accountsManager).createAccountOnExistingNumber(newAccount.capture());
|
verify(accountsManager).provisionDevice(newAccount.capture());
|
||||||
assertThat(deviceId).isEqualTo(newAccount.getValue().getDeviceId());
|
assertThat(deviceId).isEqualTo(newAccount.getValue().getDeviceId());
|
||||||
|
|
||||||
ArgumentCaptor<String> number = ArgumentCaptor.forClass(String.class);
|
ArgumentCaptor<String> number = ArgumentCaptor.forClass(String.class);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
import com.sun.jersey.api.client.ClientResponse;
|
import com.sun.jersey.api.client.ClientResponse;
|
||||||
import com.sun.jersey.api.client.GenericType;
|
import com.sun.jersey.api.client.GenericType;
|
||||||
import com.yammer.dropwizard.testing.ResourceTest;
|
import com.yammer.dropwizard.testing.ResourceTest;
|
||||||
|
@ -9,6 +10,7 @@ import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||||
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
import org.whispersystems.textsecuregcm.entities.UnstructuredPreKeyList;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||||
|
@ -31,6 +33,7 @@ public class KeyControllerTest extends ResourceTest {
|
||||||
private final Keys keys = mock(Keys.class);
|
private final Keys keys = mock(Keys.class);
|
||||||
|
|
||||||
Device[] fakeDevice;
|
Device[] fakeDevice;
|
||||||
|
Account existsAccount;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUpResources() {
|
protected void setUpResources() {
|
||||||
|
@ -43,17 +46,18 @@ public class KeyControllerTest extends ResourceTest {
|
||||||
fakeDevice = new Device[2];
|
fakeDevice = new Device[2];
|
||||||
fakeDevice[0] = mock(Device.class);
|
fakeDevice[0] = mock(Device.class);
|
||||||
fakeDevice[1] = mock(Device.class);
|
fakeDevice[1] = mock(Device.class);
|
||||||
|
existsAccount = new Account(EXISTS_NUMBER, true, Arrays.asList(fakeDevice[0], fakeDevice[1]));
|
||||||
|
|
||||||
when(rateLimiters.getPreKeysLimiter()).thenReturn(rateLimiter);
|
when(rateLimiters.getPreKeysLimiter()).thenReturn(rateLimiter);
|
||||||
|
|
||||||
when(keys.get(eq(EXISTS_NUMBER), anyList())).thenReturn(new UnstructuredPreKeyList(Arrays.asList(SAMPLE_KEY, SAMPLE_KEY2)));
|
when(keys.get(eq(EXISTS_NUMBER), isA(Account.class))).thenReturn(new UnstructuredPreKeyList(Arrays.asList(SAMPLE_KEY, SAMPLE_KEY2)));
|
||||||
when(keys.get(eq(NOT_EXISTS_NUMBER), anyList())).thenReturn(null);
|
when(keys.get(eq(NOT_EXISTS_NUMBER), isA(Account.class))).thenReturn(null);
|
||||||
|
|
||||||
when(fakeDevice[0].getDeviceId()).thenReturn(AuthHelper.DEFAULT_DEVICE_ID);
|
when(fakeDevice[0].getDeviceId()).thenReturn(AuthHelper.DEFAULT_DEVICE_ID);
|
||||||
when(fakeDevice[1].getDeviceId()).thenReturn((long) 2);
|
when(fakeDevice[1].getDeviceId()).thenReturn((long) 2);
|
||||||
|
|
||||||
when(accounts.getAllByNumber(EXISTS_NUMBER)).thenReturn(Arrays.asList(fakeDevice[0], fakeDevice[1]));
|
when(accounts.getAccount(EXISTS_NUMBER)).thenReturn(Optional.of(existsAccount));
|
||||||
when(accounts.getAllByNumber(NOT_EXISTS_NUMBER)).thenReturn(new LinkedList<Device>());
|
when(accounts.getAccount(NOT_EXISTS_NUMBER)).thenReturn(Optional.<Account>absent());
|
||||||
|
|
||||||
addResource(new KeysController(rateLimiters, keys, accounts, null));
|
addResource(new KeysController(rateLimiters, keys, accounts, null));
|
||||||
}
|
}
|
||||||
|
@ -71,7 +75,7 @@ public class KeyControllerTest extends ResourceTest {
|
||||||
assertThat(result.getId() == 0);
|
assertThat(result.getId() == 0);
|
||||||
assertThat(result.getNumber() == null);
|
assertThat(result.getNumber() == null);
|
||||||
|
|
||||||
verify(keys).get(eq(EXISTS_NUMBER), eq(Arrays.asList(fakeDevice)));
|
verify(keys).get(eq(EXISTS_NUMBER), eq(existsAccount));
|
||||||
verifyNoMoreInteractions(keys);
|
verifyNoMoreInteractions(keys);
|
||||||
|
|
||||||
List<PreKey> results = client().resource(String.format("/v1/keys/%s?multikeys", EXISTS_NUMBER))
|
List<PreKey> results = client().resource(String.format("/v1/keys/%s?multikeys", EXISTS_NUMBER))
|
||||||
|
@ -95,7 +99,7 @@ public class KeyControllerTest extends ResourceTest {
|
||||||
assertThat(result.getId() == 1);
|
assertThat(result.getId() == 1);
|
||||||
assertThat(result.getNumber() == null);
|
assertThat(result.getNumber() == null);
|
||||||
|
|
||||||
verify(keys, times(2)).get(eq(EXISTS_NUMBER), eq(Arrays.asList(fakeDevice[0], fakeDevice[1])));
|
verify(keys, times(2)).get(eq(EXISTS_NUMBER), eq(existsAccount));
|
||||||
verifyNoMoreInteractions(keys);
|
verifyNoMoreInteractions(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,8 +110,7 @@ public class KeyControllerTest extends ResourceTest {
|
||||||
.get(ClientResponse.class);
|
.get(ClientResponse.class);
|
||||||
|
|
||||||
assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(404);
|
assertThat(response.getClientResponseStatus().getStatusCode()).isEqualTo(404);
|
||||||
|
verifyNoMoreInteractions(keys);
|
||||||
verify(keys).get(NOT_EXISTS_NUMBER, new LinkedList<Device>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue