mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
WIP: clean up signal protocol
This commit is contained in:
parent
0d2f5e0cde
commit
1a907fcf54
@ -27,6 +27,7 @@ import org.session.libsignal.metadata.ProtocolLegacyMessageException;
|
||||
import org.session.libsignal.metadata.ProtocolNoSessionException;
|
||||
import org.session.libsignal.metadata.ProtocolUntrustedIdentityException;
|
||||
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.utilities.PromiseUtilities;
|
||||
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.SignalServiceReceiptMessage;
|
||||
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.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.utilities.PublicKeyValidation;
|
||||
|
||||
@ -248,7 +245,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
SessionResetProtocol sessionResetProtocol = new SessionResetImplementation(context);
|
||||
SignalServiceAddress localAddress = new SignalServiceAddress(TextSecurePreferences.getLocalNumber(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);
|
||||
|
||||
@ -265,14 +262,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
MultiDeviceProtocol.handleConfigurationMessage(context, content.configurationMessageProto.get(), content.getSender(), content.getTimestamp());
|
||||
} else if (content.getDataMessage().isPresent()) {
|
||||
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()) {
|
||||
ClosedGroupsProtocolV2.handleMessage(context, message.getClosedGroupUpdateV2().get(), message.getTimestamp(), envelope.getSource(), content.getSender());
|
||||
if (message.getClosedGroupControlMessage().isPresent()) {
|
||||
ClosedGroupsProtocolV2.handleMessage(context, message.getClosedGroupControlMessage().get(), message.getTimestamp(), envelope.getSource(), content.getSender());
|
||||
}
|
||||
if (message.isEndSession()) {
|
||||
handleEndSessionMessage(content, smsMessageId);
|
||||
} else if (message.isGroupUpdate()) {
|
||||
if (message.isGroupUpdate()) {
|
||||
handleGroupMessage(content, message, smsMessageId);
|
||||
} else if (message.isExpirationUpdate()) {
|
||||
handleExpirationUpdate(content, message, smsMessageId);
|
||||
@ -293,8 +288,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
if (SessionMetaProtocol.shouldSendDeliveryReceipt(message, Address.fromSerialized(content.getSender()))) {
|
||||
handleNeedsDeliveryReceipt(content, message);
|
||||
}
|
||||
} else if (content.getSyncMessage().isPresent()) {
|
||||
throw new UnsupportedOperationException("Device link operations are not supported!");
|
||||
} else if (content.getReceiptMessage().isPresent()) {
|
||||
SignalServiceReceiptMessage message = content.getReceiptMessage().get();
|
||||
|
||||
@ -311,37 +304,16 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
// if (envelope.isPreKeySignalMessage()) {
|
||||
// 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) {
|
||||
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
|
||||
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e);
|
||||
}
|
||||
} catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolUntrustedIdentityException e) {
|
||||
Log.w(TAG, e);
|
||||
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e);
|
||||
}catch (StorageFailedException e) {
|
||||
Log.w(TAG, e);
|
||||
handleCorruptMessage(e.getSender(), e.getSenderDevice(), envelope.getTimestamp(), smsMessageId, e);
|
||||
} catch (ProtocolNoSessionException e) {
|
||||
} catch (InvalidMetadataMessageException 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());
|
||||
|
||||
database.insertSecureDecryptedMessageInbox(mediaMessage, -1);
|
||||
@ -448,7 +419,6 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
Optional<QuoteModel> quote = getValidatedQuote(message.getQuote());
|
||||
Optional<List<Contact>> sharedContacts = getContacts(message.getSharedContacts());
|
||||
Optional<List<LinkPreview>> linkPreviews = getLinkPreviews(message.getPreviews(), message.getBody().or(""));
|
||||
Optional<Attachment> sticker = getStickerAttachment(message.getSticker());
|
||||
|
||||
Address masterAddress = masterRecipient.getAddress();
|
||||
|
||||
@ -523,7 +493,7 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
} else {
|
||||
IncomingMediaMessage mediaMessage = new IncomingMediaMessage(masterAddress, message.getTimestamp(), -1,
|
||||
message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), message.getAttachments(),
|
||||
quote, sharedContacts, linkPreviews, sticker);
|
||||
quote, sharedContacts, linkPreviews);
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
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,
|
||||
@NonNull SignalServiceDataMessage message,
|
||||
@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,
|
||||
@NonNull Optional<Long> smsMessageId, @NonNull Throwable e)
|
||||
{
|
||||
@ -1059,17 +845,8 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
|
||||
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());
|
||||
threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(author);
|
||||
}
|
||||
|
||||
if (threadId <= 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (message.getGroupInfo().isPresent()) {
|
||||
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 {
|
||||
return sender.isBlocked();
|
||||
}
|
||||
} else if (content.getSyncMessage().isPresent()) {
|
||||
throw new UnsupportedOperationException("Device link operations are not supported!");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -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.SignalServiceDataMessage
|
||||
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.loki.api.fileserver.FileServerAPI
|
||||
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)
|
||||
// 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 ->
|
||||
/*
|
||||
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.successBackground { messages ->
|
||||
|
@ -13,6 +13,7 @@ import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||
import org.session.libsignal.libsignal.util.guava.Optional
|
||||
import org.session.libsignal.service.api.messages.SignalServiceGroup
|
||||
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.utilities.ThreadUtils
|
||||
import org.session.libsignal.service.loki.utilities.hexEncodedPublicKey
|
||||
@ -402,48 +403,48 @@ object ClosedGroupsProtocolV2 {
|
||||
}
|
||||
|
||||
@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 }
|
||||
when (closedGroupUpdate.type) {
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey, sentTimestamp)
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED -> handleClosedGroupMembersRemoved(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT -> handleClosedGroupMemberLeft(context, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE -> handleClosedGroupUpdate(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.NEW -> handleNewClosedGroup(context, closedGroupUpdate, senderPublicKey, sentTimestamp)
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> handleClosedGroupMembersRemoved(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED -> handleClosedGroupMembersAdded(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> handleClosedGroupNameChange(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> handleClosedGroupMemberLeft(context, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.UPDATE -> handleClosedGroupUpdate(context, closedGroupUpdate, sentTimestamp, groupPublicKey, senderPublicKey)
|
||||
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> handleGroupEncryptionKeyPair(context, closedGroupUpdate, groupPublicKey, senderPublicKey)
|
||||
else -> {
|
||||
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)
|
||||
if (record != null) return false
|
||||
|
||||
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.encryptionKeyPair.publicKey ?: ByteString.copyFrom(ByteArray(0))).isEmpty && closedGroupUpdate.membersCount > 0 && closedGroupUpdate.adminsCount > 0)
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED,
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED,
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> {
|
||||
closedGroupUpdate.membersCount > 0
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> {
|
||||
senderPublicKey.isNotEmpty()
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE,
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.UPDATE,
|
||||
DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> {
|
||||
!closedGroupUpdate.name.isNullOrEmpty()
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> true
|
||||
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> true
|
||||
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
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
@ -483,7 +484,7 @@ object ClosedGroupsProtocolV2 {
|
||||
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 groupDB = DatabaseFactory.getGroupDatabase(context)
|
||||
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 apiDB = DatabaseFactory.getLokiAPIDatabase(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)
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(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
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)!!
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
@ -728,7 +729,7 @@ object ClosedGroupsProtocolV2 {
|
||||
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
|
||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||
val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
|
||||
|
@ -70,8 +70,7 @@ public class IncomingMediaMessage {
|
||||
Optional<List<SignalServiceAttachment>> attachments,
|
||||
Optional<QuoteModel> quote,
|
||||
Optional<List<Contact>> sharedContacts,
|
||||
Optional<List<LinkPreview>> linkPreviews,
|
||||
Optional<Attachment> sticker)
|
||||
Optional<List<LinkPreview>> linkPreviews)
|
||||
{
|
||||
this.push = true;
|
||||
this.from = from;
|
||||
@ -89,10 +88,6 @@ public class IncomingMediaMessage {
|
||||
this.attachments.addAll(PointerAttachment.forPointers(attachments));
|
||||
this.sharedContacts.addAll(sharedContacts.or(Collections.emptyList()));
|
||||
this.linkPreviews.addAll(linkPreviews.or(Collections.emptyList()));
|
||||
|
||||
if (sticker.isPresent()) {
|
||||
this.attachments.add(sticker.get());
|
||||
}
|
||||
}
|
||||
|
||||
public static IncomingMediaMessage from(VisibleMessage message,
|
||||
@ -104,7 +99,7 @@ public class IncomingMediaMessage {
|
||||
Optional<List<LinkPreview>> linkPreviews)
|
||||
{
|
||||
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() {
|
||||
|
@ -6,6 +6,7 @@ import org.session.libsignal.libsignal.ecc.DjbECPublicKey
|
||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||
import org.session.libsignal.utilities.logging.Log
|
||||
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.utilities.Hex
|
||||
|
||||
@ -55,10 +56,10 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
const val TAG = "ClosedGroupControlMessage"
|
||||
|
||||
fun fromProto(proto: SignalServiceProtos.Content): ClosedGroupControlMessage? {
|
||||
val closedGroupControlMessageProto = proto.dataMessage?.closedGroupUpdateV2 ?: return null
|
||||
val closedGroupControlMessageProto = proto.dataMessage?.closedGroupControlMessage ?: return null
|
||||
val kind: Kind
|
||||
when(closedGroupControlMessageProto.type) {
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.NEW -> {
|
||||
val publicKey = closedGroupControlMessageProto.publicKey ?: return null
|
||||
val name = closedGroupControlMessageProto.name ?: return null
|
||||
val encryptionKeyPairAsProto = closedGroupControlMessageProto.encryptionKeyPair ?: return null
|
||||
@ -71,29 +72,31 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.UPDATE -> {
|
||||
val name = closedGroupControlMessageProto.name ?: return null
|
||||
kind = Kind.Update(name, closedGroupControlMessageProto.membersList)
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR -> {
|
||||
val publicKey = closedGroupControlMessageProto.publicKey
|
||||
val wrappers = closedGroupControlMessageProto.wrappersList.mapNotNull { KeyPairWrapper.fromProto(it) }
|
||||
kind = Kind.EncryptionKeyPair(publicKey, wrappers)
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE -> {
|
||||
val name = closedGroupControlMessageProto.name ?: return null
|
||||
kind = Kind.NameChange(name)
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED -> {
|
||||
kind = Kind.MembersAdded(closedGroupControlMessageProto.membersList)
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED -> {
|
||||
kind = Kind.MembersRemoved(closedGroupControlMessageProto.membersList)
|
||||
}
|
||||
SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT -> {
|
||||
DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT -> {
|
||||
kind = Kind.MemberLeft
|
||||
}
|
||||
//TODO: SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR_REQUEST
|
||||
DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR_REQUEST -> {
|
||||
kind = Kind.EncryptionKeyPairRequest
|
||||
}
|
||||
}
|
||||
return ClosedGroupControlMessage(kind)
|
||||
}
|
||||
@ -130,10 +133,10 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
val closedGroupControlMessage: SignalServiceProtos.ClosedGroupUpdateV2.Builder = SignalServiceProtos.ClosedGroupUpdateV2.newBuilder()
|
||||
val closedGroupControlMessage: DataMessage.ClosedGroupControlMessage.Builder = DataMessage.ClosedGroupControlMessage.newBuilder()
|
||||
when (kind) {
|
||||
is Kind.New -> {
|
||||
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.NEW
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.NEW
|
||||
closedGroupControlMessage.publicKey = kind.publicKey
|
||||
closedGroupControlMessage.name = kind.name
|
||||
val encryptionKeyPairAsProto = SignalServiceProtos.KeyPair.newBuilder()
|
||||
@ -150,37 +153,37 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
closedGroupControlMessage.addAllAdmins(kind.admins)
|
||||
}
|
||||
is Kind.Update -> {
|
||||
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.UPDATE
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.UPDATE
|
||||
closedGroupControlMessage.name = kind.name
|
||||
closedGroupControlMessage.addAllMembers(kind.members)
|
||||
}
|
||||
is Kind.EncryptionKeyPair -> {
|
||||
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR
|
||||
closedGroupControlMessage.publicKey = kind.publicKey
|
||||
closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() })
|
||||
}
|
||||
is Kind.NameChange -> {
|
||||
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.NAME_CHANGE
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.NAME_CHANGE
|
||||
closedGroupControlMessage.name = kind.name
|
||||
}
|
||||
is Kind.MembersAdded -> {
|
||||
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_ADDED
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBERS_ADDED
|
||||
closedGroupControlMessage.addAllMembers(kind.members)
|
||||
}
|
||||
is Kind.MembersRemoved -> {
|
||||
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBERS_REMOVED
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBERS_REMOVED
|
||||
closedGroupControlMessage.addAllMembers(kind.members)
|
||||
}
|
||||
is Kind.MemberLeft -> {
|
||||
closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.MEMBER_LEFT
|
||||
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.MEMBER_LEFT
|
||||
}
|
||||
is Kind.EncryptionKeyPairRequest -> {
|
||||
// TODO: closedGroupControlMessage.type = SignalServiceProtos.ClosedGroupUpdateV2.Type.ENCRYPTION_KEY_PAIR_REQUEST
|
||||
}
|
||||
}
|
||||
val contentProto = SignalServiceProtos.Content.newBuilder()
|
||||
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
|
||||
dataMessageProto.closedGroupUpdateV2 = closedGroupControlMessage.build()
|
||||
val dataMessageProto = DataMessage.newBuilder()
|
||||
dataMessageProto.closedGroupControlMessage = closedGroupControlMessage.build()
|
||||
// Group context
|
||||
contentProto.dataMessage = dataMessageProto.build()
|
||||
return contentProto.build()
|
||||
@ -197,15 +200,15 @@ class ClosedGroupControlMessage() : ControlMessage() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromProto(proto: SignalServiceProtos.ClosedGroupUpdateV2.KeyPairWrapper): KeyPairWrapper {
|
||||
fun fromProto(proto: DataMessage.ClosedGroupControlMessage.KeyPairWrapper): KeyPairWrapper {
|
||||
return KeyPairWrapper(proto.publicKey.toByteArray().toHexString(), proto.encryptedKeyPair)
|
||||
}
|
||||
}
|
||||
|
||||
fun toProto(): SignalServiceProtos.ClosedGroupUpdateV2.KeyPairWrapper? {
|
||||
fun toProto(): DataMessage.ClosedGroupControlMessage.KeyPairWrapper? {
|
||||
val publicKey = publicKey ?: 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.encryptedKeyPair = encryptedKeyPair
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ class Profile() {
|
||||
val profileProto = proto.profile ?: return null
|
||||
val displayName = profileProto.displayName ?: return null
|
||||
val profileKey = proto.profileKey
|
||||
val profilePictureURL = profileProto.profilePictureURL
|
||||
val profilePictureURL = profileProto.profilePicture
|
||||
profileKey?.let {
|
||||
profilePictureURL?.let {
|
||||
return Profile(displayName = displayName, profileKey = profileKey.toByteArray(), profilePictureURL = profilePictureURL)
|
||||
@ -41,12 +41,12 @@ class Profile() {
|
||||
return null
|
||||
}
|
||||
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
|
||||
val profileProto = SignalServiceProtos.LokiUserProfile.newBuilder()
|
||||
val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder()
|
||||
profileProto.displayName = displayName
|
||||
val profileKey = profileKey
|
||||
profileKey?.let { dataMessageProto.profileKey = ByteString.copyFrom(profileKey) }
|
||||
val profilePictureURL = profilePictureURL
|
||||
profilePictureURL?.let { profileProto.profilePictureURL = profilePictureURL }
|
||||
profilePictureURL?.let { profileProto.profilePicture = profilePictureURL }
|
||||
// Build
|
||||
try {
|
||||
dataMessageProto.profile = profileProto.build()
|
||||
|
@ -162,11 +162,11 @@ class OpenGroupPoller(private val openGroup: OpenGroup) {
|
||||
}
|
||||
val messageServerID = message.serverID
|
||||
// Profile
|
||||
val profileProto = LokiUserProfile.newBuilder()
|
||||
val profileProto = DataMessage.LokiProfile.newBuilder()
|
||||
profileProto.setDisplayName(message.displayName)
|
||||
val profilePicture = message.profilePicture
|
||||
if (profilePicture != null) {
|
||||
profileProto.setProfilePictureURL(profilePicture.url)
|
||||
profileProto.setProfilePicture(profilePicture.url)
|
||||
dataMessageProto.setProfileKey(ByteString.copyFrom(profilePicture.profileKey))
|
||||
}
|
||||
dataMessageProto.setProfile(profileProto.build())
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
|
||||
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/ WhisperTextProtocol.proto LocalStorageProtocol.proto FingerprintProtocol.proto
|
||||
protoc25 --java_out=../src/main/java/ WhisperTextProtocol.proto
|
||||
|
@ -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;
|
||||
}
|
@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Copyright (C) 2014-2016 Open Whisper Systems
|
||||
*
|
||||
* Licensed according to the LICENSE file in this repository.
|
||||
*/
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package signalservice;
|
||||
@ -12,88 +6,40 @@ option java_package = "org.session.libsignal.service.internal.push";
|
||||
option java_outer_classname = "SignalServiceProtos";
|
||||
|
||||
message Envelope {
|
||||
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
CIPHERTEXT = 1;
|
||||
KEY_EXCHANGE = 2;
|
||||
PREKEY_BUNDLE = 3;
|
||||
RECEIPT = 5;
|
||||
UNIDENTIFIED_SENDER = 6;
|
||||
CLOSED_GROUP_CIPHERTEXT = 7; // Loki
|
||||
FALLBACK_MESSAGE = 101; // Loki - Encrypted using the fallback session cipher. Contains a pre key bundle if it's a session request.
|
||||
CLOSED_GROUP_CIPHERTEXT = 7;
|
||||
}
|
||||
|
||||
optional Type type = 1;
|
||||
// @required
|
||||
required Type type = 1;
|
||||
optional string source = 2;
|
||||
optional uint32 sourceDevice = 7;
|
||||
optional string relay = 3;
|
||||
// @required
|
||||
optional uint64 timestamp = 5;
|
||||
optional bytes legacyMessage = 6; // Contains an encrypted DataMessage
|
||||
optional bytes content = 8; // Contains an encrypted Content
|
||||
optional string serverGuid = 9;
|
||||
optional bytes content = 8;
|
||||
optional uint64 serverTimestamp = 10;
|
||||
}
|
||||
|
||||
message TypingMessage {
|
||||
|
||||
enum Action {
|
||||
STARTED = 0;
|
||||
STOPPED = 1;
|
||||
}
|
||||
|
||||
// @required
|
||||
optional uint64 timestamp = 1;
|
||||
// @required
|
||||
optional Action action = 2;
|
||||
}
|
||||
|
||||
message Content {
|
||||
optional DataMessage dataMessage = 1;
|
||||
optional SyncMessage syncMessage = 2 [deprecated=true];
|
||||
optional CallMessage callMessage = 3;
|
||||
optional NullMessage nullMessage = 4;
|
||||
optional ReceiptMessage receiptMessage = 5;
|
||||
optional TypingMessage typingMessage = 6;
|
||||
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 {
|
||||
@ -103,28 +49,43 @@ message ClosedGroupCiphertextMessageWrapper {
|
||||
optional bytes ephemeralPublicKey = 2;
|
||||
}
|
||||
|
||||
message KeyPair {
|
||||
// @required
|
||||
required bytes publicKey = 1;
|
||||
// @required
|
||||
required bytes privateKey = 2;
|
||||
}
|
||||
|
||||
message DataMessage {
|
||||
|
||||
enum Flags {
|
||||
END_SESSION = 1;
|
||||
EXPIRATION_TIMER_UPDATE = 2;
|
||||
PROFILE_KEY_UPDATE = 4;
|
||||
DEVICE_UNLINKING_REQUEST = 128;
|
||||
}
|
||||
|
||||
message Quote {
|
||||
|
||||
message QuotedAttachment {
|
||||
|
||||
enum Flags {
|
||||
VOICE_MESSAGE = 1;
|
||||
}
|
||||
|
||||
optional string contentType = 1;
|
||||
optional string fileName = 2;
|
||||
optional AttachmentPointer thumbnail = 3;
|
||||
optional uint32 flags = 4;
|
||||
}
|
||||
|
||||
// @required
|
||||
optional uint64 id = 1;
|
||||
// @required
|
||||
optional string author = 2;
|
||||
optional string text = 3;
|
||||
repeated QuotedAttachment attachments = 4;
|
||||
}
|
||||
|
||||
message Contact {
|
||||
|
||||
message Name {
|
||||
optional string givenName = 1;
|
||||
optional string familyName = 2;
|
||||
@ -135,6 +96,7 @@ message DataMessage {
|
||||
}
|
||||
|
||||
message Phone {
|
||||
|
||||
enum Type {
|
||||
HOME = 1;
|
||||
MOBILE = 2;
|
||||
@ -148,6 +110,7 @@ message DataMessage {
|
||||
}
|
||||
|
||||
message Email {
|
||||
|
||||
enum Type {
|
||||
HOME = 1;
|
||||
MOBILE = 2;
|
||||
@ -161,6 +124,7 @@ message DataMessage {
|
||||
}
|
||||
|
||||
message PostalAddress {
|
||||
|
||||
enum Type {
|
||||
HOME = 1;
|
||||
WORK = 2;
|
||||
@ -192,50 +156,28 @@ message DataMessage {
|
||||
}
|
||||
|
||||
message Preview {
|
||||
// @required
|
||||
optional string url = 1;
|
||||
optional string title = 2;
|
||||
optional AttachmentPointer image = 3;
|
||||
}
|
||||
|
||||
message Sticker {
|
||||
optional bytes packId = 1;
|
||||
optional bytes packKey = 2;
|
||||
optional uint32 stickerId = 3;
|
||||
optional AttachmentPointer data = 4;
|
||||
}
|
||||
|
||||
optional string body = 1;
|
||||
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 {
|
||||
message LokiProfile {
|
||||
optional string displayName = 1;
|
||||
optional string profilePictureURL = 2;
|
||||
optional string profilePicture = 2;
|
||||
}
|
||||
|
||||
message ClosedGroupUpdateV2 {
|
||||
message ClosedGroupControlMessage {
|
||||
|
||||
enum Type {
|
||||
NEW = 1; // publicKey, name, encryptionKeyPair, members, admins
|
||||
UPDATE = 2; // name, members
|
||||
ENCRYPTION_KEY_PAIR = 3; // wrappers
|
||||
ENCRYPTION_KEY_PAIR = 3; // publicKey, wrappers
|
||||
NAME_CHANGE = 4; // name
|
||||
MEMBERS_ADDED = 5; // members
|
||||
MEMBERS_REMOVED = 6; // members
|
||||
MEMBER_LEFT = 7;
|
||||
ENCRYPTION_KEY_PAIR_REQUEST = 8;
|
||||
}
|
||||
|
||||
message KeyPairWrapper {
|
||||
@ -255,65 +197,20 @@ message ClosedGroupUpdateV2 {
|
||||
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 {
|
||||
// @required
|
||||
optional bytes chainKey = 1;
|
||||
// @required
|
||||
optional uint32 keyIndex = 2;
|
||||
// @required
|
||||
optional bytes publicKey = 3;
|
||||
}
|
||||
|
||||
optional string name = 1;
|
||||
// @required
|
||||
optional bytes groupPublicKey = 2;
|
||||
optional bytes groupPrivateKey = 3;
|
||||
repeated SenderKey senderKeys = 4;
|
||||
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;
|
||||
optional string body = 1;
|
||||
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 LokiProfile profile = 101;
|
||||
optional ClosedGroupControlMessage closedGroupControlMessage = 104;
|
||||
optional string syncTarget = 105;
|
||||
optional PublicChatInfo publicChatInfo = 999;
|
||||
}
|
||||
|
||||
message ConfigurationMessage {
|
||||
@ -330,107 +227,25 @@ message ConfigurationMessage {
|
||||
repeated string openGroups = 2;
|
||||
}
|
||||
|
||||
message Verified {
|
||||
enum State {
|
||||
DEFAULT = 0;
|
||||
VERIFIED = 1;
|
||||
UNVERIFIED = 2;
|
||||
}
|
||||
message ReceiptMessage {
|
||||
|
||||
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 {
|
||||
UNKNOWN = 0;
|
||||
CONTACTS = 1;
|
||||
GROUPS = 2;
|
||||
BLOCKED = 3;
|
||||
CONFIGURATION = 4;
|
||||
DELIVERY = 0;
|
||||
READ = 1;
|
||||
}
|
||||
|
||||
// @required
|
||||
optional Type type = 1;
|
||||
}
|
||||
|
||||
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;
|
||||
repeated uint64 timestamp = 2;
|
||||
}
|
||||
|
||||
message AttachmentPointer {
|
||||
|
||||
enum Flags {
|
||||
VOICE_MESSAGE = 1;
|
||||
}
|
||||
|
||||
// @required
|
||||
optional fixed64 id = 1;
|
||||
optional string contentType = 2;
|
||||
optional bytes key = 3;
|
||||
@ -446,6 +261,7 @@ message AttachmentPointer {
|
||||
}
|
||||
|
||||
message GroupContext {
|
||||
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
UPDATE = 1;
|
||||
@ -453,43 +269,43 @@ message GroupContext {
|
||||
QUIT = 3;
|
||||
REQUEST_INFO = 4;
|
||||
}
|
||||
|
||||
// @required
|
||||
optional bytes id = 1;
|
||||
// @required
|
||||
optional Type type = 2;
|
||||
optional string name = 3;
|
||||
repeated string members = 4;
|
||||
optional AttachmentPointer avatar = 5;
|
||||
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 Avatar {
|
||||
optional string contentType = 1;
|
||||
optional uint32 length = 2;
|
||||
}
|
||||
|
||||
// @required
|
||||
optional string number = 1;
|
||||
optional string name = 2;
|
||||
optional Avatar avatar = 3;
|
||||
optional string color = 4;
|
||||
optional Verified verified = 5;
|
||||
optional bytes profileKey = 6;
|
||||
optional bool blocked = 7;
|
||||
optional uint32 expireTimer = 8;
|
||||
optional string nickname = 101; // Loki
|
||||
optional string nickname = 101;
|
||||
}
|
||||
|
||||
message GroupDetails {
|
||||
|
||||
message Avatar {
|
||||
optional string contentType = 1;
|
||||
optional uint32 length = 2;
|
||||
}
|
||||
|
||||
// @required
|
||||
optional bytes id = 1;
|
||||
optional string name = 2;
|
||||
repeated string members = 3;
|
||||
@ -500,3 +316,7 @@ message GroupDetails {
|
||||
optional bool blocked = 8;
|
||||
repeated string admins = 9;
|
||||
}
|
||||
|
||||
message PublicChatInfo { // Intended for internal use only
|
||||
optional uint64 serverID = 1;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.ProfileCipherOutputStream;
|
||||
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.SignedPreKeyEntity;
|
||||
import org.session.libsignal.service.api.util.CredentialsProvider;
|
||||
@ -376,10 +375,6 @@ public class SignalServiceAccountManager {
|
||||
this.pushServiceSocket.sendProvisioningMessage(deviceIdentifier, ciphertext);
|
||||
}
|
||||
|
||||
public List<DeviceInfo> getDevices() throws IOException {
|
||||
return this.pushServiceSocket.getDevices();
|
||||
}
|
||||
|
||||
public void removeDevice(long deviceId) throws IOException {
|
||||
this.pushServiceSocket.removeDevice(deviceId);
|
||||
}
|
||||
|
@ -225,19 +225,16 @@ public class SignalServiceMessageReceiver {
|
||||
if (entity.getSource() != null && entity.getSourceDevice() > 0) {
|
||||
envelope = new SignalServiceEnvelope(entity.getType(), entity.getSource(),
|
||||
entity.getSourceDevice(), entity.getTimestamp(),
|
||||
entity.getMessage(), entity.getContent(),
|
||||
entity.getServerTimestamp(), entity.getServerUuid());
|
||||
entity.getContent(), entity.getServerTimestamp());
|
||||
} else {
|
||||
envelope = new SignalServiceEnvelope(entity.getType(), entity.getTimestamp(),
|
||||
entity.getMessage(), entity.getContent(),
|
||||
entity.getServerTimestamp(), entity.getServerUuid());
|
||||
entity.getContent(), entity.getServerTimestamp());
|
||||
}
|
||||
|
||||
callback.onMessage(envelope);
|
||||
results.add(envelope);
|
||||
|
||||
if (envelope.hasUuid()) socket.acknowledgeMessage(envelope.getUuid());
|
||||
else socket.acknowledgeMessage(entity.getSource(), entity.getTimestamp());
|
||||
socket.acknowledgeMessage(entity.getSource(), entity.getTimestamp());
|
||||
}
|
||||
|
||||
return results;
|
||||
|
@ -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.SignalServiceReceiptMessage;
|
||||
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.push.SignalServiceAddress;
|
||||
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.DataMessage;
|
||||
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.SyncMessage;
|
||||
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.OutputStreamFactory;
|
||||
@ -238,24 +230,6 @@ public class SignalServiceMessageSender {
|
||||
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());
|
||||
|
||||
// // 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;
|
||||
}
|
||||
|
||||
@ -289,36 +263,6 @@ public class SignalServiceMessageSender {
|
||||
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) {
|
||||
this.pipe.set(Optional.fromNullable(pipe));
|
||||
this.unidentifiedPipe.set(Optional.fromNullable(unidentifiedPipe));
|
||||
@ -373,10 +317,6 @@ public class SignalServiceMessageSender {
|
||||
else if (message.isTypingStopped()) builder.setAction(TypingMessage.Action.STOPPED);
|
||||
else throw new IllegalArgumentException("Unknown typing indicator");
|
||||
|
||||
if (message.getGroupId().isPresent()) {
|
||||
builder.setGroupId(ByteString.copyFrom(message.getGroupId().get()));
|
||||
}
|
||||
|
||||
return container.setTypingMessage(builder).build().toByteArray();
|
||||
}
|
||||
|
||||
@ -414,18 +354,10 @@ public class SignalServiceMessageSender {
|
||||
builder.setGroup(createGroupContent(message.getGroupInfo().get(), recipient));
|
||||
}
|
||||
|
||||
if (message.isEndSession()) {
|
||||
builder.setFlags(DataMessage.Flags.END_SESSION_VALUE);
|
||||
}
|
||||
|
||||
if (message.isExpirationUpdate()) {
|
||||
builder.setFlags(DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE);
|
||||
}
|
||||
|
||||
if (message.isProfileKeyUpdate()) {
|
||||
builder.setFlags(DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE);
|
||||
}
|
||||
|
||||
if (message.getExpiresInSeconds() > 0) {
|
||||
builder.setExpireTimer(message.getExpiresInSeconds());
|
||||
}
|
||||
@ -485,27 +417,11 @@ public class SignalServiceMessageSender {
|
||||
}
|
||||
}
|
||||
|
||||
if (message.getSticker().isPresent()) {
|
||||
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();
|
||||
LokiProfile.Builder lokiUserProfileBuilder = LokiProfile.newBuilder();
|
||||
String displayName = userDatabase.getDisplayName(userPublicKey);
|
||||
if (displayName != null) { lokiUserProfileBuilder.setDisplayName(displayName); }
|
||||
String profilePictureURL = userDatabase.getProfilePictureURL(userPublicKey);
|
||||
if (profilePictureURL != null) { lokiUserProfileBuilder.setProfilePictureURL(profilePictureURL); }
|
||||
if (profilePictureURL != null) { lokiUserProfileBuilder.setProfilePicture(profilePictureURL); }
|
||||
builder.setProfile(lokiUserProfileBuilder.build());
|
||||
|
||||
builder.setTimestamp(message.getTimestamp());
|
||||
@ -515,183 +431,6 @@ public class SignalServiceMessageSender {
|
||||
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)
|
||||
throws IOException
|
||||
{
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
package org.session.libsignal.service.api.crypto;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
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.SealedSessionCipher;
|
||||
import org.session.libsignal.metadata.SelfSendException;
|
||||
import org.session.libsignal.metadata.certificate.CertificateValidator;
|
||||
import org.session.libsignal.libsignal.DuplicateMessageException;
|
||||
import org.session.libsignal.libsignal.IdentityKey;
|
||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
||||
import org.session.libsignal.libsignal.InvalidKeyIdException;
|
||||
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.loki.LokiSessionCipher;
|
||||
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.SignalMessage;
|
||||
import org.session.libsignal.libsignal.state.SignalProtocolStore;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
|
||||
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.SignalServiceDataMessage;
|
||||
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.SignalServiceGroup;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceNullMessage;
|
||||
import org.session.libsignal.service.api.messages.SignalServiceReceiptMessage;
|
||||
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.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.SignalServiceProtos;
|
||||
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.ClosedGroupUpdateV2;
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.ClosedGroupControlMessage;
|
||||
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.Envelope.Type;
|
||||
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.Verified;
|
||||
import org.session.libsignal.utilities.Base64;
|
||||
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.opengroups.PublicChat;
|
||||
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.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -116,67 +81,20 @@ public class SignalServiceCipher {
|
||||
private final SignalServiceAddress localAddress;
|
||||
private final SessionProtocol sessionProtocolImpl;
|
||||
private final LokiAPIDatabaseProtocol apiDB;
|
||||
private final CertificateValidator certificateValidator;
|
||||
|
||||
public SignalServiceCipher(SignalServiceAddress localAddress,
|
||||
SignalProtocolStore signalProtocolStore,
|
||||
SessionResetProtocol sessionResetProtocol,
|
||||
SessionProtocol sessionProtocolImpl,
|
||||
LokiAPIDatabaseProtocol apiDB,
|
||||
CertificateValidator certificateValidator)
|
||||
LokiAPIDatabaseProtocol apiDB)
|
||||
{
|
||||
this.signalProtocolStore = signalProtocolStore;
|
||||
this.sessionResetProtocol = sessionResetProtocol;
|
||||
this.localAddress = localAddress;
|
||||
this.sessionProtocolImpl = sessionProtocolImpl;
|
||||
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}
|
||||
*
|
||||
@ -184,32 +102,12 @@ public class SignalServiceCipher {
|
||||
*
|
||||
* @return a decrypted SignalServiceContent
|
||||
*/
|
||||
public SignalServiceContent decrypt(SignalServiceEnvelope envelope)
|
||||
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
|
||||
ProtocolInvalidKeyIdException, ProtocolLegacyMessageException,
|
||||
ProtocolUntrustedIdentityException, ProtocolNoSessionException,
|
||||
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
|
||||
ProtocolInvalidKeyException, ProtocolDuplicateMessageException,
|
||||
SelfSendException, IOException, SessionProtocol.Exception
|
||||
|
||||
public SignalServiceContent decrypt(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException,ProtocolInvalidMessageException
|
||||
{
|
||||
try {
|
||||
Plaintext plaintext = decrypt(envelope, envelope.getContent());
|
||||
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()) {
|
||||
SignalServiceCipher.Metadata metadata = plaintext.getMetadata();
|
||||
SignalServiceContent content = new SignalServiceContent(message, metadata.getSender(), metadata.getSenderDevice(), metadata.getTimestamp());
|
||||
@ -231,30 +129,9 @@ public class SignalServiceCipher {
|
||||
plaintext.getMetadata().getTimestamp(),
|
||||
plaintext.getMetadata().isNeedsReceipt());
|
||||
|
||||
content.setPreKeyBundleMessage(preKeyBundleMessage);
|
||||
|
||||
setProfile(dataMessage, 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()) {
|
||||
return new SignalServiceContent(createReceiptMessage(plaintext.getMetadata(), message.getReceiptMessage()),
|
||||
plaintext.getMetadata().getSender(),
|
||||
@ -265,15 +142,6 @@ public class SignalServiceCipher {
|
||||
plaintext.getMetadata().getSender(),
|
||||
plaintext.getMetadata().getSenderDevice(),
|
||||
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;
|
||||
@ -284,20 +152,13 @@ public class SignalServiceCipher {
|
||||
|
||||
private void setProfile(DataMessage message, SignalServiceContent content) {
|
||||
if (!message.hasProfile()) { return; }
|
||||
SignalServiceProtos.LokiUserProfile profile = message.getProfile();
|
||||
SignalServiceProtos.DataMessage.LokiProfile profile = message.getProfile();
|
||||
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)
|
||||
throws InvalidMetadataMessageException, InvalidMetadataVersionException,
|
||||
ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException,
|
||||
ProtocolLegacyMessageException, ProtocolInvalidKeyException,
|
||||
ProtocolInvalidVersionException, ProtocolInvalidMessageException,
|
||||
ProtocolInvalidKeyIdException, ProtocolNoSessionException,
|
||||
SelfSendException, IOException, SessionProtocol.Exception
|
||||
protected Plaintext decrypt(SignalServiceEnvelope envelope, byte[] ciphertext) throws InvalidMetadataMessageException
|
||||
{
|
||||
try {
|
||||
SignalProtocolAddress sourceAddress = new SignalProtocolAddress(envelope.getSource(), envelope.getSourceDevice());
|
||||
SessionCipher sessionCipher = new LokiSessionCipher(signalProtocolStore, sessionResetProtocol, sourceAddress);
|
||||
SealedSessionCipher sealedSessionCipher = new SealedSessionCipher(signalProtocolStore, sessionResetProtocol, new SignalProtocolAddress(localAddress.getNumber(), 1));
|
||||
@ -313,14 +174,6 @@ public class SignalServiceCipher {
|
||||
String senderPublicKey = plaintextAndSenderPublicKey.getSecond();
|
||||
metadata = new Metadata(senderPublicKey, 1, envelope.getTimestamp(), false);
|
||||
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()) {
|
||||
ECKeyPair userX25519KeyPair = apiDB.getUserX25519KeyPair();
|
||||
kotlin.Pair<byte[], String> plaintextAndSenderPublicKey = sessionProtocolImpl.decrypt(ciphertext, userX25519KeyPair);
|
||||
@ -336,38 +189,16 @@ public class SignalServiceCipher {
|
||||
byte[] data = transportDetails.getStrippedPaddingMessageBody(paddedMessage);
|
||||
|
||||
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 {
|
||||
SignalServiceGroup groupInfo = createGroupInfo(content);
|
||||
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 profileKeyUpdate = ((content.getFlags() & DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE ) != 0);
|
||||
SignalServiceDataMessage.Quote quote = createQuote(content);
|
||||
List<SharedContact> sharedContacts = createSharedContacts(content);
|
||||
List<Preview> previews = createPreviews(content);
|
||||
Sticker sticker = createSticker(content);
|
||||
ClosedGroupUpdate closedGroupUpdate = content.getClosedGroupUpdate();
|
||||
ClosedGroupUpdateV2 closedGroupUpdateV2 = content.getClosedGroupUpdateV2();
|
||||
boolean isDeviceUnlinkingRequest = ((content.getFlags() & DataMessage.Flags.DEVICE_UNLINKING_REQUEST_VALUE) != 0);
|
||||
ClosedGroupControlMessage closedGroupControlMessage = content.getClosedGroupControlMessage();
|
||||
String syncTarget = content.getSyncTarget();
|
||||
|
||||
for (AttachmentPointer pointer : content.getAttachmentsList()) {
|
||||
@ -384,170 +215,16 @@ public class SignalServiceCipher {
|
||||
groupInfo,
|
||||
attachments,
|
||||
content.getBody(),
|
||||
endSession,
|
||||
content.getExpireTimer(),
|
||||
expirationUpdate,
|
||||
content.hasProfileKey() ? content.getProfileKey().toByteArray() : null,
|
||||
profileKeyUpdate,
|
||||
quote,
|
||||
sharedContacts,
|
||||
previews,
|
||||
sticker,
|
||||
null,
|
||||
closedGroupUpdate,
|
||||
closedGroupUpdateV2,
|
||||
closedGroupControlMessage,
|
||||
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) {
|
||||
SignalServiceReceiptMessage.Type type;
|
||||
|
||||
@ -571,9 +248,7 @@ public class SignalServiceCipher {
|
||||
metadata.getSenderDevice());
|
||||
}
|
||||
|
||||
return new SignalServiceTypingMessage(action, content.getTimestamp(),
|
||||
content.hasGroupId() ? Optional.of(content.getGroupId().toByteArray()) :
|
||||
Optional.<byte[]>absent());
|
||||
return new SignalServiceTypingMessage(action, content.getTimestamp());
|
||||
}
|
||||
|
||||
private SignalServiceDataMessage.Quote createQuote(DataMessage content) {
|
||||
@ -613,24 +288,6 @@ public class SignalServiceCipher {
|
||||
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) {
|
||||
if (content.getContactCount() <= 0) return null;
|
||||
|
||||
|
@ -8,7 +8,6 @@ package org.session.libsignal.service.api.messages;
|
||||
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
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.loki.protocol.sessionmanagement.PreKeyBundleMessage;
|
||||
|
||||
@ -20,9 +19,6 @@ public class SignalServiceContent {
|
||||
|
||||
// Loki
|
||||
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<SignalServiceTypingMessage> typingMessage;
|
||||
|
||||
@ -38,35 +34,6 @@ public class SignalServiceContent {
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = needsReceipt;
|
||||
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.typingMessage = Optional.absent();
|
||||
}
|
||||
@ -77,9 +44,6 @@ public class SignalServiceContent {
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = false;
|
||||
this.message = Optional.absent();
|
||||
this.synchronizeMessage = Optional.absent();
|
||||
this.callMessage = Optional.absent();
|
||||
this.nullMessage = Optional.absent();
|
||||
this.readMessage = Optional.of(receiptMessage);
|
||||
this.typingMessage = Optional.absent();
|
||||
}
|
||||
@ -90,9 +54,6 @@ public class SignalServiceContent {
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = false;
|
||||
this.message = Optional.absent();
|
||||
this.synchronizeMessage = Optional.absent();
|
||||
this.callMessage = Optional.absent();
|
||||
this.nullMessage = Optional.absent();
|
||||
this.readMessage = Optional.absent();
|
||||
this.typingMessage = Optional.of(typingMessage);
|
||||
}
|
||||
@ -103,41 +64,17 @@ public class SignalServiceContent {
|
||||
this.timestamp = timestamp;
|
||||
this.needsReceipt = false;
|
||||
this.message = Optional.absent();
|
||||
this.synchronizeMessage = Optional.absent();
|
||||
this.callMessage = Optional.absent();
|
||||
this.nullMessage = Optional.absent();
|
||||
this.readMessage = Optional.absent();
|
||||
this.typingMessage = Optional.absent();
|
||||
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() {
|
||||
return 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() {
|
||||
return readMessage;
|
||||
}
|
||||
@ -162,12 +99,7 @@ public class SignalServiceContent {
|
||||
return needsReceipt;
|
||||
}
|
||||
|
||||
public Optional<SignalServiceNullMessage> getNullMessage() { return nullMessage; }
|
||||
|
||||
// Loki
|
||||
|
||||
public void setPreKeyBundleMessage(PreKeyBundleMessage preKeyBundleMessage) { this.preKeyBundleMessage = Optional.fromNullable(preKeyBundleMessage); }
|
||||
|
||||
public void setSenderDisplayName(String displayName) { senderDisplayName = Optional.fromNullable(displayName); }
|
||||
|
||||
public void setSenderProfilePictureURL(String url) { senderProfilePictureURL = Optional.fromNullable(url); }
|
||||
|
@ -10,8 +10,7 @@ import org.session.libsignal.libsignal.state.PreKeyBundle;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
import org.session.libsignal.service.api.messages.shared.SharedContact;
|
||||
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.ClosedGroupUpdate;
|
||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.ClosedGroupControlMessage;
|
||||
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
|
||||
|
||||
import java.util.LinkedList;
|
||||
@ -26,18 +25,13 @@ public class SignalServiceDataMessage {
|
||||
private final Optional<String> body;
|
||||
public final Optional<SignalServiceGroup> group;
|
||||
private final Optional<byte[]> profileKey;
|
||||
private final boolean endSession;
|
||||
private final boolean expirationUpdate;
|
||||
private final int expiresInSeconds;
|
||||
private final boolean profileKeyUpdate;
|
||||
private final Optional<Quote> quote;
|
||||
public final Optional<List<SharedContact>> contacts;
|
||||
private final Optional<List<Preview>> previews;
|
||||
private final Optional<Sticker> sticker;
|
||||
// Loki
|
||||
private final Optional<PreKeyBundle> preKeyBundle;
|
||||
private final Optional<ClosedGroupUpdate> closedGroupUpdate;
|
||||
private final Optional<ClosedGroupUpdateV2> closedGroupUpdateV2;
|
||||
private final Optional<ClosedGroupControlMessage> closedGroupControlMessage;
|
||||
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.
|
||||
*/
|
||||
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 attachments The attachments (or null if none).
|
||||
* @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.
|
||||
*/
|
||||
public SignalServiceDataMessage(long timestamp, SignalServiceGroup group,
|
||||
List<SignalServiceAttachment> attachments,
|
||||
String body, boolean endSession, int expiresInSeconds,
|
||||
boolean expirationUpdate, byte[] profileKey, boolean profileKeyUpdate,
|
||||
Quote quote, List<SharedContact> sharedContacts, List<Preview> previews,
|
||||
Sticker sticker)
|
||||
String body, int expiresInSeconds,
|
||||
boolean expirationUpdate, byte[] profileKey,
|
||||
Quote quote, List<SharedContact> sharedContacts, List<Preview> previews)
|
||||
{
|
||||
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 attachments The attachments (or null if none).
|
||||
* @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 preKeyBundle The pre key bundle to attach to this message (or null if none).
|
||||
*/
|
||||
public SignalServiceDataMessage(long timestamp, SignalServiceGroup group,
|
||||
List<SignalServiceAttachment> attachments,
|
||||
String body, boolean endSession, int expiresInSeconds,
|
||||
boolean expirationUpdate, byte[] profileKey, boolean profileKeyUpdate,
|
||||
String body, int expiresInSeconds,
|
||||
boolean expirationUpdate, byte[] profileKey,
|
||||
Quote quote, List<SharedContact> sharedContacts, List<Preview> previews,
|
||||
Sticker sticker, PreKeyBundle preKeyBundle,
|
||||
ClosedGroupUpdate closedGroupUpdate, ClosedGroupUpdateV2 closedGroupUpdateV2,
|
||||
ClosedGroupControlMessage closedGroupControlMessage,
|
||||
String syncTarget)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
this.body = Optional.fromNullable(body);
|
||||
this.group = Optional.fromNullable(group);
|
||||
this.endSession = endSession;
|
||||
this.expiresInSeconds = expiresInSeconds;
|
||||
this.expirationUpdate = expirationUpdate;
|
||||
this.profileKey = Optional.fromNullable(profileKey);
|
||||
this.profileKeyUpdate = profileKeyUpdate;
|
||||
this.quote = Optional.fromNullable(quote);
|
||||
this.sticker = Optional.fromNullable(sticker);
|
||||
this.preKeyBundle = Optional.fromNullable(preKeyBundle);
|
||||
this.closedGroupUpdate = Optional.fromNullable(closedGroupUpdate);
|
||||
this.closedGroupUpdateV2 = Optional.fromNullable(closedGroupUpdateV2);
|
||||
this.closedGroupControlMessage = Optional.fromNullable(closedGroupControlMessage);
|
||||
this.syncTarget = Optional.fromNullable(syncTarget);
|
||||
|
||||
if (attachments != null && !attachments.isEmpty()) {
|
||||
@ -221,18 +205,10 @@ public class SignalServiceDataMessage {
|
||||
return group;
|
||||
}
|
||||
|
||||
public boolean isEndSession() {
|
||||
return endSession;
|
||||
}
|
||||
|
||||
public boolean isExpirationUpdate() {
|
||||
return expirationUpdate;
|
||||
}
|
||||
|
||||
public boolean isProfileKeyUpdate() {
|
||||
return profileKeyUpdate;
|
||||
}
|
||||
|
||||
public boolean isGroupMessage() {
|
||||
return group.isPresent();
|
||||
}
|
||||
@ -263,16 +239,8 @@ public class SignalServiceDataMessage {
|
||||
return previews;
|
||||
}
|
||||
|
||||
public Optional<Sticker> getSticker() {
|
||||
return sticker;
|
||||
}
|
||||
|
||||
// Loki
|
||||
public Optional<ClosedGroupUpdate> getClosedGroupUpdate() { return closedGroupUpdate; }
|
||||
|
||||
public Optional<ClosedGroupUpdateV2> getClosedGroupUpdateV2() { return closedGroupUpdateV2; }
|
||||
|
||||
public Optional<PreKeyBundle> getPreKeyBundle() { return preKeyBundle; }
|
||||
public Optional<ClosedGroupControlMessage> getClosedGroupControlMessage() { return closedGroupControlMessage; }
|
||||
|
||||
public boolean hasVisibleContent() {
|
||||
return (body.isPresent() && !body.get().isEmpty())
|
||||
@ -399,11 +367,8 @@ public class SignalServiceDataMessage {
|
||||
public SignalServiceDataMessage build() {
|
||||
if (timestamp == 0) timestamp = System.currentTimeMillis();
|
||||
// closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob)
|
||||
return new SignalServiceDataMessage(timestamp, group, attachments, body, endSession,
|
||||
expiresInSeconds, expirationUpdate, profileKey,
|
||||
profileKeyUpdate, quote, sharedContacts, previews,
|
||||
sticker, preKeyBundle, null, null,
|
||||
syncTarget);
|
||||
return new SignalServiceDataMessage(timestamp, group, attachments, body, expiresInSeconds, expirationUpdate, profileKey, quote, sharedContacts, previews,
|
||||
null, syncTarget);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,19 +107,13 @@ public class SignalServiceEnvelope {
|
||||
}
|
||||
builder.setTimestamp(proto.getTimestamp());
|
||||
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) {
|
||||
builder.setContent(ByteString.copyFrom(proto.getContent().toByteArray()));
|
||||
}
|
||||
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()
|
||||
.setType(Envelope.Type.valueOf(type))
|
||||
.setSource(sender)
|
||||
@ -127,40 +121,22 @@ public class SignalServiceEnvelope {
|
||||
.setTimestamp(timestamp)
|
||||
.setServerTimestamp(serverTimestamp);
|
||||
|
||||
if (uuid != null) {
|
||||
builder.setServerGuid(uuid);
|
||||
}
|
||||
|
||||
if (legacyMessage != null) builder.setLegacyMessage(ByteString.copyFrom(legacyMessage));
|
||||
if (content != null) builder.setContent(ByteString.copyFrom(content));
|
||||
|
||||
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()
|
||||
.setType(Envelope.Type.valueOf(type))
|
||||
.setTimestamp(timestamp)
|
||||
.setServerTimestamp(serverTimestamp);
|
||||
|
||||
if (uuid != null) {
|
||||
builder.setServerGuid(uuid);
|
||||
}
|
||||
|
||||
if (legacyMessage != null) builder.setLegacyMessage(ByteString.copyFrom(legacyMessage));
|
||||
if (content != null) builder.setContent(ByteString.copyFrom(content));
|
||||
|
||||
this.envelope = builder.build();
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return envelope.getServerGuid();
|
||||
}
|
||||
|
||||
public boolean hasUuid() {
|
||||
return envelope.hasServerGuid();
|
||||
}
|
||||
|
||||
public boolean hasSource() {
|
||||
return envelope.hasSource() && envelope.getSource().length() > 0;
|
||||
}
|
||||
@ -208,20 +184,6 @@ public class SignalServiceEnvelope {
|
||||
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
|
||||
*/
|
||||
@ -236,35 +198,10 @@ public class SignalServiceEnvelope {
|
||||
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() {
|
||||
return envelope.getType().getNumber() == Envelope.Type.UNIDENTIFIED_SENDER_VALUE;
|
||||
}
|
||||
|
||||
public boolean isFallbackMessage() {
|
||||
return envelope.getType().getNumber() == Envelope.Type.FALLBACK_MESSAGE_VALUE;
|
||||
}
|
||||
|
||||
public boolean isClosedGroupCiphertext() {
|
||||
return envelope.getType().getNumber() == Envelope.Type.CLOSED_GROUP_CIPHERTEXT_VALUE;
|
||||
}
|
||||
|
@ -11,12 +11,10 @@ public class SignalServiceTypingMessage {
|
||||
|
||||
private final Action action;
|
||||
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.timestamp = timestamp;
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public Action getAction() {
|
||||
@ -27,10 +25,6 @@ public class SignalServiceTypingMessage {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public boolean isTypingStarted() {
|
||||
return action == Action.STARTED;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 };
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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); }
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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); }
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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.messages.SignalServiceAttachment.ProgressListener;
|
||||
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.push.ContactTokenDetails;
|
||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||
@ -217,11 +216,6 @@ public class PushServiceSocket {
|
||||
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 {
|
||||
makeServiceRequest(String.format(DEVICE_PATH, String.valueOf(deviceId)), "DELETE", null);
|
||||
}
|
||||
|
@ -19,18 +19,12 @@ public class SignalServiceEnvelopeEntity {
|
||||
@JsonProperty
|
||||
private int sourceDevice;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] message;
|
||||
|
||||
@JsonProperty
|
||||
private byte[] content;
|
||||
|
||||
@JsonProperty
|
||||
private long serverTimestamp;
|
||||
|
||||
@JsonProperty
|
||||
private String guid;
|
||||
|
||||
public SignalServiceEnvelopeEntity() {}
|
||||
|
||||
public int getType() {
|
||||
@ -53,10 +47,6 @@ public class SignalServiceEnvelopeEntity {
|
||||
return sourceDevice;
|
||||
}
|
||||
|
||||
public byte[] getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
@ -64,8 +54,4 @@ public class SignalServiceEnvelopeEntity {
|
||||
public long getServerTimestamp() {
|
||||
return serverTimestamp;
|
||||
}
|
||||
|
||||
public String getServerUuid() {
|
||||
return guid;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user