Multi-recipient message views

This adds support for storing multi-recipient message payloads and recipient views in Redis, and only fanning out on delivery or persistence. Phase 1: confirm storage and retrieval correctness.
This commit is contained in:
Chris Eager
2024-09-04 13:58:20 -05:00
committed by GitHub
parent d78c8370b6
commit 11601fd091
50 changed files with 1544 additions and 328 deletions

View File

@@ -44,6 +44,7 @@ import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.ArgumentCaptor;
import org.mockito.stubbing.Answer;
import org.whispersystems.textsecuregcm.auth.AuthenticatedDevice;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
import org.whispersystems.textsecuregcm.limits.MessageDeliveryLoopMonitor;
@@ -55,6 +56,7 @@ 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;
@@ -85,6 +87,8 @@ class WebSocketConnectionIntegrationTest {
private Scheduler messageDeliveryScheduler;
private ClientReleaseManager clientReleaseManager;
private DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
private long serialTimestamp = System.currentTimeMillis();
@BeforeEach
@@ -92,8 +96,10 @@ class WebSocketConnectionIntegrationTest {
sharedExecutorService = Executors.newSingleThreadExecutor();
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery");
dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration());
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(), sharedExecutorService,
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC());
messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC(), dynamicConfigurationManager);
messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(7),
sharedExecutorService);
@@ -381,12 +387,12 @@ class WebSocketConnectionIntegrationTest {
final long timestamp = serialTimestamp++;
return MessageProtos.Envelope.newBuilder()
.setTimestamp(timestamp)
.setClientTimestamp(timestamp)
.setServerTimestamp(timestamp)
.setContent(ByteString.copyFromUtf8(RandomStringUtils.randomAlphanumeric(256)))
.setType(MessageProtos.Envelope.Type.CIPHERTEXT)
.setServerGuid(messageGuid.toString())
.setDestinationUuid(UUID.randomUUID().toString())
.setDestinationServiceId(UUID.randomUUID().toString())
.build();
}

View File

@@ -48,7 +48,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -297,19 +296,19 @@ class WebSocketConnectionTest {
final Envelope firstMessage = Envelope.newBuilder()
.setServerGuid(UUID.randomUUID().toString())
.setSourceUuid(UUID.randomUUID().toString())
.setDestinationUuid(accountUuid.toString())
.setSourceServiceId(UUID.randomUUID().toString())
.setDestinationServiceId(accountUuid.toString())
.setUpdatedPni(UUID.randomUUID().toString())
.setTimestamp(System.currentTimeMillis())
.setClientTimestamp(System.currentTimeMillis())
.setSourceDevice(1)
.setType(Envelope.Type.CIPHERTEXT)
.build();
final Envelope secondMessage = Envelope.newBuilder()
.setServerGuid(UUID.randomUUID().toString())
.setSourceUuid(senderTwoUuid.toString())
.setDestinationUuid(accountUuid.toString())
.setTimestamp(System.currentTimeMillis())
.setSourceServiceId(senderTwoUuid.toString())
.setDestinationServiceId(accountUuid.toString())
.setClientTimestamp(System.currentTimeMillis())
.setSourceDevice(2)
.setType(Envelope.Type.CIPHERTEXT)
.build();
@@ -365,7 +364,7 @@ class WebSocketConnectionTest {
futures.get(0).completeExceptionally(new IOException());
verify(receiptSender, times(1)).sendReceipt(eq(new AciServiceIdentifier(account.getUuid())), eq(deviceId), eq(new AciServiceIdentifier(senderTwoUuid)),
eq(secondMessage.getTimestamp()));
eq(secondMessage.getClientTimestamp()));
connection.stop();
verify(client).close(anyInt(), anyString());
@@ -616,10 +615,10 @@ class WebSocketConnectionTest {
final byte[] body = argument.get();
try {
final Envelope envelope = Envelope.parseFrom(body);
if (!envelope.hasSourceUuid() || envelope.getSourceUuid().length() == 0) {
if (!envelope.hasSourceServiceId() || envelope.getSourceServiceId().length() == 0) {
return false;
}
return envelope.getSourceUuid().equals(senderUuid.toString());
return envelope.getSourceServiceId().equals(senderUuid.toString());
} catch (InvalidProtocolBufferException e) {
return false;
}
@@ -627,7 +626,7 @@ class WebSocketConnectionTest {
verify(client).sendRequest(eq("PUT"), eq("/api/v1/queue/empty"), any(List.class), eq(Optional.empty()));
}
private @NotNull WebSocketConnection webSocketConnection(final WebSocketClient client) {
private WebSocketConnection webSocketConnection(final WebSocketClient client) {
return new WebSocketConnection(receiptSender, messagesManager, new MessageMetrics(),
mock(PushNotificationManager.class), mock(PushNotificationScheduler.class), auth, client,
retrySchedulingExecutor, Schedulers.immediate(), clientReleaseManager, mock(MessageDeliveryLoopMonitor.class));
@@ -933,11 +932,11 @@ class WebSocketConnectionTest {
return Envelope.newBuilder()
.setServerGuid(UUID.randomUUID().toString())
.setType(Envelope.Type.CIPHERTEXT)
.setTimestamp(timestamp)
.setClientTimestamp(timestamp)
.setServerTimestamp(0)
.setSourceUuid(senderUuid.toString())
.setSourceServiceId(senderUuid.toString())
.setSourceDevice(SOURCE_DEVICE_ID)
.setDestinationUuid(destinationUuid.toString())
.setDestinationServiceId(destinationUuid.toString())
.setContent(ByteString.copyFrom(content.getBytes(StandardCharsets.UTF_8)))
.build();
}