mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-25 18:08:02 +01:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user