Add plumbing to roll out binary service IDs/UUIDs on envelopes to internal users

This commit is contained in:
Jon Chambers
2025-08-21 17:53:41 -04:00
committed by GitHub
parent 78a7112675
commit 7f5ea6608c
13 changed files with 120 additions and 49 deletions

View File

@@ -5,9 +5,21 @@
package org.whispersystems.textsecuregcm.storage;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.google.protobuf.ByteString;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.grpc.ServiceIdentifierUtil;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.IdentityType;
@@ -16,15 +28,15 @@ import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
import org.whispersystems.textsecuregcm.util.UUIDUtil;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import static org.junit.jupiter.api.Assertions.*;
class EnvelopeUtilTest {
@Test
void compressExpand() {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void compressExpand(final boolean includeBinaryServiceIdentifiers) {
final ExperimentEnrollmentManager experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
when(experimentEnrollmentManager.isEnrolled(any(UUID.class), eq(EnvelopeUtil.INCLUDE_BINARY_SERVICE_ID_EXPERIMENT_NAME)))
.thenReturn(includeBinaryServiceIdentifiers);
{
final MessageProtos.Envelope compressibleFieldsNullMessage = generateRandomMessageBuilder().build();
final MessageProtos.Envelope compressed = EnvelopeUtil.compress(compressibleFieldsNullMessage);
@@ -38,7 +50,7 @@ class EnvelopeUtilTest {
assertFalse(compressed.hasUpdatedPni());
assertFalse(compressed.hasUpdatedPniBinary());
final MessageProtos.Envelope expanded = EnvelopeUtil.expand(compressed);
final MessageProtos.Envelope expanded = EnvelopeUtil.expand(compressed, experimentEnrollmentManager);
assertFalse(expanded.hasSourceServiceId());
assertFalse(expanded.hasSourceServiceIdBinary());
@@ -76,22 +88,31 @@ class EnvelopeUtilTest {
assertEquals(compressed, EnvelopeUtil.compress(compressed), "Double compression should make no changes");
final MessageProtos.Envelope expanded = EnvelopeUtil.expand(compressed);
final MessageProtos.Envelope expanded = EnvelopeUtil.expand(compressed, experimentEnrollmentManager);
assertEquals(sourceServiceId.toServiceIdentifierString(), expanded.getSourceServiceId());
assertFalse(expanded.hasSourceServiceIdBinary());
assertEquals(destinationServiceId.toServiceIdentifierString(), expanded.getDestinationServiceId());
assertFalse(expanded.hasDestinationServiceIdBinary());
assertEquals(serverGuid.toString(), expanded.getServerGuid());
assertFalse(expanded.hasServerGuidBinary());
assertEquals(updatedPni.toString(), expanded.getUpdatedPni());
assertEquals(UUIDUtil.toByteString(updatedPni), expanded.getUpdatedPniBinary());
assertEquals(expanded, EnvelopeUtil.expand(expanded), "Double expansion should make no changes");
if (includeBinaryServiceIdentifiers) {
assertEquals(ServiceIdentifierUtil.toCompactByteString(sourceServiceId), expanded.getSourceServiceIdBinary());
assertEquals(ServiceIdentifierUtil.toCompactByteString(destinationServiceId), expanded.getDestinationServiceIdBinary());
assertEquals(UUIDUtil.toByteString(serverGuid), expanded.getServerGuidBinary());
} else {
assertFalse(expanded.hasSourceServiceIdBinary());
assertFalse(expanded.hasDestinationServiceIdBinary());
assertFalse(expanded.hasServerGuidBinary());
}
assertEquals(expanded, EnvelopeUtil.expand(expanded, experimentEnrollmentManager),
"Double expansion should make no changes");
// Expanded envelopes include both representations of the `updatedPni` field
assertEquals(compressibleFieldsExpandedMessage.toBuilder().setUpdatedPniBinary(UUIDUtil.toByteString(updatedPni)).build(),
expanded);
assertTrue(expanded.hasUpdatedPni());
assertTrue(expanded.hasUpdatedPniBinary());
assertEquals(UUID.fromString(expanded.getUpdatedPni()), UUIDUtil.fromByteString(expanded.getUpdatedPniBinary()));
}
}

View File

@@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.push.MessageAvailabilityListener;
import org.whispersystems.textsecuregcm.push.RedisMessageAvailabilityManager;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
@@ -59,6 +60,7 @@ class MessagePersisterIntegrationTest {
private RedisMessageAvailabilityManager redisMessageAvailabilityManager;
private MessagePersister messagePersister;
private Account account;
private ExperimentEnrollmentManager experimentEnrollmentManager;
private static final Duration PERSIST_DELAY = Duration.ofMinutes(10);
@@ -75,13 +77,14 @@ class MessagePersisterIntegrationTest {
messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery");
messageDeletionExecutorService = Executors.newSingleThreadExecutor();
experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
final MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(14),
messageDeletionExecutorService);
messageDeletionExecutorService, experimentEnrollmentManager);
final AccountsManager accountsManager = mock(AccountsManager.class);
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
messageDeliveryScheduler, messageDeletionExecutorService, Clock.systemUTC());
messageDeliveryScheduler, messageDeletionExecutorService, Clock.systemUTC(), experimentEnrollmentManager);
messagesManager = new MessagesManager(messagesDynamoDb, messagesCache, mock(RedisMessageAvailabilityManager.class),
mock(ReportMessageManager.class), messageDeletionExecutorService, Clock.systemUTC());
@@ -185,7 +188,7 @@ class MessagePersisterIntegrationTest {
dynamoDB.scan(ScanRequest.builder().tableName(Tables.MESSAGES.tableName()).build()).items().stream()
.map(item -> {
try {
return MessagesDynamoDb.convertItemToEnvelope(item);
return MessagesDynamoDb.convertItemToEnvelope(item, experimentEnrollmentManager);
} catch (InvalidProtocolBufferException e) {
fail("Could not parse stored message", e);
return null;

View File

@@ -53,6 +53,7 @@ import org.mockito.stubbing.Answer;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicMessagePersisterConfiguration;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.identity.IdentityType;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.tests.util.DevicesHelper;
@@ -115,7 +116,7 @@ class MessagePersisterTest {
resubscribeRetryExecutorService = Executors.newSingleThreadScheduledExecutor();
messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery");
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC());
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC(), mock(ExperimentEnrollmentManager.class));
messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager,
dynamicConfigurationManager, PERSIST_DELAY, 1);

View File

@@ -8,6 +8,7 @@ package org.whispersystems.textsecuregcm.storage;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import io.lettuce.core.RedisCommandExecutionException;
import io.lettuce.core.ScriptOutputType;
@@ -19,6 +20,7 @@ import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.redis.ClusterLuaScript;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
@@ -51,7 +53,8 @@ class MessagesCacheGetItemsScriptTest {
assertNotNull(messageAndScores);
assertEquals(2, messageAndScores.size());
final MessageProtos.Envelope resultEnvelope =
EnvelopeUtil.expand(MessageProtos.Envelope.parseFrom(messageAndScores.getFirst()));
EnvelopeUtil.expand(MessageProtos.Envelope.parseFrom(messageAndScores.getFirst()),
mock(ExperimentEnrollmentManager.class));
assertEquals(serverGuid, resultEnvelope.getServerGuid());
}

View File

@@ -69,6 +69,7 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.reactivestreams.Publisher;
import org.signal.libsignal.protocol.SealedSenderMultiRecipientMessage;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
@@ -106,7 +107,7 @@ class MessagesCacheTest {
resubscribeRetryExecutorService = Executors.newSingleThreadScheduledExecutor();
messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery");
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC());
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC(), mock(ExperimentEnrollmentManager.class));
}
@AfterEach
@@ -299,7 +300,7 @@ class MessagesCacheTest {
}
final MessagesCache messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
messageDeliveryScheduler, sharedExecutorService, cacheClock);
messageDeliveryScheduler, sharedExecutorService, cacheClock, mock(ExperimentEnrollmentManager.class));
final List<MessageProtos.Envelope> actualMessages = Flux.from(
messagesCache.get(DESTINATION_UUID, DESTINATION_DEVICE_ID))
@@ -623,7 +624,7 @@ class MessagesCacheTest {
messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery");
messagesCache = new MessagesCache(mockCluster, messageDeliveryScheduler,
Executors.newSingleThreadExecutor(), Clock.systemUTC());
Executors.newSingleThreadExecutor(), Clock.systemUTC(), mock(ExperimentEnrollmentManager.class));
}
@AfterEach

View File

@@ -6,6 +6,7 @@
package org.whispersystems.textsecuregcm.storage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import com.google.protobuf.ByteString;
import java.time.Duration;
@@ -26,6 +27,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.reactivestreams.Publisher;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import org.whispersystems.textsecuregcm.tests.util.DevicesHelper;
import org.whispersystems.textsecuregcm.tests.util.MessageHelper;
@@ -84,7 +86,7 @@ class MessagesDynamoDbTest {
messageDeletionExecutorService = Executors.newSingleThreadExecutor();
messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(14),
messageDeletionExecutorService);
messageDeletionExecutorService, mock(ExperimentEnrollmentManager.class));
}
@AfterEach

View File

@@ -36,6 +36,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.push.RedisMessageAvailabilityManager;
@@ -80,10 +81,11 @@ class RedisDynamoDbMessagePublisherTest {
DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
DynamoDbExtensionSchema.Tables.MESSAGES.tableName(),
Duration.ofDays(14),
sharedExecutorService);
sharedExecutorService,
mock(ExperimentEnrollmentManager.class));
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC());
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC(), mock(ExperimentEnrollmentManager.class));
redisMessageAvailabilityManager = mock(RedisMessageAvailabilityManager.class);

View File

@@ -101,10 +101,10 @@ class WebSocketConnectionIntegrationTest {
dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration());
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC());
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC(), mock(ExperimentEnrollmentManager.class));
messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(7),
sharedExecutorService);
sharedExecutorService, mock(ExperimentEnrollmentManager.class));
redisMessageAvailabilityManager = new RedisMessageAvailabilityManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(), sharedExecutorService, sharedExecutorService);
reportMessageManager = mock(ReportMessageManager.class);
account = mock(Account.class);