session-android/src/org/thoughtcrime/securesms/loki/MultiDeviceUtilities.kt

161 lines
7.4 KiB
Kotlin
Raw Normal View History

2019-10-29 22:59:11 +00:00
@file:JvmName("MultiDeviceUtilities")
package org.thoughtcrime.securesms.loki
import android.content.Context
import android.os.Handler
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.map
import nl.komponents.kovenant.toFailVoid
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
2019-10-02 23:28:08 +00:00
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.logging.Log
2019-10-02 23:28:08 +00:00
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage
import org.whispersystems.signalservice.api.push.SignalServiceAddress
2019-10-02 23:28:08 +00:00
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
2019-10-07 04:30:20 +00:00
import org.whispersystems.signalservice.loki.api.PairingAuthorisation
2019-10-02 23:28:08 +00:00
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
2019-10-02 23:28:08 +00:00
/*
All functions within this class, excluding the ones which return promises, BLOCK the thread! Don't run them on the main thread!
*/
2019-10-29 22:59:11 +00:00
fun getAllDeviceFriendRequestStatuses(context: Context, hexEncodedPublicKey: String): Map<String, LokiThreadFriendRequestStatus> {
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
2019-10-29 22:59:11 +00:00
val keys = LokiStorageAPI.shared.getAllDevicePublicKeys(hexEncodedPublicKey)
val map = mutableMapOf<String, LokiThreadFriendRequestStatus>()
for (devicePublicKey in keys) {
val device = Recipient.from(context, Address.fromSerialized(devicePublicKey), false)
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(device)
val friendRequestStatus = if (threadID < 0) LokiThreadFriendRequestStatus.NONE else lokiThreadDatabase.getFriendRequestStatus(threadID)
map[devicePublicKey] = friendRequestStatus
}
return map
}
fun getAllDevicePublicKeysWithFriendStatus(context: Context, hexEncodedPublicKey: String): Promise<Map<String, Boolean>, Unit> {
2019-10-07 23:43:09 +00:00
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
return LokiStorageAPI.shared.getAllDevicePublicKeysAsync(hexEncodedPublicKey).map { keys ->
val devices = keys.toMutableSet()
if (hexEncodedPublicKey != userHexEncodedPublicKey) {
devices.remove(userHexEncodedPublicKey)
}
val friends = getFriendPublicKeys(context, devices)
val friendMap = mutableMapOf<String, Boolean>()
for (device in devices) {
friendMap[device] = friends.contains(device)
}
friendMap
}.toFailVoid()
}
2019-10-29 22:59:11 +00:00
fun getFriendCount(context: Context, devices: Set<String>): Int {
return getFriendPublicKeys(context, devices).count()
2019-10-04 06:14:25 +00:00
}
2019-10-24 01:17:58 +00:00
fun shouldAutomaticallyBecomeFriendsWithDevice(publicKey: String, context: Context): Boolean {
2019-10-02 23:28:08 +00:00
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
2019-10-07 23:43:09 +00:00
val storageAPI = LokiStorageAPI.shared
2019-10-24 01:17:58 +00:00
2019-10-29 22:59:11 +00:00
// If the public key doesn't have any other devices then go through regular friend request logic
val primaryDevicePublicKey = storageAPI.getPrimaryDevicePublicKey(publicKey) ?: return false
// If this is one of our devices then we should become friends
if (isOneOfOurDevices(context, publicKey)) {
return true
2019-10-02 23:28:08 +00:00
}
2019-10-29 22:59:11 +00:00
// If we are friends with the primary device then we should become friends
val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false)
val primaryDeviceThreadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice)
return primaryDeviceThreadID >= 0 && lokiThreadDatabase.getFriendRequestStatus(primaryDeviceThreadID) == LokiThreadFriendRequestStatus.FRIENDS
2019-10-02 23:28:08 +00:00
}
2019-10-07 05:15:06 +00:00
fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> {
val messageSender = ApplicationContext.getInstance(context).communicationModule.provideSignalMessageSender()
val address = SignalServiceAddress(contactHexEncodedPublicKey)
val message = SignalServiceDataMessage.newBuilder().withBody("").withPairingAuthorisation(authorisation)
// A REQUEST should always act as a friend request. A GRANT should always be replying back as a normal message.
2019-10-07 04:30:20 +00:00
if (authorisation.type == PairingAuthorisation.Type.REQUEST) {
val preKeyBundle = DatabaseFactory.getLokiPreKeyBundleDatabase(context).generatePreKeyBundle(address.number)
message.asFriendRequest(true).withPreKeyBundle(preKeyBundle)
}
return try {
2019-10-07 23:43:09 +00:00
Log.d("Loki", "Sending authorisation message to: $contactHexEncodedPublicKey.")
val result = messageSender.sendMessage(0, address, Optional.absent<UnidentifiedAccessPair>(), message.build())
if (result.success == null) {
val exception = when {
2019-10-07 23:43:09 +00:00
result.isNetworkFailure -> "Failed to send authorisation message due to a network error."
else -> "Failed to send authorisation message."
}
throw Exception(exception)
}
Promise.ofSuccess(Unit)
} catch (e: Exception) {
Log.d("Loki", "Failed to send authorisation message to: $contactHexEncodedPublicKey.")
Promise.ofFail(e)
}
}
fun signAndSendPairingAuthorisationMessage(context: Context, pairingAuthorisation: PairingAuthorisation) {
val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize()
val signedPairingAuthorisation = pairingAuthorisation.sign(PairingAuthorisation.Type.GRANT, userPrivateKey)
if (signedPairingAuthorisation == null || signedPairingAuthorisation.type != PairingAuthorisation.Type.GRANT) {
Log.d("Loki", "Failed to sign pairing authorization.")
return
}
retryIfNeeded(8) {
sendPairingAuthorisationMessage(context, pairingAuthorisation.secondaryDevicePublicKey, signedPairingAuthorisation).get()
}.fail {
Log.d("Loki", "Failed to send pairing authorization message to ${pairingAuthorisation.secondaryDevicePublicKey}.")
}
DatabaseFactory.getLokiAPIDatabase(context).insertOrUpdatePairingAuthorisation(signedPairingAuthorisation)
// Call function after a short delay
Handler().postDelayed({
LokiStorageAPI.shared.updateUserDeviceMappings().fail {
Log.w("Loki", "Failed to update device mapping")
}
}, 100)
2019-10-24 06:16:53 +00:00
}
fun shouldSendSycMessage(context: Context, address: Address): Boolean {
if (address.isGroup || address.isEmail || address.isMmsGroup) {
return false
}
2019-10-29 22:59:11 +00:00
// Don't send sync messages if it's one of our devices
return !isOneOfOurDevices(context, address)
}
2019-10-24 06:16:53 +00:00
2019-10-29 22:59:11 +00:00
fun isOneOfOurDevices(context: Context, publicKey: String): Boolean {
return isOneOfOurDevices(context, Address.fromSerialized(publicKey))
2019-10-24 06:16:53 +00:00
}
fun isOneOfOurDevices(context: Context, address: Address): Boolean {
if (address.isGroup || address.isEmail || address.isMmsGroup) {
return false
}
val ourPublicKey = TextSecurePreferences.getLocalNumber(context)
2019-10-29 22:59:11 +00:00
val devices = LokiStorageAPI.shared.getAllDevicePublicKeys(ourPublicKey)
return devices.contains(address.serialize())
}
fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Boolean {
if (recipient.isGroupRecipient) return true
2019-10-29 22:59:11 +00:00
val map = getAllDeviceFriendRequestStatuses(context, recipient.address.serialize())
for (status in map.values) {
if (status == LokiThreadFriendRequestStatus.FRIENDS) {
return true
}
}
2019-10-29 22:59:11 +00:00
return false
}