Separate message decryption from message processing.
parent
3b5d9a2cae
commit
b598431237
|
@ -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;
|
||||
}
|
|
@ -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 {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,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 {
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -5,12 +5,13 @@ import android.content.Context;
|
|||
import android.content.DialogInterface;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
|
@ -20,13 +21,12 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
|
|||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.VerifySpan;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
@ -179,7 +179,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
|||
|
||||
long pushId = pushDatabase.insert(envelope);
|
||||
|
||||
ApplicationDependencies.getJobManager().add(new PushDecryptJob(getContext(), pushId, messageRecord.getId()));
|
||||
ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), pushId, messageRecord.getId()));
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
|
|
@ -5,22 +5,18 @@ import android.content.Context;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
|
@ -63,21 +59,19 @@ public class IncomingMessageProcessor {
|
|||
public class Processor implements Closeable {
|
||||
|
||||
private final Context context;
|
||||
private final RecipientDatabase recipientDatabase;
|
||||
private final PushDatabase pushDatabase;
|
||||
private final MmsSmsDatabase mmsSmsDatabase;
|
||||
private final JobManager jobManager;
|
||||
|
||||
private Processor(@NonNull Context context) {
|
||||
this.context = context;
|
||||
this.recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
|
||||
this.pushDatabase = DatabaseFactory.getPushDatabase(context);
|
||||
this.mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
|
||||
this.jobManager = ApplicationDependencies.getJobManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The id of the {@link PushDecryptJob} that was scheduled to process the message, if
|
||||
* @return The id of the {@link PushDecryptMessageJob} that was scheduled to process the message, if
|
||||
* one was created. Otherwise null.
|
||||
*/
|
||||
public @Nullable String processEnvelope(@NonNull SignalServiceEnvelope envelope) {
|
||||
|
@ -99,8 +93,8 @@ public class IncomingMessageProcessor {
|
|||
private @NonNull String processMessage(@NonNull SignalServiceEnvelope envelope) {
|
||||
Log.i(TAG, "Received message. Inserting in PushDatabase.");
|
||||
|
||||
long id = pushDatabase.insert(envelope);
|
||||
PushDecryptJob job = new PushDecryptJob(context, id);
|
||||
long id = pushDatabase.insert(envelope);
|
||||
PushDecryptMessageJob job = new PushDecryptMessageJob(context, id);
|
||||
|
||||
jobManager.add(job);
|
||||
|
||||
|
|
|
@ -2,21 +2,21 @@ package org.thoughtcrime.securesms.gcm;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import org.thoughtcrime.securesms.IncomingMessageProcessor;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobTracker;
|
||||
import org.thoughtcrime.securesms.jobs.MarkerJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushProcessMessageJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Retrieves messages over the REST endpoint.
|
||||
|
@ -33,9 +33,8 @@ public class RestStrategy implements MessageRetriever.Strategy {
|
|||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try (IncomingMessageProcessor.Processor processor = ApplicationDependencies.getIncomingMessageProcessor().acquire()) {
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
AtomicReference<String> lastJobId = new AtomicReference<>(null);
|
||||
AtomicInteger jobCount = new AtomicInteger(0);
|
||||
SignalServiceMessageReceiver receiver = ApplicationDependencies.getSignalServiceMessageReceiver();
|
||||
AtomicInteger jobCount = new AtomicInteger(0);
|
||||
|
||||
receiver.setSoTimeoutMillis(SOCKET_TIMEOUT);
|
||||
|
||||
|
@ -44,16 +43,17 @@ public class RestStrategy implements MessageRetriever.Strategy {
|
|||
String jobId = processor.processEnvelope(envelope);
|
||||
|
||||
if (jobId != null) {
|
||||
lastJobId.set(jobId);
|
||||
jobCount.incrementAndGet();
|
||||
}
|
||||
Log.i(TAG, "Successfully processed an envelope." + timeSuffix(startTime));
|
||||
});
|
||||
|
||||
Log.d(TAG, jobCount.get() + " PushDecryptJob(s) were enqueued.");
|
||||
Log.d(TAG, jobCount.get() + " PushDecryptMessageJob(s) were enqueued.");
|
||||
|
||||
if (lastJobId.get() != null) {
|
||||
blockUntilJobIsFinished(lastJobId.get());
|
||||
long timeRemainingMs = blockUntilQueueDrained(PushDecryptMessageJob.QUEUE, TimeUnit.SECONDS.toMillis(10));
|
||||
|
||||
if (timeRemainingMs > 0) {
|
||||
blockUntilQueueDrained(PushProcessMessageJob.QUEUE, timeRemainingMs);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -63,29 +63,40 @@ public class RestStrategy implements MessageRetriever.Strategy {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
private static void blockUntilJobIsFinished(@NonNull String jobId) {
|
||||
|
||||
private static long blockUntilQueueDrained(@NonNull String queue, long timeoutMs) {
|
||||
final JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
final MarkerJob markerJob = new MarkerJob(queue);
|
||||
|
||||
jobManager.add(markerJob);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
ApplicationDependencies.getJobManager().addListener(jobId, new JobTracker.JobListener() {
|
||||
jobManager.addListener(markerJob.getId(), new JobTracker.JobListener() {
|
||||
@Override
|
||||
public void onStateChanged(@NonNull JobTracker.JobState jobState) {
|
||||
if (jobState.isComplete()) {
|
||||
ApplicationDependencies.getJobManager().removeListener(this);
|
||||
jobManager.removeListener(this);
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
if (!latch.await(10, TimeUnit.SECONDS)) {
|
||||
Log.w(TAG, "Timed out waiting for PushDecryptJob(s) to finish!");
|
||||
if (!latch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
|
||||
Log.w(TAG, "Timed out waiting for " + queue + " job(s) to finish!");
|
||||
return 0;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
Log.d(TAG, "Waited " + (System.currentTimeMillis() - startTime) + " ms for the PushDecryptJob(s) to finish.");
|
||||
long endTime = System.currentTimeMillis();
|
||||
long duration = endTime - startTime;
|
||||
|
||||
Log.d(TAG, "Waited " + duration + " ms for the " + queue + " job(s) to finish.");
|
||||
return timeoutMs - duration;
|
||||
}
|
||||
|
||||
private static String timeSuffix(long startTime) {
|
||||
|
|
|
@ -2,13 +2,13 @@ package org.thoughtcrime.securesms.groups;
|
|||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
||||
|
@ -232,7 +232,7 @@ public class GroupMessageProcessor {
|
|||
} else {
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
String body = Base64.encodeBytes(storage.toByteArray());
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(group), 0, content.isNeedsReceipt());
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(GroupUtil.getEncodedId(group.getGroupId(), false)), 0, content.isNeedsReceipt());
|
||||
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
|
||||
|
||||
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(groupMessage);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.thoughtcrime.securesms.jobmanager;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
@ -250,7 +251,7 @@ public abstract class Job {
|
|||
return maxInstances;
|
||||
}
|
||||
|
||||
@Nullable String getQueue() {
|
||||
public @Nullable String getQueue() {
|
||||
return queue;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob;
|
|||
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
|
||||
|
@ -71,7 +71,7 @@ public class WorkManagerFactoryMappings {
|
|||
put(MultiDeviceReadUpdateJob.class.getName(), MultiDeviceReadUpdateJob.KEY);
|
||||
put(MultiDeviceVerifiedUpdateJob.class.getName(), MultiDeviceVerifiedUpdateJob.KEY);
|
||||
put("PushContentReceiveJob", FailingJob.KEY);
|
||||
put(PushDecryptJob.class.getName(), PushDecryptJob.KEY);
|
||||
put("PushDecryptJob", PushDecryptMessageJob.KEY);
|
||||
put(PushGroupSendJob.class.getName(), PushGroupSendJob.KEY);
|
||||
put(PushGroupUpdateJob.class.getName(), PushGroupUpdateJob.KEY);
|
||||
put(PushMediaSendJob.class.getName(), PushMediaSendJob.KEY);
|
||||
|
|
|
@ -63,7 +63,8 @@ public final class JobManagerFactories {
|
|||
put(MultiDeviceStorageSyncRequestJob.KEY, new MultiDeviceStorageSyncRequestJob.Factory());
|
||||
put(MultiDeviceVerifiedUpdateJob.KEY, new MultiDeviceVerifiedUpdateJob.Factory());
|
||||
put(MultiDeviceViewOnceOpenJob.KEY, new MultiDeviceViewOnceOpenJob.Factory());
|
||||
put(PushDecryptJob.KEY, new PushDecryptJob.Factory());
|
||||
put(PushDecryptMessageJob.KEY, new PushDecryptMessageJob.Factory());
|
||||
put(PushProcessMessageJob.KEY, new PushProcessMessageJob.Factory());
|
||||
put(PushGroupSendJob.KEY, new PushGroupSendJob.Factory());
|
||||
put(PushGroupUpdateJob.KEY, new PushGroupUpdateJob.Factory());
|
||||
put(PushMediaSendJob.KEY, new PushMediaSendJob.Factory());
|
||||
|
@ -92,6 +93,7 @@ public final class JobManagerFactories {
|
|||
put(TrimThreadJob.KEY, new TrimThreadJob.Factory());
|
||||
put(TypingSendJob.KEY, new TypingSendJob.Factory());
|
||||
put(UpdateApkJob.KEY, new UpdateApkJob.Factory());
|
||||
put(MarkerJob.KEY, new MarkerJob.Factory());
|
||||
|
||||
// Migrations
|
||||
put(AvatarMigrationJob.KEY, new AvatarMigrationJob.Factory());
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
/**
|
||||
* Useful for putting in a queue as a marker to know that previously enqueued jobs have been processed.
|
||||
* <p>
|
||||
* Does no work.
|
||||
*/
|
||||
public final class MarkerJob extends BaseJob {
|
||||
|
||||
private static final String TAG = Log.tag(MarkerJob.class);
|
||||
|
||||
public static final String KEY = "MarkerJob";
|
||||
|
||||
public MarkerJob(@Nullable String queue) {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(queue)
|
||||
.build());
|
||||
}
|
||||
|
||||
private MarkerJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRun() {
|
||||
Log.i(TAG, String.format("Marker reached in %s queue", getParameters().getQueue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onShouldRetry(@NonNull Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
return Data.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<MarkerJob> {
|
||||
@Override
|
||||
public @NonNull MarkerJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new MarkerJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
|
||||
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
|
||||
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
|
||||
import org.signal.libsignal.metadata.ProtocolException;
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidVersionException;
|
||||
import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
|
||||
import org.signal.libsignal.metadata.ProtocolNoSessionException;
|
||||
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
||||
import org.signal.libsignal.metadata.SelfSendException;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.internal.push.UnsupportedDataMessageException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class PushDecryptMessageJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "PushDecryptJob";
|
||||
public static final String QUEUE = "__PUSH_DECRYPT_JOB__";
|
||||
|
||||
public static final String TAG = Log.tag(PushDecryptMessageJob.class);
|
||||
|
||||
private static final String KEY_MESSAGE_ID = "message_id";
|
||||
private static final String KEY_SMS_MESSAGE_ID = "sms_message_id";
|
||||
|
||||
private final long messageId;
|
||||
private final long smsMessageId;
|
||||
|
||||
public PushDecryptMessageJob(Context context, long pushMessageId) {
|
||||
this(context, pushMessageId, -1);
|
||||
}
|
||||
|
||||
public PushDecryptMessageJob(Context context, long pushMessageId, long smsMessageId) {
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(QUEUE)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
pushMessageId,
|
||||
smsMessageId);
|
||||
setContext(context);
|
||||
}
|
||||
|
||||
private PushDecryptMessageJob(@NonNull Parameters parameters, long pushMessageId, long smsMessageId) {
|
||||
super(parameters);
|
||||
|
||||
this.messageId = pushMessageId;
|
||||
this.smsMessageId = smsMessageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId)
|
||||
.putLong(KEY_SMS_MESSAGE_ID, smsMessageId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun() throws NoSuchMessageException, RetryLaterException {
|
||||
if (needsMigration()) {
|
||||
Log.w(TAG, "Migration is still needed.");
|
||||
postMigrationNotification();
|
||||
throw new RetryLaterException();
|
||||
}
|
||||
|
||||
PushDatabase database = DatabaseFactory.getPushDatabase(context);
|
||||
SignalServiceEnvelope envelope = database.get(messageId);
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
|
||||
try {
|
||||
List<Job> jobs = handleMessage(envelope);
|
||||
|
||||
for (Job job: jobs) {
|
||||
jobManager.add(job);
|
||||
}
|
||||
} catch (NoSenderException e) {
|
||||
Log.w(TAG, "Invalid message, but no sender info!");
|
||||
}
|
||||
|
||||
database.delete(messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(@NonNull Exception exception) {
|
||||
return exception instanceof RetryLaterException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
}
|
||||
|
||||
private boolean needsMigration() {
|
||||
return !IdentityKeyUtil.hasIdentityKey(context) || TextSecurePreferences.getNeedsSqlCipherMigration(context);
|
||||
}
|
||||
|
||||
private void postMigrationNotification() {
|
||||
// TODO [greyson] Navigation
|
||||
NotificationManagerCompat.from(context).notify(494949,
|
||||
new NotificationCompat.Builder(context, NotificationChannels.getMessagesChannel(context))
|
||||
.setSmallIcon(R.drawable.icon_notification)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||
.setContentTitle(context.getString(R.string.PushDecryptJob_new_locked_message))
|
||||
.setContentText(context.getString(R.string.PushDecryptJob_unlock_to_view_pending_messages))
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0))
|
||||
.setDefaults(NotificationCompat.DEFAULT_SOUND | NotificationCompat.DEFAULT_VIBRATE)
|
||||
.build());
|
||||
|
||||
}
|
||||
|
||||
private @NonNull List<Job> handleMessage(@NonNull SignalServiceEnvelope envelope) throws NoSenderException {
|
||||
try {
|
||||
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
||||
SignalServiceAddress localAddress = new SignalServiceAddress(Optional.of(TextSecurePreferences.getLocalUuid(context)), Optional.of(TextSecurePreferences.getLocalNumber(context)));
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, UnidentifiedAccessUtil.getCertificateValidator());
|
||||
|
||||
SignalServiceContent content = cipher.decrypt(envelope);
|
||||
|
||||
List<Job> jobs = new ArrayList<>(2);
|
||||
|
||||
jobs.add(new PushProcessMessageJob(content.serialize(), messageId, smsMessageId, envelope.getTimestamp()));
|
||||
|
||||
if (envelope.isPreKeySignalMessage()) {
|
||||
jobs.add(new RefreshPreKeysJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
|
||||
} catch (ProtocolInvalidVersionException e) {
|
||||
Log.w(TAG, e);
|
||||
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.INVALID_VERSION,
|
||||
toExceptionMetadata(e),
|
||||
messageId,
|
||||
smsMessageId,
|
||||
envelope.getTimestamp()));
|
||||
|
||||
} catch (ProtocolInvalidMessageException | ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
|
||||
Log.w(TAG, e);
|
||||
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.CORRUPT_MESSAGE,
|
||||
toExceptionMetadata(e),
|
||||
messageId,
|
||||
smsMessageId,
|
||||
envelope.getTimestamp()));
|
||||
|
||||
} catch (ProtocolNoSessionException e) {
|
||||
Log.w(TAG, e);
|
||||
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.NO_SESSION,
|
||||
toExceptionMetadata(e),
|
||||
messageId,
|
||||
smsMessageId,
|
||||
envelope.getTimestamp()));
|
||||
|
||||
} catch (ProtocolLegacyMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.LEGACY_MESSAGE,
|
||||
toExceptionMetadata(e),
|
||||
messageId,
|
||||
smsMessageId,
|
||||
envelope.getTimestamp()));
|
||||
|
||||
} catch (ProtocolDuplicateMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.DUPLICATE_MESSAGE,
|
||||
toExceptionMetadata(e),
|
||||
messageId,
|
||||
smsMessageId,
|
||||
envelope.getTimestamp()));
|
||||
|
||||
} catch (InvalidMetadataVersionException | InvalidMetadataMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
return Collections.emptyList();
|
||||
|
||||
} catch (SelfSendException e) {
|
||||
Log.i(TAG, "Dropping UD message from self.");
|
||||
return Collections.emptyList();
|
||||
|
||||
} catch (UnsupportedDataMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
return Collections.singletonList(new PushProcessMessageJob(PushProcessMessageJob.MessageState.UNSUPPORTED_DATA_MESSAGE,
|
||||
toExceptionMetadata(e),
|
||||
messageId,
|
||||
smsMessageId,
|
||||
envelope.getTimestamp()));
|
||||
}
|
||||
}
|
||||
|
||||
private static PushProcessMessageJob.ExceptionMetadata toExceptionMetadata(@NonNull UnsupportedDataMessageException e) throws NoSenderException {
|
||||
String sender = e.getSender();
|
||||
|
||||
if (sender == null) throw new NoSenderException();
|
||||
|
||||
return new PushProcessMessageJob.ExceptionMetadata(sender,
|
||||
e.getSenderDevice(),
|
||||
e.getGroup().transform(g -> GroupUtil.getEncodedId(g.getGroupId(), false)).orNull());
|
||||
}
|
||||
|
||||
private static PushProcessMessageJob.ExceptionMetadata toExceptionMetadata(@NonNull ProtocolException e) throws NoSenderException {
|
||||
String sender = e.getSender();
|
||||
|
||||
if (sender == null) throw new NoSenderException();
|
||||
|
||||
return new PushProcessMessageJob.ExceptionMetadata(sender, e.getSenderDevice());
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<PushDecryptMessageJob> {
|
||||
@Override
|
||||
public @NonNull PushDecryptMessageJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new PushDecryptMessageJob(parameters, data.getLong(KEY_MESSAGE_ID), data.getLong(KEY_SMS_MESSAGE_ID));
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoSenderException extends Exception {}
|
||||
}
|
|
@ -1,34 +1,18 @@
|
|||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
|
||||
import org.signal.libsignal.metadata.InvalidMetadataVersionException;
|
||||
import org.signal.libsignal.metadata.ProtocolDuplicateMessageException;
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
|
||||
import org.signal.libsignal.metadata.ProtocolInvalidVersionException;
|
||||
import org.signal.libsignal.metadata.ProtocolLegacyMessageException;
|
||||
import org.signal.libsignal.metadata.ProtocolNoSessionException;
|
||||
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
|
||||
import org.signal.libsignal.metadata.SelfSendException;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.attachments.PointerAttachment;
|
||||
|
@ -36,10 +20,7 @@ import org.thoughtcrime.securesms.attachments.TombstoneAttachment;
|
|||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
|
@ -51,8 +32,6 @@ import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
|||
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.StickerDatabase;
|
||||
|
@ -79,7 +58,6 @@ import org.thoughtcrime.securesms.mms.QuoteModel;
|
|||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||
import org.thoughtcrime.securesms.mms.StickerSlide;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
|
@ -90,21 +68,18 @@ import org.thoughtcrime.securesms.sms.OutgoingEncryptedMessage;
|
|||
import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.Hex;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.state.SessionStore;
|
||||
import org.whispersystems.libsignal.state.SignalProtocolStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
||||
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.SignalServiceEnvelope;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
|
||||
|
@ -125,8 +100,8 @@ 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.internal.push.UnsupportedDataMessageException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
|
@ -135,44 +110,112 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class PushDecryptJob extends BaseJob {
|
||||
public final class PushProcessMessageJob extends BaseJob {
|
||||
|
||||
public static final String KEY = "PushDecryptJob";
|
||||
public static final String KEY = "PushProcessJob";
|
||||
public static final String QUEUE = "__PUSH_PROCESS_JOB__";
|
||||
|
||||
public static final String TAG = PushDecryptJob.class.getSimpleName();
|
||||
public static final String TAG = Log.tag(PushProcessMessageJob.class);
|
||||
|
||||
private static final String KEY_MESSAGE_ID = "message_id";
|
||||
private static final String KEY_SMS_MESSAGE_ID = "sms_message_id";
|
||||
private static final String KEY_MESSAGE_STATE = "message_state";
|
||||
private static final String KEY_MESSAGE_PLAINTEXT = "message_content";
|
||||
private static final String KEY_MESSAGE_ID = "message_id";
|
||||
private static final String KEY_SMS_MESSAGE_ID = "sms_message_id";
|
||||
private static final String KEY_TIMESTAMP = "timestamp";
|
||||
private static final String KEY_EXCEPTION_SENDER = "exception_sender";
|
||||
private static final String KEY_EXCEPTION_DEVICE = "exception_device";
|
||||
private static final String KEY_EXCEPTION_GROUP_ID = "exception_groupId";
|
||||
|
||||
private long messageId;
|
||||
private long smsMessageId;
|
||||
@NonNull private final MessageState messageState;
|
||||
@Nullable private final byte[] serializedPlaintextContent;
|
||||
@Nullable private final ExceptionMetadata exceptionMetadata;
|
||||
private final long messageId;
|
||||
private final long smsMessageId;
|
||||
private final long timestamp;
|
||||
|
||||
public PushDecryptJob(Context context, long pushMessageId) {
|
||||
this(context, pushMessageId, -1);
|
||||
}
|
||||
|
||||
public PushDecryptJob(Context context, long pushMessageId, long smsMessageId) {
|
||||
this(new Job.Parameters.Builder()
|
||||
.setQueue("__PUSH_DECRYPT_JOB__")
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
PushProcessMessageJob(@NonNull byte[] serializedPlaintextContent,
|
||||
long pushMessageId,
|
||||
long smsMessageId,
|
||||
long timestamp)
|
||||
{
|
||||
this(MessageState.DECRYPTED_OK,
|
||||
serializedPlaintextContent,
|
||||
null,
|
||||
pushMessageId,
|
||||
smsMessageId);
|
||||
setContext(context);
|
||||
smsMessageId,
|
||||
timestamp);
|
||||
}
|
||||
|
||||
private PushDecryptJob(@NonNull Job.Parameters parameters, long pushMessageId, long smsMessageId) {
|
||||
PushProcessMessageJob(@NonNull MessageState messageState,
|
||||
@NonNull ExceptionMetadata exceptionMetadata,
|
||||
long pushMessageId,
|
||||
long smsMessageId,
|
||||
long timestamp)
|
||||
{
|
||||
this(messageState,
|
||||
null,
|
||||
exceptionMetadata,
|
||||
pushMessageId,
|
||||
smsMessageId,
|
||||
timestamp);
|
||||
}
|
||||
|
||||
private PushProcessMessageJob(@NonNull MessageState messageState,
|
||||
@Nullable byte[] serializedPlaintextContent,
|
||||
@Nullable ExceptionMetadata exceptionMetadata,
|
||||
long pushMessageId,
|
||||
long smsMessageId,
|
||||
long timestamp)
|
||||
{
|
||||
this(new Parameters.Builder()
|
||||
.setQueue(QUEUE)
|
||||
.setMaxAttempts(Parameters.UNLIMITED)
|
||||
.build(),
|
||||
messageState,
|
||||
serializedPlaintextContent,
|
||||
exceptionMetadata,
|
||||
pushMessageId,
|
||||
smsMessageId,
|
||||
timestamp);
|
||||
}
|
||||
|
||||
private PushProcessMessageJob(@NonNull Parameters parameters,
|
||||
@NonNull MessageState messageState,
|
||||
@Nullable byte[] serializedPlaintextContent,
|
||||
@Nullable ExceptionMetadata exceptionMetadata,
|
||||
long pushMessageId,
|
||||
long smsMessageId,
|
||||
long timestamp)
|
||||
{
|
||||
super(parameters);
|
||||
|
||||
this.messageId = pushMessageId;
|
||||
this.smsMessageId = smsMessageId;
|
||||
this.messageState = messageState;
|
||||
this.exceptionMetadata = exceptionMetadata;
|
||||
this.serializedPlaintextContent = serializedPlaintextContent;
|
||||
this.messageId = pushMessageId;
|
||||
this.smsMessageId = smsMessageId;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Data serialize() {
|
||||
return new Data.Builder().putLong(KEY_MESSAGE_ID, messageId)
|
||||
.putLong(KEY_SMS_MESSAGE_ID, smsMessageId)
|
||||
.build();
|
||||
Data.Builder dataBuilder = new Data.Builder()
|
||||
.putInt(KEY_MESSAGE_STATE, messageState.ordinal())
|
||||
.putLong(KEY_MESSAGE_ID, messageId)
|
||||
.putLong(KEY_SMS_MESSAGE_ID, smsMessageId)
|
||||
.putLong(KEY_TIMESTAMP, timestamp);
|
||||
|
||||
if (messageState == MessageState.DECRYPTED_OK) {
|
||||
//noinspection ConstantConditions
|
||||
dataBuilder.putString(KEY_MESSAGE_PLAINTEXT, Base64.encodeBytes(serializedPlaintextContent));
|
||||
} else {
|
||||
//noinspection ConstantConditions
|
||||
dataBuilder.putString(KEY_EXCEPTION_SENDER, exceptionMetadata.sender)
|
||||
.putInt(KEY_EXCEPTION_DEVICE, exceptionMetadata.senderDevice)
|
||||
.putString(KEY_EXCEPTION_GROUP_ID, exceptionMetadata.groupId);
|
||||
}
|
||||
|
||||
return dataBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -181,59 +224,33 @@ public class PushDecryptJob extends BaseJob {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onRun() throws NoSuchMessageException, RetryLaterException {
|
||||
if (needsMigration()) {
|
||||
Log.w(TAG, "Migration is still needed.");
|
||||
postMigrationNotification();
|
||||
throw new RetryLaterException();
|
||||
public void onRun() {
|
||||
Optional<Long> optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.absent();
|
||||
|
||||
if (messageState == MessageState.DECRYPTED_OK) {
|
||||
//noinspection ConstantConditions
|
||||
handleMessage(serializedPlaintextContent, optionalSmsMessageId);
|
||||
} else {
|
||||
//noinspection ConstantConditions
|
||||
handleExceptionMessage(exceptionMetadata, optionalSmsMessageId);
|
||||
}
|
||||
|
||||
PushDatabase database = DatabaseFactory.getPushDatabase(context);
|
||||
SignalServiceEnvelope envelope = database.get(messageId);
|
||||
Optional<Long> optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) : Optional.absent();
|
||||
|
||||
handleMessage(envelope, optionalSmsMessageId);
|
||||
database.delete(messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(@NonNull Exception exception) {
|
||||
return exception instanceof RetryLaterException;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
}
|
||||
|
||||
private boolean needsMigration() {
|
||||
return !IdentityKeyUtil.hasIdentityKey(context) || TextSecurePreferences.getNeedsSqlCipherMigration(context);
|
||||
}
|
||||
|
||||
private void postMigrationNotification() {
|
||||
// TODO [greyson] Navigation
|
||||
NotificationManagerCompat.from(context).notify(494949,
|
||||
new NotificationCompat.Builder(context, NotificationChannels.getMessagesChannel(context))
|
||||
.setSmallIcon(R.drawable.icon_notification)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||
.setContentTitle(context.getString(R.string.PushDecryptJob_new_locked_message))
|
||||
.setContentText(context.getString(R.string.PushDecryptJob_unlock_to_view_pending_messages))
|
||||
.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0))
|
||||
.setDefaults(NotificationCompat.DEFAULT_SOUND | NotificationCompat.DEFAULT_VIBRATE)
|
||||
.build());
|
||||
|
||||
}
|
||||
|
||||
private void handleMessage(@NonNull SignalServiceEnvelope envelope, @NonNull Optional<Long> smsMessageId) {
|
||||
private void handleMessage(@NonNull byte[] plaintextDataBuffer, @NonNull Optional<Long> smsMessageId) {
|
||||
try {
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context);
|
||||
SignalServiceAddress localAddress = new SignalServiceAddress(Optional.of(TextSecurePreferences.getLocalUuid(context)), Optional.of(TextSecurePreferences.getLocalNumber(context)));
|
||||
SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, UnidentifiedAccessUtil.getCertificateValidator());
|
||||
SignalServiceContent content = SignalServiceContent.deserialize(plaintextDataBuffer);
|
||||
|
||||
SignalServiceContent content = cipher.decrypt(envelope);
|
||||
|
||||
if (shouldIgnore(content)) {
|
||||
if (content == null || shouldIgnore(content)) {
|
||||
Log.i(TAG, "Ignoring message.");
|
||||
return;
|
||||
}
|
||||
|
@ -242,7 +259,7 @@ public class PushDecryptJob extends BaseJob {
|
|||
SignalServiceDataMessage message = content.getDataMessage().get();
|
||||
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent();
|
||||
|
||||
if (isInvalidMessage(message)) handleInvalidMessage(content.getSender(), content.getSenderDevice(), message.getGroupInfo(), content.getTimestamp(), smsMessageId);
|
||||
if (isInvalidMessage(message)) handleInvalidMessage(content.getSender(), content.getSenderDevice(), toEncodedId(message.getGroupInfo()), content.getTimestamp(), smsMessageId);
|
||||
else if (message.isEndSession()) handleEndSessionMessage(content, smsMessageId);
|
||||
else if (message.isGroupUpdate()) handleGroupMessage(content, message, smsMessageId);
|
||||
else if (message.isExpirationUpdate()) handleExpirationUpdate(content, message, smsMessageId);
|
||||
|
@ -298,41 +315,51 @@ public class PushDecryptJob extends BaseJob {
|
|||
|
||||
resetRecipientToPush(Recipient.externalPush(context, content.getSender()));
|
||||
|
||||
if (envelope.isPreKeySignalMessage()) {
|
||||
ApplicationDependencies.getJobManager().add(new RefreshPreKeysJob());
|
||||
}
|
||||
} catch (ProtocolInvalidVersionException e) {
|
||||
Log.w(TAG, e);
|
||||
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||
} catch (ProtocolInvalidMessageException e) {
|
||||
if (!TextUtils.isEmpty(e.getSender())) {
|
||||
Log.w(TAG, e);
|
||||
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||
} else {
|
||||
Log.w(TAG, "Invalid message, but no sender info!", e);
|
||||
}
|
||||
} catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
|
||||
Log.w(TAG, e);
|
||||
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||
} catch (StorageFailedException e) {
|
||||
Log.w(TAG, e);
|
||||
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||
} catch (ProtocolNoSessionException e) {
|
||||
Log.w(TAG, e);
|
||||
handleNoSessionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||
} catch (ProtocolLegacyMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
handleLegacyMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||
} catch (ProtocolDuplicateMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
handleDuplicateMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
|
||||
} catch (InvalidMetadataVersionException | InvalidMetadataMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
} catch (SelfSendException e) {
|
||||
Log.i(TAG, "Dropping UD message from self.");
|
||||
} catch (UnsupportedDataMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
handleUnsupportedDataMessage(e.getSender(), e.getSenderDevice(), e.getGroup(), envelope.getTimestamp(), smsMessageId);
|
||||
handleCorruptMessage(e.getSender(), e.getSenderDevice(), timestamp, smsMessageId);
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull Optional<String> toEncodedId(@NonNull Optional<SignalServiceGroup> groupInfo) {
|
||||
return groupInfo.transform(g -> GroupUtil.getEncodedId(g.getGroupId(), false));
|
||||
}
|
||||
|
||||
private void handleExceptionMessage(@NonNull ExceptionMetadata e, @NonNull Optional<Long> smsMessageId) {
|
||||
switch (messageState) {
|
||||
|
||||
case INVALID_VERSION:
|
||||
Log.w(TAG, "Handling invalid version");
|
||||
handleInvalidVersionMessage(e.sender, e.senderDevice, timestamp, smsMessageId);
|
||||
break;
|
||||
|
||||
case CORRUPT_MESSAGE:
|
||||
Log.w(TAG, "Handling corrupt message");
|
||||
handleCorruptMessage(e.sender, e.senderDevice, timestamp, smsMessageId);
|
||||
break;
|
||||
|
||||
case NO_SESSION:
|
||||
Log.w(TAG, "Handling no session");
|
||||
handleNoSessionMessage(e.sender, e.senderDevice, timestamp, smsMessageId);
|
||||
break;
|
||||
|
||||
case LEGACY_MESSAGE:
|
||||
Log.w(TAG, "Handling legacy message");
|
||||
handleLegacyMessage(e.sender, e.senderDevice, timestamp, smsMessageId);
|
||||
break;
|
||||
|
||||
case DUPLICATE_MESSAGE:
|
||||
Log.w(TAG, "Handling duplicate message");
|
||||
handleDuplicateMessage(e.sender, e.senderDevice, timestamp, smsMessageId);
|
||||
break;
|
||||
|
||||
case UNSUPPORTED_DATA_MESSAGE:
|
||||
Log.w(TAG, "Handling unsupported data message");
|
||||
handleUnsupportedDataMessage(e.sender, e.senderDevice, Optional.fromNullable(e.groupId), timestamp, smsMessageId);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new AssertionError("Not handled " + messageState);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -599,7 +626,7 @@ public class PushDecryptJob extends BaseJob {
|
|||
DatabaseFactory.getRecipientDatabase(context).applyBlockedUpdate(blockMessage.getAddresses(), blockMessage.getGroupIds());
|
||||
}
|
||||
|
||||
private void handleSynchronizeFetchMessage(@NonNull SignalServiceSyncMessage.FetchType fetchType) {
|
||||
private static void handleSynchronizeFetchMessage(@NonNull SignalServiceSyncMessage.FetchType fetchType) {
|
||||
if (fetchType == SignalServiceSyncMessage.FetchType.LOCAL_PROFILE) {
|
||||
ApplicationDependencies.getJobManager().add(new RefreshOwnProfileJob());
|
||||
} else {
|
||||
|
@ -952,7 +979,7 @@ public class PushDecryptJob extends BaseJob {
|
|||
IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(),
|
||||
content.getSenderDevice(),
|
||||
message.getTimestamp(), body,
|
||||
message.getGroupInfo(),
|
||||
toEncodedId(message.getGroupInfo()),
|
||||
message.getExpiresInSeconds() * 1000L,
|
||||
content.isNeedsReceipt());
|
||||
|
||||
|
@ -1074,14 +1101,14 @@ public class PushDecryptJob extends BaseJob {
|
|||
|
||||
private void handleUnsupportedDataMessage(@NonNull String sender,
|
||||
int senderDevice,
|
||||
@NonNull Optional<SignalServiceGroup> group,
|
||||
@NonNull Optional<String> groupId,
|
||||
long timestamp,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp, group);
|
||||
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp, groupId);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
smsDatabase.markAsUnsupportedProtocolVersion(insertResult.get().getMessageId());
|
||||
|
@ -1094,14 +1121,14 @@ public class PushDecryptJob extends BaseJob {
|
|||
|
||||
private void handleInvalidMessage(@NonNull SignalServiceAddress sender,
|
||||
int senderDevice,
|
||||
@NonNull Optional<SignalServiceGroup> group,
|
||||
@NonNull Optional<String> groupId,
|
||||
long timestamp,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Optional<InsertResult> insertResult = insertPlaceholder(sender.getIdentifier(), senderDevice, timestamp, group);
|
||||
Optional<InsertResult> insertResult = insertPlaceholder(sender.getIdentifier(), senderDevice, timestamp, groupId);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
smsDatabase.markAsInvalidMessage(insertResult.get().getMessageId());
|
||||
|
@ -1223,7 +1250,7 @@ public class PushDecryptJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isInvalidMessage(@NonNull SignalServiceDataMessage message) {
|
||||
private static boolean isInvalidMessage(@NonNull SignalServiceDataMessage message) {
|
||||
if (message.isViewOnce()) {
|
||||
List<SignalServiceAttachment> attachments = message.getAttachments().or(Collections.emptyList());
|
||||
|
||||
|
@ -1234,7 +1261,7 @@ public class PushDecryptJob extends BaseJob {
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean isViewOnceSupportedContentType(@NonNull String contentType) {
|
||||
private static boolean isViewOnceSupportedContentType(@NonNull String contentType) {
|
||||
return MediaUtil.isImageType(contentType) || MediaUtil.isVideoType(contentType);
|
||||
}
|
||||
|
||||
|
@ -1326,7 +1353,7 @@ public class PushDecryptJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
private Optional<List<Contact>> getContacts(Optional<List<SharedContact>> sharedContacts) {
|
||||
private static Optional<List<Contact>> getContacts(Optional<List<SharedContact>> sharedContacts) {
|
||||
if (!sharedContacts.isPresent()) return Optional.absent();
|
||||
|
||||
List<Contact> contacts = new ArrayList<>(sharedContacts.get().size());
|
||||
|
@ -1338,7 +1365,7 @@ public class PushDecryptJob extends BaseJob {
|
|||
return Optional.of(contacts);
|
||||
}
|
||||
|
||||
private Optional<List<LinkPreview>> getLinkPreviews(Optional<List<Preview>> previews, @NonNull String message) {
|
||||
private static Optional<List<LinkPreview>> getLinkPreviews(Optional<List<Preview>> previews, @NonNull String message) {
|
||||
if (!previews.isPresent()) return Optional.absent();
|
||||
|
||||
List<LinkPreview> linkPreviews = new ArrayList<>(previews.get().size());
|
||||
|
@ -1366,11 +1393,11 @@ public class PushDecryptJob extends BaseJob {
|
|||
return insertPlaceholder(sender, senderDevice, timestamp, Optional.absent());
|
||||
}
|
||||
|
||||
private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp, Optional<SignalServiceGroup> group) {
|
||||
private Optional<InsertResult> insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp, Optional<String> groupId) {
|
||||
SmsDatabase database = DatabaseFactory.getSmsDatabase(context);
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.external(context, sender).getId(),
|
||||
senderDevice, timestamp, "",
|
||||
group, 0, false);
|
||||
groupId, 0, false);
|
||||
|
||||
textMessage = new IncomingEncryptedMessage(textMessage, "");
|
||||
return database.insertMessageInbox(textMessage);
|
||||
|
@ -1470,7 +1497,7 @@ public class PushDecryptJob extends BaseJob {
|
|||
@SuppressWarnings("WeakerAccess")
|
||||
private static class StorageFailedException extends Exception {
|
||||
private final String sender;
|
||||
private final int senderDevice;
|
||||
private final int senderDevice;
|
||||
|
||||
private StorageFailedException(Exception e, String sender, int senderDevice) {
|
||||
super(e);
|
||||
|
@ -1487,10 +1514,62 @@ public class PushDecryptJob extends BaseJob {
|
|||
}
|
||||
}
|
||||
|
||||
public static final class Factory implements Job.Factory<PushDecryptJob> {
|
||||
public static final class Factory implements Job.Factory<PushProcessMessageJob> {
|
||||
@Override
|
||||
public @NonNull PushDecryptJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new PushDecryptJob(parameters, data.getLong(KEY_MESSAGE_ID), data.getLong(KEY_SMS_MESSAGE_ID));
|
||||
public @NonNull PushProcessMessageJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
try {
|
||||
MessageState state = MessageState.values()[data.getInt(KEY_MESSAGE_STATE)];
|
||||
|
||||
if (state == MessageState.DECRYPTED_OK) {
|
||||
return new PushProcessMessageJob(parameters,
|
||||
state,
|
||||
Base64.decode(data.getString(KEY_MESSAGE_PLAINTEXT)),
|
||||
null,
|
||||
data.getLong(KEY_MESSAGE_ID),
|
||||
data.getLong(KEY_SMS_MESSAGE_ID),
|
||||
data.getLong(KEY_TIMESTAMP));
|
||||
} else {
|
||||
ExceptionMetadata exceptionMetadata = new ExceptionMetadata(data.getString(KEY_EXCEPTION_SENDER),
|
||||
data.getInt(KEY_EXCEPTION_DEVICE),
|
||||
data.getStringOrDefault(KEY_EXCEPTION_GROUP_ID, null));
|
||||
|
||||
return new PushProcessMessageJob(parameters,
|
||||
state,
|
||||
null,
|
||||
exceptionMetadata,
|
||||
data.getLong(KEY_MESSAGE_ID),
|
||||
data.getLong(KEY_SMS_MESSAGE_ID),
|
||||
data.getLong(KEY_TIMESTAMP));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MessageState {
|
||||
DECRYPTED_OK,
|
||||
INVALID_VERSION,
|
||||
CORRUPT_MESSAGE,
|
||||
NO_SESSION,
|
||||
LEGACY_MESSAGE,
|
||||
DUPLICATE_MESSAGE,
|
||||
UNSUPPORTED_DATA_MESSAGE
|
||||
}
|
||||
|
||||
static class ExceptionMetadata {
|
||||
@NonNull private final String sender;
|
||||
private final int senderDevice;
|
||||
@Nullable private final String groupId;
|
||||
|
||||
ExceptionMetadata(@NonNull String sender, int senderDevice, @Nullable String groupId) {
|
||||
this.sender = sender;
|
||||
this.senderDevice = senderDevice;
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
ExceptionMetadata(@NonNull String sender, int senderDevice) {
|
||||
this(sender, senderDevice, null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ import android.database.Cursor;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColorsLegacy;
|
||||
|
@ -21,10 +20,11 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
|||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
|
@ -294,20 +294,15 @@ public class LegacyMigrationJob extends MigrationJob {
|
|||
}
|
||||
}
|
||||
|
||||
private void scheduleMessagesInPushDatabase(Context context) {
|
||||
private static void scheduleMessagesInPushDatabase(@NonNull Context context) {
|
||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
|
||||
Cursor pushReader = null;
|
||||
|
||||
try {
|
||||
pushReader = pushDatabase.getPending();
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
|
||||
try (Cursor pushReader = pushDatabase.getPending()) {
|
||||
while (pushReader != null && pushReader.moveToNext()) {
|
||||
ApplicationDependencies.getJobManager().add(new PushDecryptJob(context,
|
||||
pushReader.getLong(pushReader.getColumnIndexOrThrow(PushDatabase.ID))));
|
||||
jobManager.add(new PushDecryptMessageJob(context,
|
||||
pushReader.getLong(pushReader.getColumnIndexOrThrow(PushDatabase.ID))));
|
||||
}
|
||||
} finally {
|
||||
if (pushReader != null)
|
||||
pushReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,13 @@ package org.thoughtcrime.securesms.sms;
|
|||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.telephony.SmsMessage;
|
||||
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -61,7 +59,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||
}
|
||||
|
||||
public IncomingTextMessage(@NonNull RecipientId sender, int senderDeviceId, long sentTimestampMillis,
|
||||
String encodedBody, Optional<SignalServiceGroup> group,
|
||||
String encodedBody, Optional<String> groupId,
|
||||
long expiresInMillis, boolean unidentified)
|
||||
{
|
||||
this.message = encodedBody;
|
||||
|
@ -76,12 +74,7 @@ public class IncomingTextMessage implements Parcelable {
|
|||
this.subscriptionId = -1;
|
||||
this.expiresInMillis = expiresInMillis;
|
||||
this.unidentified = unidentified;
|
||||
|
||||
if (group.isPresent()) {
|
||||
this.groupId = GroupUtil.getEncodedId(group.get().getGroupId(), false);
|
||||
} else {
|
||||
this.groupId = null;
|
||||
}
|
||||
this.groupId = groupId.orNull();
|
||||
}
|
||||
|
||||
public IncomingTextMessage(Parcel in) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util;
|
|||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
|
@ -34,7 +35,6 @@ import org.whispersystems.libsignal.state.IdentityKeyStore;
|
|||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SessionStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -75,17 +75,16 @@ public class IdentityUtil {
|
|||
|
||||
while ((groupRecord = reader.getNext()) != null) {
|
||||
if (groupRecord.getMembers().contains(recipient.getId()) && groupRecord.isActive() && !groupRecord.isMms()) {
|
||||
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
||||
|
||||
if (remote) {
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(group), 0, false);
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(groupRecord.getEncodedId()), 0, false);
|
||||
|
||||
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
||||
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
||||
|
||||
smsDatabase.insertMessageInbox(incoming);
|
||||
} else {
|
||||
RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(group.getGroupId(), false));
|
||||
RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupRecord.getEncodedId());
|
||||
Recipient groupRecipient = Recipient.resolved(recipientId);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
||||
OutgoingTextMessage outgoing ;
|
||||
|
@ -128,8 +127,7 @@ public class IdentityUtil {
|
|||
|
||||
while ((groupRecord = reader.getNext()) != null) {
|
||||
if (groupRecord.getMembers().contains(recipient.getId()) && groupRecord.isActive()) {
|
||||
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(group), 0, false);
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(groupRecord.getEncodedId()), 0, false);
|
||||
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);
|
||||
|
||||
smsDatabase.insertMessageInbox(groupUpdate);
|
||||
|
|
Loading…
Reference in New Issue