diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 62464ac2d..658d4c52f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -191,7 +191,6 @@ import org.whispersystems.textsecuregcm.metrics.TrafficSource; import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider; import org.whispersystems.textsecuregcm.providers.RedisClusterHealthCheck; import org.whispersystems.textsecuregcm.push.APNSender; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.FcmSender; import org.whispersystems.textsecuregcm.push.MessageSender; import org.whispersystems.textsecuregcm.push.ProvisioningManager; @@ -543,11 +542,6 @@ public class WhisperServerService extends Application(AuthenticatedDevice.class)); - environment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager, - pubSubClientEventManager)); + environment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, pubSubClientEventManager)); environment.jersey().register(new TimestampResponseFilter()); /// @@ -1009,11 +998,10 @@ public class WhisperServerService extends Application provisioningEnvironment = new WebSocketEnvironment<>(environment, webSocketEnvironment.getRequestLog(), Duration.ofMillis(60000)); - provisioningEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager, - pubSubClientEventManager)); + provisioningEnvironment.jersey().register(new WebsocketRefreshApplicationEventListener(accountsManager, pubSubClientEventManager)); provisioningEnvironment.setConnectListener(new ProvisioningConnectListener(provisioningManager)); provisioningEnvironment.jersey().register(new MetricsApplicationEventListener(TrafficSource.WEBSOCKET, clientReleaseManager)); provisioningEnvironment.jersey().register(new KeepAliveController(pubSubClientEventManager)); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/RegistrationLockVerificationManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/RegistrationLockVerificationManager.java index 7b0605cda..f4167eae6 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/RegistrationLockVerificationManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/RegistrationLockVerificationManager.java @@ -25,7 +25,6 @@ import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure; import org.whispersystems.textsecuregcm.entities.Svr3Credentials; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager; @@ -55,7 +54,6 @@ public class RegistrationLockVerificationManager { private static final String PHONE_VERIFICATION_TYPE_TAG_NAME = "phoneVerificationType"; private final AccountsManager accounts; - private final ClientPresenceManager clientPresenceManager; private final PubSubClientEventManager pubSubClientEventManager; private final ExternalServiceCredentialsGenerator svr2CredentialGenerator; private final ExternalServiceCredentialsGenerator svr3CredentialGenerator; @@ -65,7 +63,6 @@ public class RegistrationLockVerificationManager { public RegistrationLockVerificationManager( final AccountsManager accounts, - final ClientPresenceManager clientPresenceManager, final PubSubClientEventManager pubSubClientEventManager, final ExternalServiceCredentialsGenerator svr2CredentialGenerator, final ExternalServiceCredentialsGenerator svr3CredentialGenerator, @@ -73,7 +70,6 @@ public class RegistrationLockVerificationManager { final PushNotificationManager pushNotificationManager, final RateLimiters rateLimiters) { this.accounts = accounts; - this.clientPresenceManager = clientPresenceManager; this.pubSubClientEventManager = pubSubClientEventManager; this.svr2CredentialGenerator = svr2CredentialGenerator; this.svr3CredentialGenerator = svr3CredentialGenerator; @@ -165,7 +161,6 @@ public class RegistrationLockVerificationManager { } final List deviceIds = updatedAccount.getDevices().stream().map(Device::getId).toList(); - clientPresenceManager.disconnectAllPresences(updatedAccount.getUuid(), deviceIds); pubSubClientEventManager.requestDisconnection(updatedAccount.getUuid(), deviceIds); try { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/WebsocketRefreshApplicationEventListener.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/WebsocketRefreshApplicationEventListener.java index a645a790c..0c91d141f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/WebsocketRefreshApplicationEventListener.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/WebsocketRefreshApplicationEventListener.java @@ -9,7 +9,6 @@ import org.glassfish.jersey.server.monitoring.ApplicationEvent; import org.glassfish.jersey.server.monitoring.ApplicationEventListener; import org.glassfish.jersey.server.monitoring.RequestEvent; import org.glassfish.jersey.server.monitoring.RequestEventListener; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -21,11 +20,9 @@ public class WebsocketRefreshApplicationEventListener implements ApplicationEven private final WebsocketRefreshRequestEventListener websocketRefreshRequestEventListener; public WebsocketRefreshApplicationEventListener(final AccountsManager accountsManager, - final ClientPresenceManager clientPresenceManager, final PubSubClientEventManager pubSubClientEventManager) { - this.websocketRefreshRequestEventListener = new WebsocketRefreshRequestEventListener(clientPresenceManager, - pubSubClientEventManager, + this.websocketRefreshRequestEventListener = new WebsocketRefreshRequestEventListener(pubSubClientEventManager, new LinkedDeviceRefreshRequirementProvider(accountsManager), new PhoneNumberChangeRefreshRequirementProvider(accountsManager)); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/auth/WebsocketRefreshRequestEventListener.java b/service/src/main/java/org/whispersystems/textsecuregcm/auth/WebsocketRefreshRequestEventListener.java index 65f8e4dcb..a8f2e3aa8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/auth/WebsocketRefreshRequestEventListener.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/auth/WebsocketRefreshRequestEventListener.java @@ -19,12 +19,10 @@ import org.glassfish.jersey.server.monitoring.RequestEvent.Type; import org.glassfish.jersey.server.monitoring.RequestEventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; public class WebsocketRefreshRequestEventListener implements RequestEventListener { - private final ClientPresenceManager clientPresenceManager; private final PubSubClientEventManager pubSubClientEventManager; private final WebsocketRefreshRequirementProvider[] providers; @@ -37,11 +35,9 @@ public class WebsocketRefreshRequestEventListener implements RequestEventListene private static final Logger logger = LoggerFactory.getLogger(WebsocketRefreshRequestEventListener.class); public WebsocketRefreshRequestEventListener( - final ClientPresenceManager clientPresenceManager, final PubSubClientEventManager pubSubClientEventManager, final WebsocketRefreshRequirementProvider... providers) { - this.clientPresenceManager = clientPresenceManager; this.pubSubClientEventManager = pubSubClientEventManager; this.providers = providers; } @@ -64,7 +60,6 @@ public class WebsocketRefreshRequestEventListener implements RequestEventListene .forEach(pair -> { try { displacedDevices.incrementAndGet(); - clientPresenceManager.disconnectPresence(pair.first(), pair.second()); pubSubClientEventManager.requestDisconnection(pair.first(), List.of(pair.second())); } catch (final Exception e) { logger.error("Could not displace device presence", e); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeepAliveController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeepAliveController.java index c4e8f776b..3f7af8c07 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeepAliveController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/KeepAliveController.java @@ -22,7 +22,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.websocket.auth.ReadOnly; import org.whispersystems.websocket.session.WebSocketSession; diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/ClientPresenceManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/ClientPresenceManager.java deleted file mode 100644 index 54cbe6be4..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/ClientPresenceManager.java +++ /dev/null @@ -1,390 +0,0 @@ -/* - * Copyright 2013 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.push; - -import static com.codahale.metrics.MetricRegistry.name; - -import com.google.common.annotations.VisibleForTesting; -import io.dropwizard.lifecycle.Managed; -import io.lettuce.core.LettuceFutures; -import io.lettuce.core.RedisFuture; -import io.lettuce.core.ScriptOutputType; -import io.lettuce.core.cluster.SlotHash; -import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands; -import io.lettuce.core.cluster.event.ClusterTopologyChangedEvent; -import io.lettuce.core.cluster.models.partitions.RedisClusterNode; -import io.lettuce.core.cluster.pubsub.RedisClusterPubSubAdapter; -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.Metrics; -import io.micrometer.core.instrument.Timer; -import java.io.IOException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.redis.ClusterLuaScript; -import org.whispersystems.textsecuregcm.redis.FaultTolerantPubSubClusterConnection; -import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient; -import org.whispersystems.textsecuregcm.storage.Device; - -/** - * The client presence manager keeps track of which clients are actively connected and "present" to receive messages. - * Only one client per account/device may be present at a time; if a second client for the same account/device declares - * its presence, the previous client is displaced. - *

- * The client presence manager depends on Redis keyspace notifications and requires that the Redis instance support at - * least the following notification types: {@code K$z}. - */ -public class ClientPresenceManager extends RedisClusterPubSubAdapter implements Managed { - - private final String managerId = UUID.randomUUID().toString(); - private final String connectedClientSetKey = getConnectedClientSetKey(managerId); - - private final FaultTolerantRedisClusterClient presenceCluster; - private final FaultTolerantPubSubClusterConnection pubSubConnection; - - private final ClusterLuaScript clearPresenceScript; - private final ClusterLuaScript renewPresenceScript; - - private final ExecutorService keyspaceNotificationExecutorService; - private final ScheduledExecutorService scheduledExecutorService; - private ScheduledFuture pruneMissingPeersFuture; - - private final Map displacementListenersByPresenceKey = new ConcurrentHashMap<>(); - private final Map> pendingPresenceSetsByPresenceKey = new ConcurrentHashMap<>(); - - private final Timer checkPresenceTimer; - private final Timer setPresenceTimer; - private final Timer clearPresenceTimer; - private final Timer prunePeersTimer; - private final Counter pruneClientMeter; - private final Counter remoteDisplacementMeter; - private final Counter pubSubMessageMeter; - private final Counter displacementListenerAlreadyRemovedCounter; - - private static final int PRUNE_PEERS_INTERVAL_SECONDS = (int) Duration.ofSeconds(30).toSeconds(); - private static final int PRESENCE_EXPIRATION_SECONDS = (int) Duration.ofMinutes(11).toSeconds(); - - static final String MANAGER_SET_KEY = "presence::managers"; - - private static final Logger log = LoggerFactory.getLogger(ClientPresenceManager.class); - - public ClientPresenceManager(final FaultTolerantRedisClusterClient presenceCluster, - final ScheduledExecutorService scheduledExecutorService, - final ExecutorService keyspaceNotificationExecutorService) throws IOException { - this.presenceCluster = presenceCluster; - this.pubSubConnection = this.presenceCluster.createPubSubConnection(); - this.clearPresenceScript = ClusterLuaScript.fromResource(presenceCluster, "lua/clear_presence.lua", - ScriptOutputType.INTEGER); - this.renewPresenceScript = ClusterLuaScript.fromResource(presenceCluster, "lua/renew_presence.lua", - ScriptOutputType.VALUE); - this.scheduledExecutorService = scheduledExecutorService; - this.keyspaceNotificationExecutorService = keyspaceNotificationExecutorService; - - Metrics.gauge(name(getClass(), "localClientCount"), this, ignored -> displacementListenersByPresenceKey.size()); - - this.checkPresenceTimer = Metrics.timer(name(getClass(), "checkPresence")); - this.setPresenceTimer = Metrics.timer(name(getClass(), "setPresence")); - this.clearPresenceTimer = Metrics.timer(name(getClass(), "clearPresence")); - this.prunePeersTimer = Metrics.timer(name(getClass(), "prunePeers")); - this.pruneClientMeter = Metrics.counter(name(getClass(), "pruneClient")); - this.remoteDisplacementMeter = Metrics.counter(name(getClass(), "remoteDisplacement")); - this.pubSubMessageMeter = Metrics.counter(name(getClass(), "pubSubMessage")); - this.displacementListenerAlreadyRemovedCounter = Metrics.counter( - name(getClass(), "displacementListenerAlreadyRemoved")); - } - - @VisibleForTesting - FaultTolerantPubSubClusterConnection getPubSubConnection() { - return pubSubConnection; - } - - @Override - public void start() { - pubSubConnection.usePubSubConnection(connection -> { - connection.addListener(this); - - final String presenceChannel = getManagerPresenceChannel(managerId); - final int slot = SlotHash.getSlot(presenceChannel); - - connection.sync().nodes(node -> node.is(RedisClusterNode.NodeFlag.UPSTREAM) && node.hasSlot(slot)) - .commands() - .subscribe(presenceChannel); - }); - - pubSubConnection.subscribeToClusterTopologyChangedEvents(this::resubscribeAll); - - presenceCluster.useCluster(connection -> connection.sync().sadd(MANAGER_SET_KEY, managerId)); - - pruneMissingPeersFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - pruneMissingPeers(); - } catch (final Throwable t) { - log.warn("Failed to prune missing peers", t); - } - }, new Random().nextInt(PRUNE_PEERS_INTERVAL_SECONDS), PRUNE_PEERS_INTERVAL_SECONDS, TimeUnit.SECONDS); - } - - @Override - public void stop() { - pubSubConnection.usePubSubConnection(connection -> connection.removeListener(this)); - - if (pruneMissingPeersFuture != null) { - pruneMissingPeersFuture.cancel(false); - } - - for (final String presenceKey : displacementListenersByPresenceKey.keySet()) { - clearPresence(presenceKey); - } - - presenceCluster.useCluster(connection -> { - connection.sync().srem(MANAGER_SET_KEY, managerId); - connection.sync().del(getConnectedClientSetKey(managerId)); - }); - - pubSubConnection.usePubSubConnection( - connection -> connection.sync().upstream().commands().unsubscribe(getManagerPresenceChannel(managerId))); - } - - public void setPresent(final UUID accountUuid, final byte deviceId, - final DisplacedPresenceListener displacementListener) { - - setPresenceTimer.record(() -> { - final String presenceKey = getPresenceKey(accountUuid, deviceId); - - displacePresence(presenceKey, true); - - displacementListenersByPresenceKey.put(presenceKey, displacementListener); - - final CompletableFuture presenceFuture = new CompletableFuture<>(); - final CompletionStage previousFuture = pendingPresenceSetsByPresenceKey.put(presenceKey, presenceFuture); - if (previousFuture != null) { - log.debug("Another presence is already pending for {}:{}", accountUuid, deviceId); - } - - subscribeForRemotePresenceChanges(presenceKey); - - presenceCluster.withCluster(connection -> { - final RedisAdvancedClusterAsyncCommands commands = connection.async(); - - commands.sadd(connectedClientSetKey, presenceKey); - return commands.setex(presenceKey, PRESENCE_EXPIRATION_SECONDS, managerId); - }).whenComplete((result, throwable) -> { - if (throwable != null) { - presenceFuture.completeExceptionally(throwable); - } else { - presenceFuture.complete(null); - } - }); - - presenceFuture.whenComplete( - (ignored, throwable) -> pendingPresenceSetsByPresenceKey.remove(presenceKey, presenceFuture)); - }); - } - - public void renewPresence(final UUID accountUuid, final byte deviceId) { - renewPresenceScript.execute(List.of(getPresenceKey(accountUuid, deviceId)), - List.of(managerId, String.valueOf(PRESENCE_EXPIRATION_SECONDS))); - } - - public void disconnectAllPresences(final UUID accountUuid, final List deviceIds) { - - List presenceKeys = new ArrayList<>(); - deviceIds.forEach(deviceId -> { - String presenceKey = getPresenceKey(accountUuid, deviceId); - if (isLocallyPresent(accountUuid, deviceId)) { - displacePresence(presenceKey, false); - } - presenceKeys.add(presenceKey); - }); - - presenceCluster.useCluster(connection -> { - List> futures = presenceKeys.stream().map(key -> connection.async().del(key)).toList(); - LettuceFutures.awaitAll(connection.getTimeout(), futures.toArray(new RedisFuture[0])); - }); - } - - public void disconnectAllPresencesForUuid(final UUID accountUuid) { - disconnectAllPresences(accountUuid, Device.ALL_POSSIBLE_DEVICE_IDS); - } - - public void disconnectPresence(final UUID accountUuid, final byte deviceId) { - disconnectAllPresences(accountUuid, List.of(deviceId)); - } - - private void displacePresence(final String presenceKey, final boolean connectedElsewhere) { - final DisplacedPresenceListener displacementListener = displacementListenersByPresenceKey.get(presenceKey); - - if (displacementListener != null) { - displacementListener.handleDisplacement(connectedElsewhere); - } - - clearPresence(presenceKey); - } - - public boolean isPresent(final UUID accountUuid, final byte deviceId) { - return checkPresenceTimer.record(() -> - presenceCluster.withCluster(connection -> - connection.sync().exists(getPresenceKey(accountUuid, deviceId))) == 1); - } - - public boolean isLocallyPresent(final UUID accountUuid, final byte deviceId) { - return displacementListenersByPresenceKey.containsKey(getPresenceKey(accountUuid, deviceId)); - } - - public boolean clearPresence(final UUID accountUuid, final byte deviceId, final DisplacedPresenceListener listener) { - final String presenceKey = getPresenceKey(accountUuid, deviceId); - if (displacementListenersByPresenceKey.remove(presenceKey, listener)) { - return clearPresence(presenceKey); - } else { - displacementListenerAlreadyRemovedCounter.increment(); - return false; - } - } - - private boolean clearPresence(final String presenceKey) { - return clearPresenceTimer.record(() -> { - displacementListenersByPresenceKey.remove(presenceKey); - unsubscribeFromRemotePresenceChanges(presenceKey); - - final boolean removed = clearPresenceScript.execute(List.of(presenceKey), List.of(managerId)) != null; - presenceCluster.useCluster(connection -> connection.sync().srem(connectedClientSetKey, presenceKey)); - - return removed; - }); - } - - private void subscribeForRemotePresenceChanges(final String presenceKey) { - final int slot = SlotHash.getSlot(presenceKey); - - pubSubConnection.usePubSubConnection( - connection -> connection.sync().nodes(node -> node.is(RedisClusterNode.NodeFlag.UPSTREAM) && node.hasSlot(slot)) - .commands() - .subscribe(getKeyspaceNotificationChannel(presenceKey))); - } - - private void resubscribeAll(final ClusterTopologyChangedEvent event) { - for (final String presenceKey : displacementListenersByPresenceKey.keySet()) { - subscribeForRemotePresenceChanges(presenceKey); - } - } - - private void unsubscribeFromRemotePresenceChanges(final String presenceKey) { - pubSubConnection.usePubSubConnection( - connection -> connection.sync().upstream().commands().unsubscribe(getKeyspaceNotificationChannel(presenceKey))); - } - - void pruneMissingPeers() { - prunePeersTimer.record(() -> { - final Set peerIds = presenceCluster.withCluster( - connection -> connection.sync().smembers(MANAGER_SET_KEY)); - peerIds.remove(managerId); - - for (final String peerId : peerIds) { - final boolean peerMissing = presenceCluster.withCluster( - connection -> connection.sync().publish(getManagerPresenceChannel(peerId), "ping") == 0); - - if (peerMissing) { - log.debug("Presence manager {} did not respond to ping", peerId); - - final String connectedClientsKey = getConnectedClientSetKey(peerId); - - String presenceKey; - - while ((presenceKey = presenceCluster.withCluster(connection -> connection.sync().spop(connectedClientsKey))) - != null) { - clearPresenceScript.execute(List.of(presenceKey), List.of(peerId)); - pruneClientMeter.increment(); - } - - presenceCluster.useCluster(connection -> { - connection.sync().del(connectedClientsKey); - connection.sync().srem(MANAGER_SET_KEY, peerId); - }); - } - } - }); - } - - @Override - public void message(final RedisClusterNode node, final String channel, final String message) { - pubSubMessageMeter.increment(); - - if (channel.startsWith("__keyspace@0__:presence::{")) { - if ("set".equals(message) || "del".equals(message)) { - // "set" might mean the client has connected to another host, although it might just be our own `set`, - // because we subscribe for changes before setting the key. - // for "del", another process has indicated the client should be disconnected - final boolean maybeConnectedElsewhere = "set".equals(message); - - // At this point, we're on a Lettuce IO thread and need to dispatch to a separate thread before making - // synchronous Lettuce calls to avoid deadlocking. - keyspaceNotificationExecutorService.execute(() -> { - final String clientPresenceKey = channel.substring("__keyspace@0__:".length()); - - final CompletionStage pendingConnection = pendingPresenceSetsByPresenceKey.getOrDefault(clientPresenceKey, - CompletableFuture.completedFuture(null)); - - pendingConnection.thenCompose(ignored -> { - if (maybeConnectedElsewhere) { - return presenceCluster.withCluster(connection -> connection.async().get(clientPresenceKey)) - .thenApply(currentManagerId -> !managerId.equals(currentManagerId)); - } - - return CompletableFuture.completedFuture(true); - }) - .exceptionally(ignored -> true) - .thenAcceptAsync(shouldDisplace -> { - if (shouldDisplace) { - try { - displacePresence(clientPresenceKey, maybeConnectedElsewhere); - remoteDisplacementMeter.increment(); - } catch (final Exception e) { - log.warn("Error displacing presence", e); - } - } - }, keyspaceNotificationExecutorService); - }); - } - } - } - - @VisibleForTesting - String getManagerId() { - return managerId; - } - - @VisibleForTesting - static String getPresenceKey(final UUID accountUuid, final byte deviceId) { - return "presence::{" + accountUuid.toString() + "::" + deviceId + "}"; - } - - private static String getKeyspaceNotificationChannel(final String presenceKey) { - return "__keyspace@0__:" + presenceKey; - } - - @VisibleForTesting - static String getConnectedClientSetKey(final String managerId) { - return "presence::clients::" + managerId; - } - - @VisibleForTesting - static String getManagerPresenceChannel(final String managerId) { - return "presence::manager::" + managerId; - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/DisplacedPresenceListener.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/DisplacedPresenceListener.java deleted file mode 100644 index 88258be70..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/DisplacedPresenceListener.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.push; - -/** - * A displaced presence listener is notified when a specific client's presence has been displaced because the same - * client opened a newer connection to the Signal service. - */ -@FunctionalInterface -public interface DisplacedPresenceListener { - - void handleDisplacement(boolean connectedElsewhere); -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java index 989dcd04b..52e5ef5ed 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/AccountsManager.java @@ -68,19 +68,17 @@ import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfigurati import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException; import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.entities.DeviceInfo; -import org.whispersystems.textsecuregcm.entities.RestoreAccountRequest; import org.whispersystems.textsecuregcm.entities.ECSignedPreKey; import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey; import org.whispersystems.textsecuregcm.entities.RemoteAttachment; +import org.whispersystems.textsecuregcm.entities.RestoreAccountRequest; import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.ServiceIdentifier; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantPubSubConnection; -import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; -import org.whispersystems.textsecuregcm.redis.RedisOperation; +import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecoveryException; @@ -126,12 +124,10 @@ public class AccountsManager extends RedisPubSubAdapter implemen private final ProfilesManager profilesManager; private final SecureStorageClient secureStorageClient; private final SecureValueRecovery2Client secureValueRecovery2Client; - private final ClientPresenceManager clientPresenceManager; private final PubSubClientEventManager pubSubClientEventManager; private final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager; private final ClientPublicKeysManager clientPublicKeysManager; private final Executor accountLockExecutor; - private final Executor clientPresenceExecutor; private final Clock clock; private final DynamicConfigurationManager dynamicConfigurationManager; @@ -206,12 +202,10 @@ public class AccountsManager extends RedisPubSubAdapter implemen final ProfilesManager profilesManager, final SecureStorageClient secureStorageClient, final SecureValueRecovery2Client secureValueRecovery2Client, - final ClientPresenceManager clientPresenceManager, final PubSubClientEventManager pubSubClientEventManager, final RegistrationRecoveryPasswordsManager registrationRecoveryPasswordsManager, final ClientPublicKeysManager clientPublicKeysManager, final Executor accountLockExecutor, - final Executor clientPresenceExecutor, final Clock clock, final byte[] linkDeviceSecret, final DynamicConfigurationManager dynamicConfigurationManager) { @@ -225,12 +219,10 @@ public class AccountsManager extends RedisPubSubAdapter implemen this.profilesManager = profilesManager; this.secureStorageClient = secureStorageClient; this.secureValueRecovery2Client = secureValueRecovery2Client; - this.clientPresenceManager = clientPresenceManager; this.pubSubClientEventManager = pubSubClientEventManager; this.registrationRecoveryPasswordsManager = requireNonNull(registrationRecoveryPasswordsManager); this.clientPublicKeysManager = clientPublicKeysManager; this.accountLockExecutor = accountLockExecutor; - this.clientPresenceExecutor = clientPresenceExecutor; this.clock = requireNonNull(clock); this.dynamicConfigurationManager = dynamicConfigurationManager; @@ -333,10 +325,7 @@ public class AccountsManager extends RedisPubSubAdapter implemen keysManager.deleteSingleUsePreKeys(pni), messagesManager.clear(aci), profilesManager.deleteAll(aci)) - .thenRunAsync(() -> { - clientPresenceManager.disconnectAllPresencesForUuid(aci); - pubSubClientEventManager.requestDisconnection(aci); - }, clientPresenceExecutor) + .thenCompose(ignored -> pubSubClientEventManager.requestDisconnection(aci)) .thenCompose(ignored -> accounts.reclaimAccount(e.getExistingAccount(), account, additionalWriteItems)) .thenCompose(ignored -> { // We should have cleared all messages before overwriting the old account, but more may have arrived @@ -598,12 +587,11 @@ public class AccountsManager extends RedisPubSubAdapter implemen return CompletableFuture.failedFuture(throwable); }) - .whenCompleteAsync((ignored, throwable) -> { + .whenComplete((ignored, throwable) -> { if (throwable == null) { - RedisOperation.unchecked(() -> clientPresenceManager.disconnectPresence(accountIdentifier, deviceId)); pubSubClientEventManager.requestDisconnection(accountIdentifier, List.of(deviceId)); } - }, clientPresenceExecutor); + }); } public Account changeNumber(final Account account, @@ -1248,11 +1236,7 @@ public class AccountsManager extends RedisPubSubAdapter implemen registrationRecoveryPasswordsManager.removeForNumber(account.getNumber())) .thenCompose(ignored -> accounts.delete(account.getUuid(), additionalWriteItems)) .thenCompose(ignored -> redisDeleteAsync(account)) - .thenRunAsync(() -> { - RedisOperation.unchecked(() -> clientPresenceManager.disconnectAllPresencesForUuid(account.getUuid())); - - pubSubClientEventManager.requestDisconnection(account.getUuid()); - }, clientPresenceExecutor); + .thenRun(() -> pubSubClientEventManager.requestDisconnection(account.getUuid())); } private String getAccountMapKey(String key) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java index 518be62d3..f83afda3f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java @@ -9,16 +9,12 @@ import static org.whispersystems.textsecuregcm.metrics.MetricsUtil.name; import io.micrometer.core.instrument.Tags; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; import org.whispersystems.textsecuregcm.limits.MessageDeliveryLoopMonitor; import org.whispersystems.textsecuregcm.metrics.MessageMetrics; import org.whispersystems.textsecuregcm.metrics.OpenWebSocketCounter; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; @@ -38,8 +34,6 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { private static final String AUTHENTICATED_TAG_NAME = "authenticated"; - private static final long RENEW_PRESENCE_INTERVAL_MINUTES = 5; - private static final Logger log = LoggerFactory.getLogger(AuthenticatedConnectListener.class); private final ReceiptSender receiptSender; @@ -47,7 +41,6 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { private final MessageMetrics messageMetrics; private final PushNotificationManager pushNotificationManager; private final PushNotificationScheduler pushNotificationScheduler; - private final ClientPresenceManager clientPresenceManager; private final PubSubClientEventManager pubSubClientEventManager; private final ScheduledExecutorService scheduledExecutorService; private final Scheduler messageDeliveryScheduler; @@ -62,7 +55,6 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { MessageMetrics messageMetrics, PushNotificationManager pushNotificationManager, PushNotificationScheduler pushNotificationScheduler, - ClientPresenceManager clientPresenceManager, PubSubClientEventManager pubSubClientEventManager, ScheduledExecutorService scheduledExecutorService, Scheduler messageDeliveryScheduler, @@ -73,7 +65,6 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { this.messageMetrics = messageMetrics; this.pushNotificationManager = pushNotificationManager; this.pushNotificationScheduler = pushNotificationScheduler; - this.clientPresenceManager = clientPresenceManager; this.pubSubClientEventManager = pubSubClientEventManager; this.scheduledExecutorService = scheduledExecutorService; this.messageDeliveryScheduler = messageDeliveryScheduler; @@ -110,21 +101,11 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { clientReleaseManager, messageDeliveryLoopMonitor); - final AtomicReference> renewPresenceFutureReference = new AtomicReference<>(); - context.addWebsocketClosedListener((closingContext, statusCode, reason) -> { - final ScheduledFuture renewPresenceFuture = renewPresenceFutureReference.get(); - - if (renewPresenceFuture != null) { - renewPresenceFuture.cancel(false); - } - // We begin the shutdown process by removing this client's "presence," which means it will again begin to // receive push notifications for inbound messages. We should do this first because, at this point, the // connection has already closed and attempts to actually deliver a message via the connection will not succeed. // It's preferable to start sending push notifications as soon as possible. - RedisOperation.unchecked(() -> clientPresenceManager.clearPresence(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId(), connection)); - pubSubClientEventManager.handleClientDisconnected(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId()); @@ -153,14 +134,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { // Finally, we register this client's presence, which suppresses push notifications. We do this last because // receiving extra push notifications is generally preferable to missing out on a push notification. - clientPresenceManager.setPresent(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId(), connection); pubSubClientEventManager.handleClientConnected(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId(), connection); - - renewPresenceFutureReference.set(scheduledExecutorService.scheduleAtFixedRate(() -> RedisOperation.unchecked(() -> - clientPresenceManager.renewPresence(auth.getAccount().getUuid(), auth.getAuthenticatedDevice().getId())), - RENEW_PRESENCE_INTERVAL_MINUTES, - RENEW_PRESENCE_INTERVAL_MINUTES, - TimeUnit.MINUTES)); } catch (final Exception e) { log.warn("Failed to initialize websocket", e); context.getClient().close(1011, "Unexpected error initializing connection"); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java index e6e6a72fc..569453bbd 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java @@ -46,7 +46,6 @@ import org.whispersystems.textsecuregcm.metrics.MessageMetrics; import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.push.ClientEventListener; -import org.whispersystems.textsecuregcm.push.DisplacedPresenceListener; import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; import org.whispersystems.textsecuregcm.push.ReceiptSender; @@ -64,7 +63,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -public class WebSocketConnection implements MessageAvailabilityListener, DisplacedPresenceListener, ClientEventListener { +public class WebSocketConnection implements MessageAvailabilityListener, ClientEventListener { private static final DistributionSummary messageTime = Metrics.summary( name(MessageController.class, "messageDeliveryDuration")); @@ -513,24 +512,11 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac .increment(); } - @Override - public void handleDisplacement(final boolean connectedElsewhere) { - final Tags tags = Tags.of( - UserAgentTagUtil.getPlatformTag(client.getUserAgent()), - Tag.of("connectedElsewhere", String.valueOf(connectedElsewhere)), - Tag.of(PRESENCE_MANAGER_TAG, "legacy") - ); - - Metrics.counter(DISPLACEMENT_COUNTER_NAME, tags).increment(); - } - @Override public void handleConnectionDisplaced(final boolean connectedElsewhere) { final Tags tags = Tags.of( UserAgentTagUtil.getPlatformTag(client.getUserAgent()), - Tag.of("connectedElsewhere", String.valueOf(connectedElsewhere)), - Tag.of(PRESENCE_MANAGER_TAG, "pubsub") - ); + Tag.of("connectedElsewhere", String.valueOf(connectedElsewhere))); Metrics.counter(DISPLACEMENT_COUNTER_NAME, tags).increment(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java index 7dc53f5c5..b9d9990aa 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/workers/CommandDependencies.java @@ -35,7 +35,6 @@ import org.whispersystems.textsecuregcm.experiment.PushNotificationExperimentSam import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.metrics.MicrometerAwsSdkMetricPublisher; import org.whispersystems.textsecuregcm.push.APNSender; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.FcmSender; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager; @@ -77,7 +76,6 @@ record CommandDependencies( ReportMessageManager reportMessageManager, MessagesCache messagesCache, MessagesManager messagesManager, - ClientPresenceManager clientPresenceManager, KeysManager keysManager, APNSender apnSender, FcmSender fcmSender, @@ -118,8 +116,6 @@ record CommandDependencies( FaultTolerantRedisClient pubsubClient = configuration.getRedisPubSubConfiguration().build("pubsub", redisClientResourcesBuilder.build()); - ScheduledExecutorService recurringJobExecutor = environment.lifecycle() - .scheduledExecutorService(name(name, "recurringJob-%d")).threads(2).build(); Scheduler messageDeliveryScheduler = Schedulers.fromExecutorService( environment.lifecycle().executorService("messageDelivery").minThreads(4).maxThreads(4).build()); ExecutorService keyspaceNotificationDispatchExecutor = environment.lifecycle() @@ -132,8 +128,6 @@ record CommandDependencies( .executorService(name(name, "storageService-%d")).maxThreads(8).minThreads(8).build(); ExecutorService accountLockExecutor = environment.lifecycle() .executorService(name(name, "accountLock-%d")).minThreads(8).maxThreads(8).build(); - ExecutorService clientPresenceExecutor = environment.lifecycle() - .executorService(name(name, "clientPresence-%d")).minThreads(8).maxThreads(8).build(); ExecutorService remoteStorageHttpExecutor = environment.lifecycle() .executorService(name(name, "remoteStorage-%d")) .minThreads(0).maxThreads(Integer.MAX_VALUE).workQueue(new SynchronousQueue<>()) @@ -215,8 +209,6 @@ record CommandDependencies( configuration.getSvr2Configuration()); SecureStorageClient secureStorageClient = new SecureStorageClient(storageCredentialsGenerator, storageServiceExecutor, storageServiceRetryExecutor, configuration.getSecureStorageServiceConfiguration()); - ClientPresenceManager clientPresenceManager = new ClientPresenceManager(clientPresenceCluster, - recurringJobExecutor, keyspaceNotificationDispatchExecutor); PubSubClientEventManager pubSubClientEventManager = new PubSubClientEventManager(messagesCluster, clientEventExecutor); MessagesCache messagesCache = new MessagesCache(messagesCluster, keyspaceNotificationDispatchExecutor, messageDeliveryScheduler, messageDeletionExecutor, Clock.systemUTC(), dynamicConfigurationManager); @@ -234,8 +226,8 @@ record CommandDependencies( new ClientPublicKeysManager(clientPublicKeys, accountLockManager, accountLockExecutor); AccountsManager accountsManager = new AccountsManager(accounts, phoneNumberIdentifiers, cacheCluster, pubsubClient, accountLockManager, keys, messagesManager, profilesManager, - secureStorageClient, secureValueRecovery2Client, clientPresenceManager, pubSubClientEventManager, - registrationRecoveryPasswordsManager, clientPublicKeysManager, accountLockExecutor, clientPresenceExecutor, + secureStorageClient, secureValueRecovery2Client, pubSubClientEventManager, + registrationRecoveryPasswordsManager, clientPublicKeysManager, accountLockExecutor, clock, configuration.getLinkDeviceSecretConfiguration().secret().value(), dynamicConfigurationManager); RateLimiters rateLimiters = RateLimiters.createAndValidate(configuration.getLimitsConfiguration(), dynamicConfigurationManager, rateLimitersCluster); @@ -272,7 +264,6 @@ record CommandDependencies( environment.lifecycle().manage(apnSender); environment.lifecycle().manage(messagesCache); - environment.lifecycle().manage(clientPresenceManager); environment.lifecycle().manage(pubSubClientEventManager); environment.lifecycle().manage(new ManagedAwsCrt()); @@ -282,7 +273,6 @@ record CommandDependencies( reportMessageManager, messagesCache, messagesManager, - clientPresenceManager, keys, apnSender, fcmSender, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/auth/LinkedDeviceRefreshRequirementProviderTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/auth/LinkedDeviceRefreshRequirementProviderTest.java index 5d5eccaab..849a1ef1c 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/auth/LinkedDeviceRefreshRequirementProviderTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/auth/LinkedDeviceRefreshRequirementProviderTest.java @@ -59,7 +59,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -96,7 +95,6 @@ class LinkedDeviceRefreshRequirementProviderTest { .build(); private AccountsManager accountsManager; - private ClientPresenceManager clientPresenceManager; private PubSubClientEventManager pubSubClientEventManager; private LinkedDeviceRefreshRequirementProvider provider; @@ -104,13 +102,12 @@ class LinkedDeviceRefreshRequirementProviderTest { @BeforeEach void setup() { accountsManager = mock(AccountsManager.class); - clientPresenceManager = mock(ClientPresenceManager.class); pubSubClientEventManager = mock(PubSubClientEventManager.class); provider = new LinkedDeviceRefreshRequirementProvider(accountsManager); final WebsocketRefreshRequestEventListener listener = - new WebsocketRefreshRequestEventListener(clientPresenceManager, pubSubClientEventManager, provider); + new WebsocketRefreshRequestEventListener(pubSubClientEventManager, provider); when(applicationEventListener.onRequest(any())).thenReturn(listener); @@ -121,9 +118,6 @@ class LinkedDeviceRefreshRequirementProviderTest { .forEach(deviceId -> account.addDevice(DevicesHelper.createDevice((byte) deviceId))); when(accountsManager.getByAccountIdentifier(uuid)).thenReturn(Optional.of(account)); - - account.getDevices() - .forEach(device -> when(clientPresenceManager.isPresent(uuid, device.getId())).thenReturn(true)); } @Test @@ -145,10 +139,6 @@ class LinkedDeviceRefreshRequirementProviderTest { assertEquals(initialDeviceCount + addedDeviceNames.size(), account.getDevices().size()); - verify(clientPresenceManager).disconnectPresence(account.getUuid(), (byte) 1); - verify(clientPresenceManager).disconnectPresence(account.getUuid(), (byte) 2); - verify(clientPresenceManager).disconnectPresence(account.getUuid(), (byte) 3); - verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of((byte) 1)); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of((byte) 2)); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of((byte) 3)); @@ -180,11 +170,10 @@ class LinkedDeviceRefreshRequirementProviderTest { assertEquals(200, response.getStatus()); initialDeviceIds.forEach(deviceId -> { - verify(clientPresenceManager).disconnectPresence(account.getUuid(), deviceId); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of(deviceId)); }); - verifyNoMoreInteractions(clientPresenceManager); + verifyNoMoreInteractions(pubSubClientEventManager); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/auth/PhoneNumberChangeRefreshRequirementProviderTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/auth/PhoneNumberChangeRefreshRequirementProviderTest.java index 0bb77228e..db4272b77 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/auth/PhoneNumberChangeRefreshRequirementProviderTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/auth/PhoneNumberChangeRefreshRequirementProviderTest.java @@ -47,7 +47,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.whispersystems.textsecuregcm.filters.RemoteAddressFilter; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -75,7 +74,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest { private static final AccountAuthenticator AUTHENTICATOR = mock(AccountAuthenticator.class); private static final AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class); - private static final ClientPresenceManager CLIENT_PRESENCE = mock(ClientPresenceManager.class); private static final PubSubClientEventManager PUBSUB_CLIENT_PRESENCE = mock(PubSubClientEventManager.class); private WebSocketClient client; @@ -86,7 +84,7 @@ class PhoneNumberChangeRefreshRequirementProviderTest { @BeforeEach void setUp() throws Exception { - reset(AUTHENTICATOR, CLIENT_PRESENCE, ACCOUNTS_MANAGER); + reset(AUTHENTICATOR, ACCOUNTS_MANAGER, PUBSUB_CLIENT_PRESENCE); client = new WebSocketClient(); client.start(); @@ -125,9 +123,9 @@ class PhoneNumberChangeRefreshRequirementProviderTest { .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*"); webSocketEnvironment.jersey().register(new RemoteAddressFilter()); webSocketEnvironment.jersey() - .register(new WebsocketRefreshApplicationEventListener(ACCOUNTS_MANAGER, CLIENT_PRESENCE, PUBSUB_CLIENT_PRESENCE)); + .register(new WebsocketRefreshApplicationEventListener(ACCOUNTS_MANAGER, PUBSUB_CLIENT_PRESENCE)); environment.jersey() - .register(new WebsocketRefreshApplicationEventListener(ACCOUNTS_MANAGER, CLIENT_PRESENCE, PUBSUB_CLIENT_PRESENCE)); + .register(new WebsocketRefreshApplicationEventListener(ACCOUNTS_MANAGER, PUBSUB_CLIENT_PRESENCE)); webSocketEnvironment.setConnectListener(webSocketSessionContext -> { }); @@ -201,7 +199,7 @@ class PhoneNumberChangeRefreshRequirementProviderTest { // Event listeners can fire after responses are sent verify(ACCOUNTS_MANAGER, timeout(5000).times(1)).getByAccountIdentifier(eq(account1.getUuid())); - verifyNoMoreInteractions(CLIENT_PRESENCE); + verifyNoMoreInteractions(PUBSUB_CLIENT_PRESENCE); verifyNoMoreInteractions(ACCOUNTS_MANAGER); } @@ -215,10 +213,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest { // Make sure we disconnect the account if the account has changed numbers. Event listeners can fire after responses // are sent, so use a timeout. - verify(CLIENT_PRESENCE, timeout(5000)) - .disconnectPresence(eq(account1.getUuid()), eq(authenticatedDevice.getId())); - verifyNoMoreInteractions(CLIENT_PRESENCE); - verify(PUBSUB_CLIENT_PRESENCE, timeout(5000)) .requestDisconnection(account1.getUuid(), List.of(authenticatedDevice.getId())); verifyNoMoreInteractions(PUBSUB_CLIENT_PRESENCE); @@ -235,10 +229,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest { // Make sure we disconnect the account if the account has changed numbers. Event listeners can fire after responses // are sent, so use a timeout. - verify(CLIENT_PRESENCE, timeout(5000)) - .disconnectPresence(eq(account1.getUuid()), eq(authenticatedDevice.getId())); - verifyNoMoreInteractions(CLIENT_PRESENCE); - verify(PUBSUB_CLIENT_PRESENCE, timeout(5000)) .requestDisconnection(account1.getUuid(), List.of(authenticatedDevice.getId())); verifyNoMoreInteractions(PUBSUB_CLIENT_PRESENCE); @@ -255,7 +245,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest { // Shouldn't even read the account if the method has not been annotated verifyNoMoreInteractions(ACCOUNTS_MANAGER); - verifyNoMoreInteractions(CLIENT_PRESENCE); } @ParameterizedTest @@ -269,7 +258,6 @@ class PhoneNumberChangeRefreshRequirementProviderTest { // Shouldn't even read the account if the method has not been annotated verifyNoMoreInteractions(ACCOUNTS_MANAGER); - verifyNoMoreInteractions(CLIENT_PRESENCE); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/auth/RegistrationLockVerificationManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/auth/RegistrationLockVerificationManagerTest.java index d922e526b..645dd7e72 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/auth/RegistrationLockVerificationManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/auth/RegistrationLockVerificationManagerTest.java @@ -33,7 +33,6 @@ import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException; import org.whispersystems.textsecuregcm.entities.PhoneVerificationRequest; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager; @@ -47,7 +46,6 @@ import org.whispersystems.textsecuregcm.util.Pair; class RegistrationLockVerificationManagerTest { private final AccountsManager accountsManager = mock(AccountsManager.class); - private final ClientPresenceManager clientPresenceManager = mock(ClientPresenceManager.class); private final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class); private final ExternalServiceCredentialsGenerator svr2CredentialsGenerator = mock( ExternalServiceCredentialsGenerator.class); @@ -58,7 +56,7 @@ class RegistrationLockVerificationManagerTest { private static PushNotificationManager pushNotificationManager = mock(PushNotificationManager.class); private final RateLimiters rateLimiters = mock(RateLimiters.class); private final RegistrationLockVerificationManager registrationLockVerificationManager = new RegistrationLockVerificationManager( - accountsManager, clientPresenceManager, pubSubClientEventManager, svr2CredentialsGenerator, svr3CredentialsGenerator, + accountsManager, pubSubClientEventManager, svr2CredentialsGenerator, svr3CredentialsGenerator, registrationRecoveryPasswordsManager, pushNotificationManager, rateLimiters); private final RateLimiter pinLimiter = mock(RateLimiter.class); @@ -109,7 +107,6 @@ class RegistrationLockVerificationManagerTest { } else { verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber()); } - verify(clientPresenceManager).disconnectAllPresences(account.getUuid(), List.of(Device.PRIMARY_ID)); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of(Device.PRIMARY_ID)); try { verify(pushNotificationManager).sendAttemptLoginNotification(any(), eq("failedRegistrationLock")); @@ -133,7 +130,6 @@ class RegistrationLockVerificationManagerTest { verify(pushNotificationManager, never()).sendAttemptLoginNotification(any(), eq("failedRegistrationLock")); } catch (NotPushRegisteredException npre) {} verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber()); - verify(clientPresenceManager, never()).disconnectAllPresences(account.getUuid(), List.of(Device.PRIMARY_ID)); verify(pubSubClientEventManager, never()).requestDisconnection(any(), any()); }); } @@ -172,7 +168,6 @@ class RegistrationLockVerificationManagerTest { verify(account, never()).lockAuthTokenHash(); verify(registrationRecoveryPasswordsManager, never()).removeForNumber(account.getNumber()); - verify(clientPresenceManager, never()).disconnectAllPresences(account.getUuid(), List.of(Device.PRIMARY_ID)); verify(pubSubClientEventManager, never()).requestDisconnection(any(), any()); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/DeviceControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/DeviceControllerTest.java index 9078b14df..87242241c 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/DeviceControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/DeviceControllerTest.java @@ -79,7 +79,6 @@ import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper; import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -111,7 +110,6 @@ class DeviceControllerTest { private static final Account account = mock(Account.class); private static final Account maxedAccount = mock(Account.class); private static final Device primaryDevice = mock(Device.class); - private static final ClientPresenceManager clientPresenceManager = mock(ClientPresenceManager.class); private static final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class); private static final Map deviceConfiguration = new HashMap<>(); private static final TestClock testClock = TestClock.now(); @@ -133,8 +131,7 @@ class DeviceControllerTest { .addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class)) .addProvider(new RateLimitExceededExceptionMapper()) .setTestContainerFactory(new GrizzlyWebTestContainerFactory()) - .addProvider(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager, - pubSubClientEventManager)) + .addProvider(new WebsocketRefreshApplicationEventListener(accountsManager, pubSubClientEventManager)) .addProvider(new DeviceLimitExceededExceptionMapper()) .addResource(deviceController) .build(); @@ -174,8 +171,7 @@ class DeviceControllerTest { asyncCommands, account, maxedAccount, - primaryDevice, - clientPresenceManager + primaryDevice ); testClock.unpin(); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/push/ClientPresenceManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/push/ClientPresenceManagerTest.java deleted file mode 100644 index 474ca73d6..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/push/ClientPresenceManagerTest.java +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.push; - - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.mock; - -import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; -import io.lettuce.core.cluster.event.ClusterTopologyChangedEvent; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; - -@Timeout(value = 10, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) -class ClientPresenceManagerTest { - - @RegisterExtension - static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); - - private ScheduledExecutorService presenceRenewalExecutorService; - private ClientPresenceManager clientPresenceManager; - - private static final DisplacedPresenceListener NO_OP = connectedElsewhere -> { - }; - - private boolean expectExceptionOnClientPresenceManagerStop = false; - - @BeforeEach - void setUp() throws Exception { - - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> { - connection.sync().flushall(); - connection.sync().upstream().commands().configSet("notify-keyspace-events", "K$glz"); - }); - - presenceRenewalExecutorService = Executors.newSingleThreadScheduledExecutor(); - clientPresenceManager = new ClientPresenceManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(), - presenceRenewalExecutorService, - presenceRenewalExecutorService); - } - - @AfterEach - public void tearDown() throws Exception { - presenceRenewalExecutorService.shutdown(); - presenceRenewalExecutorService.awaitTermination(1, TimeUnit.MINUTES); - - try { - clientPresenceManager.stop(); - } catch (final Exception e) { - if (!expectExceptionOnClientPresenceManagerStop) { - throw e; - } - } - } - - @Test - void testIsPresent() { - final UUID accountUuid = UUID.randomUUID(); - final byte deviceId = 1; - - assertFalse(clientPresenceManager.isPresent(accountUuid, deviceId)); - - clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP); - assertTrue(clientPresenceManager.isPresent(accountUuid, deviceId)); - } - - @Test - void testIsLocallyPresent() { - final UUID accountUuid = UUID.randomUUID(); - final byte deviceId = 1; - - assertFalse(clientPresenceManager.isLocallyPresent(accountUuid, deviceId)); - - clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP); - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> connection.sync().flushall()); - - assertTrue(clientPresenceManager.isLocallyPresent(accountUuid, deviceId)); - } - - @Test - void testLocalDisplacement() { - final UUID accountUuid = UUID.randomUUID(); - final byte deviceId = 1; - - final AtomicInteger displacementCounter = new AtomicInteger(0); - final DisplacedPresenceListener displacementListener = connectedElsewhere -> displacementCounter.incrementAndGet(); - - clientPresenceManager.setPresent(accountUuid, deviceId, displacementListener); - - assertEquals(0, displacementCounter.get()); - - clientPresenceManager.setPresent(accountUuid, deviceId, displacementListener); - - assertEquals(1, displacementCounter.get()); - } - - @Test - void testRemoteDisplacement() { - final UUID accountUuid = UUID.randomUUID(); - final byte deviceId = 1; - - final CompletableFuture displaced = new CompletableFuture<>(); - - clientPresenceManager.start(); - - clientPresenceManager.setPresent(accountUuid, deviceId, connectedElsewhere -> displaced.complete(null)); - - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster( - connection -> connection.sync().set(ClientPresenceManager.getPresenceKey(accountUuid, deviceId), - UUID.randomUUID().toString())); - - displaced.join(); - } - - @Test - void testRemoteDisplacementAfterTopologyChange() { - final UUID accountUuid = UUID.randomUUID(); - final byte deviceId = 1; - - final CompletableFuture displaced = new CompletableFuture<>(); - - clientPresenceManager.start(); - - clientPresenceManager.setPresent(accountUuid, deviceId, connectedElsewhere -> displaced.complete(null)); - - clientPresenceManager.getPubSubConnection() - .usePubSubConnection(connection -> connection.getResources().eventBus() - .publish(new ClusterTopologyChangedEvent(List.of(), List.of()))); - - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster( - connection -> connection.sync().set(ClientPresenceManager.getPresenceKey(accountUuid, deviceId), - UUID.randomUUID().toString())); - - displaced.join(); - } - - @Test - void testClearPresence() { - final UUID accountUuid = UUID.randomUUID(); - final byte deviceId = 1; - - assertFalse(clientPresenceManager.isPresent(accountUuid, deviceId)); - - clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP); - assertFalse(clientPresenceManager.clearPresence(accountUuid, deviceId, - ignored -> fail("this listener should never be called"))); - assertTrue(clientPresenceManager.clearPresence(accountUuid, deviceId, NO_OP)); - - clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP); - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster( - connection -> connection.sync().set(ClientPresenceManager.getPresenceKey(accountUuid, deviceId), - UUID.randomUUID().toString())); - - assertFalse(clientPresenceManager.clearPresence(accountUuid, deviceId, NO_OP)); - } - - @Test - void testPruneMissingPeers() { - final String presentPeerId = UUID.randomUUID().toString(); - final String missingPeerId = UUID.randomUUID().toString(); - - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> { - connection.sync().sadd(ClientPresenceManager.MANAGER_SET_KEY, presentPeerId); - connection.sync().sadd(ClientPresenceManager.MANAGER_SET_KEY, missingPeerId); - }); - - for (int i = 0; i < 10; i++) { - addClientPresence(presentPeerId); - addClientPresence(missingPeerId); - } - - clientPresenceManager.getPubSubConnection().usePubSubConnection( - connection -> connection.sync().upstream().commands() - .subscribe(ClientPresenceManager.getManagerPresenceChannel(presentPeerId))); - clientPresenceManager.pruneMissingPeers(); - - assertEquals(1, (long) REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster( - connection -> connection.sync().exists(ClientPresenceManager.getConnectedClientSetKey(presentPeerId)))); - assertTrue(REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster( - (Function, Boolean>) connection -> connection.sync() - .sismember(ClientPresenceManager.MANAGER_SET_KEY, presentPeerId))); - - assertEquals(0, (long) REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster( - connection -> connection.sync().exists(ClientPresenceManager.getConnectedClientSetKey(missingPeerId)))); - assertFalse(REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster( - (Function, Boolean>) connection -> connection.sync() - .sismember(ClientPresenceManager.MANAGER_SET_KEY, missingPeerId))); - } - - @Test - void testInitialPresenceExpiration() { - final UUID accountUuid = UUID.randomUUID(); - final byte deviceId = 1; - - clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP); - - { - final int ttl = REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(connection -> - connection.sync().ttl(ClientPresenceManager.getPresenceKey(accountUuid, deviceId)).intValue()); - - assertTrue(ttl > 0); - } - } - - @Test - void testRenewPresence() { - final UUID accountUuid = UUID.randomUUID(); - final byte deviceId = 1; - - final String presenceKey = ClientPresenceManager.getPresenceKey(accountUuid, deviceId); - - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> - connection.sync().set(presenceKey, clientPresenceManager.getManagerId())); - - { - final int ttl = REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(connection -> - connection.sync().ttl(presenceKey).intValue()); - - assertEquals(-1, ttl); - } - - clientPresenceManager.renewPresence(accountUuid, deviceId); - - { - final int ttl = REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(connection -> - connection.sync().ttl(presenceKey).intValue()); - - assertTrue(ttl > 0); - } - } - - @Test - void testExpiredPresence() { - final UUID accountUuid = UUID.randomUUID(); - final byte deviceId = 1; - - clientPresenceManager.setPresent(accountUuid, deviceId, NO_OP); - - assertTrue(clientPresenceManager.isPresent(accountUuid, deviceId)); - - // Hackily set this key to expire immediately - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> - connection.sync().expire(ClientPresenceManager.getPresenceKey(accountUuid, deviceId), 0)); - - assertFalse(clientPresenceManager.isPresent(accountUuid, deviceId)); - } - - private void addClientPresence(final String managerId) { - final String clientPresenceKey = ClientPresenceManager.getPresenceKey(UUID.randomUUID(), (byte) 7); - - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> { - connection.sync().set(clientPresenceKey, managerId); - connection.sync().sadd(ClientPresenceManager.getConnectedClientSetKey(managerId), clientPresenceKey); - }); - } - - @Test - void testClearAllOnStop() { - final int localAccounts = 10; - final UUID[] localUuids = new UUID[localAccounts]; - final byte[] localDeviceIds = new byte[localAccounts]; - - for (int i = 0; i < localAccounts; i++) { - localUuids[i] = UUID.randomUUID(); - localDeviceIds[i] = (byte) i; - - clientPresenceManager.setPresent(localUuids[i], localDeviceIds[i], NO_OP); - } - - final UUID displacedAccountUuid = UUID.randomUUID(); - final byte displacedAccountDeviceId = 7; - - clientPresenceManager.setPresent(displacedAccountUuid, displacedAccountDeviceId, NO_OP); - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> connection.sync() - .set(ClientPresenceManager.getPresenceKey(displacedAccountUuid, displacedAccountDeviceId), - UUID.randomUUID().toString())); - - clientPresenceManager.stop(); - - for (int i = 0; i < localAccounts; i++) { - localUuids[i] = UUID.randomUUID(); - localDeviceIds[i] = (byte) i; - - assertFalse(clientPresenceManager.isPresent(localUuids[i], localDeviceIds[i])); - } - - assertTrue(clientPresenceManager.isPresent(displacedAccountUuid, displacedAccountDeviceId)); - - expectExceptionOnClientPresenceManagerStop = true; - } - - @Nested - class MultiServerTest { - - private ClientPresenceManager server1; - private ClientPresenceManager server2; - - @BeforeEach - void setup() throws Exception { - - REDIS_CLUSTER_EXTENSION.getRedisCluster().useCluster(connection -> { - connection.sync().flushall(); - connection.sync().upstream().commands().configSet("notify-keyspace-events", "K$glz"); - }); - - final ScheduledExecutorService scheduledExecutorService1 = mock(ScheduledExecutorService.class); - final ExecutorService keyspaceNotificationExecutorService1 = Executors.newSingleThreadExecutor(); - server1 = new ClientPresenceManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(), - scheduledExecutorService1, keyspaceNotificationExecutorService1); - - final ScheduledExecutorService scheduledExecutorService2 = mock(ScheduledExecutorService.class); - final ExecutorService keyspaceNotificationExecutorService2 = Executors.newSingleThreadExecutor(); - server2 = new ClientPresenceManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(), - scheduledExecutorService2, keyspaceNotificationExecutorService2); - - server1.start(); - server2.start(); - } - - @AfterEach - void teardown() { - server2.stop(); - server1.stop(); - } - - @Test - void testSetPresentRemotely() { - final UUID uuid1 = UUID.randomUUID(); - final byte deviceId = 1; - - final CompletableFuture displaced = new CompletableFuture<>(); - final DisplacedPresenceListener listener1 = connectedElsewhere -> displaced.complete(null); - server1.setPresent(uuid1, deviceId, listener1); - - server2.setPresent(uuid1, deviceId, connectedElsewhere -> {}); - - displaced.join(); - } - - @Test - void testDisconnectPresenceLocally() { - final UUID uuid1 = UUID.randomUUID(); - final byte deviceId = 1; - - final CompletableFuture displaced = new CompletableFuture<>(); - final DisplacedPresenceListener listener1 = connectedElsewhere -> displaced.complete(null); - server1.setPresent(uuid1, deviceId, listener1); - - server1.disconnectPresence(uuid1, deviceId); - - displaced.join(); - } - - @Test - void testDisconnectPresenceRemotely() { - final UUID uuid1 = UUID.randomUUID(); - final byte deviceId = 1; - - final CompletableFuture displaced = new CompletableFuture<>(); - final DisplacedPresenceListener listener1 = connectedElsewhere -> displaced.complete(null); - server1.setPresent(uuid1, deviceId, listener1); - - server2.disconnectPresence(uuid1, deviceId); - - displaced.join(); - } - - @RepeatedTest(value = 100) - void testConcurrentConnection() throws Exception { - final UUID uuid1 = UUID.randomUUID(); - final byte deviceId = 1; - - final CompletableFuture displaced = new CompletableFuture<>(); - final DisplacedPresenceListener listener1 = connectedElsewhere -> displaced.complete(null); - - final Thread server1Thread = new Thread(() -> server1.setPresent(uuid1, deviceId, listener1)); - final Thread server2Thread = new Thread(() -> server2.setPresent(uuid1, deviceId, listener1)); - - server1Thread.start(); - server2Thread.start(); - - displaced.join(); - server2Thread.join(); - server1Thread.join(); - - while (server1.isLocallyPresent(uuid1, deviceId) == server2.isLocallyPresent(uuid1, deviceId)) { - Thread.sleep(50); - } - } - - } -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountCreationDeletionIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountCreationDeletionIntegrationTest.java index eaee1f505..eafd6c0fb 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountCreationDeletionIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountCreationDeletionIntegrationTest.java @@ -43,7 +43,6 @@ import org.whispersystems.textsecuregcm.entities.ECSignedPreKey; import org.whispersystems.textsecuregcm.entities.GcmRegistrationId; import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey; import org.whispersystems.textsecuregcm.identity.IdentityType; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; @@ -74,7 +73,6 @@ public class AccountCreationDeletionIntegrationTest { private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneId.systemDefault()); private ExecutorService accountLockExecutor; - private ExecutorService clientPresenceExecutor; private AccountsManager accountsManager; private KeysManager keysManager; @@ -112,7 +110,6 @@ public class AccountCreationDeletionIntegrationTest { DynamoDbExtensionSchema.Tables.USED_LINK_DEVICE_TOKENS.tableName()); accountLockExecutor = Executors.newSingleThreadExecutor(); - clientPresenceExecutor = Executors.newSingleThreadExecutor(); final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(), DynamoDbExtensionSchema.Tables.DELETED_ACCOUNTS_LOCK.tableName()); @@ -141,6 +138,10 @@ public class AccountCreationDeletionIntegrationTest { when(registrationRecoveryPasswordsManager.removeForNumber(any())) .thenReturn(CompletableFuture.completedFuture(null)); + final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class); + when(pubSubClientEventManager.requestDisconnection(any())) + .thenReturn(CompletableFuture.completedFuture(null)); + accountsManager = new AccountsManager( accounts, phoneNumberIdentifiers, @@ -152,12 +153,10 @@ public class AccountCreationDeletionIntegrationTest { profilesManager, secureStorageClient, svr2Client, - mock(ClientPresenceManager.class), - mock(PubSubClientEventManager.class), + pubSubClientEventManager, registrationRecoveryPasswordsManager, clientPublicKeysManager, accountLockExecutor, - clientPresenceExecutor, CLOCK, "link-device-secret".getBytes(StandardCharsets.UTF_8), dynamicConfigurationManager); @@ -166,13 +165,9 @@ public class AccountCreationDeletionIntegrationTest { @AfterEach void tearDown() throws InterruptedException { accountLockExecutor.shutdown(); - clientPresenceExecutor.shutdown(); //noinspection ResultOfMethodCallIgnored accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS); - - //noinspection ResultOfMethodCallIgnored - clientPresenceExecutor.awaitTermination(1, TimeUnit.SECONDS); } @CartesianTest diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java index 274903c7b..e994317e5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java @@ -36,7 +36,6 @@ import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException; import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.entities.ECSignedPreKey; import org.whispersystems.textsecuregcm.identity.IdentityType; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; @@ -67,10 +66,8 @@ class AccountsManagerChangeNumberIntegrationTest { static final RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); private KeysManager keysManager; - private ClientPresenceManager clientPresenceManager; private PubSubClientEventManager pubSubClientEventManager; private ExecutorService accountLockExecutor; - private ExecutorService clientPresenceExecutor; private AccountsManager accountsManager; @@ -106,7 +103,6 @@ class AccountsManagerChangeNumberIntegrationTest { Tables.USED_LINK_DEVICE_TOKENS.tableName()); accountLockExecutor = Executors.newSingleThreadExecutor(); - clientPresenceExecutor = Executors.newSingleThreadExecutor(); final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.DELETED_ACCOUNTS_LOCK.tableName()); @@ -120,7 +116,6 @@ class AccountsManagerChangeNumberIntegrationTest { final SecureValueRecovery2Client svr2Client = mock(SecureValueRecovery2Client.class); when(svr2Client.deleteBackups(any())).thenReturn(CompletableFuture.completedFuture(null)); - clientPresenceManager = mock(ClientPresenceManager.class); pubSubClientEventManager = mock(PubSubClientEventManager.class); final PhoneNumberIdentifiers phoneNumberIdentifiers = @@ -149,12 +144,10 @@ class AccountsManagerChangeNumberIntegrationTest { profilesManager, secureStorageClient, svr2Client, - clientPresenceManager, pubSubClientEventManager, registrationRecoveryPasswordsManager, clientPublicKeysManager, accountLockExecutor, - clientPresenceExecutor, mock(Clock.class), "link-device-secret".getBytes(StandardCharsets.UTF_8), dynamicConfigurationManager); @@ -164,13 +157,9 @@ class AccountsManagerChangeNumberIntegrationTest { @AfterEach void tearDown() throws InterruptedException { accountLockExecutor.shutdown(); - clientPresenceExecutor.shutdown(); //noinspection ResultOfMethodCallIgnored accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS); - - //noinspection ResultOfMethodCallIgnored - clientPresenceExecutor.awaitTermination(1, TimeUnit.SECONDS); } @Test @@ -285,7 +274,6 @@ class AccountsManagerChangeNumberIntegrationTest { assertEquals(secondNumber, accountsManager.getByAccountIdentifier(originalUuid).map(Account::getNumber).orElseThrow()); - verify(clientPresenceManager).disconnectAllPresencesForUuid(existingAccountUuid); verify(pubSubClientEventManager).requestDisconnection(existingAccountUuid); assertEquals(Optional.of(existingAccountUuid), accountsManager.findRecentlyDeletedAccountIdentifier(originalNumber)); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java index 1ba0a8e8b..0bc3f5fc6 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java @@ -48,7 +48,6 @@ import org.whispersystems.textsecuregcm.auth.UnidentifiedAccessUtil; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.identity.IdentityType; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; @@ -134,12 +133,10 @@ class AccountsManagerConcurrentModificationIntegrationTest { mock(ProfilesManager.class), mock(SecureStorageClient.class), mock(SecureValueRecovery2Client.class), - mock(ClientPresenceManager.class), mock(PubSubClientEventManager.class), mock(RegistrationRecoveryPasswordsManager.class), mock(ClientPublicKeysManager.class), mock(Executor.class), - mock(Executor.class), mock(Clock.class), "link-device-secret".getBytes(StandardCharsets.UTF_8), dynamicConfigurationManager diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerDeviceTransferIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerDeviceTransferIntegrationTest.java index 228ebef2e..d5bb98a3c 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerDeviceTransferIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerDeviceTransferIntegrationTest.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.whispersystems.textsecuregcm.entities.RestoreAccountRequest; import org.whispersystems.textsecuregcm.entities.RemoteAttachment; import org.whispersystems.textsecuregcm.identity.IdentityType; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient; import org.whispersystems.textsecuregcm.redis.RedisServerExtension; @@ -63,12 +62,10 @@ public class AccountsManagerDeviceTransferIntegrationTest { mock(ProfilesManager.class), mock(SecureStorageClient.class), mock(SecureValueRecovery2Client.class), - mock(ClientPresenceManager.class), mock(PubSubClientEventManager.class), mock(RegistrationRecoveryPasswordsManager.class), mock(ClientPublicKeysManager.class), mock(ExecutorService.class), - mock(ExecutorService.class), Clock.systemUTC(), "link-device-secret".getBytes(StandardCharsets.UTF_8), mock(DynamicConfigurationManager.class)); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java index f435d35a0..6c781b260 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerTest.java @@ -79,7 +79,6 @@ import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.identity.IdentityType; import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient; @@ -118,7 +117,6 @@ class AccountsManagerTest { private KeysManager keysManager; private MessagesManager messagesManager; private ProfilesManager profilesManager; - private ClientPresenceManager clientPresenceManager; private PubSubClientEventManager pubSubClientEventManager; private ClientPublicKeysManager clientPublicKeysManager; @@ -154,20 +152,10 @@ class AccountsManagerTest { keysManager = mock(KeysManager.class); messagesManager = mock(MessagesManager.class); profilesManager = mock(ProfilesManager.class); - clientPresenceManager = mock(ClientPresenceManager.class); pubSubClientEventManager = mock(PubSubClientEventManager.class); clientPublicKeysManager = mock(ClientPublicKeysManager.class); dynamicConfiguration = mock(DynamicConfiguration.class); - final Executor clientPresenceExecutor = mock(Executor.class); - - doAnswer(invocation -> { - final Runnable runnable = invocation.getArgument(0); - runnable.run(); - - return null; - }).when(clientPresenceExecutor).execute(any()); - //noinspection unchecked asyncCommands = mock(RedisAsyncCommands.class); when(asyncCommands.set(any(), any(), any())).thenReturn(MockRedisFuture.completedFuture("OK")); @@ -250,6 +238,9 @@ class AccountsManagerTest { .stringAsyncCommands(asyncClusterCommands) .build(); + when(pubSubClientEventManager.requestDisconnection(any())) + .thenReturn(CompletableFuture.completedFuture(null)); + accountsManager = new AccountsManager( accounts, phoneNumberIdentifiers, @@ -261,12 +252,10 @@ class AccountsManagerTest { profilesManager, storageClient, svr2Client, - clientPresenceManager, pubSubClientEventManager, registrationRecoveryPasswordsManager, clientPublicKeysManager, mock(Executor.class), - clientPresenceExecutor, CLOCK, LINK_DEVICE_SECRET, dynamicConfigurationManager); @@ -802,7 +791,6 @@ class AccountsManagerTest { verify(keysManager, times(2)).deleteSingleUsePreKeys(account.getUuid(), linkedDevice.getId()); verify(keysManager).buildWriteItemsForRemovedDevice(account.getUuid(), account.getPhoneNumberIdentifier(), linkedDevice.getId()); verify(clientPublicKeysManager).buildTransactWriteItemForDeletion(account.getUuid(), linkedDevice.getId()); - verify(clientPresenceManager).disconnectPresence(account.getUuid(), linkedDevice.getId()); verify(pubSubClientEventManager).requestDisconnection(account.getUuid(), List.of(linkedDevice.getId())); } @@ -821,7 +809,6 @@ class AccountsManagerTest { assertDoesNotThrow(account::getPrimaryDevice); verify(messagesManager, never()).clear(any(), anyByte()); verify(keysManager, never()).deleteSingleUsePreKeys(any(), anyByte()); - verify(clientPresenceManager, never()).disconnectPresence(any(), anyByte()); verify(pubSubClientEventManager, never()).requestDisconnection(any(), any()); } @@ -891,7 +878,6 @@ class AccountsManagerTest { verify(keysManager, times(2)).deleteSingleUsePreKeys(phoneNumberIdentifiersByE164.get(e164)); verify(messagesManager, times(2)).clear(existingUuid); verify(profilesManager, times(2)).deleteAll(existingUuid); - verify(clientPresenceManager).disconnectAllPresencesForUuid(existingUuid); verify(pubSubClientEventManager).requestDisconnection(existingUuid); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java index ebf9f506d..73e72e105 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java @@ -35,7 +35,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.mockito.Mockito; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; @@ -135,6 +134,9 @@ class AccountsManagerUsernameIntegrationTest { when(messageManager.clear(any())).thenReturn(CompletableFuture.completedFuture(null)); when(profileManager.deleteAll(any())).thenReturn(CompletableFuture.completedFuture(null)); + final PubSubClientEventManager pubSubClientEventManager = mock(PubSubClientEventManager.class); + when(pubSubClientEventManager.requestDisconnection(any())).thenReturn(CompletableFuture.completedFuture(null)); + accountsManager = new AccountsManager( accounts, phoneNumberIdentifiers, @@ -146,12 +148,10 @@ class AccountsManagerUsernameIntegrationTest { profileManager, mock(SecureStorageClient.class), mock(SecureValueRecovery2Client.class), - mock(ClientPresenceManager.class), - mock(PubSubClientEventManager.class), + pubSubClientEventManager, mock(RegistrationRecoveryPasswordsManager.class), mock(ClientPublicKeysManager.class), Executors.newSingleThreadExecutor(), - Executors.newSingleThreadExecutor(), mock(Clock.class), "link-device-secret".getBytes(StandardCharsets.UTF_8), dynamicConfigurationManager); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AddRemoveDeviceIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AddRemoveDeviceIntegrationTest.java index 27dbcd548..444f20cd3 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AddRemoveDeviceIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AddRemoveDeviceIntegrationTest.java @@ -33,7 +33,6 @@ import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.entities.DeviceInfo; import org.whispersystems.textsecuregcm.identity.IdentityType; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.redis.RedisServerExtension; @@ -70,7 +69,6 @@ public class AddRemoveDeviceIntegrationTest { private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneId.systemDefault()); private ExecutorService accountLockExecutor; - private ExecutorService clientPresenceExecutor; private KeysManager keysManager; private ClientPublicKeysManager clientPublicKeysManager; @@ -107,7 +105,6 @@ public class AddRemoveDeviceIntegrationTest { DynamoDbExtensionSchema.Tables.USED_LINK_DEVICE_TOKENS.tableName()); accountLockExecutor = Executors.newSingleThreadExecutor(); - clientPresenceExecutor = Executors.newSingleThreadExecutor(); final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(), DynamoDbExtensionSchema.Tables.DELETED_ACCOUNTS_LOCK.tableName()); @@ -152,12 +149,10 @@ public class AddRemoveDeviceIntegrationTest { profilesManager, secureStorageClient, svr2Client, - mock(ClientPresenceManager.class), mock(PubSubClientEventManager.class), registrationRecoveryPasswordsManager, clientPublicKeysManager, accountLockExecutor, - clientPresenceExecutor, CLOCK, "link-device-secret".getBytes(StandardCharsets.UTF_8), dynamicConfigurationManager); @@ -170,13 +165,9 @@ public class AddRemoveDeviceIntegrationTest { accountsManager.stop(); accountLockExecutor.shutdown(); - clientPresenceExecutor.shutdown(); //noinspection ResultOfMethodCallIgnored accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS); - - //noinspection ResultOfMethodCallIgnored - clientPresenceExecutor.awaitTermination(1, TimeUnit.SECONDS); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionTest.java index 5ce2d20bd..1e871f8c8 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionTest.java @@ -57,7 +57,6 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.limits.MessageDeliveryLoopMonitor; import org.whispersystems.textsecuregcm.metrics.MessageMetrics; -import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PubSubClientEventManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.PushNotificationScheduler; @@ -125,7 +124,7 @@ class WebSocketConnectionTest { new WebSocketAccountAuthenticator(accountAuthenticator, mock(PrincipalSupplier.class)); AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(receiptSender, messagesManager, new MessageMetrics(), mock(PushNotificationManager.class), mock(PushNotificationScheduler.class), - mock(ClientPresenceManager.class), mock(PubSubClientEventManager.class), retrySchedulingExecutor, + mock(PubSubClientEventManager.class), retrySchedulingExecutor, messageDeliveryScheduler, clientReleaseManager, mock(MessageDeliveryLoopMonitor.class)); WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/workers/FinishPushNotificationExperimentCommandTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/workers/FinishPushNotificationExperimentCommandTest.java index 65f32351d..505783083 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/workers/FinishPushNotificationExperimentCommandTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/workers/FinishPushNotificationExperimentCommandTest.java @@ -73,7 +73,6 @@ class FinishPushNotificationExperimentCommandTest { null, null, null, - null, pushNotificationExperimentSamples, null, null, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/workers/NotifyIdleDevicesCommandTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/workers/NotifyIdleDevicesCommandTest.java index d0c4afc39..86a03200e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/workers/NotifyIdleDevicesCommandTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/workers/NotifyIdleDevicesCommandTest.java @@ -66,7 +66,6 @@ class NotifyIdleDevicesCommandTest { null, null, null, - null, null); this.idleDeviceNotificationScheduler = idleDeviceNotificationScheduler; diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/workers/StartPushNotificationExperimentCommandTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/workers/StartPushNotificationExperimentCommandTest.java index 3b54fc52b..b547e3273 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/workers/StartPushNotificationExperimentCommandTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/workers/StartPushNotificationExperimentCommandTest.java @@ -62,7 +62,6 @@ class StartPushNotificationExperimentCommandTest { null, null, null, - null, pushNotificationExperimentSamples, null, null,