mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-08 09:18:39 +01:00
Separate message decryption from message processing.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
syntax = "proto2";
|
||||
|
||||
package textsecure;
|
||||
|
||||
import "SignalService.proto";
|
||||
|
||||
option java_package = "org.whispersystems.signalservice.internal.serialize.protos";
|
||||
option java_multiple_files = true;
|
||||
|
||||
message SignalServiceContentProto {
|
||||
optional AddressProto localAddress = 1;
|
||||
optional MetadataProto metadata = 2;
|
||||
oneof data {
|
||||
signalservice.DataMessage legacyDataMessage = 3;
|
||||
signalservice.Content content = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message MetadataProto {
|
||||
optional AddressProto address = 1;
|
||||
optional int32 senderDevice = 2;
|
||||
optional int64 timestamp = 3;
|
||||
optional bool needsReceipt = 4;
|
||||
}
|
||||
|
||||
message AddressProto {
|
||||
optional bytes uuid = 1;
|
||||
optional string e164 = 2;
|
||||
optional string relay = 3;
|
||||
}
|
||||
+33
-639
@@ -6,7 +6,6 @@
|
||||
|
||||
package org.whispersystems.signalservice.api.crypto;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
|
||||
@@ -24,7 +23,6 @@ import org.signal.libsignal.metadata.SealedSessionCipher.DecryptionResult;
|
||||
import org.signal.libsignal.metadata.SelfSendException;
|
||||
import org.signal.libsignal.metadata.certificate.CertificateValidator;
|
||||
import org.whispersystems.libsignal.DuplicateMessageException;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.InvalidKeyIdException;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
@@ -34,65 +32,26 @@ import org.whispersystems.libsignal.NoSessionException;
|
||||
import org.whispersystems.libsignal.SessionCipher;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.UntrustedIdentityException;
|
||||
import org.whispersystems.libsignal.logging.Log;
|
||||
import org.whispersystems.libsignal.protocol.CiphertextMessage;
|
||||
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
|
||||
import org.whispersystems.libsignal.protocol.SignalMessage;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Reaction;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Sticker;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage.VerifiedState;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage;
|
||||
import org.whispersystems.signalservice.internal.push.PushTransportDetails;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope.Type;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.ReceiptMessage;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.TypingMessage;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Verified;
|
||||
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer;
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer;
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.CallMessage;
|
||||
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext.Type.DELIVER;
|
||||
|
||||
/**
|
||||
* This is used to decrypt received {@link SignalServiceEnvelope}s.
|
||||
*
|
||||
@@ -162,52 +121,30 @@ public class SignalServiceCipher {
|
||||
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
|
||||
ProtocolInvalidKeyException, ProtocolDuplicateMessageException,
|
||||
SelfSendException, UnsupportedDataMessageException
|
||||
|
||||
{
|
||||
try {
|
||||
if (envelope.hasLegacyMessage()) {
|
||||
Plaintext plaintext = decrypt(envelope, envelope.getLegacyMessage());
|
||||
DataMessage message = DataMessage.parseFrom(plaintext.getData());
|
||||
return new SignalServiceContent(createSignalServiceMessage(plaintext.getMetadata(), message),
|
||||
plaintext.getMetadata().getSender(),
|
||||
plaintext.getMetadata().getSenderDevice(),
|
||||
plaintext.getMetadata().getTimestamp(),
|
||||
plaintext.getMetadata().isNeedsReceipt());
|
||||
} else if (envelope.hasContent()) {
|
||||
Plaintext plaintext = decrypt(envelope, envelope.getContent());
|
||||
Content message = Content.parseFrom(plaintext.getData());
|
||||
Plaintext plaintext = decrypt(envelope, envelope.getLegacyMessage());
|
||||
SignalServiceProtos.DataMessage dataMessage = SignalServiceProtos.DataMessage.parseFrom(plaintext.getData());
|
||||
|
||||
if (message.hasDataMessage()) {
|
||||
return new SignalServiceContent(createSignalServiceMessage(plaintext.getMetadata(), message.getDataMessage()),
|
||||
plaintext.getMetadata().getSender(),
|
||||
plaintext.getMetadata().getSenderDevice(),
|
||||
plaintext.getMetadata().getTimestamp(),
|
||||
plaintext.getMetadata().isNeedsReceipt());
|
||||
} else if (message.hasSyncMessage() && localAddress.matches(plaintext.getMetadata().getSender())) {
|
||||
return new SignalServiceContent(createSynchronizeMessage(plaintext.getMetadata(), message.getSyncMessage()),
|
||||
plaintext.getMetadata().getSender(),
|
||||
plaintext.getMetadata().getSenderDevice(),
|
||||
plaintext.getMetadata().getTimestamp(),
|
||||
plaintext.getMetadata().isNeedsReceipt());
|
||||
} else if (message.hasCallMessage()) {
|
||||
return new SignalServiceContent(createCallMessage(message.getCallMessage()),
|
||||
plaintext.getMetadata().getSender(),
|
||||
plaintext.getMetadata().getSenderDevice(),
|
||||
plaintext.getMetadata().getTimestamp(),
|
||||
plaintext.getMetadata().isNeedsReceipt());
|
||||
} else if (message.hasReceiptMessage()) {
|
||||
return new SignalServiceContent(createReceiptMessage(plaintext.getMetadata(), message.getReceiptMessage()),
|
||||
plaintext.getMetadata().getSender(),
|
||||
plaintext.getMetadata().getSenderDevice(),
|
||||
plaintext.getMetadata().getTimestamp(),
|
||||
plaintext.getMetadata().isNeedsReceipt());
|
||||
} else if (message.hasTypingMessage()) {
|
||||
return new SignalServiceContent(createTypingMessage(plaintext.getMetadata(), message.getTypingMessage()),
|
||||
plaintext.getMetadata().getSender(),
|
||||
plaintext.getMetadata().getSenderDevice(),
|
||||
plaintext.getMetadata().getTimestamp(),
|
||||
false);
|
||||
}
|
||||
SignalServiceContentProto contentProto = SignalServiceContentProto.newBuilder()
|
||||
.setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress))
|
||||
.setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(plaintext.metadata))
|
||||
.setLegacyDataMessage(dataMessage)
|
||||
.build();
|
||||
|
||||
return SignalServiceContent.createFromProto(contentProto);
|
||||
} else if (envelope.hasContent()) {
|
||||
Plaintext plaintext = decrypt(envelope, envelope.getContent());
|
||||
SignalServiceProtos.Content content = SignalServiceProtos.Content.parseFrom(plaintext.getData());
|
||||
|
||||
SignalServiceContentProto contentProto = SignalServiceContentProto.newBuilder()
|
||||
.setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress))
|
||||
.setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(plaintext.metadata))
|
||||
.setContent(content)
|
||||
.build();
|
||||
|
||||
return SignalServiceContent.createFromProto(contentProto);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -226,9 +163,9 @@ public class SignalServiceCipher {
|
||||
{
|
||||
try {
|
||||
|
||||
byte[] paddedMessage;
|
||||
Metadata metadata;
|
||||
int sessionVersion;
|
||||
byte[] paddedMessage;
|
||||
SignalServiceMetadata metadata;
|
||||
int sessionVersion;
|
||||
|
||||
if (!envelope.hasSource() && !envelope.isUnidentifiedSender()) {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("Non-UD envelope is missing a source!"), null, 0);
|
||||
@@ -239,14 +176,14 @@ public class SignalServiceCipher {
|
||||
SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, sourceAddress);
|
||||
|
||||
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
|
||||
metadata = new Metadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||
sessionVersion = sessionCipher.getSessionVersion();
|
||||
} else if (envelope.isSignalMessage()) {
|
||||
SignalProtocolAddress sourceAddress = getPreferredProtocolAddress(signalProtocolStore, envelope.getSourceAddress(), envelope.getSourceDevice());
|
||||
SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, sourceAddress);
|
||||
|
||||
paddedMessage = sessionCipher.decrypt(new SignalMessage(ciphertext));
|
||||
metadata = new Metadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||
metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
|
||||
sessionVersion = sessionCipher.getSessionVersion();
|
||||
} else if (envelope.isUnidentifiedSender()) {
|
||||
SealedSessionCipher sealedSessionCipher = new SealedSessionCipher(signalProtocolStore, localAddress.getUuid().orNull(), localAddress.getNumber().orNull(), 1);
|
||||
@@ -255,7 +192,7 @@ public class SignalServiceCipher {
|
||||
SignalProtocolAddress protocolAddress = getPreferredProtocolAddress(signalProtocolStore, resultAddress, result.getDeviceId());
|
||||
|
||||
paddedMessage = result.getPaddedMessage();
|
||||
metadata = new Metadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), true);
|
||||
metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), true);
|
||||
sessionVersion = sealedSessionCipher.getSessionVersion(protocolAddress);
|
||||
} else {
|
||||
throw new InvalidMetadataMessageException("Unknown type: " + envelope.getType());
|
||||
@@ -297,558 +234,16 @@ public class SignalServiceCipher {
|
||||
}
|
||||
}
|
||||
|
||||
private SignalServiceDataMessage createSignalServiceMessage(Metadata metadata, DataMessage content)
|
||||
throws ProtocolInvalidMessageException, UnsupportedDataMessageException
|
||||
{
|
||||
SignalServiceGroup groupInfo = createGroupInfo(content);
|
||||
List<SignalServiceAttachment> attachments = new LinkedList<>();
|
||||
boolean endSession = ((content.getFlags() & DataMessage.Flags.END_SESSION_VALUE ) != 0);
|
||||
boolean expirationUpdate = ((content.getFlags() & DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE) != 0);
|
||||
boolean profileKeyUpdate = ((content.getFlags() & DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE ) != 0);
|
||||
SignalServiceDataMessage.Quote quote = createQuote(content);
|
||||
List<SharedContact> sharedContacts = createSharedContacts(content);
|
||||
List<Preview> previews = createPreviews(content);
|
||||
Sticker sticker = createSticker(content);
|
||||
Reaction reaction = createReaction(content);
|
||||
|
||||
if (content.getRequiredProtocolVersion() > DataMessage.ProtocolVersion.CURRENT.getNumber()) {
|
||||
throw new UnsupportedDataMessageException(DataMessage.ProtocolVersion.CURRENT.getNumber(),
|
||||
content.getRequiredProtocolVersion(),
|
||||
metadata.getSender().getIdentifier(),
|
||||
metadata.getSenderDevice(),
|
||||
Optional.fromNullable(groupInfo));
|
||||
}
|
||||
|
||||
for (AttachmentPointer pointer : content.getAttachmentsList()) {
|
||||
attachments.add(createAttachmentPointer(pointer));
|
||||
}
|
||||
|
||||
if (content.hasTimestamp() && content.getTimestamp() != metadata.getTimestamp()) {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("Timestamps don't match: " + content.getTimestamp() + " vs " + metadata.getTimestamp()),
|
||||
metadata.getSender().getIdentifier(),
|
||||
metadata.getSenderDevice());
|
||||
}
|
||||
|
||||
return new SignalServiceDataMessage(metadata.getTimestamp(),
|
||||
groupInfo,
|
||||
attachments,
|
||||
content.getBody(),
|
||||
endSession,
|
||||
content.getExpireTimer(),
|
||||
expirationUpdate,
|
||||
content.hasProfileKey() ? content.getProfileKey().toByteArray() : null,
|
||||
profileKeyUpdate,
|
||||
quote,
|
||||
sharedContacts,
|
||||
previews,
|
||||
sticker,
|
||||
content.getIsViewOnce(),
|
||||
reaction);
|
||||
}
|
||||
|
||||
private SignalServiceSyncMessage createSynchronizeMessage(Metadata metadata, SyncMessage content)
|
||||
throws ProtocolInvalidMessageException, ProtocolInvalidKeyException, UnsupportedDataMessageException
|
||||
{
|
||||
if (content.hasSent()) {
|
||||
SyncMessage.Sent sentContent = content.getSent();
|
||||
SignalServiceDataMessage dataMessage = createSignalServiceMessage(metadata, sentContent.getMessage());
|
||||
Optional<SignalServiceAddress> address = SignalServiceAddress.isValidAddress(sentContent.getDestinationUuid(), sentContent.getDestinationE164())
|
||||
? Optional.of(new SignalServiceAddress(UuidUtil.parseOrNull(sentContent.getDestinationUuid()), sentContent.getDestinationE164()))
|
||||
: Optional.<SignalServiceAddress>absent();
|
||||
Map<SignalServiceAddress, Boolean> unidentifiedStatuses = new HashMap<>();
|
||||
|
||||
if (!address.isPresent() && !dataMessage.getGroupInfo().isPresent()) {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("SyncMessage missing both destination and group ID!"), null, 0);
|
||||
}
|
||||
|
||||
for (SyncMessage.Sent.UnidentifiedDeliveryStatus status : sentContent.getUnidentifiedStatusList()) {
|
||||
if (SignalServiceAddress.isValidAddress(status.getDestinationUuid(), status.getDestinationE164())) {
|
||||
SignalServiceAddress recipient = new SignalServiceAddress(UuidUtil.parseOrNull(status.getDestinationUuid()), status.getDestinationE164());
|
||||
unidentifiedStatuses.put(recipient, status.getUnidentified());
|
||||
} else {
|
||||
Log.w(TAG, "Encountered an invalid UnidentifiedDeliveryStatus in a SentTranscript! Ignoring.");
|
||||
}
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forSentTranscript(new SentTranscriptMessage(address,
|
||||
sentContent.getTimestamp(),
|
||||
dataMessage,
|
||||
sentContent.getExpirationStartTimestamp(),
|
||||
unidentifiedStatuses,
|
||||
sentContent.getIsRecipientUpdate()));
|
||||
}
|
||||
|
||||
if (content.hasRequest()) {
|
||||
return SignalServiceSyncMessage.forRequest(new RequestMessage(content.getRequest()));
|
||||
}
|
||||
|
||||
if (content.getReadList().size() > 0) {
|
||||
List<ReadMessage> readMessages = new LinkedList<>();
|
||||
|
||||
for (SyncMessage.Read read : content.getReadList()) {
|
||||
if (SignalServiceAddress.isValidAddress(read.getSenderUuid(), read.getSenderE164())) {
|
||||
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(read.getSenderUuid()), read.getSenderE164());
|
||||
readMessages.add(new ReadMessage(address, read.getTimestamp()));
|
||||
} else {
|
||||
Log.w(TAG, "Encountered an invalid ReadMessage! Ignoring.");
|
||||
}
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forRead(readMessages);
|
||||
}
|
||||
|
||||
if (content.hasViewOnceOpen()) {
|
||||
if (SignalServiceAddress.isValidAddress(content.getViewOnceOpen().getSenderUuid(), content.getViewOnceOpen().getSenderE164())) {
|
||||
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(content.getViewOnceOpen().getSenderUuid()), content.getViewOnceOpen().getSenderE164());
|
||||
ViewOnceOpenMessage timerRead = new ViewOnceOpenMessage(address, content.getViewOnceOpen().getTimestamp());
|
||||
return SignalServiceSyncMessage.forViewOnceOpen(timerRead);
|
||||
} else {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("ViewOnceOpen message has no sender!"), null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.hasVerified()) {
|
||||
if (SignalServiceAddress.isValidAddress(content.getVerified().getDestinationUuid(), content.getVerified().getDestinationE164())) {
|
||||
try {
|
||||
Verified verified = content.getVerified();
|
||||
SignalServiceAddress destination = new SignalServiceAddress(UuidUtil.parseOrNull(verified.getDestinationUuid()), verified.getDestinationE164());
|
||||
IdentityKey identityKey = new IdentityKey(verified.getIdentityKey().toByteArray(), 0);
|
||||
|
||||
VerifiedState verifiedState;
|
||||
|
||||
if (verified.getState() == Verified.State.DEFAULT) {
|
||||
verifiedState = VerifiedState.DEFAULT;
|
||||
} else if (verified.getState() == Verified.State.VERIFIED) {
|
||||
verifiedState = VerifiedState.VERIFIED;
|
||||
} else if (verified.getState() == Verified.State.UNVERIFIED) {
|
||||
verifiedState = VerifiedState.UNVERIFIED;
|
||||
} else {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("Unknown state: " + verified.getState().getNumber()),
|
||||
metadata.getSender().getIdentifier(), metadata.getSenderDevice());
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forVerified(new VerifiedMessage(destination, identityKey, verifiedState, System.currentTimeMillis()));
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new ProtocolInvalidKeyException(e, metadata.getSender().getIdentifier(), metadata.getSenderDevice());
|
||||
}
|
||||
} else {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("Verified message has no sender!"), null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.getStickerPackOperationList().size() > 0) {
|
||||
List<StickerPackOperationMessage> operations = new LinkedList<>();
|
||||
|
||||
for (SyncMessage.StickerPackOperation operation : content.getStickerPackOperationList()) {
|
||||
byte[] packId = operation.hasPackId() ? operation.getPackId().toByteArray() : null;
|
||||
byte[] packKey = operation.hasPackKey() ? operation.getPackKey().toByteArray() : null;
|
||||
StickerPackOperationMessage.Type type = null;
|
||||
|
||||
if (operation.hasType()) {
|
||||
switch (operation.getType()) {
|
||||
case INSTALL: type = StickerPackOperationMessage.Type.INSTALL; break;
|
||||
case REMOVE: type = StickerPackOperationMessage.Type.REMOVE; break;
|
||||
}
|
||||
}
|
||||
operations.add(new StickerPackOperationMessage(packId, packKey, type));
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forStickerPackOperations(operations);
|
||||
}
|
||||
|
||||
if (content.hasBlocked()) {
|
||||
List<String> numbers = content.getBlocked().getNumbersList();
|
||||
List<String> uuids = content.getBlocked().getUuidsList();
|
||||
List<SignalServiceAddress> addresses = new ArrayList<>(numbers.size() + uuids.size());
|
||||
List<byte[]> groupIds = new ArrayList<>(content.getBlocked().getGroupIdsList().size());
|
||||
|
||||
for (String e164 : numbers) {
|
||||
Optional<SignalServiceAddress> address = SignalServiceAddress.fromRaw(null, e164);
|
||||
if (address.isPresent()) {
|
||||
addresses.add(address.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (String uuid : uuids) {
|
||||
Optional<SignalServiceAddress> address = SignalServiceAddress.fromRaw(uuid, null);
|
||||
if (address.isPresent()) {
|
||||
addresses.add(address.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (ByteString groupId : content.getBlocked().getGroupIdsList()) {
|
||||
groupIds.add(groupId.toByteArray());
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds));
|
||||
}
|
||||
|
||||
if (content.hasConfiguration()) {
|
||||
Boolean readReceipts = content.getConfiguration().hasReadReceipts() ? content.getConfiguration().getReadReceipts() : null;
|
||||
Boolean unidentifiedDeliveryIndicators = content.getConfiguration().hasUnidentifiedDeliveryIndicators() ? content.getConfiguration().getUnidentifiedDeliveryIndicators() : null;
|
||||
Boolean typingIndicators = content.getConfiguration().hasTypingIndicators() ? content.getConfiguration().getTypingIndicators() : null;
|
||||
Boolean linkPreviews = content.getConfiguration().hasLinkPreviews() ? content.getConfiguration().getLinkPreviews() : null;
|
||||
|
||||
return SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.fromNullable(readReceipts),
|
||||
Optional.fromNullable(unidentifiedDeliveryIndicators),
|
||||
Optional.fromNullable(typingIndicators),
|
||||
Optional.fromNullable(linkPreviews)));
|
||||
}
|
||||
|
||||
if (content.hasFetchLatest() && content.getFetchLatest().hasType()) {
|
||||
switch (content.getFetchLatest().getType()) {
|
||||
case LOCAL_PROFILE: return SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE);
|
||||
case STORAGE_MANIFEST: return SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST);
|
||||
}
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.empty();
|
||||
}
|
||||
|
||||
private SignalServiceCallMessage createCallMessage(CallMessage content) {
|
||||
if (content.hasOffer()) {
|
||||
CallMessage.Offer offerContent = content.getOffer();
|
||||
return SignalServiceCallMessage.forOffer(new OfferMessage(offerContent.getId(), offerContent.getDescription()));
|
||||
} else if (content.hasAnswer()) {
|
||||
CallMessage.Answer answerContent = content.getAnswer();
|
||||
return SignalServiceCallMessage.forAnswer(new AnswerMessage(answerContent.getId(), answerContent.getDescription()));
|
||||
} else if (content.getIceUpdateCount() > 0) {
|
||||
List<IceUpdateMessage> iceUpdates = new LinkedList<>();
|
||||
|
||||
for (CallMessage.IceUpdate iceUpdate : content.getIceUpdateList()) {
|
||||
iceUpdates.add(new IceUpdateMessage(iceUpdate.getId(), iceUpdate.getSdpMid(), iceUpdate.getSdpMLineIndex(), iceUpdate.getSdp()));
|
||||
}
|
||||
|
||||
return SignalServiceCallMessage.forIceUpdates(iceUpdates);
|
||||
} else if (content.hasHangup()) {
|
||||
CallMessage.Hangup hangup = content.getHangup();
|
||||
return SignalServiceCallMessage.forHangup(new HangupMessage(hangup.getId()));
|
||||
} else if (content.hasBusy()) {
|
||||
CallMessage.Busy busy = content.getBusy();
|
||||
return SignalServiceCallMessage.forBusy(new BusyMessage(busy.getId()));
|
||||
}
|
||||
|
||||
return SignalServiceCallMessage.empty();
|
||||
}
|
||||
|
||||
private SignalServiceReceiptMessage createReceiptMessage(Metadata metadata, ReceiptMessage content) {
|
||||
SignalServiceReceiptMessage.Type type;
|
||||
|
||||
if (content.getType() == ReceiptMessage.Type.DELIVERY) type = SignalServiceReceiptMessage.Type.DELIVERY;
|
||||
else if (content.getType() == ReceiptMessage.Type.READ) type = SignalServiceReceiptMessage.Type.READ;
|
||||
else type = SignalServiceReceiptMessage.Type.UNKNOWN;
|
||||
|
||||
return new SignalServiceReceiptMessage(type, content.getTimestampList(), metadata.getTimestamp());
|
||||
}
|
||||
|
||||
private SignalServiceTypingMessage createTypingMessage(Metadata metadata, TypingMessage content) throws ProtocolInvalidMessageException {
|
||||
SignalServiceTypingMessage.Action action;
|
||||
|
||||
if (content.getAction() == TypingMessage.Action.STARTED) action = SignalServiceTypingMessage.Action.STARTED;
|
||||
else if (content.getAction() == TypingMessage.Action.STOPPED) action = SignalServiceTypingMessage.Action.STOPPED;
|
||||
else action = SignalServiceTypingMessage.Action.UNKNOWN;
|
||||
|
||||
if (content.hasTimestamp() && content.getTimestamp() != metadata.getTimestamp()) {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("Timestamps don't match: " + content.getTimestamp() + " vs " + metadata.getTimestamp()),
|
||||
metadata.getSender().getIdentifier(),
|
||||
metadata.getSenderDevice());
|
||||
}
|
||||
|
||||
return new SignalServiceTypingMessage(action, content.getTimestamp(),
|
||||
content.hasGroupId() ? Optional.of(content.getGroupId().toByteArray()) :
|
||||
Optional.<byte[]>absent());
|
||||
}
|
||||
|
||||
private SignalServiceDataMessage.Quote createQuote(DataMessage content) {
|
||||
if (!content.hasQuote()) return null;
|
||||
|
||||
List<SignalServiceDataMessage.Quote.QuotedAttachment> attachments = new LinkedList<>();
|
||||
|
||||
for (DataMessage.Quote.QuotedAttachment attachment : content.getQuote().getAttachmentsList()) {
|
||||
attachments.add(new SignalServiceDataMessage.Quote.QuotedAttachment(attachment.getContentType(),
|
||||
attachment.getFileName(),
|
||||
attachment.hasThumbnail() ? createAttachmentPointer(attachment.getThumbnail()) : null));
|
||||
}
|
||||
|
||||
if (SignalServiceAddress.isValidAddress(content.getQuote().getAuthorUuid(), content.getQuote().getAuthorE164())) {
|
||||
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(content.getQuote().getAuthorUuid()), content.getQuote().getAuthorE164());
|
||||
|
||||
return new SignalServiceDataMessage.Quote(content.getQuote().getId(),
|
||||
address,
|
||||
content.getQuote().getText(),
|
||||
attachments);
|
||||
} else {
|
||||
Log.w(TAG, "Quote was missing an author! Returning null.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Preview> createPreviews(DataMessage content) {
|
||||
if (content.getPreviewCount() <= 0) return null;
|
||||
|
||||
List<Preview> results = new LinkedList<>();
|
||||
|
||||
for (DataMessage.Preview preview : content.getPreviewList()) {
|
||||
SignalServiceAttachment attachment = null;
|
||||
|
||||
if (preview.hasImage()) {
|
||||
attachment = createAttachmentPointer(preview.getImage());
|
||||
}
|
||||
|
||||
results.add(new Preview(preview.getUrl(),
|
||||
preview.getTitle(),
|
||||
Optional.fromNullable(attachment)));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private Sticker createSticker(DataMessage content) {
|
||||
if (!content.hasSticker() ||
|
||||
!content.getSticker().hasPackId() ||
|
||||
!content.getSticker().hasPackKey() ||
|
||||
!content.getSticker().hasStickerId() ||
|
||||
!content.getSticker().hasData())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DataMessage.Sticker sticker = content.getSticker();
|
||||
|
||||
return new Sticker(sticker.getPackId().toByteArray(),
|
||||
sticker.getPackKey().toByteArray(),
|
||||
sticker.getStickerId(),
|
||||
createAttachmentPointer(sticker.getData()));
|
||||
}
|
||||
|
||||
private Reaction createReaction(DataMessage content) {
|
||||
if (!content.hasReaction() ||
|
||||
!content.getReaction().hasEmoji() ||
|
||||
!(content.getReaction().hasTargetAuthorE164() || content.getReaction().hasTargetAuthorUuid()) ||
|
||||
!content.getReaction().hasTargetSentTimestamp())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DataMessage.Reaction reaction = content.getReaction();
|
||||
|
||||
return new Reaction(reaction.getEmoji(),
|
||||
reaction.getRemove(),
|
||||
new SignalServiceAddress(UuidUtil.parseOrNull(reaction.getTargetAuthorUuid()), reaction.getTargetAuthorE164()),
|
||||
reaction.getTargetSentTimestamp());
|
||||
}
|
||||
|
||||
private List<SharedContact> createSharedContacts(DataMessage content) {
|
||||
if (content.getContactCount() <= 0) return null;
|
||||
|
||||
List<SharedContact> results = new LinkedList<>();
|
||||
|
||||
for (DataMessage.Contact contact : content.getContactList()) {
|
||||
SharedContact.Builder builder = SharedContact.newBuilder()
|
||||
.setName(SharedContact.Name.newBuilder()
|
||||
.setDisplay(contact.getName().getDisplayName())
|
||||
.setFamily(contact.getName().getFamilyName())
|
||||
.setGiven(contact.getName().getGivenName())
|
||||
.setMiddle(contact.getName().getMiddleName())
|
||||
.setPrefix(contact.getName().getPrefix())
|
||||
.setSuffix(contact.getName().getSuffix())
|
||||
.build());
|
||||
|
||||
if (contact.getAddressCount() > 0) {
|
||||
for (DataMessage.Contact.PostalAddress address : contact.getAddressList()) {
|
||||
SharedContact.PostalAddress.Type type = SharedContact.PostalAddress.Type.HOME;
|
||||
|
||||
switch (address.getType()) {
|
||||
case WORK: type = SharedContact.PostalAddress.Type.WORK; break;
|
||||
case HOME: type = SharedContact.PostalAddress.Type.HOME; break;
|
||||
case CUSTOM: type = SharedContact.PostalAddress.Type.CUSTOM; break;
|
||||
}
|
||||
|
||||
builder.withAddress(SharedContact.PostalAddress.newBuilder()
|
||||
.setCity(address.getCity())
|
||||
.setCountry(address.getCountry())
|
||||
.setLabel(address.getLabel())
|
||||
.setNeighborhood(address.getNeighborhood())
|
||||
.setPobox(address.getPobox())
|
||||
.setPostcode(address.getPostcode())
|
||||
.setRegion(address.getRegion())
|
||||
.setStreet(address.getStreet())
|
||||
.setType(type)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
if (contact.getNumberCount() > 0) {
|
||||
for (DataMessage.Contact.Phone phone : contact.getNumberList()) {
|
||||
SharedContact.Phone.Type type = SharedContact.Phone.Type.HOME;
|
||||
|
||||
switch (phone.getType()) {
|
||||
case HOME: type = SharedContact.Phone.Type.HOME; break;
|
||||
case WORK: type = SharedContact.Phone.Type.WORK; break;
|
||||
case MOBILE: type = SharedContact.Phone.Type.MOBILE; break;
|
||||
case CUSTOM: type = SharedContact.Phone.Type.CUSTOM; break;
|
||||
}
|
||||
|
||||
builder.withPhone(SharedContact.Phone.newBuilder()
|
||||
.setLabel(phone.getLabel())
|
||||
.setType(type)
|
||||
.setValue(phone.getValue())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
if (contact.getEmailCount() > 0) {
|
||||
for (DataMessage.Contact.Email email : contact.getEmailList()) {
|
||||
SharedContact.Email.Type type = SharedContact.Email.Type.HOME;
|
||||
|
||||
switch (email.getType()) {
|
||||
case HOME: type = SharedContact.Email.Type.HOME; break;
|
||||
case WORK: type = SharedContact.Email.Type.WORK; break;
|
||||
case MOBILE: type = SharedContact.Email.Type.MOBILE; break;
|
||||
case CUSTOM: type = SharedContact.Email.Type.CUSTOM; break;
|
||||
}
|
||||
|
||||
builder.withEmail(SharedContact.Email.newBuilder()
|
||||
.setLabel(email.getLabel())
|
||||
.setType(type)
|
||||
.setValue(email.getValue())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
if (contact.hasAvatar()) {
|
||||
builder.setAvatar(SharedContact.Avatar.newBuilder()
|
||||
.withAttachment(createAttachmentPointer(contact.getAvatar().getAvatar()))
|
||||
.withProfileFlag(contact.getAvatar().getIsProfile())
|
||||
.build());
|
||||
}
|
||||
|
||||
if (contact.hasOrganization()) {
|
||||
builder.withOrganization(contact.getOrganization());
|
||||
}
|
||||
|
||||
results.add(builder.build());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private SignalServiceAttachmentPointer createAttachmentPointer(AttachmentPointer pointer) {
|
||||
return new SignalServiceAttachmentPointer(pointer.getId(),
|
||||
pointer.getContentType(),
|
||||
pointer.getKey().toByteArray(),
|
||||
pointer.hasSize() ? Optional.of(pointer.getSize()) : Optional.<Integer>absent(),
|
||||
pointer.hasThumbnail() ? Optional.of(pointer.getThumbnail().toByteArray()): Optional.<byte[]>absent(),
|
||||
pointer.getWidth(), pointer.getHeight(),
|
||||
pointer.hasDigest() ? Optional.of(pointer.getDigest().toByteArray()) : Optional.<byte[]>absent(),
|
||||
pointer.hasFileName() ? Optional.of(pointer.getFileName()) : Optional.<String>absent(),
|
||||
(pointer.getFlags() & AttachmentPointer.Flags.VOICE_MESSAGE_VALUE) != 0,
|
||||
pointer.hasCaption() ? Optional.of(pointer.getCaption()) : Optional.<String>absent(),
|
||||
pointer.hasBlurHash() ? Optional.of(pointer.getBlurHash()) : Optional.<String>absent());
|
||||
|
||||
}
|
||||
|
||||
private SignalServiceGroup createGroupInfo(DataMessage content) throws ProtocolInvalidMessageException {
|
||||
if (!content.hasGroup()) return null;
|
||||
|
||||
SignalServiceGroup.Type type;
|
||||
|
||||
switch (content.getGroup().getType()) {
|
||||
case DELIVER: type = SignalServiceGroup.Type.DELIVER; break;
|
||||
case UPDATE: type = SignalServiceGroup.Type.UPDATE; break;
|
||||
case QUIT: type = SignalServiceGroup.Type.QUIT; break;
|
||||
case REQUEST_INFO: type = SignalServiceGroup.Type.REQUEST_INFO; break;
|
||||
default: type = SignalServiceGroup.Type.UNKNOWN; break;
|
||||
}
|
||||
|
||||
if (content.getGroup().getType() != DELIVER) {
|
||||
String name = null;
|
||||
List<SignalServiceAddress> members = null;
|
||||
SignalServiceAttachmentPointer avatar = null;
|
||||
|
||||
if (content.getGroup().hasName()) {
|
||||
name = content.getGroup().getName();
|
||||
}
|
||||
|
||||
if (content.getGroup().getMembersCount() > 0) {
|
||||
members = new ArrayList<>(content.getGroup().getMembersCount());
|
||||
|
||||
for (SignalServiceProtos.GroupContext.Member member : content.getGroup().getMembersList()) {
|
||||
if (SignalServiceAddress.isValidAddress(member.getUuid(), member.getE164())) {
|
||||
members.add(new SignalServiceAddress(UuidUtil.parseOrNull(member.getUuid()), member.getE164()));
|
||||
} else {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("GroupContext.Member had no address!"), null, 0);
|
||||
}
|
||||
}
|
||||
} else if (content.getGroup().getMembersE164Count() > 0) {
|
||||
members = new ArrayList<>(content.getGroup().getMembersE164Count());
|
||||
|
||||
for (String member : content.getGroup().getMembersE164List()) {
|
||||
members.add(new SignalServiceAddress(null, member));
|
||||
}
|
||||
}
|
||||
|
||||
if (content.getGroup().hasAvatar()) {
|
||||
AttachmentPointer pointer = content.getGroup().getAvatar();
|
||||
|
||||
avatar = new SignalServiceAttachmentPointer(pointer.getId(),
|
||||
pointer.getContentType(),
|
||||
pointer.getKey().toByteArray(),
|
||||
Optional.of(pointer.getSize()),
|
||||
Optional.<byte[]>absent(), 0, 0,
|
||||
Optional.fromNullable(pointer.hasDigest() ? pointer.getDigest().toByteArray() : null),
|
||||
Optional.<String>absent(),
|
||||
false,
|
||||
Optional.<String>absent(),
|
||||
Optional.<String>absent());
|
||||
}
|
||||
|
||||
return new SignalServiceGroup(type, content.getGroup().getId().toByteArray(), name, members, avatar);
|
||||
}
|
||||
|
||||
return new SignalServiceGroup(content.getGroup().getId().toByteArray());
|
||||
}
|
||||
|
||||
private static class Metadata {
|
||||
private final SignalServiceAddress sender;
|
||||
private final int senderDevice;
|
||||
private final long timestamp;
|
||||
private final boolean needsReceipt;
|
||||
|
||||
private Metadata(SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
}
|
||||
|
||||
public SignalServiceAddress getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public int getSenderDevice() {
|
||||
return senderDevice;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public boolean isNeedsReceipt() {
|
||||
return needsReceipt;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Plaintext {
|
||||
private final Metadata metadata;
|
||||
private final SignalServiceMetadata metadata;
|
||||
private final byte[] data;
|
||||
|
||||
private Plaintext(Metadata metadata, byte[] data) {
|
||||
private Plaintext(SignalServiceMetadata metadata, byte[] data) {
|
||||
this.metadata = metadata;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Metadata getMetadata() {
|
||||
public SignalServiceMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@@ -858,4 +253,3 @@ public class SignalServiceCipher {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+666
-30
@@ -6,17 +6,57 @@
|
||||
|
||||
package org.whispersystems.signalservice.api.messages;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.InvalidMessageException;
|
||||
import org.whispersystems.libsignal.logging.Log;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage;
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
|
||||
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer;
|
||||
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer;
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto;
|
||||
|
||||
public class SignalServiceContent {
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
private final SignalServiceAddress sender;
|
||||
private final int senderDevice;
|
||||
private final long timestamp;
|
||||
private final boolean needsReceipt;
|
||||
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext.Type.DELIVER;
|
||||
|
||||
public final class SignalServiceContent {
|
||||
|
||||
private static final String TAG = SignalServiceContent.class.getSimpleName();
|
||||
|
||||
private final SignalServiceAddress sender;
|
||||
private final int senderDevice;
|
||||
private final long timestamp;
|
||||
private final boolean needsReceipt;
|
||||
private final SignalServiceContentProto serializedState;
|
||||
|
||||
private final Optional<SignalServiceDataMessage> message;
|
||||
private final Optional<SignalServiceSyncMessage> synchronizeMessage;
|
||||
@@ -24,11 +64,12 @@ public class SignalServiceContent {
|
||||
private final Optional<SignalServiceReceiptMessage> readMessage;
|
||||
private final Optional<SignalServiceTypingMessage> typingMessage;
|
||||
|
||||
public SignalServiceContent(SignalServiceDataMessage message, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
private SignalServiceContent(SignalServiceDataMessage message, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt, SignalServiceContentProto serializedState) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
this.serializedState = serializedState;
|
||||
|
||||
this.message = Optional.fromNullable(message);
|
||||
this.synchronizeMessage = Optional.absent();
|
||||
@@ -37,11 +78,12 @@ public class SignalServiceContent {
|
||||
this.typingMessage = Optional.absent();
|
||||
}
|
||||
|
||||
public SignalServiceContent(SignalServiceSyncMessage synchronizeMessage, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
private SignalServiceContent(SignalServiceSyncMessage synchronizeMessage, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt, SignalServiceContentProto serializedState) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
this.serializedState = serializedState;
|
||||
|
||||
this.message = Optional.absent();
|
||||
this.synchronizeMessage = Optional.fromNullable(synchronizeMessage);
|
||||
@@ -50,11 +92,12 @@ public class SignalServiceContent {
|
||||
this.typingMessage = Optional.absent();
|
||||
}
|
||||
|
||||
public SignalServiceContent(SignalServiceCallMessage callMessage, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
private SignalServiceContent(SignalServiceCallMessage callMessage, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt, SignalServiceContentProto serializedState) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
this.serializedState = serializedState;
|
||||
|
||||
this.message = Optional.absent();
|
||||
this.synchronizeMessage = Optional.absent();
|
||||
@@ -63,11 +106,12 @@ public class SignalServiceContent {
|
||||
this.typingMessage = Optional.absent();
|
||||
}
|
||||
|
||||
public SignalServiceContent(SignalServiceReceiptMessage receiptMessage, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
private SignalServiceContent(SignalServiceReceiptMessage receiptMessage, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt, SignalServiceContentProto serializedState) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
this.serializedState = serializedState;
|
||||
|
||||
this.message = Optional.absent();
|
||||
this.synchronizeMessage = Optional.absent();
|
||||
@@ -76,11 +120,12 @@ public class SignalServiceContent {
|
||||
this.typingMessage = Optional.absent();
|
||||
}
|
||||
|
||||
public SignalServiceContent(SignalServiceTypingMessage typingMessage, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
private SignalServiceContent(SignalServiceTypingMessage typingMessage, SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt, SignalServiceContentProto serializedState) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
this.serializedState = serializedState;
|
||||
|
||||
this.message = Optional.absent();
|
||||
this.synchronizeMessage = Optional.absent();
|
||||
@@ -124,4 +169,595 @@ public class SignalServiceContent {
|
||||
public boolean isNeedsReceipt() {
|
||||
return needsReceipt;
|
||||
}
|
||||
|
||||
public byte[] serialize() {
|
||||
return serializedState.toByteArray();
|
||||
}
|
||||
|
||||
public static SignalServiceContent deserialize(byte[] data) {
|
||||
try {
|
||||
if (data == null) return null;
|
||||
|
||||
SignalServiceContentProto signalServiceContentProto = SignalServiceContentProto.parseFrom(data);
|
||||
|
||||
return createFromProto(signalServiceContentProto);
|
||||
} catch (InvalidProtocolBufferException | ProtocolInvalidMessageException | ProtocolInvalidKeyException | UnsupportedDataMessageException e) {
|
||||
// We do not expect any of these exceptions if this byte[] has come from serialize.
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes internal protobuf serialization format and processes it into a {@link SignalServiceContent}.
|
||||
*/
|
||||
public static SignalServiceContent createFromProto(SignalServiceContentProto serviceContentProto)
|
||||
throws ProtocolInvalidMessageException, ProtocolInvalidKeyException, UnsupportedDataMessageException
|
||||
{
|
||||
SignalServiceMetadata metadata = SignalServiceMetadataProtobufSerializer.fromProtobuf(serviceContentProto.getMetadata());
|
||||
SignalServiceAddress localAddress = SignalServiceAddressProtobufSerializer.fromProtobuf(serviceContentProto.getLocalAddress());
|
||||
|
||||
if (serviceContentProto.getDataCase() == SignalServiceContentProto.DataCase.LEGACYDATAMESSAGE) {
|
||||
SignalServiceProtos.DataMessage message = serviceContentProto.getLegacyDataMessage();
|
||||
|
||||
return new SignalServiceContent(createSignalServiceMessage(metadata, message),
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
metadata.isNeedsReceipt(),
|
||||
serviceContentProto);
|
||||
} else if (serviceContentProto.getDataCase() == SignalServiceContentProto.DataCase.CONTENT) {
|
||||
SignalServiceProtos.Content message = serviceContentProto.getContent();
|
||||
|
||||
if (message.hasDataMessage()) {
|
||||
return new SignalServiceContent(createSignalServiceMessage(metadata, message.getDataMessage()),
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
metadata.isNeedsReceipt(),
|
||||
serviceContentProto);
|
||||
} else if (message.hasSyncMessage() && localAddress.matches(metadata.getSender())) {
|
||||
return new SignalServiceContent(createSynchronizeMessage(metadata, message.getSyncMessage()),
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
metadata.isNeedsReceipt(),
|
||||
serviceContentProto);
|
||||
} else if (message.hasCallMessage()) {
|
||||
return new SignalServiceContent(createCallMessage(message.getCallMessage()),
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
metadata.isNeedsReceipt(),
|
||||
serviceContentProto);
|
||||
} else if (message.hasReceiptMessage()) {
|
||||
return new SignalServiceContent(createReceiptMessage(metadata, message.getReceiptMessage()),
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
metadata.isNeedsReceipt(),
|
||||
serviceContentProto);
|
||||
} else if (message.hasTypingMessage()) {
|
||||
return new SignalServiceContent(createTypingMessage(metadata, message.getTypingMessage()),
|
||||
metadata.getSender(),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
false,
|
||||
serviceContentProto);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static SignalServiceDataMessage createSignalServiceMessage(SignalServiceMetadata metadata, SignalServiceProtos.DataMessage content)
|
||||
throws ProtocolInvalidMessageException, UnsupportedDataMessageException
|
||||
{
|
||||
SignalServiceGroup groupInfo = createGroupInfo(content);
|
||||
List<SignalServiceAttachment> attachments = new LinkedList<>();
|
||||
boolean endSession = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.END_SESSION_VALUE ) != 0);
|
||||
boolean expirationUpdate = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE) != 0);
|
||||
boolean profileKeyUpdate = ((content.getFlags() & SignalServiceProtos.DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE ) != 0);
|
||||
SignalServiceDataMessage.Quote quote = createQuote(content);
|
||||
List<SharedContact> sharedContacts = createSharedContacts(content);
|
||||
List<SignalServiceDataMessage.Preview> previews = createPreviews(content);
|
||||
SignalServiceDataMessage.Sticker sticker = createSticker(content);
|
||||
SignalServiceDataMessage.Reaction reaction = createReaction(content);
|
||||
|
||||
if (content.getRequiredProtocolVersion() > SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT.getNumber()) {
|
||||
throw new UnsupportedDataMessageException(SignalServiceProtos.DataMessage.ProtocolVersion.CURRENT.getNumber(),
|
||||
content.getRequiredProtocolVersion(),
|
||||
metadata.getSender().getIdentifier(),
|
||||
metadata.getSenderDevice(),
|
||||
Optional.fromNullable(groupInfo));
|
||||
}
|
||||
|
||||
for (SignalServiceProtos.AttachmentPointer pointer : content.getAttachmentsList()) {
|
||||
attachments.add(createAttachmentPointer(pointer));
|
||||
}
|
||||
|
||||
if (content.hasTimestamp() && content.getTimestamp() != metadata.getTimestamp()) {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("Timestamps don't match: " + content.getTimestamp() + " vs " + metadata.getTimestamp()),
|
||||
metadata.getSender().getIdentifier(),
|
||||
metadata.getSenderDevice());
|
||||
}
|
||||
|
||||
return new SignalServiceDataMessage(metadata.getTimestamp(),
|
||||
groupInfo,
|
||||
attachments,
|
||||
content.getBody(),
|
||||
endSession,
|
||||
content.getExpireTimer(),
|
||||
expirationUpdate,
|
||||
content.hasProfileKey() ? content.getProfileKey().toByteArray() : null,
|
||||
profileKeyUpdate,
|
||||
quote,
|
||||
sharedContacts,
|
||||
previews,
|
||||
sticker,
|
||||
content.getIsViewOnce(),
|
||||
reaction);
|
||||
}
|
||||
|
||||
private static SignalServiceSyncMessage createSynchronizeMessage(SignalServiceMetadata metadata, SignalServiceProtos.SyncMessage content)
|
||||
throws ProtocolInvalidMessageException, ProtocolInvalidKeyException, UnsupportedDataMessageException
|
||||
{
|
||||
if (content.hasSent()) {
|
||||
Map<SignalServiceAddress, Boolean> unidentifiedStatuses = new HashMap<>();
|
||||
SignalServiceProtos.SyncMessage.Sent sentContent = content.getSent();
|
||||
SignalServiceDataMessage dataMessage = createSignalServiceMessage(metadata, sentContent.getMessage());
|
||||
Optional<SignalServiceAddress> address = SignalServiceAddress.isValidAddress(sentContent.getDestinationUuid(), sentContent.getDestinationE164())
|
||||
? Optional.of(new SignalServiceAddress(UuidUtil.parseOrNull(sentContent.getDestinationUuid()), sentContent.getDestinationE164()))
|
||||
: Optional.<SignalServiceAddress>absent();
|
||||
|
||||
if (!address.isPresent() && !dataMessage.getGroupInfo().isPresent()) {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("SyncMessage missing both destination and group ID!"), null, 0);
|
||||
}
|
||||
|
||||
for (SignalServiceProtos.SyncMessage.Sent.UnidentifiedDeliveryStatus status : sentContent.getUnidentifiedStatusList()) {
|
||||
if (SignalServiceAddress.isValidAddress(status.getDestinationUuid(), status.getDestinationE164())) {
|
||||
SignalServiceAddress recipient = new SignalServiceAddress(UuidUtil.parseOrNull(status.getDestinationUuid()), status.getDestinationE164());
|
||||
unidentifiedStatuses.put(recipient, status.getUnidentified());
|
||||
} else {
|
||||
Log.w(TAG, "Encountered an invalid UnidentifiedDeliveryStatus in a SentTranscript! Ignoring.");
|
||||
}
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forSentTranscript(new SentTranscriptMessage(address,
|
||||
sentContent.getTimestamp(),
|
||||
dataMessage,
|
||||
sentContent.getExpirationStartTimestamp(),
|
||||
unidentifiedStatuses,
|
||||
sentContent.getIsRecipientUpdate()));
|
||||
}
|
||||
|
||||
if (content.hasRequest()) {
|
||||
return SignalServiceSyncMessage.forRequest(new RequestMessage(content.getRequest()));
|
||||
}
|
||||
|
||||
if (content.getReadList().size() > 0) {
|
||||
List<ReadMessage> readMessages = new LinkedList<>();
|
||||
|
||||
for (SignalServiceProtos.SyncMessage.Read read : content.getReadList()) {
|
||||
if (SignalServiceAddress.isValidAddress(read.getSenderUuid(), read.getSenderE164())) {
|
||||
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(read.getSenderUuid()), read.getSenderE164());
|
||||
readMessages.add(new ReadMessage(address, read.getTimestamp()));
|
||||
} else {
|
||||
Log.w(TAG, "Encountered an invalid ReadMessage! Ignoring.");
|
||||
}
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forRead(readMessages);
|
||||
}
|
||||
|
||||
if (content.hasViewOnceOpen()) {
|
||||
if (SignalServiceAddress.isValidAddress(content.getViewOnceOpen().getSenderUuid(), content.getViewOnceOpen().getSenderE164())) {
|
||||
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(content.getViewOnceOpen().getSenderUuid()), content.getViewOnceOpen().getSenderE164());
|
||||
ViewOnceOpenMessage timerRead = new ViewOnceOpenMessage(address, content.getViewOnceOpen().getTimestamp());
|
||||
return SignalServiceSyncMessage.forViewOnceOpen(timerRead);
|
||||
} else {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("ViewOnceOpen message has no sender!"), null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.hasVerified()) {
|
||||
if (SignalServiceAddress.isValidAddress(content.getVerified().getDestinationUuid(), content.getVerified().getDestinationE164())) {
|
||||
try {
|
||||
SignalServiceProtos.Verified verified = content.getVerified();
|
||||
SignalServiceAddress destination = new SignalServiceAddress(UuidUtil.parseOrNull(verified.getDestinationUuid()), verified.getDestinationE164());
|
||||
IdentityKey identityKey = new IdentityKey(verified.getIdentityKey().toByteArray(), 0);
|
||||
|
||||
VerifiedMessage.VerifiedState verifiedState;
|
||||
|
||||
if (verified.getState() == SignalServiceProtos.Verified.State.DEFAULT) {
|
||||
verifiedState = VerifiedMessage.VerifiedState.DEFAULT;
|
||||
} else if (verified.getState() == SignalServiceProtos.Verified.State.VERIFIED) {
|
||||
verifiedState = VerifiedMessage.VerifiedState.VERIFIED;
|
||||
} else if (verified.getState() == SignalServiceProtos.Verified.State.UNVERIFIED) {
|
||||
verifiedState = VerifiedMessage.VerifiedState.UNVERIFIED;
|
||||
} else {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("Unknown state: " + verified.getState().getNumber()),
|
||||
metadata.getSender().getIdentifier(), metadata.getSenderDevice());
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forVerified(new VerifiedMessage(destination, identityKey, verifiedState, System.currentTimeMillis()));
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new ProtocolInvalidKeyException(e, metadata.getSender().getIdentifier(), metadata.getSenderDevice());
|
||||
}
|
||||
} else {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("Verified message has no sender!"), null, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.getStickerPackOperationList().size() > 0) {
|
||||
List<StickerPackOperationMessage> operations = new LinkedList<>();
|
||||
|
||||
for (SignalServiceProtos.SyncMessage.StickerPackOperation operation : content.getStickerPackOperationList()) {
|
||||
byte[] packId = operation.hasPackId() ? operation.getPackId().toByteArray() : null;
|
||||
byte[] packKey = operation.hasPackKey() ? operation.getPackKey().toByteArray() : null;
|
||||
StickerPackOperationMessage.Type type = null;
|
||||
|
||||
if (operation.hasType()) {
|
||||
switch (operation.getType()) {
|
||||
case INSTALL: type = StickerPackOperationMessage.Type.INSTALL; break;
|
||||
case REMOVE: type = StickerPackOperationMessage.Type.REMOVE; break;
|
||||
}
|
||||
}
|
||||
operations.add(new StickerPackOperationMessage(packId, packKey, type));
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forStickerPackOperations(operations);
|
||||
}
|
||||
|
||||
if (content.hasBlocked()) {
|
||||
List<String> numbers = content.getBlocked().getNumbersList();
|
||||
List<String> uuids = content.getBlocked().getUuidsList();
|
||||
List<SignalServiceAddress> addresses = new ArrayList<>(numbers.size() + uuids.size());
|
||||
List<byte[]> groupIds = new ArrayList<>(content.getBlocked().getGroupIdsList().size());
|
||||
|
||||
for (String e164 : numbers) {
|
||||
Optional<SignalServiceAddress> address = SignalServiceAddress.fromRaw(null, e164);
|
||||
if (address.isPresent()) {
|
||||
addresses.add(address.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (String uuid : uuids) {
|
||||
Optional<SignalServiceAddress> address = SignalServiceAddress.fromRaw(uuid, null);
|
||||
if (address.isPresent()) {
|
||||
addresses.add(address.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (ByteString groupId : content.getBlocked().getGroupIdsList()) {
|
||||
groupIds.add(groupId.toByteArray());
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.forBlocked(new BlockedListMessage(addresses, groupIds));
|
||||
}
|
||||
|
||||
if (content.hasConfiguration()) {
|
||||
Boolean readReceipts = content.getConfiguration().hasReadReceipts() ? content.getConfiguration().getReadReceipts() : null;
|
||||
Boolean unidentifiedDeliveryIndicators = content.getConfiguration().hasUnidentifiedDeliveryIndicators() ? content.getConfiguration().getUnidentifiedDeliveryIndicators() : null;
|
||||
Boolean typingIndicators = content.getConfiguration().hasTypingIndicators() ? content.getConfiguration().getTypingIndicators() : null;
|
||||
Boolean linkPreviews = content.getConfiguration().hasLinkPreviews() ? content.getConfiguration().getLinkPreviews() : null;
|
||||
|
||||
return SignalServiceSyncMessage.forConfiguration(new ConfigurationMessage(Optional.fromNullable(readReceipts),
|
||||
Optional.fromNullable(unidentifiedDeliveryIndicators),
|
||||
Optional.fromNullable(typingIndicators),
|
||||
Optional.fromNullable(linkPreviews)));
|
||||
}
|
||||
|
||||
if (content.hasFetchLatest() && content.getFetchLatest().hasType()) {
|
||||
switch (content.getFetchLatest().getType()) {
|
||||
case LOCAL_PROFILE: return SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.LOCAL_PROFILE);
|
||||
case STORAGE_MANIFEST: return SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST);
|
||||
}
|
||||
}
|
||||
|
||||
return SignalServiceSyncMessage.empty();
|
||||
}
|
||||
|
||||
private static SignalServiceCallMessage createCallMessage(SignalServiceProtos.CallMessage content) {
|
||||
if (content.hasOffer()) {
|
||||
SignalServiceProtos.CallMessage.Offer offerContent = content.getOffer();
|
||||
return SignalServiceCallMessage.forOffer(new OfferMessage(offerContent.getId(), offerContent.getDescription()));
|
||||
} else if (content.hasAnswer()) {
|
||||
SignalServiceProtos.CallMessage.Answer answerContent = content.getAnswer();
|
||||
return SignalServiceCallMessage.forAnswer(new AnswerMessage(answerContent.getId(), answerContent.getDescription()));
|
||||
} else if (content.getIceUpdateCount() > 0) {
|
||||
List<IceUpdateMessage> iceUpdates = new LinkedList<>();
|
||||
|
||||
for (SignalServiceProtos.CallMessage.IceUpdate iceUpdate : content.getIceUpdateList()) {
|
||||
iceUpdates.add(new IceUpdateMessage(iceUpdate.getId(), iceUpdate.getSdpMid(), iceUpdate.getSdpMLineIndex(), iceUpdate.getSdp()));
|
||||
}
|
||||
|
||||
return SignalServiceCallMessage.forIceUpdates(iceUpdates);
|
||||
} else if (content.hasHangup()) {
|
||||
SignalServiceProtos.CallMessage.Hangup hangup = content.getHangup();
|
||||
return SignalServiceCallMessage.forHangup(new HangupMessage(hangup.getId()));
|
||||
} else if (content.hasBusy()) {
|
||||
SignalServiceProtos.CallMessage.Busy busy = content.getBusy();
|
||||
return SignalServiceCallMessage.forBusy(new BusyMessage(busy.getId()));
|
||||
}
|
||||
|
||||
return SignalServiceCallMessage.empty();
|
||||
}
|
||||
|
||||
private static SignalServiceReceiptMessage createReceiptMessage(SignalServiceMetadata metadata, SignalServiceProtos.ReceiptMessage content) {
|
||||
SignalServiceReceiptMessage.Type type;
|
||||
|
||||
if (content.getType() == SignalServiceProtos.ReceiptMessage.Type.DELIVERY) type = SignalServiceReceiptMessage.Type.DELIVERY;
|
||||
else if (content.getType() == SignalServiceProtos.ReceiptMessage.Type.READ) type = SignalServiceReceiptMessage.Type.READ;
|
||||
else type = SignalServiceReceiptMessage.Type.UNKNOWN;
|
||||
|
||||
return new SignalServiceReceiptMessage(type, content.getTimestampList(), metadata.getTimestamp());
|
||||
}
|
||||
|
||||
private static SignalServiceTypingMessage createTypingMessage(SignalServiceMetadata metadata, SignalServiceProtos.TypingMessage content) throws ProtocolInvalidMessageException {
|
||||
SignalServiceTypingMessage.Action action;
|
||||
|
||||
if (content.getAction() == SignalServiceProtos.TypingMessage.Action.STARTED) action = SignalServiceTypingMessage.Action.STARTED;
|
||||
else if (content.getAction() == SignalServiceProtos.TypingMessage.Action.STOPPED) action = SignalServiceTypingMessage.Action.STOPPED;
|
||||
else action = SignalServiceTypingMessage.Action.UNKNOWN;
|
||||
|
||||
if (content.hasTimestamp() && content.getTimestamp() != metadata.getTimestamp()) {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("Timestamps don't match: " + content.getTimestamp() + " vs " + metadata.getTimestamp()),
|
||||
metadata.getSender().getIdentifier(),
|
||||
metadata.getSenderDevice());
|
||||
}
|
||||
|
||||
return new SignalServiceTypingMessage(action, content.getTimestamp(),
|
||||
content.hasGroupId() ? Optional.of(content.getGroupId().toByteArray()) :
|
||||
Optional.<byte[]>absent());
|
||||
}
|
||||
|
||||
private static SignalServiceDataMessage.Quote createQuote(SignalServiceProtos.DataMessage content) {
|
||||
if (!content.hasQuote()) return null;
|
||||
|
||||
List<SignalServiceDataMessage.Quote.QuotedAttachment> attachments = new LinkedList<>();
|
||||
|
||||
for (SignalServiceProtos.DataMessage.Quote.QuotedAttachment attachment : content.getQuote().getAttachmentsList()) {
|
||||
attachments.add(new SignalServiceDataMessage.Quote.QuotedAttachment(attachment.getContentType(),
|
||||
attachment.getFileName(),
|
||||
attachment.hasThumbnail() ? createAttachmentPointer(attachment.getThumbnail()) : null));
|
||||
}
|
||||
|
||||
if (SignalServiceAddress.isValidAddress(content.getQuote().getAuthorUuid(), content.getQuote().getAuthorE164())) {
|
||||
SignalServiceAddress address = new SignalServiceAddress(UuidUtil.parseOrNull(content.getQuote().getAuthorUuid()), content.getQuote().getAuthorE164());
|
||||
|
||||
return new SignalServiceDataMessage.Quote(content.getQuote().getId(),
|
||||
address,
|
||||
content.getQuote().getText(),
|
||||
attachments);
|
||||
} else {
|
||||
Log.w(TAG, "Quote was missing an author! Returning null.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<SignalServiceDataMessage.Preview> createPreviews(SignalServiceProtos.DataMessage content) {
|
||||
if (content.getPreviewCount() <= 0) return null;
|
||||
|
||||
List<SignalServiceDataMessage.Preview> results = new LinkedList<>();
|
||||
|
||||
for (SignalServiceProtos.DataMessage.Preview preview : content.getPreviewList()) {
|
||||
SignalServiceAttachment attachment = null;
|
||||
|
||||
if (preview.hasImage()) {
|
||||
attachment = createAttachmentPointer(preview.getImage());
|
||||
}
|
||||
|
||||
results.add(new SignalServiceDataMessage.Preview(preview.getUrl(),
|
||||
preview.getTitle(),
|
||||
Optional.fromNullable(attachment)));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static SignalServiceDataMessage.Sticker createSticker(SignalServiceProtos.DataMessage content) {
|
||||
if (!content.hasSticker() ||
|
||||
!content.getSticker().hasPackId() ||
|
||||
!content.getSticker().hasPackKey() ||
|
||||
!content.getSticker().hasStickerId() ||
|
||||
!content.getSticker().hasData())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
SignalServiceProtos.DataMessage.Sticker sticker = content.getSticker();
|
||||
|
||||
return new SignalServiceDataMessage.Sticker(sticker.getPackId().toByteArray(),
|
||||
sticker.getPackKey().toByteArray(),
|
||||
sticker.getStickerId(),
|
||||
createAttachmentPointer(sticker.getData()));
|
||||
}
|
||||
|
||||
private static SignalServiceDataMessage.Reaction createReaction(SignalServiceProtos.DataMessage content) {
|
||||
if (!content.hasReaction() ||
|
||||
!content.getReaction().hasEmoji() ||
|
||||
!(content.getReaction().hasTargetAuthorE164() || content.getReaction().hasTargetAuthorUuid()) ||
|
||||
!content.getReaction().hasTargetSentTimestamp())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
SignalServiceProtos.DataMessage.Reaction reaction = content.getReaction();
|
||||
|
||||
return new SignalServiceDataMessage.Reaction(reaction.getEmoji(),
|
||||
reaction.getRemove(),
|
||||
new SignalServiceAddress(UuidUtil.parseOrNull(reaction.getTargetAuthorUuid()), reaction.getTargetAuthorE164()),
|
||||
reaction.getTargetSentTimestamp());
|
||||
}
|
||||
|
||||
private static List<SharedContact> createSharedContacts(SignalServiceProtos.DataMessage content) {
|
||||
if (content.getContactCount() <= 0) return null;
|
||||
|
||||
List<SharedContact> results = new LinkedList<>();
|
||||
|
||||
for (SignalServiceProtos.DataMessage.Contact contact : content.getContactList()) {
|
||||
SharedContact.Builder builder = SharedContact.newBuilder()
|
||||
.setName(SharedContact.Name.newBuilder()
|
||||
.setDisplay(contact.getName().getDisplayName())
|
||||
.setFamily(contact.getName().getFamilyName())
|
||||
.setGiven(contact.getName().getGivenName())
|
||||
.setMiddle(contact.getName().getMiddleName())
|
||||
.setPrefix(contact.getName().getPrefix())
|
||||
.setSuffix(contact.getName().getSuffix())
|
||||
.build());
|
||||
|
||||
if (contact.getAddressCount() > 0) {
|
||||
for (SignalServiceProtos.DataMessage.Contact.PostalAddress address : contact.getAddressList()) {
|
||||
SharedContact.PostalAddress.Type type = SharedContact.PostalAddress.Type.HOME;
|
||||
|
||||
switch (address.getType()) {
|
||||
case WORK: type = SharedContact.PostalAddress.Type.WORK; break;
|
||||
case HOME: type = SharedContact.PostalAddress.Type.HOME; break;
|
||||
case CUSTOM: type = SharedContact.PostalAddress.Type.CUSTOM; break;
|
||||
}
|
||||
|
||||
builder.withAddress(SharedContact.PostalAddress.newBuilder()
|
||||
.setCity(address.getCity())
|
||||
.setCountry(address.getCountry())
|
||||
.setLabel(address.getLabel())
|
||||
.setNeighborhood(address.getNeighborhood())
|
||||
.setPobox(address.getPobox())
|
||||
.setPostcode(address.getPostcode())
|
||||
.setRegion(address.getRegion())
|
||||
.setStreet(address.getStreet())
|
||||
.setType(type)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
if (contact.getNumberCount() > 0) {
|
||||
for (SignalServiceProtos.DataMessage.Contact.Phone phone : contact.getNumberList()) {
|
||||
SharedContact.Phone.Type type = SharedContact.Phone.Type.HOME;
|
||||
|
||||
switch (phone.getType()) {
|
||||
case HOME: type = SharedContact.Phone.Type.HOME; break;
|
||||
case WORK: type = SharedContact.Phone.Type.WORK; break;
|
||||
case MOBILE: type = SharedContact.Phone.Type.MOBILE; break;
|
||||
case CUSTOM: type = SharedContact.Phone.Type.CUSTOM; break;
|
||||
}
|
||||
|
||||
builder.withPhone(SharedContact.Phone.newBuilder()
|
||||
.setLabel(phone.getLabel())
|
||||
.setType(type)
|
||||
.setValue(phone.getValue())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
if (contact.getEmailCount() > 0) {
|
||||
for (SignalServiceProtos.DataMessage.Contact.Email email : contact.getEmailList()) {
|
||||
SharedContact.Email.Type type = SharedContact.Email.Type.HOME;
|
||||
|
||||
switch (email.getType()) {
|
||||
case HOME: type = SharedContact.Email.Type.HOME; break;
|
||||
case WORK: type = SharedContact.Email.Type.WORK; break;
|
||||
case MOBILE: type = SharedContact.Email.Type.MOBILE; break;
|
||||
case CUSTOM: type = SharedContact.Email.Type.CUSTOM; break;
|
||||
}
|
||||
|
||||
builder.withEmail(SharedContact.Email.newBuilder()
|
||||
.setLabel(email.getLabel())
|
||||
.setType(type)
|
||||
.setValue(email.getValue())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
if (contact.hasAvatar()) {
|
||||
builder.setAvatar(SharedContact.Avatar.newBuilder()
|
||||
.withAttachment(createAttachmentPointer(contact.getAvatar().getAvatar()))
|
||||
.withProfileFlag(contact.getAvatar().getIsProfile())
|
||||
.build());
|
||||
}
|
||||
|
||||
if (contact.hasOrganization()) {
|
||||
builder.withOrganization(contact.getOrganization());
|
||||
}
|
||||
|
||||
results.add(builder.build());
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static SignalServiceAttachmentPointer createAttachmentPointer(SignalServiceProtos.AttachmentPointer pointer) {
|
||||
return new SignalServiceAttachmentPointer(pointer.getId(),
|
||||
pointer.getContentType(),
|
||||
pointer.getKey().toByteArray(),
|
||||
pointer.hasSize() ? Optional.of(pointer.getSize()) : Optional.<Integer>absent(),
|
||||
pointer.hasThumbnail() ? Optional.of(pointer.getThumbnail().toByteArray()): Optional.<byte[]>absent(),
|
||||
pointer.getWidth(), pointer.getHeight(),
|
||||
pointer.hasDigest() ? Optional.of(pointer.getDigest().toByteArray()) : Optional.<byte[]>absent(),
|
||||
pointer.hasFileName() ? Optional.of(pointer.getFileName()) : Optional.<String>absent(),
|
||||
(pointer.getFlags() & SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE_VALUE) != 0,
|
||||
pointer.hasCaption() ? Optional.of(pointer.getCaption()) : Optional.<String>absent(),
|
||||
pointer.hasBlurHash() ? Optional.of(pointer.getBlurHash()) : Optional.<String>absent());
|
||||
|
||||
}
|
||||
|
||||
private static SignalServiceGroup createGroupInfo(SignalServiceProtos.DataMessage content) throws ProtocolInvalidMessageException {
|
||||
if (!content.hasGroup()) return null;
|
||||
|
||||
SignalServiceGroup.Type type;
|
||||
|
||||
switch (content.getGroup().getType()) {
|
||||
case DELIVER: type = SignalServiceGroup.Type.DELIVER; break;
|
||||
case UPDATE: type = SignalServiceGroup.Type.UPDATE; break;
|
||||
case QUIT: type = SignalServiceGroup.Type.QUIT; break;
|
||||
case REQUEST_INFO: type = SignalServiceGroup.Type.REQUEST_INFO; break;
|
||||
default: type = SignalServiceGroup.Type.UNKNOWN; break;
|
||||
}
|
||||
|
||||
if (content.getGroup().getType() != DELIVER) {
|
||||
String name = null;
|
||||
List<SignalServiceAddress> members = null;
|
||||
SignalServiceAttachmentPointer avatar = null;
|
||||
|
||||
if (content.getGroup().hasName()) {
|
||||
name = content.getGroup().getName();
|
||||
}
|
||||
|
||||
if (content.getGroup().getMembersCount() > 0) {
|
||||
members = new ArrayList<>(content.getGroup().getMembersCount());
|
||||
|
||||
for (SignalServiceProtos.GroupContext.Member member : content.getGroup().getMembersList()) {
|
||||
if (SignalServiceAddress.isValidAddress(member.getUuid(), member.getE164())) {
|
||||
members.add(new SignalServiceAddress(UuidUtil.parseOrNull(member.getUuid()), member.getE164()));
|
||||
} else {
|
||||
throw new ProtocolInvalidMessageException(new InvalidMessageException("GroupContext.Member had no address!"), null, 0);
|
||||
}
|
||||
}
|
||||
} else if (content.getGroup().getMembersE164Count() > 0) {
|
||||
members = new ArrayList<>(content.getGroup().getMembersE164Count());
|
||||
|
||||
for (String member : content.getGroup().getMembersE164List()) {
|
||||
members.add(new SignalServiceAddress(null, member));
|
||||
}
|
||||
}
|
||||
|
||||
if (content.getGroup().hasAvatar()) {
|
||||
SignalServiceProtos.AttachmentPointer pointer = content.getGroup().getAvatar();
|
||||
|
||||
avatar = new SignalServiceAttachmentPointer(pointer.getId(),
|
||||
pointer.getContentType(),
|
||||
pointer.getKey().toByteArray(),
|
||||
Optional.of(pointer.getSize()),
|
||||
Optional.<byte[]>absent(), 0, 0,
|
||||
Optional.fromNullable(pointer.hasDigest() ? pointer.getDigest().toByteArray() : null),
|
||||
Optional.<String>absent(),
|
||||
false,
|
||||
Optional.<String>absent(),
|
||||
Optional.<String>absent());
|
||||
}
|
||||
|
||||
return new SignalServiceGroup(type, content.getGroup().getId().toByteArray(), name, members, avatar);
|
||||
}
|
||||
|
||||
return new SignalServiceGroup(content.getGroup().getId().toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package org.whispersystems.signalservice.api.messages;
|
||||
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
public final class SignalServiceMetadata {
|
||||
private final SignalServiceAddress sender;
|
||||
private final int senderDevice;
|
||||
private final long timestamp;
|
||||
private final boolean needsReceipt;
|
||||
|
||||
public SignalServiceMetadata(SignalServiceAddress sender, int senderDevice, long timestamp, boolean needsReceipt) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
}
|
||||
|
||||
public SignalServiceAddress getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public int getSenderDevice() {
|
||||
return senderDevice;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public boolean isNeedsReceipt() {
|
||||
return needsReceipt;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package org.whispersystems.signalservice.internal.serialize;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.AddressProto;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public final class SignalServiceAddressProtobufSerializer {
|
||||
|
||||
private SignalServiceAddressProtobufSerializer() {
|
||||
}
|
||||
|
||||
public static AddressProto toProtobuf(SignalServiceAddress signalServiceAddress) {
|
||||
AddressProto.Builder builder = AddressProto.newBuilder();
|
||||
if(signalServiceAddress.getNumber().isPresent()){
|
||||
builder.setE164(signalServiceAddress.getNumber().get());
|
||||
}
|
||||
if(signalServiceAddress.getUuid().isPresent()){
|
||||
builder.setUuid(ByteString.copyFrom(UuidUtil.toByteArray(signalServiceAddress.getUuid().get())));
|
||||
}
|
||||
if(signalServiceAddress.getRelay().isPresent()){
|
||||
builder.setRelay(signalServiceAddress.getRelay().get());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static SignalServiceAddress fromProtobuf(AddressProto addressProto) {
|
||||
Optional<UUID> uuid = addressProto.hasUuid() ? Optional.of(UuidUtil.parseOrThrow(addressProto.getUuid().toByteArray())) : Optional.<UUID>absent();
|
||||
Optional<String> number = addressProto.hasE164() ? Optional.of(addressProto.getE164() ) : Optional.<String>absent();
|
||||
Optional<String> relay = addressProto.hasRelay() ? Optional.of(addressProto.getRelay() ) : Optional.<String>absent();
|
||||
return new SignalServiceAddress(uuid, number, relay);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package org.whispersystems.signalservice.internal.serialize;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata;
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.MetadataProto;
|
||||
|
||||
public final class SignalServiceMetadataProtobufSerializer {
|
||||
|
||||
private SignalServiceMetadataProtobufSerializer() {
|
||||
}
|
||||
|
||||
public static MetadataProto toProtobuf(SignalServiceMetadata metadata) {
|
||||
return MetadataProto.newBuilder()
|
||||
.setAddress(SignalServiceAddressProtobufSerializer.toProtobuf(metadata.getSender()))
|
||||
.setSenderDevice(metadata.getSenderDevice())
|
||||
.setNeedsReceipt(metadata.isNeedsReceipt())
|
||||
.setTimestamp(metadata.getTimestamp())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static SignalServiceMetadata fromProtobuf(MetadataProto metadata) {
|
||||
return new SignalServiceMetadata(SignalServiceAddressProtobufSerializer.fromProtobuf(metadata.getAddress()),
|
||||
metadata.getSenderDevice(),
|
||||
metadata.getTimestamp(),
|
||||
metadata.getNeedsReceipt());
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -1,12 +1,9 @@
|
||||
package org.whispersystems.signalservice.util;
|
||||
package org.whispersystems.signalservice.api.util;
|
||||
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class PhoneNumberFormatterTest extends TestCase {
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package org.whispersystems.signalservice.api.util;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.libsignal.util.Hex;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public final class UuidUtilTest {
|
||||
|
||||
@Test
|
||||
public void toByteArray() throws IOException {
|
||||
UUID uuid = UUID.fromString("67dfd496-ea02-4720-b13d-83a462168b1d");
|
||||
|
||||
byte[] serialized = UuidUtil.toByteArray(uuid);
|
||||
|
||||
assertArrayEquals(Hex.fromStringCondensed("67dfd496ea024720b13d83a462168b1d"), serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toByteArray_alternativeValues() throws IOException {
|
||||
UUID uuid = UUID.fromString("b70df6ac-3b21-4b39-a514-613561f51e2a");
|
||||
|
||||
byte[] serialized = UuidUtil.toByteArray(uuid);
|
||||
|
||||
assertArrayEquals(Hex.fromStringCondensed("b70df6ac3b214b39a514613561f51e2a"), serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseOrThrow_from_byteArray() throws IOException {
|
||||
byte[] bytes = Hex.fromStringCondensed("3dc48790568b49c19bd6ab6604a5bc32");
|
||||
|
||||
UUID uuid = UuidUtil.parseOrThrow(bytes);
|
||||
|
||||
assertEquals("3dc48790-568b-49c1-9bd6-ab6604a5bc32", uuid.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseOrThrow_from_byteArray_alternativeValues() throws IOException {
|
||||
byte[] bytes = Hex.fromStringCondensed("b83dfb0b67f141aa992e030c167cd011");
|
||||
|
||||
UUID uuid = UuidUtil.parseOrThrow(bytes);
|
||||
|
||||
assertEquals("b83dfb0b-67f1-41aa-992e-030c167cd011", uuid.toString());
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package org.whispersystems.signalservice.internal.serialize;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.serialize.protos.AddressProto;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public final class SignalServiceAddressProtobufSerializerTest {
|
||||
|
||||
@Test
|
||||
public void serialize_and_deserialize_uuid_address() {
|
||||
SignalServiceAddress address = new SignalServiceAddress(Optional.fromNullable(UUID.randomUUID()), Optional.<String>absent(), Optional.<String>absent());
|
||||
AddressProto addressProto = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.toProtobuf(address);
|
||||
SignalServiceAddress deserialized = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.fromProtobuf(addressProto);
|
||||
|
||||
assertEquals(address, deserialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serialize_and_deserialize_e164_address() {
|
||||
SignalServiceAddress address = new SignalServiceAddress(Optional.<UUID>absent(), Optional.of("+15552345678"), Optional.<String>absent());
|
||||
AddressProto addressProto = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.toProtobuf(address);
|
||||
SignalServiceAddress deserialized = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.fromProtobuf(addressProto);
|
||||
|
||||
assertEquals(address, deserialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serialize_and_deserialize_both_address() {
|
||||
SignalServiceAddress address = new SignalServiceAddress(Optional.fromNullable(UUID.randomUUID()), Optional.of("+15552345678"), Optional.<String>absent());
|
||||
AddressProto addressProto = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.toProtobuf(address);
|
||||
SignalServiceAddress deserialized = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.fromProtobuf(addressProto);
|
||||
|
||||
assertEquals(address, deserialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serialize_and_deserialize_both_address_with_relay() {
|
||||
SignalServiceAddress address = new SignalServiceAddress(Optional.fromNullable(UUID.randomUUID()), Optional.of("+15552345678"), Optional.of("relay"));
|
||||
AddressProto addressProto = org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer.toProtobuf(address);
|
||||
SignalServiceAddress deserialized = SignalServiceAddressProtobufSerializer.fromProtobuf(addressProto);
|
||||
|
||||
assertEquals(address, deserialized);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user