0
0
mirror of https://github.com/signalapp/Signal-Server.git synced 2024-09-20 03:52:16 +02:00

Support for GCM stored messages.

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-02-04 14:19:23 -08:00
parent 1fef812c67
commit aa84ab66af
9 changed files with 105 additions and 46 deletions

View File

@ -129,7 +129,7 @@
<dependency>
<groupId>org.whispersystems</groupId>
<artifactId>websocket-resources</artifactId>
<version>0.2.0</version>
<version>0.2.1</version>
</dependency>

View File

@ -197,10 +197,14 @@ public class AccountController {
@PUT
@Path("/gcm/")
@Consumes(MediaType.APPLICATION_JSON)
public void setGcmRegistrationId(@Auth Account account, @Valid GcmRegistrationId registrationId) {
public void setGcmRegistrationId(@Auth Account account, @Valid GcmRegistrationId registrationId) {
Device device = account.getAuthenticatedDevice().get();
device.setApnId(null);
device.setGcmId(registrationId.getGcmRegistrationId());
if (registrationId.isWebSocketChannel()) device.setFetchesMessages(true);
else device.setFetchesMessages(false);
accounts.update(account);
}
@ -210,6 +214,7 @@ public class AccountController {
public void deleteGcmRegistrationId(@Auth Account account) {
Device device = account.getAuthenticatedDevice().get();
device.setGcmId(null);
device.setFetchesMessages(false);
accounts.update(account);
}
@ -221,6 +226,7 @@ public class AccountController {
Device device = account.getAuthenticatedDevice().get();
device.setApnId(registrationId.getApnRegistrationId());
device.setGcmId(null);
device.setFetchesMessages(true);
accounts.update(account);
}
@ -230,6 +236,25 @@ public class AccountController {
public void deleteApnRegistrationId(@Auth Account account) {
Device device = account.getAuthenticatedDevice().get();
device.setApnId(null);
device.setFetchesMessages(false);
accounts.update(account);
}
@Timed
@PUT
@Path("/wsc/")
public void setWebSocketChannelSupported(@Auth Account account) {
Device device = account.getAuthenticatedDevice().get();
device.setFetchesMessages(true);
accounts.update(account);
}
@Timed
@DELETE
@Path("/wsc/")
public void deleteWebSocketChannel(@Auth Account account) {
Device device = account.getAuthenticatedDevice().get();
device.setFetchesMessages(false);
accounts.update(account);
}

View File

@ -20,20 +20,23 @@ public class GcmMessage {
private int deviceId;
@JsonProperty
@NotEmpty
private String message;
@JsonProperty
private boolean receipt;
@JsonProperty
private boolean notification;
public GcmMessage() {}
public GcmMessage(String gcmId, String number, int deviceId, String message, boolean receipt) {
this.gcmId = gcmId;
this.number = number;
this.deviceId = deviceId;
this.message = message;
this.receipt = receipt;
public GcmMessage(String gcmId, String number, int deviceId, String message, boolean receipt, boolean notification) {
this.gcmId = gcmId;
this.number = number;
this.deviceId = deviceId;
this.message = message;
this.receipt = receipt;
this.notification = notification;
}
}

View File

@ -25,9 +25,15 @@ public class GcmRegistrationId {
@NotEmpty
private String gcmRegistrationId;
@JsonProperty
private boolean webSocketChannel;
public String getGcmRegistrationId() {
return gcmRegistrationId;
}
public boolean isWebSocketChannel() {
return webSocketChannel;
}
}

View File

@ -57,6 +57,13 @@ public class PushSender {
private void sendGcmMessage(Account account, Device device, OutgoingMessageSignal message)
throws TransientPushFailureException, NotPushRegisteredException
{
if (device.getFetchesMessages()) sendNotificationGcmMessage(account, device, message);
else sendPayloadGcmMessage(account, device, message);
}
private void sendPayloadGcmMessage(Account account, Device device, OutgoingMessageSignal message)
throws TransientPushFailureException, NotPushRegisteredException
{
try {
String number = account.getNumber();
@ -65,7 +72,7 @@ public class PushSender {
boolean isReceipt = message.getType() == OutgoingMessageSignal.Type.RECEIPT_VALUE;
EncryptedOutgoingMessage encryptedMessage = new EncryptedOutgoingMessage(message, device.getSignalingKey());
GcmMessage gcmMessage = new GcmMessage(registrationId, number, (int) deviceId,
encryptedMessage.toEncodedString(), isReceipt);
encryptedMessage.toEncodedString(), isReceipt, false);
pushServiceClient.send(gcmMessage);
} catch (CryptoEncodingException e) {
@ -73,10 +80,26 @@ public class PushSender {
}
}
private void sendNotificationGcmMessage(Account account, Device device, OutgoingMessageSignal message)
throws TransientPushFailureException
{
DeliveryStatus deliveryStatus = webSocketSender.sendMessage(account, device, message, WebsocketSender.Type.GCM);
if (!deliveryStatus.isDelivered()) {
GcmMessage gcmMessage = new GcmMessage(device.getGcmId(), account.getNumber(),
(int)device.getId(), "", false, true);
pushServiceClient.send(gcmMessage);
} else {
logger.warn("Delivered!");
}
}
private void sendApnMessage(Account account, Device device, OutgoingMessageSignal outgoingMessage)
throws TransientPushFailureException
{
DeliveryStatus deliveryStatus = webSocketSender.sendMessage(account, device, outgoingMessage, true);
DeliveryStatus deliveryStatus = webSocketSender.sendMessage(account, device, outgoingMessage, WebsocketSender.Type.APN);
if (!deliveryStatus.isDelivered() && outgoingMessage.getType() != OutgoingMessageSignal.Type.RECEIPT_VALUE) {
ApnMessage apnMessage = new ApnMessage(device.getApnId(), account.getNumber(), (int)device.getId(),
@ -87,6 +110,6 @@ public class PushSender {
private void sendWebSocketMessage(Account account, Device device, OutgoingMessageSignal outgoingMessage)
{
webSocketSender.sendMessage(account, device, outgoingMessage, false);
webSocketSender.sendMessage(account, device, outgoingMessage, WebsocketSender.Type.WEB);
}
}

View File

@ -36,6 +36,12 @@ import static org.whispersystems.textsecuregcm.storage.PubSubProtos.PubSubMessag
public class WebsocketSender {
public static enum Type {
APN,
GCM,
WEB
}
private static final Logger logger = LoggerFactory.getLogger(WebsocketSender.class);
private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
@ -46,6 +52,9 @@ public class WebsocketSender {
private final Meter apnOnlineMeter = metricRegistry.meter(name(getClass(), "apn_online" ));
private final Meter apnOfflineMeter = metricRegistry.meter(name(getClass(), "apn_offline"));
private final Meter gcmOnlineMeter = metricRegistry.meter(name(getClass(), "gcm_online" ));
private final Meter gcmOfflineMeter = metricRegistry.meter(name(getClass(), "gcm_offline"));
private final Meter provisioningOnlineMeter = metricRegistry.meter(name(getClass(), "provisioning_online" ));
private final Meter provisioningOfflineMeter = metricRegistry.meter(name(getClass(), "provisioning_offline"));
@ -57,7 +66,7 @@ public class WebsocketSender {
this.pubSubManager = pubSubManager;
}
public DeliveryStatus sendMessage(Account account, Device device, OutgoingMessageSignal message, boolean apn) {
public DeliveryStatus sendMessage(Account account, Device device, OutgoingMessageSignal message, Type channel) {
WebsocketAddress address = new WebsocketAddress(account.getNumber(), device.getId());
PubSubMessage pubSubMessage = PubSubMessage.newBuilder()
.setType(PubSubMessage.Type.DELIVER)
@ -65,13 +74,15 @@ public class WebsocketSender {
.build();
if (pubSubManager.publish(address, pubSubMessage)) {
if (apn) apnOnlineMeter.mark();
else websocketOnlineMeter.mark();
if (channel == Type.APN) apnOnlineMeter.mark();
else if (channel == Type.GCM) gcmOnlineMeter.mark();
else websocketOnlineMeter.mark();
return new DeliveryStatus(true, 0);
} else {
if (apn) apnOfflineMeter.mark();
else websocketOfflineMeter.mark();
if (channel == Type.APN) apnOfflineMeter.mark();
else if (channel == Type.GCM) gcmOfflineMeter.mark();
else websocketOfflineMeter.mark();
int queueDepth = messagesManager.insert(account.getNumber(), device.getId(), message);
pubSubManager.publish(address, PubSubMessage.newBuilder()

View File

@ -33,30 +33,17 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
@Override
public void onWebSocketConnect(WebSocketSessionContext context) {
Optional<Account> account = context.getAuthenticated(Account.class);
Account account = context.getAuthenticated(Account.class).get();
Device device = account.getAuthenticatedDevice().get();
if (!account.isPresent()) {
logger.debug("WS Connection with no authentication...");
context.getClient().close(4001, "Authentication failed");
return;
}
Optional<Device> device = account.get().getAuthenticatedDevice();
if (!device.isPresent()) {
logger.debug("WS Connection with no authenticated device...");
context.getClient().close(4001, "Device authentication failed");
return;
}
if (device.get().getLastSeen() != Util.todayInMillis()) {
device.get().setLastSeen(Util.todayInMillis());
accountsManager.update(account.get());
if (device.getLastSeen() != Util.todayInMillis()) {
device.setLastSeen(Util.todayInMillis());
accountsManager.update(account);
}
final WebSocketConnection connection = new WebSocketConnection(accountsManager, pushSender,
messagesManager, pubSubManager,
account.get(), device.get(),
account, device,
context.getClient());
connection.onConnected();

View File

@ -34,7 +34,9 @@ public class WebSocketAccountAuthenticator implements WebSocketAuthenticator<Acc
return Optional.absent();
}
BasicCredentials credentials = new BasicCredentials(usernames[0], passwords[0]);
BasicCredentials credentials = new BasicCredentials(usernames[0].replace(" ", "+"),
passwords[0].replace(" ", "+"));
return accountAuthenticator.authenticate(credentials);
} catch (io.dropwizard.auth.AuthenticationException e) {
throw new AuthenticationException(e);

View File

@ -32,6 +32,7 @@ import java.util.List;
import java.util.Set;
import io.dropwizard.auth.basic.BasicCredentials;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
@ -101,15 +102,16 @@ public class WebSocketConnectionTest {
}});
account = webSocketAuthenticator.authenticate(upgradeRequest);
when(sessionContext.getAuthenticated(Account.class)).thenReturn(account);
WebSocketClient client = mock(WebSocketClient.class);
when(sessionContext.getClient()).thenReturn(client);
connectListener.onWebSocketConnect(sessionContext);
verify(sessionContext, times(1)).addListener(any(WebSocketSessionContext.WebSocketEventListener.class));
verify(client).close(eq(4001), anyString());
assertFalse(account.isPresent());
// when(sessionContext.getAuthenticated(Account.class)).thenReturn(account);
//
// WebSocketClient client = mock(WebSocketClient.class);
// when(sessionContext.getClient()).thenReturn(client);
//
// connectListener.onWebSocketConnect(sessionContext);
//
// verify(sessionContext, times(1)).addListener(any(WebSocketSessionContext.WebSocketEventListener.class));
// verify(client).close(eq(4001), anyString());
}
@Test