mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 13:35:18 +00:00
WIP: clean up V1 multi device
This commit is contained in:
parent
c5cc191ff3
commit
3285975b1a
@ -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) {
|
||||||
|
@ -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() {
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
if (masterHexEncodedPublicKey == null) {
|
|
||||||
val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})"
|
val senderDisplayName = "${message.displayName} (...${message.senderPublicKey.takeLast(8)})"
|
||||||
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName)
|
DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(group.id, message.senderPublicKey, senderDisplayName)
|
||||||
}
|
val senderHexEncodedPublicKey = message.senderPublicKey
|
||||||
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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -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,13 +549,8 @@ public class Recipient implements RecipientModifiedListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean isBlocked() {
|
public synchronized boolean isBlocked() {
|
||||||
String masterPublicKey = MultiDeviceProtocol.shared.getMasterDevice(this.address.serialize());
|
|
||||||
if (masterPublicKey != null) {
|
|
||||||
return Recipient.from(context, Address.Companion.fromSerialized(masterPublicKey), false).blocked;
|
|
||||||
} else {
|
|
||||||
return blocked;
|
return blocked;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void setBlocked(boolean blocked) {
|
public void setBlocked(boolean blocked) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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); }
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.protocol.shelved.multidevice
|
|
||||||
|
|
||||||
interface DeviceLinkingSessionListener {
|
|
||||||
|
|
||||||
fun requestUserAuthorization(deviceLink: DeviceLink) { }
|
|
||||||
fun onDeviceLinkRequestAuthorized(deviceLink: DeviceLink) { }
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user