Convert incoming/outgoing message entities to records

This commit is contained in:
Jon Chambers 2022-07-27 11:51:32 -04:00 committed by Jon Chambers
parent c4c5397b44
commit 3d875f1ce5
11 changed files with 81 additions and 263 deletions

View File

@ -182,16 +182,16 @@ public class MessageController {
senderType = SENDER_TYPE_UNIDENTIFIED;
}
for (final IncomingMessage message : messages.getMessages()) {
for (final IncomingMessage message : messages.messages()) {
int contentLength = 0;
if (!Util.isEmpty(message.getContent())) {
contentLength += message.getContent().length();
if (!Util.isEmpty(message.content())) {
contentLength += message.content().length();
}
validateContentLength(contentLength, userAgent);
validateEnvelopeType(message.getType(), userAgent);
validateEnvelopeType(message.type(), userAgent);
}
try {
@ -222,25 +222,25 @@ public class MessageController {
}
DestinationDeviceValidator.validateCompleteDeviceList(destination.get(),
messages.getMessages().stream().map(IncomingMessage::getDestinationDeviceId).collect(Collectors.toSet()),
messages.messages().stream().map(IncomingMessage::destinationDeviceId).collect(Collectors.toSet()),
excludedDeviceIds);
DestinationDeviceValidator.validateRegistrationIds(destination.get(),
messages.getMessages(),
IncomingMessage::getDestinationDeviceId,
IncomingMessage::getDestinationRegistrationId,
messages.messages(),
IncomingMessage::destinationDeviceId,
IncomingMessage::destinationRegistrationId,
destination.get().getPhoneNumberIdentifier().equals(destinationUuid));
final List<Tag> tags = List.of(UserAgentTagUtil.getPlatformTag(userAgent),
Tag.of(EPHEMERAL_TAG_NAME, String.valueOf(messages.isOnline())),
Tag.of(EPHEMERAL_TAG_NAME, String.valueOf(messages.online())),
Tag.of(SENDER_TYPE_TAG_NAME, senderType));
for (IncomingMessage incomingMessage : messages.getMessages()) {
Optional<Device> destinationDevice = destination.get().getDevice(incomingMessage.getDestinationDeviceId());
for (IncomingMessage incomingMessage : messages.messages()) {
Optional<Device> destinationDevice = destination.get().getDevice(incomingMessage.destinationDeviceId());
if (destinationDevice.isPresent()) {
Metrics.counter(SENT_MESSAGE_COUNTER_NAME, tags).increment();
sendMessage(source, destination.get(), destinationDevice.get(), destinationUuid, messages.getTimestamp(), messages.isOnline(), incomingMessage, userAgent);
sendMessage(source, destination.get(), destinationDevice.get(), destinationUuid, messages.timestamp(), messages.online(), incomingMessage, userAgent);
}
}
@ -438,9 +438,9 @@ public class MessageController {
private static long estimateMessageListSizeBytes(final OutgoingMessageEntityList messageList) {
long size = 0;
for (final OutgoingMessageEntity message : messageList.getMessages()) {
size += message.getContent() == null ? 0 : message.getContent().length;
size += Util.isEmpty(message.getSource()) ? 0 : message.getSource().length();
for (final OutgoingMessageEntity message : messageList.messages()) {
size += message.content() == null ? 0 : message.content().length;
size += Util.isEmpty(message.source()) ? 0 : message.source().length();
}
return size;
@ -458,10 +458,10 @@ public class MessageController {
null);
if (message.isPresent()) {
WebSocketConnection.recordMessageDeliveryDuration(message.get().getTimestamp(), auth.getAuthenticatedDevice());
if (!Util.isEmpty(message.get().getSource())
&& message.get().getType() != Envelope.Type.SERVER_DELIVERY_RECEIPT_VALUE) {
receiptSender.sendReceipt(auth, message.get().getSourceUuid(), message.get().getTimestamp());
WebSocketConnection.recordMessageDeliveryDuration(message.get().timestamp(), auth.getAuthenticatedDevice());
if (!Util.isEmpty(message.get().source())
&& message.get().type() != Envelope.Type.SERVER_DELIVERY_RECEIPT_VALUE) {
receiptSender.sendReceipt(auth, message.get().sourceUuid(), message.get().timestamp());
}
}
@ -523,10 +523,10 @@ public class MessageController {
Optional<byte[]> messageContent = getMessageContent(incomingMessage);
Envelope.Builder messageBuilder = Envelope.newBuilder();
final Envelope.Type envelopeType = Envelope.Type.forNumber(incomingMessage.getType());
final Envelope.Type envelopeType = Envelope.Type.forNumber(incomingMessage.type());
if (envelopeType == null) {
logger.warn("Received bad envelope type {} from {}", incomingMessage.getType(), userAgentString);
logger.warn("Received bad envelope type {} from {}", incomingMessage.type(), userAgentString);
throw new BadRequestException();
}
@ -621,10 +621,10 @@ public class MessageController {
}
public static Optional<byte[]> getMessageContent(IncomingMessage message) {
if (Util.isEmpty(message.getContent())) return Optional.empty();
if (Util.isEmpty(message.content())) return Optional.empty();
try {
return Optional.of(Base64.getDecoder().decode(message.getContent()));
return Optional.of(Base64.getDecoder().decode(message.content()));
} catch (IllegalArgumentException e) {
logger.debug("Bad B64", e);
return Optional.empty();

View File

@ -4,57 +4,6 @@
*/
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class IncomingMessage {
@JsonProperty
private final int type;
@JsonProperty
private final String destination;
@JsonProperty
private final long destinationDeviceId;
@JsonProperty
private final int destinationRegistrationId;
@JsonProperty
private final String content;
@JsonCreator
public IncomingMessage(
@JsonProperty("id") final int type,
@JsonProperty("destination") final String destination,
@JsonProperty("destinationDeviceId") final long destinationDeviceId,
@JsonProperty("destinationRegistrationId") final int destinationRegistrationId,
@JsonProperty("content") final String content) {
this.type = type;
this.destination = destination;
this.destinationDeviceId = destinationDeviceId;
this.destinationRegistrationId = destinationRegistrationId;
this.content = content;
}
public String getDestination() {
return destination;
}
public int getType() {
return type;
}
public long getDestinationDeviceId() {
return destinationDeviceId;
}
public int getDestinationRegistrationId() {
return destinationRegistrationId;
}
public String getContent() {
return content;
}
public record IncomingMessage(int type, String destination, long destinationDeviceId, int destinationRegistrationId,
String content) {
}

View File

@ -4,44 +4,9 @@
*/
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
public class IncomingMessageList {
@JsonProperty
@NotNull
@Valid
private final List<@NotNull IncomingMessage> messages;
@JsonProperty
private final long timestamp;
@JsonProperty
private final boolean online;
@JsonCreator
public IncomingMessageList(
@JsonProperty("messages") final List<@NotNull IncomingMessage> messages,
@JsonProperty("online") final boolean online,
@JsonProperty("timestamp") final long timestamp) {
this.messages = messages;
this.timestamp = timestamp;
this.online = online;
}
public List<IncomingMessage> getMessages() {
return messages;
}
public long getTimestamp() {
return timestamp;
}
public boolean isOnline() {
return online;
}
public record IncomingMessageList(@NotNull @Valid List<@NotNull IncomingMessage> messages, boolean online, long timestamp) {
}

View File

@ -11,89 +11,18 @@ import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
public class OutgoingMessageEntity {
private final UUID guid;
private final int type;
private final long timestamp;
private final String source;
private final UUID sourceUuid;
private final int sourceDevice;
private final UUID destinationUuid;
private final UUID updatedPni;
private final byte[] content;
private final long serverTimestamp;
@JsonCreator
public OutgoingMessageEntity(@JsonProperty("guid") final UUID guid,
@JsonProperty("type") final int type,
@JsonProperty("timestamp") final long timestamp,
@JsonProperty("source") final String source,
@JsonProperty("sourceUuid") final UUID sourceUuid,
@JsonProperty("sourceDevice") final int sourceDevice,
@JsonProperty("destinationUuid") final UUID destinationUuid,
@JsonProperty("updatedPni") final UUID updatedPni,
@JsonProperty("content") final byte[] content,
@JsonProperty("serverTimestamp") final long serverTimestamp)
{
this.guid = guid;
this.type = type;
this.timestamp = timestamp;
this.source = source;
this.sourceUuid = sourceUuid;
this.sourceDevice = sourceDevice;
this.destinationUuid = destinationUuid;
this.updatedPni = updatedPni;
this.content = content;
this.serverTimestamp = serverTimestamp;
}
public UUID getGuid() {
return guid;
}
public int getType() {
return type;
}
public long getTimestamp() {
return timestamp;
}
public String getSource() {
return source;
}
public UUID getSourceUuid() {
return sourceUuid;
}
public int getSourceDevice() {
return sourceDevice;
}
public UUID getDestinationUuid() {
return destinationUuid;
}
public UUID getUpdatedPni() {
return updatedPni;
}
public byte[] getContent() {
return content;
}
public long getServerTimestamp() {
return serverTimestamp;
}
public record OutgoingMessageEntity(UUID guid, int type, long timestamp, String source, UUID sourceUuid,
int sourceDevice, UUID destinationUuid, UUID updatedPni, byte[] content,
long serverTimestamp) {
@Override
public boolean equals(final Object o) {
if (this == o)
if (this == o) {
return true;
if (o == null || getClass() != o.getClass())
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final OutgoingMessageEntity that = (OutgoingMessageEntity) o;
return type == that.type && timestamp == that.timestamp && sourceDevice == that.sourceDevice
&& serverTimestamp == that.serverTimestamp && guid.equals(that.guid) && Objects.equals(source, that.source)

View File

@ -5,30 +5,7 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class OutgoingMessageEntityList {
@JsonProperty
private List<OutgoingMessageEntity> messages;
@JsonProperty
private boolean more;
public OutgoingMessageEntityList() {}
public OutgoingMessageEntityList(List<OutgoingMessageEntity> messages, boolean more) {
this.messages = messages;
this.more = more;
}
public List<OutgoingMessageEntity> getMessages() {
return messages;
}
public boolean hasMore() {
return more;
}
public record OutgoingMessageEntityList(List<OutgoingMessageEntity> messages, boolean more) {
}

View File

@ -17,7 +17,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.controllers.MessageController;
import org.whispersystems.textsecuregcm.util.DestinationDeviceValidator;
import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
import org.whispersystems.textsecuregcm.controllers.StaleDevicesException;
import org.whispersystems.textsecuregcm.entities.IncomingMessage;
@ -25,7 +24,7 @@ import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
import org.whispersystems.textsecuregcm.push.MessageSender;
import org.whispersystems.textsecuregcm.push.NotPushRegisteredException;
import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.textsecuregcm.util.DestinationDeviceValidator;
public class ChangeNumberManager {
private static final Logger logger = LoggerFactory.getLogger(AccountController.class);
@ -55,14 +54,14 @@ public class ChangeNumberManager {
// Check that all except master ID are in device messages
DestinationDeviceValidator.validateCompleteDeviceList(
account,
deviceMessages.stream().map(IncomingMessage::getDestinationDeviceId).collect(Collectors.toSet()),
deviceMessages.stream().map(IncomingMessage::destinationDeviceId).collect(Collectors.toSet()),
Set.of(Device.MASTER_ID));
DestinationDeviceValidator.validateRegistrationIds(
account,
deviceMessages,
IncomingMessage::getDestinationDeviceId,
IncomingMessage::getDestinationRegistrationId,
IncomingMessage::destinationDeviceId,
IncomingMessage::destinationRegistrationId,
false);
} else if (!ObjectUtils.allNull(pniIdentityKey, deviceSignedPreKeys, deviceMessages, pniRegistrationIds)) {
throw new IllegalArgumentException("PNI identity key, signed pre-keys, device messages, and registration IDs must be all null or all non-null");
@ -83,7 +82,7 @@ public class ChangeNumberManager {
// around the server crashed at/above this point.
if (deviceMessages != null) {
deviceMessages.forEach(message ->
sendMessageToSelf(updatedAccount, updatedAccount.getDevice(message.getDestinationDeviceId()), message));
sendMessageToSelf(updatedAccount, updatedAccount.getDevice(message.destinationDeviceId()), message));
}
return updatedAccount;
@ -103,7 +102,7 @@ public class ChangeNumberManager {
try {
long serverTimestamp = System.currentTimeMillis();
Envelope envelope = Envelope.newBuilder()
.setType(Envelope.Type.forNumber(message.getType()))
.setType(Envelope.Type.forNumber(message.type()))
.setTimestamp(serverTimestamp)
.setServerTimestamp(serverTimestamp)
.setDestinationUuid(sourceAndDestinationAccount.getUuid().toString())

View File

@ -310,45 +310,45 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
final OutgoingMessageEntityList messages = messagesManager
.getMessagesForDevice(auth.getAccount().getUuid(), device.getId(), client.getUserAgent(), cachedMessagesOnly);
final CompletableFuture<?>[] sendFutures = new CompletableFuture[messages.getMessages().size()];
final CompletableFuture<?>[] sendFutures = new CompletableFuture[messages.messages().size()];
for (int i = 0; i < messages.getMessages().size(); i++) {
final OutgoingMessageEntity message = messages.getMessages().get(i);
for (int i = 0; i < messages.messages().size(); i++) {
final OutgoingMessageEntity message = messages.messages().get(i);
final Envelope.Builder builder = Envelope.newBuilder()
.setType(Envelope.Type.forNumber(message.getType()))
.setTimestamp(message.getTimestamp())
.setServerTimestamp(message.getServerTimestamp());
.setType(Envelope.Type.forNumber(message.type()))
.setTimestamp(message.timestamp())
.setServerTimestamp(message.serverTimestamp());
if (!Util.isEmpty(message.getSource())) {
builder.setSource(message.getSource())
.setSourceDevice(message.getSourceDevice());
if (message.getSourceUuid() != null) {
builder.setSourceUuid(message.getSourceUuid().toString());
if (!Util.isEmpty(message.source())) {
builder.setSource(message.source())
.setSourceDevice(message.sourceDevice());
if (message.sourceUuid() != null) {
builder.setSourceUuid(message.sourceUuid().toString());
}
}
if (message.getContent() != null) {
builder.setContent(ByteString.copyFrom(message.getContent()));
if (message.content() != null) {
builder.setContent(ByteString.copyFrom(message.content()));
}
builder.setDestinationUuid(message.getDestinationUuid().toString());
builder.setDestinationUuid(message.destinationUuid().toString());
if (message.getUpdatedPni() != null) {
builder.setUpdatedPni(message.getUpdatedPni().toString());
if (message.updatedPni() != null) {
builder.setUpdatedPni(message.updatedPni().toString());
}
builder.setServerGuid(message.getGuid().toString());
builder.setServerGuid(message.guid().toString());
final Envelope envelope = builder.build();
if (envelope.getSerializedSize() > MAX_DESKTOP_MESSAGE_SIZE && isDesktopClient) {
messagesManager.delete(auth.getAccount().getUuid(), device.getId(), message.getGuid(), message.getServerTimestamp());
messagesManager.delete(auth.getAccount().getUuid(), device.getId(), message.guid(), message.serverTimestamp());
discardedMessagesMeter.mark();
sendFutures[i] = CompletableFuture.completedFuture(null);
} else {
sendFutures[i] = sendMessage(builder.build(), Optional.of(new StoredMessageInfo(message.getGuid(), message.getServerTimestamp())));
sendFutures[i] = sendMessage(builder.build(), Optional.of(new StoredMessageInfo(message.guid(), message.serverTimestamp())));
}
}
@ -357,7 +357,7 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac
.orTimeout(sendFuturesTimeoutMillis, TimeUnit.MILLISECONDS)
.whenComplete((v, cause) -> {
if (cause == null) {
if (messages.hasMore()) {
if (messages.more()) {
sendNextMessagePage(cachedMessagesOnly, queueClearedFuture);
} else {
queueClearedFuture.complete(null);

View File

@ -391,19 +391,19 @@ class MessageControllerTest {
.accept(MediaType.APPLICATION_JSON_TYPE)
.get(OutgoingMessageEntityList.class);
assertEquals(response.getMessages().size(), 2);
assertEquals(response.messages().size(), 2);
assertEquals(response.getMessages().get(0).getTimestamp(), timestampOne);
assertEquals(response.getMessages().get(1).getTimestamp(), timestampTwo);
assertEquals(response.messages().get(0).timestamp(), timestampOne);
assertEquals(response.messages().get(1).timestamp(), timestampTwo);
assertEquals(response.getMessages().get(0).getGuid(), messageGuidOne);
assertNull(response.getMessages().get(1).getGuid());
assertEquals(response.messages().get(0).guid(), messageGuidOne);
assertNull(response.messages().get(1).guid());
assertEquals(response.getMessages().get(0).getSourceUuid(), sourceUuid);
assertEquals(response.getMessages().get(1).getSourceUuid(), sourceUuid);
assertEquals(response.messages().get(0).sourceUuid(), sourceUuid);
assertEquals(response.messages().get(1).sourceUuid(), sourceUuid);
assertEquals(updatedPniOne, response.getMessages().get(0).getUpdatedPni());
assertNull(response.getMessages().get(1).getUpdatedPni());
assertEquals(updatedPniOne, response.messages().get(0).updatedPni());
assertNull(response.messages().get(1).updatedPni());
}
@Test

View File

@ -116,8 +116,8 @@ public class ChangeNumberManagerTest {
final Map<Long, Integer> registrationIds = Map.of(1L, 17, 2L, 19);
final IncomingMessage msg = mock(IncomingMessage.class);
when(msg.getDestinationDeviceId()).thenReturn(2L);
when(msg.getContent()).thenReturn(Base64.encodeBase64String(new byte[]{1}));
when(msg.destinationDeviceId()).thenReturn(2L);
when(msg.content()).thenReturn(Base64.encodeBase64String(new byte[]{1}));
changeNumberManager.changeNumber(account, changedE164, pniIdentityKey, prekeys, List.of(msg), registrationIds);

View File

@ -194,15 +194,15 @@ class MessagesDynamoDbTest {
}
private static void verify(OutgoingMessageEntity retrieved, MessageProtos.Envelope inserted) {
assertThat(retrieved.getTimestamp()).isEqualTo(inserted.getTimestamp());
assertThat(retrieved.getSource()).isEqualTo(inserted.hasSource() ? inserted.getSource() : null);
assertThat(retrieved.getSourceUuid()).isEqualTo(inserted.hasSourceUuid() ? UUID.fromString(inserted.getSourceUuid()) : null);
assertThat(retrieved.getSourceDevice()).isEqualTo(inserted.getSourceDevice());
assertThat(retrieved.getType()).isEqualTo(inserted.getType().getNumber());
assertThat(retrieved.getContent()).isEqualTo(inserted.hasContent() ? inserted.getContent().toByteArray() : null);
assertThat(retrieved.getServerTimestamp()).isEqualTo(inserted.getServerTimestamp());
assertThat(retrieved.getGuid()).isEqualTo(UUID.fromString(inserted.getServerGuid()));
assertThat(retrieved.getDestinationUuid()).isEqualTo(UUID.fromString(inserted.getDestinationUuid()));
assertThat(retrieved.timestamp()).isEqualTo(inserted.getTimestamp());
assertThat(retrieved.source()).isEqualTo(inserted.hasSource() ? inserted.getSource() : null);
assertThat(retrieved.sourceUuid()).isEqualTo(inserted.hasSourceUuid() ? UUID.fromString(inserted.getSourceUuid()) : null);
assertThat(retrieved.sourceDevice()).isEqualTo(inserted.getSourceDevice());
assertThat(retrieved.type()).isEqualTo(inserted.getType().getNumber());
assertThat(retrieved.content()).isEqualTo(inserted.hasContent() ? inserted.getContent().toByteArray() : null);
assertThat(retrieved.serverTimestamp()).isEqualTo(inserted.getServerTimestamp());
assertThat(retrieved.guid()).isEqualTo(UUID.fromString(inserted.getServerGuid()));
assertThat(retrieved.destinationUuid()).isEqualTo(UUID.fromString(inserted.getDestinationUuid()));
}
private static VerifyMessage verify(MessageProtos.Envelope expected) {

View File

@ -64,7 +64,6 @@ import org.whispersystems.websocket.WebSocketClient;
import org.whispersystems.websocket.auth.WebSocketAuthenticator.AuthenticationResult;
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
import org.whispersystems.websocket.session.WebSocketSessionContext;
import javax.annotation.Nullable;
class WebSocketConnectionTest {
@ -208,7 +207,7 @@ class WebSocketConnectionTest {
futures.get(0).completeExceptionally(new IOException());
futures.get(2).completeExceptionally(new IOException());
verify(storedMessages, times(1)).delete(eq(accountUuid), eq(2L), eq(outgoingMessages.get(1).getGuid()), eq(outgoingMessages.get(1).getServerTimestamp()));
verify(storedMessages, times(1)).delete(eq(accountUuid), eq(2L), eq(outgoingMessages.get(1).guid()), eq(outgoingMessages.get(1).serverTimestamp()));
verify(receiptSender, times(1)).sendReceipt(eq(auth), eq(senderOneUuid), eq(2222L));
connection.stop();