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

@@ -25,6 +25,7 @@ import com.yammer.metrics.core.Meter;
import org.bouncycastle.openssl.PEMReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.entities.CryptoEncodingException;
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
import org.whispersystems.textsecuregcm.util.Util;
@@ -32,7 +33,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
@@ -66,12 +66,12 @@ public class APNSender {
}
public void sendMessage(String registrationId, EncryptedOutgoingMessage message)
throws IOException
throws TransientPushFailureException, NotPushRegisteredException
{
try {
if (!apnService.isPresent()) {
failure.mark();
throw new IOException("APN access not configured!");
throw new TransientPushFailureException("APN access not configured!");
}
String payload = APNS.newPayload()
@@ -83,12 +83,12 @@ public class APNSender {
apnService.get().push(registrationId, payload);
success.mark();
} catch (MalformedURLException mue) {
throw new AssertionError(mue);
} catch (NetworkIOException nioe) {
logger.warn("Network Error", nioe);
failure.mark();
throw new IOException("Error sending APN");
throw new TransientPushFailureException(nioe);
} catch (CryptoEncodingException e) {
throw new NotPushRegisteredException(e);
}
}

View File

@@ -22,7 +22,7 @@ import com.google.android.gcm.server.Result;
import com.google.android.gcm.server.Sender;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Meter;
import org.whispersystems.textsecuregcm.controllers.NoSuchUserException;
import org.whispersystems.textsecuregcm.entities.CryptoEncodingException;
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
import java.io.IOException;
@@ -40,24 +40,30 @@ public class GCMSender {
}
public String sendMessage(String gcmRegistrationId, EncryptedOutgoingMessage outgoingMessage)
throws IOException, NoSuchUserException
throws NotPushRegisteredException, TransientPushFailureException
{
Message gcmMessage = new Message.Builder().addData("type", "message")
.addData("message", outgoingMessage.serialize())
.build();
try {
Message gcmMessage = new Message.Builder().addData("type", "message")
.addData("message", outgoingMessage.serialize())
.build();
Result result = sender.send(gcmMessage, gcmRegistrationId, 5);
Result result = sender.send(gcmMessage, gcmRegistrationId, 5);
if (result.getMessageId() != null) {
success.mark();
return result.getCanonicalRegistrationId();
} else {
failure.mark();
if (result.getErrorCodeName().equals(Constants.ERROR_NOT_REGISTERED)) {
throw new NoSuchUserException("User no longer registered with GCM.");
if (result.getMessageId() != null) {
success.mark();
return result.getCanonicalRegistrationId();
} else {
throw new IOException("GCM Failed: " + result.getErrorCodeName());
failure.mark();
if (result.getErrorCodeName().equals(Constants.ERROR_NOT_REGISTERED)) {
throw new NotPushRegisteredException("Device no longer registered with GCM.");
} else {
throw new TransientPushFailureException("GCM Failed: " + result.getErrorCodeName());
}
}
} catch (IOException e) {
throw new TransientPushFailureException(e);
} catch (CryptoEncodingException e) {
throw new NotPushRegisteredException(e);
}
}
}

View File

@@ -0,0 +1,11 @@
package org.whispersystems.textsecuregcm.push;
public class NotPushRegisteredException extends Exception {
public NotPushRegisteredException(String s) {
super(s);
}
public NotPushRegisteredException(Exception e) {
super(e);
}
}

View File

@@ -16,92 +16,94 @@
*/
package org.whispersystems.textsecuregcm.push;
import com.google.common.base.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.ApnConfiguration;
import org.whispersystems.textsecuregcm.configuration.GcmConfiguration;
import org.whispersystems.textsecuregcm.controllers.NoSuchUserException;
import org.whispersystems.textsecuregcm.entities.CryptoEncodingException;
import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.StoredMessageManager;
import org.whispersystems.textsecuregcm.util.Pair;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class PushSender {
private final Logger logger = LoggerFactory.getLogger(PushSender.class);
private final AccountsManager accounts;
private final GCMSender gcmSender;
private final APNSender apnSender;
private final AccountsManager accounts;
private final GCMSender gcmSender;
private final APNSender apnSender;
private final StoredMessageManager storedMessageManager;
public PushSender(GcmConfiguration gcmConfiguration,
ApnConfiguration apnConfiguration,
StoredMessageManager storedMessageManager,
AccountsManager accounts,
DirectoryManager directory)
AccountsManager accounts)
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException
{
this.accounts = accounts;
this.accounts = accounts;
this.storedMessageManager = storedMessageManager;
this.gcmSender = new GCMSender(gcmConfiguration.getApiKey());
this.apnSender = new APNSender(apnConfiguration.getCertificate(), apnConfiguration.getKey());
}
public void sendMessage(Device device, MessageProtos.OutgoingMessageSignal outgoingMessage)
throws IOException, NoSuchUserException
public void sendMessage(Account account, Device device, MessageProtos.OutgoingMessageSignal outgoingMessage)
throws NotPushRegisteredException, TransientPushFailureException
{
String signalingKey = device.getSignalingKey();
EncryptedOutgoingMessage message = new EncryptedOutgoingMessage(outgoingMessage, signalingKey);
String signalingKey = device.getSignalingKey();
EncryptedOutgoingMessage message = new EncryptedOutgoingMessage(outgoingMessage, signalingKey);
if (device.getGcmRegistrationId() != null) sendGcmMessage(device, message);
else if (device.getApnRegistrationId() != null) sendApnMessage(device, message);
else if (device.getFetchesMessages()) storeFetchedMessage(device, message);
else throw new NoSuchUserException("No push identifier!");
if (device.getGcmId() != null) sendGcmMessage(account, device, message);
else if (device.getApnId() != null) sendApnMessage(account, device, message);
else if (device.getFetchesMessages()) storeFetchedMessage(device, message);
else throw new NotPushRegisteredException("No delivery possible!");
}
private void sendGcmMessage(Device device, EncryptedOutgoingMessage outgoingMessage)
throws IOException, NoSuchUserException
private void sendGcmMessage(Account account, Device device, EncryptedOutgoingMessage outgoingMessage)
throws NotPushRegisteredException, TransientPushFailureException
{
try {
String canonicalId = gcmSender.sendMessage(device.getGcmRegistrationId(),
outgoingMessage);
String canonicalId = gcmSender.sendMessage(device.getGcmId(), outgoingMessage);
if (canonicalId != null) {
device.setGcmRegistrationId(canonicalId);
accounts.update(device);
device.setGcmId(canonicalId);
accounts.update(account);
}
} catch (NoSuchUserException e) {
} catch (NotPushRegisteredException e) {
logger.debug("No Such User", e);
device.setGcmRegistrationId(null);
accounts.update(device);
throw new NoSuchUserException("User no longer exists in GCM.");
device.setGcmId(null);
accounts.update(account);
throw new NotPushRegisteredException(e);
}
}
private void sendApnMessage(Device device, EncryptedOutgoingMessage outgoingMessage)
throws IOException
private void sendApnMessage(Account account, Device device, EncryptedOutgoingMessage outgoingMessage)
throws TransientPushFailureException, NotPushRegisteredException
{
apnSender.sendMessage(device.getApnRegistrationId(), outgoingMessage);
try {
apnSender.sendMessage(device.getApnId(), outgoingMessage);
} catch (NotPushRegisteredException e) {
device.setApnId(null);
accounts.update(account);
throw new NotPushRegisteredException(e);
}
}
private void storeFetchedMessage(Device device, EncryptedOutgoingMessage outgoingMessage) throws IOException {
storedMessageManager.storeMessage(device, outgoingMessage);
private void storeFetchedMessage(Device device, EncryptedOutgoingMessage outgoingMessage)
throws NotPushRegisteredException
{
try {
storedMessageManager.storeMessage(device, outgoingMessage);
} catch (CryptoEncodingException e) {
throw new NotPushRegisteredException(e);
}
}
}

View File

@@ -0,0 +1,11 @@
package org.whispersystems.textsecuregcm.push;
public class TransientPushFailureException extends Exception {
public TransientPushFailureException(String s) {
super(s);
}
public TransientPushFailureException(Exception e) {
super(e);
}
}