Refactor scheduled APNs notifications in preparation for future development

This commit is contained in:
Jon Chambers 2022-08-12 10:47:49 -04:00 committed by GitHub
parent a44c18e9b7
commit a53a85d788
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 200 additions and 213 deletions

View File

@ -136,14 +136,14 @@ import org.whispersystems.textsecuregcm.metrics.MicrometerRegistryManager;
import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge; import org.whispersystems.textsecuregcm.metrics.NetworkReceivedGauge;
import org.whispersystems.textsecuregcm.metrics.NetworkSentGauge; import org.whispersystems.textsecuregcm.metrics.NetworkSentGauge;
import org.whispersystems.textsecuregcm.metrics.OperatingSystemMemoryGauge; import org.whispersystems.textsecuregcm.metrics.OperatingSystemMemoryGauge;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager; import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.metrics.ReportedMessageMetricsListener; import org.whispersystems.textsecuregcm.metrics.ReportedMessageMetricsListener;
import org.whispersystems.textsecuregcm.metrics.TrafficSource; import org.whispersystems.textsecuregcm.metrics.TrafficSource;
import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider; import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider;
import org.whispersystems.textsecuregcm.providers.RedisClientFactory; import org.whispersystems.textsecuregcm.providers.RedisClientFactory;
import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck; import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck;
import org.whispersystems.textsecuregcm.push.APNSender; import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager; import org.whispersystems.textsecuregcm.push.ApnPushNotificationScheduler;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.FcmSender; import org.whispersystems.textsecuregcm.push.FcmSender;
import org.whispersystems.textsecuregcm.push.MessageSender; import org.whispersystems.textsecuregcm.push.MessageSender;
@ -443,7 +443,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
MessagesCache messagesCache = new MessagesCache(messagesCluster, messagesCluster, keyspaceNotificationDispatchExecutor); MessagesCache messagesCache = new MessagesCache(messagesCluster, messagesCluster, keyspaceNotificationDispatchExecutor);
PushLatencyManager pushLatencyManager = new PushLatencyManager(metricsCluster, dynamicConfigurationManager); PushLatencyManager pushLatencyManager = new PushLatencyManager(metricsCluster, dynamicConfigurationManager);
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, config.getReportMessageConfiguration().getCounterTtl()); ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, config.getReportMessageConfiguration().getCounterTtl());
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager, reportMessageManager); MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, reportMessageManager);
DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
deletedAccountsLockDynamoDbClient, config.getDynamoDbTables().getDeletedAccountsLock().getTableName()); deletedAccountsLockDynamoDbClient, config.getDynamoDbTables().getDeletedAccountsLock().getTableName());
AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster, AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster,
@ -454,8 +454,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
PubSubManager pubSubManager = new PubSubManager(pubsubClient, dispatchManager); PubSubManager pubSubManager = new PubSubManager(pubsubClient, dispatchManager);
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration()); APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials()); FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials());
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushSchedulerCluster, apnSender, accountsManager); ApnPushNotificationScheduler apnPushNotificationScheduler = new ApnPushNotificationScheduler(pushSchedulerCluster, apnSender, accountsManager);
PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, apnFallbackManager); PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, apnPushNotificationScheduler, pushLatencyManager);
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), rateLimitersCluster); RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), rateLimitersCluster);
DynamicRateLimiters dynamicRateLimiters = new DynamicRateLimiters(rateLimitersCluster, dynamicConfigurationManager); DynamicRateLimiters dynamicRateLimiters = new DynamicRateLimiters(rateLimitersCluster, dynamicConfigurationManager);
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager); ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);
@ -567,7 +567,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, ftxClient, config.getPaymentsServiceConfiguration().getPaymentCurrencies()); CurrencyConversionManager currencyManager = new CurrencyConversionManager(fixerClient, ftxClient, config.getPaymentsServiceConfiguration().getPaymentCurrencies());
environment.lifecycle().manage(apnSender); environment.lifecycle().manage(apnSender);
environment.lifecycle().manage(apnFallbackManager); environment.lifecycle().manage(apnPushNotificationScheduler);
environment.lifecycle().manage(pubSubManager); environment.lifecycle().manage(pubSubManager);
environment.lifecycle().manage(accountDatabaseCrawler); environment.lifecycle().manage(accountDatabaseCrawler);
environment.lifecycle().manage(directoryReconciliationAccountDatabaseCrawler); environment.lifecycle().manage(directoryReconciliationAccountDatabaseCrawler);
@ -625,7 +625,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
config.getWebSocketConfiguration(), 90000); config.getWebSocketConfiguration(), 90000);
webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator)); webSocketEnvironment.setAuthenticator(new WebSocketAccountAuthenticator(accountAuthenticator));
webSocketEnvironment.setConnectListener( webSocketEnvironment.setConnectListener(
new AuthenticatedConnectListener(receiptSender, messagesManager, pushNotificationManager, apnFallbackManager, new AuthenticatedConnectListener(receiptSender, messagesManager, pushNotificationManager,
clientPresenceManager, websocketScheduledExecutor)); clientPresenceManager, websocketScheduledExecutor));
webSocketEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager)); webSocketEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager));
webSocketEnvironment.jersey().register(new ContentLengthFilter(TrafficSource.WEBSOCKET)); webSocketEnvironment.jersey().register(new ContentLengthFilter(TrafficSource.WEBSOCKET));
@ -652,8 +652,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
new DirectoryV2Controller(directoryV2CredentialsGenerator), new DirectoryV2Controller(directoryV2CredentialsGenerator),
new DonationController(clock, zkReceiptOperations, redeemedReceiptsManager, accountsManager, config.getBadges(), new DonationController(clock, zkReceiptOperations, redeemedReceiptsManager, accountsManager, config.getBadges(),
ReceiptCredentialPresentation::new, stripeExecutor, config.getDonationConfiguration(), config.getStripe()), ReceiptCredentialPresentation::new, stripeExecutor, config.getDonationConfiguration(), config.getStripe()),
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager, new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager, messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor),
messagesManager, apnFallbackManager, reportMessageManager, multiRecipientMessageExecutor),
new PaymentsController(currencyManager, paymentsCredentialsGenerator), new PaymentsController(currencyManager, paymentsCredentialsGenerator),
new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager, profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations, batchIdentityCheckExecutor), new ProfileController(clock, rateLimiters, accountsManager, profilesManager, dynamicConfigurationManager, profileBadgeConverter, config.getBadges(), cdnS3Client, profileCdnPolicyGenerator, profileCdnPolicySigner, config.getCdnConfiguration().getBucket(), zkProfileOperations, batchIdentityCheckExecutor),
new ProvisioningController(rateLimiters, provisioningManager), new ProvisioningController(rateLimiters, provisioningManager),

View File

@ -77,11 +77,10 @@ import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.metrics.MessageMetrics; import org.whispersystems.textsecuregcm.metrics.MessageMetrics;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider; import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
import org.whispersystems.textsecuregcm.push.MessageSender; import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.redis.RedisOperation;
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.DeletedAccountsManager; import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager;
@ -107,7 +106,7 @@ public class MessageController {
private final AccountsManager accountsManager; private final AccountsManager accountsManager;
private final DeletedAccountsManager deletedAccountsManager; private final DeletedAccountsManager deletedAccountsManager;
private final MessagesManager messagesManager; private final MessagesManager messagesManager;
private final ApnFallbackManager apnFallbackManager; private final PushNotificationManager pushNotificationManager;
private final ReportMessageManager reportMessageManager; private final ReportMessageManager reportMessageManager;
private final ExecutorService multiRecipientMessageExecutor; private final ExecutorService multiRecipientMessageExecutor;
@ -138,7 +137,7 @@ public class MessageController {
AccountsManager accountsManager, AccountsManager accountsManager,
DeletedAccountsManager deletedAccountsManager, DeletedAccountsManager deletedAccountsManager,
MessagesManager messagesManager, MessagesManager messagesManager,
ApnFallbackManager apnFallbackManager, PushNotificationManager pushNotificationManager,
ReportMessageManager reportMessageManager, ReportMessageManager reportMessageManager,
@Nonnull ExecutorService multiRecipientMessageExecutor) { @Nonnull ExecutorService multiRecipientMessageExecutor) {
this.rateLimiters = rateLimiters; this.rateLimiters = rateLimiters;
@ -147,7 +146,7 @@ public class MessageController {
this.accountsManager = accountsManager; this.accountsManager = accountsManager;
this.deletedAccountsManager = deletedAccountsManager; this.deletedAccountsManager = deletedAccountsManager;
this.messagesManager = messagesManager; this.messagesManager = messagesManager;
this.apnFallbackManager = apnFallbackManager; this.pushNotificationManager = pushNotificationManager;
this.reportMessageManager = reportMessageManager; this.reportMessageManager = reportMessageManager;
this.multiRecipientMessageExecutor = Objects.requireNonNull(multiRecipientMessageExecutor); this.multiRecipientMessageExecutor = Objects.requireNonNull(multiRecipientMessageExecutor);
} }
@ -408,18 +407,14 @@ public class MessageController {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public OutgoingMessageEntityList getPendingMessages(@Auth AuthenticatedAccount auth, public OutgoingMessageEntityList getPendingMessages(@Auth AuthenticatedAccount auth,
@HeaderParam("User-Agent") String userAgent) { @HeaderParam("User-Agent") String userAgent) {
assert auth.getAuthenticatedDevice() != null;
if (!Util.isEmpty(auth.getAuthenticatedDevice().getApnId())) { pushNotificationManager.handleMessagesRetrieved(auth.getAccount(), auth.getAuthenticatedDevice(), userAgent);
RedisOperation.unchecked(() -> apnFallbackManager.cancel(auth.getAccount(), auth.getAuthenticatedDevice()));
}
final OutgoingMessageEntityList outgoingMessages; final OutgoingMessageEntityList outgoingMessages;
{ {
final Pair<List<Envelope>, Boolean> messagesAndHasMore = messagesManager.getMessagesForDevice( final Pair<List<Envelope>, Boolean> messagesAndHasMore = messagesManager.getMessagesForDevice(
auth.getAccount().getUuid(), auth.getAccount().getUuid(),
auth.getAuthenticatedDevice().getId(), auth.getAuthenticatedDevice().getId(),
userAgent,
false); false);
outgoingMessages = new OutgoingMessageEntityList(messagesAndHasMore.first().stream() outgoingMessages = new OutgoingMessageEntityList(messagesAndHasMore.first().stream()

View File

@ -5,26 +5,12 @@
package org.whispersystems.textsecuregcm.push; package org.whispersystems.textsecuregcm.push;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.RatioGauge;
import com.codahale.metrics.SharedMetricRegistries;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.lifecycle.Managed; import io.dropwizard.lifecycle.Managed;
import io.lettuce.core.ScriptOutputType; import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.cluster.SlotHash; import io.lettuce.core.cluster.SlotHash;
import org.slf4j.Logger; import io.micrometer.core.instrument.Counter;
import org.slf4j.LoggerFactory; import io.micrometer.core.instrument.Metrics;
import org.whispersystems.textsecuregcm.redis.ClusterLuaScript;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.RedisClusterUtil;
import org.whispersystems.textsecuregcm.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -32,33 +18,40 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.redis.ClusterLuaScript;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.RedisClusterUtil;
import org.whispersystems.textsecuregcm.util.Util;
import static com.codahale.metrics.MetricRegistry.name; import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
public class ApnFallbackManager implements Managed { public class ApnPushNotificationScheduler implements Managed {
private static final Logger logger = LoggerFactory.getLogger(ApnFallbackManager.class); private static final Logger logger = LoggerFactory.getLogger(ApnPushNotificationScheduler.class);
private static final String PENDING_NOTIFICATIONS_KEY = "PENDING_APN"; private static final String PENDING_NOTIFICATIONS_KEY = "PENDING_APN";
static final String NEXT_SLOT_TO_PERSIST_KEY = "pending_notification_next_slot";
private static final MetricRegistry metricRegistry = SharedMetricRegistries.getOrCreate(Constants.METRICS_NAME); @VisibleForTesting
private static final Meter delivered = metricRegistry.meter(name(ApnFallbackManager.class, "voip_delivered")); static final String NEXT_SLOT_TO_PERSIST_KEY = "pending_notification_next_slot";
private static final Meter sent = metricRegistry.meter(name(ApnFallbackManager.class, "voip_sent" ));
private static final Meter retry = metricRegistry.meter(name(ApnFallbackManager.class, "voip_retry"));
private static final Meter evicted = metricRegistry.meter(name(ApnFallbackManager.class, "voip_evicted"));
static { private static final Counter delivered = Metrics.counter(name(ApnPushNotificationScheduler.class, "voip_delivered"));
metricRegistry.register(name(ApnFallbackManager.class, "voip_ratio"), new VoipRatioGauge(delivered, sent)); private static final Counter sent = Metrics.counter(name(ApnPushNotificationScheduler.class, "voip_sent"));
} private static final Counter retry = Metrics.counter(name(ApnPushNotificationScheduler.class, "voip_retry"));
private static final Counter evicted = Metrics.counter(name(ApnPushNotificationScheduler.class, "voip_evicted"));
private final APNSender apnSender; private final APNSender apnSender;
private final AccountsManager accountsManager; private final AccountsManager accountsManager;
private final FaultTolerantRedisCluster cluster; private final FaultTolerantRedisCluster pushSchedulingCluster;
private final ClusterLuaScript getScript; private final ClusterLuaScript getPendingVoipDestinationsScript;
private final ClusterLuaScript insertScript; private final ClusterLuaScript insertPendingVoipDestinationScript;
private final ClusterLuaScript removeScript; private final ClusterLuaScript removePendingVoipDestinationScript;
private final Thread[] workerThreads = new Thread[WORKER_THREAD_COUNT]; private final Thread[] workerThreads = new Thread[WORKER_THREAD_COUNT];
@ -90,7 +83,7 @@ public class ApnFallbackManager implements Managed {
long entriesProcessed = 0; long entriesProcessed = 0;
do { do {
pendingDestinations = getPendingDestinations(slot, 100); pendingDestinations = getPendingDestinationsForRecurringVoipNotifications(slot, 100);
entriesProcessed += pendingDestinations.size(); entriesProcessed += pendingDestinations.size();
for (final String uuidAndDevice : pendingDestinations) { for (final String uuidAndDevice : pendingDestinations) {
@ -104,9 +97,9 @@ public class ApnFallbackManager implements Managed {
.flatMap(deviceId -> maybeAccount.flatMap(account -> account.getDevice(deviceId))); .flatMap(deviceId -> maybeAccount.flatMap(account -> account.getDevice(deviceId)));
if (maybeAccount.isPresent() && maybeDevice.isPresent()) { if (maybeAccount.isPresent() && maybeDevice.isPresent()) {
sendNotification(maybeAccount.get(), maybeDevice.get()); sendRecurringVoipNotification(maybeAccount.get(), maybeDevice.get());
} else { } else {
remove(uuidAndDevice); removeRecurringVoipNotificationEntry(uuidAndDevice);
} }
} }
} while (!pendingDestinations.isEmpty()); } while (!pendingDestinations.isEmpty());
@ -115,37 +108,37 @@ public class ApnFallbackManager implements Managed {
} }
} }
public ApnFallbackManager(FaultTolerantRedisCluster cluster, public ApnPushNotificationScheduler(FaultTolerantRedisCluster pushSchedulingCluster,
APNSender apnSender, APNSender apnSender,
AccountsManager accountsManager) AccountsManager accountsManager)
throws IOException throws IOException
{ {
this.apnSender = apnSender; this.apnSender = apnSender;
this.accountsManager = accountsManager; this.accountsManager = accountsManager;
this.cluster = cluster; this.pushSchedulingCluster = pushSchedulingCluster;
this.getScript = ClusterLuaScript.fromResource(cluster, "lua/apn/get.lua", ScriptOutputType.MULTI); this.getPendingVoipDestinationsScript = ClusterLuaScript.fromResource(pushSchedulingCluster, "lua/apn/get.lua", ScriptOutputType.MULTI);
this.insertScript = ClusterLuaScript.fromResource(cluster, "lua/apn/insert.lua", ScriptOutputType.VALUE); this.insertPendingVoipDestinationScript = ClusterLuaScript.fromResource(pushSchedulingCluster, "lua/apn/insert.lua", ScriptOutputType.VALUE);
this.removeScript = ClusterLuaScript.fromResource(cluster, "lua/apn/remove.lua", ScriptOutputType.INTEGER); this.removePendingVoipDestinationScript = ClusterLuaScript.fromResource(pushSchedulingCluster, "lua/apn/remove.lua", ScriptOutputType.INTEGER);
for (int i = 0; i < this.workerThreads.length; i++) { for (int i = 0; i < this.workerThreads.length; i++) {
this.workerThreads[i] = new Thread(new NotificationWorker(), "ApnFallbackManagerWorker-" + i); this.workerThreads[i] = new Thread(new NotificationWorker(), "ApnFallbackManagerWorker-" + i);
} }
} }
public void schedule(Account account, Device device) { public void scheduleRecurringVoipNotification(Account account, Device device) {
schedule(account, device, System.currentTimeMillis()); scheduleRecurringVoipNotification(account, device, System.currentTimeMillis());
} }
@VisibleForTesting @VisibleForTesting
void schedule(Account account, Device device, long timestamp) { void scheduleRecurringVoipNotification(Account account, Device device, long timestamp) {
sent.mark(); sent.increment();
insert(account, device, timestamp + (15 * 1000), (15 * 1000)); insertRecurringVoipNotificationEntry(account, device, timestamp + (15 * 1000), (15 * 1000));
} }
public void cancel(Account account, Device device) { public void cancelRecurringVoipNotification(Account account, Device device) {
if (remove(account, device)) { if (removeRecurringVoipNotificationEntry(account, device)) {
delivered.mark(); delivered.increment();
} }
} }
@ -167,24 +160,24 @@ public class ApnFallbackManager implements Managed {
} }
} }
private void sendNotification(final Account account, final Device device) { private void sendRecurringVoipNotification(final Account account, final Device device) {
String apnId = device.getVoipApnId(); String apnId = device.getVoipApnId();
if (apnId == null) { if (apnId == null) {
remove(account, device); removeRecurringVoipNotificationEntry(account, device);
return; return;
} }
long deviceLastSeen = device.getLastSeen(); long deviceLastSeen = device.getLastSeen();
if (deviceLastSeen < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7)) { if (deviceLastSeen < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7)) {
evicted.mark(); evicted.increment();
remove(account, device); removeRecurringVoipNotificationEntry(account, device);
return; return;
} }
apnSender.sendNotification(new PushNotification(apnId, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, account, device)); apnSender.sendNotification(new PushNotification(apnId, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, account, device));
retry.mark(); retry.increment();
} }
@VisibleForTesting @VisibleForTesting
@ -206,30 +199,33 @@ public class ApnFallbackManager implements Managed {
} }
} }
private boolean remove(Account account, Device device) { private boolean removeRecurringVoipNotificationEntry(Account account, Device device) {
return remove(getEndpointKey(account, device)); return removeRecurringVoipNotificationEntry(getEndpointKey(account, device));
} }
private boolean remove(final String endpoint) { private boolean removeRecurringVoipNotificationEntry(final String endpoint) {
return (long)removeScript.execute(List.of(getPendingNotificationQueueKey(endpoint), endpoint), return (long) removePendingVoipDestinationScript.execute(
Collections.emptyList()) > 0; List.of(getPendingRecurringVoipNotificationQueueKey(endpoint), endpoint),
Collections.emptyList()) > 0;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@VisibleForTesting @VisibleForTesting
List<String> getPendingDestinations(final int slot, final int limit) { List<String> getPendingDestinationsForRecurringVoipNotifications(final int slot, final int limit) {
return (List<String>)getScript.execute(List.of(getPendingNotificationQueueKey(slot)), return (List<String>) getPendingVoipDestinationsScript.execute(
List.of(String.valueOf(System.currentTimeMillis()), String.valueOf(limit))); List.of(getPendingRecurringVoipNotificationQueueKey(slot)),
List.of(String.valueOf(System.currentTimeMillis()), String.valueOf(limit)));
} }
private void insert(final Account account, final Device device, final long timestamp, final long interval) { private void insertRecurringVoipNotificationEntry(final Account account, final Device device, final long timestamp, final long interval) {
final String endpoint = getEndpointKey(account, device); final String endpoint = getEndpointKey(account, device);
insertScript.execute(List.of(getPendingNotificationQueueKey(endpoint), endpoint), insertPendingVoipDestinationScript.execute(
List.of(String.valueOf(timestamp), List.of(getPendingRecurringVoipNotificationQueueKey(endpoint), endpoint),
String.valueOf(interval), List.of(String.valueOf(timestamp),
account.getUuid().toString(), String.valueOf(interval),
String.valueOf(device.getId()))); account.getUuid().toString(),
String.valueOf(device.getId())));
} }
@VisibleForTesting @VisibleForTesting
@ -237,32 +233,15 @@ public class ApnFallbackManager implements Managed {
return "apn_device::{" + account.getUuid() + "::" + device.getId() + "}"; return "apn_device::{" + account.getUuid() + "::" + device.getId() + "}";
} }
private String getPendingNotificationQueueKey(final String endpoint) { private String getPendingRecurringVoipNotificationQueueKey(final String endpoint) {
return getPendingNotificationQueueKey(SlotHash.getSlot(endpoint)); return getPendingRecurringVoipNotificationQueueKey(SlotHash.getSlot(endpoint));
} }
private String getPendingNotificationQueueKey(final int slot) { private String getPendingRecurringVoipNotificationQueueKey(final int slot) {
return PENDING_NOTIFICATIONS_KEY + "::{" + RedisClusterUtil.getMinimalHashTag(slot) + "}"; return PENDING_NOTIFICATIONS_KEY + "::{" + RedisClusterUtil.getMinimalHashTag(slot) + "}";
} }
private int getNextSlot() { private int getNextSlot() {
return (int)(cluster.withCluster(connection -> connection.sync().incr(NEXT_SLOT_TO_PERSIST_KEY)) % SlotHash.SLOT_COUNT); return (int)(pushSchedulingCluster.withCluster(connection -> connection.sync().incr(NEXT_SLOT_TO_PERSIST_KEY)) % SlotHash.SLOT_COUNT);
} }
private static class VoipRatioGauge extends RatioGauge {
private final Meter success;
private final Meter attempts;
private VoipRatioGauge(Meter success, Meter attempts) {
this.success = success;
this.attempts = attempts;
}
@Override
protected Ratio getRatio() {
return RatioGauge.Ratio.of(success.getFiveMinuteRate(), attempts.getFiveMinuteRate());
}
}
} }

View File

@ -9,7 +9,6 @@ import static org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
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;

View File

@ -1,9 +1,9 @@
/* /*
* Copyright 2013-2020 Signal Messenger, LLC * Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.metrics; package org.whispersystems.textsecuregcm.push;
import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
@ -28,6 +28,7 @@ import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.SystemMapper;
@ -103,7 +104,7 @@ public class PushLatencyManager {
this.clock = clock; this.clock = clock;
} }
public void recordPushSent(final UUID accountUuid, final long deviceId, final boolean isVoip) { void recordPushSent(final UUID accountUuid, final long deviceId, final boolean isVoip) {
try { try {
final String recordJson = SystemMapper.getMapper().writeValueAsString( final String recordJson = SystemMapper.getMapper().writeValueAsString(
new PushRecord(Instant.now(clock), isVoip ? PushType.VOIP : PushType.STANDARD)); new PushRecord(Instant.now(clock), isVoip ? PushType.VOIP : PushType.STANDARD));
@ -118,7 +119,7 @@ public class PushLatencyManager {
} }
} }
public void recordQueueRead(final UUID accountUuid, final long deviceId, final String userAgentString) { void recordQueueRead(final UUID accountUuid, final long deviceId, final String userAgentString) {
takePushRecord(accountUuid, deviceId).thenAccept(pushRecord -> { takePushRecord(accountUuid, deviceId).thenAccept(pushRecord -> {
if (pushRecord != null) { if (pushRecord != null) {
final Duration latency = Duration.between(pushRecord.getTimestamp(), Instant.now()); final Duration latency = Duration.between(pushRecord.getTimestamp(), Instant.now());

View File

@ -5,6 +5,8 @@
package org.whispersystems.textsecuregcm.push; package org.whispersystems.textsecuregcm.push;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Tags;
@ -18,14 +20,13 @@ import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.Util; import org.whispersystems.textsecuregcm.util.Util;
import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name;
public class PushNotificationManager { public class PushNotificationManager {
private final AccountsManager accountsManager; private final AccountsManager accountsManager;
private final APNSender apnSender; private final APNSender apnSender;
private final FcmSender fcmSender; private final FcmSender fcmSender;
private final ApnFallbackManager fallbackManager; private final ApnPushNotificationScheduler apnPushNotificationScheduler;
private final PushLatencyManager pushLatencyManager;
private static final String SENT_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "sentPushNotification"); private static final String SENT_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "sentPushNotification");
private static final String FAILED_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "failedPushNotification"); private static final String FAILED_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "failedPushNotification");
@ -35,12 +36,14 @@ public class PushNotificationManager {
public PushNotificationManager(final AccountsManager accountsManager, public PushNotificationManager(final AccountsManager accountsManager,
final APNSender apnSender, final APNSender apnSender,
final FcmSender fcmSender, final FcmSender fcmSender,
final ApnFallbackManager fallbackManager) { final ApnPushNotificationScheduler apnPushNotificationScheduler,
final PushLatencyManager pushLatencyManager) {
this.accountsManager = accountsManager; this.accountsManager = accountsManager;
this.apnSender = apnSender; this.apnSender = apnSender;
this.fcmSender = fcmSender; this.fcmSender = fcmSender;
this.fallbackManager = fallbackManager; this.apnPushNotificationScheduler = apnPushNotificationScheduler;
this.pushLatencyManager = pushLatencyManager;
} }
public void sendNewMessageNotification(final Account destination, final long destinationDeviceId) throws NotPushRegisteredException { public void sendNewMessageNotification(final Account destination, final long destinationDeviceId) throws NotPushRegisteredException {
@ -65,6 +68,11 @@ public class PushNotificationManager {
PushNotification.NotificationType.RATE_LIMIT_CHALLENGE, challengeToken, destination, device)); PushNotification.NotificationType.RATE_LIMIT_CHALLENGE, challengeToken, destination, device));
} }
public void handleMessagesRetrieved(final Account account, final Device device, final String userAgent) {
RedisOperation.unchecked(() -> pushLatencyManager.recordQueueRead(account.getUuid(), device.getId(), userAgent));
RedisOperation.unchecked(() -> apnPushNotificationScheduler.cancelRecurringVoipNotification(account, device));
}
@VisibleForTesting @VisibleForTesting
Pair<String, PushNotification.TokenType> getToken(final Device device) throws NotPushRegisteredException { Pair<String, PushNotification.TokenType> getToken(final Device device) throws NotPushRegisteredException {
final Pair<String, PushNotification.TokenType> tokenAndType; final Pair<String, PushNotification.TokenType> tokenAndType;
@ -112,7 +120,7 @@ public class PushNotificationManager {
pushNotification.destination() != null && pushNotification.destination() != null &&
pushNotification.destinationDevice() != null) { pushNotification.destinationDevice() != null) {
RedisOperation.unchecked(() -> fallbackManager.schedule(pushNotification.destination(), RedisOperation.unchecked(() -> apnPushNotificationScheduler.scheduleRecurringVoipNotification(pushNotification.destination(),
pushNotification.destinationDevice())); pushNotification.destinationDevice()));
} }
} else { } else {
@ -131,7 +139,7 @@ public class PushNotificationManager {
d.setUninstalledFeedbackTimestamp(Util.todayInMillis())); d.setUninstalledFeedbackTimestamp(Util.todayInMillis()));
} }
} else { } else {
RedisOperation.unchecked(() -> fallbackManager.cancel(account, device)); RedisOperation.unchecked(() -> apnPushNotificationScheduler.cancelRecurringVoipNotification(account, device));
} }
} }
} }

View File

@ -15,8 +15,6 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
import org.whispersystems.textsecuregcm.redis.RedisOperation;
import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.Pair;
@ -32,17 +30,14 @@ public class MessagesManager {
private final MessagesDynamoDb messagesDynamoDb; private final MessagesDynamoDb messagesDynamoDb;
private final MessagesCache messagesCache; private final MessagesCache messagesCache;
private final PushLatencyManager pushLatencyManager;
private final ReportMessageManager reportMessageManager; private final ReportMessageManager reportMessageManager;
public MessagesManager( public MessagesManager(
final MessagesDynamoDb messagesDynamoDb, final MessagesDynamoDb messagesDynamoDb,
final MessagesCache messagesCache, final MessagesCache messagesCache,
final PushLatencyManager pushLatencyManager,
final ReportMessageManager reportMessageManager) { final ReportMessageManager reportMessageManager) {
this.messagesDynamoDb = messagesDynamoDb; this.messagesDynamoDb = messagesDynamoDb;
this.messagesCache = messagesCache; this.messagesCache = messagesCache;
this.pushLatencyManager = pushLatencyManager;
this.reportMessageManager = reportMessageManager; this.reportMessageManager = reportMessageManager;
} }
@ -60,9 +55,7 @@ public class MessagesManager {
return messagesCache.hasMessages(destinationUuid, destinationDevice); return messagesCache.hasMessages(destinationUuid, destinationDevice);
} }
public Pair<List<Envelope>, Boolean> getMessagesForDevice(UUID destinationUuid, long destinationDevice, final String userAgent, final boolean cachedMessagesOnly) { public Pair<List<Envelope>, Boolean> getMessagesForDevice(UUID destinationUuid, long destinationDevice, final boolean cachedMessagesOnly) {
RedisOperation.unchecked(() -> pushLatencyManager.recordQueueRead(destinationUuid, destinationDevice, userAgent));
List<Envelope> messageList = new ArrayList<>(); List<Envelope> messageList = new ArrayList<>();
if (!cachedMessagesOnly) { if (!cachedMessagesOnly) {

View File

@ -18,7 +18,6 @@ import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
@ -44,21 +43,18 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
private final ReceiptSender receiptSender; private final ReceiptSender receiptSender;
private final MessagesManager messagesManager; private final MessagesManager messagesManager;
private final PushNotificationManager pushNotificationManager; private final PushNotificationManager pushNotificationManager;
private final ApnFallbackManager apnFallbackManager;
private final ClientPresenceManager clientPresenceManager; private final ClientPresenceManager clientPresenceManager;
private final ScheduledExecutorService scheduledExecutorService; private final ScheduledExecutorService scheduledExecutorService;
public AuthenticatedConnectListener(ReceiptSender receiptSender, public AuthenticatedConnectListener(ReceiptSender receiptSender,
MessagesManager messagesManager, MessagesManager messagesManager,
PushNotificationManager pushNotificationManager, PushNotificationManager pushNotificationManager,
ApnFallbackManager apnFallbackManager,
ClientPresenceManager clientPresenceManager, ClientPresenceManager clientPresenceManager,
ScheduledExecutorService scheduledExecutorService) ScheduledExecutorService scheduledExecutorService)
{ {
this.receiptSender = receiptSender; this.receiptSender = receiptSender;
this.messagesManager = messagesManager; this.messagesManager = messagesManager;
this.pushNotificationManager = pushNotificationManager; this.pushNotificationManager = pushNotificationManager;
this.apnFallbackManager = apnFallbackManager;
this.clientPresenceManager = clientPresenceManager; this.clientPresenceManager = clientPresenceManager;
this.scheduledExecutorService = scheduledExecutorService; this.scheduledExecutorService = scheduledExecutorService;
} }
@ -75,7 +71,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
scheduledExecutorService); scheduledExecutorService);
openWebsocketCounter.inc(); openWebsocketCounter.inc();
RedisOperation.unchecked(() -> apnFallbackManager.cancel(auth.getAccount(), device)); pushNotificationManager.handleMessagesRetrieved(auth.getAccount(), device, context.getClient().getUserAgent());
final AtomicReference<ScheduledFuture<?>> renewPresenceFutureReference = new AtomicReference<>(); final AtomicReference<ScheduledFuture<?>> renewPresenceFutureReference = new AtomicReference<>();

View File

@ -320,8 +320,8 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
private void sendNextMessagePage(final boolean cachedMessagesOnly, final CompletableFuture<Void> queueClearedFuture) { private void sendNextMessagePage(final boolean cachedMessagesOnly, final CompletableFuture<Void> queueClearedFuture) {
try { try {
final Pair<List<Envelope>, Boolean> messagesAndHasMore = messagesManager final Pair<List<Envelope>, Boolean> messagesAndHasMore = messagesManager.getMessagesForDevice(
.getMessagesForDevice(auth.getAccount().getUuid(), device.getId(), client.getUserAgent(), cachedMessagesOnly); auth.getAccount().getUuid(), device.getId(), cachedMessagesOnly);
final List<Envelope> messages = messagesAndHasMore.first(); final List<Envelope> messages = messagesAndHasMore.first();
final boolean hasMore = messagesAndHasMore.second(); final boolean hasMore = messagesAndHasMore.second();

View File

@ -25,7 +25,7 @@ import net.sourceforge.argparse4j.inf.Subparser;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration; import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager; import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
@ -180,7 +180,7 @@ public class AssignUsernameCommand extends EnvironmentCommand<WhisperServerConfi
configuration.getReportMessageConfiguration().getReportTtl()); configuration.getReportMessageConfiguration().getReportTtl());
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster,
configuration.getReportMessageConfiguration().getCounterTtl()); configuration.getReportMessageConfiguration().getCounterTtl());
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager, MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache,
reportMessageManager); reportMessageManager);
DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
deletedAccountsLockDynamoDbClient, deletedAccountsLockDynamoDbClient,

View File

@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration; import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager; import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
@ -183,8 +183,8 @@ public class DeleteUserCommand extends EnvironmentCommand<WhisperServerConfigura
configuration.getReportMessageConfiguration().getReportTtl()); configuration.getReportMessageConfiguration().getReportTtl());
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster,
configuration.getReportMessageConfiguration().getCounterTtl()); configuration.getReportMessageConfiguration().getCounterTtl());
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager, MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache,
reportMessageManager); reportMessageManager);
DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
deletedAccountsLockDynamoDbClient, deletedAccountsLockDynamoDbClient,
configuration.getDynamoDbTables().getDeletedAccountsLock().getTableName()); configuration.getDynamoDbTables().getDeletedAccountsLock().getTableName());

View File

@ -26,7 +26,7 @@ import net.sourceforge.argparse4j.inf.Subparser;
import org.whispersystems.textsecuregcm.WhisperServerConfiguration; import org.whispersystems.textsecuregcm.WhisperServerConfiguration;
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator; import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialGenerator;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager; import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
@ -184,8 +184,8 @@ public class SetUserDiscoverabilityCommand extends EnvironmentCommand<WhisperSer
configuration.getReportMessageConfiguration().getReportTtl()); configuration.getReportMessageConfiguration().getReportTtl());
ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster, ReportMessageManager reportMessageManager = new ReportMessageManager(reportMessageDynamoDb, rateLimitersCluster,
configuration.getReportMessageConfiguration().getCounterTtl()); configuration.getReportMessageConfiguration().getCounterTtl());
MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, pushLatencyManager, MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache,
reportMessageManager); reportMessageManager);
DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
deletedAccountsLockDynamoDbClient, deletedAccountsLockDynamoDbClient,
configuration.getDynamoDbTables().getDeletedAccountsLock().getTableName()); configuration.getDynamoDbTables().getDeletedAccountsLock().getTableName());

View File

@ -67,8 +67,8 @@ import org.whispersystems.textsecuregcm.entities.StaleDevices;
import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
import org.whispersystems.textsecuregcm.push.MessageSender; import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.AccountsManager;
@ -107,7 +107,7 @@ class MessageControllerTest {
private static final MessagesManager messagesManager = mock(MessagesManager.class); private static final MessagesManager messagesManager = mock(MessagesManager.class);
private static final RateLimiters rateLimiters = mock(RateLimiters.class); private static final RateLimiters rateLimiters = mock(RateLimiters.class);
private static final RateLimiter rateLimiter = mock(RateLimiter.class); private static final RateLimiter rateLimiter = mock(RateLimiter.class);
private static final ApnFallbackManager apnFallbackManager = mock(ApnFallbackManager.class); private static final PushNotificationManager pushNotificationManager = mock(PushNotificationManager.class);
private static final ReportMessageManager reportMessageManager = mock(ReportMessageManager.class); private static final ReportMessageManager reportMessageManager = mock(ReportMessageManager.class);
private static final ExecutorService multiRecipientMessageExecutor = mock(ExecutorService.class); private static final ExecutorService multiRecipientMessageExecutor = mock(ExecutorService.class);
@ -119,7 +119,7 @@ class MessageControllerTest {
.setTestContainerFactory(new GrizzlyWebTestContainerFactory()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource( .addResource(
new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager, new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccountsManager,
messagesManager, apnFallbackManager, reportMessageManager, multiRecipientMessageExecutor)) messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor))
.build(); .build();
@BeforeEach @BeforeEach
@ -170,7 +170,7 @@ class MessageControllerTest {
messagesManager, messagesManager,
rateLimiters, rateLimiters,
rateLimiter, rateLimiter,
apnFallbackManager, pushNotificationManager,
reportMessageManager reportMessageManager
); );
} }
@ -435,13 +435,16 @@ class MessageControllerTest {
.map(OutgoingMessageEntity::fromEnvelope) .map(OutgoingMessageEntity::fromEnvelope)
.toList(), false); .toList(), false);
when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_UUID), eq(1L), anyString(), anyBoolean())) when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_UUID), eq(1L), anyBoolean()))
.thenReturn(new Pair<>(messages, false)); .thenReturn(new Pair<>(messages, false));
final String userAgent = "Test-UA";
OutgoingMessageEntityList response = OutgoingMessageEntityList response =
resources.getJerseyTest().target("/v1/messages/") resources.getJerseyTest().target("/v1/messages/")
.request() .request()
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD)) .header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
.header("USer-Agent", userAgent)
.accept(MediaType.APPLICATION_JSON_TYPE) .accept(MediaType.APPLICATION_JSON_TYPE)
.get(OutgoingMessageEntityList.class); .get(OutgoingMessageEntityList.class);
@ -458,6 +461,8 @@ class MessageControllerTest {
assertEquals(updatedPniOne, response.messages().get(0).updatedPni()); assertEquals(updatedPniOne, response.messages().get(0).updatedPni());
assertNull(response.messages().get(1).updatedPni()); assertNull(response.messages().get(1).updatedPni());
verify(pushNotificationManager).handleMessagesRetrieved(AuthHelper.VALID_ACCOUNT, AuthHelper.VALID_DEVICE, userAgent);
} }
@Test @Test
@ -472,7 +477,7 @@ class MessageControllerTest {
UUID.randomUUID(), 2, AuthHelper.VALID_UUID, null, null, 0) UUID.randomUUID(), 2, AuthHelper.VALID_UUID, null, null, 0)
); );
when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_UUID), eq(1L), anyString(), anyBoolean())) when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_UUID), eq(1L), anyBoolean()))
.thenReturn(new Pair<>(messages, false)); .thenReturn(new Pair<>(messages, false));
Response response = Response response =

View File

@ -26,7 +26,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.Pair;
class ApnFallbackManagerTest { class ApnPushNotificationSchedulerTest {
@RegisterExtension @RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
@ -36,7 +36,7 @@ class ApnFallbackManagerTest {
private APNSender apnSender; private APNSender apnSender;
private ApnFallbackManager apnFallbackManager; private ApnPushNotificationScheduler apnPushNotificationScheduler;
private static final UUID ACCOUNT_UUID = UUID.randomUUID(); private static final UUID ACCOUNT_UUID = UUID.randomUUID();
private static final String ACCOUNT_NUMBER = "+18005551234"; private static final String ACCOUNT_NUMBER = "+18005551234";
@ -62,41 +62,43 @@ class ApnFallbackManagerTest {
apnSender = mock(APNSender.class); apnSender = mock(APNSender.class);
apnFallbackManager = new ApnFallbackManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(), apnSender, accountsManager); apnPushNotificationScheduler = new ApnPushNotificationScheduler(REDIS_CLUSTER_EXTENSION.getRedisCluster(), apnSender, accountsManager);
} }
@Test @Test
void testClusterInsert() { void testClusterInsert() {
final String endpoint = apnFallbackManager.getEndpointKey(account, device); final String endpoint = apnPushNotificationScheduler.getEndpointKey(account, device);
assertTrue(apnFallbackManager.getPendingDestinations(SlotHash.getSlot(endpoint), 1).isEmpty()); assertTrue(
apnPushNotificationScheduler.getPendingDestinationsForRecurringVoipNotifications(SlotHash.getSlot(endpoint), 1).isEmpty());
apnFallbackManager.schedule(account, device, System.currentTimeMillis() - 30_000); apnPushNotificationScheduler.scheduleRecurringVoipNotification(account, device, System.currentTimeMillis() - 30_000);
final List<String> pendingDestinations = apnFallbackManager.getPendingDestinations(SlotHash.getSlot(endpoint), 2); final List<String> pendingDestinations = apnPushNotificationScheduler.getPendingDestinationsForRecurringVoipNotifications(SlotHash.getSlot(endpoint), 2);
assertEquals(1, pendingDestinations.size()); assertEquals(1, pendingDestinations.size());
final Optional<Pair<String, Long>> maybeUuidAndDeviceId = ApnFallbackManager.getSeparated( final Optional<Pair<String, Long>> maybeUuidAndDeviceId = ApnPushNotificationScheduler.getSeparated(
pendingDestinations.get(0)); pendingDestinations.get(0));
assertTrue(maybeUuidAndDeviceId.isPresent()); assertTrue(maybeUuidAndDeviceId.isPresent());
assertEquals(ACCOUNT_UUID.toString(), maybeUuidAndDeviceId.get().first()); assertEquals(ACCOUNT_UUID.toString(), maybeUuidAndDeviceId.get().first());
assertEquals(DEVICE_ID, (long) maybeUuidAndDeviceId.get().second()); assertEquals(DEVICE_ID, (long) maybeUuidAndDeviceId.get().second());
assertTrue(apnFallbackManager.getPendingDestinations(SlotHash.getSlot(endpoint), 1).isEmpty()); assertTrue(
apnPushNotificationScheduler.getPendingDestinationsForRecurringVoipNotifications(SlotHash.getSlot(endpoint), 1).isEmpty());
} }
@Test @Test
void testProcessNextSlot() { void testProcessNextSlot() {
final ApnFallbackManager.NotificationWorker worker = apnFallbackManager.new NotificationWorker(); final ApnPushNotificationScheduler.NotificationWorker worker = apnPushNotificationScheduler.new NotificationWorker();
apnFallbackManager.schedule(account, device, System.currentTimeMillis() - 30_000); apnPushNotificationScheduler.scheduleRecurringVoipNotification(account, device, System.currentTimeMillis() - 30_000);
final int slot = SlotHash.getSlot(apnFallbackManager.getEndpointKey(account, device)); final int slot = SlotHash.getSlot(apnPushNotificationScheduler.getEndpointKey(account, device));
final int previousSlot = (slot + SlotHash.SLOT_COUNT - 1) % SlotHash.SLOT_COUNT; final int previousSlot = (slot + SlotHash.SLOT_COUNT - 1) % SlotHash.SLOT_COUNT;
REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(connection -> connection.sync() REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(connection -> connection.sync()
.set(ApnFallbackManager.NEXT_SLOT_TO_PERSIST_KEY, String.valueOf(previousSlot))); .set(ApnPushNotificationScheduler.NEXT_SLOT_TO_PERSIST_KEY, String.valueOf(previousSlot)));
assertEquals(1, worker.processNextSlot()); assertEquals(1, worker.processNextSlot());

View File

@ -26,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
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.storage.MessagesManager;

View File

@ -1,9 +1,9 @@
/* /*
* Copyright 2013-2020 Signal Messenger, LLC * Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
package org.whispersystems.textsecuregcm.metrics; package org.whispersystems.textsecuregcm.push;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -23,8 +23,9 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicPushLatencyConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicPushLatencyConfiguration;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager.PushRecord; import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager.PushType; import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushRecord;
import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushType;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;

View File

@ -14,6 +14,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -28,7 +29,8 @@ class PushNotificationManagerTest {
private AccountsManager accountsManager; private AccountsManager accountsManager;
private APNSender apnSender; private APNSender apnSender;
private FcmSender fcmSender; private FcmSender fcmSender;
private ApnFallbackManager apnFallbackManager; private ApnPushNotificationScheduler apnPushNotificationScheduler;
private PushLatencyManager pushLatencyManager;
private PushNotificationManager pushNotificationManager; private PushNotificationManager pushNotificationManager;
@ -37,11 +39,13 @@ class PushNotificationManagerTest {
accountsManager = mock(AccountsManager.class); accountsManager = mock(AccountsManager.class);
apnSender = mock(APNSender.class); apnSender = mock(APNSender.class);
fcmSender = mock(FcmSender.class); fcmSender = mock(FcmSender.class);
apnFallbackManager = mock(ApnFallbackManager.class); apnPushNotificationScheduler = mock(ApnPushNotificationScheduler.class);
pushLatencyManager = mock(PushLatencyManager.class);
AccountsHelper.setupMockUpdate(accountsManager); AccountsHelper.setupMockUpdate(accountsManager);
pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, apnFallbackManager); pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender,
apnPushNotificationScheduler, pushLatencyManager);
} }
@Test @Test
@ -113,7 +117,7 @@ class PushNotificationManagerTest {
verifyNoInteractions(apnSender); verifyNoInteractions(apnSender);
verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any()); verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any());
verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis()); verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis());
verifyNoInteractions(apnFallbackManager); verifyNoInteractions(apnPushNotificationScheduler);
} }
@Test @Test
@ -136,7 +140,7 @@ class PushNotificationManagerTest {
verifyNoInteractions(fcmSender); verifyNoInteractions(fcmSender);
verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any()); verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any());
verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis()); verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis());
verify(apnFallbackManager).schedule(account, device); verify(apnPushNotificationScheduler).scheduleRecurringVoipNotification(account, device);
} }
@Test @Test
@ -159,7 +163,7 @@ class PushNotificationManagerTest {
verify(accountsManager).updateDevice(eq(account), eq(Device.MASTER_ID), any()); verify(accountsManager).updateDevice(eq(account), eq(Device.MASTER_ID), any());
verify(device).setUninstalledFeedbackTimestamp(Util.todayInMillis()); verify(device).setUninstalledFeedbackTimestamp(Util.todayInMillis());
verifyNoInteractions(apnSender); verifyNoInteractions(apnSender);
verifyNoInteractions(apnFallbackManager); verifyNoInteractions(apnPushNotificationScheduler);
} }
@Test @Test
@ -181,6 +185,22 @@ class PushNotificationManagerTest {
verifyNoInteractions(fcmSender); verifyNoInteractions(fcmSender);
verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any()); verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any());
verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis()); verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis());
verify(apnFallbackManager).cancel(account, device); verify(apnPushNotificationScheduler).cancelRecurringVoipNotification(account, device);
}
@Test
void testHandleMessagesRetrieved() {
final UUID accountIdentifier = UUID.randomUUID();
final Account account = mock(Account.class);
final Device device = mock(Device.class);
final String userAgent = "User-Agent";
when(account.getUuid()).thenReturn(accountIdentifier);
when(device.getId()).thenReturn(Device.MASTER_ID);
pushNotificationManager.handleMessagesRetrieved(account, device, userAgent);
verify(pushLatencyManager).recordQueueRead(accountIdentifier, Device.MASTER_ID, userAgent);
verify(apnPushNotificationScheduler).cancelRecurringVoipNotification(account, device);
} }
} }

View File

@ -32,7 +32,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager; import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension; import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@ -73,8 +73,7 @@ class MessagePersisterIntegrationTest {
notificationExecutorService = Executors.newSingleThreadExecutor(); notificationExecutorService = Executors.newSingleThreadExecutor();
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(), messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
REDIS_CLUSTER_EXTENSION.getRedisCluster(), notificationExecutorService); REDIS_CLUSTER_EXTENSION.getRedisCluster(), notificationExecutorService);
messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, mock(PushLatencyManager.class), messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, mock(ReportMessageManager.class));
mock(ReportMessageManager.class));
messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager, messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager,
dynamicConfigurationManager, PERSIST_DELAY); dynamicConfigurationManager, PERSIST_DELAY);

View File

@ -14,7 +14,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.util.UUID; import java.util.UUID;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager; import org.whispersystems.textsecuregcm.push.PushLatencyManager;
class MessagesManagerTest { class MessagesManagerTest {
@ -24,7 +24,7 @@ class MessagesManagerTest {
private final ReportMessageManager reportMessageManager = mock(ReportMessageManager.class); private final ReportMessageManager reportMessageManager = mock(ReportMessageManager.class);
private final MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, private final MessagesManager messagesManager = new MessagesManager(messagesDynamoDb, messagesCache,
pushLatencyManager, reportMessageManager); reportMessageManager);
@Test @Test
void insert() { void insert() {

View File

@ -43,7 +43,6 @@ import org.mockito.stubbing.Answer;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
@ -100,7 +99,7 @@ class WebSocketConnectionIntegrationTest {
webSocketConnection = new WebSocketConnection( webSocketConnection = new WebSocketConnection(
mock(ReceiptSender.class), mock(ReceiptSender.class),
new MessagesManager(messagesDynamoDb, messagesCache, mock(PushLatencyManager.class), reportMessageManager), new MessagesManager(messagesDynamoDb, messagesCache, reportMessageManager),
new AuthenticatedAccount(() -> new Pair<>(account, device)), new AuthenticatedAccount(() -> new Pair<>(account, device)),
device, device,
webSocketClient, webSocketClient,

View File

@ -52,7 +52,7 @@ import org.mockito.stubbing.Answer;
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList; import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager; import org.whispersystems.textsecuregcm.push.ApnPushNotificationScheduler;
import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager;
import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.push.ReceiptSender;
@ -83,7 +83,7 @@ class WebSocketConnectionTest {
private AuthenticatedAccount auth; private AuthenticatedAccount auth;
private UpgradeRequest upgradeRequest; private UpgradeRequest upgradeRequest;
private ReceiptSender receiptSender; private ReceiptSender receiptSender;
private ApnFallbackManager apnFallbackManager; private PushNotificationManager pushNotificationManager;
private ScheduledExecutorService retrySchedulingExecutor; private ScheduledExecutorService retrySchedulingExecutor;
@BeforeEach @BeforeEach
@ -95,7 +95,7 @@ class WebSocketConnectionTest {
auth = new AuthenticatedAccount(() -> new Pair<>(account, device)); auth = new AuthenticatedAccount(() -> new Pair<>(account, device));
upgradeRequest = mock(UpgradeRequest.class); upgradeRequest = mock(UpgradeRequest.class);
receiptSender = mock(ReceiptSender.class); receiptSender = mock(ReceiptSender.class);
apnFallbackManager = mock(ApnFallbackManager.class); pushNotificationManager = mock(PushNotificationManager.class);
retrySchedulingExecutor = mock(ScheduledExecutorService.class); retrySchedulingExecutor = mock(ScheduledExecutorService.class);
} }
@ -104,7 +104,7 @@ class WebSocketConnectionTest {
MessagesManager storedMessages = mock(MessagesManager.class); MessagesManager storedMessages = mock(MessagesManager.class);
WebSocketAccountAuthenticator webSocketAuthenticator = new WebSocketAccountAuthenticator(accountAuthenticator); WebSocketAccountAuthenticator webSocketAuthenticator = new WebSocketAccountAuthenticator(accountAuthenticator);
AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(receiptSender, storedMessages, AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(receiptSender, storedMessages,
mock(PushNotificationManager.class), apnFallbackManager, mock(ClientPresenceManager.class), mock(PushNotificationManager.class), mock(ClientPresenceManager.class),
retrySchedulingExecutor); retrySchedulingExecutor);
WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class); WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class);
@ -166,7 +166,7 @@ class WebSocketConnectionTest {
String userAgent = "user-agent"; String userAgent = "user-agent";
when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), userAgent, false)) when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), false))
.thenReturn(new Pair<>(outgoingMessages, false)); .thenReturn(new Pair<>(outgoingMessages, false));
final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>(); final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
@ -221,9 +221,8 @@ class WebSocketConnectionTest {
when(account.getUuid()).thenReturn(accountUuid); when(account.getUuid()).thenReturn(accountUuid);
when(device.getId()).thenReturn(1L); when(device.getId()).thenReturn(1L);
when(client.isOpen()).thenReturn(true); when(client.isOpen()).thenReturn(true);
when(client.getUserAgent()).thenReturn("Test-UA");
when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), eq("Test-UA"), anyBoolean())) when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), anyBoolean()))
.thenReturn(new Pair<>(Collections.emptyList(), false)) .thenReturn(new Pair<>(Collections.emptyList(), false))
.thenReturn(new Pair<>(List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 1111, "first")), false)) .thenReturn(new Pair<>(List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 1111, "first")), false))
.thenReturn(new Pair<>(List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 2222, "second")), false)); .thenReturn(new Pair<>(List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 2222, "second")), false));
@ -316,7 +315,7 @@ class WebSocketConnectionTest {
String userAgent = "user-agent"; String userAgent = "user-agent";
when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), userAgent, false)) when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), false))
.thenReturn(new Pair<>(pendingMessages, false)); .thenReturn(new Pair<>(pendingMessages, false));
final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>(); final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
@ -362,12 +361,11 @@ class WebSocketConnectionTest {
when(account.getUuid()).thenReturn(UUID.randomUUID()); when(account.getUuid()).thenReturn(UUID.randomUUID());
when(device.getId()).thenReturn(1L); when(device.getId()).thenReturn(1L);
when(client.isOpen()).thenReturn(true); when(client.isOpen()).thenReturn(true);
when(client.getUserAgent()).thenReturn("Test-UA");
final AtomicBoolean threadWaiting = new AtomicBoolean(false); final AtomicBoolean threadWaiting = new AtomicBoolean(false);
final AtomicBoolean returnMessageList = new AtomicBoolean(false); final AtomicBoolean returnMessageList = new AtomicBoolean(false);
when(messagesManager.getMessagesForDevice(account.getUuid(), 1L, client.getUserAgent(), false)).thenAnswer( when(messagesManager.getMessagesForDevice(account.getUuid(), 1L, false)).thenAnswer(
(Answer<OutgoingMessageEntityList>) invocation -> { (Answer<OutgoingMessageEntityList>) invocation -> {
synchronized (threadWaiting) { synchronized (threadWaiting) {
threadWaiting.set(true); threadWaiting.set(true);
@ -415,7 +413,7 @@ class WebSocketConnectionTest {
} }
}); });
verify(messagesManager).getMessagesForDevice(any(UUID.class), anyLong(), anyString(), eq(false)); verify(messagesManager).getMessagesForDevice(any(UUID.class), anyLong(), eq(false));
} }
@Test @Test
@ -429,7 +427,6 @@ class WebSocketConnectionTest {
when(account.getUuid()).thenReturn(UUID.randomUUID()); when(account.getUuid()).thenReturn(UUID.randomUUID());
when(device.getId()).thenReturn(1L); when(device.getId()).thenReturn(1L);
when(client.isOpen()).thenReturn(true); when(client.isOpen()).thenReturn(true);
when(client.getUserAgent()).thenReturn("Test-UA");
final List<Envelope> firstPageMessages = final List<Envelope> firstPageMessages =
List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 1111, "first"), List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 1111, "first"),
@ -438,7 +435,7 @@ class WebSocketConnectionTest {
final List<Envelope> secondPageMessages = final List<Envelope> secondPageMessages =
List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 3333, "third")); List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 3333, "third"));
when(messagesManager.getMessagesForDevice(account.getUuid(), 1L, client.getUserAgent(), false)) when(messagesManager.getMessagesForDevice(account.getUuid(), 1L, false))
.thenReturn(new Pair<>(firstPageMessages, true)) .thenReturn(new Pair<>(firstPageMessages, true))
.thenReturn(new Pair<>(secondPageMessages, false)); .thenReturn(new Pair<>(secondPageMessages, false));
@ -473,13 +470,12 @@ class WebSocketConnectionTest {
when(account.getUuid()).thenReturn(UUID.randomUUID()); when(account.getUuid()).thenReturn(UUID.randomUUID());
when(device.getId()).thenReturn(1L); when(device.getId()).thenReturn(1L);
when(client.isOpen()).thenReturn(true); when(client.isOpen()).thenReturn(true);
when(client.getUserAgent()).thenReturn("Test-UA");
final UUID senderUuid = UUID.randomUUID(); final UUID senderUuid = UUID.randomUUID();
final List<Envelope> messages = List.of( final List<Envelope> messages = List.of(
createMessage(senderUuid, UUID.randomUUID(), 1111L, "message the first")); createMessage(senderUuid, UUID.randomUUID(), 1111L, "message the first"));
when(messagesManager.getMessagesForDevice(account.getUuid(), 1L, client.getUserAgent(), false)) when(messagesManager.getMessagesForDevice(account.getUuid(), 1L, false))
.thenReturn(new Pair<>(messages, false)); .thenReturn(new Pair<>(messages, false));
final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class); final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class);
@ -530,9 +526,8 @@ class WebSocketConnectionTest {
when(account.getUuid()).thenReturn(accountUuid); when(account.getUuid()).thenReturn(accountUuid);
when(device.getId()).thenReturn(1L); when(device.getId()).thenReturn(1L);
when(client.isOpen()).thenReturn(true); when(client.isOpen()).thenReturn(true);
when(client.getUserAgent()).thenReturn("Test-UA");
when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), eq("Test-UA"), anyBoolean())) when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), anyBoolean()))
.thenReturn(new Pair<>(Collections.emptyList(), false)); .thenReturn(new Pair<>(Collections.emptyList(), false));
final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class); final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class);
@ -560,7 +555,6 @@ class WebSocketConnectionTest {
when(account.getUuid()).thenReturn(accountUuid); when(account.getUuid()).thenReturn(accountUuid);
when(device.getId()).thenReturn(1L); when(device.getId()).thenReturn(1L);
when(client.isOpen()).thenReturn(true); when(client.isOpen()).thenReturn(true);
when(client.getUserAgent()).thenReturn("Test-UA");
final List<Envelope> firstPageMessages = final List<Envelope> firstPageMessages =
List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 1111, "first"), List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 1111, "first"),
@ -569,7 +563,7 @@ class WebSocketConnectionTest {
final List<Envelope> secondPageMessages = final List<Envelope> secondPageMessages =
List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 3333, "third")); List.of(createMessage(UUID.randomUUID(), UUID.randomUUID(), 3333, "third"));
when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), eq("Test-UA"), anyBoolean())) when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), anyBoolean()))
.thenReturn(new Pair<>(firstPageMessages, false)) .thenReturn(new Pair<>(firstPageMessages, false))
.thenReturn(new Pair<>(secondPageMessages, false)) .thenReturn(new Pair<>(secondPageMessages, false))
.thenReturn(new Pair<>(Collections.emptyList(), false)); .thenReturn(new Pair<>(Collections.emptyList(), false));
@ -609,9 +603,8 @@ class WebSocketConnectionTest {
when(account.getUuid()).thenReturn(accountUuid); when(account.getUuid()).thenReturn(accountUuid);
when(device.getId()).thenReturn(1L); when(device.getId()).thenReturn(1L);
when(client.isOpen()).thenReturn(true); when(client.isOpen()).thenReturn(true);
when(client.getUserAgent()).thenReturn("Test-UA");
when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), eq("Test-UA"), anyBoolean())) when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), anyBoolean()))
.thenReturn(new Pair<>(Collections.emptyList(), false)); .thenReturn(new Pair<>(Collections.emptyList(), false));
final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class); final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class);
@ -623,11 +616,11 @@ class WebSocketConnectionTest {
// anything. // anything.
connection.processStoredMessages(); connection.processStoredMessages();
verify(messagesManager).getMessagesForDevice(account.getUuid(), device.getId(), client.getUserAgent(), false); verify(messagesManager).getMessagesForDevice(account.getUuid(), device.getId(), false);
connection.handleNewMessagesAvailable(); connection.handleNewMessagesAvailable();
verify(messagesManager).getMessagesForDevice(account.getUuid(), device.getId(), client.getUserAgent(), true); verify(messagesManager).getMessagesForDevice(account.getUuid(), device.getId(), true);
} }
@Test @Test
@ -643,9 +636,8 @@ class WebSocketConnectionTest {
when(account.getUuid()).thenReturn(accountUuid); when(account.getUuid()).thenReturn(accountUuid);
when(device.getId()).thenReturn(1L); when(device.getId()).thenReturn(1L);
when(client.isOpen()).thenReturn(true); when(client.isOpen()).thenReturn(true);
when(client.getUserAgent()).thenReturn("Test-UA");
when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), eq("Test-UA"), anyBoolean())) when(messagesManager.getMessagesForDevice(eq(accountUuid), eq(1L), anyBoolean()))
.thenReturn(new Pair<>(Collections.emptyList(), false)); .thenReturn(new Pair<>(Collections.emptyList(), false));
final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class); final WebSocketResponseMessage successResponse = mock(WebSocketResponseMessage.class);
@ -658,7 +650,7 @@ class WebSocketConnectionTest {
connection.processStoredMessages(); connection.processStoredMessages();
connection.handleMessagesPersisted(); connection.handleMessagesPersisted();
verify(messagesManager, times(2)).getMessagesForDevice(account.getUuid(), device.getId(), client.getUserAgent(), false); verify(messagesManager, times(2)).getMessagesForDevice(account.getUuid(), device.getId(), false);
} }
@Test @Test
@ -692,7 +684,7 @@ class WebSocketConnectionTest {
String userAgent = "Signal-Desktop/1.2.3"; String userAgent = "Signal-Desktop/1.2.3";
when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), userAgent, false)) when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), false))
.thenReturn(new Pair<>(outgoingMessages, false)); .thenReturn(new Pair<>(outgoingMessages, false));
final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>(); final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
@ -762,7 +754,7 @@ class WebSocketConnectionTest {
String userAgent = "Signal-Android/4.68.3"; String userAgent = "Signal-Android/4.68.3";
when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), userAgent, false)) when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), false))
.thenReturn(new Pair<>(outgoingMessages, false)); .thenReturn(new Pair<>(outgoingMessages, false));
final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>(); final List<CompletableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
@ -814,7 +806,7 @@ class WebSocketConnectionTest {
String userAgent = "Signal-Android/4.68.3"; String userAgent = "Signal-Android/4.68.3";
when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), userAgent, false)) when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), false))
.thenThrow(new RedisException("OH NO")); .thenThrow(new RedisException("OH NO"));
when(retrySchedulingExecutor.schedule(any(Runnable.class), anyLong(), any())).thenAnswer( when(retrySchedulingExecutor.schedule(any(Runnable.class), anyLong(), any())).thenAnswer(
@ -848,7 +840,7 @@ class WebSocketConnectionTest {
String userAgent = "Signal-Android/4.68.3"; String userAgent = "Signal-Android/4.68.3";
when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), userAgent, false)) when(storedMessages.getMessagesForDevice(account.getUuid(), device.getId(), false))
.thenThrow(new RedisException("OH NO")); .thenThrow(new RedisException("OH NO"));
final WebSocketClient client = mock(WebSocketClient.class); final WebSocketClient client = mock(WebSocketClient.class);