WIP: clean up signal protocol

This commit is contained in:
Ryan ZHAO 2021-02-18 17:05:34 +11:00
parent 0d2f5e0cde
commit 1a907fcf54
46 changed files with 5785 additions and 30161 deletions

View File

@ -27,6 +27,7 @@ import org.session.libsignal.metadata.ProtocolLegacyMessageException;
import org.session.libsignal.metadata.ProtocolNoSessionException; import org.session.libsignal.metadata.ProtocolNoSessionException;
import org.session.libsignal.metadata.ProtocolUntrustedIdentityException; import org.session.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.session.libsignal.metadata.SelfSendException; import org.session.libsignal.metadata.SelfSendException;
import org.session.libsignal.service.api.crypto.SignalServiceCipher;
import org.session.libsignal.service.loki.api.crypto.SessionProtocol; import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
import org.session.libsignal.utilities.PromiseUtilities; import org.session.libsignal.utilities.PromiseUtilities;
import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ApplicationContext;
@ -108,12 +109,8 @@ import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.api.messages.SignalServiceGroup; import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage; import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage; import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
import org.session.libsignal.service.api.messages.multidevice.SentTranscriptMessage;
import org.session.libsignal.service.api.messages.multidevice.StickerPackOperationMessage;
import org.session.libsignal.service.api.messages.shared.SharedContact; import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress; import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI;
import org.session.libsignal.service.loki.crypto.LokiServiceCipher;
import org.session.libsignal.service.loki.protocol.mentions.MentionsManager; import org.session.libsignal.service.loki.protocol.mentions.MentionsManager;
import org.session.libsignal.service.loki.utilities.PublicKeyValidation; import org.session.libsignal.service.loki.utilities.PublicKeyValidation;
@ -248,7 +245,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context); SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context)); SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(context));
LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(context); LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(context);
LokiServiceCipher cipher = new LokiServiceCipher(localAddress, axolotlStore, new SessionProtocolImpl(context), sessionResetProtocol, apiDB, UnidentifiedAccessUtil.getCertificateValidator()); SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, sessionResetProtocol, new SessionProtocolImpl(context), apiDB);
SignalServiceContent content = cipher.decrypt(envelope); SignalServiceContent content = cipher.decrypt(envelope);
@ -265,14 +262,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
MultiDeviceProtocol.handleConfigurationMessage(context, content.configurationMessageProto.get(), content.getSender(), content.getTimestamp()); MultiDeviceProtocol.handleConfigurationMessage(context, content.configurationMessageProto.get(), content.getSender(), content.getTimestamp());
} else if (content.getDataMessage().isPresent()) { } else if (content.getDataMessage().isPresent()) {
SignalServiceDataMessage message = content.getDataMessage().get(); SignalServiceDataMessage message = content.getDataMessage().get();
boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent() || message.getSticker().isPresent(); boolean isMediaMessage = message.getAttachments().isPresent() || message.getQuote().isPresent() || message.getSharedContacts().isPresent() || message.getPreviews().isPresent();
if (message.getClosedGroupUpdateV2().isPresent()) { if (message.getClosedGroupControlMessage().isPresent()) {
ClosedGroupsProtocolV2.handleMessage(context, message.getClosedGroupUpdateV2().get(), message.getTimestamp(), envelope.getSource(), content.getSender()); ClosedGroupsProtocolV2.handleMessage(context, message.getClosedGroupControlMessage().get(), message.getTimestamp(), envelope.getSource(), content.getSender());
} }
if (message.isEndSession()) { if (message.isGroupUpdate()) {
handleEndSessionMessage(content, smsMessageId);
} else if (message.isGroupUpdate()) {
handleGroupMessage(content, message, smsMessageId); handleGroupMessage(content, message, smsMessageId);
} else if (message.isExpirationUpdate()) { } else if (message.isExpirationUpdate()) {
handleExpirationUpdate(content, message, smsMessageId); handleExpirationUpdate(content, message, smsMessageId);
@ -293,8 +288,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
if (SessionMetaProtocol.shouldSendDeliveryReceipt(message, Address.fromSerialized(content.getSender()))) { if (SessionMetaProtocol.shouldSendDeliveryReceipt(message, Address.fromSerialized(content.getSender()))) {
handleNeedsDeliveryReceipt(content, message); handleNeedsDeliveryReceipt(content, message);
} }
} else if (content.getSyncMessage().isPresent()) {
throw new UnsupportedOperationException("Device link operations are not supported!");
} else if (content.getReceiptMessage().isPresent()) { } else if (content.getReceiptMessage().isPresent()) {
SignalServiceReceiptMessage message = content.getReceiptMessage().get(); SignalServiceReceiptMessage message = content.getReceiptMessage().get();
@ -311,37 +304,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
// if (envelope.isPreKeySignalMessage()) { // if (envelope.isPreKeySignalMessage()) {
// ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob()); // ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob());
// } // }
} catch (ProtocolInvalidVersionException e) {
Log.w(TAG, e);
handleInvalidVersionMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId);
} catch (ProtocolInvalidMessageException e) { } catch (ProtocolInvalidMessageException e) {
Log.w(TAG, e); Log.w(TAG, e);
if (!isPushNotification) { // This can be triggered if a PN encrypted with an old session comes in after the user performed a session reset if (!isPushNotification) { // This can be triggered if a PN encrypted with an old session comes in after the user performed a session reset
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e); handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e);
} }
} catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) { }catch (StorageFailedException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e); handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e);
} catch (StorageFailedException e) { } catch (InvalidMetadataMessageException e) {
Log.w(TAG, e); Log.w(TAG, e);
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e);
} 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 (IOException e) {
Log.i(TAG, "IOException during message decryption.");
} catch (SessionProtocol.Exception e) {
Log.i(TAG, "Couldn't handle message due to error: " + e.getDescription());
} }
} }
@ -417,7 +389,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
Optional.absent(), Optional.absent(),
Optional.absent(), Optional.absent(),
Optional.absent(), Optional.absent(),
Optional.absent(),
Optional.absent()); Optional.absent());
database.insertSecureDecryptedMessageInbox(mediaMessage, -1); database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
@ -448,7 +419,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote()); Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts()); Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts());
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or("")); Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
Optional<Attachment> sticker = getStickerAttachment(message.getSticker());
Address masterAddress = masterRecipient.getAddress(); Address masterAddress = masterRecipient.getAddress();
@ -523,7 +493,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else { } else {
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1, IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1,
message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(), message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
quote, sharedContacts, linkPreviews, sticker); quote, sharedContacts, linkPreviews);
MmsDatabase database = DatabaseFactory.getMmsDatabase(context); MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
database.beginTransaction(); database.beginTransaction();
@ -594,110 +564,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message) throws MmsException {
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipient = getSyncMessageMasterDestination(message);
OutgoingExpirationUpdateMessage expirationUpdateMessage = new OutgoingExpirationUpdateMessage(recipient,
message.getTimestamp(),
message.getMessage().getExpiresInSeconds() * 1000L);
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
long messageId = database.insertMessageOutbox(expirationUpdateMessage, threadId, false, null);
database.markAsSent(messageId, true);
DatabaseFactory.getRecipientDatabase(context).setExpireMessages(recipient, message.getMessage().getExpiresInSeconds());
return threadId;
}
public long handleSynchronizeSentMediaMessage(@NonNull SentTranscriptMessage message)
throws MmsException
{
if (SessionMetaProtocol.shouldIgnoreMessage(message.getTimestamp())) { return -1; }
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
Recipient recipients = getSyncMessageMasterDestination(message);
Optional<QuoteModel> quote = getValidatedQuote(message.getMessage().getQuote());
Optional<Attachment> sticker = getStickerAttachment(message.getMessage().getSticker());
Optional<List<Contact>> sharedContacts = getContacts(message.getMessage().getSharedContacts());
Optional<List<LinkPreview>> previews = getLinkPreviews(message.getMessage().getPreviews(), message.getMessage().getBody().or(""));
List<Attachment> syncAttachments = PointerAttachment.forPointers(message.getMessage().getAttachments());
if (sticker.isPresent()) {
syncAttachments.add(sticker.get());
}
OutgoingMediaMessage mediaMessage = new OutgoingMediaMessage(recipients, message.getMessage().getBody().orNull(),
syncAttachments,
message.getTimestamp(), -1,
message.getMessage().getExpiresInSeconds() * 1000,
ThreadDatabase.DistributionTypes.DEFAULT, quote.orNull(),
sharedContacts.or(Collections.emptyList()),
previews.or(Collections.emptyList()),
Collections.emptyList(), Collections.emptyList());
mediaMessage = new OutgoingSecureMediaMessage(mediaMessage);
if (recipients.getExpireMessages() != message.getMessage().getExpiresInSeconds()) {
handleSynchronizeSentExpirationUpdate(message);
}
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipients);
database.beginTransaction();
try {
long messageId = database.insertMessageOutbox(mediaMessage, threadId, false, null);
if (message.messageServerID >= 0) { DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageId, message.messageServerID); }
if (recipients.getAddress().isGroup()) {
GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipients.getAddress().toGroupString(), false);
for (Recipient member : members) {
receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize()));
}
}
database.markAsSent(messageId, true);
database.markUnidentified(messageId, message.isUnidentified(recipients.getAddress().serialize()));
List<DatabaseAttachment> allAttachments = DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageId);
List<DatabaseAttachment> stickerAttachments = Stream.of(allAttachments).filter(Attachment::isSticker).toList();
List<DatabaseAttachment> attachments = Stream.of(allAttachments).filterNot(Attachment::isSticker).toList();
forceStickerDownloadIfNecessary(stickerAttachments);
for (DatabaseAttachment attachment : attachments) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(messageId, attachment.getAttachmentId(), false));
}
if (message.getMessage().getExpiresInSeconds() > 0) {
database.markExpireStarted(messageId, message.getExpirationStartTimestamp());
ApplicationContext.getInstance(context)
.getExpiringMessageManager()
.scheduleDeletion(messageId, true,
message.getExpirationStartTimestamp(),
message.getMessage().getExpiresInSeconds() * 1000L);
}
if (recipients.isLocalNumber()) {
SyncMessageId id = new SyncMessageId(recipients.getAddress(), message.getTimestamp());
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis());
}
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
return threadId;
}
public void handleTextMessage(@NonNull SignalServiceContent content, public void handleTextMessage(@NonNull SignalServiceContent content,
@NonNull SignalServiceDataMessage message, @NonNull SignalServiceDataMessage message,
@NonNull Optional<Long> smsMessageId, @NonNull Optional<Long> smsMessageId,
@ -824,86 +690,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} }
} }
public long handleSynchronizeSentTextMessage(@NonNull SentTranscriptMessage message)
throws MmsException
{
if (SessionMetaProtocol.shouldIgnoreMessage(message.getTimestamp())) { return -1; }
Recipient recipient = getSyncMessageMasterDestination(message);
String body = message.getMessage().getBody().or("");
long expiresInMillis = message.getMessage().getExpiresInSeconds() * 1000L;
// Ignore the message if it has no body
if (body.isEmpty()) { return -1; }
if (recipient.getExpireMessages() != message.getMessage().getExpiresInSeconds()) {
handleSynchronizeSentExpirationUpdate(message);
}
long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient);
boolean isGroup = recipient.getAddress().isGroup();
MessagingDatabase database;
long messageId;
if (isGroup) {
OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getMessage().getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), Collections.emptyList());
outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage);
messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null,message.getTimestamp());
if (message.messageServerID >= 0) { DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageId, message.messageServerID); }
database = DatabaseFactory.getMmsDatabase(context);
GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.getAddress().toGroupString(), false);
for (Recipient member : members) {
receiptDatabase.setUnidentified(member.getAddress(), messageId, message.isUnidentified(member.getAddress().serialize()));
}
} else {
OutgoingTextMessage outgoingTextMessage = new OutgoingEncryptedMessage(recipient, body, expiresInMillis);
messageId = DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoingTextMessage, false, message.getTimestamp(), null);
database = DatabaseFactory.getSmsDatabase(context);
database.markUnidentified(messageId, message.isUnidentified(recipient.getAddress().serialize()));
}
database.markAsSent(messageId, true);
if (expiresInMillis > 0) {
database.markExpireStarted(messageId, message.getExpirationStartTimestamp());
ApplicationContext.getInstance(context)
.getExpiringMessageManager()
.scheduleDeletion(messageId, isGroup, message.getExpirationStartTimestamp(), expiresInMillis);
}
if (recipient.isLocalNumber()) {
SyncMessageId id = new SyncMessageId(recipient.getAddress(), message.getTimestamp());
DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(id, System.currentTimeMillis());
DatabaseFactory.getMmsSmsDatabase(context).incrementReadReceiptCount(id, System.currentTimeMillis());
}
return threadId;
}
private void handleInvalidVersionMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId)
{
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
if (!smsMessageId.isPresent()) {
Optional<InsertResult> insertResult = insertPlaceholder(sender, senderDevice, timestamp);
if (insertResult.isPresent()) {
smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId());
messageNotifier.updateNotification(context, insertResult.get().getThreadId());
}
} else {
smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get());
}
}
private void handleCorruptMessage(@NonNull String sender, int senderDevice, long timestamp, private void handleCorruptMessage(@NonNull String sender, int senderDevice, long timestamp,
@NonNull Optional<Long> smsMessageId, @NonNull Throwable e) @NonNull Optional<Long> smsMessageId, @NonNull Throwable e)
{ {
@ -1059,17 +845,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
long threadId; long threadId;
if (typingMessage.getGroupId().isPresent()) {
// Typing messages should only apply to closed groups, thus we use `getEncodedId`
Address groupAddress = Address.fromSerialized(GroupUtil.getEncodedClosedGroupID(typingMessage.getGroupId().get()));
Recipient groupRecipient = Recipient.from(context, groupAddress, false);
threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(groupRecipient);
} else {
// See if we need to redirect the message
author = getMessageMasterDestination(content.getSender()); author = getMessageMasterDestination(content.getSender());
threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(author); threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(author);
}
if (threadId <= 0) { if (threadId <= 0) {
Log.w(TAG, "Couldn't find a matching thread for a typing message."); Log.w(TAG, "Couldn't find a matching thread for a typing message.");
@ -1211,28 +988,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
return database.insertMessageInbox(textMessage); return database.insertMessageInbox(textMessage);
} }
private Recipient getSyncMessageDestination(SentTranscriptMessage message) {
if (message.getMessage().isGroupMessage()) {
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get())), false);
} else {
return Recipient.from(context, Address.fromSerialized(message.getDestination().get()), false);
}
}
private Recipient getSyncMessageMasterDestination(SentTranscriptMessage message) {
if (message.getMessage().isGroupMessage()) {
return Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get())), false);
} else {
String publicKey = message.getDestination().get();
String userPublicKey = TextSecurePreferences.getLocalNumber(context);
if (publicKey.equals(userPublicKey)) {
return Recipient.from(context, Address.fromSerialized(userPublicKey), false);
} else {
return Recipient.from(context, Address.fromSerialized(publicKey), false);
}
}
}
private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) { private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) {
if (message.getGroupInfo().isPresent()) { if (message.getGroupInfo().isPresent()) {
return Recipient.from(context, Address.fromExternal(context, GroupUtil.getEncodedClosedGroupID(message.getGroupInfo().get().getGroupId())), false); return Recipient.from(context, Address.fromExternal(context, GroupUtil.getEncodedClosedGroupID(message.getGroupInfo().get().getGroupId())), false);
@ -1309,8 +1064,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
} else { } else {
return sender.isBlocked(); return sender.isBlocked();
} }
} else if (content.getSyncMessage().isPresent()) {
throw new UnsupportedOperationException("Device link operations are not supported!");
} }
return false; return false;

View File

@ -21,7 +21,6 @@ import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import org.session.libsignal.service.api.messages.SignalServiceContent import org.session.libsignal.service.api.messages.SignalServiceContent
import org.session.libsignal.service.api.messages.SignalServiceDataMessage import org.session.libsignal.service.api.messages.SignalServiceDataMessage
import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.api.messages.multidevice.SentTranscriptMessage
import org.session.libsignal.service.api.push.SignalServiceAddress import org.session.libsignal.service.api.push.SignalServiceAddress
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import org.session.libsignal.service.loki.api.opengroups.PublicChat import org.session.libsignal.service.loki.api.opengroups.PublicChat
@ -222,16 +221,6 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB) FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB)
// Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below // Kovenant propagates a context to chained promises, so LokiPublicChatAPI.sharedContext should be used for all of the below
val promise = api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages -> val promise = api.getMessages(group.channel, group.server).bind(PublicChatAPI.sharedContext) { messages ->
/*
if (messages.isNotEmpty()) {
// We need to fetch the device mapping for any devices we don't have
uniqueDevices = messages.map { it.senderPublicKey }.toSet()
val devicesToUpdate = uniqueDevices.filter { !userDevices.contains(it) && FileServerAPI.shared.hasDeviceLinkCacheExpired(publicKey = it) }
if (devicesToUpdate.isNotEmpty()) {
return@bind FileServerAPI.shared.getDeviceLinks(devicesToUpdate.toSet()).then { messages }
}
}
*/
Promise.of(messages) Promise.of(messages)
} }
promise.successBackground { messages -> promise.successBackground { messages ->

View File

@ -13,6 +13,7 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.messages.SignalServiceGroup import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
@ -402,48 +403,48 @@ object ClosedGroupsProtocolV2 {
} }
@JvmStatic @JvmStatic
fun handleMessage(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { fun handleMessage(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
if (!isValid(context, closedGroupUpdate, senderPublicKey, sentTimestamp)) { return } if (!isValid(context, closedGroupUpdate, senderPublicKey, sentTimestamp)) { return }
when (closedGroupUpdate.type) { when (closedGroupUpdate.type) {
SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey, sentTimestamp) DataMessage.ClosedGroupControlMessage.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey, sentTimestamp)
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED -> handleClosedGroupMembersRemoved(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> handleClosedGroupMembersRemoved(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT -> handleClosedGroupMemberLeft(context, sentTimestamp, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> handleClosedGroupMemberLeft(context, sentTimestamp, groupPublicKey, senderPublicKey)
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE -> handleClosedGroupUpdate(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.UPDATE -> handleClosedGroupUpdate(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey) DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey)
else -> { else -> {
Log.d("Loki","Can't handle closed group update of unknown type: ${closedGroupUpdate.type}") Log.d("Loki","Can't handle closed group update of unknown type: ${closedGroupUpdate.type}")
} }
} }
} }
private fun isValid(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, senderPublicKey: String, sentTimestamp: Long): Boolean { private fun isValid(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, senderPublicKey: String, sentTimestamp: Long): Boolean {
val record = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(sentTimestamp, senderPublicKey) val record = DatabaseFactory.getMmsSmsDatabase(context).getMessageFor(sentTimestamp, senderPublicKey)
if (record != null) return false if (record != null) return false
return when (closedGroupUpdate.type) { return when (closedGroupUpdate.type) {
SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> { DataMessage.ClosedGroupControlMessage.Type.NEW -> {
(!closedGroupUpdate.publicKey.isEmpty && !closedGroupUpdate.name.isNullOrEmpty() && !(closedGroupUpdate.encryptionKeyPair.privateKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty (!closedGroupUpdate.publicKey.isEmpty && !closedGroupUpdate.name.isNullOrEmpty() && !(closedGroupUpdate.encryptionKeyPair.privateKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty
&& !(closedGroupUpdate.encryptionKeyPair.publicKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty && closedGroupUpdate.membersCount > 0 && closedGroupUpdate.adminsCount > 0) && !(closedGroupUpdate.encryptionKeyPair.publicKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty && closedGroupUpdate.membersCount > 0 && closedGroupUpdate.adminsCount > 0)
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED, DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED,
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED -> { DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> {
closedGroupUpdate.membersCount > 0 closedGroupUpdate.membersCount > 0
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT -> { DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> {
senderPublicKey.isNotEmpty() senderPublicKey.isNotEmpty()
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE, DataMessage.ClosedGroupControlMessage.Type.UPDATE,
SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE -> { DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> {
!closedGroupUpdate.name.isNullOrEmpty() !closedGroupUpdate.name.isNullOrEmpty()
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> true DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> true
else -> false else -> false
} }
} }
public fun handleNewClosedGroup(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, senderPublicKey: String, sentTimestamp: Long) { public fun handleNewClosedGroup(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, senderPublicKey: String, sentTimestamp: Long) {
// Prepare // Prepare
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
@ -483,7 +484,7 @@ object ClosedGroupsProtocolV2 {
LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey) LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
} }
fun handleClosedGroupMembersRemoved(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { fun handleClosedGroupMembersRemoved(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
val groupDB = DatabaseFactory.getGroupDatabase(context) val groupDB = DatabaseFactory.getGroupDatabase(context)
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
@ -536,7 +537,7 @@ object ClosedGroupsProtocolV2 {
} }
} }
fun handleClosedGroupMembersAdded(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { fun handleClosedGroupMembersAdded(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
val userPublicKey = TextSecurePreferences.getLocalNumber(context) val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
val groupDB = DatabaseFactory.getGroupDatabase(context) val groupDB = DatabaseFactory.getGroupDatabase(context)
@ -578,7 +579,7 @@ object ClosedGroupsProtocolV2 {
} }
} }
fun handleClosedGroupNameChange(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { fun handleClosedGroupNameChange(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
// Check that the sender is a member of the group (before the update) // Check that the sender is a member of the group (before the update)
val userPublicKey = TextSecurePreferences.getLocalNumber(context) val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val groupDB = DatabaseFactory.getGroupDatabase(context) val groupDB = DatabaseFactory.getGroupDatabase(context)
@ -646,7 +647,7 @@ object ClosedGroupsProtocolV2 {
} }
} }
private fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) { private fun handleClosedGroupUpdate(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, sentTimestamp: Long, groupPublicKey: String, senderPublicKey: String) {
// Prepare // Prepare
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!! val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
@ -728,7 +729,7 @@ object ClosedGroupsProtocolV2 {
return true return true
} }
private fun handleGroupEncryptionKeyPair(context: Context, closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdateV2, groupPublicKey: String, senderPublicKey: String) { private fun handleGroupEncryptionKeyPair(context: Context, closedGroupUpdate: DataMessage.ClosedGroupControlMessage, groupPublicKey: String, senderPublicKey: String) {
// Prepare // Prepare
val userPublicKey = TextSecurePreferences.getLocalNumber(context) val userPublicKey = TextSecurePreferences.getLocalNumber(context)
val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)

View File

@ -70,8 +70,7 @@ public class IncomingMediaMessage {
Optional<List<SignalServiceAttachment>> attachments, Optional<List<SignalServiceAttachment>> attachments,
Optional<QuoteModel> quote, Optional<QuoteModel> quote,
Optional<List<Contact>> sharedContacts, Optional<List<Contact>> sharedContacts,
Optional<List<LinkPreview>> linkPreviews, Optional<List<LinkPreview>> linkPreviews)
Optional<Attachment> sticker)
{ {
this.push = true; this.push = true;
this.from = from; this.from = from;
@ -89,10 +88,6 @@ public class IncomingMediaMessage {
this.attachments.addAll(PointerAttachment.forPointers(attachments)); this.attachments.addAll(PointerAttachment.forPointers(attachments));
this.sharedContacts.addAll(sharedContacts.or(Collections.emptyList())); this.sharedContacts.addAll(sharedContacts.or(Collections.emptyList()));
this.linkPreviews.addAll(linkPreviews.or(Collections.emptyList())); this.linkPreviews.addAll(linkPreviews.or(Collections.emptyList()));
if (sticker.isPresent()) {
this.attachments.add(sticker.get());
}
} }
public static IncomingMediaMessage from(VisibleMessage message, public static IncomingMediaMessage from(VisibleMessage message,
@ -104,7 +99,7 @@ public class IncomingMediaMessage {
Optional<List<LinkPreview>> linkPreviews) Optional<List<LinkPreview>> linkPreviews)
{ {
return new IncomingMediaMessage(from, message.getReceivedTimestamp(), -1, expiresIn, false, return new IncomingMediaMessage(from, message.getReceivedTimestamp(), -1, expiresIn, false,
false, Optional.fromNullable(message.getText()), group, attachments, quote, Optional.absent(), linkPreviews, Optional.absent()); false, Optional.fromNullable(message.getText()), group, attachments, quote, Optional.absent(), linkPreviews);
} }
public int getSubscriptionId() { public int getSubscriptionId() {

View File

@ -6,6 +6,7 @@ import org.session.libsignal.libsignal.ecc.DjbECPublicKey
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.utilities.logging.Log import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
@ -55,10 +56,10 @@ class ClosedGroupControlMessage() : ControlMessage() {
const val TAG = "ClosedGroupControlMessage" const val TAG = "ClosedGroupControlMessage"
fun fromProto(proto: SignalServiceProtos.Content): ClosedGroupControlMessage? { fun fromProto(proto: SignalServiceProtos.Content): ClosedGroupControlMessage? {
val closedGroupControlMessageProto = proto.dataMessage?.closedGroupUpdateV2 ?: return null val closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage ?: return null
val kind: Kind val kind: Kind
when(closedGroupControlMessageProto.type) { when(closedGroupControlMessageProto.type) {
SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> { DataMessage.ClosedGroupControlMessage.Type.NEW -> {
val publicKey = closedGroupControlMessageProto.publicKey ?: return null val publicKey = closedGroupControlMessageProto.publicKey ?: return null
val name = closedGroupControlMessageProto.name ?: return null val name = closedGroupControlMessageProto.name ?: return null
val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null
@ -71,29 +72,31 @@ class ClosedGroupControlMessage() : ControlMessage() {
return null return null
} }
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE -> { DataMessage.ClosedGroupControlMessage.Type.UPDATE -> {
val name = closedGroupControlMessageProto.name ?: return null val name = closedGroupControlMessageProto.name ?: return null
kind = Kind.Update(name, closedGroupControlMessageProto.membersList) kind = Kind.Update(name, closedGroupControlMessageProto.membersList)
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> { DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> {
val publicKey = closedGroupControlMessageProto.publicKey val publicKey = closedGroupControlMessageProto.publicKey
val wrappers = closedGroupControlMessageProto.wrappersList.mapNotNull { KeyPairWrapper.fromProto(it) } val wrappers = closedGroupControlMessageProto.wrappersList.mapNotNull { KeyPairWrapper.fromProto(it) }
kind = Kind.EncryptionKeyPair(publicKey, wrappers) kind = Kind.EncryptionKeyPair(publicKey, wrappers)
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE -> { DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> {
val name = closedGroupControlMessageProto.name ?: return null val name = closedGroupControlMessageProto.name ?: return null
kind = Kind.NameChange(name) kind = Kind.NameChange(name)
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED -> { DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED -> {
kind = Kind.MembersAdded(closedGroupControlMessageProto.membersList) kind = Kind.MembersAdded(closedGroupControlMessageProto.membersList)
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED -> { DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> {
kind = Kind.MembersRemoved(closedGroupControlMessageProto.membersList) kind = Kind.MembersRemoved(closedGroupControlMessageProto.membersList)
} }
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT -> { DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> {
kind = Kind.MemberLeft kind = Kind.MemberLeft
} }
//TODO: SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR_REQUEST DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR_REQUEST -> {
kind = Kind.EncryptionKeyPairRequest
}
} }
return ClosedGroupControlMessage(kind) return ClosedGroupControlMessage(kind)
} }
@ -130,10 +133,10 @@ class ClosedGroupControlMessage() : ControlMessage() {
return null return null
} }
try { try {
val closedGroupControlMessage: SignalServiceProtos.ClosedGroupUpdateV2.Builder = SignalServiceProtos.ClosedGroupUpdateV2.newBuilder() val closedGroupControlMessage: DataMessage.ClosedGroupControlMessage.Builder = DataMessage.ClosedGroupControlMessage.newBuilder()
when (kind) { when (kind) {
is Kind.New -> { is Kind.New -> {
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.NEW
closedGroupControlMessage.publicKey = kind.publicKey closedGroupControlMessage.publicKey = kind.publicKey
closedGroupControlMessage.name = kind.name closedGroupControlMessage.name = kind.name
val encryptionKeyPairAsProto = SignalServiceProtos.KeyPair.newBuilder() val encryptionKeyPairAsProto = SignalServiceProtos.KeyPair.newBuilder()
@ -150,37 +153,37 @@ class ClosedGroupControlMessage() : ControlMessage() {
closedGroupControlMessage.addAllAdmins(kind.admins) closedGroupControlMessage.addAllAdmins(kind.admins)
} }
is Kind.Update -> { is Kind.Update -> {
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.UPDATE
closedGroupControlMessage.name = kind.name closedGroupControlMessage.name = kind.name
closedGroupControlMessage.addAllMembers(kind.members) closedGroupControlMessage.addAllMembers(kind.members)
} }
is Kind.EncryptionKeyPair -> { is Kind.EncryptionKeyPair -> {
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR
closedGroupControlMessage.publicKey = kind.publicKey closedGroupControlMessage.publicKey = kind.publicKey
closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() }) closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() })
} }
is Kind.NameChange -> { is Kind.NameChange -> {
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE
closedGroupControlMessage.name = kind.name closedGroupControlMessage.name = kind.name
} }
is Kind.MembersAdded -> { is Kind.MembersAdded -> {
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED
closedGroupControlMessage.addAllMembers(kind.members) closedGroupControlMessage.addAllMembers(kind.members)
} }
is Kind.MembersRemoved -> { is Kind.MembersRemoved -> {
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED
closedGroupControlMessage.addAllMembers(kind.members) closedGroupControlMessage.addAllMembers(kind.members)
} }
is Kind.MemberLeft -> { is Kind.MemberLeft -> {
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT
} }
is Kind.EncryptionKeyPairRequest -> { is Kind.EncryptionKeyPairRequest -> {
// TODO: closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR_REQUEST // TODO: closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR_REQUEST
} }
} }
val contentProto = SignalServiceProtos.Content.newBuilder() val contentProto = SignalServiceProtos.Content.newBuilder()
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder() val dataMessageProto = DataMessage.newBuilder()
dataMessageProto.closedGroupUpdateV2 = closedGroupControlMessage.build() dataMessageProto.closedGroupControlMessage = closedGroupControlMessage.build()
// Group context // Group context
contentProto.dataMessage = dataMessageProto.build() contentProto.dataMessage = dataMessageProto.build()
return contentProto.build() return contentProto.build()
@ -197,15 +200,15 @@ class ClosedGroupControlMessage() : ControlMessage() {
} }
companion object { companion object {
fun fromProto(proto: SignalServiceProtos.ClosedGroupUpdateV2.KeyPairWrapper): KeyPairWrapper { fun fromProto(proto: DataMessage.ClosedGroupControlMessage.KeyPairWrapper): KeyPairWrapper {
return KeyPairWrapper(proto.publicKey.toByteArray().toHexString(), proto.encryptedKeyPair) return KeyPairWrapper(proto.publicKey.toByteArray().toHexString(), proto.encryptedKeyPair)
} }
} }
fun toProto(): SignalServiceProtos.ClosedGroupUpdateV2.KeyPairWrapper? { fun toProto(): DataMessage.ClosedGroupControlMessage.KeyPairWrapper? {
val publicKey = publicKey ?: return null val publicKey = publicKey ?: return null
val encryptedKeyPair = encryptedKeyPair ?: return null val encryptedKeyPair = encryptedKeyPair ?: return null
val result = SignalServiceProtos.ClosedGroupUpdateV2.KeyPairWrapper.newBuilder() val result = DataMessage.ClosedGroupControlMessage.KeyPairWrapper.newBuilder()
result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey)) result.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(publicKey))
result.encryptedKeyPair = encryptedKeyPair result.encryptedKeyPair = encryptedKeyPair

View File

@ -1,35 +0,0 @@
package org.session.libsession.messaging.messages.control.unused
import com.google.protobuf.ByteString
import org.session.libsession.messaging.messages.control.ControlMessage
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
import java.security.SecureRandom
class NullMessage() : ControlMessage() {
companion object {
const val TAG = "NullMessage"
fun fromProto(proto: SignalServiceProtos.Content): NullMessage? {
if (proto.nullMessage == null) return null
return NullMessage()
}
}
override fun toProto(): SignalServiceProtos.Content? {
val nullMessageProto = SignalServiceProtos.NullMessage.newBuilder()
val sr = SecureRandom()
val paddingSize = sr.nextInt(512)
val padding = ByteArray(paddingSize)
nullMessageProto.padding = ByteString.copyFrom(padding)
val contentProto = SignalServiceProtos.Content.newBuilder()
try {
contentProto.nullMessage = nullMessageProto.build()
return contentProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct null message proto from: $this")
return null
}
}
}

View File

@ -1,83 +0,0 @@
package org.session.libsession.messaging.messages.control.unused
import com.google.protobuf.ByteString
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.messages.control.ControlMessage
import org.session.libsignal.libsignal.IdentityKey
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.libsignal.state.PreKeyBundle
import org.session.libsignal.service.internal.push.SignalServiceProtos
import java.security.SecureRandom
class SessionRequest() : ControlMessage() {
var preKeyBundle: PreKeyBundle? = null
companion object {
const val TAG = "SessionRequest"
fun fromProto(proto: SignalServiceProtos.Content): SessionRequest? {
if (proto.nullMessage == null) return null
val preKeyBundleProto = proto.preKeyBundleMessage ?: return null
var registrationID: Int = 0
registrationID = MessagingConfiguration.shared.storage.getOrGenerateRegistrationID() //TODO no implementation for getOrGenerateRegistrationID yet
//TODO just confirm if the above code does the equivalent to swift below:
/*iOS code: Configuration.shared.storage.with { transaction in
registrationID = Configuration.shared.storage.getOrGenerateRegistrationID(using: transaction)
}*/
val preKeyBundle = PreKeyBundle(
registrationID,
1,
preKeyBundleProto.preKeyId,
DjbECPublicKey(preKeyBundleProto.preKey.toByteArray()),
preKeyBundleProto.signedKeyId,
DjbECPublicKey(preKeyBundleProto.signedKey.toByteArray()),
preKeyBundleProto.signature.toByteArray(),
IdentityKey(DjbECPublicKey(preKeyBundleProto.identityKey.toByteArray()))
)
return SessionRequest(preKeyBundle)
}
}
//constructor
internal constructor(preKeyBundle: PreKeyBundle) : this() {
this.preKeyBundle = preKeyBundle
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
return preKeyBundle != null
}
override fun toProto(): SignalServiceProtos.Content? {
val preKeyBundle = preKeyBundle
if (preKeyBundle == null) {
Log.w(TAG, "Couldn't construct session request proto from: $this")
return null
}
val nullMessageProto = SignalServiceProtos.NullMessage.newBuilder()
val sr = SecureRandom()
val paddingSize = sr.nextInt(512)
val padding = ByteArray(paddingSize)
nullMessageProto.padding = ByteString.copyFrom(padding)
val preKeyBundleProto = SignalServiceProtos.PreKeyBundleMessage.newBuilder()
preKeyBundleProto.identityKey = ByteString.copyFrom(preKeyBundle.identityKey.publicKey.serialize())
preKeyBundleProto.deviceId = preKeyBundle.deviceId
preKeyBundleProto.preKeyId = preKeyBundle.preKeyId
preKeyBundleProto.preKey = ByteString.copyFrom(preKeyBundle.preKey.serialize())
preKeyBundleProto.signedKeyId = preKeyBundle.signedPreKeyId
preKeyBundleProto.signedKey = ByteString.copyFrom(preKeyBundle.signedPreKey.serialize())
preKeyBundleProto.signature = ByteString.copyFrom(preKeyBundle.signedPreKeySignature)
val contentProto = SignalServiceProtos.Content.newBuilder()
try {
contentProto.nullMessage = nullMessageProto.build()
contentProto.preKeyBundleMessage = preKeyBundleProto.build()
return contentProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct session request proto from: $this")
return null
}
}
}

View File

@ -17,7 +17,7 @@ class Profile() {
val profileProto = proto.profile ?: return null val profileProto = proto.profile ?: return null
val displayName = profileProto.displayName ?: return null val displayName = profileProto.displayName ?: return null
val profileKey = proto.profileKey val profileKey = proto.profileKey
val profilePictureURL = profileProto.profilePictureURL val profilePictureURL = profileProto.profilePicture
profileKey?.let { profileKey?.let {
profilePictureURL?.let { profilePictureURL?.let {
return Profile(displayName = displayName, profileKey = profileKey.toByteArray(), profilePictureURL = profilePictureURL) return Profile(displayName = displayName, profileKey = profileKey.toByteArray(), profilePictureURL = profilePictureURL)
@ -41,12 +41,12 @@ class Profile() {
return null return null
} }
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder() val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
val profileProto = SignalServiceProtos.LokiUserProfile.newBuilder() val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder()
profileProto.displayName = displayName profileProto.displayName = displayName
val profileKey = profileKey val profileKey = profileKey
profileKey?.let { dataMessageProto.profileKey = ByteString.copyFrom(profileKey) } profileKey?.let { dataMessageProto.profileKey = ByteString.copyFrom(profileKey) }
val profilePictureURL = profilePictureURL val profilePictureURL = profilePictureURL
profilePictureURL?.let { profileProto.profilePictureURL = profilePictureURL } profilePictureURL?.let { profileProto.profilePicture = profilePictureURL }
// Build // Build
try { try {
dataMessageProto.profile = profileProto.build() dataMessageProto.profile = profileProto.build()

View File

@ -162,11 +162,11 @@ class OpenGroupPoller(private val openGroup: OpenGroup) {
} }
val messageServerID = message.serverID val messageServerID = message.serverID
// Profile // Profile
val profileProto = LokiUserProfile.newBuilder() val profileProto = DataMessage.LokiProfile.newBuilder()
profileProto.setDisplayName(message.displayName) profileProto.setDisplayName(message.displayName)
val profilePicture = message.profilePicture val profilePicture = message.profilePicture
if (profilePicture != null) { if (profilePicture != null) {
profileProto.setProfilePictureURL(profilePicture.url) profileProto.setProfilePicture(profilePicture.url)
dataMessageProto.setProfileKey(ByteString.copyFrom(profilePicture.profileKey)) dataMessageProto.setProfileKey(ByteString.copyFrom(profilePicture.profileKey))
} }
dataMessageProto.setProfile(profileProto.build()) dataMessageProto.setProfile(profileProto.build())

View File

@ -1,17 +0,0 @@
syntax = "proto2";
package textsecure;
option java_package = "org.session.libsignal.libsignal.fingerprint";
option java_outer_classname = "FingerprintProtos";
message LogicalFingerprint {
optional bytes content = 1;
// optional bytes identifier = 2;
}
message CombinedFingerprints {
optional uint32 version = 1;
optional LogicalFingerprint localFingerprint = 2;
optional LogicalFingerprint remoteFingerprint = 3;
}

View File

@ -1,114 +0,0 @@
syntax = "proto2";
package textsecure;
option java_package = "org.session.libsignal.libsignal.state";
option java_outer_classname = "StorageProtos";
message SessionStructure {
message Chain {
optional bytes senderRatchetKey = 1;
optional bytes senderRatchetKeyPrivate = 2;
message ChainKey {
optional uint32 index = 1;
optional bytes key = 2;
}
optional ChainKey chainKey = 3;
message MessageKey {
optional uint32 index = 1;
optional bytes cipherKey = 2;
optional bytes macKey = 3;
optional bytes iv = 4;
}
repeated MessageKey messageKeys = 4;
}
message PendingKeyExchange {
optional uint32 sequence = 1;
optional bytes localBaseKey = 2;
optional bytes localBaseKeyPrivate = 3;
optional bytes localRatchetKey = 4;
optional bytes localRatchetKeyPrivate = 5;
optional bytes localIdentityKey = 7;
optional bytes localIdentityKeyPrivate = 8;
}
message PendingPreKey {
optional uint32 preKeyId = 1;
optional int32 signedPreKeyId = 3;
optional bytes baseKey = 2;
}
optional uint32 sessionVersion = 1;
optional bytes localIdentityPublic = 2;
optional bytes remoteIdentityPublic = 3;
optional bytes rootKey = 4;
optional uint32 previousCounter = 5;
optional Chain senderChain = 6;
repeated Chain receiverChains = 7;
optional PendingKeyExchange pendingKeyExchange = 8;
optional PendingPreKey pendingPreKey = 9;
optional uint32 remoteRegistrationId = 10;
optional uint32 localRegistrationId = 11;
optional bool needsRefresh = 12;
optional bytes aliceBaseKey = 13;
}
message RecordStructure {
optional SessionStructure currentSession = 1;
repeated SessionStructure previousSessions = 2;
}
message PreKeyRecordStructure {
optional uint32 id = 1;
optional bytes publicKey = 2;
optional bytes privateKey = 3;
}
message SignedPreKeyRecordStructure {
optional uint32 id = 1;
optional bytes publicKey = 2;
optional bytes privateKey = 3;
optional bytes signature = 4;
optional fixed64 timestamp = 5;
}
message IdentityKeyPairStructure {
optional bytes publicKey = 1;
optional bytes privateKey = 2;
}
message SenderKeyStateStructure {
message SenderChainKey {
optional uint32 iteration = 1;
optional bytes seed = 2;
}
message SenderMessageKey {
optional uint32 iteration = 1;
optional bytes seed = 2;
}
message SenderSigningKey {
optional bytes public = 1;
optional bytes private = 2;
}
optional uint32 senderKeyId = 1;
optional SenderChainKey senderChainKey = 2;
optional SenderSigningKey senderSigningKey = 3;
repeated SenderMessageKey senderMessageKeys = 4;
}
message SenderKeyRecordStructure {
repeated SenderKeyStateStructure senderKeyStates = 1;
}

View File

@ -1,5 +1,5 @@
all: all:
protoc25 --java_out=../src/main/java/ SignalService.proto Provisioning.proto WebSocketResources.proto StickerResources.proto protoc25 --java_out=../src/main/java/ SignalService.proto WebSocketResources.proto
protoc25 --java_out=../src/main/java/ UnidentifiedDelivery.proto protoc25 --java_out=../src/main/java/ UnidentifiedDelivery.proto
protoc25 --java_out=../src/main/java/ WhisperTextProtocol.proto LocalStorageProtocol.proto FingerprintProtocol.proto protoc25 --java_out=../src/main/java/ WhisperTextProtocol.proto

View File

@ -1,27 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
syntax = "proto2";
package signalservice;
option java_package = "org.session.libsignal.service.internal.push";
option java_outer_classname = "ProvisioningProtos";
message ProvisionEnvelope {
optional bytes publicKey = 1;
optional bytes body = 2; // Encrypted ProvisionMessage
}
message ProvisionMessage {
optional bytes identityKeyPublic = 1;
optional bytes identityKeyPrivate = 2;
optional string number = 3;
optional string provisioningCode = 4;
optional string userAgent = 5;
optional bytes profileKey = 6;
optional bool readReceipts = 7;
}

View File

@ -1,9 +1,3 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
syntax = "proto2"; syntax = "proto2";
package signalservice; package signalservice;
@ -12,88 +6,40 @@ option java_package = "org.session.libsignal.service.internal.push";
option java_outer_classname = "SignalServiceProtos"; option java_outer_classname = "SignalServiceProtos";
message Envelope { message Envelope {
enum Type { enum Type {
UNKNOWN = 0;
CIPHERTEXT = 1;
KEY_EXCHANGE = 2;
PREKEY_BUNDLE = 3;
RECEIPT = 5;
UNIDENTIFIED_SENDER = 6; UNIDENTIFIED_SENDER = 6;
CLOSED_GROUP_CIPHERTEXT = 7; // Loki CLOSED_GROUP_CIPHERTEXT = 7;
FALLBACK_MESSAGE = 101; // Loki - Encrypted using the fallback session cipher. Contains a pre key bundle if it's a session request.
} }
optional Type type = 1; // @required
required Type type = 1;
optional string source = 2; optional string source = 2;
optional uint32 sourceDevice = 7; optional uint32 sourceDevice = 7;
optional string relay = 3; // @required
optional uint64 timestamp = 5; optional uint64 timestamp = 5;
optional bytes legacyMessage = 6; // Contains an encrypted DataMessage optional bytes content = 8;
optional bytes content = 8; // Contains an encrypted Content
optional string serverGuid = 9;
optional uint64 serverTimestamp = 10; optional uint64 serverTimestamp = 10;
} }
message TypingMessage {
enum Action {
STARTED = 0;
STOPPED = 1;
}
// @required
optional uint64 timestamp = 1;
// @required
optional Action action = 2;
}
message Content { message Content {
optional DataMessage dataMessage = 1; optional DataMessage dataMessage = 1;
optional SyncMessage syncMessage = 2 [deprecated=true];
optional CallMessage callMessage = 3;
optional NullMessage nullMessage = 4;
optional ReceiptMessage receiptMessage = 5; optional ReceiptMessage receiptMessage = 5;
optional TypingMessage typingMessage = 6; optional TypingMessage typingMessage = 6;
optional ConfigurationMessage configurationMessage = 7; optional ConfigurationMessage configurationMessage = 7;
optional PreKeyBundleMessage preKeyBundleMessage = 101; // Loki
optional DeviceLinkMessage deviceLinkMessage = 103; // Loki
}
message DeviceLinkMessage {
optional string primaryPublicKey = 1;
optional string secondaryPublicKey = 2;
optional bytes requestSignature = 3;
optional bytes authorizationSignature = 4;
}
message PreKeyBundleMessage {
optional bytes identityKey = 1;
optional uint32 deviceId = 2;
optional uint32 preKeyId = 3;
optional uint32 signedKeyId = 4;
optional bytes preKey = 5;
optional bytes signedKey = 6;
optional bytes signature = 7;
}
message CallMessage {
message Offer {
optional uint64 id = 1;
optional string description = 2;
}
message Answer {
optional uint64 id = 1;
optional string description = 2;
}
message IceUpdate {
optional uint64 id = 1;
optional string sdpMid = 2;
optional uint32 sdpMLineIndex = 3;
optional string sdp = 4;
}
message Busy {
optional uint64 id = 1;
}
message Hangup {
optional uint64 id = 1;
}
optional Offer offer = 1;
optional Answer answer = 2;
repeated IceUpdate iceUpdate = 3;
optional Hangup hangup = 4;
optional Busy busy = 5;
} }
message ClosedGroupCiphertextMessageWrapper { message ClosedGroupCiphertextMessageWrapper {
@ -103,28 +49,43 @@ message ClosedGroupCiphertextMessageWrapper {
optional bytes ephemeralPublicKey = 2; optional bytes ephemeralPublicKey = 2;
} }
message KeyPair {
// @required
required bytes publicKey = 1;
// @required
required bytes privateKey = 2;
}
message DataMessage { message DataMessage {
enum Flags { enum Flags {
END_SESSION = 1;
EXPIRATION_TIMER_UPDATE = 2; EXPIRATION_TIMER_UPDATE = 2;
PROFILE_KEY_UPDATE = 4;
DEVICE_UNLINKING_REQUEST = 128;
} }
message Quote { message Quote {
message QuotedAttachment { message QuotedAttachment {
enum Flags {
VOICE_MESSAGE = 1;
}
optional string contentType = 1; optional string contentType = 1;
optional string fileName = 2; optional string fileName = 2;
optional AttachmentPointer thumbnail = 3; optional AttachmentPointer thumbnail = 3;
optional uint32 flags = 4;
} }
// @required
optional uint64 id = 1; optional uint64 id = 1;
// @required
optional string author = 2; optional string author = 2;
optional string text = 3; optional string text = 3;
repeated QuotedAttachment attachments = 4; repeated QuotedAttachment attachments = 4;
} }
message Contact { message Contact {
message Name { message Name {
optional string givenName = 1; optional string givenName = 1;
optional string familyName = 2; optional string familyName = 2;
@ -135,6 +96,7 @@ message DataMessage {
} }
message Phone { message Phone {
enum Type { enum Type {
HOME = 1; HOME = 1;
MOBILE = 2; MOBILE = 2;
@ -148,6 +110,7 @@ message DataMessage {
} }
message Email { message Email {
enum Type { enum Type {
HOME = 1; HOME = 1;
MOBILE = 2; MOBILE = 2;
@ -161,6 +124,7 @@ message DataMessage {
} }
message PostalAddress { message PostalAddress {
enum Type { enum Type {
HOME = 1; HOME = 1;
WORK = 2; WORK = 2;
@ -192,50 +156,28 @@ message DataMessage {
} }
message Preview { message Preview {
// @required
optional string url = 1; optional string url = 1;
optional string title = 2; optional string title = 2;
optional AttachmentPointer image = 3; optional AttachmentPointer image = 3;
} }
message Sticker { message LokiProfile {
optional bytes packId = 1; optional string displayName = 1;
optional bytes packKey = 2; optional string profilePicture = 2;
optional uint32 stickerId = 3;
optional AttachmentPointer data = 4;
} }
optional string body = 1; message ClosedGroupControlMessage {
repeated AttachmentPointer attachments = 2;
optional GroupContext group = 3;
optional uint32 flags = 4;
optional uint32 expireTimer = 5;
optional bytes profileKey = 6;
optional uint64 timestamp = 7;
optional Quote quote = 8;
repeated Contact contact = 9;
repeated Preview preview = 10;
optional Sticker sticker = 11;
optional LokiUserProfile profile = 101; // Loki - The profile of the current user
optional ClosedGroupUpdate closedGroupUpdate = 103; // Loki
optional ClosedGroupUpdateV2 closedGroupUpdateV2 = 104;
optional string syncTarget = 105;
}
message LokiUserProfile {
optional string displayName = 1;
optional string profilePictureURL = 2;
}
message ClosedGroupUpdateV2 {
enum Type { enum Type {
NEW = 1; // publicKey, name, encryptionKeyPair, members, admins NEW = 1; // publicKey, name, encryptionKeyPair, members, admins
UPDATE = 2; // name, members UPDATE = 2; // name, members
ENCRYPTION_KEY_PAIR = 3; // wrappers ENCRYPTION_KEY_PAIR = 3; // publicKey, wrappers
NAME_CHANGE = 4; // name NAME_CHANGE = 4; // name
MEMBERS_ADDED = 5; // members MEMBERS_ADDED = 5; // members
MEMBERS_REMOVED = 6; // members MEMBERS_REMOVED = 6; // members
MEMBER_LEFT = 7; MEMBER_LEFT = 7;
ENCRYPTION_KEY_PAIR_REQUEST = 8;
} }
message KeyPairWrapper { message KeyPairWrapper {
@ -253,67 +195,22 @@ message ClosedGroupUpdateV2 {
repeated bytes members = 5; repeated bytes members = 5;
repeated bytes admins = 6; repeated bytes admins = 6;
repeated KeyPairWrapper wrappers = 7; repeated KeyPairWrapper wrappers = 7;
}
message KeyPair {
// @required
required bytes publicKey = 1;
// @required
required bytes privateKey = 2;
}
message ClosedGroupUpdate {
enum Type {
NEW = 0; // groupPublicKey, name, groupPrivateKey, senderKeys, members, admins
INFO = 1; // groupPublicKey, name, senderKeys, members, admins
SENDER_KEY_REQUEST = 2; // groupPublicKey
SENDER_KEY = 3; // groupPublicKey, senderKeys
} }
message SenderKey { optional string body = 1;
// @required repeated AttachmentPointer attachments = 2;
optional bytes chainKey = 1; optional GroupContext group = 3;
// @required optional uint32 flags = 4;
optional uint32 keyIndex = 2; optional uint32 expireTimer = 5;
// @required optional bytes profileKey = 6;
optional bytes publicKey = 3; optional uint64 timestamp = 7;
} optional Quote quote = 8;
repeated Contact contact = 9;
optional string name = 1; repeated Preview preview = 10;
// @required optional LokiProfile profile = 101;
optional bytes groupPublicKey = 2; optional ClosedGroupControlMessage closedGroupControlMessage = 104;
optional bytes groupPrivateKey = 3; optional string syncTarget = 105;
repeated SenderKey senderKeys = 4; optional PublicChatInfo publicChatInfo = 999;
repeated bytes members = 5;
repeated bytes admins = 6;
// @required
optional Type type = 7;
}
message NullMessage {
optional bytes padding = 1;
}
message ReceiptMessage {
enum Type {
DELIVERY = 0;
READ = 1;
}
optional Type type = 1;
repeated uint64 timestamp = 2;
}
message TypingMessage {
enum Action {
STARTED = 0;
STOPPED = 1;
}
optional uint64 timestamp = 1;
optional Action action = 2;
optional bytes groupId = 3;
} }
message ConfigurationMessage { message ConfigurationMessage {
@ -330,107 +227,25 @@ message ConfigurationMessage {
repeated string openGroups = 2; repeated string openGroups = 2;
} }
message Verified { message ReceiptMessage {
enum State {
DEFAULT = 0;
VERIFIED = 1;
UNVERIFIED = 2;
}
optional string destination = 1;
optional bytes identityKey = 2;
optional State state = 3;
optional bytes nullMessage = 4;
}
message SyncMessage {
message Sent {
message UnidentifiedDeliveryStatus {
optional string destination = 1;
optional bool unidentified = 2;
}
optional string destination = 1;
optional uint64 timestamp = 2;
optional DataMessage message = 3;
optional uint64 expirationStartTimestamp = 4;
repeated UnidentifiedDeliveryStatus unidentifiedStatus = 5;
}
message Contacts {
optional AttachmentPointer blob = 1;
optional bool complete = 2 [default = false];
optional bytes data = 101;
}
message Groups {
optional AttachmentPointer blob = 1;
optional bytes data = 101;
}
message Blocked {
repeated string numbers = 1;
repeated bytes groupIds = 2;
}
message Request {
enum Type { enum Type {
UNKNOWN = 0; DELIVERY = 0;
CONTACTS = 1; READ = 1;
GROUPS = 2;
BLOCKED = 3;
CONFIGURATION = 4;
} }
// @required
optional Type type = 1; optional Type type = 1;
} repeated uint64 timestamp = 2;
message Read {
optional string sender = 1;
optional uint64 timestamp = 2;
}
message Configuration {
optional bool readReceipts = 1;
optional bool unidentifiedDeliveryIndicators = 2;
optional bool typingIndicators = 3;
optional bool linkPreviews = 4;
}
message StickerPackOperation {
enum Type {
INSTALL = 0;
REMOVE = 1;
}
optional bytes packId = 1;
optional bytes packKey = 2;
optional Type type = 3;
}
message OpenGroupDetails {
optional string url = 1;
optional uint32 channelID = 2;
}
optional Sent sent = 1;
optional Contacts contacts = 2;
optional Groups groups = 3;
optional Request request = 4;
repeated Read read = 5;
optional Blocked blocked = 6;
optional Verified verified = 7;
optional Configuration configuration = 9;
optional bytes padding = 8;
repeated StickerPackOperation stickerPackOperation = 10;
repeated OpenGroupDetails openGroups = 100;
} }
message AttachmentPointer { message AttachmentPointer {
enum Flags { enum Flags {
VOICE_MESSAGE = 1; VOICE_MESSAGE = 1;
} }
// @required
optional fixed64 id = 1; optional fixed64 id = 1;
optional string contentType = 2; optional string contentType = 2;
optional bytes key = 3; optional bytes key = 3;
@ -446,6 +261,7 @@ message AttachmentPointer {
} }
message GroupContext { message GroupContext {
enum Type { enum Type {
UNKNOWN = 0; UNKNOWN = 0;
UPDATE = 1; UPDATE = 1;
@ -453,43 +269,43 @@ message GroupContext {
QUIT = 3; QUIT = 3;
REQUEST_INFO = 4; REQUEST_INFO = 4;
} }
// @required
optional bytes id = 1; optional bytes id = 1;
// @required
optional Type type = 2; optional Type type = 2;
optional string name = 3; optional string name = 3;
repeated string members = 4; repeated string members = 4;
optional AttachmentPointer avatar = 5; optional AttachmentPointer avatar = 5;
repeated string admins = 6; repeated string admins = 6;
// Loki - These fields are only used internally for the Android code base.
// This is so that we can differentiate adding/kicking.
// DO NOT USE WHEN SENDING MESSAGES.
repeated string newMembers = 998;
repeated string removedMembers = 999;
} }
message ContactDetails { message ContactDetails {
message Avatar { message Avatar {
optional string contentType = 1; optional string contentType = 1;
optional uint32 length = 2; optional uint32 length = 2;
} }
// @required
optional string number = 1; optional string number = 1;
optional string name = 2; optional string name = 2;
optional Avatar avatar = 3; optional Avatar avatar = 3;
optional string color = 4; optional string color = 4;
optional Verified verified = 5;
optional bytes profileKey = 6; optional bytes profileKey = 6;
optional bool blocked = 7; optional bool blocked = 7;
optional uint32 expireTimer = 8; optional uint32 expireTimer = 8;
optional string nickname = 101; // Loki optional string nickname = 101;
} }
message GroupDetails { message GroupDetails {
message Avatar { message Avatar {
optional string contentType = 1; optional string contentType = 1;
optional uint32 length = 2; optional uint32 length = 2;
} }
// @required
optional bytes id = 1; optional bytes id = 1;
optional string name = 2; optional string name = 2;
repeated string members = 3; repeated string members = 3;
@ -500,3 +316,7 @@ message GroupDetails {
optional bool blocked = 8; optional bool blocked = 8;
repeated string admins = 9; repeated string admins = 9;
} }
message PublicChatInfo { // Intended for internal use only
optional uint64 serverID = 1;
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (C) 2019 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
syntax = "proto2";
package signalservice;
option java_package = "org.session.libsignal.service.internal.sticker";
option java_outer_classname = "StickerProtos";
message Pack {
message Sticker {
optional uint32 id = 1;
optional string emoji = 2;
}
optional string title = 1;
optional string author = 2;
optional Sticker cover = 3;
repeated Sticker stickers = 4;
}

View File

@ -24,7 +24,6 @@ import org.session.libsignal.service.api.crypto.InvalidCiphertextException;
import org.session.libsignal.service.api.crypto.ProfileCipher; import org.session.libsignal.service.api.crypto.ProfileCipher;
import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream; import org.session.libsignal.service.api.crypto.ProfileCipherOutputStream;
import org.session.libsignal.service.api.messages.calls.TurnServerInfo; import org.session.libsignal.service.api.messages.calls.TurnServerInfo;
import org.session.libsignal.service.api.messages.multidevice.DeviceInfo;
import org.session.libsignal.service.api.push.ContactTokenDetails; import org.session.libsignal.service.api.push.ContactTokenDetails;
import org.session.libsignal.service.api.push.SignedPreKeyEntity; import org.session.libsignal.service.api.push.SignedPreKeyEntity;
import org.session.libsignal.service.api.util.CredentialsProvider; import org.session.libsignal.service.api.util.CredentialsProvider;
@ -376,10 +375,6 @@ public class SignalServiceAccountManager {
this.pushServiceSocket.sendProvisioningMessage(deviceIdentifier, ciphertext); this.pushServiceSocket.sendProvisioningMessage(deviceIdentifier, ciphertext);
} }
public List<DeviceInfo> getDevices() throws IOException {
return this.pushServiceSocket.getDevices();
}
public void removeDevice(long deviceId) throws IOException { public void removeDevice(long deviceId) throws IOException {
this.pushServiceSocket.removeDevice(deviceId); this.pushServiceSocket.removeDevice(deviceId);
} }

View File

@ -225,19 +225,16 @@ public class SignalServiceMessageReceiver {
if (entity.getSource() != null && entity.getSourceDevice() > 0) { if (entity.getSource() != null && entity.getSourceDevice() > 0) {
envelope = new SignalServiceEnvelope(entity.getType(), entity.getSource(), envelope = new SignalServiceEnvelope(entity.getType(), entity.getSource(),
entity.getSourceDevice(), entity.getTimestamp(), entity.getSourceDevice(), entity.getTimestamp(),
entity.getMessage(), entity.getContent(), entity.getContent(), entity.getServerTimestamp());
entity.getServerTimestamp(), entity.getServerUuid());
} else { } else {
envelope = new SignalServiceEnvelope(entity.getType(), entity.getTimestamp(), envelope = new SignalServiceEnvelope(entity.getType(), entity.getTimestamp(),
entity.getMessage(), entity.getContent(), entity.getContent(), entity.getServerTimestamp());
entity.getServerTimestamp(), entity.getServerUuid());
} }
callback.onMessage(envelope); callback.onMessage(envelope);
results.add(envelope); results.add(envelope);
if (envelope.hasUuid()) socket.acknowledgeMessage(envelope.getUuid()); socket.acknowledgeMessage(entity.getSource(), entity.getTimestamp());
else socket.acknowledgeMessage(entity.getSource(), entity.getTimestamp());
} }
return results; return results;

View File

@ -26,13 +26,6 @@ import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceGroup; import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage; import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage; import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
import org.session.libsignal.service.api.messages.multidevice.BlockedListMessage;
import org.session.libsignal.service.api.messages.multidevice.ConfigurationMessage;
import org.session.libsignal.service.api.messages.multidevice.ReadMessage;
import org.session.libsignal.service.api.messages.multidevice.SentTranscriptMessage;
import org.session.libsignal.service.api.messages.multidevice.SignalServiceSyncMessage;
import org.session.libsignal.service.api.messages.multidevice.StickerPackOperationMessage;
import org.session.libsignal.service.api.messages.multidevice.VerifiedMessage;
import org.session.libsignal.service.api.messages.shared.SharedContact; import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress; import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.api.push.exceptions.PushNetworkException; import org.session.libsignal.service.api.push.exceptions.PushNetworkException;
@ -50,9 +43,8 @@ import org.session.libsignal.service.internal.push.SignalServiceProtos.Attachmen
import org.session.libsignal.service.internal.push.SignalServiceProtos.Content; import org.session.libsignal.service.internal.push.SignalServiceProtos.Content;
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext; import org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext;
import org.session.libsignal.service.internal.push.SignalServiceProtos.LokiUserProfile; import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.LokiProfile;
import org.session.libsignal.service.internal.push.SignalServiceProtos.ReceiptMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.ReceiptMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.SyncMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage;
import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory; import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory;
import org.session.libsignal.service.internal.push.http.OutputStreamFactory; import org.session.libsignal.service.internal.push.http.OutputStreamFactory;
@ -238,24 +230,6 @@ public class SignalServiceMessageSender {
boolean isClosedGroup = message.group.isPresent() && message.group.get().getGroupType() == SignalServiceGroup.GroupType.SIGNAL; boolean isClosedGroup = message.group.isPresent() && message.group.get().getGroupType() == SignalServiceGroup.GroupType.SIGNAL;
SendMessageResult result = sendMessage(messageID, recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, content, false, message.getTTL(), true, isClosedGroup, message.hasVisibleContent(), message.getSyncTarget()); SendMessageResult result = sendMessage(messageID, recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, content, false, message.getTTL(), true, isClosedGroup, message.hasVisibleContent(), message.getSyncTarget());
// // Loki - This shouldn't get invoked for note to self
// boolean wouldSignalSendSyncMessage = (result.getSuccess() != null && result.getSuccess().isNeedsSync()) || unidentifiedAccess.isPresent();
// if (wouldSignalSendSyncMessage && SyncMessagesProtocol.shared.shouldSyncMessage(message)) {
// byte[] syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.of(recipient), timestamp, Collections.singletonList(result));
// // Loki - Customize multi device logic
// Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
// for (String device : linkedDevices) {
// SignalServiceAddress deviceAsAddress = new SignalServiceAddress(device);
// boolean useFallbackEncryptionForSyncMessage = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(syncMessage, device, store);
// sendMessage(deviceAsAddress, Optional.<UnidentifiedAccess>absent(), timestamp, syncMessage, false, message.getTTL(), useFallbackEncryptionForSyncMessage, true);
// }
// }
// Loki - Start a session reset if needed
if (message.isEndSession()) {
SessionManagementProtocol.shared.setSessionResetStatusToInProgressIfNeeded(recipient, eventListener);
}
return result; return result;
} }
@ -289,36 +263,6 @@ public class SignalServiceMessageSender {
return results; return results;
} }
public void sendMessage(SignalServiceSyncMessage message, Optional<UnidentifiedAccessPair> unidentifiedAccess)
throws IOException, UntrustedIdentityException
{
byte[] content;
long timestamp = System.currentTimeMillis();
if (message.getContacts().isPresent()) {
content = createMultiDeviceContactsContent(message.getContacts().get().getContactsStream().asStream(), message.getContacts().get().isComplete());
} else if (message.getGroups().isPresent()) {
content = createMultiDeviceGroupsContent(message.getGroups().get().asStream());
} else if (message.getOpenGroups().isPresent()) {
content = createMultiDeviceOpenGroupsContent(message.getOpenGroups().get());
} else if (message.getRead().isPresent()) {
content = createMultiDeviceReadContent(message.getRead().get());
} else if (message.getBlockedList().isPresent()) {
content = createMultiDeviceBlockedContent(message.getBlockedList().get());
} else if (message.getConfiguration().isPresent()) {
content = createMultiDeviceConfigurationContent(message.getConfiguration().get());
} else if (message.getSent().isPresent()) {
content = createMultiDeviceSentTranscriptContent(message.getSent().get(), unidentifiedAccess);
timestamp = message.getSent().get().getTimestamp();
} else if (message.getStickerPackOperations().isPresent()) {
content = createMultiDeviceStickerPackOperationContent(message.getStickerPackOperations().get());
} else if (message.getVerified().isPresent()) {
return;
} else {
throw new IOException("Unsupported sync message!");
}
}
public void setMessagePipe(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) { public void setMessagePipe(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
this.pipe.set(Optional.fromNullable(pipe)); this.pipe.set(Optional.fromNullable(pipe));
this.unidentifiedPipe.set(Optional.fromNullable(unidentifiedPipe)); this.unidentifiedPipe.set(Optional.fromNullable(unidentifiedPipe));
@ -373,10 +317,6 @@ public class SignalServiceMessageSender {
else if (message.isTypingStopped()) builder.setAction(TypingMessage.Action.STOPPED); else if (message.isTypingStopped()) builder.setAction(TypingMessage.Action.STOPPED);
else throw new IllegalArgumentException("Unknown typing indicator"); else throw new IllegalArgumentException("Unknown typing indicator");
if (message.getGroupId().isPresent()) {
builder.setGroupId(ByteString.copyFrom(message.getGroupId().get()));
}
return container.setTypingMessage(builder).build().toByteArray(); return container.setTypingMessage(builder).build().toByteArray();
} }
@ -414,18 +354,10 @@ public class SignalServiceMessageSender {
builder.setGroup(createGroupContent(message.getGroupInfo().get(), recipient)); builder.setGroup(createGroupContent(message.getGroupInfo().get(), recipient));
} }
if (message.isEndSession()) {
builder.setFlags(DataMessage.Flags.END_SESSION_VALUE);
}
if (message.isExpirationUpdate()) { if (message.isExpirationUpdate()) {
builder.setFlags(DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE); builder.setFlags(DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE);
} }
if (message.isProfileKeyUpdate()) {
builder.setFlags(DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE);
}
if (message.getExpiresInSeconds() > 0) { if (message.getExpiresInSeconds() > 0) {
builder.setExpireTimer(message.getExpiresInSeconds()); builder.setExpireTimer(message.getExpiresInSeconds());
} }
@ -485,27 +417,11 @@ public class SignalServiceMessageSender {
} }
} }
if (message.getSticker().isPresent()) { LokiProfile.Builder lokiUserProfileBuilder = LokiProfile.newBuilder();
DataMessage.Sticker.Builder stickerBuilder = DataMessage.Sticker.newBuilder();
stickerBuilder.setPackId(ByteString.copyFrom(message.getSticker().get().getPackId()));
stickerBuilder.setPackKey(ByteString.copyFrom(message.getSticker().get().getPackKey()));
stickerBuilder.setStickerId(message.getSticker().get().getStickerId());
if (message.getSticker().get().getAttachment().isStream()) {
stickerBuilder.setData(createAttachmentPointer(message.getSticker().get().getAttachment().asStream(), true, recipient));
} else {
stickerBuilder.setData(createAttachmentPointer(message.getSticker().get().getAttachment().asPointer()));
}
builder.setSticker(stickerBuilder.build());
}
LokiUserProfile.Builder lokiUserProfileBuilder = LokiUserProfile.newBuilder();
String displayName = userDatabase.getDisplayName(userPublicKey); String displayName = userDatabase.getDisplayName(userPublicKey);
if (displayName != null) { lokiUserProfileBuilder.setDisplayName(displayName); } if (displayName != null) { lokiUserProfileBuilder.setDisplayName(displayName); }
String profilePictureURL = userDatabase.getProfilePictureURL(userPublicKey); String profilePictureURL = userDatabase.getProfilePictureURL(userPublicKey);
if (profilePictureURL != null) { lokiUserProfileBuilder.setProfilePictureURL(profilePictureURL); } if (profilePictureURL != null) { lokiUserProfileBuilder.setProfilePicture(profilePictureURL); }
builder.setProfile(lokiUserProfileBuilder.build()); builder.setProfile(lokiUserProfileBuilder.build());
builder.setTimestamp(message.getTimestamp()); builder.setTimestamp(message.getTimestamp());
@ -515,183 +431,6 @@ public class SignalServiceMessageSender {
return container.build().toByteArray(); return container.build().toByteArray();
} }
private byte[] createMultiDeviceContactsContent(SignalServiceAttachmentStream contacts, boolean complete)
throws IOException
{
Content.Builder container = Content.newBuilder();
SyncMessage.Builder builder = createSyncMessageBuilder();
builder.setContacts(SyncMessage.Contacts.newBuilder()
.setData(ByteString.readFrom(contacts.getInputStream()))
.setComplete(complete));
return container.setSyncMessage(builder).build().toByteArray();
}
private byte[] createMultiDeviceGroupsContent(SignalServiceAttachmentStream groups)
throws IOException
{
Content.Builder container = Content.newBuilder();
SyncMessage.Builder builder = createSyncMessageBuilder();
builder.setGroups(SyncMessage.Groups.newBuilder()
.setData(ByteString.readFrom(groups.getInputStream())));
return container.setSyncMessage(builder).build().toByteArray();
}
private byte[] createMultiDeviceOpenGroupsContent(List<PublicChat> openGroups)
throws IOException
{
Content.Builder container = Content.newBuilder();
SyncMessage.Builder builder = createSyncMessageBuilder();
for (PublicChat openGroup : openGroups) {
String url = openGroup.getServer();
int channel = Long.valueOf(openGroup.getChannel()).intValue();
SyncMessage.OpenGroupDetails details = SyncMessage.OpenGroupDetails.newBuilder()
.setUrl(url)
.setChannelID(channel)
.build();
builder.addOpenGroups(details);
}
return container.setSyncMessage(builder).build().toByteArray();
}
private byte[] createMultiDeviceSentTranscriptContent(SentTranscriptMessage transcript, Optional<UnidentifiedAccessPair> unidentifiedAccess)
throws IOException
{
SignalServiceAddress address = new SignalServiceAddress(transcript.getDestination().get());
SendMessageResult result = SendMessageResult.success(address, unidentifiedAccess.isPresent(), true);
return createMultiDeviceSentTranscriptContent(createMessageContent(transcript.getMessage(), address),
Optional.of(address),
transcript.getTimestamp(),
Collections.singletonList(result));
}
private byte[] createMultiDeviceSentTranscriptContent(byte[] content, Optional<SignalServiceAddress> recipient,
long timestamp, List<SendMessageResult> sendMessageResults)
{
try {
Content.Builder container = Content.newBuilder();
SyncMessage.Builder syncMessage = createSyncMessageBuilder();
SyncMessage.Sent.Builder sentMessage = SyncMessage.Sent.newBuilder();
DataMessage dataMessage = Content.parseFrom(content).getDataMessage();
sentMessage.setTimestamp(timestamp);
sentMessage.setMessage(dataMessage);
for (SendMessageResult result : sendMessageResults) {
if (result.getSuccess() != null) {
sentMessage.addUnidentifiedStatus(SyncMessage.Sent.UnidentifiedDeliveryStatus.newBuilder()
.setDestination(result.getAddress().getNumber())
.setUnidentified(result.getSuccess().isUnidentified()));
}
}
if (recipient.isPresent()) {
sentMessage.setDestination(recipient.get().getNumber());
}
if (dataMessage.getExpireTimer() > 0) {
sentMessage.setExpirationStartTimestamp(System.currentTimeMillis());
}
return container.setSyncMessage(syncMessage.setSent(sentMessage)).build().toByteArray();
} catch (InvalidProtocolBufferException e) {
throw new AssertionError(e);
}
}
private byte[] createMultiDeviceReadContent(List<ReadMessage> readMessages) {
Content.Builder container = Content.newBuilder();
SyncMessage.Builder builder = createSyncMessageBuilder();
for (ReadMessage readMessage : readMessages) {
builder.addRead(SyncMessage.Read.newBuilder()
.setTimestamp(readMessage.getTimestamp())
.setSender(readMessage.getSender()));
}
return container.setSyncMessage(builder).build().toByteArray();
}
private byte[] createMultiDeviceBlockedContent(BlockedListMessage blocked) {
Content.Builder container = Content.newBuilder();
SyncMessage.Builder syncMessage = createSyncMessageBuilder();
SyncMessage.Blocked.Builder blockedMessage = SyncMessage.Blocked.newBuilder();
blockedMessage.addAllNumbers(blocked.getNumbers());
for (byte[] groupId : blocked.getGroupIds()) {
blockedMessage.addGroupIds(ByteString.copyFrom(groupId));
}
return container.setSyncMessage(syncMessage.setBlocked(blockedMessage)).build().toByteArray();
}
private byte[] createMultiDeviceConfigurationContent(ConfigurationMessage configuration) {
Content.Builder container = Content.newBuilder();
SyncMessage.Builder syncMessage = createSyncMessageBuilder();
SyncMessage.Configuration.Builder configurationMessage = SyncMessage.Configuration.newBuilder();
if (configuration.getReadReceipts().isPresent()) {
configurationMessage.setReadReceipts(configuration.getReadReceipts().get());
}
if (configuration.getUnidentifiedDeliveryIndicators().isPresent()) {
configurationMessage.setUnidentifiedDeliveryIndicators(configuration.getUnidentifiedDeliveryIndicators().get());
}
if (configuration.getTypingIndicators().isPresent()) {
configurationMessage.setTypingIndicators(configuration.getTypingIndicators().get());
}
if (configuration.getLinkPreviews().isPresent()) {
configurationMessage.setLinkPreviews(configuration.getLinkPreviews().get());
}
return container.setSyncMessage(syncMessage.setConfiguration(configurationMessage)).build().toByteArray();
}
private byte[] createMultiDeviceStickerPackOperationContent(List<StickerPackOperationMessage> stickerPackOperations) {
Content.Builder container = Content.newBuilder();
SyncMessage.Builder syncMessage = createSyncMessageBuilder();
for (StickerPackOperationMessage stickerPackOperation : stickerPackOperations) {
SyncMessage.StickerPackOperation.Builder builder = SyncMessage.StickerPackOperation.newBuilder();
if (stickerPackOperation.getPackId().isPresent()) {
builder.setPackId(ByteString.copyFrom(stickerPackOperation.getPackId().get()));
}
if (stickerPackOperation.getPackKey().isPresent()) {
builder.setPackKey(ByteString.copyFrom(stickerPackOperation.getPackKey().get()));
}
if (stickerPackOperation.getType().isPresent()) {
switch (stickerPackOperation.getType().get()) {
case INSTALL: builder.setType(SyncMessage.StickerPackOperation.Type.INSTALL); break;
case REMOVE: builder.setType(SyncMessage.StickerPackOperation.Type.REMOVE); break;
}
}
syncMessage.addStickerPackOperation(builder);
}
return container.setSyncMessage(syncMessage).build().toByteArray();
}
private SyncMessage.Builder createSyncMessageBuilder() {
SecureRandom random = new SecureRandom();
byte[] padding = Util.getRandomLengthBytes(512);
random.nextBytes(padding);
SyncMessage.Builder builder = SyncMessage.newBuilder();
builder.setPadding(ByteString.copyFrom(padding));
return builder;
}
private GroupContext createGroupContent(SignalServiceGroup group, SignalServiceAddress recipient) private GroupContext createGroupContent(SignalServiceGroup group, SignalServiceAddress recipient)
throws IOException throws IOException
{ {

View File

@ -6,7 +6,6 @@
package org.session.libsignal.service.api.crypto; package org.session.libsignal.service.api.crypto;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import org.session.libsignal.libsignal.ecc.ECKeyPair; import org.session.libsignal.libsignal.ecc.ECKeyPair;
@ -22,9 +21,7 @@ import org.session.libsignal.metadata.ProtocolNoSessionException;
import org.session.libsignal.metadata.ProtocolUntrustedIdentityException; import org.session.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.session.libsignal.metadata.SealedSessionCipher; import org.session.libsignal.metadata.SealedSessionCipher;
import org.session.libsignal.metadata.SelfSendException; import org.session.libsignal.metadata.SelfSendException;
import org.session.libsignal.metadata.certificate.CertificateValidator;
import org.session.libsignal.libsignal.DuplicateMessageException; import org.session.libsignal.libsignal.DuplicateMessageException;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.InvalidKeyException; import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.InvalidKeyIdException; import org.session.libsignal.libsignal.InvalidKeyIdException;
import org.session.libsignal.libsignal.InvalidMessageException; import org.session.libsignal.libsignal.InvalidMessageException;
@ -36,69 +33,37 @@ import org.session.libsignal.libsignal.SignalProtocolAddress;
import org.session.libsignal.libsignal.UntrustedIdentityException; import org.session.libsignal.libsignal.UntrustedIdentityException;
import org.session.libsignal.libsignal.loki.LokiSessionCipher; import org.session.libsignal.libsignal.loki.LokiSessionCipher;
import org.session.libsignal.libsignal.loki.SessionResetProtocol; import org.session.libsignal.libsignal.loki.SessionResetProtocol;
import org.session.libsignal.libsignal.protocol.CiphertextMessage;
import org.session.libsignal.libsignal.protocol.PreKeySignalMessage; import org.session.libsignal.libsignal.protocol.PreKeySignalMessage;
import org.session.libsignal.libsignal.protocol.SignalMessage; import org.session.libsignal.libsignal.protocol.SignalMessage;
import org.session.libsignal.libsignal.state.SignalProtocolStore; import org.session.libsignal.libsignal.state.SignalProtocolStore;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachment; import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer; import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream;
import org.session.libsignal.service.api.messages.SignalServiceContent; import org.session.libsignal.service.api.messages.SignalServiceContent;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage; import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Preview; import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Preview;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage.Sticker;
import org.session.libsignal.service.api.messages.SignalServiceEnvelope; import org.session.libsignal.service.api.messages.SignalServiceEnvelope;
import org.session.libsignal.service.api.messages.SignalServiceGroup; import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsignal.service.api.messages.SignalServiceNullMessage;
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage; import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
import org.session.libsignal.service.api.messages.SignalServiceTypingMessage; import org.session.libsignal.service.api.messages.SignalServiceTypingMessage;
import org.session.libsignal.service.api.messages.calls.AnswerMessage;
import org.session.libsignal.service.api.messages.calls.BusyMessage;
import org.session.libsignal.service.api.messages.calls.HangupMessage;
import org.session.libsignal.service.api.messages.calls.IceUpdateMessage;
import org.session.libsignal.service.api.messages.calls.OfferMessage;
import org.session.libsignal.service.api.messages.calls.SignalServiceCallMessage;
import org.session.libsignal.service.api.messages.multidevice.BlockedListMessage;
import org.session.libsignal.service.api.messages.multidevice.ContactsMessage;
import org.session.libsignal.service.api.messages.multidevice.ReadMessage;
import org.session.libsignal.service.api.messages.multidevice.RequestMessage;
import org.session.libsignal.service.api.messages.multidevice.SentTranscriptMessage;
import org.session.libsignal.service.api.messages.multidevice.SignalServiceSyncMessage;
import org.session.libsignal.service.api.messages.multidevice.StickerPackOperationMessage;
import org.session.libsignal.service.api.messages.multidevice.VerifiedMessage;
import org.session.libsignal.service.api.messages.multidevice.VerifiedMessage.VerifiedState;
import org.session.libsignal.service.api.messages.shared.SharedContact; import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress; import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.internal.push.OutgoingPushMessage;
import org.session.libsignal.service.internal.push.PushTransportDetails; import org.session.libsignal.service.internal.push.PushTransportDetails;
import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.session.libsignal.service.internal.push.SignalServiceProtos;
import org.session.libsignal.service.internal.push.SignalServiceProtos.AttachmentPointer; import org.session.libsignal.service.internal.push.SignalServiceProtos.AttachmentPointer;
import org.session.libsignal.service.internal.push.SignalServiceProtos.ClosedGroupUpdate; import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.ClosedGroupControlMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.ClosedGroupUpdateV2;
import org.session.libsignal.service.internal.push.SignalServiceProtos.Content; import org.session.libsignal.service.internal.push.SignalServiceProtos.Content;
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.Envelope.Type;
import org.session.libsignal.service.internal.push.SignalServiceProtos.ReceiptMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.ReceiptMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.SyncMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage; import org.session.libsignal.service.internal.push.SignalServiceProtos.TypingMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.Verified;
import org.session.libsignal.utilities.Base64;
import org.session.libsignal.service.loki.api.crypto.SessionProtocol; import org.session.libsignal.service.loki.api.crypto.SessionProtocol;
import org.session.libsignal.service.loki.api.crypto.SessionProtocolUtilities; import org.session.libsignal.service.loki.api.crypto.SessionProtocolUtilities;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol; import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import static org.session.libsignal.service.internal.push.SignalServiceProtos.CallMessage;
import static org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext.Type.DELIVER; import static org.session.libsignal.service.internal.push.SignalServiceProtos.GroupContext.Type.DELIVER;
/** /**
@ -116,67 +81,20 @@ public class SignalServiceCipher {
private final SignalServiceAddress localAddress; private final SignalServiceAddress localAddress;
private final SessionProtocol sessionProtocolImpl; private final SessionProtocol sessionProtocolImpl;
private final LokiAPIDatabaseProtocol apiDB; private final LokiAPIDatabaseProtocol apiDB;
private final CertificateValidator certificateValidator;
public SignalServiceCipher(SignalServiceAddress localAddress, public SignalServiceCipher(SignalServiceAddress localAddress,
SignalProtocolStore signalProtocolStore, SignalProtocolStore signalProtocolStore,
SessionResetProtocol sessionResetProtocol, SessionResetProtocol sessionResetProtocol,
SessionProtocol sessionProtocolImpl, SessionProtocol sessionProtocolImpl,
LokiAPIDatabaseProtocol apiDB, LokiAPIDatabaseProtocol apiDB)
CertificateValidator certificateValidator)
{ {
this.signalProtocolStore = signalProtocolStore; this.signalProtocolStore = signalProtocolStore;
this.sessionResetProtocol = sessionResetProtocol; this.sessionResetProtocol = sessionResetProtocol;
this.localAddress = localAddress; this.localAddress = localAddress;
this.sessionProtocolImpl = sessionProtocolImpl; this.sessionProtocolImpl = sessionProtocolImpl;
this.apiDB = apiDB; this.apiDB = apiDB;
this.certificateValidator = certificateValidator;
} }
// public OutgoingPushMessage encrypt(SignalProtocolAddress destination,
// Optional<UnidentifiedAccess> unidentifiedAccess,
// byte[] unpaddedMessage)
// throws UntrustedIdentityException, InvalidKeyException, IOException
// {
// if (unidentifiedAccess.isPresent() && sskDatabase.isSSKBasedClosedGroup(destination.getName())) {
// String userPublicKey = localAddress.getNumber();
// SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(userPublicKey, 1);
// SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, signalProtocolAddress);
// PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination));
// byte[] plaintext = transportDetails.getPaddedMessageBody(unpaddedMessage);
// byte[] ciphertext = ClosedGroupUtilities.encrypt(plaintext, destination.getName(), userPublicKey);
// String body = Base64.encodeBytes(ciphertext);
// int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination);
// return new OutgoingPushMessage(Type.CLOSED_GROUP_CIPHERTEXT_VALUE, destination.getDeviceId(), remoteRegistrationId, body);
// } else if (unidentifiedAccess.isPresent()) {
// SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, new SignalProtocolAddress(localAddress.getNumber(), 1));
// PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination));
// byte[] ciphertext = sessionCipher.encrypt(destination, unidentifiedAccess.get().getUnidentifiedCertificate(), transportDetails.getPaddedMessageBody(unpaddedMessage));
// String body = Base64.encodeBytes(ciphertext);
// int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination);
//
// return new OutgoingPushMessage(Type.UNIDENTIFIED_SENDER_VALUE, destination.getDeviceId(), remoteRegistrationId, body);
// } else {
// SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, destination);
// PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion());
// CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage));
// int remoteRegistrationId = sessionCipher.getRemoteRegistrationId();
// String body = Base64.encodeBytes(message.serialize());
//
// int type;
//
// switch (message.getType()) {
// case CiphertextMessage.PREKEY_TYPE: type = Type.PREKEY_BUNDLE_VALUE; break;
// case CiphertextMessage.WHISPER_TYPE: type = Type.CIPHERTEXT_VALUE; break;
// case CiphertextMessage.FALLBACK_MESSAGE_TYPE: type = Type.FALLBACK_MESSAGE_VALUE; break;
// case CiphertextMessage.CLOSED_GROUP_CIPHERTEXT: type = Type.CLOSED_GROUP_CIPHERTEXT_VALUE; break;
// default: throw new AssertionError("Bad type: " + message.getType());
// }
//
// return new OutgoingPushMessage(type, destination.getDeviceId(), remoteRegistrationId, body);
// }
// }
/** /**
* Decrypt a received {@link SignalServiceEnvelope} * Decrypt a received {@link SignalServiceEnvelope}
* *
@ -184,32 +102,12 @@ public class SignalServiceCipher {
* *
* @return a decrypted SignalServiceContent * @return a decrypted SignalServiceContent
*/ */
public SignalServiceContent decrypt(SignalServiceEnvelope envelope) public SignalServiceContent decrypt(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException,ProtocolInvalidMessageException
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
ProtocolInvalidKeyIdException, ProtocolLegacyMessageException,
ProtocolUntrustedIdentityException, ProtocolNoSessionException,
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
ProtocolInvalidKeyException, ProtocolDuplicateMessageException,
SelfSendException, IOException, SessionProtocol.Exception
{ {
try { try {
Plaintext plaintext = decrypt(envelope, envelope.getContent()); Plaintext plaintext = decrypt(envelope, envelope.getContent());
Content message = Content.parseFrom(plaintext.getData()); Content message = Content.parseFrom(plaintext.getData());
PreKeyBundleMessage preKeyBundleMessage = null;
if (message.hasPreKeyBundleMessage()) {
SignalServiceProtos.PreKeyBundleMessage protoPreKeyBundleMessage = message.getPreKeyBundleMessage();
preKeyBundleMessage = new PreKeyBundleMessage(protoPreKeyBundleMessage.getIdentityKey().toByteArray(),
protoPreKeyBundleMessage.getDeviceId(),
protoPreKeyBundleMessage.getPreKeyId(),
protoPreKeyBundleMessage.getSignedKeyId(),
protoPreKeyBundleMessage.getPreKey().toByteArray(),
protoPreKeyBundleMessage.getSignedKey().toByteArray(),
protoPreKeyBundleMessage.getSignature().toByteArray()
);
}
if (message.hasConfigurationMessage()) { if (message.hasConfigurationMessage()) {
SignalServiceCipher.Metadata metadata = plaintext.getMetadata(); SignalServiceCipher.Metadata metadata = plaintext.getMetadata();
SignalServiceContent content = new SignalServiceContent(message, metadata.getSender(), metadata.getSenderDevice(), metadata.getTimestamp()); SignalServiceContent content = new SignalServiceContent(message, metadata.getSender(), metadata.getSenderDevice(), metadata.getTimestamp());
@ -231,30 +129,9 @@ public class SignalServiceCipher {
plaintext.getMetadata().getTimestamp(), plaintext.getMetadata().getTimestamp(),
plaintext.getMetadata().isNeedsReceipt()); plaintext.getMetadata().isNeedsReceipt());
content.setPreKeyBundleMessage(preKeyBundleMessage);
setProfile(dataMessage, content); setProfile(dataMessage, content);
return content; return content;
} else if (message.hasSyncMessage()) {
SignalServiceContent content = new SignalServiceContent(createSynchronizeMessage(
plaintext.getMetadata(),
message.getSyncMessage()),
plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp());
if (message.getSyncMessage().hasSent() && message.getSyncMessage().getSent().hasMessage()) {
DataMessage dataMessage = message.getSyncMessage().getSent().getMessage();
setProfile(dataMessage, content);
}
return content;
} else if (message.hasCallMessage()) {
return new SignalServiceContent(createCallMessage(message.getCallMessage()),
plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp());
} else if (message.hasReceiptMessage()) { } else if (message.hasReceiptMessage()) {
return new SignalServiceContent(createReceiptMessage(plaintext.getMetadata(), message.getReceiptMessage()), return new SignalServiceContent(createReceiptMessage(plaintext.getMetadata(), message.getReceiptMessage()),
plaintext.getMetadata().getSender(), plaintext.getMetadata().getSender(),
@ -265,15 +142,6 @@ public class SignalServiceCipher {
plaintext.getMetadata().getSender(), plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(), plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp()); plaintext.getMetadata().getTimestamp());
} else if (message.hasNullMessage()) {
SignalServiceContent content = new SignalServiceContent(new SignalServiceNullMessage(),
plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp());
content.setPreKeyBundleMessage(preKeyBundleMessage);
return content;
} }
return null; return null;
@ -284,20 +152,13 @@ public class SignalServiceCipher {
private void setProfile(DataMessage message, SignalServiceContent content) { private void setProfile(DataMessage message, SignalServiceContent content) {
if (!message.hasProfile()) { return; } if (!message.hasProfile()) { return; }
SignalServiceProtos.LokiUserProfile profile = message.getProfile(); SignalServiceProtos.DataMessage.LokiProfile profile = message.getProfile();
if (profile.hasDisplayName()) { content.setSenderDisplayName(profile.getDisplayName()); } if (profile.hasDisplayName()) { content.setSenderDisplayName(profile.getDisplayName()); }
if (profile.hasProfilePictureURL()) { content.setSenderProfilePictureURL(profile.getProfilePictureURL()); } if (profile.hasProfilePicture()) { content.setSenderProfilePictureURL(profile.getProfilePicture()); }
} }
protected Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) protected Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) throws InvalidMetadataMessageException
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException,
ProtocolLegacyMessageException, ProtocolInvalidKeyException,
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
ProtocolInvalidKeyIdException, ProtocolNoSessionException,
SelfSendException, IOException, SessionProtocol.Exception
{ {
try {
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSource(), envelope.getSourceDevice()); SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSource(), envelope.getSourceDevice());
SessionCipher sessionCipher = new LokiSessionCipher(signalProtocolStore, sessionResetProtocol, sourceAddress); SessionCipher sessionCipher = new LokiSessionCipher(signalProtocolStore, sessionResetProtocol, sourceAddress);
SealedSessionCipher sealedSessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, new SignalProtocolAddress(localAddress.getNumber(), 1)); SealedSessionCipher sealedSessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, new SignalProtocolAddress(localAddress.getNumber(), 1));
@ -313,14 +174,6 @@ public class SignalServiceCipher {
String senderPublicKey = plaintextAndSenderPublicKey.getSecond(); String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false); metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
sessionVersion = sessionCipher.getSessionVersion(); sessionVersion = sessionCipher.getSessionVersion();
} else if (envelope.isPreKeySignalMessage()) {
paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext));
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
sessionVersion = sessionCipher.getSessionVersion();
} else if (envelope.isSignalMessage()) {
paddedMessage = sessionCipher.decrypt(new SignalMessage(ciphertext));
metadata = new Metadata(envelope.getSource(), envelope.getSourceDevice(), envelope.getTimestamp(), false);
sessionVersion = sessionCipher.getSessionVersion();
} else if (envelope.isUnidentifiedSender()) { } else if (envelope.isUnidentifiedSender()) {
ECKeyPair userX25519KeyPair = apiDB.getUserX25519KeyPair(); ECKeyPair userX25519KeyPair = apiDB.getUserX25519KeyPair();
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(ciphertext, userX25519KeyPair); kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(ciphertext, userX25519KeyPair);
@ -336,38 +189,16 @@ public class SignalServiceCipher {
byte[] data = transportDetails.getStrippedPaddingMessageBody(paddedMessage); byte[] data = transportDetails.getStrippedPaddingMessageBody(paddedMessage);
return new Plaintext(metadata, data); return new Plaintext(metadata, data);
} catch (DuplicateMessageException e) {
throw new ProtocolDuplicateMessageException(e, envelope.getSource(), envelope.getSourceDevice());
} catch (LegacyMessageException e) {
throw new ProtocolLegacyMessageException(e, envelope.getSource(), envelope.getSourceDevice());
} catch (InvalidMessageException e) {
throw new ProtocolInvalidMessageException(e, envelope.getSource(), envelope.getSourceDevice());
} catch (InvalidKeyIdException e) {
throw new ProtocolInvalidKeyIdException(e, envelope.getSource(), envelope.getSourceDevice());
} catch (InvalidKeyException e) {
throw new ProtocolInvalidKeyException(e, envelope.getSource(), envelope.getSourceDevice());
} catch (UntrustedIdentityException e) {
throw new ProtocolUntrustedIdentityException(e, envelope.getSource(), envelope.getSourceDevice());
} catch (InvalidVersionException e) {
throw new ProtocolInvalidVersionException(e, envelope.getSource(), envelope.getSourceDevice());
} catch (NoSessionException e) {
throw new ProtocolNoSessionException(e, envelope.getSource(), envelope.getSourceDevice());
}
} }
private SignalServiceDataMessage createSignalServiceMessage(Metadata metadata, DataMessage content) throws ProtocolInvalidMessageException { private SignalServiceDataMessage createSignalServiceMessage(Metadata metadata, DataMessage content) throws ProtocolInvalidMessageException {
SignalServiceGroup groupInfo = createGroupInfo(content); SignalServiceGroup groupInfo = createGroupInfo(content);
List<SignalServiceAttachment> attachments = new LinkedList<SignalServiceAttachment>(); List<SignalServiceAttachment> attachments = new LinkedList<SignalServiceAttachment>();
boolean endSession = ((content.getFlags() & DataMessage.Flags.END_SESSION_VALUE ) != 0);
boolean expirationUpdate = ((content.getFlags() & DataMessage.Flags.EXPIRATION_TIMER_UPDATE_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); SignalServiceDataMessage.Quote quote = createQuote(content);
List<SharedContact> sharedContacts = createSharedContacts(content); List<SharedContact> sharedContacts = createSharedContacts(content);
List<Preview> previews = createPreviews(content); List<Preview> previews = createPreviews(content);
Sticker sticker = createSticker(content); ClosedGroupControlMessage closedGroupControlMessage = content.getClosedGroupControlMessage();
ClosedGroupUpdate closedGroupUpdate = content.getClosedGroupUpdate();
ClosedGroupUpdateV2 closedGroupUpdateV2 = content.getClosedGroupUpdateV2();
boolean isDeviceUnlinkingRequest = ((content.getFlags() & DataMessage.Flags.DEVICE_UNLINKING_REQUEST_VALUE) != 0);
String syncTarget = content.getSyncTarget(); String syncTarget = content.getSyncTarget();
for (AttachmentPointer pointer : content.getAttachmentsList()) { for (AttachmentPointer pointer : content.getAttachmentsList()) {
@ -384,170 +215,16 @@ public class SignalServiceCipher {
groupInfo, groupInfo,
attachments, attachments,
content.getBody(), content.getBody(),
endSession,
content.getExpireTimer(), content.getExpireTimer(),
expirationUpdate, expirationUpdate,
content.hasProfileKey() ? content.getProfileKey().toByteArray() : null, content.hasProfileKey() ? content.getProfileKey().toByteArray() : null,
profileKeyUpdate,
quote, quote,
sharedContacts, sharedContacts,
previews, previews,
sticker, closedGroupControlMessage,
null,
closedGroupUpdate,
closedGroupUpdateV2,
syncTarget); syncTarget);
} }
private SignalServiceSyncMessage createSynchronizeMessage(Metadata metadata, SyncMessage content)
throws ProtocolInvalidMessageException, ProtocolInvalidKeyException
{
if (content.hasSent()) {
SyncMessage.Sent sentContent = content.getSent();
Map<String, Boolean> unidentifiedStatuses = new HashMap<String, Boolean>();
for (SyncMessage.Sent.UnidentifiedDeliveryStatus status : sentContent.getUnidentifiedStatusList()) {
unidentifiedStatuses.put(status.getDestination(), status.getUnidentified());
}
return SignalServiceSyncMessage.forSentTranscript(new SentTranscriptMessage(sentContent.getDestination(),
sentContent.getTimestamp(),
createSignalServiceMessage(metadata, sentContent.getMessage()),
sentContent.getExpirationStartTimestamp(),
unidentifiedStatuses));
}
if (content.hasRequest()) {
return SignalServiceSyncMessage.forRequest(new RequestMessage(content.getRequest()));
}
if (content.getReadList().size() > 0) {
List<ReadMessage> readMessages = new LinkedList<ReadMessage>();
for (SyncMessage.Read read : content.getReadList()) {
readMessages.add(new ReadMessage(read.getSender(), read.getTimestamp()));
}
return SignalServiceSyncMessage.forRead(readMessages);
}
if (content.hasContacts()) {
SyncMessage.Contacts contacts = content.getContacts();
ByteString data = contacts.getData();
if (data != null && !data.isEmpty()) {
byte[] bytes = data.toByteArray();
SignalServiceAttachmentStream attachmentStream = SignalServiceAttachment.newStreamBuilder()
.withStream(new ByteArrayInputStream(data.toByteArray()))
.withContentType("application/octet-stream")
.withLength(bytes.length)
.build();
return SignalServiceSyncMessage.forContacts(new ContactsMessage(attachmentStream, contacts.getComplete()));
}
}
if (content.hasGroups()) {
SyncMessage.Groups groups = content.getGroups();
ByteString data = groups.getData();
if (data != null && !data.isEmpty()) {
byte[] bytes = data.toByteArray();
SignalServiceAttachmentStream attachmentStream = SignalServiceAttachment.newStreamBuilder()
.withStream(new ByteArrayInputStream(data.toByteArray()))
.withContentType("application/octet-stream")
.withLength(bytes.length)
.build();
return SignalServiceSyncMessage.forGroups(attachmentStream);
}
}
if (content.hasVerified()) {
try {
Verified verified = content.getVerified();
String destination = verified.getDestination();
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(), metadata.getSenderDevice());
}
return SignalServiceSyncMessage.forVerified(new VerifiedMessage(destination, identityKey, verifiedState, System.currentTimeMillis()));
} catch (InvalidKeyException e) {
throw new ProtocolInvalidKeyException(e, metadata.getSender(), metadata.getSenderDevice());
}
}
if (content.getStickerPackOperationList().size() > 0) {
List<StickerPackOperationMessage> operations = new LinkedList<StickerPackOperationMessage>();
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);
}
List<SyncMessage.OpenGroupDetails> openGroupDetails = content.getOpenGroupsList();
if (openGroupDetails.size() > 0) {
List<PublicChat> openGroups = new LinkedList<>();
for (SyncMessage.OpenGroupDetails details : content.getOpenGroupsList()) {
openGroups.add(new PublicChat(details.getChannelID(), details.getUrl(), "", true));
}
return SignalServiceSyncMessage.forOpenGroups(openGroups);
}
if (content.hasBlocked()) {
SyncMessage.Blocked blocked = content.getBlocked();
List<String> publicKeys = blocked.getNumbersList();
return SignalServiceSyncMessage.forBlocked(new BlockedListMessage(publicKeys, new ArrayList<byte[]>()));
}
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<IceUpdateMessage>();
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) { private SignalServiceReceiptMessage createReceiptMessage(Metadata metadata, ReceiptMessage content) {
SignalServiceReceiptMessage.Type type; SignalServiceReceiptMessage.Type type;
@ -571,9 +248,7 @@ public class SignalServiceCipher {
metadata.getSenderDevice()); metadata.getSenderDevice());
} }
return new SignalServiceTypingMessage(action, content.getTimestamp(), return new SignalServiceTypingMessage(action, content.getTimestamp());
content.hasGroupId() ? Optional.of(content.getGroupId().toByteArray()) :
Optional.<byte[]>absent());
} }
private SignalServiceDataMessage.Quote createQuote(DataMessage content) { private SignalServiceDataMessage.Quote createQuote(DataMessage content) {
@ -613,24 +288,6 @@ public class SignalServiceCipher {
return results; 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 List<SharedContact> createSharedContacts(DataMessage content) { private List<SharedContact> createSharedContacts(DataMessage content) {
if (content.getContactCount() <= 0) return null; if (content.getContactCount() <= 0) return null;

View File

@ -8,7 +8,6 @@ package org.session.libsignal.service.api.messages;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.calls.SignalServiceCallMessage; import org.session.libsignal.service.api.messages.calls.SignalServiceCallMessage;
import org.session.libsignal.service.api.messages.multidevice.SignalServiceSyncMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.session.libsignal.service.internal.push.SignalServiceProtos;
import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage; import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage;
@ -20,9 +19,6 @@ public class SignalServiceContent {
// Loki // Loki
private Optional<SignalServiceDataMessage> message; private Optional<SignalServiceDataMessage> message;
private Optional<SignalServiceSyncMessage> synchronizeMessage;
private final Optional<SignalServiceCallMessage> callMessage;
private final Optional<SignalServiceNullMessage> nullMessage;
private final Optional<SignalServiceReceiptMessage> readMessage; private final Optional<SignalServiceReceiptMessage> readMessage;
private final Optional<SignalServiceTypingMessage> typingMessage; private final Optional<SignalServiceTypingMessage> typingMessage;
@ -38,35 +34,6 @@ public class SignalServiceContent {
this.timestamp = timestamp; this.timestamp = timestamp;
this.needsReceipt = needsReceipt; this.needsReceipt = needsReceipt;
this.message = Optional.fromNullable(message); this.message = Optional.fromNullable(message);
this.synchronizeMessage = Optional.absent();
this.callMessage = Optional.absent();
this.nullMessage = Optional.absent();
this.readMessage = Optional.absent();
this.typingMessage = Optional.absent();
}
public SignalServiceContent(SignalServiceSyncMessage synchronizeMessage, String sender, int senderDevice, long timestamp) {
this.sender = sender;
this.senderDevice = senderDevice;
this.timestamp = timestamp;
this.needsReceipt = false;
this.message = Optional.absent();
this.synchronizeMessage = Optional.fromNullable(synchronizeMessage);
this.callMessage = Optional.absent();
this.nullMessage = Optional.absent();
this.readMessage = Optional.absent();
this.typingMessage = Optional.absent();
}
public SignalServiceContent(SignalServiceCallMessage callMessage, String sender, int senderDevice, long timestamp) {
this.sender = sender;
this.senderDevice = senderDevice;
this.timestamp = timestamp;
this.needsReceipt = false;
this.message = Optional.absent();
this.synchronizeMessage = Optional.absent();
this.callMessage = Optional.of(callMessage);
this.nullMessage = Optional.absent();
this.readMessage = Optional.absent(); this.readMessage = Optional.absent();
this.typingMessage = Optional.absent(); this.typingMessage = Optional.absent();
} }
@ -77,9 +44,6 @@ public class SignalServiceContent {
this.timestamp = timestamp; this.timestamp = timestamp;
this.needsReceipt = false; this.needsReceipt = false;
this.message = Optional.absent(); this.message = Optional.absent();
this.synchronizeMessage = Optional.absent();
this.callMessage = Optional.absent();
this.nullMessage = Optional.absent();
this.readMessage = Optional.of(receiptMessage); this.readMessage = Optional.of(receiptMessage);
this.typingMessage = Optional.absent(); this.typingMessage = Optional.absent();
} }
@ -90,9 +54,6 @@ public class SignalServiceContent {
this.timestamp = timestamp; this.timestamp = timestamp;
this.needsReceipt = false; this.needsReceipt = false;
this.message = Optional.absent(); this.message = Optional.absent();
this.synchronizeMessage = Optional.absent();
this.callMessage = Optional.absent();
this.nullMessage = Optional.absent();
this.readMessage = Optional.absent(); this.readMessage = Optional.absent();
this.typingMessage = Optional.of(typingMessage); this.typingMessage = Optional.of(typingMessage);
} }
@ -103,41 +64,17 @@ public class SignalServiceContent {
this.timestamp = timestamp; this.timestamp = timestamp;
this.needsReceipt = false; this.needsReceipt = false;
this.message = Optional.absent(); this.message = Optional.absent();
this.synchronizeMessage = Optional.absent();
this.callMessage = Optional.absent();
this.nullMessage = Optional.absent();
this.readMessage = Optional.absent(); this.readMessage = Optional.absent();
this.typingMessage = Optional.absent(); this.typingMessage = Optional.absent();
this.configurationMessageProto = Optional.fromNullable(configurationMessageProto); this.configurationMessageProto = Optional.fromNullable(configurationMessageProto);
} }
public SignalServiceContent(SignalServiceNullMessage nullMessage, String sender, int senderDevice, long timestamp) {
this.sender = sender;
this.senderDevice = senderDevice;
this.timestamp = timestamp;
this.needsReceipt = false;
this.message = Optional.absent();
this.synchronizeMessage = Optional.absent();
this.callMessage = Optional.absent();
this.nullMessage = Optional.of(nullMessage);
this.readMessage = Optional.absent();
this.typingMessage = Optional.absent();
}
public Optional<SignalServiceDataMessage> getDataMessage() { public Optional<SignalServiceDataMessage> getDataMessage() {
return message; return message;
} }
public void setDataMessage(SignalServiceDataMessage message) { this.message = Optional.fromNullable(message); } public void setDataMessage(SignalServiceDataMessage message) { this.message = Optional.fromNullable(message); }
public Optional<SignalServiceSyncMessage> getSyncMessage() { return synchronizeMessage; }
public void setSyncMessage(SignalServiceSyncMessage message) { this.synchronizeMessage = Optional.fromNullable(message); }
public Optional<SignalServiceCallMessage> getCallMessage() {
return callMessage;
}
public Optional<SignalServiceReceiptMessage> getReceiptMessage() { public Optional<SignalServiceReceiptMessage> getReceiptMessage() {
return readMessage; return readMessage;
} }
@ -162,12 +99,7 @@ public class SignalServiceContent {
return needsReceipt; return needsReceipt;
} }
public Optional<SignalServiceNullMessage> getNullMessage() { return nullMessage; }
// Loki // Loki
public void setPreKeyBundleMessage(PreKeyBundleMessage preKeyBundleMessage) { this.preKeyBundleMessage = Optional.fromNullable(preKeyBundleMessage); }
public void setSenderDisplayName(String displayName) { senderDisplayName = Optional.fromNullable(displayName); } public void setSenderDisplayName(String displayName) { senderDisplayName = Optional.fromNullable(displayName); }
public void setSenderProfilePictureURL(String url) { senderProfilePictureURL = Optional.fromNullable(url); } public void setSenderProfilePictureURL(String url) { senderProfilePictureURL = Optional.fromNullable(url); }

View File

@ -10,8 +10,7 @@ import org.session.libsignal.libsignal.state.PreKeyBundle;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.shared.SharedContact; import org.session.libsignal.service.api.messages.shared.SharedContact;
import org.session.libsignal.service.api.push.SignalServiceAddress; import org.session.libsignal.service.api.push.SignalServiceAddress;
import org.session.libsignal.service.internal.push.SignalServiceProtos.ClosedGroupUpdateV2; import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.ClosedGroupControlMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos.ClosedGroupUpdate;
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities; import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
import java.util.LinkedList; import java.util.LinkedList;
@ -26,18 +25,13 @@ public class SignalServiceDataMessage {
private final Optional<String> body; private final Optional<String> body;
public final Optional<SignalServiceGroup> group; public final Optional<SignalServiceGroup> group;
private final Optional<byte[]> profileKey; private final Optional<byte[]> profileKey;
private final boolean endSession;
private final boolean expirationUpdate; private final boolean expirationUpdate;
private final int expiresInSeconds; private final int expiresInSeconds;
private final boolean profileKeyUpdate;
private final Optional<Quote> quote; private final Optional<Quote> quote;
public final Optional<List<SharedContact>> contacts; public final Optional<List<SharedContact>> contacts;
private final Optional<List<Preview>> previews; private final Optional<List<Preview>> previews;
private final Optional<Sticker> sticker;
// Loki // Loki
private final Optional<PreKeyBundle> preKeyBundle; private final Optional<ClosedGroupControlMessage> closedGroupControlMessage;
private final Optional<ClosedGroupUpdate> closedGroupUpdate;
private final Optional<ClosedGroupUpdateV2> closedGroupUpdateV2;
private final Optional<String> syncTarget; private final Optional<String> syncTarget;
/** /**
@ -112,7 +106,7 @@ public class SignalServiceDataMessage {
* @param expiresInSeconds The number of seconds in which a message should disappear after having been seen. * @param expiresInSeconds The number of seconds in which a message should disappear after having been seen.
*/ */
public SignalServiceDataMessage(long timestamp, SignalServiceGroup group, List<SignalServiceAttachment> attachments, String body, int expiresInSeconds) { public SignalServiceDataMessage(long timestamp, SignalServiceGroup group, List<SignalServiceAttachment> attachments, String body, int expiresInSeconds) {
this(timestamp, group, attachments, body, false, expiresInSeconds, false, null, false, null, null, null, null); this(timestamp, group, attachments, body, expiresInSeconds, false, null, null, null, null);
} }
/** /**
@ -122,17 +116,15 @@ public class SignalServiceDataMessage {
* @param group The group information (or null if none). * @param group The group information (or null if none).
* @param attachments The attachments (or null if none). * @param attachments The attachments (or null if none).
* @param body The message contents. * @param body The message contents.
* @param endSession Flag indicating whether this message should close a session.
* @param expiresInSeconds Number of seconds in which the message should disappear after being seen. * @param expiresInSeconds Number of seconds in which the message should disappear after being seen.
*/ */
public SignalServiceDataMessage(long timestamp, SignalServiceGroup group, public SignalServiceDataMessage(long timestamp, SignalServiceGroup group,
List<SignalServiceAttachment> attachments, List<SignalServiceAttachment> attachments,
String body, boolean endSession, int expiresInSeconds, String body, int expiresInSeconds,
boolean expirationUpdate, byte[] profileKey, boolean profileKeyUpdate, boolean expirationUpdate, byte[] profileKey,
Quote quote, List<SharedContact> sharedContacts, List<Preview> previews, Quote quote, List<SharedContact> sharedContacts, List<Preview> previews)
Sticker sticker)
{ {
this(timestamp, group, attachments, body, endSession, expiresInSeconds, expirationUpdate, profileKey, profileKeyUpdate, quote, sharedContacts, previews, sticker, null, null, null, null); this(timestamp, group, attachments, body, expiresInSeconds, expirationUpdate, profileKey, quote, sharedContacts, previews, null, null);
} }
/** /**
@ -142,32 +134,24 @@ public class SignalServiceDataMessage {
* @param group The group information (or null if none). * @param group The group information (or null if none).
* @param attachments The attachments (or null if none). * @param attachments The attachments (or null if none).
* @param body The message contents. * @param body The message contents.
* @param endSession Flag indicating whether this message should close a session.
* @param expiresInSeconds Number of seconds in which the message should disappear after being seen. * @param expiresInSeconds Number of seconds in which the message should disappear after being seen.
* @param preKeyBundle The pre key bundle to attach to this message (or null if none).
*/ */
public SignalServiceDataMessage(long timestamp, SignalServiceGroup group, public SignalServiceDataMessage(long timestamp, SignalServiceGroup group,
List<SignalServiceAttachment> attachments, List<SignalServiceAttachment> attachments,
String body, boolean endSession, int expiresInSeconds, String body, int expiresInSeconds,
boolean expirationUpdate, byte[] profileKey, boolean profileKeyUpdate, boolean expirationUpdate, byte[] profileKey,
Quote quote, List<SharedContact> sharedContacts, List<Preview> previews, Quote quote, List<SharedContact> sharedContacts, List<Preview> previews,
Sticker sticker, PreKeyBundle preKeyBundle, ClosedGroupControlMessage closedGroupControlMessage,
ClosedGroupUpdate closedGroupUpdate, ClosedGroupUpdateV2 closedGroupUpdateV2,
String syncTarget) String syncTarget)
{ {
this.timestamp = timestamp; this.timestamp = timestamp;
this.body = Optional.fromNullable(body); this.body = Optional.fromNullable(body);
this.group = Optional.fromNullable(group); this.group = Optional.fromNullable(group);
this.endSession = endSession;
this.expiresInSeconds = expiresInSeconds; this.expiresInSeconds = expiresInSeconds;
this.expirationUpdate = expirationUpdate; this.expirationUpdate = expirationUpdate;
this.profileKey = Optional.fromNullable(profileKey); this.profileKey = Optional.fromNullable(profileKey);
this.profileKeyUpdate = profileKeyUpdate;
this.quote = Optional.fromNullable(quote); this.quote = Optional.fromNullable(quote);
this.sticker = Optional.fromNullable(sticker); this.closedGroupControlMessage = Optional.fromNullable(closedGroupControlMessage);
this.preKeyBundle = Optional.fromNullable(preKeyBundle);
this.closedGroupUpdate = Optional.fromNullable(closedGroupUpdate);
this.closedGroupUpdateV2 = Optional.fromNullable(closedGroupUpdateV2);
this.syncTarget = Optional.fromNullable(syncTarget); this.syncTarget = Optional.fromNullable(syncTarget);
if (attachments != null && !attachments.isEmpty()) { if (attachments != null && !attachments.isEmpty()) {
@ -221,18 +205,10 @@ public class SignalServiceDataMessage {
return group; return group;
} }
public boolean isEndSession() {
return endSession;
}
public boolean isExpirationUpdate() { public boolean isExpirationUpdate() {
return expirationUpdate; return expirationUpdate;
} }
public boolean isProfileKeyUpdate() {
return profileKeyUpdate;
}
public boolean isGroupMessage() { public boolean isGroupMessage() {
return group.isPresent(); return group.isPresent();
} }
@ -263,16 +239,8 @@ public class SignalServiceDataMessage {
return previews; return previews;
} }
public Optional<Sticker> getSticker() {
return sticker;
}
// Loki // Loki
public Optional<ClosedGroupUpdate> getClosedGroupUpdate() { return closedGroupUpdate; } public Optional<ClosedGroupControlMessage> getClosedGroupControlMessage() { return closedGroupControlMessage; }
public Optional<ClosedGroupUpdateV2> getClosedGroupUpdateV2() { return closedGroupUpdateV2; }
public Optional<PreKeyBundle> getPreKeyBundle() { return preKeyBundle; }
public boolean hasVisibleContent() { public boolean hasVisibleContent() {
return (body.isPresent() && !body.get().isEmpty()) return (body.isPresent() && !body.get().isEmpty())
@ -399,11 +367,8 @@ public class SignalServiceDataMessage {
public SignalServiceDataMessage build() { public SignalServiceDataMessage build() {
if (timestamp == 0) timestamp = System.currentTimeMillis(); if (timestamp == 0) timestamp = System.currentTimeMillis();
// closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob) // closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob)
return new SignalServiceDataMessage(timestamp, group, attachments, body, endSession, return new SignalServiceDataMessage(timestamp, group, attachments, body, expiresInSeconds, expirationUpdate, profileKey, quote, sharedContacts, previews,
expiresInSeconds, expirationUpdate, profileKey, null, syncTarget);
profileKeyUpdate, quote, sharedContacts, previews,
sticker, preKeyBundle, null, null,
syncTarget);
} }
} }

View File

@ -107,19 +107,13 @@ public class SignalServiceEnvelope {
} }
builder.setTimestamp(proto.getTimestamp()); builder.setTimestamp(proto.getTimestamp());
builder.setServerTimestamp(proto.getServerTimestamp()); builder.setServerTimestamp(proto.getServerTimestamp());
if (proto.getServerGuid() != null) {
builder.setServerGuid(proto.getServerGuid());
}
if (proto.getLegacyMessage() != null) {
builder.setLegacyMessage(ByteString.copyFrom(proto.getLegacyMessage().toByteArray()));
}
if (proto.getContent() != null) { if (proto.getContent() != null) {
builder.setContent(ByteString.copyFrom(proto.getContent().toByteArray())); builder.setContent(ByteString.copyFrom(proto.getContent().toByteArray()));
} }
this.envelope = builder.build(); this.envelope = builder.build();
} }
public SignalServiceEnvelope(int type, String sender, int senderDevice, long timestamp, byte[] legacyMessage, byte[] content, long serverTimestamp, String uuid) { public SignalServiceEnvelope(int type, String sender, int senderDevice, long timestamp, byte[] content, long serverTimestamp) {
Envelope.Builder builder = Envelope.newBuilder() Envelope.Builder builder = Envelope.newBuilder()
.setType(Envelope.Type.valueOf(type)) .setType(Envelope.Type.valueOf(type))
.setSource(sender) .setSource(sender)
@ -127,40 +121,22 @@ public class SignalServiceEnvelope {
.setTimestamp(timestamp) .setTimestamp(timestamp)
.setServerTimestamp(serverTimestamp); .setServerTimestamp(serverTimestamp);
if (uuid != null) {
builder.setServerGuid(uuid);
}
if (legacyMessage != null) builder.setLegacyMessage(ByteString.copyFrom(legacyMessage));
if (content != null) builder.setContent(ByteString.copyFrom(content)); if (content != null) builder.setContent(ByteString.copyFrom(content));
this.envelope = builder.build(); this.envelope = builder.build();
} }
public SignalServiceEnvelope(int type, long timestamp, byte[] legacyMessage, byte[] content, long serverTimestamp, String uuid) { public SignalServiceEnvelope(int type, long timestamp, byte[] content, long serverTimestamp) {
Envelope.Builder builder = Envelope.newBuilder() Envelope.Builder builder = Envelope.newBuilder()
.setType(Envelope.Type.valueOf(type)) .setType(Envelope.Type.valueOf(type))
.setTimestamp(timestamp) .setTimestamp(timestamp)
.setServerTimestamp(serverTimestamp); .setServerTimestamp(serverTimestamp);
if (uuid != null) {
builder.setServerGuid(uuid);
}
if (legacyMessage != null) builder.setLegacyMessage(ByteString.copyFrom(legacyMessage));
if (content != null) builder.setContent(ByteString.copyFrom(content)); if (content != null) builder.setContent(ByteString.copyFrom(content));
this.envelope = builder.build(); this.envelope = builder.build();
} }
public String getUuid() {
return envelope.getServerGuid();
}
public boolean hasUuid() {
return envelope.hasServerGuid();
}
public boolean hasSource() { public boolean hasSource() {
return envelope.hasSource() && envelope.getSource().length() > 0; return envelope.hasSource() && envelope.getSource().length() > 0;
} }
@ -208,20 +184,6 @@ public class SignalServiceEnvelope {
return envelope.getServerTimestamp(); return envelope.getServerTimestamp();
} }
/**
* @return Whether the envelope contains a SignalServiceDataMessage
*/
public boolean hasLegacyMessage() {
return envelope.hasLegacyMessage();
}
/**
* @return The envelope's containing SignalService message.
*/
public byte[] getLegacyMessage() {
return envelope.getLegacyMessage().toByteArray();
}
/** /**
* @return Whether the envelope contains an encrypted SignalServiceContent * @return Whether the envelope contains an encrypted SignalServiceContent
*/ */
@ -236,35 +198,10 @@ public class SignalServiceEnvelope {
return envelope.getContent().toByteArray(); return envelope.getContent().toByteArray();
} }
/**
* @return true if the containing message is a {@link org.session.libsignal.libsignal.protocol.SignalMessage}
*/
public boolean isSignalMessage() {
return envelope.getType().getNumber() == Envelope.Type.CIPHERTEXT_VALUE;
}
/**
* @return true if the containing message is a {@link org.session.libsignal.libsignal.protocol.PreKeySignalMessage}
*/
public boolean isPreKeySignalMessage() {
return envelope.getType().getNumber() == Envelope.Type.PREKEY_BUNDLE_VALUE;
}
/**
* @return true if the containing message is a delivery receipt.
*/
public boolean isReceipt() {
return envelope.getType().getNumber() == Envelope.Type.RECEIPT_VALUE;
}
public boolean isUnidentifiedSender() { public boolean isUnidentifiedSender() {
return envelope.getType().getNumber() == Envelope.Type.UNIDENTIFIED_SENDER_VALUE; return envelope.getType().getNumber() == Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
} }
public boolean isFallbackMessage() {
return envelope.getType().getNumber() == Envelope.Type.FALLBACK_MESSAGE_VALUE;
}
public boolean isClosedGroupCiphertext() { public boolean isClosedGroupCiphertext() {
return envelope.getType().getNumber() == Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE; return envelope.getType().getNumber() == Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE;
} }

View File

@ -11,12 +11,10 @@ public class SignalServiceTypingMessage {
private final Action action; private final Action action;
private final long timestamp; private final long timestamp;
private final Optional<byte[]> groupId;
public SignalServiceTypingMessage(Action action, long timestamp, Optional<byte[]> groupId) { public SignalServiceTypingMessage(Action action, long timestamp) {
this.action = action; this.action = action;
this.timestamp = timestamp; this.timestamp = timestamp;
this.groupId = groupId;
} }
public Action getAction() { public Action getAction() {
@ -27,10 +25,6 @@ public class SignalServiceTypingMessage {
return timestamp; return timestamp;
} }
public Optional<byte[]> getGroupId() {
return groupId;
}
public boolean isTypingStarted() { public boolean isTypingStarted() {
return action == Action.STARTED; return action == Action.STARTED;
} }

View File

@ -1,22 +0,0 @@
package org.session.libsignal.service.api.messages.multidevice;
import java.util.List;
public class BlockedListMessage {
private final List<String> numbers;
private final List<byte[]> groupIds;
public BlockedListMessage(List<String> numbers, List<byte[]> groupIds) {
this.numbers = numbers;
this.groupIds = groupIds;
}
public List<String> getNumbers() {
return numbers;
}
public List<byte[]> getGroupIds() {
return groupIds;
}
}

View File

@ -1,128 +0,0 @@
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.service.internal.util.Util;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class ChunkedInputStream {
protected final InputStream in;
public ChunkedInputStream(InputStream in) {
this.in = in;
}
protected int readInt32() throws IOException {
try {
byte[] bytes = new byte[4];
Util.readFully(in, bytes);
return bytes[0] << 24 | (bytes[1] & 0xFF) << 16 | (bytes[2] & 0xFF) << 8 | (bytes[3] & 0xFF);
} catch (IndexOutOfBoundsException e) {
throw new IOException(e);
}
}
protected int readRawVarint32() throws IOException {
byte tmp = (byte)in.read();
if (tmp >= 0) {
return tmp;
}
int result = tmp & 0x7f;
if ((tmp = (byte)in.read()) >= 0) {
result |= tmp << 7;
} else {
result |= (tmp & 0x7f) << 7;
if ((tmp = (byte)in.read()) >= 0) {
result |= tmp << 14;
} else {
result |= (tmp & 0x7f) << 14;
if ((tmp = (byte)in.read()) >= 0) {
result |= tmp << 21;
} else {
result |= (tmp & 0x7f) << 21;
result |= (tmp = (byte)in.read()) << 28;
if (tmp < 0) {
// Discard upper 32 bits.
for (int i = 0; i < 5; i++) {
if ((byte)in.read() >= 0) {
return result;
}
}
throw new IOException("Malformed varint!");
}
}
}
}
return result;
}
protected static final class LimitedInputStream extends FilterInputStream {
private long left;
private long mark = -1;
LimitedInputStream(InputStream in, long limit) {
super(in);
left = limit;
}
@Override public int available() throws IOException {
return (int) Math.min(in.available(), left);
}
// it's okay to mark even if mark isn't supported, as reset won't work
@Override public synchronized void mark(int readLimit) {
in.mark(readLimit);
mark = left;
}
@Override public int read() throws IOException {
if (left == 0) {
return -1;
}
int result = in.read();
if (result != -1) {
--left;
}
return result;
}
@Override public int read(byte[] b, int off, int len) throws IOException {
if (left == 0) {
return -1;
}
len = (int) Math.min(len, left);
int result = in.read(b, off, len);
if (result != -1) {
left -= result;
}
return result;
}
@Override public synchronized void reset() throws IOException {
if (!in.markSupported()) {
throw new IOException("Mark not supported");
}
if (mark == -1) {
throw new IOException("Mark not set");
}
in.reset();
left = mark;
}
@Override public long skip(long n) throws IOException {
n = Math.min(n, left);
long skipped = in.skip(n);
left -= skipped;
return skipped;
}
}
}

View File

@ -1,41 +0,0 @@
package org.session.libsignal.service.api.messages.multidevice;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class ChunkedOutputStream {
protected final OutputStream out;
public ChunkedOutputStream(OutputStream out) {
this.out = out;
}
protected void writeVarint32(int value) throws IOException {
while (true) {
if ((value & ~0x7F) == 0) {
out.write(value);
return;
} else {
out.write((value & 0x7F) | 0x80);
value >>>= 7;
}
}
}
protected void writeStream(InputStream in) throws IOException {
byte[] buffer = new byte[4096];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
}
protected byte[] toByteArray(int value) {
return new byte[] { (byte)(value >> 24), (byte)(value >> 16), (byte)(value >> 8), (byte)value };
}
}

View File

@ -1,39 +0,0 @@
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.libsignal.util.guava.Optional;
public class ConfigurationMessage {
private final Optional<Boolean> readReceipts;
private final Optional<Boolean> unidentifiedDeliveryIndicators;
private final Optional<Boolean> typingIndicators;
private final Optional<Boolean> linkPreviews;
public ConfigurationMessage(Optional<Boolean> readReceipts,
Optional<Boolean> unidentifiedDeliveryIndicators,
Optional<Boolean> typingIndicators,
Optional<Boolean> linkPreviews)
{
this.readReceipts = readReceipts;
this.unidentifiedDeliveryIndicators = unidentifiedDeliveryIndicators;
this.typingIndicators = typingIndicators;
this.linkPreviews = linkPreviews;
}
public Optional<Boolean> getReadReceipts() {
return readReceipts;
}
public Optional<Boolean> getUnidentifiedDeliveryIndicators() {
return unidentifiedDeliveryIndicators;
}
public Optional<Boolean> getTypingIndicators() {
return typingIndicators;
}
public Optional<Boolean> getLinkPreviews() {
return linkPreviews;
}
}

View File

@ -1,23 +0,0 @@
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
public class ContactsMessage {
private final SignalServiceAttachment contacts;
private final boolean complete;
public ContactsMessage(SignalServiceAttachment contacts, boolean complete) {
this.contacts = contacts;
this.complete = complete;
}
public SignalServiceAttachment getContactsStream() {
return contacts;
}
public boolean isComplete() {
return complete;
}
}

View File

@ -1,73 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream;
import org.session.libsignal.service.api.messages.multidevice.VerifiedMessage;
public class DeviceContact {
private final String number;
private final Optional<String> name;
private final Optional<SignalServiceAttachmentStream> avatar;
private final Optional<String> color;
private final Optional<VerifiedMessage> verified;
private final Optional<byte[]> profileKey;
private final boolean blocked;
private final Optional<Integer> expirationTimer;
public DeviceContact(String number, Optional<String> name,
Optional<SignalServiceAttachmentStream> avatar,
Optional<String> color,
Optional<VerifiedMessage> verified,
Optional<byte[]> profileKey,
boolean blocked,
Optional<Integer> expirationTimer)
{
this.number = number;
this.name = name;
this.avatar = avatar;
this.color = color;
this.verified = verified;
this.profileKey = profileKey;
this.blocked = blocked;
this.expirationTimer = expirationTimer;
}
public Optional<SignalServiceAttachmentStream> getAvatar() {
return avatar;
}
public Optional<String> getName() {
return name;
}
public String getNumber() {
return number;
}
public Optional<String> getColor() {
return color;
}
public Optional<VerifiedMessage> getVerified() {
return verified;
}
public Optional<byte[]> getProfileKey() {
return profileKey;
}
public boolean isBlocked() {
return blocked;
}
public Optional<Integer> getExpirationTimer() {
return expirationTimer;
}
}

View File

@ -1,121 +0,0 @@
/*
* Copyright (C) 2014-2018 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.libsignal.InvalidKeyException;
import org.session.libsignal.libsignal.InvalidMessageException;
import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream;
import org.session.libsignal.service.internal.push.SignalServiceProtos;
import org.session.libsignal.service.internal.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class DeviceContactsInputStream extends ChunkedInputStream {
private static final String TAG = DeviceContactsInputStream.class.getSimpleName();
public DeviceContactsInputStream(InputStream in) {
super(in);
}
public DeviceContact read() throws Exception {
try {
long detailsLength = readInt32();
byte[] detailsSerialized = new byte[(int) detailsLength];
Util.readFully(in, detailsSerialized);
SignalServiceProtos.ContactDetails details = SignalServiceProtos.ContactDetails.parseFrom(detailsSerialized);
String number = details.getNumber();
Optional<String> name = Optional.fromNullable(details.getName());
Optional<SignalServiceAttachmentStream> avatar = Optional.absent();
Optional<String> color = details.hasColor() ? Optional.of(details.getColor()) : Optional.<String>absent();
Optional<VerifiedMessage> verified = Optional.absent();
Optional<byte[]> profileKey = Optional.absent();
boolean blocked = false;
Optional<Integer> expireTimer = Optional.absent();
if (details.hasAvatar()) {
long avatarLength = details.getAvatar().getLength();
InputStream avatarStream = new LimitedInputStream(in, avatarLength);
String avatarContentType = details.getAvatar().getContentType();
avatar = Optional.of(new SignalServiceAttachmentStream(avatarStream, avatarContentType, avatarLength, Optional.<String>absent(), false, null));
}
if (details.hasVerified()) {
try {
String destination = details.getVerified().getDestination();
IdentityKey identityKey = new IdentityKey(details.getVerified().getIdentityKey().toByteArray(), 0);
VerifiedMessage.VerifiedState state;
switch (details.getVerified().getState()) {
case VERIFIED:
state = VerifiedMessage.VerifiedState.VERIFIED;
break;
case UNVERIFIED:
state = VerifiedMessage.VerifiedState.UNVERIFIED;
break;
case DEFAULT:
state = VerifiedMessage.VerifiedState.DEFAULT;
break;
default:
throw new InvalidMessageException("Unknown state: " + details.getVerified().getState());
}
verified = Optional.of(new VerifiedMessage(destination, identityKey, state, System.currentTimeMillis()));
} catch (InvalidKeyException e) {
Log.w(TAG, e);
verified = Optional.absent();
} catch (InvalidMessageException e) {
Log.w(TAG, e);
verified = Optional.absent();
}
}
if (details.hasProfileKey()) {
profileKey = Optional.fromNullable(details.getProfileKey().toByteArray());
}
if (details.hasExpireTimer() && details.getExpireTimer() > 0) {
expireTimer = Optional.of(details.getExpireTimer());
}
blocked = details.getBlocked();
return new DeviceContact(number, name, avatar, color, verified, profileKey, blocked, expireTimer);
} catch (IOException e) {
return null;
}
}
/**
* Read all device contacts.
*
* This will also close the input stream upon reading.
*/
public List<DeviceContact> readAll() throws Exception {
ArrayList<DeviceContact> devices = new ArrayList<DeviceContact>();
try {
DeviceContact deviceContact = read();
while (deviceContact != null) {
devices.add(deviceContact);
// Read the next contact
deviceContact = read();
}
return devices;
} finally {
in.close();
}
}
}

View File

@ -1,87 +0,0 @@
/*
* Copyright (C) 2014-2018 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import com.google.protobuf.ByteString;
import org.session.libsignal.service.internal.push.SignalServiceProtos;
import java.io.IOException;
import java.io.OutputStream;
public class DeviceContactsOutputStream extends ChunkedOutputStream {
public DeviceContactsOutputStream(OutputStream out) {
super(out);
}
public void write(DeviceContact contact) throws IOException {
writeContactDetails(contact);
writeAvatarImage(contact);
}
public void close() throws IOException {
out.close();
}
private void writeAvatarImage(DeviceContact contact) throws IOException {
if (contact.getAvatar().isPresent()) {
writeStream(contact.getAvatar().get().getInputStream());
}
}
private void writeContactDetails(DeviceContact contact) throws IOException {
SignalServiceProtos.ContactDetails.Builder contactDetails = SignalServiceProtos.ContactDetails.newBuilder();
contactDetails.setNumber(contact.getNumber());
if (contact.getName().isPresent()) {
contactDetails.setName(contact.getName().get());
}
if (contact.getAvatar().isPresent()) {
SignalServiceProtos.ContactDetails.Avatar.Builder avatarBuilder = SignalServiceProtos.ContactDetails.Avatar.newBuilder();
avatarBuilder.setContentType(contact.getAvatar().get().getContentType());
avatarBuilder.setLength((int)contact.getAvatar().get().getLength());
contactDetails.setAvatar(avatarBuilder);
}
if (contact.getColor().isPresent()) {
contactDetails.setColor(contact.getColor().get());
}
if (contact.getVerified().isPresent()) {
SignalServiceProtos.Verified.State state;
switch (contact.getVerified().get().getVerified()) {
case VERIFIED: state = SignalServiceProtos.Verified.State.VERIFIED; break;
case UNVERIFIED: state = SignalServiceProtos.Verified.State.UNVERIFIED; break;
default: state = SignalServiceProtos.Verified.State.DEFAULT; break;
}
contactDetails.setVerified(SignalServiceProtos.Verified.newBuilder()
.setDestination(contact.getVerified().get().getDestination())
.setIdentityKey(ByteString.copyFrom(contact.getVerified().get().getIdentityKey().serialize()))
.setState(state));
}
if (contact.getProfileKey().isPresent()) {
contactDetails.setProfileKey(ByteString.copyFrom(contact.getProfileKey().get()));
}
if (contact.getExpirationTimer().isPresent()) {
contactDetails.setExpireTimer(contact.getExpirationTimer().get());
}
contactDetails.setBlocked(contact.isBlocked());
byte[] serializedContactDetails = contactDetails.build().toByteArray();
// Loki - Since iOS has trouble parsing variable length integers, just write a fixed length one
out.write(toByteArray(serializedContactDetails.length));
out.write(serializedContactDetails);
}
}

View File

@ -1,76 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream;
import java.util.List;
public class DeviceGroup {
private final byte[] id;
private final Optional<String> name;
private final List<String> members;
private final List<String> admins;
private final Optional<SignalServiceAttachmentStream> avatar;
private final boolean active;
private final Optional<Integer> expirationTimer;
private final Optional<String> color;
private final boolean blocked;
public DeviceGroup(byte[] id, Optional<String> name, List<String> members,
List<String> admins,
Optional<SignalServiceAttachmentStream> avatar,
boolean active, Optional<Integer> expirationTimer,
Optional<String> color, boolean blocked)
{
this.id = id;
this.name = name;
this.members = members;
this.admins = admins;
this.avatar = avatar;
this.active = active;
this.expirationTimer = expirationTimer;
this.color = color;
this.blocked = blocked;
}
public Optional<SignalServiceAttachmentStream> getAvatar() {
return avatar;
}
public Optional<String> getName() {
return name;
}
public byte[] getId() {
return id;
}
public List<String> getMembers() {
return members;
}
public List<String> getAdmins() { return admins; }
public boolean isActive() {
return active;
}
public Optional<Integer> getExpirationTimer() {
return expirationTimer;
}
public Optional<String> getColor() {
return color;
}
public boolean isBlocked() {
return blocked;
}
}

View File

@ -1,84 +0,0 @@
/*
* Copyright (C) 2014-2018 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream;
import org.session.libsignal.service.internal.push.SignalServiceProtos;
import org.session.libsignal.service.internal.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class DeviceGroupsInputStream extends ChunkedInputStream{
public DeviceGroupsInputStream(InputStream in) {
super(in);
}
public DeviceGroup read() throws IOException {
try {
long detailsLength = readInt32();
byte[] detailsSerialized = new byte[(int) detailsLength];
Util.readFully(in, detailsSerialized);
SignalServiceProtos.GroupDetails details = SignalServiceProtos.GroupDetails.parseFrom(detailsSerialized);
if (!details.hasId()) {
throw new IOException("ID missing on group record!");
}
byte[] id = details.getId().toByteArray();
Optional<String> name = Optional.fromNullable(details.getName());
List<String> members = details.getMembersList();
List<String> admins = details.getAdminsList();
Optional<SignalServiceAttachmentStream> avatar = Optional.absent();
boolean active = details.getActive();
Optional<Integer> expirationTimer = Optional.absent();
Optional<String> color = Optional.fromNullable(details.getColor());
boolean blocked = details.getBlocked();
if (details.hasAvatar()) {
long avatarLength = details.getAvatar().getLength();
InputStream avatarStream = new ChunkedInputStream.LimitedInputStream(in, avatarLength);
String avatarContentType = details.getAvatar().getContentType();
avatar = Optional.of(new SignalServiceAttachmentStream(avatarStream, avatarContentType, avatarLength, Optional.<String>absent(), false, null));
}
if (details.hasExpireTimer() && details.getExpireTimer() > 0) {
expirationTimer = Optional.of(details.getExpireTimer());
}
return new DeviceGroup(id, name, members, admins, avatar, active, expirationTimer, color, blocked);
} catch (IOException e) {
return null;
}
}
/**
* Read all device contacts.
*
* This will also close the input stream upon reading.
*/
public List<DeviceGroup> readAll() throws Exception {
ArrayList<DeviceGroup> devices = new ArrayList<>();
try {
DeviceGroup deviceGroup = read();
while (deviceGroup != null) {
devices.add(deviceGroup);
// Read the next contact
deviceGroup = read();
}
return devices;
} finally {
in.close();
}
}
}

View File

@ -1,74 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import com.google.protobuf.ByteString;
import org.session.libsignal.service.internal.push.SignalServiceProtos;
import java.io.IOException;
import java.io.OutputStream;
public class DeviceGroupsOutputStream extends ChunkedOutputStream {
public DeviceGroupsOutputStream(OutputStream out) {
super(out);
}
public void write(DeviceGroup group) throws IOException {
writeGroupDetails(group);
writeAvatarImage(group);
}
public void close() throws IOException {
out.close();
}
private void writeAvatarImage(DeviceGroup contact) throws IOException {
// Loki - Temporarily disable this
/*
if (contact.getAvatar().isPresent()) {
writeStream(contact.getAvatar().get().getInputStream());
}
*/
}
private void writeGroupDetails(DeviceGroup group) throws IOException {
SignalServiceProtos.GroupDetails.Builder groupDetails = SignalServiceProtos.GroupDetails.newBuilder();
groupDetails.setId(ByteString.copyFrom(group.getId()));
if (group.getName().isPresent()) {
groupDetails.setName(group.getName().get());
}
if (group.getAvatar().isPresent()) {
SignalServiceProtos.GroupDetails.Avatar.Builder avatarBuilder = SignalServiceProtos.GroupDetails.Avatar.newBuilder();
avatarBuilder.setContentType(group.getAvatar().get().getContentType());
avatarBuilder.setLength((int)group.getAvatar().get().getLength());
groupDetails.setAvatar(avatarBuilder);
}
if (group.getExpirationTimer().isPresent()) {
groupDetails.setExpireTimer(group.getExpirationTimer().get());
}
if (group.getColor().isPresent()) {
groupDetails.setColor(group.getColor().get());
}
groupDetails.addAllMembers(group.getMembers());
groupDetails.addAllAdmins(group.getAdmins());
groupDetails.setActive(group.isActive());
groupDetails.setBlocked(group.isBlocked());
byte[] serializedContactDetails = groupDetails.build().toByteArray();
// Loki - Since iOS has trouble parsing variable length integers, just write a fixed length one
out.write(toByteArray(serializedContactDetails.length));
out.write(serializedContactDetails);
}
}

View File

@ -1,42 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import com.fasterxml.jackson.annotation.JsonProperty;
public class DeviceInfo {
@JsonProperty
private long id;
@JsonProperty
private String name;
@JsonProperty
private long created;
@JsonProperty
private long lastSeen;
public DeviceInfo() {}
public long getId() {
return id;
}
public String getName() {
return name;
}
public long getCreated() {
return created;
}
public long getLastSeen() {
return lastSeen;
}
}

View File

@ -1,30 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import java.util.LinkedList;
import java.util.List;
public class ReadMessage {
private final String sender;
private final long timestamp;
public ReadMessage(String sender, long timestamp) {
this.sender = sender;
this.timestamp = timestamp;
}
public long getTimestamp() {
return timestamp;
}
public String getSender() {
return sender;
}
}

View File

@ -1,34 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.service.internal.push.SignalServiceProtos.SyncMessage.Request;
public class RequestMessage {
private final Request request;
public RequestMessage(Request request) {
this.request = request;
}
public boolean isContactsRequest() {
return request.getType() == Request.Type.CONTACTS;
}
public boolean isGroupsRequest() {
return request.getType() == Request.Type.GROUPS;
}
public boolean isBlockedListRequest() {
return request.getType() == Request.Type.BLOCKED;
}
public boolean isConfigurationRequest() {
return request.getType() == Request.Type.CONFIGURATION;
}
}

View File

@ -1,67 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class SentTranscriptMessage {
private final Optional<String> destination;
private final long timestamp;
private final long expirationStartTimestamp;
private final SignalServiceDataMessage message;
private final Map<String, Boolean> unidentifiedStatus;
// Loki - Open groups
public long messageServerID = -1;
public SentTranscriptMessage(String destination, long timestamp, SignalServiceDataMessage message,
long expirationStartTimestamp, Map<String, Boolean> unidentifiedStatus)
{
this.destination = Optional.of(destination);
this.timestamp = timestamp;
this.message = message;
this.expirationStartTimestamp = expirationStartTimestamp;
this.unidentifiedStatus = new HashMap<String, Boolean>(unidentifiedStatus);
}
public SentTranscriptMessage(long timestamp, SignalServiceDataMessage message) {
this.destination = Optional.absent();
this.timestamp = timestamp;
this.message = message;
this.expirationStartTimestamp = 0;
this.unidentifiedStatus = Collections.emptyMap();
}
public Optional<String> getDestination() {
return destination;
}
public long getTimestamp() {
return timestamp;
}
public long getExpirationStartTimestamp() {
return expirationStartTimestamp;
}
public SignalServiceDataMessage getMessage() {
return message;
}
public boolean isUnidentified(String destination) {
if (unidentifiedStatus.containsKey(destination)) {
return unidentifiedStatus.get(destination);
}
return false;
}
}

View File

@ -1,250 +0,0 @@
/**
* Copyright (C) 2014-2016 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.multidevice.StickerPackOperationMessage;
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
import java.util.LinkedList;
import java.util.List;
public class SignalServiceSyncMessage {
private final Optional<SentTranscriptMessage> sent;
private final Optional<ContactsMessage> contacts;
private final Optional<SignalServiceAttachment> groups;
private final Optional<List<PublicChat>> openGroups;
private final Optional<BlockedListMessage> blockedList;
private final Optional<RequestMessage> request;
private final Optional<List<ReadMessage>> reads;
private final Optional<VerifiedMessage> verified;
private final Optional<ConfigurationMessage> configuration;
private final Optional<List<StickerPackOperationMessage>> stickerPackOperations;
private SignalServiceSyncMessage(Optional<SentTranscriptMessage> sent,
Optional<ContactsMessage> contacts,
Optional<SignalServiceAttachment> groups,
Optional<BlockedListMessage> blockedList,
Optional<RequestMessage> request,
Optional<List<ReadMessage>> reads,
Optional<VerifiedMessage> verified,
Optional<ConfigurationMessage> configuration,
Optional<List<StickerPackOperationMessage>> stickerPackOperations,
Optional<List<PublicChat>> openGroups)
{
this.sent = sent;
this.contacts = contacts;
this.groups = groups;
this.blockedList = blockedList;
this.request = request;
this.reads = reads;
this.verified = verified;
this.configuration = configuration;
this.stickerPackOperations = stickerPackOperations;
this.openGroups = openGroups;
}
public static SignalServiceSyncMessage forSentTranscript(SentTranscriptMessage sent) {
return new SignalServiceSyncMessage(Optional.of(sent),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.<List<ReadMessage>>absent(),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forContacts(ContactsMessage contacts) {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.of(contacts),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.<List<ReadMessage>>absent(),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forGroups(SignalServiceAttachment groups) {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.of(groups),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.<List<ReadMessage>>absent(),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forRequest(RequestMessage request) {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.of(request),
Optional.<List<ReadMessage>>absent(),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forRead(List<ReadMessage> reads) {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.of(reads),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forRead(ReadMessage read) {
List<ReadMessage> reads = new LinkedList<ReadMessage>();
reads.add(read);
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.of(reads),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forVerified(VerifiedMessage verifiedMessage) {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.<List<ReadMessage>>absent(),
Optional.of(verifiedMessage),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forBlocked(BlockedListMessage blocked) {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.of(blocked),
Optional.<RequestMessage>absent(),
Optional.<List<ReadMessage>>absent(),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forConfiguration(ConfigurationMessage configuration) {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.<List<ReadMessage>>absent(),
Optional.<VerifiedMessage>absent(),
Optional.of(configuration),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forStickerPackOperations(List<StickerPackOperationMessage> stickerPackOperations) {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.<List<ReadMessage>>absent(),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.of(stickerPackOperations),
Optional.<List<PublicChat>>absent());
}
public static SignalServiceSyncMessage forOpenGroups(List<PublicChat> openGroups) {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.<List<ReadMessage>>absent(),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.of(openGroups));
}
public static SignalServiceSyncMessage empty() {
return new SignalServiceSyncMessage(Optional.<SentTranscriptMessage>absent(),
Optional.<ContactsMessage>absent(),
Optional.<SignalServiceAttachment>absent(),
Optional.<BlockedListMessage>absent(),
Optional.<RequestMessage>absent(),
Optional.<List<ReadMessage>>absent(),
Optional.<VerifiedMessage>absent(),
Optional.<ConfigurationMessage>absent(),
Optional.<List<StickerPackOperationMessage>>absent(),
Optional.<List<PublicChat>>absent());
}
public Optional<SentTranscriptMessage> getSent() {
return sent;
}
public Optional<SignalServiceAttachment> getGroups() {
return groups;
}
public Optional<ContactsMessage> getContacts() {
return contacts;
}
public Optional<RequestMessage> getRequest() {
return request;
}
public Optional<List<ReadMessage>> getRead() {
return reads;
}
public Optional<BlockedListMessage> getBlockedList() {
return blockedList;
}
public Optional<VerifiedMessage> getVerified() {
return verified;
}
public Optional<ConfigurationMessage> getConfiguration() {
return configuration;
}
public Optional<List<StickerPackOperationMessage>> getStickerPackOperations() { return stickerPackOperations; }
public Optional<List<PublicChat>> getOpenGroups() { return openGroups; }
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.Sync); }
}

View File

@ -1,32 +0,0 @@
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.libsignal.util.guava.Optional;
public class StickerPackOperationMessage {
private final Optional<byte[]> packId;
private final Optional<byte[]> packKey;
private final Optional<Type> type;
public StickerPackOperationMessage(byte[] packId, byte[] packKey, Type type) {
this.packId = Optional.fromNullable(packId);
this.packKey = Optional.fromNullable(packKey);
this.type = Optional.fromNullable(type);
}
public Optional<byte[]> getPackId() {
return packId;
}
public Optional<byte[]> getPackKey() {
return packKey;
}
public Optional<Type> getType() {
return type;
}
public enum Type {
INSTALL, REMOVE
}
}

View File

@ -1,42 +0,0 @@
package org.session.libsignal.service.api.messages.multidevice;
import org.session.libsignal.libsignal.IdentityKey;
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
public class VerifiedMessage {
public enum VerifiedState {
DEFAULT, VERIFIED, UNVERIFIED
}
private final String destination;
private final IdentityKey identityKey;
private final VerifiedState verified;
private final long timestamp;
public VerifiedMessage(String destination, IdentityKey identityKey, VerifiedState verified, long timestamp) {
this.destination = destination;
this.identityKey = identityKey;
this.verified = verified;
this.timestamp = timestamp;
}
public String getDestination() {
return destination;
}
public IdentityKey getIdentityKey() {
return identityKey;
}
public VerifiedState getVerified() {
return verified;
}
public long getTimestamp() {
return timestamp;
}
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.Verified); }
}

View File

@ -1,19 +0,0 @@
package org.session.libsignal.service.internal.push;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.session.libsignal.service.api.messages.multidevice.DeviceInfo;
import java.util.List;
public class DeviceInfoList {
@JsonProperty
private List<DeviceInfo> devices;
public DeviceInfoList() {}
public List<DeviceInfo> getDevices() {
return devices;
}
}

View File

@ -20,7 +20,6 @@ import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.crypto.UnidentifiedAccess; import org.session.libsignal.service.api.crypto.UnidentifiedAccess;
import org.session.libsignal.service.api.messages.SignalServiceAttachment.ProgressListener; import org.session.libsignal.service.api.messages.SignalServiceAttachment.ProgressListener;
import org.session.libsignal.service.api.messages.calls.TurnServerInfo; import org.session.libsignal.service.api.messages.calls.TurnServerInfo;
import org.session.libsignal.service.api.messages.multidevice.DeviceInfo;
import org.session.libsignal.service.api.profiles.SignalServiceProfile; import org.session.libsignal.service.api.profiles.SignalServiceProfile;
import org.session.libsignal.service.api.push.ContactTokenDetails; import org.session.libsignal.service.api.push.ContactTokenDetails;
import org.session.libsignal.service.api.push.SignalServiceAddress; import org.session.libsignal.service.api.push.SignalServiceAddress;
@ -217,11 +216,6 @@ public class PushServiceSocket {
return JsonUtil.fromJson(responseText, DeviceCode.class).getVerificationCode(); return JsonUtil.fromJson(responseText, DeviceCode.class).getVerificationCode();
} }
public List<DeviceInfo> getDevices() throws IOException {
String responseText = makeServiceRequest(String.format(DEVICE_PATH, ""), "GET", null);
return JsonUtil.fromJson(responseText, DeviceInfoList.class).getDevices();
}
public void removeDevice(long deviceId) throws IOException { public void removeDevice(long deviceId) throws IOException {
makeServiceRequest(String.format(DEVICE_PATH, String.valueOf(deviceId)), "DELETE", null); makeServiceRequest(String.format(DEVICE_PATH, String.valueOf(deviceId)), "DELETE", null);
} }

View File

@ -19,18 +19,12 @@ public class SignalServiceEnvelopeEntity {
@JsonProperty @JsonProperty
private int sourceDevice; private int sourceDevice;
@JsonProperty
private byte[] message;
@JsonProperty @JsonProperty
private byte[] content; private byte[] content;
@JsonProperty @JsonProperty
private long serverTimestamp; private long serverTimestamp;
@JsonProperty
private String guid;
public SignalServiceEnvelopeEntity() {} public SignalServiceEnvelopeEntity() {}
public int getType() { public int getType() {
@ -53,10 +47,6 @@ public class SignalServiceEnvelopeEntity {
return sourceDevice; return sourceDevice;
} }
public byte[] getMessage() {
return message;
}
public byte[] getContent() { public byte[] getContent() {
return content; return content;
} }
@ -64,8 +54,4 @@ public class SignalServiceEnvelopeEntity {
public long getServerTimestamp() { public long getServerTimestamp() {
return serverTimestamp; return serverTimestamp;
} }
public String getServerUuid() {
return guid;
}
} }

View File

@ -1,32 +0,0 @@
package org.session.libsignal.service.loki.crypto
import org.session.libsignal.metadata.certificate.CertificateValidator
import org.session.libsignal.libsignal.InvalidMessageException
import org.session.libsignal.libsignal.loki.FallbackSessionCipher
import org.session.libsignal.libsignal.loki.SessionResetProtocol
import org.session.libsignal.libsignal.state.SignalProtocolStore
import org.session.libsignal.service.api.crypto.SignalServiceCipher
import org.session.libsignal.service.api.messages.SignalServiceEnvelope
import org.session.libsignal.service.api.push.SignalServiceAddress
import org.session.libsignal.service.internal.push.PushTransportDetails
import org.session.libsignal.service.loki.api.crypto.SessionProtocol
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
class LokiServiceCipher(localAddress: SignalServiceAddress, private val signalProtocolStore: SignalProtocolStore, sessionProtocolImpl: SessionProtocol, sessionResetProtocol: SessionResetProtocol, apiDB: LokiAPIDatabaseProtocol, certificateValidator: CertificateValidator?) : SignalServiceCipher(localAddress, signalProtocolStore, sessionResetProtocol, sessionProtocolImpl, apiDB, certificateValidator) {
private val userPrivateKey get() = signalProtocolStore.identityKeyPair.privateKey.serialize()
override fun decrypt(envelope: SignalServiceEnvelope, ciphertext: ByteArray): Plaintext {
return if (envelope.isFallbackMessage) decryptFallbackMessage(envelope, ciphertext) else super.decrypt(envelope, ciphertext)
}
private fun decryptFallbackMessage(envelope: SignalServiceEnvelope, ciphertext: ByteArray): Plaintext {
val cipher = FallbackSessionCipher(userPrivateKey, envelope.source)
val paddedMessageBody = cipher.decrypt(ciphertext) ?: throw InvalidMessageException("Failed to decrypt fallback message.")
val transportDetails = PushTransportDetails(FallbackSessionCipher.sessionVersion)
val unpaddedMessageBody = transportDetails.getStrippedPaddingMessageBody(paddedMessageBody)
val metadata = Metadata(envelope.source, envelope.sourceDevice, envelope.timestamp, false)
return Plaintext(metadata, unpaddedMessageBody)
}
}