Collapse WebsocketSender into PushSender.
This commit is contained in:
		
							parent
							
								
									5e30b0499a
								
							
						
					
					
						commit
						74b3daa70a
					
				| 
						 | 
					@ -106,7 +106,6 @@ import org.whispersystems.textsecuregcm.push.GCMSender;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.push.ProvisioningManager;
 | 
					import org.whispersystems.textsecuregcm.push.ProvisioningManager;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.push.PushSender;
 | 
					import org.whispersystems.textsecuregcm.push.PushSender;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.push.ReceiptSender;
 | 
					import org.whispersystems.textsecuregcm.push.ReceiptSender;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.push.WebsocketSender;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
 | 
					import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
 | 
					import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
 | 
					import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
 | 
				
			||||||
| 
						 | 
					@ -313,7 +312,6 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
				
			||||||
    PubSubManager              pubSubManager              = new PubSubManager(pubsubClient, dispatchManager);
 | 
					    PubSubManager              pubSubManager              = new PubSubManager(pubsubClient, dispatchManager);
 | 
				
			||||||
    APNSender                  apnSender                  = new APNSender(accountsManager, config.getApnConfiguration());
 | 
					    APNSender                  apnSender                  = new APNSender(accountsManager, config.getApnConfiguration());
 | 
				
			||||||
    GCMSender                  gcmSender                  = new GCMSender(accountsManager, config.getGcmConfiguration().getApiKey());
 | 
					    GCMSender                  gcmSender                  = new GCMSender(accountsManager, config.getGcmConfiguration().getApiKey());
 | 
				
			||||||
    WebsocketSender            websocketSender            = new WebsocketSender(messagesManager, clientPresenceManager);
 | 
					 | 
				
			||||||
    RateLimiters               rateLimiters               = new RateLimiters(config.getLimitsConfiguration(), cacheCluster);
 | 
					    RateLimiters               rateLimiters               = new RateLimiters(config.getLimitsConfiguration(), cacheCluster);
 | 
				
			||||||
    ProvisioningManager        provisioningManager        = new ProvisioningManager(pubSubManager);
 | 
					    ProvisioningManager        provisioningManager        = new ProvisioningManager(pubSubManager);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -331,7 +329,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
 | 
				
			||||||
    ApnFallbackManager       apnFallbackManager = new ApnFallbackManager(pushSchedulerClient, apnSender, accountsManager);
 | 
					    ApnFallbackManager       apnFallbackManager = new ApnFallbackManager(pushSchedulerClient, apnSender, accountsManager);
 | 
				
			||||||
    TwilioSmsSender          twilioSmsSender    = new TwilioSmsSender(config.getTwilioConfiguration());
 | 
					    TwilioSmsSender          twilioSmsSender    = new TwilioSmsSender(config.getTwilioConfiguration());
 | 
				
			||||||
    SmsSender                smsSender          = new SmsSender(twilioSmsSender);
 | 
					    SmsSender                smsSender          = new SmsSender(twilioSmsSender);
 | 
				
			||||||
    PushSender               pushSender         = new PushSender(apnFallbackManager, gcmSender, apnSender, websocketSender, config.getPushConfiguration().getQueueSize(), pushLatencyManager);
 | 
					    PushSender               pushSender         = new PushSender(apnFallbackManager, clientPresenceManager, messagesManager, gcmSender, apnSender, config.getPushConfiguration().getQueueSize(), pushLatencyManager);
 | 
				
			||||||
    ReceiptSender            receiptSender      = new ReceiptSender(accountsManager, pushSender);
 | 
					    ReceiptSender            receiptSender      = new ReceiptSender(accountsManager, pushSender);
 | 
				
			||||||
    TurnTokenGenerator       turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
 | 
					    TurnTokenGenerator       turnTokenGenerator = new TurnTokenGenerator(config.getTurnConfiguration());
 | 
				
			||||||
    RecaptchaClient          recaptchaClient    = new RecaptchaClient(config.getRecaptchaConfiguration().getSecret());
 | 
					    RecaptchaClient          recaptchaClient    = new RecaptchaClient(config.getRecaptchaConfiguration().getSecret());
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,17 +18,21 @@ package org.whispersystems.textsecuregcm.push;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.codahale.metrics.Gauge;
 | 
					import com.codahale.metrics.Gauge;
 | 
				
			||||||
import com.codahale.metrics.SharedMetricRegistries;
 | 
					import com.codahale.metrics.SharedMetricRegistries;
 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import com.google.common.annotations.VisibleForTesting;
 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					import io.micrometer.core.instrument.Metrics;
 | 
				
			||||||
 | 
					import io.micrometer.core.instrument.Tag;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
 | 
					import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.redis.RedisOperation;
 | 
					import org.whispersystems.textsecuregcm.redis.RedisOperation;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.Account;
 | 
					import org.whispersystems.textsecuregcm.storage.Account;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.Device;
 | 
					import org.whispersystems.textsecuregcm.storage.Device;
 | 
				
			||||||
 | 
					import org.whispersystems.textsecuregcm.storage.MessagesManager;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.util.BlockingThreadPoolExecutor;
 | 
					import org.whispersystems.textsecuregcm.util.BlockingThreadPoolExecutor;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.util.Constants;
 | 
					import org.whispersystems.textsecuregcm.util.Constants;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.util.Util;
 | 
					import org.whispersystems.textsecuregcm.util.Util;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Optional;
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					import java.util.concurrent.ExecutorService;
 | 
				
			||||||
import java.util.concurrent.TimeUnit;
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static com.codahale.metrics.MetricRegistry.name;
 | 
					import static com.codahale.metrics.MetricRegistry.name;
 | 
				
			||||||
| 
						 | 
					@ -37,33 +41,60 @@ import static org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class PushSender implements Managed {
 | 
					public class PushSender implements Managed {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @SuppressWarnings("unused")
 | 
					 | 
				
			||||||
  private final Logger logger = LoggerFactory.getLogger(PushSender.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private final ApnFallbackManager         apnFallbackManager;
 | 
					  private final ApnFallbackManager         apnFallbackManager;
 | 
				
			||||||
 | 
					  private final ClientPresenceManager      clientPresenceManager;
 | 
				
			||||||
 | 
					  private final MessagesManager            messagesManager;
 | 
				
			||||||
  private final GCMSender                  gcmSender;
 | 
					  private final GCMSender                  gcmSender;
 | 
				
			||||||
  private final APNSender                  apnSender;
 | 
					  private final APNSender                  apnSender;
 | 
				
			||||||
  private final WebsocketSender            webSocketSender;
 | 
					  private final ExecutorService            executor;
 | 
				
			||||||
  private final BlockingThreadPoolExecutor executor;
 | 
					 | 
				
			||||||
  private final int                        queueSize;
 | 
					  private final int                        queueSize;
 | 
				
			||||||
  private final PushLatencyManager         pushLatencyManager;
 | 
					  private final PushLatencyManager         pushLatencyManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static final String SEND_COUNTER_NAME      = name(PushSender.class, "sendMessage");
 | 
				
			||||||
 | 
					  private static final String CHANNEL_TAG_NAME       = "channel";
 | 
				
			||||||
 | 
					  private static final String EPHEMERAL_TAG_NAME     = "ephemeral";
 | 
				
			||||||
 | 
					  private static final String CLIENT_ONLINE_TAG_NAME = "clientOnline";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public PushSender(ApnFallbackManager    apnFallbackManager,
 | 
					  public PushSender(ApnFallbackManager    apnFallbackManager,
 | 
				
			||||||
                    GCMSender gcmSender, APNSender apnSender,
 | 
					                    ClientPresenceManager clientPresenceManager,
 | 
				
			||||||
                    WebsocketSender websocketSender, int queueSize,
 | 
					                    MessagesManager       messagesManager,
 | 
				
			||||||
 | 
					                    GCMSender             gcmSender,
 | 
				
			||||||
 | 
					                    APNSender             apnSender,
 | 
				
			||||||
 | 
					                    int                   queueSize,
 | 
				
			||||||
                    PushLatencyManager    pushLatencyManager)
 | 
					                    PushLatencyManager    pushLatencyManager)
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    this.apnFallbackManager = apnFallbackManager;
 | 
					    this(apnFallbackManager,
 | 
				
			||||||
    this.gcmSender          = gcmSender;
 | 
					         clientPresenceManager,
 | 
				
			||||||
    this.apnSender          = apnSender;
 | 
					         messagesManager,
 | 
				
			||||||
    this.webSocketSender    = websocketSender;
 | 
					         gcmSender,
 | 
				
			||||||
    this.queueSize          = queueSize;
 | 
					         apnSender,
 | 
				
			||||||
    this.executor           = new BlockingThreadPoolExecutor("pushSender", 50, queueSize);
 | 
					         queueSize,
 | 
				
			||||||
    this.pushLatencyManager = pushLatencyManager;
 | 
					         new BlockingThreadPoolExecutor("pushSender", 50, queueSize),
 | 
				
			||||||
 | 
					         pushLatencyManager);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME)
 | 
					    SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME)
 | 
				
			||||||
                          .register(name(PushSender.class, "send_queue_depth"),
 | 
					                          .register(name(PushSender.class, "send_queue_depth"),
 | 
				
			||||||
                                    (Gauge<Integer>) executor::getSize);
 | 
					                                    (Gauge<Integer>) ((BlockingThreadPoolExecutor)executor)::getSize);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @VisibleForTesting
 | 
				
			||||||
 | 
					  PushSender(ApnFallbackManager    apnFallbackManager,
 | 
				
			||||||
 | 
					             ClientPresenceManager clientPresenceManager,
 | 
				
			||||||
 | 
					             MessagesManager       messagesManager,
 | 
				
			||||||
 | 
					             GCMSender             gcmSender,
 | 
				
			||||||
 | 
					             APNSender             apnSender,
 | 
				
			||||||
 | 
					             int                   queueSize,
 | 
				
			||||||
 | 
					             ExecutorService       executor,
 | 
				
			||||||
 | 
					             PushLatencyManager    pushLatencyManager) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.apnFallbackManager    = apnFallbackManager;
 | 
				
			||||||
 | 
					    this.clientPresenceManager = clientPresenceManager;
 | 
				
			||||||
 | 
					    this.messagesManager       = messagesManager;
 | 
				
			||||||
 | 
					    this.gcmSender             = gcmSender;
 | 
				
			||||||
 | 
					    this.apnSender             = apnSender;
 | 
				
			||||||
 | 
					    this.queueSize             = queueSize;
 | 
				
			||||||
 | 
					    this.executor              = executor;
 | 
				
			||||||
 | 
					    this.pushLatencyManager    = pushLatencyManager;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public void sendMessage(final Account account, final Device device, final Envelope message, boolean online)
 | 
					  public void sendMessage(final Account account, final Device device, final Envelope message, boolean online)
 | 
				
			||||||
| 
						 | 
					@ -80,20 +111,45 @@ public class PushSender implements Managed {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private void sendSynchronousMessage(Account account, Device device, Envelope message, boolean online) {
 | 
					  @VisibleForTesting
 | 
				
			||||||
    if      (device.getGcmId() != null)   sendGcmMessage(account, device, message, online);
 | 
					  void sendSynchronousMessage(Account account, Device device, Envelope message, boolean online) {
 | 
				
			||||||
    else if (device.getApnId() != null)   sendApnMessage(account, device, message, online);
 | 
					    final String channel;
 | 
				
			||||||
    else if (device.getFetchesMessages()) sendWebSocketMessage(account, device, message, online);
 | 
					
 | 
				
			||||||
    else                                  throw new AssertionError();
 | 
					    if (device.getGcmId() != null) {
 | 
				
			||||||
 | 
					      channel = "gcm";
 | 
				
			||||||
 | 
					    } else if (device.getApnId() != null) {
 | 
				
			||||||
 | 
					      channel = "apn";
 | 
				
			||||||
 | 
					    } else if (device.getFetchesMessages()) {
 | 
				
			||||||
 | 
					      channel = "websocket";
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      throw new AssertionError();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private void sendGcmMessage(Account account, Device device, Envelope message, boolean online) {
 | 
					    final boolean clientPresent = clientPresenceManager.isPresent(account.getUuid(), device.getId());
 | 
				
			||||||
    final boolean delivered = webSocketSender.sendMessage(account, device, message, WebsocketSender.Type.GCM, online);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!delivered && !online) {
 | 
					    if (online) {
 | 
				
			||||||
 | 
					      if (clientPresent) {
 | 
				
			||||||
 | 
					        messagesManager.insertEphemeral(account.getUuid(), device.getId(), message);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      messagesManager.insert(account.getUuid(), device.getId(), message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!clientPresent) {
 | 
				
			||||||
 | 
					        if (!Util.isEmpty(device.getGcmId())) {
 | 
				
			||||||
          sendGcmNotification(account, device);
 | 
					          sendGcmNotification(account, device);
 | 
				
			||||||
 | 
					        } else if (!Util.isEmpty(device.getApnId()) || !Util.isEmpty(device.getVoipApnId())) {
 | 
				
			||||||
 | 
					          sendApnNotification(account, device);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final List<Tag> tags = List.of(
 | 
				
			||||||
 | 
					            Tag.of(CHANNEL_TAG_NAME, channel),
 | 
				
			||||||
 | 
					            Tag.of(EPHEMERAL_TAG_NAME, String.valueOf(online)),
 | 
				
			||||||
 | 
					            Tag.of(CLIENT_ONLINE_TAG_NAME, String.valueOf(clientPresent)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Metrics.counter(SEND_COUNTER_NAME, tags).increment();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private void sendGcmNotification(Account account, Device device) {
 | 
					  private void sendGcmNotification(Account account, Device device) {
 | 
				
			||||||
    GcmMessage gcmMessage = new GcmMessage(device.getGcmId(), account.getNumber(),
 | 
					    GcmMessage gcmMessage = new GcmMessage(device.getGcmId(), account.getNumber(),
 | 
				
			||||||
| 
						 | 
					@ -104,21 +160,9 @@ public class PushSender implements Managed {
 | 
				
			||||||
    RedisOperation.unchecked(() -> pushLatencyManager.recordPushSent(account.getUuid(), device.getId()));
 | 
					    RedisOperation.unchecked(() -> pushLatencyManager.recordPushSent(account.getUuid(), device.getId()));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private void sendApnMessage(Account account, Device device, Envelope outgoingMessage, boolean online) {
 | 
					  private void sendApnNotification(Account account, Device device) {
 | 
				
			||||||
    final boolean delivered = webSocketSender.sendMessage(account, device, outgoingMessage, WebsocketSender.Type.APN, online);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!delivered && outgoingMessage.getType() != Envelope.Type.RECEIPT && !online) {
 | 
					 | 
				
			||||||
      sendApnNotification(account, device, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private void sendApnNotification(Account account, Device device, boolean newOnly) {
 | 
					 | 
				
			||||||
    ApnMessage apnMessage;
 | 
					    ApnMessage apnMessage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (newOnly && RedisOperation.unchecked(() -> apnFallbackManager.isScheduled(account, device))) {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!Util.isEmpty(device.getVoipApnId())) {
 | 
					    if (!Util.isEmpty(device.getVoipApnId())) {
 | 
				
			||||||
      apnMessage = new ApnMessage(device.getVoipApnId(), account.getNumber(), device.getId(), true, Optional.empty());
 | 
					      apnMessage = new ApnMessage(device.getVoipApnId(), account.getNumber(), device.getId(), true, Optional.empty());
 | 
				
			||||||
      RedisOperation.unchecked(() -> apnFallbackManager.schedule(account, device));
 | 
					      RedisOperation.unchecked(() -> apnFallbackManager.schedule(account, device));
 | 
				
			||||||
| 
						 | 
					@ -131,13 +175,8 @@ public class PushSender implements Managed {
 | 
				
			||||||
    RedisOperation.unchecked(() -> pushLatencyManager.recordPushSent(account.getUuid(), device.getId()));
 | 
					    RedisOperation.unchecked(() -> pushLatencyManager.recordPushSent(account.getUuid(), device.getId()));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private void sendWebSocketMessage(Account account, Device device, Envelope outgoingMessage, boolean online)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    webSocketSender.sendMessage(account, device, outgoingMessage, WebsocketSender.Type.WEB, online);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Override
 | 
					  @Override
 | 
				
			||||||
  public void start() throws Exception {
 | 
					  public void start() {
 | 
				
			||||||
    apnSender.start();
 | 
					    apnSender.start();
 | 
				
			||||||
    gcmSender.start();
 | 
					    gcmSender.start();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -150,5 +189,4 @@ public class PushSender implements Managed {
 | 
				
			||||||
    apnSender.stop();
 | 
					    apnSender.stop();
 | 
				
			||||||
    gcmSender.stop();
 | 
					    gcmSender.stop();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,97 +0,0 @@
 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
 * Copyright (C) 2014 Open WhisperSystems
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This program is free software: you can redistribute it and/or modify
 | 
					 | 
				
			||||||
 * it under the terms of the GNU Affero General Public License as published by
 | 
					 | 
				
			||||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
					 | 
				
			||||||
 * (at your option) any later version.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This program is distributed in the hope that it will be useful,
 | 
					 | 
				
			||||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					 | 
				
			||||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					 | 
				
			||||||
 * GNU Affero General Public License for more details.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * You should have received a copy of the GNU Affero General Public License
 | 
					 | 
				
			||||||
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
package org.whispersystems.textsecuregcm.push;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.codahale.metrics.Meter;
 | 
					 | 
				
			||||||
import com.codahale.metrics.MetricRegistry;
 | 
					 | 
				
			||||||
import com.codahale.metrics.SharedMetricRegistries;
 | 
					 | 
				
			||||||
import io.micrometer.core.instrument.Counter;
 | 
					 | 
				
			||||||
import io.micrometer.core.instrument.Metrics;
 | 
					 | 
				
			||||||
import org.slf4j.Logger;
 | 
					 | 
				
			||||||
import org.slf4j.LoggerFactory;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.Account;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.Device;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.util.Constants;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static com.codahale.metrics.MetricRegistry.name;
 | 
					 | 
				
			||||||
import static org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class WebsocketSender {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public enum Type {
 | 
					 | 
				
			||||||
    APN,
 | 
					 | 
				
			||||||
    GCM,
 | 
					 | 
				
			||||||
    WEB
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @SuppressWarnings("unused")
 | 
					 | 
				
			||||||
  private static final Logger logger = LoggerFactory.getLogger(WebsocketSender.class);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private final Meter websocketOnlineMeter  = metricRegistry.meter(name(getClass(), "ws_online"  ));
 | 
					 | 
				
			||||||
  private final Meter websocketOfflineMeter = metricRegistry.meter(name(getClass(), "ws_offline" ));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  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 Counter ephemeralOnlineCounter  = Metrics.counter(name(getClass(), "ephemeral"), "online", "true");
 | 
					 | 
				
			||||||
  private final Counter ephemeralOfflineCounter = Metrics.counter(name(getClass(), "ephemeral"), "offline", "true");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private final MessagesManager       messagesManager;
 | 
					 | 
				
			||||||
  private final ClientPresenceManager clientPresenceManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public WebsocketSender(MessagesManager messagesManager, ClientPresenceManager clientPresenceManager) {
 | 
					 | 
				
			||||||
    this.messagesManager       = messagesManager;
 | 
					 | 
				
			||||||
    this.clientPresenceManager = clientPresenceManager;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public boolean sendMessage(Account account, Device device, Envelope message, Type channel, boolean online) {
 | 
					 | 
				
			||||||
    final boolean clientPresent = clientPresenceManager.isPresent(account.getUuid(), device.getId());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (online) {
 | 
					 | 
				
			||||||
      if (clientPresent) {
 | 
					 | 
				
			||||||
        ephemeralOnlineCounter.increment();
 | 
					 | 
				
			||||||
        messagesManager.insertEphemeral(account.getUuid(), device.getId(), message);
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        ephemeralOfflineCounter.increment();
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      messagesManager.insert(account.getUuid(), device.getId(), message);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (clientPresent) {
 | 
					 | 
				
			||||||
        if (channel == Type.APN) apnOnlineMeter.mark();
 | 
					 | 
				
			||||||
        else if (channel == Type.GCM) gcmOnlineMeter.mark();
 | 
					 | 
				
			||||||
        else websocketOnlineMeter.mark();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        if (channel == Type.APN) apnOfflineMeter.mark();
 | 
					 | 
				
			||||||
        else if (channel == Type.GCM) gcmOfflineMeter.mark();
 | 
					 | 
				
			||||||
        else websocketOfflineMeter.mark();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,150 @@
 | 
				
			||||||
 | 
					package org.whispersystems.textsecuregcm.push;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.protobuf.ByteString;
 | 
				
			||||||
 | 
					import org.apache.commons.lang3.RandomStringUtils;
 | 
				
			||||||
 | 
					import org.junit.Before;
 | 
				
			||||||
 | 
					import org.junit.Test;
 | 
				
			||||||
 | 
					import org.whispersystems.textsecuregcm.entities.MessageProtos;
 | 
				
			||||||
 | 
					import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
 | 
				
			||||||
 | 
					import org.whispersystems.textsecuregcm.storage.Account;
 | 
				
			||||||
 | 
					import org.whispersystems.textsecuregcm.storage.Device;
 | 
				
			||||||
 | 
					import org.whispersystems.textsecuregcm.storage.MessagesManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.UUID;
 | 
				
			||||||
 | 
					import java.util.concurrent.ExecutorService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.mockito.ArgumentMatchers.any;
 | 
				
			||||||
 | 
					import static org.mockito.ArgumentMatchers.anyLong;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.never;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.verify;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.verifyZeroInteractions;
 | 
				
			||||||
 | 
					import static org.mockito.Mockito.when;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class PushSenderTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private Account                account;
 | 
				
			||||||
 | 
					    private Device                 device;
 | 
				
			||||||
 | 
					    private MessageProtos.Envelope message;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ClientPresenceManager clientPresenceManager;
 | 
				
			||||||
 | 
					    private MessagesManager       messagesManager;
 | 
				
			||||||
 | 
					    private GCMSender             gcmSender;
 | 
				
			||||||
 | 
					    private APNSender             apnSender;
 | 
				
			||||||
 | 
					    private PushSender            pushSender;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final UUID ACCOUNT_UUID = UUID.randomUUID();
 | 
				
			||||||
 | 
					    private static final long DEVICE_ID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Before
 | 
				
			||||||
 | 
					    public void setUp() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        account = mock(Account.class);
 | 
				
			||||||
 | 
					        device  = mock(Device.class);
 | 
				
			||||||
 | 
					        message = generateRandomMessage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        clientPresenceManager = mock(ClientPresenceManager.class);
 | 
				
			||||||
 | 
					        messagesManager       = mock(MessagesManager.class);
 | 
				
			||||||
 | 
					        gcmSender             = mock(GCMSender.class);
 | 
				
			||||||
 | 
					        apnSender             = mock(APNSender.class);
 | 
				
			||||||
 | 
					        pushSender            = new PushSender(mock(ApnFallbackManager.class),
 | 
				
			||||||
 | 
					                                               clientPresenceManager,
 | 
				
			||||||
 | 
					                                               messagesManager,
 | 
				
			||||||
 | 
					                                               gcmSender,
 | 
				
			||||||
 | 
					                                               apnSender,
 | 
				
			||||||
 | 
					                                               0,
 | 
				
			||||||
 | 
					                                               mock(ExecutorService.class),
 | 
				
			||||||
 | 
					                                               mock(PushLatencyManager.class));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        when(account.getUuid()).thenReturn(ACCOUNT_UUID);
 | 
				
			||||||
 | 
					        when(device.getId()).thenReturn(DEVICE_ID);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testSendOnlineMessageClientPresent() {
 | 
				
			||||||
 | 
					        when(clientPresenceManager.isPresent(ACCOUNT_UUID, DEVICE_ID)).thenReturn(true);
 | 
				
			||||||
 | 
					        when(device.getGcmId()).thenReturn("gcm-id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pushSender.sendSynchronousMessage(account, device, message, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verify(messagesManager).insertEphemeral(ACCOUNT_UUID, DEVICE_ID, message);
 | 
				
			||||||
 | 
					        verify(messagesManager, never()).insert(any(), anyLong(), any());
 | 
				
			||||||
 | 
					        verifyZeroInteractions(gcmSender);
 | 
				
			||||||
 | 
					        verifyZeroInteractions(apnSender);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testSendOnlineMessageClientNotPresent() {
 | 
				
			||||||
 | 
					        when(clientPresenceManager.isPresent(ACCOUNT_UUID, DEVICE_ID)).thenReturn(false);
 | 
				
			||||||
 | 
					        when(device.getGcmId()).thenReturn("gcm-id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pushSender.sendSynchronousMessage(account, device, message, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verify(messagesManager, never()).insertEphemeral(any(), anyLong(), any());
 | 
				
			||||||
 | 
					        verify(messagesManager, never()).insert(any(), anyLong(), any());
 | 
				
			||||||
 | 
					        verifyZeroInteractions(gcmSender);
 | 
				
			||||||
 | 
					        verifyZeroInteractions(apnSender);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testSendMessageClientPresent() {
 | 
				
			||||||
 | 
					        when(clientPresenceManager.isPresent(ACCOUNT_UUID, DEVICE_ID)).thenReturn(true);
 | 
				
			||||||
 | 
					        when(device.getGcmId()).thenReturn("gcm-id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pushSender.sendSynchronousMessage(account, device, message, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verify(messagesManager, never()).insertEphemeral(any(), anyLong(), any());
 | 
				
			||||||
 | 
					        verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
 | 
				
			||||||
 | 
					        verifyZeroInteractions(gcmSender);
 | 
				
			||||||
 | 
					        verifyZeroInteractions(apnSender);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testSendMessageGcmClientNotPresent() {
 | 
				
			||||||
 | 
					        when(clientPresenceManager.isPresent(ACCOUNT_UUID, DEVICE_ID)).thenReturn(false);
 | 
				
			||||||
 | 
					        when(device.getGcmId()).thenReturn("gcm-id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pushSender.sendSynchronousMessage(account, device, message, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verify(messagesManager, never()).insertEphemeral(any(), anyLong(), any());
 | 
				
			||||||
 | 
					        verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
 | 
				
			||||||
 | 
					        verify(gcmSender).sendMessage(any());
 | 
				
			||||||
 | 
					        verifyZeroInteractions(apnSender);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testSendMessageApnClientNotPresent() {
 | 
				
			||||||
 | 
					        when(clientPresenceManager.isPresent(ACCOUNT_UUID, DEVICE_ID)).thenReturn(false);
 | 
				
			||||||
 | 
					        when(device.getApnId()).thenReturn("apn-id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pushSender.sendSynchronousMessage(account, device, message, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verify(messagesManager, never()).insertEphemeral(any(), anyLong(), any());
 | 
				
			||||||
 | 
					        verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
 | 
				
			||||||
 | 
					        verifyZeroInteractions(gcmSender);
 | 
				
			||||||
 | 
					        verify(apnSender).sendMessage(any());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test
 | 
				
			||||||
 | 
					    public void testSendMessageFetchClientNotPresent() {
 | 
				
			||||||
 | 
					        when(clientPresenceManager.isPresent(ACCOUNT_UUID, DEVICE_ID)).thenReturn(false);
 | 
				
			||||||
 | 
					        when(device.getFetchesMessages()).thenReturn(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pushSender.sendSynchronousMessage(account, device, message, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verify(messagesManager, never()).insertEphemeral(any(), anyLong(), any());
 | 
				
			||||||
 | 
					        verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
 | 
				
			||||||
 | 
					        verifyZeroInteractions(gcmSender);
 | 
				
			||||||
 | 
					        verifyZeroInteractions(apnSender);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private MessageProtos.Envelope generateRandomMessage() {
 | 
				
			||||||
 | 
					        return MessageProtos.Envelope.newBuilder()
 | 
				
			||||||
 | 
					                .setTimestamp(System.currentTimeMillis())
 | 
				
			||||||
 | 
					                .setServerTimestamp(System.currentTimeMillis())
 | 
				
			||||||
 | 
					                .setContent(ByteString.copyFromUtf8(RandomStringUtils.randomAlphanumeric(256)))
 | 
				
			||||||
 | 
					                .setType(MessageProtos.Envelope.Type.CIPHERTEXT)
 | 
				
			||||||
 | 
					                .setServerGuid(UUID.randomUUID().toString())
 | 
				
			||||||
 | 
					                .build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,6 @@ import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
 | 
					import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
 | 
					import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.push.ReceiptSender;
 | 
					import org.whispersystems.textsecuregcm.push.ReceiptSender;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.push.WebsocketSender;
 | 
					 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.Account;
 | 
					import org.whispersystems.textsecuregcm.storage.Account;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
 | 
					import org.whispersystems.textsecuregcm.storage.AccountsManager;
 | 
				
			||||||
import org.whispersystems.textsecuregcm.storage.Device;
 | 
					import org.whispersystems.textsecuregcm.storage.Device;
 | 
				
			||||||
| 
						 | 
					@ -48,10 +47,8 @@ import static org.mockito.Mockito.anyInt;
 | 
				
			||||||
import static org.mockito.Mockito.anyLong;
 | 
					import static org.mockito.Mockito.anyLong;
 | 
				
			||||||
import static org.mockito.Mockito.anyString;
 | 
					import static org.mockito.Mockito.anyString;
 | 
				
			||||||
import static org.mockito.Mockito.mock;
 | 
					import static org.mockito.Mockito.mock;
 | 
				
			||||||
import static org.mockito.Mockito.reset;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.times;
 | 
					import static org.mockito.Mockito.times;
 | 
				
			||||||
import static org.mockito.Mockito.verify;
 | 
					import static org.mockito.Mockito.verify;
 | 
				
			||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.when;
 | 
					import static org.mockito.Mockito.when;
 | 
				
			||||||
import static org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
 | 
					import static org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -245,7 +242,6 @@ public class WebSocketConnectionTest {
 | 
				
			||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
  public void testPendingSend() throws Exception {
 | 
					  public void testPendingSend() throws Exception {
 | 
				
			||||||
    MessagesManager storedMessages  = mock(MessagesManager.class);
 | 
					    MessagesManager storedMessages  = mock(MessagesManager.class);
 | 
				
			||||||
    WebsocketSender websocketSender = mock(WebsocketSender.class);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final Envelope firstMessage = Envelope.newBuilder()
 | 
					    final Envelope firstMessage = Envelope.newBuilder()
 | 
				
			||||||
                                    .setLegacyMessage(ByteString.copyFrom("first".getBytes()))
 | 
					                                    .setLegacyMessage(ByteString.copyFrom("first".getBytes()))
 | 
				
			||||||
| 
						 | 
					@ -331,7 +327,6 @@ public class WebSocketConnectionTest {
 | 
				
			||||||
    futures.get(0).completeExceptionally(new IOException());
 | 
					    futures.get(0).completeExceptionally(new IOException());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender2"), eq(secondMessage.getTimestamp()));
 | 
					    verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender2"), eq(secondMessage.getTimestamp()));
 | 
				
			||||||
    verifyNoMoreInteractions(websocketSender);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    connection.stop();
 | 
					    connection.stop();
 | 
				
			||||||
    verify(client).close(anyInt(), anyString());
 | 
					    verify(client).close(anyInt(), anyString());
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue