Add support for receiving, storing, and returning `urgent` flags on messages
This commit is contained in:
parent
1175ff5867
commit
9c03f2e468
|
@ -239,7 +239,7 @@ public class MessageController {
|
||||||
|
|
||||||
if (destinationDevice.isPresent()) {
|
if (destinationDevice.isPresent()) {
|
||||||
Metrics.counter(SENT_MESSAGE_COUNTER_NAME, tags).increment();
|
Metrics.counter(SENT_MESSAGE_COUNTER_NAME, tags).increment();
|
||||||
sendMessage(source, destination.get(), destinationDevice.get(), destinationUuid, messages.timestamp(), messages.online(), incomingMessage, userAgent);
|
sendMessage(source, destination.get(), destinationDevice.get(), destinationUuid, messages.timestamp(), messages.online(), messages.urgent(), incomingMessage, userAgent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,6 +523,7 @@ public class MessageController {
|
||||||
UUID destinationUuid,
|
UUID destinationUuid,
|
||||||
long timestamp,
|
long timestamp,
|
||||||
boolean online,
|
boolean online,
|
||||||
|
boolean urgent,
|
||||||
IncomingMessage incomingMessage,
|
IncomingMessage incomingMessage,
|
||||||
String userAgentString)
|
String userAgentString)
|
||||||
throws NoSuchUserException {
|
throws NoSuchUserException {
|
||||||
|
@ -533,7 +534,8 @@ public class MessageController {
|
||||||
envelope = incomingMessage.toEnvelope(destinationUuid,
|
envelope = incomingMessage.toEnvelope(destinationUuid,
|
||||||
source.map(AuthenticatedAccount::getAccount).orElse(null),
|
source.map(AuthenticatedAccount::getAccount).orElse(null),
|
||||||
source.map(authenticatedAccount -> authenticatedAccount.getAuthenticatedDevice().getId()).orElse(null),
|
source.map(authenticatedAccount -> authenticatedAccount.getAuthenticatedDevice().getId()).orElse(null),
|
||||||
timestamp == 0 ? System.currentTimeMillis() : timestamp);
|
timestamp == 0 ? System.currentTimeMillis() : timestamp,
|
||||||
|
urgent);
|
||||||
} catch (final IllegalArgumentException e) {
|
} catch (final IllegalArgumentException e) {
|
||||||
logger.warn("Received bad envelope type {} from {}", incomingMessage.type(), userAgentString);
|
logger.warn("Received bad envelope type {} from {}", incomingMessage.type(), userAgentString);
|
||||||
throw new BadRequestException(e);
|
throw new BadRequestException(e);
|
||||||
|
|
|
@ -13,7 +13,12 @@ import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
|
|
||||||
public record IncomingMessage(int type, long destinationDeviceId, int destinationRegistrationId, String content) {
|
public record IncomingMessage(int type, long destinationDeviceId, int destinationRegistrationId, String content) {
|
||||||
|
|
||||||
public MessageProtos.Envelope toEnvelope(final UUID destinationUuid, @Nullable Account sourceAccount, @Nullable Long sourceDeviceId, final long timestamp) {
|
public MessageProtos.Envelope toEnvelope(final UUID destinationUuid,
|
||||||
|
@Nullable Account sourceAccount,
|
||||||
|
@Nullable Long sourceDeviceId,
|
||||||
|
final long timestamp,
|
||||||
|
final boolean urgent) {
|
||||||
|
|
||||||
final MessageProtos.Envelope.Type envelopeType = MessageProtos.Envelope.Type.forNumber(type());
|
final MessageProtos.Envelope.Type envelopeType = MessageProtos.Envelope.Type.forNumber(type());
|
||||||
|
|
||||||
if (envelopeType == null) {
|
if (envelopeType == null) {
|
||||||
|
@ -25,7 +30,8 @@ public record IncomingMessage(int type, long destinationDeviceId, int destinatio
|
||||||
envelopeBuilder.setType(envelopeType)
|
envelopeBuilder.setType(envelopeType)
|
||||||
.setTimestamp(timestamp)
|
.setTimestamp(timestamp)
|
||||||
.setServerTimestamp(System.currentTimeMillis())
|
.setServerTimestamp(System.currentTimeMillis())
|
||||||
.setDestinationUuid(destinationUuid.toString());
|
.setDestinationUuid(destinationUuid.toString())
|
||||||
|
.setUrgent(urgent);
|
||||||
|
|
||||||
if (sourceAccount != null && sourceDeviceId != null) {
|
if (sourceAccount != null && sourceDeviceId != null) {
|
||||||
envelopeBuilder.setSourceUuid(sourceAccount.getUuid().toString())
|
envelopeBuilder.setSourceUuid(sourceAccount.getUuid().toString())
|
||||||
|
|
|
@ -4,9 +4,21 @@
|
||||||
*/
|
*/
|
||||||
package org.whispersystems.textsecuregcm.entities;
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
public record IncomingMessageList(@NotNull @Valid List<@NotNull IncomingMessage> messages, boolean online, long timestamp) {
|
public record IncomingMessageList(@NotNull @Valid List<@NotNull IncomingMessage> messages,
|
||||||
|
boolean online, boolean urgent, long timestamp) {
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public IncomingMessageList(@JsonProperty("messages") @NotNull @Valid List<@NotNull IncomingMessage> messages,
|
||||||
|
@JsonProperty("online") boolean online,
|
||||||
|
@JsonProperty("urgent") Boolean urgent,
|
||||||
|
@JsonProperty("timestamp") long timestamp) {
|
||||||
|
|
||||||
|
this(messages, online, urgent == null || urgent, timestamp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import javax.annotation.Nullable;
|
||||||
|
|
||||||
public record OutgoingMessageEntity(UUID guid, int type, long timestamp, @Nullable UUID sourceUuid, int sourceDevice,
|
public record OutgoingMessageEntity(UUID guid, int type, long timestamp, @Nullable UUID sourceUuid, int sourceDevice,
|
||||||
UUID destinationUuid, @Nullable UUID updatedPni, byte[] content,
|
UUID destinationUuid, @Nullable UUID updatedPni, byte[] content,
|
||||||
long serverTimestamp) {
|
long serverTimestamp, boolean urgent) {
|
||||||
|
|
||||||
public MessageProtos.Envelope toEnvelope() {
|
public MessageProtos.Envelope toEnvelope() {
|
||||||
final MessageProtos.Envelope.Builder builder = MessageProtos.Envelope.newBuilder()
|
final MessageProtos.Envelope.Builder builder = MessageProtos.Envelope.newBuilder()
|
||||||
|
@ -21,7 +21,8 @@ public record OutgoingMessageEntity(UUID guid, int type, long timestamp, @Nullab
|
||||||
.setTimestamp(timestamp())
|
.setTimestamp(timestamp())
|
||||||
.setServerTimestamp(serverTimestamp())
|
.setServerTimestamp(serverTimestamp())
|
||||||
.setDestinationUuid(destinationUuid().toString())
|
.setDestinationUuid(destinationUuid().toString())
|
||||||
.setServerGuid(guid().toString());
|
.setServerGuid(guid().toString())
|
||||||
|
.setUrgent(urgent);
|
||||||
|
|
||||||
if (sourceUuid() != null) {
|
if (sourceUuid() != null) {
|
||||||
builder.setSourceUuid(sourceUuid().toString());
|
builder.setSourceUuid(sourceUuid().toString());
|
||||||
|
@ -49,7 +50,8 @@ public record OutgoingMessageEntity(UUID guid, int type, long timestamp, @Nullab
|
||||||
envelope.hasDestinationUuid() ? UUID.fromString(envelope.getDestinationUuid()) : null,
|
envelope.hasDestinationUuid() ? UUID.fromString(envelope.getDestinationUuid()) : null,
|
||||||
envelope.hasUpdatedPni() ? UUID.fromString(envelope.getUpdatedPni()) : null,
|
envelope.hasUpdatedPni() ? UUID.fromString(envelope.getUpdatedPni()) : null,
|
||||||
envelope.getContent().toByteArray(),
|
envelope.getContent().toByteArray(),
|
||||||
envelope.getServerTimestamp());
|
envelope.getServerTimestamp(),
|
||||||
|
envelope.getUrgent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,13 +66,13 @@ public record OutgoingMessageEntity(UUID guid, int type, long timestamp, @Nullab
|
||||||
return type == that.type && timestamp == that.timestamp && sourceDevice == that.sourceDevice
|
return type == that.type && timestamp == that.timestamp && sourceDevice == that.sourceDevice
|
||||||
&& serverTimestamp == that.serverTimestamp && guid.equals(that.guid)
|
&& serverTimestamp == that.serverTimestamp && guid.equals(that.guid)
|
||||||
&& Objects.equals(sourceUuid, that.sourceUuid) && destinationUuid.equals(that.destinationUuid)
|
&& Objects.equals(sourceUuid, that.sourceUuid) && destinationUuid.equals(that.destinationUuid)
|
||||||
&& Objects.equals(updatedPni, that.updatedPni) && Arrays.equals(content, that.content);
|
&& Objects.equals(updatedPni, that.updatedPni) && Arrays.equals(content, that.content) && urgent == that.urgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = Objects.hash(guid, type, timestamp, sourceUuid, sourceDevice, destinationUuid, updatedPni,
|
int result = Objects.hash(guid, type, timestamp, sourceUuid, sourceDevice, destinationUuid, updatedPni,
|
||||||
serverTimestamp);
|
serverTimestamp, urgent);
|
||||||
result = 31 * result + Arrays.hashCode(content);
|
result = 31 * result + Arrays.hashCode(content);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -278,7 +278,7 @@ public class MessagesDynamoDb extends AbstractDynamoDbStore {
|
||||||
final UUID updatedPni = AttributeValues.getUUID(item, KEY_UPDATED_PNI, null);
|
final UUID updatedPni = AttributeValues.getUUID(item, KEY_UPDATED_PNI, null);
|
||||||
|
|
||||||
envelope = new OutgoingMessageEntity(messageUuid, type, timestamp, sourceUuid, sourceDevice, destinationUuid,
|
envelope = new OutgoingMessageEntity(messageUuid, type, timestamp, sourceUuid, sourceDevice, destinationUuid,
|
||||||
updatedPni, content, sortKey.getServerTimestamp()).toEnvelope();
|
updatedPni, content, sortKey.getServerTimestamp(), true).toEnvelope();
|
||||||
|
|
||||||
GET_MESSAGE_WITH_ATTRIBUTES_COUNTER.increment();
|
GET_MESSAGE_WITH_ATTRIBUTES_COUNTER.increment();
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ message Envelope {
|
||||||
optional uint64 server_timestamp = 10;
|
optional uint64 server_timestamp = 10;
|
||||||
optional bool ephemeral = 12; // indicates that the message should not be persisted if the recipient is offline
|
optional bool ephemeral = 12; // indicates that the message should not be persisted if the recipient is offline
|
||||||
optional string destination_uuid = 13;
|
optional string destination_uuid = 13;
|
||||||
|
optional bool urgent = 14 [default=true];
|
||||||
optional string updated_pni = 15;
|
optional string updated_pni = 15;
|
||||||
// next: 16
|
// next: 16
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,6 +207,28 @@ class MessageControllerTest {
|
||||||
|
|
||||||
assertTrue(captor.getValue().hasSourceUuid());
|
assertTrue(captor.getValue().hasSourceUuid());
|
||||||
assertTrue(captor.getValue().hasSourceDevice());
|
assertTrue(captor.getValue().hasSourceDevice());
|
||||||
|
assertTrue(captor.getValue().getUrgent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSingleDeviceCurrentNotUrgent() throws Exception {
|
||||||
|
Response response =
|
||||||
|
resources.getJerseyTest()
|
||||||
|
.target(String.format("/v1/messages/%s", SINGLE_DEVICE_UUID))
|
||||||
|
.request()
|
||||||
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
|
.put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_single_device_not_urgent.json"),
|
||||||
|
IncomingMessageList.class),
|
||||||
|
MediaType.APPLICATION_JSON_TYPE));
|
||||||
|
|
||||||
|
assertThat("Good Response", response.getStatus(), is(equalTo(200)));
|
||||||
|
|
||||||
|
ArgumentCaptor<Envelope> captor = ArgumentCaptor.forClass(Envelope.class);
|
||||||
|
verify(messageSender, times(1)).sendMessage(any(Account.class), any(Device.class), captor.capture(), eq(false));
|
||||||
|
|
||||||
|
assertTrue(captor.getValue().hasSourceUuid());
|
||||||
|
assertTrue(captor.getValue().hasSourceDevice());
|
||||||
|
assertFalse(captor.getValue().getUrgent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -328,7 +350,31 @@ class MessageControllerTest {
|
||||||
|
|
||||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(200)));
|
assertThat("Good Response Code", response.getStatus(), is(equalTo(200)));
|
||||||
|
|
||||||
verify(messageSender, times(2)).sendMessage(any(Account.class), any(Device.class), any(Envelope.class), eq(false));
|
final ArgumentCaptor<Envelope> envelopeCaptor = ArgumentCaptor.forClass(Envelope.class);
|
||||||
|
|
||||||
|
verify(messageSender, times(2)).sendMessage(any(Account.class), any(Device.class), envelopeCaptor.capture(), eq(false));
|
||||||
|
|
||||||
|
envelopeCaptor.getAllValues().forEach(envelope -> assertTrue(envelope.getUrgent()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultiDeviceNotUrgent() throws Exception {
|
||||||
|
Response response =
|
||||||
|
resources.getJerseyTest()
|
||||||
|
.target(String.format("/v1/messages/%s", MULTI_DEVICE_UUID))
|
||||||
|
.request()
|
||||||
|
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||||
|
.put(Entity.entity(SystemMapper.getMapper().readValue(jsonFixture("fixtures/current_message_multi_device_not_urgent.json"),
|
||||||
|
IncomingMessageList.class),
|
||||||
|
MediaType.APPLICATION_JSON_TYPE));
|
||||||
|
|
||||||
|
assertThat("Good Response Code", response.getStatus(), is(equalTo(200)));
|
||||||
|
|
||||||
|
final ArgumentCaptor<Envelope> envelopeCaptor = ArgumentCaptor.forClass(Envelope.class);
|
||||||
|
|
||||||
|
verify(messageSender, times(2)).sendMessage(any(Account.class), any(Device.class), envelopeCaptor.capture(), eq(false));
|
||||||
|
|
||||||
|
envelopeCaptor.getAllValues().forEach(envelope -> assertFalse(envelope.getUrgent()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -595,7 +641,7 @@ class MessageControllerTest {
|
||||||
.request()
|
.request()
|
||||||
.header(OptionalAccess.UNIDENTIFIED, Base64.getEncoder().encodeToString("1234".getBytes()))
|
.header(OptionalAccess.UNIDENTIFIED, Base64.getEncoder().encodeToString("1234".getBytes()))
|
||||||
.put(Entity.entity(new IncomingMessageList(
|
.put(Entity.entity(new IncomingMessageList(
|
||||||
List.of(new IncomingMessage(1, 1L, 1, new String(contentBytes))), false,
|
List.of(new IncomingMessage(1, 1L, 1, new String(contentBytes))), false, true,
|
||||||
System.currentTimeMillis()),
|
System.currentTimeMillis()),
|
||||||
MediaType.APPLICATION_JSON_TYPE));
|
MediaType.APPLICATION_JSON_TYPE));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2013-2022 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.textsecuregcm.entities;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class IncomingMessageListTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void fromJson() throws JsonProcessingException {
|
||||||
|
{
|
||||||
|
final String incomingMessageListJson = """
|
||||||
|
{
|
||||||
|
"messages": [],
|
||||||
|
"timestamp": 123456789,
|
||||||
|
"online": true,
|
||||||
|
"urgent": false
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
final IncomingMessageList incomingMessageList =
|
||||||
|
SystemMapper.getMapper().readValue(incomingMessageListJson, IncomingMessageList.class);
|
||||||
|
|
||||||
|
assertTrue(incomingMessageList.online());
|
||||||
|
assertFalse(incomingMessageList.urgent());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
final String incomingMessageListJson = """
|
||||||
|
{
|
||||||
|
"messages": [],
|
||||||
|
"timestamp": 123456789,
|
||||||
|
"online": true
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
final IncomingMessageList incomingMessageList =
|
||||||
|
SystemMapper.getMapper().readValue(incomingMessageListJson, IncomingMessageList.class);
|
||||||
|
|
||||||
|
assertTrue(incomingMessageList.online());
|
||||||
|
assertTrue(incomingMessageList.urgent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,8 @@ class OutgoingMessageEntityTest {
|
||||||
UUID.randomUUID(),
|
UUID.randomUUID(),
|
||||||
updatedPni,
|
updatedPni,
|
||||||
messageContent,
|
messageContent,
|
||||||
serverTimestamp);
|
serverTimestamp,
|
||||||
|
true);
|
||||||
|
|
||||||
assertEquals(outgoingMessageEntity, OutgoingMessageEntity.fromEnvelope(outgoingMessageEntity.toEnvelope()));
|
assertEquals(outgoingMessageEntity, OutgoingMessageEntity.fromEnvelope(outgoingMessageEntity.toEnvelope()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"urgent": false,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 1,
|
||||||
|
"destinationDeviceId": 1,
|
||||||
|
"destinationRegistrationId": 222,
|
||||||
|
"content": "Zm9vYmFyego",
|
||||||
|
"timestamp": 1234
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 1,
|
||||||
|
"destinationDeviceId": 2,
|
||||||
|
"destinationRegistrationId": 333,
|
||||||
|
"content": "Zm9vYmFyego",
|
||||||
|
"timestamp": 1234
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"urgent": false,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"type": 1,
|
||||||
|
"destinationDeviceId": 1,
|
||||||
|
"content": "Zm9vYmFyego",
|
||||||
|
"timestamp": 1234
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue