Support for expiration on APN messages.

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-06-06 21:04:08 -07:00
parent 6f67a812dc
commit 83078a48ab
5 changed files with 42 additions and 22 deletions

View File

@ -25,7 +25,7 @@ message OutgoingMessageSignal {
CIPHERTEXT = 1; CIPHERTEXT = 1;
KEY_EXCHANGE = 2; KEY_EXCHANGE = 2;
PREKEY_BUNDLE = 3; PREKEY_BUNDLE = 3;
PLAINTEXT = 4; // PLAINTEXT = 4;
RECEIPT = 5; RECEIPT = 5;
} }

View File

@ -8,6 +8,9 @@ import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
public class ApnMessage { public class ApnMessage {
public static long MAX_EXPIRATION = Integer.MAX_VALUE * 1000L;
@JsonProperty @JsonProperty
@NotEmpty @NotEmpty
private String apnId; private String apnId;
@ -28,22 +31,27 @@ public class ApnMessage {
@NotNull @NotNull
private boolean voip; private boolean voip;
@JsonProperty
private long expirationTime;
public ApnMessage() {} public ApnMessage() {}
public ApnMessage(String apnId, String number, int deviceId, String message, boolean voip) { public ApnMessage(String apnId, String number, int deviceId, String message, boolean voip, long expirationTime) {
this.apnId = apnId; this.apnId = apnId;
this.number = number; this.number = number;
this.deviceId = deviceId; this.deviceId = deviceId;
this.message = message; this.message = message;
this.voip = voip; this.voip = voip;
this.expirationTime = expirationTime;
} }
public ApnMessage(ApnMessage copy, String apnId, boolean voip) { public ApnMessage(ApnMessage copy, String apnId, boolean voip, long expirationTime) {
this.apnId = apnId; this.apnId = apnId;
this.number = copy.number; this.number = copy.number;
this.deviceId = copy.deviceId; this.deviceId = copy.deviceId;
this.message = copy.message; this.message = copy.message;
this.voip = voip; this.voip = voip;
this.expirationTime = expirationTime;
} }
@VisibleForTesting @VisibleForTesting
@ -60,4 +68,9 @@ public class ApnMessage {
public String getMessage() { public String getMessage() {
return message; return message;
} }
@VisibleForTesting
public long getExpirationTime() {
return expirationTime;
}
} }

View File

@ -71,7 +71,9 @@ public class ApnFallbackManager implements Managed, Runnable {
try { try {
Entry<WebsocketAddress, ApnFallbackTask> taskEntry = taskQueue.get(); Entry<WebsocketAddress, ApnFallbackTask> taskEntry = taskQueue.get();
ApnFallbackTask task = taskEntry.getValue(); ApnFallbackTask task = taskEntry.getValue();
pushServiceClient.send(new ApnMessage(task.getMessage(), task.getApnId(), false));
pushServiceClient.send(new ApnMessage(task.getMessage(), task.getApnId(),
false, ApnMessage.MAX_EXPIRATION));
} catch (Throwable e) { } catch (Throwable e) {
logger.warn("ApnFallbackThread", e); logger.warn("ApnFallbackThread", e);
} }

View File

@ -30,6 +30,7 @@ import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.concurrent.TimeUnit;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal; import static org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
@ -107,16 +108,19 @@ public class PushSender {
DeliveryStatus deliveryStatus = webSocketSender.sendMessage(account, device, outgoingMessage, WebsocketSender.Type.APN); DeliveryStatus deliveryStatus = webSocketSender.sendMessage(account, device, outgoingMessage, WebsocketSender.Type.APN);
if (!deliveryStatus.isDelivered() && outgoingMessage.getType() != OutgoingMessageSignal.Type.RECEIPT_VALUE) { if (!deliveryStatus.isDelivered() && outgoingMessage.getType() != OutgoingMessageSignal.Type.RECEIPT_VALUE) {
String apnId = Util.isEmpty(device.getVoipApnId()) ? device.getApnId() : device.getVoipApnId(); ApnMessage apnMessage;
boolean isVoip = !Util.isEmpty(device.getVoipApnId());
ApnMessage apnMessage = new ApnMessage(apnId, account.getNumber(), (int)device.getId(), if (!Util.isEmpty(device.getVoipApnId())) {
String.format(APN_PAYLOAD, deliveryStatus.getMessageQueueDepth()), apnMessage = new ApnMessage(device.getVoipApnId(), account.getNumber(), (int)device.getId(),
isVoip); String.format(APN_PAYLOAD, deliveryStatus.getMessageQueueDepth()),
true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30));
if (isVoip) {
apnFallbackManager.schedule(new WebsocketAddress(account.getNumber(), device.getId()), apnFallbackManager.schedule(new WebsocketAddress(account.getNumber(), device.getId()),
new ApnFallbackTask(device.getApnId(), apnMessage)); new ApnFallbackTask(device.getApnId(), apnMessage));
} else {
apnMessage = new ApnMessage(device.getApnId(), account.getNumber(), (int)device.getId(),
String.format(APN_PAYLOAD, deliveryStatus.getMessageQueueDepth()),
false, ApnMessage.MAX_EXPIRATION);
} }
pushServiceClient.send(apnMessage); pushServiceClient.send(apnMessage);

View File

@ -19,7 +19,7 @@ public class ApnFallbackManagerTest {
public void testFullFallback() throws Exception { public void testFullFallback() throws Exception {
PushServiceClient pushServiceClient = mock(PushServiceClient.class); PushServiceClient pushServiceClient = mock(PushServiceClient.class);
WebsocketAddress address = mock(WebsocketAddress.class ); WebsocketAddress address = mock(WebsocketAddress.class );
ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true); ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true, 1111);
ApnFallbackTask task = new ApnFallbackTask("foo", message, 500); ApnFallbackTask task = new ApnFallbackTask("foo", message, 500);
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient); ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient);
@ -35,13 +35,14 @@ public class ApnFallbackManagerTest {
assertEquals(captor.getValue().getMessage(), message.getMessage()); assertEquals(captor.getValue().getMessage(), message.getMessage());
assertEquals(captor.getValue().getApnId(), task.getApnId()); assertEquals(captor.getValue().getApnId(), task.getApnId());
assertFalse(captor.getValue().isVoip()); assertFalse(captor.getValue().isVoip());
assertEquals(captor.getValue().getExpirationTime(), Integer.MAX_VALUE * 1000L);
} }
@Test @Test
public void testNoFallback() throws Exception { public void testNoFallback() throws Exception {
PushServiceClient pushServiceClient = mock(PushServiceClient.class); PushServiceClient pushServiceClient = mock(PushServiceClient.class);
WebsocketAddress address = mock(WebsocketAddress.class ); WebsocketAddress address = mock(WebsocketAddress.class );
ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true); ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true, 5555);
ApnFallbackTask task = new ApnFallbackTask ("foo", message, 500); ApnFallbackTask task = new ApnFallbackTask ("foo", message, 500);
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient); ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient);