2019-10-01 06:12:37 +00:00
|
|
|
package org.thoughtcrime.securesms.loki
|
|
|
|
|
|
|
|
import android.content.Context
|
2019-10-29 00:23:30 +00:00
|
|
|
import android.os.Handler
|
2019-10-02 02:20:18 +00:00
|
|
|
import nl.komponents.kovenant.Promise
|
|
|
|
import nl.komponents.kovenant.deferred
|
2019-10-23 01:40:25 +00:00
|
|
|
import nl.komponents.kovenant.functional.bind
|
|
|
|
import nl.komponents.kovenant.functional.map
|
2019-10-01 06:12:37 +00:00
|
|
|
import org.thoughtcrime.securesms.ApplicationContext
|
2019-10-23 04:29:56 +00:00
|
|
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
2019-10-02 23:28:08 +00:00
|
|
|
import org.thoughtcrime.securesms.database.Address
|
2019-10-02 04:22:09 +00:00
|
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
2019-10-01 06:12:37 +00:00
|
|
|
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
|
2019-10-24 01:17:58 +00:00
|
|
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture
|
2019-10-01 06:12:37 +00:00
|
|
|
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
|
2019-10-23 04:29:56 +00:00
|
|
|
import org.whispersystems.signalservice.loki.utilities.retryIfNeeded
|
2019-10-02 23:28:08 +00:00
|
|
|
|
2019-10-23 01:40:25 +00:00
|
|
|
fun getAllDeviceFriendRequestStatus(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI): Promise<Map<String, LokiThreadFriendRequestStatus>, Exception> {
|
|
|
|
val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
|
|
|
|
return storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).map { keys ->
|
|
|
|
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.put(devicePublicKey, friendRequestStatus);
|
|
|
|
}
|
|
|
|
|
|
|
|
map
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-07 23:43:09 +00:00
|
|
|
fun getAllDevicePublicKeys(context: Context, hexEncodedPublicKey: String, storageAPI: LokiStorageAPI, block: (devicePublicKey: String, isFriend: Boolean, friendCount: Int) -> Unit) {
|
|
|
|
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
2019-10-29 01:13:22 +00:00
|
|
|
val devices = getAllDevicePublicKeys(hexEncodedPublicKey, storageAPI).toMutableSet()
|
|
|
|
if (hexEncodedPublicKey != userHexEncodedPublicKey) {
|
|
|
|
devices.remove(userHexEncodedPublicKey)
|
|
|
|
}
|
|
|
|
val friends = getFriendPublicKeys(context, devices)
|
|
|
|
for (device in devices) {
|
|
|
|
block(device, friends.contains(device), friends.count())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getAllDevicePublicKeys(hexEncodedPublicKey: String, storageAPI: LokiStorageAPI): Set<String> {
|
|
|
|
val future = SettableFuture<Set<String>>()
|
|
|
|
storageAPI.getAllDevicePublicKeys(hexEncodedPublicKey).success { future.set(it) }.fail { future.setException(it) }
|
|
|
|
return try {
|
|
|
|
future.get()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
setOf()
|
2019-10-06 22:44:43 +00:00
|
|
|
}
|
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
|
|
|
val future = SettableFuture<Boolean>()
|
2019-10-07 23:43:09 +00:00
|
|
|
storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey ->
|
|
|
|
if (primaryDevicePublicKey == null) {
|
2019-10-24 03:26:54 +00:00
|
|
|
// If the public key doesn't have any other devices then go through regular friend request logic
|
2019-10-24 01:17:58 +00:00
|
|
|
future.set(false)
|
2019-10-02 23:28:08 +00:00
|
|
|
return@success
|
|
|
|
}
|
2019-10-24 03:26:54 +00:00
|
|
|
// If we are the primary device and the public key is our secondary device then we should become friends
|
2019-10-07 23:43:09 +00:00
|
|
|
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
|
|
if (primaryDevicePublicKey == userHexEncodedPublicKey) {
|
|
|
|
storageAPI.getSecondaryDevicePublicKeys(userHexEncodedPublicKey).success { secondaryDevices ->
|
2019-10-24 01:17:58 +00:00
|
|
|
future.set(secondaryDevices.contains(publicKey))
|
2019-10-02 23:28:08 +00:00
|
|
|
}.fail {
|
2019-10-24 01:17:58 +00:00
|
|
|
future.set(false)
|
2019-10-02 23:28:08 +00:00
|
|
|
}
|
|
|
|
return@success
|
|
|
|
}
|
2019-10-24 03:26:54 +00:00
|
|
|
// If we share the same primary device then we should become friends
|
|
|
|
val ourPrimaryDevice = TextSecurePreferences.getMasterHexEncodedPublicKey(context)
|
|
|
|
if (ourPrimaryDevice != null && ourPrimaryDevice == primaryDevicePublicKey) {
|
|
|
|
future.set(true)
|
|
|
|
return@success
|
|
|
|
}
|
|
|
|
// If we are friends with the primary device then we should become friends
|
2019-10-07 23:43:09 +00:00
|
|
|
val primaryDevice = Recipient.from(context, Address.fromSerialized(primaryDevicePublicKey), false)
|
2019-10-02 23:28:08 +00:00
|
|
|
val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(primaryDevice)
|
|
|
|
if (threadID < 0) {
|
2019-10-24 01:17:58 +00:00
|
|
|
future.set(false)
|
2019-10-02 23:28:08 +00:00
|
|
|
return@success
|
|
|
|
}
|
2019-10-24 01:17:58 +00:00
|
|
|
future.set(lokiThreadDatabase.getFriendRequestStatus(threadID) == LokiThreadFriendRequestStatus.FRIENDS)
|
|
|
|
}
|
|
|
|
|
|
|
|
return try {
|
|
|
|
future.get()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
false
|
2019-10-02 23:28:08 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-01 06:12:37 +00:00
|
|
|
|
2019-10-07 05:15:06 +00:00
|
|
|
fun sendPairingAuthorisationMessage(context: Context, contactHexEncodedPublicKey: String, authorisation: PairingAuthorisation): Promise<Unit, Exception> {
|
2019-10-02 04:22:09 +00:00
|
|
|
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) {
|
2019-10-02 04:22:09 +00:00
|
|
|
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.")
|
2019-10-02 04:22:09 +00:00
|
|
|
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."
|
2019-10-02 02:20:18 +00:00
|
|
|
}
|
2019-10-02 04:22:09 +00:00
|
|
|
throw Exception(exception)
|
2019-10-01 06:12:37 +00:00
|
|
|
}
|
2019-10-02 04:22:09 +00:00
|
|
|
Promise.ofSuccess(Unit)
|
|
|
|
} catch (e: Exception) {
|
|
|
|
Log.d("Loki", "Failed to send authorisation message to: $contactHexEncodedPublicKey.")
|
|
|
|
Promise.ofFail(e)
|
2019-10-01 06:12:37 +00:00
|
|
|
}
|
2019-10-23 04:29:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2019-10-29 00:23:30 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't send sync messages if it's our address
|
|
|
|
val publicKey = address.serialize()
|
|
|
|
if (publicKey == TextSecurePreferences.getLocalNumber(context)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
val storageAPI = LokiStorageAPI.shared
|
|
|
|
val future = SettableFuture<Boolean>()
|
|
|
|
storageAPI.getPrimaryDevicePublicKey(publicKey).success { primaryDevicePublicKey ->
|
|
|
|
val isOurPrimaryDevice = primaryDevicePublicKey != null && TextSecurePreferences.getMasterHexEncodedPublicKey(context) == publicKey
|
|
|
|
// Don't send sync message if the primary device is the same as ours
|
|
|
|
future.set(!isOurPrimaryDevice)
|
|
|
|
}.fail {
|
|
|
|
future.set(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
return try {
|
|
|
|
future.get()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun isOneOfOurDevices(context: Context, address: Address): Boolean {
|
|
|
|
if (address.isGroup || address.isEmail || address.isMmsGroup) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
val ourPublicKey = TextSecurePreferences.getLocalNumber(context)
|
|
|
|
val storageAPI = LokiStorageAPI.shared
|
|
|
|
val future = SettableFuture<Boolean>()
|
|
|
|
storageAPI.getAllDevicePublicKeys(ourPublicKey).success {
|
|
|
|
future.set(it.contains(address.serialize()))
|
|
|
|
}.fail {
|
|
|
|
future.set(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
return try {
|
|
|
|
future.get()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getPrimaryDevicePublicKey(hexEncodedPublicKey: String): String? {
|
|
|
|
val storageAPI = LokiStorageAPI.shared
|
|
|
|
val future = SettableFuture<String?>()
|
|
|
|
storageAPI.getPrimaryDevicePublicKey(hexEncodedPublicKey).success {
|
|
|
|
future.set(it)
|
|
|
|
}.fail {
|
|
|
|
future.set(null)
|
|
|
|
}
|
|
|
|
|
|
|
|
return try {
|
|
|
|
future.get()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
null
|
|
|
|
}
|
2019-10-25 05:14:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fun isFriendsWithAnyLinkedDevice(context: Context, recipient: Recipient): Boolean {
|
|
|
|
if (recipient.isGroupRecipient) return true
|
|
|
|
val future = SettableFuture<Boolean>()
|
|
|
|
val storageAPI = LokiStorageAPI.shared
|
|
|
|
|
|
|
|
getAllDeviceFriendRequestStatus(context, recipient.address.serialize(), storageAPI).success { map ->
|
|
|
|
for (status in map.values) {
|
|
|
|
if (status == LokiThreadFriendRequestStatus.FRIENDS) {
|
|
|
|
future.set(true)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!future.isDone) {
|
|
|
|
future.set(false)
|
|
|
|
}
|
|
|
|
}.fail { e ->
|
|
|
|
future.set(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
return future.get()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-10-01 06:12:37 +00:00
|
|
|
}
|