WIP: clean up V1 multi device

This commit is contained in:
Ryan ZHAO 2021-02-17 10:51:02 +11:00
parent c5cc191ff3
commit 3285975b1a
19 changed files with 19 additions and 791 deletions

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.loki.activities
import android.content.Context import android.content.Context
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.util.AsyncLoader import org.thoughtcrime.securesms.util.AsyncLoader
class EditClosedGroupLoader(context: Context, val groupID: String) : AsyncLoader<List<String>>(context) { class EditClosedGroupLoader(context: Context, val groupID: String) : AsyncLoader<List<String>>(context) {

View File

@ -44,12 +44,9 @@ import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.TextSecurePreferences.getBooleanPreference import org.session.libsession.utilities.TextSecurePreferences.getBooleanPreference
import org.session.libsession.utilities.TextSecurePreferences.setBooleanPreference import org.session.libsession.utilities.TextSecurePreferences.setBooleanPreference
import org.session.libsession.utilities.Util import org.session.libsession.utilities.Util
import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import org.session.libsignal.service.loki.protocol.mentions.MentionsManager import org.session.libsignal.service.loki.protocol.mentions.MentionsManager
import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import org.session.libsignal.service.loki.protocol.shelved.syncmessages.SyncMessagesProtocol
import org.session.libsignal.utilities.ThreadUtils import org.session.libsignal.utilities.ThreadUtils
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.loki.dialogs.* import org.thoughtcrime.securesms.loki.dialogs.*
@ -177,20 +174,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
if (userPublicKey != null) { if (userPublicKey != null) {
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB) MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey) SessionMetaProtocol.configureIfNeeded(apiDB, userPublicKey)
SyncMessagesProtocol.configureIfNeeded(apiDB, userPublicKey)
application.publicChatManager.startPollersIfNeeded() application.publicChatManager.startPollersIfNeeded()
} }
SessionManagementProtocol.configureIfNeeded(sessionResetImpl, sskDatabase, application) SessionManagementProtocol.configureIfNeeded(sessionResetImpl, sskDatabase, application)
MultiDeviceProtocol.configureIfNeeded(apiDB)
IP2Country.configureIfNeeded(this) IP2Country.configureIfNeeded(this)
application.registerForFCMIfNeeded(false) application.registerForFCMIfNeeded(false)
// Preload device links to make message sending quicker
val publicKeys = ContactUtilities.getAllContacts(this).filter { contact ->
!contact.recipient.isGroupRecipient && !contact.isOurDevice && !contact.isSlave
}.map {
it.recipient.address.toString()
}.toSet()
FileServerAPI.shared.getDeviceLinks(publicKeys)
// Observe blocked contacts changed events // Observe blocked contacts changed events
val broadcastReceiver = object : BroadcastReceiver() { val broadcastReceiver = object : BroadcastReceiver() {

View File

@ -27,7 +27,6 @@ import org.session.libsignal.service.loki.api.fileserver.FileServerAPI
import org.session.libsignal.service.loki.api.opengroups.PublicChat import org.session.libsignal.service.loki.api.opengroups.PublicChat
import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI import org.session.libsignal.service.loki.api.opengroups.PublicChatAPI
import org.session.libsignal.service.loki.api.opengroups.PublicChatMessage import org.session.libsignal.service.loki.api.opengroups.PublicChatMessage
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import java.security.MessageDigest import java.security.MessageDigest
import java.util.* import java.util.*
@ -161,14 +160,11 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
fun pollForNewMessages(): Promise<Unit, Exception> { fun pollForNewMessages(): Promise<Unit, Exception> {
fun processIncomingMessage(message: PublicChatMessage) { fun processIncomingMessage(message: PublicChatMessage) {
// If the sender of the current message is not a slave device, set the display name in the database // If the sender of the current message is not a slave device, set the display name in the database
val masterHexEncodedPublicKey = MultiDeviceProtocol.shared.getMasterDevice(message.senderPublicKey) val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})"
if (masterHexEncodedPublicKey == null) { DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName)
val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})" val senderHexEncodedPublicKey = message.senderPublicKey
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName)
}
val senderHexEncodedPublicKey = masterHexEncodedPublicKey ?: message.senderPublicKey
val serviceDataMessage = getDataMessage(message) val serviceDataMessage = getDataMessage(message)
val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.serverTimestamp, false, false) val serviceContent = SignalServiceContent(serviceDataMessage, senderHexEncodedPublicKey, SignalServiceAddress.DEFAULT_DEVICE_ID, message.serverTimestamp, false)
if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) { if (serviceDataMessage.quote.isPresent || (serviceDataMessage.attachments.isPresent && serviceDataMessage.attachments.get().size > 0) || serviceDataMessage.previews.isPresent) {
PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID)) PushDecryptJob(context).handleMediaMessage(serviceContent, serviceDataMessage, Optional.absent(), Optional.of(message.serverID))
} else { } else {
@ -221,8 +217,6 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
} }
if (isPollOngoing) { return Promise.of(Unit) } if (isPollOngoing) { return Promise.of(Unit) }
isPollOngoing = true isPollOngoing = true
val userDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userHexEncodedPublicKey)
var uniqueDevices = setOf<String>()
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
val apiDB = DatabaseFactory.getLokiAPIDatabase(context) val apiDB = DatabaseFactory.getLokiAPIDatabase(context)
FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB) FileServerAPI.configure(userHexEncodedPublicKey, userPrivateKey, apiDB)
@ -240,20 +234,10 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh
*/ */
Promise.of(messages) Promise.of(messages)
} }
promise.successBackground {
/*
val newDisplayNameUpdatees = uniqueDevices.mapNotNull {
// This will return null if the current device is a master device
MultiDeviceProtocol.shared.getMasterDevice(it)
}.toSet()
// Fetch the display names of the master devices
displayNameUpdatees = displayNameUpdatees.union(newDisplayNameUpdatees)
*/
}
promise.successBackground { messages -> promise.successBackground { messages ->
// Process messages in the background // Process messages in the background
messages.forEach { message -> messages.forEach { message ->
if (userDevices.contains(message.senderPublicKey)) { if (message.senderPublicKey == userHexEncodedPublicKey) {
processOutgoingMessage(message) processOutgoingMessage(message)
} else { } else {
processIncomingMessage(message) processIncomingMessage(message)

View File

@ -11,7 +11,6 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
import org.thoughtcrime.securesms.loki.utilities.* import org.thoughtcrime.securesms.loki.utilities.*
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.api.Snode
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString import org.session.libsignal.service.loki.utilities.toHexString
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
@ -459,48 +458,6 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
database.delete(closedGroupPublicKeysTable, "${Companion.groupPublicKey} = ?", wrap(groupPublicKey)) database.delete(closedGroupPublicKeysTable, "${Companion.groupPublicKey} = ?", wrap(groupPublicKey))
} }
// region Deprecated
override fun getDeviceLinks(publicKey: String): Set<DeviceLink> {
return setOf()
/*
val database = databaseHelper.readableDatabase
return database.getAll(deviceLinkCache, "$masterPublicKey = ? OR $slavePublicKey = ?", arrayOf( publicKey, publicKey )) { cursor ->
val masterHexEncodedPublicKey = cursor.getString(masterPublicKey)
val slaveHexEncodedPublicKey = cursor.getString(slavePublicKey)
val requestSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(requestSignature))) null else cursor.getBase64EncodedData(requestSignature)
val authorizationSignature: ByteArray? = if (cursor.isNull(cursor.getColumnIndexOrThrow(authorizationSignature))) null else cursor.getBase64EncodedData(authorizationSignature)
DeviceLink(masterHexEncodedPublicKey, slaveHexEncodedPublicKey, requestSignature, authorizationSignature)
}.toSet()
*/
}
override fun clearDeviceLinks(publicKey: String) {
/*
val database = databaseHelper.writableDatabase
database.delete(deviceLinkCache, "$masterPublicKey = ? OR $slavePublicKey = ?", arrayOf( publicKey, publicKey ))
*/
}
override fun addDeviceLink(deviceLink: DeviceLink) {
/*
val database = databaseHelper.writableDatabase
val values = ContentValues()
values.put(masterPublicKey, deviceLink.masterPublicKey)
values.put(slavePublicKey, deviceLink.slavePublicKey)
if (deviceLink.requestSignature != null) { values.put(requestSignature, Base64.encodeBytes(deviceLink.requestSignature)) }
if (deviceLink.authorizationSignature != null) { values.put(authorizationSignature, Base64.encodeBytes(deviceLink.authorizationSignature)) }
database.insertOrUpdate(deviceLinkCache, values, "$masterPublicKey = ? AND $slavePublicKey = ?", arrayOf( deviceLink.masterPublicKey, deviceLink.slavePublicKey ))
*/
}
override fun removeDeviceLink(deviceLink: DeviceLink) {
/*
val database = databaseHelper.writableDatabase
database.delete(deviceLinkCache, "$masterPublicKey = ? OR $slavePublicKey = ?", arrayOf( deviceLink.masterPublicKey, deviceLink.slavePublicKey ))
*/
}
// endregion
} }
// region Convenience // region Convenience

View File

@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.loki.SessionResetStatus import org.session.libsignal.libsignal.loki.SessionResetStatus
import org.session.libsignal.service.api.messages.SignalServiceContent import org.session.libsignal.service.api.messages.SignalServiceContent
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol
import java.util.* import java.util.*
object SessionManagementProtocol { object SessionManagementProtocol {
@ -95,13 +94,12 @@ object SessionManagementProtocol {
@JvmStatic @JvmStatic
fun triggerSessionRestorationUI(context: Context, publicKey: String, errorTimestamp: Long) { fun triggerSessionRestorationUI(context: Context, publicKey: String, errorTimestamp: Long) {
val masterDevicePublicKey = MultiDeviceProtocol.shared.getMasterDevice(publicKey) ?: publicKey val recipient = recipient(context, publicKey)
val masterDeviceAsRecipient = recipient(context, masterDevicePublicKey) if (recipient.isGroupRecipient) { return }
if (masterDeviceAsRecipient.isGroupRecipient) { return }
if (TextSecurePreferences.getRestorationTime(context) > errorTimestamp) { if (TextSecurePreferences.getRestorationTime(context) > errorTimestamp) {
return ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(publicKey) return ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(publicKey)
} }
val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(masterDeviceAsRecipient) val threadID = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient)
DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, publicKey) DatabaseFactory.getLokiThreadDatabase(context).addSessionRestoreDevice(threadID, publicKey)
} }
} }

View File

@ -9,7 +9,6 @@ import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.JsonUtil import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
import org.session.libsignal.service.loki.utilities.* import org.session.libsignal.service.loki.utilities.*
import java.net.URL import java.net.URL
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -17,21 +16,6 @@ import java.util.concurrent.ConcurrentHashMap
class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : DotNetAPI() { class FileServerAPI(public val server: String, userPublicKey: String, userPrivateKey: ByteArray, private val database: LokiAPIDatabaseProtocol) : DotNetAPI() {
companion object { companion object {
// region Settings
/**
* Deprecated.
*/
private val deviceLinkType = "network.loki.messenger.devicemapping"
/**
* Deprecated.
*/
private val deviceLinkRequestCache = ConcurrentHashMap<String, Promise<Set<DeviceLink>, Exception>>()
/**
* Deprecated.
*/
private val deviceLinkUpdateInterval = 60 * 1000
private val lastDeviceLinkUpdate = ConcurrentHashMap<String, Long>()
internal val fileServerPublicKey = "62509D59BDEEC404DD0D489C1E15BA8F94FD3D619B01C1BF48A9922BFCB7311C" internal val fileServerPublicKey = "62509D59BDEEC404DD0D489C1E15BA8F94FD3D619B01C1BF48A9922BFCB7311C"
internal val maxRetryCount = 4 internal val maxRetryCount = 4
@ -63,172 +47,6 @@ class FileServerAPI(public val server: String, userPublicKey: String, userPrivat
// endregion // endregion
} }
// region Device Link Update Result
sealed class DeviceLinkUpdateResult {
class Success(val publicKey: String, val deviceLinks: Set<DeviceLink>) : DeviceLinkUpdateResult()
class Failure(val publicKey: String, val error: Exception) : DeviceLinkUpdateResult()
}
// endregion
// region API
public fun hasDeviceLinkCacheExpired(referenceTime: Long = System.currentTimeMillis(), publicKey: String): Boolean {
return !lastDeviceLinkUpdate.containsKey(publicKey) || (referenceTime - lastDeviceLinkUpdate[publicKey]!! > deviceLinkUpdateInterval)
}
fun getDeviceLinks(publicKey: String, isForcedUpdate: Boolean = false): Promise<Set<DeviceLink>, Exception> {
return Promise.of(setOf())
/*
if (deviceLinkRequestCache.containsKey(publicKey) && !isForcedUpdate) {
val result = deviceLinkRequestCache[publicKey]
if (result != null) { return result } // A request was already pending
}
val promise = getDeviceLinks(setOf(publicKey), isForcedUpdate)
deviceLinkRequestCache[publicKey] = promise
promise.always {
deviceLinkRequestCache.remove(publicKey)
}
return promise
*/
}
fun getDeviceLinks(publicKeys: Set<String>, isForcedUpdate: Boolean = false): Promise<Set<DeviceLink>, Exception> {
return Promise.of(setOf())
/*
val validPublicKeys = publicKeys.filter { PublicKeyValidation.isValid(it) }
val now = System.currentTimeMillis()
// IMPORTANT: Don't fetch device links for the current user (i.e. don't remove the it != userHexEncodedPublicKey) check below
val updatees = validPublicKeys.filter { it != userPublicKey && (hasDeviceLinkCacheExpired(now, it) || isForcedUpdate) }.toSet()
val cachedDeviceLinks = validPublicKeys.minus(updatees).flatMap { database.getDeviceLinks(it) }.toSet()
if (updatees.isEmpty()) {
return Promise.of(cachedDeviceLinks)
} else {
return getUserProfiles(updatees, server, true).map(SnodeAPI.sharedContext) { data ->
data.map dataMap@ { node ->
val publicKey = node["username"] as String
val annotations = node["annotations"] as List<Map<*, *>>
val deviceLinksAnnotation = annotations.find {
annotation -> (annotation["type"] as String) == deviceLinkType
} ?: return@dataMap DeviceLinkUpdateResult.Success(publicKey, setOf())
val value = deviceLinksAnnotation["value"] as Map<*, *>
val deviceLinksAsJSON = value["authorisations"] as List<Map<*, *>>
val deviceLinks = deviceLinksAsJSON.mapNotNull { deviceLinkAsJSON ->
try {
val masterPublicKey = deviceLinkAsJSON["primaryDevicePubKey"] as String
val slavePublicKey = deviceLinkAsJSON["secondaryDevicePubKey"] as String
var requestSignature: ByteArray? = null
var authorizationSignature: ByteArray? = null
if (deviceLinkAsJSON["requestSignature"] != null) {
val base64EncodedSignature = deviceLinkAsJSON["requestSignature"] as String
requestSignature = Base64.decode(base64EncodedSignature)
}
if (deviceLinkAsJSON["grantSignature"] != null) {
val base64EncodedSignature = deviceLinkAsJSON["grantSignature"] as String
authorizationSignature = Base64.decode(base64EncodedSignature)
}
val deviceLink = DeviceLink(masterPublicKey, slavePublicKey, requestSignature, authorizationSignature)
val isValid = deviceLink.verify()
if (!isValid) {
Log.d("Loki", "Ignoring invalid device link: $deviceLinkAsJSON.")
return@mapNotNull null
}
deviceLink
} catch (e: Exception) {
Log.d("Loki", "Failed to parse device links for $publicKey from $deviceLinkAsJSON due to error: $e.")
null
}
}.toSet()
DeviceLinkUpdateResult.Success(publicKey, deviceLinks)
}
}.recover { e ->
publicKeys.map { DeviceLinkUpdateResult.Failure(it, e) }
}.success { updateResults ->
for (updateResult in updateResults) {
if (updateResult is DeviceLinkUpdateResult.Success) {
database.clearDeviceLinks(updateResult.publicKey)
updateResult.deviceLinks.forEach { database.addDeviceLink(it) }
} else {
// Do nothing
}
}
}.map(SnodeAPI.sharedContext) { updateResults ->
val deviceLinks = mutableListOf<DeviceLink>()
for (updateResult in updateResults) {
when (updateResult) {
is DeviceLinkUpdateResult.Success -> {
lastDeviceLinkUpdate[updateResult.publicKey] = now
deviceLinks.addAll(updateResult.deviceLinks)
}
is DeviceLinkUpdateResult.Failure -> {
if (updateResult.error is SnodeAPI.Error.ParsingFailed) {
lastDeviceLinkUpdate[updateResult.publicKey] = now // Don't infinitely update in case of a parsing failure
}
deviceLinks.addAll(database.getDeviceLinks(updateResult.publicKey)) // Fall back on cached device links in case of a failure
}
}
}
// Updatees that didn't show up in the response provided by the file server are assumed to not have any device links
val excludedUpdatees = updatees.filter { updatee ->
updateResults.find { updateResult ->
when (updateResult) {
is DeviceLinkUpdateResult.Success -> updateResult.publicKey == updatee
is DeviceLinkUpdateResult.Failure -> updateResult.publicKey == updatee
}
} == null
}
excludedUpdatees.forEach {
lastDeviceLinkUpdate[it] = now
}
deviceLinks.union(cachedDeviceLinks)
}.recover {
publicKeys.flatMap { database.getDeviceLinks(it) }.toSet()
}
}
*/
}
fun setDeviceLinks(deviceLinks: Set<DeviceLink>): Promise<Unit, Exception> {
return Promise.of(Unit)
/*
val isMaster = deviceLinks.find { it.masterPublicKey == userPublicKey } != null
val deviceLinksAsJSON = deviceLinks.map { it.toJSON() }
val value = if (deviceLinks.isNotEmpty()) mapOf( "isPrimary" to isMaster, "authorisations" to deviceLinksAsJSON ) else null
val annotation = mapOf( "type" to deviceLinkType, "value" to value )
val parameters = mapOf( "annotations" to listOf( annotation ) )
return retryIfNeeded(maxRetryCount) {
execute(HTTPVerb.PATCH, server, "/users/me", parameters = parameters)
}.map { Unit }
*/
}
fun addDeviceLink(deviceLink: DeviceLink): Promise<Unit, Exception> {
return Promise.of(Unit)
/*
Log.d("Loki", "Updating device links.")
return getDeviceLinks(userPublicKey, true).bind { deviceLinks ->
val mutableDeviceLinks = deviceLinks.toMutableSet()
mutableDeviceLinks.add(deviceLink)
setDeviceLinks(mutableDeviceLinks)
}.success {
database.addDeviceLink(deviceLink)
}.map { Unit }
*/
}
fun removeDeviceLink(deviceLink: DeviceLink): Promise<Unit, Exception> {
return Promise.of(Unit)
/*
Log.d("Loki", "Updating device links.")
return getDeviceLinks(userPublicKey, true).bind { deviceLinks ->
val mutableDeviceLinks = deviceLinks.toMutableSet()
mutableDeviceLinks.remove(deviceLink)
setDeviceLinks(mutableDeviceLinks)
}.success {
database.removeDeviceLink(deviceLink)
}.map { Unit }
*/
}
// endregion
// region Open Group Server Public Key // region Open Group Server Public Key
fun getPublicKeyForOpenGroupServer(openGroupServer: String): Promise<String, Exception> { fun getPublicKeyForOpenGroupServer(openGroupServer: String): Promise<String, Exception> {
val publicKey = database.getOpenGroupPublicKey(openGroupServer) val publicKey = database.getOpenGroupPublicKey(openGroupServer)

View File

@ -38,7 +38,6 @@ import org.session.libsession.utilities.Util;
import org.session.libsession.utilities.color.MaterialColor; import org.session.libsession.utilities.color.MaterialColor;
import org.session.libsignal.utilities.logging.Log; import org.session.libsignal.utilities.logging.Log;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import org.session.libsession.messaging.avatars.ContactColors; import org.session.libsession.messaging.avatars.ContactColors;
import org.session.libsession.messaging.avatars.ContactPhoto; import org.session.libsession.messaging.avatars.ContactPhoto;
import org.session.libsession.messaging.avatars.GroupRecordContactPhoto; import org.session.libsession.messaging.avatars.GroupRecordContactPhoto;
@ -550,12 +549,7 @@ public class Recipient implements RecipientModifiedListener {
} }
public synchronized boolean isBlocked() { public synchronized boolean isBlocked() {
String masterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(this.address.serialize()); return blocked;
if (masterPublicKey != null) {
return Recipient.from(context, Address.Companion.fromSerialized(masterPublicKey), false).blocked;
} else {
return blocked;
}
} }
public void setBlocked(boolean blocked) { public void setBlocked(boolean blocked) {

View File

@ -78,8 +78,6 @@ import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol;
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol; import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol;
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities; import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol; import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol;
import org.session.libsignal.service.loki.protocol.shelved.syncmessages.SyncMessagesProtocol;
import org.session.libsignal.service.loki.utilities.Broadcaster; import org.session.libsignal.service.loki.utilities.Broadcaster;
import org.session.libsignal.service.loki.utilities.HexEncodingKt; import org.session.libsignal.service.loki.utilities.HexEncodingKt;
import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory; import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory;
@ -248,7 +246,7 @@ public class SignalServiceMessageSender {
long timestamp = message.getTimestamp(); long timestamp = message.getTimestamp();
boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(message, recipient.getNumber(), store); boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(message, recipient.getNumber(), store);
boolean isClosedGroup = message.group.isPresent() && message.group.get().getGroupType() == SignalServiceGroup.GroupType.SIGNAL; boolean isClosedGroup = message.group.isPresent() && message.group.get().getGroupType() == SignalServiceGroup.GroupType.SIGNAL;
SendMessageResult result = sendMessage(messageID, recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, content, false, message.getTTL(), message.getDeviceLink().isPresent(), useFallbackEncryption, isClosedGroup, message.hasVisibleContent(), message.getSyncTarget()); SendMessageResult result = sendMessage(messageID, recipient, getTargetUnidentifiedAccess(unidentifiedAccess), timestamp, content, false, message.getTTL(), useFallbackEncryption, isClosedGroup, message.hasVisibleContent(), message.getSyncTarget());
// // Loki - This shouldn't get invoked for note to self // // Loki - This shouldn't get invoked for note to self
// boolean wouldSignalSendSyncMessage = (result.getSuccess() != null && result.getSuccess().isNeedsSync()) || unidentifiedAccess.isPresent(); // boolean wouldSignalSendSyncMessage = (result.getSuccess() != null && result.getSuccess().isNeedsSync()) || unidentifiedAccess.isPresent();
@ -298,18 +296,6 @@ public class SignalServiceMessageSender {
} }
} }
// Loki - This shouldn't get invoked for note to self
if (needsSyncInResults && SyncMessagesProtocol.shared.shouldSyncMessage(message)) {
byte[] syncMessage = createMultiDeviceSentTranscriptContent(content, Optional.<SignalServiceAddress>absent(), timestamp, results);
// Loki - Customize multi device logic
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
for (String device : linkedDevices) {
SignalServiceAddress deviceAsAddress = new SignalServiceAddress(device);
boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(syncMessage, device, store);
sendMessage(deviceAsAddress, Optional.<UnidentifiedAccess>absent(), timestamp, syncMessage, false, message.getTTL(), useFallbackEncryption);
}
}
return results; return results;
} }
@ -341,14 +327,6 @@ public class SignalServiceMessageSender {
} else { } else {
throw new IOException("Unsupported sync message!"); throw new IOException("Unsupported sync message!");
} }
// Loki - Customize multi device logic
Set<String> linkedDevices = MultiDeviceProtocol.shared.getAllLinkedDevices(userPublicKey);
for (String device : linkedDevices) {
SignalServiceAddress deviceAsAddress = new SignalServiceAddress(device);
boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(message, device, store);
sendMessageToPrivateChat(0, deviceAsAddress, Optional.absent(), timestamp, content, false, message.getTTL(), useFallbackEncryption, false, false, Optional.absent());
}
} }
public void setMessagePipe(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) { public void setMessagePipe(SignalServiceMessagePipe pipe, SignalServiceMessagePipe unidentifiedPipe) {
@ -462,10 +440,6 @@ public class SignalServiceMessageSender {
builder.setFlags(DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE); builder.setFlags(DataMessage.Flags.PROFILE_KEY_UPDATE_VALUE);
} }
if (message.isDeviceUnlinkingRequest()) {
builder.setFlags(DataMessage.Flags.DEVICE_UNLINKING_REQUEST_VALUE);
}
if (message.getExpiresInSeconds() > 0) { if (message.getExpiresInSeconds() > 0) {
builder.setExpireTimer(message.getExpiresInSeconds()); builder.setExpireTimer(message.getExpiresInSeconds());
} }
@ -881,7 +855,7 @@ public class SignalServiceMessageSender {
try { try {
boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(content, recipient.getNumber(), store); boolean useFallbackEncryption = SessionManagementProtocol.shared.shouldMessageUseFallbackEncryption(content, recipient.getNumber(), store);
SendMessageResult result = sendMessage(messageID, recipient, unidentifiedAccessIterator.next(), timestamp, content, online, ttl, false, useFallbackEncryption, isClosedGroup, notifyPNServer, Optional.absent()); SendMessageResult result = sendMessage(messageID, recipient, unidentifiedAccessIterator.next(), timestamp, content, online, ttl, useFallbackEncryption, isClosedGroup, notifyPNServer, Optional.absent());
results.add(result); results.add(result);
} catch (UnregisteredUserException e) { } catch (UnregisteredUserException e) {
Log.w(TAG, e); Log.w(TAG, e);
@ -905,7 +879,7 @@ public class SignalServiceMessageSender {
throws IOException throws IOException
{ {
// Loki - This method is only invoked for various types of control messages // Loki - This method is only invoked for various types of control messages
return sendMessage(0, recipient, unidentifiedAccess, timestamp, content, online, ttl, false, false, useFallbackEncryption, false,Optional.absent()); return sendMessage(0, recipient, unidentifiedAccess, timestamp, content, online, ttl, false, useFallbackEncryption, false,Optional.absent());
} }
public SendMessageResult sendMessage(final long messageID, public SendMessageResult sendMessage(final long messageID,
@ -915,7 +889,6 @@ public class SignalServiceMessageSender {
byte[] content, byte[] content,
boolean online, boolean online,
int ttl, int ttl,
boolean isDeviceLinkMessage,
boolean useFallbackEncryption, boolean useFallbackEncryption,
boolean isClosedGroup, boolean isClosedGroup,
boolean notifyPNServer, boolean notifyPNServer,

View File

@ -91,7 +91,6 @@ import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol;
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupUtilities; import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupUtilities;
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol; import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysDatabaseProtocol;
import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage; import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
@ -226,30 +225,6 @@ public class SignalServiceCipher {
content.setDataMessage(signalServiceDataMessage); content.setDataMessage(signalServiceDataMessage);
} }
return content;
} else if (message.hasDeviceLinkMessage()) {
SignalServiceProtos.DeviceLinkMessage protoDeviceLinkMessage = message.getDeviceLinkMessage();
String masterPublicKey = protoDeviceLinkMessage.getPrimaryPublicKey();
String slavePublicKey = protoDeviceLinkMessage.getSecondaryPublicKey();
byte[] requestSignature = protoDeviceLinkMessage.hasRequestSignature() ? protoDeviceLinkMessage.getRequestSignature().toByteArray() : null;
byte[] authorizationSignature = protoDeviceLinkMessage.hasAuthorizationSignature() ? protoDeviceLinkMessage.getAuthorizationSignature().toByteArray() : null;
DeviceLink deviceLink = new DeviceLink(masterPublicKey, slavePublicKey, requestSignature, authorizationSignature);
SignalServiceCipher.Metadata metadata = plaintext.getMetadata();
SignalServiceContent content = new SignalServiceContent(deviceLink, metadata.getSender(), metadata.getSenderDevice(), metadata.getTimestamp());
content.setPreKeyBundleMessage(preKeyBundleMessage);
if (message.hasSyncMessage() && message.getSyncMessage().hasContacts()) {
SignalServiceSyncMessage syncMessage = createSynchronizeMessage(metadata, message.getSyncMessage());
content.setSyncMessage(syncMessage);
}
if (message.hasDataMessage()) {
setProfile(message.getDataMessage(), content);
SignalServiceDataMessage signalServiceDataMessage = createSignalServiceMessage(metadata, message.getDataMessage());
content.setDataMessage(signalServiceDataMessage);
}
return content; return content;
} else if (message.hasDataMessage()) { } else if (message.hasDataMessage()) {
DataMessage dataMessage = message.getDataMessage(); DataMessage dataMessage = message.getDataMessage();
@ -259,8 +234,7 @@ public class SignalServiceCipher {
plaintext.getMetadata().getSender(), plaintext.getMetadata().getSender(),
plaintext.getMetadata().getSenderDevice(), plaintext.getMetadata().getSenderDevice(),
plaintext.getMetadata().getTimestamp(), plaintext.getMetadata().getTimestamp(),
plaintext.getMetadata().isNeedsReceipt(), plaintext.getMetadata().isNeedsReceipt());
signalServiceDataMessage.isDeviceUnlinkingRequest());
content.setPreKeyBundleMessage(preKeyBundleMessage); content.setPreKeyBundleMessage(preKeyBundleMessage);
@ -425,10 +399,8 @@ public class SignalServiceCipher {
previews, previews,
sticker, sticker,
null, null,
null,
closedGroupUpdate, closedGroupUpdate,
closedGroupUpdateV2, closedGroupUpdateV2,
isDeviceUnlinkingRequest,
syncTarget); syncTarget);
} }

View File

@ -7,15 +7,9 @@
package org.session.libsignal.service.api.messages; package org.session.libsignal.service.api.messages;
import org.session.libsignal.libsignal.util.guava.Optional; import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceDataMessage;
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.SignalServiceCallMessage; import org.session.libsignal.service.api.messages.calls.SignalServiceCallMessage;
import org.session.libsignal.service.api.messages.multidevice.ConfigurationMessage;
import org.session.libsignal.service.api.messages.multidevice.SignalServiceSyncMessage; import org.session.libsignal.service.api.messages.multidevice.SignalServiceSyncMessage;
import org.session.libsignal.service.internal.push.SignalServiceProtos; import org.session.libsignal.service.internal.push.SignalServiceProtos;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink;
import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage; import org.session.libsignal.service.loki.protocol.sessionmanagement.PreKeyBundleMessage;
public class SignalServiceContent { public class SignalServiceContent {
@ -25,8 +19,6 @@ public class SignalServiceContent {
private final boolean needsReceipt; private final boolean needsReceipt;
// Loki // Loki
private final boolean isDeviceUnlinkingRequest;
private Optional<SignalServiceDataMessage> message; private Optional<SignalServiceDataMessage> message;
private Optional<SignalServiceSyncMessage> synchronizeMessage; private Optional<SignalServiceSyncMessage> synchronizeMessage;
private final Optional<SignalServiceCallMessage> callMessage; private final Optional<SignalServiceCallMessage> callMessage;
@ -35,13 +27,12 @@ public class SignalServiceContent {
private final Optional<SignalServiceTypingMessage> typingMessage; private final Optional<SignalServiceTypingMessage> typingMessage;
// Loki // Loki
private final Optional<DeviceLink> deviceLink;
public Optional<SignalServiceProtos.Content> configurationMessageProto = Optional.absent(); public Optional<SignalServiceProtos.Content> configurationMessageProto = Optional.absent();
public Optional<PreKeyBundleMessage> preKeyBundleMessage = Optional.absent(); public Optional<PreKeyBundleMessage> preKeyBundleMessage = Optional.absent();
public Optional<String> senderDisplayName = Optional.absent(); public Optional<String> senderDisplayName = Optional.absent();
public Optional<String> senderProfilePictureURL = Optional.absent(); public Optional<String> senderProfilePictureURL = Optional.absent();
public SignalServiceContent(SignalServiceDataMessage message, String sender, int senderDevice, long timestamp, boolean needsReceipt, boolean isDeviceUnlinkingRequest) { public SignalServiceContent(SignalServiceDataMessage message, String sender, int senderDevice, long timestamp, boolean needsReceipt) {
this.sender = sender; this.sender = sender;
this.senderDevice = senderDevice; this.senderDevice = senderDevice;
this.timestamp = timestamp; this.timestamp = timestamp;
@ -52,8 +43,6 @@ public class SignalServiceContent {
this.nullMessage = Optional.absent(); this.nullMessage = Optional.absent();
this.readMessage = Optional.absent(); this.readMessage = Optional.absent();
this.typingMessage = Optional.absent(); this.typingMessage = Optional.absent();
this.deviceLink = Optional.absent();
this.isDeviceUnlinkingRequest = isDeviceUnlinkingRequest;
} }
public SignalServiceContent(SignalServiceSyncMessage synchronizeMessage, String sender, int senderDevice, long timestamp) { public SignalServiceContent(SignalServiceSyncMessage synchronizeMessage, String sender, int senderDevice, long timestamp) {
@ -67,8 +56,6 @@ public class SignalServiceContent {
this.nullMessage = Optional.absent(); this.nullMessage = Optional.absent();
this.readMessage = Optional.absent(); this.readMessage = Optional.absent();
this.typingMessage = Optional.absent(); this.typingMessage = Optional.absent();
this.deviceLink = Optional.absent();
this.isDeviceUnlinkingRequest = false;
} }
public SignalServiceContent(SignalServiceCallMessage callMessage, String sender, int senderDevice, long timestamp) { public SignalServiceContent(SignalServiceCallMessage callMessage, String sender, int senderDevice, long timestamp) {
@ -82,8 +69,6 @@ public class SignalServiceContent {
this.nullMessage = Optional.absent(); this.nullMessage = Optional.absent();
this.readMessage = Optional.absent(); this.readMessage = Optional.absent();
this.typingMessage = Optional.absent(); this.typingMessage = Optional.absent();
this.deviceLink = Optional.absent();
this.isDeviceUnlinkingRequest = false;
} }
public SignalServiceContent(SignalServiceReceiptMessage receiptMessage, String sender, int senderDevice, long timestamp) { public SignalServiceContent(SignalServiceReceiptMessage receiptMessage, String sender, int senderDevice, long timestamp) {
@ -97,8 +82,6 @@ public class SignalServiceContent {
this.nullMessage = Optional.absent(); this.nullMessage = Optional.absent();
this.readMessage = Optional.of(receiptMessage); this.readMessage = Optional.of(receiptMessage);
this.typingMessage = Optional.absent(); this.typingMessage = Optional.absent();
this.deviceLink = Optional.absent();
this.isDeviceUnlinkingRequest = false;
} }
public SignalServiceContent(SignalServiceTypingMessage typingMessage, String sender, int senderDevice, long timestamp) { public SignalServiceContent(SignalServiceTypingMessage typingMessage, String sender, int senderDevice, long timestamp) {
@ -112,23 +95,6 @@ public class SignalServiceContent {
this.nullMessage = Optional.absent(); this.nullMessage = Optional.absent();
this.readMessage = Optional.absent(); this.readMessage = Optional.absent();
this.typingMessage = Optional.of(typingMessage); this.typingMessage = Optional.of(typingMessage);
this.deviceLink = Optional.absent();
this.isDeviceUnlinkingRequest = false;
}
public SignalServiceContent(DeviceLink deviceLink, 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.absent();
this.readMessage = Optional.absent();
this.typingMessage = Optional.absent();
this.deviceLink = Optional.fromNullable(deviceLink);
this.isDeviceUnlinkingRequest = false;
} }
public SignalServiceContent(SignalServiceProtos.Content configurationMessageProto, String sender, int senderDevice, long timestamp) { public SignalServiceContent(SignalServiceProtos.Content configurationMessageProto, String sender, int senderDevice, long timestamp) {
@ -142,9 +108,7 @@ public class SignalServiceContent {
this.nullMessage = Optional.absent(); this.nullMessage = Optional.absent();
this.readMessage = Optional.absent(); this.readMessage = Optional.absent();
this.typingMessage = Optional.absent(); this.typingMessage = Optional.absent();
this.deviceLink = Optional.absent();
this.configurationMessageProto = Optional.fromNullable(configurationMessageProto); this.configurationMessageProto = Optional.fromNullable(configurationMessageProto);
this.isDeviceUnlinkingRequest = false;
} }
public SignalServiceContent(SignalServiceNullMessage nullMessage, String sender, int senderDevice, long timestamp) { public SignalServiceContent(SignalServiceNullMessage nullMessage, String sender, int senderDevice, long timestamp) {
@ -158,8 +122,6 @@ public class SignalServiceContent {
this.nullMessage = Optional.of(nullMessage); this.nullMessage = Optional.of(nullMessage);
this.readMessage = Optional.absent(); this.readMessage = Optional.absent();
this.typingMessage = Optional.absent(); this.typingMessage = Optional.absent();
this.deviceLink = Optional.absent();
this.isDeviceUnlinkingRequest = false;
} }
public Optional<SignalServiceDataMessage> getDataMessage() { public Optional<SignalServiceDataMessage> getDataMessage() {
@ -203,9 +165,6 @@ public class SignalServiceContent {
public Optional<SignalServiceNullMessage> getNullMessage() { return nullMessage; } public Optional<SignalServiceNullMessage> getNullMessage() { return nullMessage; }
// Loki // Loki
public boolean isDeviceUnlinkingRequest() { return isDeviceUnlinkingRequest; }
public Optional<DeviceLink> getDeviceLink() { return deviceLink; }
public void setPreKeyBundleMessage(PreKeyBundleMessage preKeyBundleMessage) { this.preKeyBundleMessage = Optional.fromNullable(preKeyBundleMessage); } public void setPreKeyBundleMessage(PreKeyBundleMessage preKeyBundleMessage) { this.preKeyBundleMessage = Optional.fromNullable(preKeyBundleMessage); }

View File

@ -13,7 +13,6 @@ 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.ClosedGroupUpdateV2;
import org.session.libsignal.service.internal.push.SignalServiceProtos.ClosedGroupUpdate; import org.session.libsignal.service.internal.push.SignalServiceProtos.ClosedGroupUpdate;
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities; import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -37,10 +36,8 @@ public class SignalServiceDataMessage {
private final Optional<Sticker> sticker; private final Optional<Sticker> sticker;
// Loki // Loki
private final Optional<PreKeyBundle> preKeyBundle; private final Optional<PreKeyBundle> preKeyBundle;
private final Optional<DeviceLink> deviceLink;
private final Optional<ClosedGroupUpdate> closedGroupUpdate; private final Optional<ClosedGroupUpdate> closedGroupUpdate;
private final Optional<ClosedGroupUpdateV2> closedGroupUpdateV2; private final Optional<ClosedGroupUpdateV2> closedGroupUpdateV2;
private final boolean isDeviceUnlinkingRequest;
private final Optional<String> syncTarget; private final Optional<String> syncTarget;
/** /**
@ -135,7 +132,7 @@ public class SignalServiceDataMessage {
Quote quote, List<SharedContact> sharedContacts, List<Preview> previews, Quote quote, List<SharedContact> sharedContacts, List<Preview> previews,
Sticker sticker) Sticker sticker)
{ {
this(timestamp, group, attachments, body, endSession, expiresInSeconds, expirationUpdate, profileKey, profileKeyUpdate, quote, sharedContacts, previews, sticker, null, null, null, null, false, null); this(timestamp, group, attachments, body, endSession, expiresInSeconds, expirationUpdate, profileKey, profileKeyUpdate, quote, sharedContacts, previews, sticker, null, null, null, null);
} }
/** /**
@ -154,9 +151,9 @@ public class SignalServiceDataMessage {
String body, boolean endSession, int expiresInSeconds, String body, boolean endSession, int expiresInSeconds,
boolean expirationUpdate, byte[] profileKey, boolean profileKeyUpdate, boolean expirationUpdate, byte[] profileKey, boolean profileKeyUpdate,
Quote quote, List<SharedContact> sharedContacts, List<Preview> previews, Quote quote, List<SharedContact> sharedContacts, List<Preview> previews,
Sticker sticker, PreKeyBundle preKeyBundle, DeviceLink deviceLink, Sticker sticker, PreKeyBundle preKeyBundle,
ClosedGroupUpdate closedGroupUpdate, ClosedGroupUpdateV2 closedGroupUpdateV2, ClosedGroupUpdate closedGroupUpdate, ClosedGroupUpdateV2 closedGroupUpdateV2,
boolean isDeviceUnlinkingRequest, String syncTarget) String syncTarget)
{ {
this.timestamp = timestamp; this.timestamp = timestamp;
this.body = Optional.fromNullable(body); this.body = Optional.fromNullable(body);
@ -169,10 +166,8 @@ public class SignalServiceDataMessage {
this.quote = Optional.fromNullable(quote); this.quote = Optional.fromNullable(quote);
this.sticker = Optional.fromNullable(sticker); this.sticker = Optional.fromNullable(sticker);
this.preKeyBundle = Optional.fromNullable(preKeyBundle); this.preKeyBundle = Optional.fromNullable(preKeyBundle);
this.deviceLink = Optional.fromNullable(deviceLink);
this.closedGroupUpdate = Optional.fromNullable(closedGroupUpdate); this.closedGroupUpdate = Optional.fromNullable(closedGroupUpdate);
this.closedGroupUpdateV2 = Optional.fromNullable(closedGroupUpdateV2); this.closedGroupUpdateV2 = Optional.fromNullable(closedGroupUpdateV2);
this.isDeviceUnlinkingRequest = isDeviceUnlinkingRequest;
this.syncTarget = Optional.fromNullable(syncTarget); this.syncTarget = Optional.fromNullable(syncTarget);
if (attachments != null && !attachments.isEmpty()) { if (attachments != null && !attachments.isEmpty()) {
@ -273,26 +268,18 @@ public class SignalServiceDataMessage {
} }
// Loki // Loki
public boolean isDeviceUnlinkingRequest() {
return isDeviceUnlinkingRequest;
}
public Optional<ClosedGroupUpdate> getClosedGroupUpdate() { return closedGroupUpdate; } public Optional<ClosedGroupUpdate> getClosedGroupUpdate() { return closedGroupUpdate; }
public Optional<ClosedGroupUpdateV2> getClosedGroupUpdateV2() { return closedGroupUpdateV2; } public Optional<ClosedGroupUpdateV2> getClosedGroupUpdateV2() { return closedGroupUpdateV2; }
public Optional<PreKeyBundle> getPreKeyBundle() { return preKeyBundle; } public Optional<PreKeyBundle> getPreKeyBundle() { return preKeyBundle; }
public Optional<DeviceLink> getDeviceLink() { return deviceLink; }
public boolean hasVisibleContent() { public boolean hasVisibleContent() {
return (body.isPresent() && !body.get().isEmpty()) return (body.isPresent() && !body.get().isEmpty())
|| (attachments.isPresent() && !attachments.get().isEmpty()); || (attachments.isPresent() && !attachments.get().isEmpty());
} }
public int getTTL() { public int getTTL() {
if (deviceLink.isPresent()) { return TTLUtilities.getTTL(TTLUtilities.MessageType.DeviceLink); }
else if (isDeviceUnlinkingRequest) { return TTLUtilities.getTTL(TTLUtilities.MessageType.DeviceUnlinkingRequest); }
return TTLUtilities.getTTL(TTLUtilities.MessageType.Regular); return TTLUtilities.getTTL(TTLUtilities.MessageType.Regular);
} }
@ -312,9 +299,7 @@ public class SignalServiceDataMessage {
private Quote quote; private Quote quote;
private Sticker sticker; private Sticker sticker;
private PreKeyBundle preKeyBundle; private PreKeyBundle preKeyBundle;
private DeviceLink deviceLink;
private String syncTarget; private String syncTarget;
private boolean isDeviceUnlinkingRequest;
private Builder() {} private Builder() {}
@ -411,25 +396,14 @@ public class SignalServiceDataMessage {
return this; return this;
} }
public Builder withDeviceLink(DeviceLink deviceLink) {
this.deviceLink = deviceLink;
return this;
}
public Builder asDeviceUnlinkingRequest(boolean isDeviceUnlinkingRequest) {
this.isDeviceUnlinkingRequest = isDeviceUnlinkingRequest;
return this;
}
public SignalServiceDataMessage build() { public SignalServiceDataMessage build() {
if (timestamp == 0) timestamp = System.currentTimeMillis(); if (timestamp == 0) timestamp = System.currentTimeMillis();
// closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob) // closedGroupUpdate is always null because we don't use SignalServiceDataMessage to send them (we use ClosedGroupUpdateMessageSendJob)
return new SignalServiceDataMessage(timestamp, group, attachments, body, endSession, return new SignalServiceDataMessage(timestamp, group, attachments, body, endSession,
expiresInSeconds, expirationUpdate, profileKey, expiresInSeconds, expirationUpdate, profileKey,
profileKeyUpdate, quote, sharedContacts, previews, profileKeyUpdate, quote, sharedContacts, previews,
sticker, preKeyBundle, deviceLink, sticker, preKeyBundle, null, null,
null, null, syncTarget);
isDeviceUnlinkingRequest, syncTarget);
} }
} }

View File

@ -9,7 +9,6 @@ import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.service.loki.api.LokiDotNetAPI import org.session.libsignal.service.loki.api.LokiDotNetAPI
import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI import org.session.libsignal.service.loki.api.onionrequests.OnionRequestAPI
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
import org.session.libsignal.service.loki.utilities.* import org.session.libsignal.service.loki.utilities.*
import java.net.URL import java.net.URL
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@ -18,20 +17,6 @@ class FileServerAPI(public val server: String, userPublicKey: String, userPrivat
companion object { companion object {
// region Settings // region Settings
/**
* Deprecated.
*/
private val deviceLinkType = "network.loki.messenger.devicemapping"
/**
* Deprecated.
*/
private val deviceLinkRequestCache = ConcurrentHashMap<String, Promise<Set<DeviceLink>, Exception>>()
/**
* Deprecated.
*/
private val deviceLinkUpdateInterval = 60 * 1000
private val lastDeviceLinkUpdate = ConcurrentHashMap<String, Long>()
internal val fileServerPublicKey = "62509D59BDEEC404DD0D489C1E15BA8F94FD3D619B01C1BF48A9922BFCB7311C" internal val fileServerPublicKey = "62509D59BDEEC404DD0D489C1E15BA8F94FD3D619B01C1BF48A9922BFCB7311C"
internal val maxRetryCount = 4 internal val maxRetryCount = 4
@ -62,172 +47,6 @@ class FileServerAPI(public val server: String, userPublicKey: String, userPrivat
// endregion // endregion
} }
// region Device Link Update Result
sealed class DeviceLinkUpdateResult {
class Success(val publicKey: String, val deviceLinks: Set<DeviceLink>) : DeviceLinkUpdateResult()
class Failure(val publicKey: String, val error: Exception) : DeviceLinkUpdateResult()
}
// endregion
// region API
public fun hasDeviceLinkCacheExpired(referenceTime: Long = System.currentTimeMillis(), publicKey: String): Boolean {
return !lastDeviceLinkUpdate.containsKey(publicKey) || (referenceTime - lastDeviceLinkUpdate[publicKey]!! > deviceLinkUpdateInterval)
}
fun getDeviceLinks(publicKey: String, isForcedUpdate: Boolean = false): Promise<Set<DeviceLink>, Exception> {
return Promise.of(setOf())
/*
if (deviceLinkRequestCache.containsKey(publicKey) && !isForcedUpdate) {
val result = deviceLinkRequestCache[publicKey]
if (result != null) { return result } // A request was already pending
}
val promise = getDeviceLinks(setOf(publicKey), isForcedUpdate)
deviceLinkRequestCache[publicKey] = promise
promise.always {
deviceLinkRequestCache.remove(publicKey)
}
return promise
*/
}
fun getDeviceLinks(publicKeys: Set<String>, isForcedUpdate: Boolean = false): Promise<Set<DeviceLink>, Exception> {
return Promise.of(setOf())
/*
val validPublicKeys = publicKeys.filter { PublicKeyValidation.isValid(it) }
val now = System.currentTimeMillis()
// IMPORTANT: Don't fetch device links for the current user (i.e. don't remove the it != userHexEncodedPublicKey) check below
val updatees = validPublicKeys.filter { it != userPublicKey && (hasDeviceLinkCacheExpired(now, it) || isForcedUpdate) }.toSet()
val cachedDeviceLinks = validPublicKeys.minus(updatees).flatMap { database.getDeviceLinks(it) }.toSet()
if (updatees.isEmpty()) {
return Promise.of(cachedDeviceLinks)
} else {
return getUserProfiles(updatees, server, true).map(SnodeAPI.sharedContext) { data ->
data.map dataMap@ { node ->
val publicKey = node["username"] as String
val annotations = node["annotations"] as List<Map<*, *>>
val deviceLinksAnnotation = annotations.find {
annotation -> (annotation["type"] as String) == deviceLinkType
} ?: return@dataMap DeviceLinkUpdateResult.Success(publicKey, setOf())
val value = deviceLinksAnnotation["value"] as Map<*, *>
val deviceLinksAsJSON = value["authorisations"] as List<Map<*, *>>
val deviceLinks = deviceLinksAsJSON.mapNotNull { deviceLinkAsJSON ->
try {
val masterPublicKey = deviceLinkAsJSON["primaryDevicePubKey"] as String
val slavePublicKey = deviceLinkAsJSON["secondaryDevicePubKey"] as String
var requestSignature: ByteArray? = null
var authorizationSignature: ByteArray? = null
if (deviceLinkAsJSON["requestSignature"] != null) {
val base64EncodedSignature = deviceLinkAsJSON["requestSignature"] as String
requestSignature = Base64.decode(base64EncodedSignature)
}
if (deviceLinkAsJSON["grantSignature"] != null) {
val base64EncodedSignature = deviceLinkAsJSON["grantSignature"] as String
authorizationSignature = Base64.decode(base64EncodedSignature)
}
val deviceLink = DeviceLink(masterPublicKey, slavePublicKey, requestSignature, authorizationSignature)
val isValid = deviceLink.verify()
if (!isValid) {
Log.d("Loki", "Ignoring invalid device link: $deviceLinkAsJSON.")
return@mapNotNull null
}
deviceLink
} catch (e: Exception) {
Log.d("Loki", "Failed to parse device links for $publicKey from $deviceLinkAsJSON due to error: $e.")
null
}
}.toSet()
DeviceLinkUpdateResult.Success(publicKey, deviceLinks)
}
}.recover { e ->
publicKeys.map { DeviceLinkUpdateResult.Failure(it, e) }
}.success { updateResults ->
for (updateResult in updateResults) {
if (updateResult is DeviceLinkUpdateResult.Success) {
database.clearDeviceLinks(updateResult.publicKey)
updateResult.deviceLinks.forEach { database.addDeviceLink(it) }
} else {
// Do nothing
}
}
}.map(SnodeAPI.sharedContext) { updateResults ->
val deviceLinks = mutableListOf<DeviceLink>()
for (updateResult in updateResults) {
when (updateResult) {
is DeviceLinkUpdateResult.Success -> {
lastDeviceLinkUpdate[updateResult.publicKey] = now
deviceLinks.addAll(updateResult.deviceLinks)
}
is DeviceLinkUpdateResult.Failure -> {
if (updateResult.error is SnodeAPI.Error.ParsingFailed) {
lastDeviceLinkUpdate[updateResult.publicKey] = now // Don't infinitely update in case of a parsing failure
}
deviceLinks.addAll(database.getDeviceLinks(updateResult.publicKey)) // Fall back on cached device links in case of a failure
}
}
}
// Updatees that didn't show up in the response provided by the file server are assumed to not have any device links
val excludedUpdatees = updatees.filter { updatee ->
updateResults.find { updateResult ->
when (updateResult) {
is DeviceLinkUpdateResult.Success -> updateResult.publicKey == updatee
is DeviceLinkUpdateResult.Failure -> updateResult.publicKey == updatee
}
} == null
}
excludedUpdatees.forEach {
lastDeviceLinkUpdate[it] = now
}
deviceLinks.union(cachedDeviceLinks)
}.recover {
publicKeys.flatMap { database.getDeviceLinks(it) }.toSet()
}
}
*/
}
fun setDeviceLinks(deviceLinks: Set<DeviceLink>): Promise<Unit, Exception> {
return Promise.of(Unit)
/*
val isMaster = deviceLinks.find { it.masterPublicKey == userPublicKey } != null
val deviceLinksAsJSON = deviceLinks.map { it.toJSON() }
val value = if (deviceLinks.isNotEmpty()) mapOf( "isPrimary" to isMaster, "authorisations" to deviceLinksAsJSON ) else null
val annotation = mapOf( "type" to deviceLinkType, "value" to value )
val parameters = mapOf( "annotations" to listOf( annotation ) )
return retryIfNeeded(maxRetryCount) {
execute(HTTPVerb.PATCH, server, "/users/me", parameters = parameters)
}.map { Unit }
*/
}
fun addDeviceLink(deviceLink: DeviceLink): Promise<Unit, Exception> {
return Promise.of(Unit)
/*
Log.d("Loki", "Updating device links.")
return getDeviceLinks(userPublicKey, true).bind { deviceLinks ->
val mutableDeviceLinks = deviceLinks.toMutableSet()
mutableDeviceLinks.add(deviceLink)
setDeviceLinks(mutableDeviceLinks)
}.success {
database.addDeviceLink(deviceLink)
}.map { Unit }
*/
}
fun removeDeviceLink(deviceLink: DeviceLink): Promise<Unit, Exception> {
return Promise.of(Unit)
/*
Log.d("Loki", "Updating device links.")
return getDeviceLinks(userPublicKey, true).bind { deviceLinks ->
val mutableDeviceLinks = deviceLinks.toMutableSet()
mutableDeviceLinks.remove(deviceLink)
setDeviceLinks(mutableDeviceLinks)
}.success {
database.removeDeviceLink(deviceLink)
}.map { Unit }
*/
}
// endregion
// region Open Group Server Public Key // region Open Group Server Public Key
fun getPublicKeyForOpenGroupServer(openGroupServer: String): Promise<String, Exception> { fun getPublicKeyForOpenGroupServer(openGroupServer: String): Promise<String, Exception> {
val publicKey = database.getOpenGroupPublicKey(openGroupServer) val publicKey = database.getOpenGroupPublicKey(openGroupServer)

View File

@ -2,7 +2,6 @@ package org.session.libsignal.service.loki.database
import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.service.loki.api.Snode import org.session.libsignal.service.loki.api.Snode
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink
import java.util.* import java.util.*
interface LokiAPIDatabaseProtocol { interface LokiAPIDatabaseProtocol {
@ -38,11 +37,4 @@ interface LokiAPIDatabaseProtocol {
fun getUserX25519KeyPair(): ECKeyPair fun getUserX25519KeyPair(): ECKeyPair
fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): List<ECKeyPair> fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): List<ECKeyPair>
fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair?
// region Deprecated
fun getDeviceLinks(publicKey: String): Set<DeviceLink>
fun clearDeviceLinks(publicKey: String)
fun addDeviceLink(deviceLink: DeviceLink)
fun removeDeviceLink(deviceLink: DeviceLink)
// endregion
} }

View File

@ -1,7 +1,6 @@
package org.session.libsignal.service.loki.protocol.meta package org.session.libsignal.service.loki.protocol.meta
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.protocol.shelved.multidevice.MultiDeviceProtocol
public class SessionMetaProtocol(private val apiDatabase: LokiAPIDatabaseProtocol, private val userPublicKey: String) { public class SessionMetaProtocol(private val apiDatabase: LokiAPIDatabaseProtocol, private val userPublicKey: String) {

View File

@ -1,72 +0,0 @@
package org.session.libsignal.service.loki.protocol.shelved.multidevice
import org.whispersystems.curve25519.Curve25519
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.Hex
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import java.util.*
data class DeviceLink(val masterPublicKey: String, val slavePublicKey: String, val requestSignature: ByteArray?, val authorizationSignature: ByteArray?) {
private val curve = Curve25519.getInstance(Curve25519.BEST)
val type: Type
get() = when (authorizationSignature) {
null -> Type.REQUEST
else -> Type.AUTHORIZATION
}
enum class Type(val rawValue: Int) { REQUEST(1), AUTHORIZATION(2) }
constructor(masterPublicKey: String, slavePublicKey: String) : this(masterPublicKey, slavePublicKey, null, null)
fun sign(type: Type, privateKey: ByteArray): DeviceLink? {
val target = if (type == Type.REQUEST) masterPublicKey else slavePublicKey
val data = Hex.fromStringCondensed(target) + ByteArray(1) { type.rawValue.toByte() }
try {
val signature = curve.calculateSignature(privateKey, data)
return if (type == Type.REQUEST) copy(requestSignature = signature) else copy(authorizationSignature = signature)
} catch (e: Exception) {
return null
}
}
fun verify(): Boolean {
if (requestSignature == null && authorizationSignature == null) { return false }
val signature = if (type == Type.REQUEST) requestSignature else authorizationSignature
val issuer = if (type == Type.REQUEST) slavePublicKey else masterPublicKey
val target = if (type == Type.REQUEST) masterPublicKey else slavePublicKey
return try {
val data = Hex.fromStringCondensed(target) + ByteArray(1) { type.rawValue.toByte() }
val issuerPublicKey = Hex.fromStringCondensed(issuer.removing05PrefixIfNeeded())
curve.verifySignature(issuerPublicKey, data, signature)
} catch (e: Exception) {
Log.w("LOKI", e.message)
false
}
}
fun toJSON(): Map<String, Any> {
val result = mutableMapOf( "primaryDevicePubKey" to masterPublicKey, "secondaryDevicePubKey" to slavePublicKey )
if (requestSignature != null) { result["requestSignature"] = Base64.encodeBytes(requestSignature) }
if (authorizationSignature != null) { result["grantSignature"] = Base64.encodeBytes(authorizationSignature) }
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is DeviceLink) {
return (masterPublicKey == other.masterPublicKey && slavePublicKey == other.slavePublicKey
&& Arrays.equals(requestSignature, other.requestSignature) && Arrays.equals(authorizationSignature, other.authorizationSignature))
} else {
return false
}
}
override fun hashCode(): Int {
var hash = masterPublicKey.hashCode() xor slavePublicKey.hashCode()
if (requestSignature != null) { hash = hash xor Arrays.hashCode(requestSignature) }
if (authorizationSignature != null) { hash = hash xor Arrays.hashCode(authorizationSignature) }
return hash
}
}

View File

@ -1,37 +0,0 @@
package org.session.libsignal.service.loki.protocol.shelved.multidevice
class DeviceLinkingSession {
private val listeners = mutableListOf<DeviceLinkingSessionListener>()
var isListeningForLinkingRequests: Boolean = false
private set
companion object {
val shared = DeviceLinkingSession()
}
fun addListener(listener: DeviceLinkingSessionListener) {
listeners.add(listener)
}
fun removeListener(listener: DeviceLinkingSessionListener) {
listeners.remove(listener)
}
fun startListeningForLinkingRequests() {
isListeningForLinkingRequests = true
}
fun stopListeningForLinkingRequests() {
isListeningForLinkingRequests = false
}
fun processLinkingRequest(deviceLink: DeviceLink) {
if (!isListeningForLinkingRequests || !deviceLink.verify()) { return }
listeners.forEach { it.requestUserAuthorization(deviceLink) }
}
fun processLinkingAuthorization(deviceLink: DeviceLink) {
if (!isListeningForLinkingRequests || !deviceLink.verify()) { return }
listeners.forEach { it.onDeviceLinkRequestAuthorized(deviceLink) }
}
}

View File

@ -1,7 +0,0 @@
package org.session.libsignal.service.loki.protocol.shelved.multidevice
interface DeviceLinkingSessionListener {
fun requestUserAuthorization(deviceLink: DeviceLink) { }
fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) { }
}

View File

@ -1,46 +0,0 @@
package org.session.libsignal.service.loki.protocol.shelved.multidevice
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
public class MultiDeviceProtocol(private val apiDatabase: LokiAPIDatabaseProtocol) {
// region Initialization
companion object {
public lateinit var shared: MultiDeviceProtocol
public fun configureIfNeeded(apiDatabase: LokiAPIDatabaseProtocol) {
if (Companion::shared.isInitialized) { return; }
shared = MultiDeviceProtocol(apiDatabase)
}
}
// endregion
// region Utilities
public fun getMasterDevice(publicKey: String): String? {
return null
/*
val deviceLinks = apiDatabase.getDeviceLinks(publicKey)
return deviceLinks.firstOrNull { it.slavePublicKey == publicKey }?.masterPublicKey
*/
}
public fun getSlaveDevices(publicKey: String): Set<String> {
return setOf()
/*
val deviceLinks = apiDatabase.getDeviceLinks(publicKey)
if (deviceLinks.isEmpty()) { return setOf() }
return deviceLinks.map { it.slavePublicKey }.toSet()
*/
}
public fun getAllLinkedDevices(publicKey: String): Set<String> {
return setOf( publicKey )
/*
val deviceLinks = apiDatabase.getDeviceLinks(publicKey)
if (deviceLinks.isEmpty()) { return setOf( publicKey ) }
return deviceLinks.flatMap { listOf( it.masterPublicKey, it.slavePublicKey ) }.toSet()
*/
}
// endregion
}

View File

@ -1,36 +0,0 @@
package org.session.libsignal.service.loki.protocol.shelved.syncmessages
import org.session.libsignal.service.api.messages.SignalServiceDataMessage
import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
public class SyncMessagesProtocol(private val apiDatabase: LokiAPIDatabaseProtocol, private val userPublicKey: String) {
// region Initialization
companion object {
public lateinit var shared: SyncMessagesProtocol
public fun configureIfNeeded(apiDatabase: LokiAPIDatabaseProtocol, userPublicKey: String) {
if (Companion::shared.isInitialized) { return; }
shared = SyncMessagesProtocol(apiDatabase, userPublicKey)
}
}
// endregion
// region Sending
/**
* Note: This is called only if based on Signal's logic we'd want to send a sync message.
*/
public fun shouldSyncMessage(message: SignalServiceDataMessage): Boolean {
return false
/*
if (message.deviceLink.isPresent) { return false }
val isOpenGroupMessage = message.group.isPresent && message.group.get().groupType == SignalServiceGroup.GroupType.PUBLIC_CHAT
if (isOpenGroupMessage) { return false }
val usesMultiDevice = apiDatabase.getDeviceLinks(userPublicKey).isNotEmpty()
return usesMultiDevice
*/
}
// endregion
}