diff --git a/service/config/sample.yml b/service/config/sample.yml index bfe046b2f..bcf76bb8c 100644 --- a/service/config/sample.yml +++ b/service/config/sample.yml @@ -88,6 +88,8 @@ dynamoDbTables: phoneNumberIdentifierTableName: Example_Accounts_PhoneNumberIdentifiers usernamesTableName: Example_Accounts_Usernames scanPageSize: 100 + clientReleases: + tableName: Example_ClientReleases deletedAccounts: tableName: Example_DeletedAccounts deletedAccountsLock: diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java index ab5e733fe..a585bbb72 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java @@ -6,6 +6,7 @@ package org.whispersystems.textsecuregcm; import com.fasterxml.jackson.annotation.JsonProperty; import io.dropwizard.Configuration; +import java.time.Duration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -24,6 +25,7 @@ import org.whispersystems.textsecuregcm.configuration.AwsAttachmentsConfiguratio import org.whispersystems.textsecuregcm.configuration.BadgesConfiguration; import org.whispersystems.textsecuregcm.configuration.BraintreeConfiguration; import org.whispersystems.textsecuregcm.configuration.CdnConfiguration; +import org.whispersystems.textsecuregcm.configuration.ClientReleaseConfiguration; import org.whispersystems.textsecuregcm.configuration.DatadogConfiguration; import org.whispersystems.textsecuregcm.configuration.DirectoryV2Configuration; import org.whispersystems.textsecuregcm.configuration.DynamoDbClientConfiguration; @@ -281,6 +283,11 @@ public class WhisperServerConfiguration extends Configuration { @JsonProperty private int grpcPort; + @Valid + @NotNull + @JsonProperty + private ClientReleaseConfiguration clientRelease = new ClientReleaseConfiguration(Duration.ofHours(4)); + public AdminEventLoggingConfiguration getAdminEventLoggingConfiguration() { return adminEventLoggingConfiguration; } @@ -468,4 +475,7 @@ public class WhisperServerConfiguration extends Configuration { return grpcPort; } + public ClientReleaseConfiguration getClientReleaseConfiguration() { + return clientRelease; + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index 72f44ebd0..d3c62277b 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -168,6 +168,8 @@ import org.whispersystems.textsecuregcm.storage.AccountLockManager; import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.ChangeNumberManager; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; +import org.whispersystems.textsecuregcm.storage.ClientReleases; import org.whispersystems.textsecuregcm.storage.DeletedAccounts; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager; @@ -320,6 +322,8 @@ public class WhisperServerService extends Application> instrumentedVersions) { -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicPushLatencyConfiguration.java b/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicPushLatencyConfiguration.java deleted file mode 100644 index 157264c0c..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/configuration/dynamic/DynamicPushLatencyConfiguration.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2013-2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.configuration.dynamic; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.vdurmont.semver4j.Semver; -import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; -import java.util.Map; -import java.util.Set; - -public record DynamicPushLatencyConfiguration(Map> instrumentedVersions) { -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java index b2787e75b..469abe04a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java @@ -97,6 +97,7 @@ import org.whispersystems.textsecuregcm.spam.FilterSpam; import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.storage.DeletedAccounts; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; @@ -127,6 +128,7 @@ public class MessageController { private final ExecutorService multiRecipientMessageExecutor; private final Scheduler messageDeliveryScheduler; private final ReportSpamTokenProvider reportSpamTokenProvider; + private final ClientReleaseManager clientReleaseManager; private final DynamicConfigurationManager dynamicConfigurationManager; private static final String REJECT_OVERSIZE_MESSAGE_COUNTER = name(MessageController.class, "rejectOversizeMessage"); @@ -161,6 +163,7 @@ public class MessageController { @Nonnull ExecutorService multiRecipientMessageExecutor, Scheduler messageDeliveryScheduler, @Nonnull ReportSpamTokenProvider reportSpamTokenProvider, + final ClientReleaseManager clientReleaseManager, final DynamicConfigurationManager dynamicConfigurationManager) { this.rateLimiters = rateLimiters; this.messageSender = messageSender; @@ -173,6 +176,7 @@ public class MessageController { this.multiRecipientMessageExecutor = Objects.requireNonNull(multiRecipientMessageExecutor); this.messageDeliveryScheduler = messageDeliveryScheduler; this.reportSpamTokenProvider = reportSpamTokenProvider; + this.clientReleaseManager = clientReleaseManager; this.dynamicConfigurationManager = dynamicConfigurationManager; } @@ -543,8 +547,7 @@ public class MessageController { .map(OutgoingMessageEntity::fromEnvelope) .peek(outgoingMessageEntity -> { MessageMetrics.measureAccountOutgoingMessageUuidMismatches(auth.getAccount(), outgoingMessageEntity); - MessageMetrics.measureOutgoingMessageLatency(outgoingMessageEntity.serverTimestamp(), "rest", userAgent, - dynamicConfigurationManager.getConfiguration().getDeliveryLatencyConfiguration().instrumentedVersions()); + MessageMetrics.measureOutgoingMessageLatency(outgoingMessageEntity.serverTimestamp(), "rest", userAgent, clientReleaseManager); }) .collect(Collectors.toList()), messagesAndHasMore.second()); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MessageMetrics.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MessageMetrics.java index ff7279573..f00c8eeb4 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MessageMetrics.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/MessageMetrics.java @@ -22,6 +22,7 @@ import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity; import org.whispersystems.textsecuregcm.identity.ServiceIdentifier; import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; public final class MessageMetrics { @@ -61,13 +62,13 @@ public final class MessageMetrics { public static void measureOutgoingMessageLatency(final long serverTimestamp, final String channel, final String userAgent, - final Map> taggedVersions) { + final ClientReleaseManager clientReleaseManager) { final List tags = new ArrayList<>(3); tags.add(UserAgentTagUtil.getPlatformTag(userAgent)); tags.add(Tag.of("channel", channel)); - UserAgentTagUtil.getClientVersionTag(userAgent, taggedVersions).ifPresent(tags::add); + UserAgentTagUtil.getClientVersionTag(userAgent, clientReleaseManager).ifPresent(tags::add); Metrics.timer(DELIVERY_LATENCY_TIMER_NAME, tags) .record(Duration.between(Instant.ofEpochMilli(serverTimestamp), Instant.now())); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java index 77253e97b..7ca8ccfc1 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtil.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.Set; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException; import org.whispersystems.textsecuregcm.util.ua.UserAgent; @@ -39,11 +40,11 @@ public class UserAgentTagUtil { return Tag.of(PLATFORM_TAG, platform); } - public static Optional getClientVersionTag(final String userAgentString, final Map> taggedVersions) { + public static Optional getClientVersionTag(final String userAgentString, final ClientReleaseManager clientReleaseManager) { try { final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString); - if (taggedVersions.getOrDefault(userAgent.getPlatform(), Collections.emptySet()).contains(userAgent.getVersion())) { + if (clientReleaseManager.isVersionActive(userAgent.getPlatform(), userAgent.getVersion())) { return Optional.of(Tag.of(VERSION_TAG, userAgent.getVersion().toString())); } } catch (final UnrecognizedUserAgentException ignored) { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/push/PushLatencyManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/push/PushLatencyManager.java index 742aa7379..a9ab13950 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/push/PushLatencyManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/push/PushLatencyManager.java @@ -22,10 +22,9 @@ import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.util.SystemMapper; /** @@ -40,7 +39,7 @@ import org.whispersystems.textsecuregcm.util.SystemMapper; public class PushLatencyManager { private final FaultTolerantRedisCluster redisCluster; - private final DynamicConfigurationManager dynamicConfigurationManager; + private final ClientReleaseManager clientReleaseManager; private final Clock clock; @@ -59,18 +58,18 @@ public class PushLatencyManager { } public PushLatencyManager(final FaultTolerantRedisCluster redisCluster, - final DynamicConfigurationManager dynamicConfigurationManager) { + final ClientReleaseManager clientReleaseManager) { - this(redisCluster, dynamicConfigurationManager, Clock.systemUTC()); + this(redisCluster, clientReleaseManager, Clock.systemUTC()); } @VisibleForTesting PushLatencyManager(final FaultTolerantRedisCluster redisCluster, - final DynamicConfigurationManager dynamicConfigurationManager, + final ClientReleaseManager clientReleaseManager, final Clock clock) { this.redisCluster = redisCluster; - this.dynamicConfigurationManager = dynamicConfigurationManager; + this.clientReleaseManager = clientReleaseManager; this.clock = clock; } @@ -99,8 +98,7 @@ public class PushLatencyManager { tags.add(UserAgentTagUtil.getPlatformTag(userAgentString)); tags.add(Tag.of("pushType", pushRecord.pushType().name().toLowerCase())); - UserAgentTagUtil.getClientVersionTag(userAgentString, - dynamicConfigurationManager.getConfiguration().getPushLatencyConfiguration().instrumentedVersions()) + UserAgentTagUtil.getClientVersionTag(userAgentString, clientReleaseManager) .ifPresent(tags::add); pushRecord.urgent().ifPresent(urgent -> tags.add(Tag.of("urgent", String.valueOf(urgent)))); 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 b158f1db6..f33e4f091 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/AuthenticatedConnectListener.java @@ -23,7 +23,6 @@ import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.push.ClientPresenceManager; @@ -31,8 +30,8 @@ import org.whispersystems.textsecuregcm.push.NotPushRegisteredException; import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.redis.RedisOperation; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.util.Constants; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; @@ -69,7 +68,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { private final ClientPresenceManager clientPresenceManager; private final ScheduledExecutorService scheduledExecutorService; private final Scheduler messageDeliveryScheduler; - private final DynamicConfigurationManager dynamicConfigurationManager; + private final ClientReleaseManager clientReleaseManager; private final Map openAuthenticatedWebsocketsByClientPlatform; private final Map openUnauthenticatedWebsocketsByClientPlatform; @@ -87,14 +86,14 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { ClientPresenceManager clientPresenceManager, ScheduledExecutorService scheduledExecutorService, Scheduler messageDeliveryScheduler, - final DynamicConfigurationManager dynamicConfigurationManager) { + ClientReleaseManager clientReleaseManager) { this.receiptSender = receiptSender; this.messagesManager = messagesManager; this.pushNotificationManager = pushNotificationManager; this.clientPresenceManager = clientPresenceManager; this.scheduledExecutorService = scheduledExecutorService; this.messageDeliveryScheduler = messageDeliveryScheduler; - this.dynamicConfigurationManager = dynamicConfigurationManager; + this.clientReleaseManager = clientReleaseManager; openAuthenticatedWebsocketsByClientPlatform = new EnumMap<>(ClientPlatform.class); openUnauthenticatedWebsocketsByClientPlatform = new EnumMap<>(ClientPlatform.class); @@ -157,7 +156,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener { context.getClient(), scheduledExecutorService, messageDeliveryScheduler, - dynamicConfigurationManager); + clientReleaseManager); openWebsocketAtomicInteger.incrementAndGet(); openWebsocketCounter.inc(); 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 facba2146..e1fe1d61d 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java @@ -37,7 +37,6 @@ import org.reactivestreams.Publisher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.controllers.MessageController; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; @@ -47,8 +46,8 @@ import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; import org.whispersystems.textsecuregcm.push.DisplacedPresenceListener; import org.whispersystems.textsecuregcm.push.ReceiptSender; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.MessageAvailabilityListener; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.util.Constants; @@ -132,7 +131,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac private final Random random = new Random(); private final Scheduler messageDeliveryScheduler; - private final DynamicConfigurationManager dynamicConfigurationManager; + private final ClientReleaseManager clientReleaseManager; private enum StoredMessageState { EMPTY, @@ -147,7 +146,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac WebSocketClient client, ScheduledExecutorService scheduledExecutorService, Scheduler messageDeliveryScheduler, - DynamicConfigurationManager dynamicConfigurationManager) { + ClientReleaseManager clientReleaseManager) { this(receiptSender, messagesManager, @@ -156,7 +155,8 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac client, DEFAULT_SEND_FUTURES_TIMEOUT_MILLIS, scheduledExecutorService, - messageDeliveryScheduler, dynamicConfigurationManager); + messageDeliveryScheduler, + clientReleaseManager); } @VisibleForTesting @@ -168,7 +168,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac int sendFuturesTimeoutMillis, ScheduledExecutorService scheduledExecutorService, Scheduler messageDeliveryScheduler, - DynamicConfigurationManager dynamicConfigurationManager) { + ClientReleaseManager clientReleaseManager) { this.receiptSender = receiptSender; this.messagesManager = messagesManager; @@ -178,7 +178,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac this.sendFuturesTimeoutMillis = sendFuturesTimeoutMillis; this.scheduledExecutorService = scheduledExecutorService; this.messageDeliveryScheduler = messageDeliveryScheduler; - this.dynamicConfigurationManager = dynamicConfigurationManager; + this.clientReleaseManager = clientReleaseManager; } public void start() { @@ -217,8 +217,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac if (throwable != null) { sendFailuresMeter.mark(); } else { - MessageMetrics.measureOutgoingMessageLatency(message.getServerTimestamp(), "websocket", client.getUserAgent(), - dynamicConfigurationManager.getConfiguration().getDeliveryLatencyConfiguration().instrumentedVersions()); + MessageMetrics.measureOutgoingMessageLatency(message.getServerTimestamp(), "websocket", client.getUserAgent(), clientReleaseManager); } }).thenCompose(response -> { final CompletableFuture result; diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java index 5f60aa6d5..993e46ea6 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/MessageControllerTest.java @@ -81,7 +81,6 @@ import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount; import org.whispersystems.textsecuregcm.auth.OptionalAccess; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicDeliveryLatencyConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicInboundMessageByteLimitConfiguration; import org.whispersystems.textsecuregcm.entities.AccountMismatchedDevices; import org.whispersystems.textsecuregcm.entities.AccountStaleDevices; @@ -112,6 +111,7 @@ import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.spam.ReportSpamTokenProvider; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.storage.DeletedAccounts; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; @@ -179,7 +179,7 @@ class MessageControllerTest { .addResource( new MessageController(rateLimiters, messageSender, receiptSender, accountsManager, deletedAccounts, messagesManager, pushNotificationManager, reportMessageManager, multiRecipientMessageExecutor, - messageDeliveryScheduler, ReportSpamTokenProvider.noop(), dynamicConfigurationManager)) + messageDeliveryScheduler, ReportSpamTokenProvider.noop(), mock(ClientReleaseManager.class), dynamicConfigurationManager)) .build(); @BeforeEach @@ -209,16 +209,12 @@ class MessageControllerTest { when(accountsManager.getByServiceIdentifier(new PniServiceIdentifier(MULTI_DEVICE_PNI))).thenReturn(Optional.of(multiDeviceAccount)); when(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(INTERNATIONAL_UUID))).thenReturn(Optional.of(internationalAccount)); - final DynamicDeliveryLatencyConfiguration deliveryLatencyConfiguration = mock(DynamicDeliveryLatencyConfiguration.class); - when(deliveryLatencyConfiguration.instrumentedVersions()).thenReturn(Collections.emptyMap()); - final DynamicInboundMessageByteLimitConfiguration inboundMessageByteLimitConfiguration = mock(DynamicInboundMessageByteLimitConfiguration.class); when(inboundMessageByteLimitConfiguration.enforceInboundLimit()).thenReturn(false); final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class); - when(dynamicConfiguration.getDeliveryLatencyConfiguration()).thenReturn(deliveryLatencyConfiguration); when(dynamicConfiguration.getInboundMessageByteLimitConfiguration()).thenReturn(inboundMessageByteLimitConfiguration); when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtilTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtilTest.java index de507ef8a..e1ff4f53b 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtilTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/metrics/UserAgentTagUtilTest.java @@ -6,6 +6,9 @@ package org.whispersystems.textsecuregcm.metrics; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.vdurmont.semver4j.Semver; import io.micrometer.core.instrument.Tag; @@ -17,6 +20,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; class UserAgentTagUtilTest { @@ -49,26 +53,21 @@ class UserAgentTagUtilTest { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") @ParameterizedTest @MethodSource - void getClientVersionTag(final String userAgent, final Map> taggedVersions, final Optional expectedTag) { - assertEquals(expectedTag, UserAgentTagUtil.getClientVersionTag(userAgent, taggedVersions)); + void getClientVersionTag(final String userAgent, final boolean isVersionLive, final Optional expectedTag) { + final ClientReleaseManager clientReleaseManager = mock(ClientReleaseManager.class); + when(clientReleaseManager.isVersionActive(any(), any())).thenReturn(isVersionLive); + + assertEquals(expectedTag, UserAgentTagUtil.getClientVersionTag(userAgent, clientReleaseManager)); } private static Stream getClientVersionTag() { return Stream.of( Arguments.of("Signal-Android/1.2.3 (Android 9)", - Map.of(ClientPlatform.ANDROID, Set.of(new Semver("1.2.3"))), + true, Optional.of(Tag.of(UserAgentTagUtil.VERSION_TAG, "1.2.3"))), Arguments.of("Signal-Android/1.2.3 (Android 9)", - Collections.emptyMap(), - Optional.empty()), - - Arguments.of("Signal-Android/1.2.3.0-bobsbootlegclient", - Map.of(ClientPlatform.ANDROID, Set.of(new Semver("1.2.3"))), - Optional.empty()), - - Arguments.of("Signal-Desktop/1.2.3", - Map.of(ClientPlatform.ANDROID, Set.of(new Semver("1.2.3"))), + false, Optional.empty()) ); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/push/PushLatencyManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/push/PushLatencyManagerTest.java index b6d6c8d7b..3fc81e29f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/push/PushLatencyManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/push/PushLatencyManagerTest.java @@ -9,47 +9,28 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import java.time.Clock; import java.time.Instant; import java.time.ZoneId; -import java.util.Collections; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Stream; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicPushLatencyConfiguration; import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushRecord; import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushType; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; class PushLatencyManagerTest { @RegisterExtension static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); - private DynamicConfigurationManager dynamicConfigurationManager; - - @BeforeEach - void setUp() { - //noinspection unchecked - dynamicConfigurationManager = mock(DynamicConfigurationManager.class); - final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class); - final DynamicPushLatencyConfiguration dynamicPushLatencyConfiguration = mock(DynamicPushLatencyConfiguration.class); - - when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); - when(dynamicConfiguration.getPushLatencyConfiguration()).thenReturn(dynamicPushLatencyConfiguration); - when(dynamicPushLatencyConfiguration.instrumentedVersions()).thenReturn(Collections.emptyMap()); - } - @ParameterizedTest @MethodSource void testTakeRecord(final boolean isVoip, final boolean isUrgent) throws ExecutionException, InterruptedException { @@ -59,7 +40,7 @@ class PushLatencyManagerTest { final Instant pushTimestamp = Instant.now(); final PushLatencyManager pushLatencyManager = new PushLatencyManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(), - dynamicConfigurationManager, Clock.fixed(pushTimestamp, ZoneId.systemDefault())); + mock(ClientReleaseManager.class), Clock.fixed(pushTimestamp, ZoneId.systemDefault())); assertNull(pushLatencyManager.takePushRecord(accountUuid, deviceId).get()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionIntegrationTest.java index 2165b251e..5d24ffae1 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionIntegrationTest.java @@ -25,7 +25,6 @@ import java.io.IOException; import java.time.Clock; import java.time.Duration; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -45,15 +44,13 @@ import org.junit.jupiter.params.provider.CsvSource; import org.mockito.ArgumentCaptor; import org.mockito.stubbing.Answer; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicDeliveryLatencyConfiguration; import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.storage.Account; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.DynamoDbExtension; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.storage.MessagesCache; @@ -83,7 +80,7 @@ class WebSocketConnectionIntegrationTest { private Device device; private WebSocketClient webSocketClient; private Scheduler messageDeliveryScheduler; - private DynamicConfigurationManager dynamicConfigurationManager; + private ClientReleaseManager clientReleaseManager; private long serialTimestamp = System.currentTimeMillis(); @@ -102,15 +99,7 @@ class WebSocketConnectionIntegrationTest { account = mock(Account.class); device = mock(Device.class); webSocketClient = mock(WebSocketClient.class); - - final DynamicDeliveryLatencyConfiguration deliveryLatencyConfiguration = mock(DynamicDeliveryLatencyConfiguration.class); - when(deliveryLatencyConfiguration.instrumentedVersions()).thenReturn(Collections.emptyMap()); - - final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class); - when(dynamicConfiguration.getDeliveryLatencyConfiguration()).thenReturn(deliveryLatencyConfiguration); - - dynamicConfigurationManager = mock(DynamicConfigurationManager.class); - when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); + clientReleaseManager = mock(ClientReleaseManager.class); when(account.getNumber()).thenReturn("+18005551234"); when(account.getUuid()).thenReturn(UUID.randomUUID()); @@ -140,7 +129,8 @@ class WebSocketConnectionIntegrationTest { device, webSocketClient, scheduledExecutorService, - messageDeliveryScheduler, dynamicConfigurationManager); + messageDeliveryScheduler, + clientReleaseManager); final List expectedMessages = new ArrayList<>(persistedMessageCount + cachedMessageCount); @@ -224,7 +214,8 @@ class WebSocketConnectionIntegrationTest { device, webSocketClient, scheduledExecutorService, - messageDeliveryScheduler, dynamicConfigurationManager); + messageDeliveryScheduler, + clientReleaseManager); final int persistedMessageCount = 207; final int cachedMessageCount = 173; @@ -291,7 +282,7 @@ class WebSocketConnectionIntegrationTest { 100, // use a very short timeout, so that this test completes quickly scheduledExecutorService, messageDeliveryScheduler, - dynamicConfigurationManager); + clientReleaseManager); final int persistedMessageCount = 207; final int cachedMessageCount = 173; 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 1595c4cec..a95ae6dff 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionTest.java @@ -33,7 +33,6 @@ import io.lettuce.core.RedisException; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -53,16 +52,14 @@ import org.junit.jupiter.api.Test; import org.mockito.stubbing.Answer; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; -import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicDeliveryLatencyConfiguration; import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier; import org.whispersystems.textsecuregcm.push.ClientPresenceManager; import org.whispersystems.textsecuregcm.push.PushNotificationManager; import org.whispersystems.textsecuregcm.push.ReceiptSender; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; +import org.whispersystems.textsecuregcm.storage.ClientReleaseManager; import org.whispersystems.textsecuregcm.storage.Device; -import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.websocket.WebSocketClient; @@ -93,7 +90,7 @@ class WebSocketConnectionTest { private ReceiptSender receiptSender; private ScheduledExecutorService retrySchedulingExecutor; private Scheduler messageDeliveryScheduler; - private DynamicConfigurationManager dynamicConfigurationManager; + private ClientReleaseManager clientReleaseManager; @BeforeEach void setup() { @@ -107,15 +104,7 @@ class WebSocketConnectionTest { receiptSender = mock(ReceiptSender.class); retrySchedulingExecutor = mock(ScheduledExecutorService.class); messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery"); - - final DynamicDeliveryLatencyConfiguration deliveryLatencyConfiguration = mock(DynamicDeliveryLatencyConfiguration.class); - when(deliveryLatencyConfiguration.instrumentedVersions()).thenReturn(Collections.emptyMap()); - - final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class); - when(dynamicConfiguration.getDeliveryLatencyConfiguration()).thenReturn(deliveryLatencyConfiguration); - - dynamicConfigurationManager = mock(DynamicConfigurationManager.class); - when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); + clientReleaseManager = mock(ClientReleaseManager.class); } @AfterEach @@ -129,7 +118,7 @@ class WebSocketConnectionTest { WebSocketAccountAuthenticator webSocketAuthenticator = new WebSocketAccountAuthenticator(accountAuthenticator); AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(receiptSender, messagesManager, mock(PushNotificationManager.class), mock(ClientPresenceManager.class), - retrySchedulingExecutor, messageDeliveryScheduler, dynamicConfigurationManager); + retrySchedulingExecutor, messageDeliveryScheduler, clientReleaseManager); WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class); when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_USER, VALID_PASSWORD)))) @@ -209,7 +198,7 @@ class WebSocketConnectionTest { }); WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, - auth, device, client, retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + auth, device, client, retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); connection.start(); verify(client, times(3)).sendRequest(eq("PUT"), eq("/api/v1/message"), any(List.class), @@ -237,7 +226,7 @@ class WebSocketConnectionTest { public void testOnlineSend() { final WebSocketClient client = mock(WebSocketClient.class); final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); final UUID accountUuid = UUID.randomUUID(); @@ -357,7 +346,7 @@ class WebSocketConnectionTest { }); WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, - auth, device, client, retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + auth, device, client, retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); connection.start(); @@ -381,7 +370,7 @@ class WebSocketConnectionTest { void testProcessStoredMessageConcurrency() { final WebSocketClient client = mock(WebSocketClient.class); final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); when(account.getNumber()).thenReturn("+18005551234"); when(account.getUuid()).thenReturn(UUID.randomUUID()); @@ -446,7 +435,7 @@ class WebSocketConnectionTest { void testProcessStoredMessagesMultiplePages() { final WebSocketClient client = mock(WebSocketClient.class); final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); when(account.getNumber()).thenReturn("+18005551234"); final UUID accountUuid = UUID.randomUUID(); @@ -498,7 +487,7 @@ class WebSocketConnectionTest { void testProcessStoredMessagesContainsSenderUuid() { final WebSocketClient client = mock(WebSocketClient.class); final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); when(account.getNumber()).thenReturn("+18005551234"); final UUID accountUuid = UUID.randomUUID(); @@ -560,7 +549,7 @@ class WebSocketConnectionTest { void testProcessStoredMessagesSingleEmptyCall() { final WebSocketClient client = mock(WebSocketClient.class); final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); final UUID accountUuid = UUID.randomUUID(); @@ -589,7 +578,7 @@ class WebSocketConnectionTest { public void testRequeryOnStateMismatch() { final WebSocketClient client = mock(WebSocketClient.class); final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); final UUID accountUuid = UUID.randomUUID(); when(account.getNumber()).thenReturn("+18005551234"); @@ -645,7 +634,7 @@ class WebSocketConnectionTest { void testProcessCachedMessagesOnly() { final WebSocketClient client = mock(WebSocketClient.class); final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); final UUID accountUuid = UUID.randomUUID(); @@ -677,7 +666,7 @@ class WebSocketConnectionTest { void testProcessDatabaseMessagesAfterPersist() { final WebSocketClient client = mock(WebSocketClient.class); final WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); final UUID accountUuid = UUID.randomUUID(); @@ -724,7 +713,7 @@ class WebSocketConnectionTest { when(client.isOpen()).thenReturn(true); WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); connection.start(); verify(retrySchedulingExecutor, times(WebSocketConnection.MAX_CONSECUTIVE_RETRIES)).schedule(any(Runnable.class), @@ -748,7 +737,7 @@ class WebSocketConnectionTest { when(client.isOpen()).thenReturn(false); WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); connection.start(); verify(retrySchedulingExecutor, never()).schedule(any(Runnable.class), anyLong(), any()); @@ -782,7 +771,7 @@ class WebSocketConnectionTest { CompletableFuture.completedFuture(Optional.empty())); WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, messageDeliveryScheduler, dynamicConfigurationManager); + retrySchedulingExecutor, messageDeliveryScheduler, clientReleaseManager); connection.start(); @@ -839,7 +828,7 @@ class WebSocketConnectionTest { CompletableFuture.completedFuture(Optional.empty())); WebSocketConnection connection = new WebSocketConnection(receiptSender, messagesManager, auth, device, client, - retrySchedulingExecutor, Schedulers.immediate(), dynamicConfigurationManager); + retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager); connection.start();