Introduce "service identifiers"

This commit is contained in:
Jon Chambers
2023-07-21 09:34:10 -04:00
committed by GitHub
parent 4a6c7152cf
commit abb32bd919
39 changed files with 1304 additions and 588 deletions

View File

@@ -4,7 +4,14 @@
*/
package org.whispersystems.textsecuregcm.entities;
import javax.validation.constraints.NotNull;
import java.util.UUID;
public record AccountIdentifierResponse(@NotNull UUID uuid) {}
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import javax.validation.constraints.NotNull;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter;
public record AccountIdentifierResponse(@NotNull
@JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(using = ServiceIdentifierAdapter.AciServiceIdentifierDeserializer.class)
AciServiceIdentifier uuid) {}

View File

@@ -5,22 +5,14 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.UUID;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter;
public class AccountMismatchedDevices {
@JsonProperty
public final UUID uuid;
public record AccountMismatchedDevices(@JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(using = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class)
ServiceIdentifier uuid,
@JsonProperty
public final MismatchedDevices devices;
public String toString() {
return "AccountMismatchedDevices(" + uuid + ", " + devices + ")";
}
public AccountMismatchedDevices(final UUID uuid, final MismatchedDevices devices) {
this.uuid = uuid;
this.devices = devices;
}
MismatchedDevices devices) {
}

View File

@@ -5,22 +5,14 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.UUID;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter;
public class AccountStaleDevices {
@JsonProperty
public final UUID uuid;
public record AccountStaleDevices(@JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(using = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class)
ServiceIdentifier uuid,
@JsonProperty
public final StaleDevices devices;
public String toString() {
return "AccountStaleDevices(" + uuid + ", " + devices + ")";
}
public AccountStaleDevices(final UUID uuid, final StaleDevices devices) {
this.uuid = uuid;
this.devices = devices;
}
StaleDevices devices) {
}

View File

@@ -9,11 +9,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter;
import org.whispersystems.textsecuregcm.util.IdentityKeyAdapter;
import java.util.List;
import java.util.UUID;
public class BaseProfileResponse {
@@ -35,7 +35,9 @@ public class BaseProfileResponse {
private List<Badge> badges;
@JsonProperty
private UUID uuid;
@JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(using = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class)
private ServiceIdentifier uuid;
public BaseProfileResponse() {
}
@@ -45,7 +47,7 @@ public class BaseProfileResponse {
final boolean unrestrictedUnidentifiedAccess,
final UserCapabilities capabilities,
final List<Badge> badges,
final UUID uuid) {
final ServiceIdentifier uuid) {
this.identityKey = identityKey;
this.unidentifiedAccess = unidentifiedAccess;
@@ -75,7 +77,7 @@ public class BaseProfileResponse {
return badges;
}
public UUID getUuid() {
public ServiceIdentifier getUuid() {
return uuid;
}
}

View File

@@ -5,13 +5,15 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.util.ExactlySize;
import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter;
public record BatchIdentityCheckRequest(@Valid @NotNull @Size(max = 1000) List<Element> elements) {
@@ -20,18 +22,13 @@ public record BatchIdentityCheckRequest(@Valid @NotNull @Size(max = 1000) List<E
* @param fingerprint most significant 4 bytes of SHA-256 of the 33-byte identity key field (32-byte curve25519 public
* key prefixed with 0x05)
*/
public record Element(@Deprecated @Nullable UUID aci,
@Nullable UUID uuid,
@NotNull @ExactlySize(4) byte[] fingerprint) {
public record Element(@NotNull
@JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(using = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class)
ServiceIdentifier uuid,
public Element {
if (aci == null && uuid == null) {
throw new IllegalArgumentException("aci and uuid cannot both be null");
}
if (aci != null && uuid != null) {
throw new IllegalArgumentException("aci and uuid cannot both be non-null");
}
}
@NotNull
@ExactlySize(4)
byte[] fingerprint) {
}
}

View File

@@ -6,39 +6,27 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.util.IdentityKeyAdapter;
import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter;
public record BatchIdentityCheckResponse(@Valid List<Element> elements) {
public record Element(@Deprecated
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Nullable UUID aci,
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@Nullable UUID uuid,
public record Element(@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(using = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class)
@NotNull
ServiceIdentifier uuid,
@NotNull
@JsonSerialize(using = IdentityKeyAdapter.Serializer.class)
@JsonDeserialize(using = IdentityKeyAdapter.Deserializer.class)
IdentityKey identityKey) {
public Element {
if (aci == null && uuid == null) {
throw new IllegalArgumentException("aci and uuid cannot both be null");
}
if (aci != null && uuid != null) {
throw new IllegalArgumentException("aci and uuid cannot both be non-null");
}
}
}
}

View File

@@ -6,14 +6,15 @@ package org.whispersystems.textsecuregcm.entities;
import com.google.protobuf.ByteString;
import java.util.Base64;
import java.util.UUID;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.storage.Account;
public record IncomingMessage(int type, long destinationDeviceId, int destinationRegistrationId, String content) {
public MessageProtos.Envelope toEnvelope(final UUID destinationUuid,
public MessageProtos.Envelope toEnvelope(final ServiceIdentifier destinationIdentifier,
@Nullable Account sourceAccount,
@Nullable Long sourceDeviceId,
final long timestamp,
@@ -32,13 +33,13 @@ public record IncomingMessage(int type, long destinationDeviceId, int destinatio
envelopeBuilder.setType(envelopeType)
.setTimestamp(timestamp)
.setServerTimestamp(System.currentTimeMillis())
.setDestinationUuid(destinationUuid.toString())
.setDestinationUuid(destinationIdentifier.toServiceIdentifierString())
.setStory(story)
.setUrgent(urgent);
if (sourceAccount != null && sourceDeviceId != null) {
envelopeBuilder
.setSourceUuid(sourceAccount.getUuid().toString())
.setSourceUuid(new AciServiceIdentifier(sourceAccount.getUuid()).toServiceIdentifierString())
.setSourceDevice(sourceDeviceId.intValue());
}

View File

@@ -6,31 +6,15 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
public class MismatchedDevices {
@JsonProperty
@Schema(description = "Devices present on the account but absent in the request")
public List<Long> missingDevices;
@JsonProperty
@Schema(description = "Devices absent on the request but present in the account")
public List<Long> extraDevices;
@VisibleForTesting
public MismatchedDevices() {}
public String toString() {
return "MismatchedDevices(" + missingDevices + ", " + extraDevices + ")";
}
public MismatchedDevices(List<Long> missingDevices, List<Long> extraDevices) {
this.missingDevices = missingDevices;
this.extraDevices = extraDevices;
}
public record MismatchedDevices(@JsonProperty
@Schema(description = "Devices present on the account but absent in the request")
List<Long> missingDevices,
@JsonProperty
@Schema(description = "Devices absent on the request but present in the account")
List<Long> extraDevices) {
}

View File

@@ -8,7 +8,7 @@ package org.whispersystems.textsecuregcm.entities;
import static com.codahale.metrics.MetricRegistry.name;
import java.util.Arrays;
import java.util.UUID;
import java.util.Objects;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Max;
@@ -16,58 +16,33 @@ import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.whispersystems.textsecuregcm.controllers.MessageController;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.providers.MultiRecipientMessageProvider;
import org.whispersystems.textsecuregcm.util.Pair;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Metrics;
import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter;
public class MultiRecipientMessage {
public record MultiRecipientMessage(
@NotNull @Size(min = 1, max = MultiRecipientMessageProvider.MAX_RECIPIENT_COUNT) @Valid Recipient[] recipients,
@NotNull @Size(min = 32) byte[] commonPayload) {
private static final Counter REJECT_DUPLICATE_RECIPIENT_COUNTER =
Metrics.counter(
name(MessageController.class, "rejectDuplicateRecipients"),
"multiRecipient", "false");
public static class Recipient {
@NotNull
private final UUID uuid;
@Min(1)
private final long deviceId;
@Min(0)
@Max(65535)
private final int registrationId;
@Size(min = 48, max = 48)
@NotNull
private final byte[] perRecipientKeyMaterial;
public Recipient(UUID uuid, long deviceId, int registrationId, byte[] perRecipientKeyMaterial) {
this.uuid = uuid;
this.deviceId = deviceId;
this.registrationId = registrationId;
this.perRecipientKeyMaterial = perRecipientKeyMaterial;
}
public UUID getUuid() {
return uuid;
}
public long getDeviceId() {
return deviceId;
}
public int getRegistrationId() {
return registrationId;
}
public byte[] getPerRecipientKeyMaterial() {
return perRecipientKeyMaterial;
}
public record Recipient(@NotNull
@JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(using = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class)
ServiceIdentifier uuid,
@Min(1) long deviceId,
@Min(0) @Max(65535) int registrationId,
@Size(min = 48, max = 48) @NotNull byte[] perRecipientKeyMaterial) {
@Override
public boolean equals(final Object o) {
@@ -75,60 +50,48 @@ public class MultiRecipientMessage {
return true;
if (o == null || getClass() != o.getClass())
return false;
Recipient recipient = (Recipient) o;
if (deviceId != recipient.deviceId)
return false;
if (registrationId != recipient.registrationId)
return false;
if (!uuid.equals(recipient.uuid))
return false;
return Arrays.equals(perRecipientKeyMaterial, recipient.perRecipientKeyMaterial);
return deviceId == recipient.deviceId && registrationId == recipient.registrationId && uuid.equals(recipient.uuid)
&& Arrays.equals(perRecipientKeyMaterial, recipient.perRecipientKeyMaterial);
}
@Override
public int hashCode() {
int result = uuid.hashCode();
result = 31 * result + (int) (deviceId ^ (deviceId >>> 32));
result = 31 * result + registrationId;
int result = Objects.hash(uuid, deviceId, registrationId);
result = 31 * result + Arrays.hashCode(perRecipientKeyMaterial);
return result;
}
public String toString() {
return "Recipient(" + uuid + ", " + deviceId + ", " + registrationId + ", " + Arrays.toString(perRecipientKeyMaterial) + ")";
}
}
@NotNull
@Size(min = 1, max = MultiRecipientMessageProvider.MAX_RECIPIENT_COUNT)
@Valid
private final Recipient[] recipients;
@NotNull
@Size(min = 32)
private final byte[] commonPayload;
public MultiRecipientMessage(Recipient[] recipients, byte[] commonPayload) {
this.recipients = recipients;
this.commonPayload = commonPayload;
}
public Recipient[] getRecipients() {
return recipients;
}
public byte[] getCommonPayload() {
return commonPayload;
}
@AssertTrue
public boolean hasNoDuplicateRecipients() {
boolean valid = Arrays.stream(recipients).map(r -> new Pair<>(r.getUuid(), r.getDeviceId())).distinct().count() == recipients.length;
boolean valid =
Arrays.stream(recipients).map(r -> new Pair<>(r.uuid(), r.deviceId())).distinct().count() == recipients.length;
if (!valid) {
REJECT_DUPLICATE_RECIPIENT_COUNTER.increment();
}
return valid;
}
@Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
MultiRecipientMessage that = (MultiRecipientMessage) o;
return Arrays.equals(recipients, that.recipients) && Arrays.equals(commonPayload, that.commonPayload);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(recipients);
result = 31 * result + Arrays.hashCode(commonPayload);
return result;
}
}

View File

@@ -5,28 +5,50 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.protobuf.ByteString;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import javax.annotation.Nullable;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter;
public record OutgoingMessageEntity(UUID guid, int type, long timestamp, @Nullable UUID sourceUuid, int sourceDevice,
UUID destinationUuid, @Nullable UUID updatedPni, byte[] content,
long serverTimestamp, boolean urgent, boolean story, @Nullable byte[] reportSpamToken) {
public record OutgoingMessageEntity(UUID guid,
int type,
long timestamp,
@JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(using = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class)
@Nullable
ServiceIdentifier sourceUuid,
int sourceDevice,
@JsonSerialize(using = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(using = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class)
ServiceIdentifier destinationUuid,
@Nullable UUID updatedPni,
byte[] content,
long serverTimestamp,
boolean urgent,
boolean story,
@Nullable byte[] reportSpamToken) {
public MessageProtos.Envelope toEnvelope() {
final MessageProtos.Envelope.Builder builder = MessageProtos.Envelope.newBuilder()
.setType(MessageProtos.Envelope.Type.forNumber(type()))
.setTimestamp(timestamp())
.setServerTimestamp(serverTimestamp())
.setDestinationUuid(destinationUuid().toString())
.setDestinationUuid(destinationUuid().toServiceIdentifierString())
.setServerGuid(guid().toString())
.setStory(story)
.setUrgent(urgent);
if (sourceUuid() != null) {
builder.setSourceUuid(sourceUuid().toString());
builder.setSourceUuid(sourceUuid().toServiceIdentifierString());
builder.setSourceDevice(sourceDevice());
}
@@ -51,9 +73,9 @@ public record OutgoingMessageEntity(UUID guid, int type, long timestamp, @Nullab
UUID.fromString(envelope.getServerGuid()),
envelope.getType().getNumber(),
envelope.getTimestamp(),
envelope.hasSourceUuid() ? UUID.fromString(envelope.getSourceUuid()) : null,
envelope.hasSourceUuid() ? ServiceIdentifier.valueOf(envelope.getSourceUuid()) : null,
envelope.getSourceDevice(),
envelope.hasDestinationUuid() ? UUID.fromString(envelope.getDestinationUuid()) : null,
envelope.hasDestinationUuid() ? ServiceIdentifier.valueOf(envelope.getDestinationUuid()) : null,
envelope.hasUpdatedPni() ? UUID.fromString(envelope.getUpdatedPni()) : null,
envelope.getContent().toByteArray(),
envelope.getServerTimestamp(),

View File

@@ -6,27 +6,15 @@
package org.whispersystems.textsecuregcm.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.annotations.VisibleForTesting;
import org.whispersystems.textsecuregcm.identity.ServiceIdentifier;
import org.whispersystems.textsecuregcm.util.ServiceIdentifierAdapter;
import java.util.List;
import java.util.UUID;
public class SendMultiRecipientMessageResponse {
@JsonProperty
private List<UUID> uuids404;
public SendMultiRecipientMessageResponse() {
}
public String toString() {
return "SendMultiRecipientMessageResponse(" + uuids404 + ")";
}
@VisibleForTesting
public List<UUID> getUUIDs404() {
return this.uuids404;
}
public SendMultiRecipientMessageResponse(final List<UUID> uuids404) {
this.uuids404 = uuids404;
}
public record SendMultiRecipientMessageResponse(@JsonSerialize(contentUsing = ServiceIdentifierAdapter.ServiceIdentifierSerializer.class)
@JsonDeserialize(contentUsing = ServiceIdentifierAdapter.ServiceIdentifierDeserializer.class)
List<ServiceIdentifier> uuids404) {
}

View File

@@ -10,20 +10,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
public class StaleDevices {
@JsonProperty
@Schema(description = "Devices that are no longer active")
private List<Long> staleDevices;
public StaleDevices() {}
public String toString() {
return "StaleDevices(" + staleDevices + ")";
}
public StaleDevices(List<Long> staleDevices) {
this.staleDevices = staleDevices;
}
public record StaleDevices(@JsonProperty
@Schema(description = "Devices that are no longer active")
List<Long> staleDevices) {
}