Add read support for binary service ids.

This commit is contained in:
Michelle Tang
2025-10-28 14:29:43 -04:00
committed by jeffrey-signal
parent bf4aa9cae9
commit f16405fabf
48 changed files with 399 additions and 205 deletions

View File

@@ -184,6 +184,7 @@ public class SignalServiceMessageSender {
private final Scheduler scheduler;
private final long maxEnvelopeSize;
private final BooleanSupplier useRestFallback;
private final boolean useBinaryId;
public SignalServiceMessageSender(PushServiceSocket pushServiceSocket,
SignalServiceDataStore store,
@@ -194,7 +195,8 @@ public class SignalServiceMessageSender {
Optional<EventListener> eventListener,
ExecutorService executor,
long maxEnvelopeSize,
BooleanSupplier useRestFallback)
BooleanSupplier useRestFallback,
boolean useBinaryId)
{
CredentialsProvider credentialsProvider = pushServiceSocket.getCredentialsProvider();
@@ -212,6 +214,7 @@ public class SignalServiceMessageSender {
this.scheduler = Schedulers.from(executor, false, false);
this.keysApi = keysApi;
this.useRestFallback = useRestFallback;
this.useBinaryId = useBinaryId;
}
/**
@@ -1057,6 +1060,7 @@ public class SignalServiceMessageSender {
.id(message.getQuote().get().getId())
.text(message.getQuote().get().getText())
.authorAci(message.getQuote().get().getAuthor().toString())
.authorAciBinary(useBinaryId ? message.getQuote().get().getAuthor().toByteString() : null)
.type(message.getQuote().get().getType().getProtoType());
List<SignalServiceDataMessage.Mention> mentions = message.getQuote().get().getMentions();
@@ -1164,7 +1168,8 @@ public class SignalServiceMessageSender {
.emoji(message.getReaction().get().getEmoji())
.remove(message.getReaction().get().isRemove())
.targetSentTimestamp(message.getReaction().get().getTargetSentTimestamp())
.targetAuthorAci(message.getReaction().get().getTargetAuthor().toString());
.targetAuthorAci(message.getReaction().get().getTargetAuthor().toString())
.targetAuthorAciBinary(useBinaryId ? message.getReaction().get().getTargetAuthor().toByteString() : null);
builder.reaction(reactionBuilder.build());
builder.requiredProtocolVersion(Math.max(DataMessage.ProtocolVersion.REACTIONS.getValue(), builder.requiredProtocolVersion));
@@ -1209,6 +1214,7 @@ public class SignalServiceMessageSender {
builder.storyContext(new DataMessage.StoryContext.Builder()
.authorAci(storyContext.getAuthorServiceId().toString())
.authorAciBinary(useBinaryId ? storyContext.getAuthorServiceId().toByteString() : null)
.sentTimestamp(storyContext.getSentTimestamp())
.build());
}
@@ -1399,6 +1405,7 @@ public class SignalServiceMessageSender {
unidentifiedDeliveryStatuses.add(new SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder()
.destinationServiceId(result.getAddress().getServiceId().toString())
.destinationServiceIdBinary(useBinaryId ? result.getAddress().getServiceId().toByteString() : null)
.unidentified(false)
.destinationPniIdentityKey(identity)
.build());
@@ -1408,6 +1415,7 @@ public class SignalServiceMessageSender {
if (recipient.isPresent()) {
sentMessage.destinationServiceId(recipient.get().getServiceId().toString());
sentMessage.destinationServiceIdBinary(useBinaryId ? recipient.get().getServiceId().toByteString() : null);
if (recipient.get().getNumber().isPresent()) {
sentMessage.destinationE164(recipient.get().getNumber().get());
}
@@ -1447,6 +1455,7 @@ public class SignalServiceMessageSender {
return new SyncMessage.Sent.StoryMessageRecipient.Builder()
.distributionListIds(storyMessageRecipient.getDistributionListIds())
.destinationServiceId(storyMessageRecipient.getSignalServiceAddress().getIdentifier())
.destinationServiceIdBinary(useBinaryId ? storyMessageRecipient.getSignalServiceAddress().getServiceId().toByteString() : null)
.isAllowedToReply(storyMessageRecipient.isAllowedToReply())
.build();
}
@@ -1460,6 +1469,7 @@ public class SignalServiceMessageSender {
.map(readMessage -> new SyncMessage.Read.Builder()
.timestamp(readMessage.getTimestamp())
.senderAci(readMessage.getSenderAci().toString())
.senderAciBinary(useBinaryId ? readMessage.getSenderAci().toByteString() : null)
.build())
.collect(Collectors.toList())
);
@@ -1476,6 +1486,7 @@ public class SignalServiceMessageSender {
.map(readMessage -> new SyncMessage.Viewed.Builder()
.timestamp(readMessage.getTimestamp())
.senderAci(readMessage.getSender().toString())
.senderAciBinary(useBinaryId ? readMessage.getSender().toByteString() : null)
.build())
.collect(Collectors.toList())
);
@@ -1490,6 +1501,7 @@ public class SignalServiceMessageSender {
builder.viewOnceOpen(new SyncMessage.ViewOnceOpen.Builder()
.timestamp(readMessage.getTimestamp())
.senderAci(readMessage.getSender().toString())
.senderAciBinary(useBinaryId ? readMessage.getSender().toByteString() : null)
.build());
return container.syncMessage(builder.build()).build();
@@ -1501,6 +1513,7 @@ public class SignalServiceMessageSender {
SyncMessage.Blocked.Builder blockedMessage = new SyncMessage.Blocked.Builder();
blockedMessage.acis(blocked.individuals.stream().filter(a -> a.getAci() != null).map(a -> a.getAci().toString()).collect(Collectors.toList()));
blockedMessage.acisBinary(useBinaryId ? blocked.individuals.stream().filter(a -> a.getAci() != null).map(a -> a.getAci().toByteString()).collect(Collectors.toList()) : Collections.emptyList());
blockedMessage.numbers(blocked.individuals.stream().filter(a -> a.getE164() != null).map(a -> a.getE164()).collect(Collectors.toList()));
blockedMessage.groupIds(blocked.groupIds.stream().map(ByteString::of).collect(Collectors.toList()));
@@ -1600,6 +1613,7 @@ public class SignalServiceMessageSender {
if (message.getPerson().isPresent()) {
responseMessage.threadAci(message.getPerson().get().toString());
responseMessage.threadAciBinary(useBinaryId ? message.getPerson().get().toByteString() : null);
}
switch (message.getType()) {
@@ -1697,6 +1711,7 @@ public class SignalServiceMessageSender {
verifiedMessageBuilder.nullMessage(ByteString.of(nullMessage));
verifiedMessageBuilder.identityKey(ByteString.of(verifiedMessage.getIdentityKey().serialize()));
verifiedMessageBuilder.destinationAci(verifiedMessage.getDestination().getServiceId().toString());
verifiedMessageBuilder.destinationAciBinary(useBinaryId ? verifiedMessage.getDestination().getServiceId().toByteString() : null);
switch (verifiedMessage.getVerified()) {

View File

@@ -49,6 +49,7 @@ import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.Content;
import org.whispersystems.signalservice.internal.push.Envelope;
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
@@ -171,32 +172,36 @@ public class SignalServiceCipher {
ProtocolInvalidKeyIdException, ProtocolNoSessionException,
SelfSendException, InvalidMessageStructureException
{
ServiceId sourceServiceId = ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary);
try {
ServiceId destinationServiceId = ServiceId.parseOrNull(envelope.destinationServiceId, envelope.destinationServiceIdBinary);
String destinationStr = (destinationServiceId != null) ? destinationServiceId.toString() : "";
String serverGuid = UuidUtil.getStringUUID(envelope.serverGuid, envelope.serverGuidBinary);
byte[] paddedMessage;
SignalServiceMetadata metadata;
if (envelope.sourceServiceId == null && envelope.type != Envelope.Type.UNIDENTIFIED_SENDER) {
if (sourceServiceId == null && envelope.type != Envelope.Type.UNIDENTIFIED_SENDER) {
throw new InvalidMessageStructureException("Non-UD envelope is missing a UUID!");
}
if (envelope.type == Envelope.Type.PREKEY_BUNDLE) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.sourceServiceId, envelope.sourceDevice);
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDevice);
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(envelope.content.toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, envelope.serverGuid, Optional.empty(), envelope.destinationServiceId);
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
signalProtocolStore.clearSenderKeySharedWith(Collections.singleton(sourceAddress));
} else if (envelope.type == Envelope.Type.CIPHERTEXT) {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.sourceServiceId, envelope.sourceDevice);
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(sourceServiceId.toString(), envelope.sourceDevice);
SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress));
paddedMessage = sessionCipher.decrypt(new SignalMessage(envelope.content.toByteArray()));
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, envelope.serverGuid, Optional.empty(), envelope.destinationServiceId);
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
} else if (envelope.type == Envelope.Type.PLAINTEXT_CONTENT) {
paddedMessage = new PlaintextContent(envelope.content.toByteArray()).getBody();
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, envelope.serverGuid, Optional.empty(), envelope.destinationServiceId);
metadata = new SignalServiceMetadata(getSourceAddress(envelope), envelope.sourceDevice, envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, false, serverGuid, Optional.empty(), destinationStr);
} else if (envelope.type == Envelope.Type.UNIDENTIFIED_SENDER) {
SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getServiceId().getRawUuid(), localAddress.getNumber().orElse(null), localDeviceId));
DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, envelope.content.toByteArray(), envelope.serverTimestamp);
@@ -204,7 +209,7 @@ public class SignalServiceCipher {
Optional<byte[]> groupId = result.getGroupId();
boolean needsReceipt = true;
if (envelope.sourceServiceId != null) {
if (sourceServiceId != null) {
Log.w(TAG, "[" + envelope.timestamp + "] Received a UD-encrypted message sent over an identified channel. Marking as needsReceipt=false");
needsReceipt = false;
}
@@ -214,7 +219,7 @@ public class SignalServiceCipher {
}
paddedMessage = result.getPaddedMessage();
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, needsReceipt, envelope.serverGuid, groupId, envelope.destinationServiceId);
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.timestamp, envelope.serverTimestamp, serverDeliveredTimestamp, needsReceipt, serverGuid, groupId, destinationStr);
} else {
throw new InvalidMetadataMessageException("Unknown type: " + envelope.type);
}
@@ -224,26 +229,26 @@ public class SignalServiceCipher {
return new Plaintext(metadata, data);
} catch (DuplicateMessageException e) {
throw new ProtocolDuplicateMessageException(e, envelope.sourceServiceId, envelope.sourceDevice);
throw new ProtocolDuplicateMessageException(e, sourceServiceId.toString(), envelope.sourceDevice);
} catch (LegacyMessageException e) {
throw new ProtocolLegacyMessageException(e, envelope.sourceServiceId, envelope.sourceDevice);
throw new ProtocolLegacyMessageException(e, sourceServiceId.toString(), envelope.sourceDevice);
} catch (InvalidMessageException e) {
throw new ProtocolInvalidMessageException(e, envelope.sourceServiceId, envelope.sourceDevice);
throw new ProtocolInvalidMessageException(e, sourceServiceId.toString(), envelope.sourceDevice);
} catch (InvalidKeyIdException e) {
throw new ProtocolInvalidKeyIdException(e, envelope.sourceServiceId, envelope.sourceDevice);
throw new ProtocolInvalidKeyIdException(e, sourceServiceId.toString(), envelope.sourceDevice);
} catch (InvalidKeyException e) {
throw new ProtocolInvalidKeyException(e, envelope.sourceServiceId, envelope.sourceDevice);
throw new ProtocolInvalidKeyException(e, sourceServiceId.toString(), envelope.sourceDevice);
} catch (UntrustedIdentityException e) {
throw new ProtocolUntrustedIdentityException(e, envelope.sourceServiceId, envelope.sourceDevice);
throw new ProtocolUntrustedIdentityException(e, sourceServiceId.toString(), envelope.sourceDevice);
} catch (InvalidVersionException e) {
throw new ProtocolInvalidVersionException(e, envelope.sourceServiceId, envelope.sourceDevice);
throw new ProtocolInvalidVersionException(e, sourceServiceId.toString(), envelope.sourceDevice);
} catch (NoSessionException e) {
throw new ProtocolNoSessionException(e, envelope.sourceServiceId, envelope.sourceDevice);
throw new ProtocolNoSessionException(e, sourceServiceId.toString(), envelope.sourceDevice);
}
}
private static SignalServiceAddress getSourceAddress(Envelope envelope) {
return new SignalServiceAddress(ServiceId.parseOrNull(envelope.sourceServiceId));
return new SignalServiceAddress(ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary));
}
private static class Plaintext {

View File

@@ -19,6 +19,7 @@ import org.whispersystems.signalservice.internal.push.ReceiptMessage
import org.whispersystems.signalservice.internal.push.StoryMessage
import org.whispersystems.signalservice.internal.push.SyncMessage
import org.whispersystems.signalservice.internal.push.TypingMessage
import org.whispersystems.signalservice.internal.util.Util
/**
* Validates an [Envelope] and its decrypted [Content] so that we know the message can be processed safely
@@ -36,7 +37,8 @@ object EnvelopeContentValidator {
validatePlaintextContent(content)?.let { return it }
}
if (envelope.sourceServiceId != null && envelope.sourceServiceId.isInvalidServiceId()) {
val sourceServiceId = ServiceId.parseOrNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary)
if (Util.anyNotNull(envelope.sourceServiceId, envelope.sourceServiceIdBinary) && sourceServiceId.isNullOrInvalidServiceId()) {
return Result.Invalid("Envelope had an invalid sourceServiceId!")
}
@@ -83,7 +85,7 @@ object EnvelopeContentValidator {
return Result.Invalid("[DataMessage] Timestamps don't match! envelope: ${envelope.timestamp}, content: ${dataMessage.timestamp}")
}
if (dataMessage.quote != null && dataMessage.quote.authorAci.isNullOrInvalidAci()) {
if (dataMessage.quote != null && ACI.parseOrNull(dataMessage.quote.authorAci, dataMessage.quote.authorAciBinary).isNullOrInvalidServiceId()) {
return Result.Invalid("[DataMessage] Invalid ACI on quote!")
}
@@ -95,7 +97,7 @@ object EnvelopeContentValidator {
return Result.Invalid("[DataMessage] Invalid AttachmentPointer on DataMessage.previewList.image!")
}
if (dataMessage.bodyRanges.any { it.mentionAci != null && it.mentionAci.isNullOrInvalidAci() }) {
if (dataMessage.bodyRanges.any { Util.anyNotNull(it.mentionAci, it.mentionAciBinary) && ACI.parseOrNull(it.mentionAci, it.mentionAciBinary).isNullOrInvalidServiceId() }) {
return Result.Invalid("[DataMessage] Invalid ACI on body range!")
}
@@ -108,7 +110,7 @@ object EnvelopeContentValidator {
return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.reaction!")
}
if (dataMessage.reaction.targetAuthorAci.isNullOrInvalidAci()) {
if (ACI.parseOrNull(dataMessage.reaction.targetAuthorAci, dataMessage.reaction.targetAuthorAciBinary).isNullOrInvalidServiceId()) {
return Result.Invalid("[DataMessage] Invalid ACI on DataMessage.reaction!")
}
}
@@ -117,7 +119,7 @@ object EnvelopeContentValidator {
return Result.Invalid("[DataMessage] Missing timestamp on DataMessage.delete!")
}
if (dataMessage.storyContext != null && dataMessage.storyContext.authorAci.isNullOrInvalidAci()) {
if (dataMessage.storyContext != null && ACI.parseOrNull(dataMessage.storyContext.authorAci, dataMessage.storyContext.authorAciBinary).isNullOrInvalidServiceId()) {
return Result.Invalid("[DataMessage] Invalid ACI on DataMessage.storyContext!")
}
@@ -166,14 +168,14 @@ object EnvelopeContentValidator {
private fun validateSyncMessage(envelope: Envelope, syncMessage: SyncMessage, localAci: ACI): Result {
// Source serviceId was already determined to be a valid serviceId in general
val sourceServiceId = ServiceId.parseOrThrow(envelope.sourceServiceId!!)
val sourceServiceId = ServiceId.parseOrThrow(envelope.sourceServiceId, envelope.sourceServiceIdBinary)
if (sourceServiceId != localAci) {
return Result.Invalid("[SyncMessage] Source was not our own account!")
}
if (syncMessage.sent != null) {
val validAddress = syncMessage.sent.destinationServiceId.isValidServiceId()
val validAddress = ServiceId.parseOrNull(syncMessage.sent.destinationServiceId, syncMessage.sent.destinationServiceIdBinary) != null
val hasDataGroup = syncMessage.sent.message?.groupV2 != null
val hasStoryGroup = syncMessage.sent.storyMessage?.group != null
val hasStoryManifest = syncMessage.sent.storyMessageRecipients.isNotEmpty()
@@ -196,7 +198,7 @@ object EnvelopeContentValidator {
}
for (status in syncMessage.sent.unidentifiedStatus) {
if (status.destinationServiceId.isNullOrInvalidServiceId()) {
if (ServiceId.parseOrNull(status.destinationServiceId, status.destinationServiceIdBinary).isNullOrInvalidServiceId()) {
return Result.Invalid("[SyncMessage] Invalid ServiceId in SyncMessage.sent.unidentifiedStatusList!")
}
}
@@ -214,19 +216,19 @@ object EnvelopeContentValidator {
}
}
if (syncMessage.read.any { it.senderAci.isNullOrInvalidAci() }) {
if (syncMessage.read.any { ACI.parseOrNull(it.senderAci, it.senderAciBinary).isNullOrInvalidServiceId() }) {
return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.readList!")
}
if (syncMessage.viewed.any { it.senderAci.isNullOrInvalidAci() }) {
if (syncMessage.viewed.any { ACI.parseOrNull(it.senderAci, it.senderAciBinary).isNullOrInvalidServiceId() }) {
return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.viewList!")
}
if (syncMessage.viewOnceOpen != null && syncMessage.viewOnceOpen.senderAci.isNullOrInvalidAci()) {
if (syncMessage.viewOnceOpen != null && ACI.parseOrNull(syncMessage.viewOnceOpen.senderAci, syncMessage.viewOnceOpen.senderAciBinary).isNullOrInvalidServiceId()) {
return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.viewOnceOpen!")
}
if (syncMessage.verified != null && syncMessage.verified.destinationAci.isNullOrInvalidAci()) {
if (syncMessage.verified != null && ACI.parseOrNull(syncMessage.verified.destinationAci, syncMessage.verified.destinationAciBinary).isNullOrInvalidServiceId()) {
return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.verified!")
}
@@ -234,11 +236,11 @@ object EnvelopeContentValidator {
return Result.Invalid("[SyncMessage] Missing packId in stickerPackOperationList!")
}
if (syncMessage.blocked != null && syncMessage.blocked.acis.any { it.isNullOrInvalidAci() }) {
if (syncMessage.blocked != null && syncMessage.blocked.acis.any { it.isNullOrInvalidAci() } && syncMessage.blocked.acisBinary.any { it.isNullOrInvalidAci() }) {
return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.blocked!")
}
if (syncMessage.messageRequestResponse != null && syncMessage.messageRequestResponse.groupId == null && syncMessage.messageRequestResponse.threadAci.isNullOrInvalidAci()) {
if (syncMessage.messageRequestResponse != null && syncMessage.messageRequestResponse.groupId == null && ACI.parseOrNull(syncMessage.messageRequestResponse.threadAci, syncMessage.messageRequestResponse.threadAciBinary).isNullOrInvalidServiceId()) {
return Result.Invalid("[SyncMessage] Invalid ACI in SyncMessage.messageRequestResponse!")
}
@@ -329,7 +331,7 @@ object EnvelopeContentValidator {
return Result.Invalid("[EditMessage] Invalid AttachmentPointer on DataMessage.previewList.image!")
}
if (dataMessage.bodyRanges.any { it.mentionAci != null && it.mentionAci.isNullOrInvalidAci() }) {
if (dataMessage.bodyRanges.any { Util.anyNotNull(it.mentionAci, it.mentionAciBinary) && ACI.parseOrNull(it.mentionAci, it.mentionAciBinary).isNullOrInvalidServiceId() }) {
return Result.Invalid("[EditMessage] Invalid UUID on body range!")
}
@@ -362,11 +364,6 @@ object EnvelopeContentValidator {
return parsed == null || parsed.isUnknown
}
private fun String.isInvalidServiceId(): Boolean {
val parsed = ServiceId.parseOrNull(this)
return parsed == null || parsed.isUnknown
}
private fun String?.isNullOrInvalidAci(): Boolean {
val parsed = ACI.parseOrNull(this)
return parsed == null || parsed.isUnknown
@@ -382,6 +379,10 @@ object EnvelopeContentValidator {
return parsed == null || parsed.isUnknown
}
private fun ServiceId?.isNullOrInvalidServiceId(): Boolean {
return this == null || this.isUnknown
}
private fun Content?.meetsStoryFlagCriteria(): Boolean {
return when {
this == null -> false

View File

@@ -16,6 +16,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.ContactDetails;
import org.whispersystems.signalservice.internal.util.Util;
@@ -42,11 +43,11 @@ public class DeviceContactsInputStream extends ChunkedInputStream {
ContactDetails details = ContactDetails.ADAPTER.decode(detailsSerialized);
if (!SignalServiceAddress.isValidAddress(details.aci, details.number)) {
if (ACI.parseOrNull(details.aci, details.aciBinary) == null) {
throw new IOException("Missing contact address!");
}
Optional<ACI> aci = Optional.ofNullable(ACI.parseOrNull(details.aci));
Optional<ACI> aci = Optional.ofNullable(ACI.parseOrNull(details.aci, details.aciBinary));
Optional<String> e164 = Optional.ofNullable(details.number);
Optional<String> name = Optional.ofNullable(details.name);
Optional<DeviceContactAvatar> avatar = Optional.empty();

View File

@@ -16,8 +16,11 @@ import okio.ByteString;
public class DeviceContactsOutputStream extends ChunkedOutputStream {
public DeviceContactsOutputStream(OutputStream out) {
private final boolean useBinaryId;
public DeviceContactsOutputStream(OutputStream out, boolean useBinaryId) {
super(out);
this.useBinaryId = useBinaryId;
}
public void write(DeviceContact contact) throws IOException {
@@ -40,6 +43,7 @@ public class DeviceContactsOutputStream extends ChunkedOutputStream {
if (contact.getAci().isPresent()) {
contactDetails.aci(contact.getAci().get().toString());
contactDetails.aciBinary(useBinaryId ? contact.getAci().get().toByteString() : null);
}
if (contact.getE164().isPresent()) {

View File

@@ -86,7 +86,7 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) {
/** Parses a ServiceId serialized as a string. Crashes if the ServiceId is invalid. */
@JvmStatic
@Throws(IllegalArgumentException::class)
fun parseOrThrow(raw: String): ServiceId = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid ServiceId!")
fun parseOrThrow(raw: String?): ServiceId = parseOrNull(raw) ?: throw IllegalArgumentException("Invalid ServiceId!")
/** Parses a ServiceId serialized as a byte array. Crashes if the ServiceId is invalid. */
@JvmStatic
@@ -104,6 +104,19 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) {
fun parseOrUnknown(bytes: ByteString): ServiceId {
return parseOrNull(bytes) ?: ACI.UNKNOWN
}
/** Parses a ServiceId serialized as either a byteString or string, with preference to the byteString if available. Returns null if invalid. */
@JvmStatic
fun parseOrNull(raw: String?, bytes: ByteString?): ServiceId? {
return parseOrNull(bytes) ?: parseOrNull(raw)
}
/** Parses a ServiceId serialized as either a byteString or string, with preference to the byteString if available. Throws if invalid. */
@JvmStatic
@Throws(IllegalArgumentException::class)
fun parseOrThrow(raw: String?, bytes: ByteString?): ServiceId {
return parseOrNull(bytes) ?: parseOrThrow(raw)
}
}
val rawUuid: UUID = libSignalServiceId.rawUUID
@@ -144,7 +157,7 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) {
fun parseOrNull(raw: ByteArray?): ACI? = ServiceId.parseOrNull(raw).let { if (it is ACI) it else null }
@JvmStatic
fun parseOrNull(bytes: ByteString): ACI? = parseOrNull(bytes.toByteArray())
fun parseOrNull(bytes: ByteString?): ACI? = parseOrNull(bytes?.toByteArray())
@JvmStatic
@Throws(IllegalArgumentException::class)
@@ -163,6 +176,19 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) {
@JvmStatic
fun parseOrUnknown(raw: String?): ACI = parseOrNull(raw) ?: UNKNOWN
/** Parses either a byteString or string as an ACI, with preference to the byteString if available. Returns null if invalid or missing. */
@JvmStatic
fun parseOrNull(raw: String?, bytes: ByteString?): ACI? {
return parseOrNull(bytes) ?: parseOrNull(raw)
}
/** Parses either a byteString or string as an ACI, with preference to the byteString if available. Throws if invalid or missing. */
@JvmStatic
@Throws(IllegalArgumentException::class)
fun parseOrThrow(raw: String?, bytes: ByteString?): ACI {
return parseOrNull(bytes) ?: parseOrThrow(raw)
}
}
override fun toString(): String = super.toString()
@@ -212,7 +238,7 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) {
/** Parses a [ByteString] as a PNI, regardless if the `PNI:` prefix is present or not. Only use this if you are certain that what you're reading is a PNI. */
@JvmStatic
fun parseOrNull(bytes: ByteString): PNI? = parseOrNull(bytes.toByteArray())
fun parseOrNull(bytes: ByteString?): PNI? = parseOrNull(bytes?.toByteArray())
/** Parses a string as a PNI, regardless if the `PNI:` prefix is present or not. Only use this if you are certain that what you're reading is a PNI. */
@JvmStatic
@@ -231,6 +257,24 @@ sealed class ServiceId(val libSignalServiceId: LibSignalServiceId) {
/** Parses a string as a PNI, expecting that the value has a `PNI:` prefix. If it does not have the prefix (or is otherwise invalid), this will return null. */
fun parsePrefixedOrNull(raw: String?): PNI? = ServiceId.parseOrNull(raw).let { if (it is PNI) it else null }
/** Parses either a byteString or string as a PNI, with preference to the byteString. Expecting that the value has a `PNI:` prefix. If it does not have the prefix (or is otherwise invalid), this will return null. */
fun parsePrefixedOrNull(raw: String?, bytes: ByteString?): PNI? {
return parseOrNull(bytes).let { if (it is PNI) it else null } ?: parsePrefixedOrNull(raw)
}
/** Parses either a byteString or string as a PNI, with preference to the byteString. Only use this if you are certain what you're reading is a PNI. Returns null if invalid. */
@JvmStatic
fun parseOrNull(raw: String?, bytes: ByteString?): PNI? {
return parseOrNull(bytes) ?: parseOrNull(raw)
}
/** Parses either a byteString or string as a PNI, with preference to the byteString. Only use this if you are certain what you're reading is a PNI. Throws if missing or invalid. */
@JvmStatic
@Throws(IllegalArgumentException::class)
fun parseOrThrow(raw: String?, bytes: ByteString?): PNI {
return parseOrNull(bytes) ?: parseOrThrow(raw)
}
}
override fun toString(): String = super.toString()

View File

@@ -8,7 +8,6 @@ package org.whispersystems.signalservice.api.storage
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.signal.core.util.isNotEmpty
import org.signal.core.util.isNotNullOrBlank
import org.whispersystems.signalservice.api.payments.PaymentsConstants
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
@@ -58,6 +57,6 @@ fun AccountRecord.Builder.safeSetBackupsSubscriber(subscriberId: ByteString, iap
}
fun AccountRecord.PinnedConversation.Contact.toSignalServiceAddress(): SignalServiceAddress {
val serviceId = ServiceId.parseOrNull(this.serviceId)
val serviceId = ServiceId.parseOrNull(this.serviceId, this.serviceIdBinary)
return SignalServiceAddress(serviceId, this.e164)
}

View File

@@ -9,7 +9,7 @@ import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
val ContactRecord.signalAci: ServiceId.ACI?
get() = ServiceId.ACI.parseOrNull(this.aci)
get() = ServiceId.ACI.parseOrNull(this.aci, this.aciBinary)
val ContactRecord.signalPni: ServiceId.PNI?
get() = ServiceId.PNI.parseOrNull(this.pni)
get() = ServiceId.PNI.parseOrNull(this.pni, this.pniBinary)

View File

@@ -11,7 +11,10 @@ import org.whispersystems.signalservice.internal.storage.protos.StoryDistributio
val StoryDistributionListRecord.recipientServiceAddresses: List<SignalServiceAddress>
get() {
return this.recipientServiceIds
.mapNotNull { ServiceId.parseOrNull(it) }
.map { SignalServiceAddress(it) }
val serviceIds = if (this.recipientServiceIdsBinary.isNotEmpty()) {
this.recipientServiceIdsBinary.mapNotNull { ServiceId.parseOrNull(it) }
} else {
this.recipientServiceIds.mapNotNull { ServiceId.parseOrNull(it) }
}
return serviceIds.map { SignalServiceAddress(it) }
}

View File

@@ -78,6 +78,11 @@ public final class UuidUtil {
return parseOrNull(bytes.toByteArray());
}
public static @Nullable String getStringUUID(@Nullable String stringId, @Nullable ByteString bytes) {
UUID uuid = parseOrNull(bytes);
return (uuid != null) ? uuid.toString() : stringId;
}
public static UUID fromByteStringOrUnknown(ByteString bytes) {
UUID uuid = fromByteStringOrNull(bytes);
return uuid != null ? uuid : UNKNOWN_UUID;
@@ -88,7 +93,7 @@ public final class UuidUtil {
}
public static UUID parseOrNull(ByteString byteString) {
return parseOrNull(byteString.toByteArray());
return byteString != null ? parseOrNull(byteString.toByteArray()): null;
}
public static List<UUID> fromByteStrings(Collection<ByteString> byteStringCollection) {

View File

@@ -164,4 +164,22 @@ public class Util {
return defaultValue;
}
}
public static boolean anyNotNull(Object... values) {
for (Object value : values) {
if (value != null) {
return true;
}
}
return false;
}
public static boolean allAreNull(Object... values) {
for (Object value : values) {
if (value != null) {
return false;
}
}
return true;
}
}

View File

@@ -40,7 +40,11 @@ message Envelope {
optional bool story = 16; // indicates that the content is a story.
optional bytes report_spam_token = 17; // token sent when reporting spam
reserved 18; // internal server use
// next: 19
optional bytes sourceServiceIdBinary = 19; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
optional bytes destinationServiceIdBinary = 20; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
optional bytes serverGuidBinary = 21; // 16-byte UUID
optional bytes updatedPniBinary = 22; // 16-byte UUID
// next: 22
}
message Content {
@@ -193,6 +197,7 @@ message DataMessage {
repeated QuotedAttachment attachments = 4;
repeated BodyRange bodyRanges = 6;
optional Type type = 7;
optional bytes authorAciBinary = 8; // 16-byte UUID
}
message Contact {
@@ -277,6 +282,7 @@ message DataMessage {
reserved /* targetAuthorE164 */ 3;
optional string targetAuthorAci = 4;
optional uint64 targetSentTimestamp = 5;
optional bytes targetAuthorAciBinary = 6; // 16-byte UUID
}
message Delete {
@@ -290,6 +296,7 @@ message DataMessage {
message StoryContext {
optional string authorAci = 1;
optional uint64 sentTimestamp = 2;
optional bytes authorAciBinary = 3; // 16-byte UUID
}
enum ProtocolVersion {
@@ -442,6 +449,7 @@ message Verified {
optional bytes identityKey = 2;
optional State state = 3;
optional bytes nullMessage = 4;
optional bytes destinationAciBinary = 6; // 16-byte UUID
}
message SyncMessage {
@@ -452,6 +460,7 @@ message SyncMessage {
optional bool unidentified = 2;
reserved /*destinationPni */ 4;
optional bytes destinationPniIdentityKey = 5; // Only set for PNI destinations
optional bytes destinationServiceIdBinary = 6; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
}
message StoryMessageRecipient {
@@ -459,6 +468,7 @@ message SyncMessage {
repeated string distributionListIds = 2;
optional bool isAllowedToReply = 3;
reserved /*destinationPni */ 4;
optional bytes destinationServiceIdBinary = 5; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
}
optional string destinationE164 = 1;
@@ -472,7 +482,8 @@ message SyncMessage {
repeated StoryMessageRecipient storyMessageRecipients = 9;
optional EditMessage editMessage = 10;
reserved /*destinationPni */ 11;
// Next ID: 12
optional bytes destinationServiceIdBinary = 12; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
// Next ID: 13
}
message Contacts {
@@ -484,6 +495,7 @@ message SyncMessage {
repeated string numbers = 1;
repeated string acis = 3;
repeated bytes groupIds = 2;
repeated bytes acisBinary = 4; // 16-byte UUID
}
message Request {
@@ -504,12 +516,14 @@ message SyncMessage {
reserved /*senderE164*/ 1;
optional string senderAci = 3;
optional uint64 timestamp = 2;
optional bytes senderAciBinary = 4; // 16-byte UUID
}
message Viewed {
reserved /*senderE164*/ 1;
optional string senderAci = 3;
optional uint64 timestamp = 2;
optional bytes senderAciBinary = 4; // 16-byte UUID
}
message Configuration {
@@ -536,6 +550,7 @@ message SyncMessage {
reserved /*senderE164*/ 1;
optional string senderAci = 3;
optional uint64 timestamp = 2;
optional bytes senderAciBinary = 4; // 16-byte UUID
}
message FetchLatest {
@@ -576,6 +591,7 @@ message SyncMessage {
optional string threadAci = 2;
optional bytes groupId = 3;
optional Type type = 4;
optional bytes threadAciBinary = 5; // 16-byte UUID
}
message OutgoingPayment {
@@ -823,6 +839,7 @@ message ContactDetails {
optional string number = 1;
optional string aci = 9;
optional bytes aciBinary = 13; // 16-byte UUID
optional string name = 2;
optional Avatar avatar = 3;
reserved /* color */ 4;
@@ -833,7 +850,7 @@ message ContactDetails {
optional uint32 expireTimerVersion = 12;
optional uint32 inboxPosition = 10;
reserved /* archived */ 11;
// NEXT ID: 13
// NEXT ID: 14
}
message PaymentAddress {
@@ -880,6 +897,7 @@ message BodyRange {
oneof associatedValue {
string mentionAci = 3;
Style style = 4;
bytes mentionAciBinary = 5; // 16-byte UUID
}
}
@@ -887,6 +905,7 @@ message AddressableMessage {
oneof author {
string authorServiceId = 1;
string authorE164 = 2;
bytes authorServiceIdBinary = 4; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
}
optional uint64 sentTimestamp = 3;
}
@@ -896,5 +915,6 @@ message ConversationIdentifier {
string threadServiceId = 1;
bytes threadGroupId = 2;
string threadE164 = 3;
bytes threadServiceIdBinary = 4; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
}
}

View File

@@ -140,7 +140,9 @@ message ContactRecord {
Name nickname = 22;
string note = 23;
optional AvatarColor avatarColor = 24;
// Next ID: 25
bytes aciBinary = 25; // 16-byte UUID
bytes pniBinary = 26; // 16-byte UUID
// Next ID: 27
}
message GroupV1Record {
@@ -187,8 +189,9 @@ message AccountRecord {
message PinnedConversation {
message Contact {
string serviceId = 1;
string e164 = 2;
string serviceId = 1;
string e164 = 2;
bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
}
oneof identifier {
@@ -294,12 +297,13 @@ message AccountRecord {
}
message StoryDistributionListRecord {
bytes identifier = 1;
string name = 2;
repeated string recipientServiceIds = 3;
uint64 deletedAtTimestamp = 4;
bool allowsReplies = 5;
bool isBlockList = 6;
bytes identifier = 1;
string name = 2;
repeated string recipientServiceIds = 3;
uint64 deletedAtTimestamp = 4;
bool allowsReplies = 5;
bool isBlockList = 6;
repeated bytes recipientServiceIdsBinary = 7; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
}
message CallLinkRecord {
@@ -311,8 +315,9 @@ message CallLinkRecord {
message Recipient {
message Contact {
string serviceId = 1;
string e164 = 2;
string serviceId = 1;
string e164 = 2;
bytes serviceIdBinary = 3; // service ID binary (i.e. 16 byte UUID for ACI, 1 byte prefix + 16 byte UUID for PNI)
}
oneof identifier {