Try a second fallback before APNS

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2017-04-04 14:55:50 -07:00
parent 074fd14849
commit 818c5a9cf5
3 changed files with 60 additions and 15 deletions

View File

@ -30,6 +30,8 @@ public class ApnFallbackManager implements Managed, Runnable, DispatchChannel {
private static final Logger logger = LoggerFactory.getLogger(ApnFallbackManager.class); private static final Logger logger = LoggerFactory.getLogger(ApnFallbackManager.class);
public static final int FALLBACK_DURATION = 15;
private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
private static final Meter voipOneSuccess = metricRegistry.meter(name(ApnFallbackManager.class, "voip_one_success")); private static final Meter voipOneSuccess = metricRegistry.meter(name(ApnFallbackManager.class, "voip_one_success"));
private static final Meter voipOneDelivery = metricRegistry.meter(name(ApnFallbackManager.class, "voip_one_failure")); private static final Meter voipOneDelivery = metricRegistry.meter(name(ApnFallbackManager.class, "voip_one_failure"));
@ -57,6 +59,12 @@ public class ApnFallbackManager implements Managed, Runnable, DispatchChannel {
} }
} }
private void scheduleRetry(final WebsocketAddress address, ApnFallbackTask task) {
if (taskQueue.putIfMissing(address, task)) {
pubSubManager.subscribe(new WebSocketConnectionInfo(address), this);
}
}
private void cancel(WebsocketAddress address) { private void cancel(WebsocketAddress address) {
ApnFallbackTask task = taskQueue.remove(address); ApnFallbackTask task = taskQueue.remove(address);
@ -84,9 +92,17 @@ public class ApnFallbackManager implements Managed, Runnable, DispatchChannel {
Entry<WebsocketAddress, ApnFallbackTask> taskEntry = taskQueue.get(); Entry<WebsocketAddress, ApnFallbackTask> taskEntry = taskQueue.get();
ApnFallbackTask task = taskEntry.getValue(); ApnFallbackTask task = taskEntry.getValue();
pubSubManager.unsubscribe(new WebSocketConnectionInfo(taskEntry.getKey()), this); ApnMessage message;
pushServiceClient.send(new ApnMessage(task.getMessage(), task.getApnId(),
false, ApnMessage.MAX_EXPIRATION)); if (task.getAttempt() == 0) {
message = new ApnMessage(task.getMessage(), task.getVoipApnId(), true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(FALLBACK_DURATION));
scheduleRetry(taskEntry.getKey(), new ApnFallbackTask(task.getApnId(), task.getVoipApnId(), task.getMessage(), task.getDelay(),1));
} else {
message = new ApnMessage(task.getMessage(), task.getApnId(), false, ApnMessage.MAX_EXPIRATION);
pubSubManager.unsubscribe(new WebSocketConnectionInfo(taskEntry.getKey()), this);
}
pushServiceClient.send(message);
} catch (Throwable e) { } catch (Throwable e) {
logger.warn("ApnFallbackThread", e); logger.warn("ApnFallbackThread", e);
} }
@ -123,24 +139,32 @@ public class ApnFallbackManager implements Managed, Runnable, DispatchChannel {
private final long delay; private final long delay;
private final long scheduledTime; private final long scheduledTime;
private final String apnId; private final String apnId;
private final String voipApnId;
private final ApnMessage message; private final ApnMessage message;
private final int attempt;
public ApnFallbackTask(String apnId, ApnMessage message) { public ApnFallbackTask(String apnId, String voipApnId, ApnMessage message) {
this(apnId, message, TimeUnit.SECONDS.toMillis(30)); this(apnId, voipApnId, message, TimeUnit.SECONDS.toMillis(FALLBACK_DURATION), 0);
} }
@VisibleForTesting @VisibleForTesting
public ApnFallbackTask(String apnId, ApnMessage message, long delay) { public ApnFallbackTask(String apnId, String voipApnId, ApnMessage message, long delay, int attempt) {
this.scheduledTime = System.currentTimeMillis(); this.scheduledTime = System.currentTimeMillis();
this.delay = delay; this.delay = delay;
this.apnId = apnId; this.apnId = apnId;
this.voipApnId = voipApnId;
this.message = message; this.message = message;
this.attempt = attempt;
} }
public String getApnId() { public String getApnId() {
return apnId; return apnId;
} }
public String getVoipApnId() {
return voipApnId;
}
public ApnMessage getMessage() { public ApnMessage getMessage() {
return message; return message;
} }
@ -156,6 +180,10 @@ public class ApnFallbackManager implements Managed, Runnable, DispatchChannel {
public long getDelay() { public long getDelay() {
return delay; return delay;
} }
public int getAttempt() {
return attempt;
}
} }
@VisibleForTesting @VisibleForTesting
@ -194,6 +222,13 @@ public class ApnFallbackManager implements Managed, Runnable, DispatchChannel {
} }
} }
public boolean putIfMissing(WebsocketAddress address, ApnFallbackTask task) {
synchronized (tasks) {
if (tasks.containsKey(address)) return false;
return put(address, task);
}
}
public ApnFallbackTask remove(WebsocketAddress address) { public ApnFallbackTask remove(WebsocketAddress address) {
synchronized (tasks) { synchronized (tasks) {
return tasks.remove(address); return tasks.remove(address);

View File

@ -140,11 +140,11 @@ public class PushSender implements Managed {
if (!Util.isEmpty(device.getVoipApnId())) { if (!Util.isEmpty(device.getVoipApnId())) {
apnMessage = new ApnMessage(device.getVoipApnId(), account.getNumber(), (int)device.getId(), apnMessage = new ApnMessage(device.getVoipApnId(), account.getNumber(), (int)device.getId(),
String.format(APN_PAYLOAD, messageQueueDepth), String.format(APN_PAYLOAD, messageQueueDepth),
true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(30)); true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(ApnFallbackManager.FALLBACK_DURATION));
if (fallback) { if (fallback) {
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(), device.getVoipApnId(), apnMessage));
} }
} else { } else {
apnMessage = new ApnMessage(device.getApnId(), account.getNumber(), (int)device.getId(), apnMessage = new ApnMessage(device.getApnId(), account.getNumber(), (int)device.getId(),

View File

@ -12,8 +12,11 @@ import org.whispersystems.textsecuregcm.util.Util;
import org.whispersystems.textsecuregcm.websocket.WebSocketConnectionInfo; import org.whispersystems.textsecuregcm.websocket.WebSocketConnectionInfo;
import org.whispersystems.textsecuregcm.websocket.WebsocketAddress; import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
public class ApnFallbackManagerTest { public class ApnFallbackManagerTest {
@ -25,7 +28,7 @@ public class ApnFallbackManagerTest {
WebsocketAddress address = new WebsocketAddress("+14152222223", 1L); WebsocketAddress address = new WebsocketAddress("+14152222223", 1L);
WebSocketConnectionInfo info = new WebSocketConnectionInfo(address); WebSocketConnectionInfo info = new WebSocketConnectionInfo(address);
ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true, 1111); ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true, 1111);
ApnFallbackTask task = new ApnFallbackTask("foo", message, 500); ApnFallbackTask task = new ApnFallbackTask("foo", "voipfoo", message, 500, 0);
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient, pubSubManager); ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient, pubSubManager);
apnFallbackManager.start(); apnFallbackManager.start();
@ -35,13 +38,20 @@ public class ApnFallbackManagerTest {
Util.sleep(1100); Util.sleep(1100);
ArgumentCaptor<ApnMessage> captor = ArgumentCaptor.forClass(ApnMessage.class); ArgumentCaptor<ApnMessage> captor = ArgumentCaptor.forClass(ApnMessage.class);
verify(pushServiceClient, times(1)).send(captor.capture()); verify(pushServiceClient, times(2)).send(captor.capture());
verify(pubSubManager).unsubscribe(eq(info), eq(apnFallbackManager)); verify(pubSubManager).unsubscribe(eq(info), eq(apnFallbackManager));
assertEquals(captor.getValue().getMessage(), message.getMessage()); List<ApnMessage> arguments = captor.getAllValues();
assertEquals(captor.getValue().getApnId(), task.getApnId());
assertFalse(captor.getValue().isVoip()); assertEquals(arguments.get(0).getMessage(), message.getMessage());
assertEquals(captor.getValue().getExpirationTime(), Integer.MAX_VALUE * 1000L); assertEquals(arguments.get(0).getApnId(), task.getVoipApnId());
assertTrue(arguments.get(0).isVoip());
// assertEquals(arguments.get(0).getExpirationTime(), Integer.MAX_VALUE * 1000L);
assertEquals(arguments.get(1).getMessage(), message.getMessage());
assertEquals(arguments.get(1).getApnId(), task.getApnId());
assertFalse(arguments.get(1).isVoip());
assertEquals(arguments.get(1).getExpirationTime(), Integer.MAX_VALUE * 1000L);
} }
@Test @Test
@ -51,7 +61,7 @@ public class ApnFallbackManagerTest {
WebsocketAddress address = new WebsocketAddress("+14152222222", 1); WebsocketAddress address = new WebsocketAddress("+14152222222", 1);
WebSocketConnectionInfo info = new WebSocketConnectionInfo(address); WebSocketConnectionInfo info = new WebSocketConnectionInfo(address);
ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true, 5555); ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true, 5555);
ApnFallbackTask task = new ApnFallbackTask ("foo", message, 500); ApnFallbackTask task = new ApnFallbackTask ("foo", "voipfoo", message, 500, 0);
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient, pubSubManager); ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient, pubSubManager);
apnFallbackManager.start(); apnFallbackManager.start();