Separate out Session message sending logic

pull/162/head
nielsandriesse 4 years ago
parent 08ead1bd4e
commit 5b630b3b49

@ -1 +1 @@
Subproject commit 06e0efeb7131a738848fdd435d8793fc2cd1f0f5
Subproject commit 0058e521ff7405f6b24acbde1509ff58d9188802

@ -1237,24 +1237,9 @@ typedef enum : NSUInteger {
}
- (void)restoreSession {
if ([self.thread isKindOfClass:[TSContactThread class]]) {
OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender;
TSContactThread *thread = (TSContactThread *)self.thread;
NSArray *devices = thread.sessionRestoreDevices;
for (NSString *device in devices) {
if (device.length == 0) { continue; }
OWSMessageSend *sessionRestoreMessage = [messageSender getSessionRestoreMessageForHexEncodedPublicKey:device];
if (sessionRestoreMessage) {
dispatch_async(OWSDispatch.sendingQueue, ^{
[messageSender sendMessage:sessionRestoreMessage];
});
}
}
[[[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageType:TSInfoMessageTypeLokiSessionResetInProgress] save];
thread.sessionResetStatus = LKSessionResetStatusRequestReceived;
[thread save];
[thread removeAllSessionRestoreDevicesWithTransaction:nil];
}
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[SessionProtocol startSessionResetInThread:self.thread using:transaction];
}];
}
- (void)noLongerVerifiedBannerViewWasTapped:(UIGestureRecognizer *)sender
@ -4506,37 +4491,15 @@ typedef enum : NSUInteger {
- (void)acceptFriendRequest:(TSIncomingMessage *)friendRequest
{
// Accept all outstanding friend requests associated with this user and try to establish sessions with the
// subset of their devices that haven't sent a friend request.
NSString *senderID = friendRequest.authorId;
__block NSSet<TSContactThread *> *linkedDeviceThreads;
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
linkedDeviceThreads = [LKDatabaseUtilities getLinkedDeviceThreadsFor:senderID in:transaction];
[SessionProtocol acceptFriendRequest:friendRequest in:self.thread using:transaction];
}];
for (TSContactThread *thread in linkedDeviceThreads) {
if (thread.hasPendingFriendRequest) {
[ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:self.thread];
} else {
OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender;
OWSMessageSend *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:thread.contactIdentifier];
dispatch_async(OWSDispatch.sendingQueue, ^{
[messageSender sendMessage:automatedFriendRequestMessage];
});
}
}
// Update the thread's friend request status
[self.thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:nil];
}
- (void)declineFriendRequest:(TSIncomingMessage *)friendRequest
{
// Reset the thread's friend request status
[self.thread saveFriendRequestStatus:LKThreadFriendRequestStatusNone withTransaction:nil];
// Delete prekeys
NSString *contactID = friendRequest.authorId;
OWSPrimaryStorage *primaryStorage = SSKEnvironment.shared.primaryStorage;
[self.editingDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[primaryStorage removePreKeyBundleForContact:contactID transaction:transaction];
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[SessionProtocol declineFriendRequest:friendRequest in:self.thread using:transaction];
}];
}

@ -7,18 +7,10 @@ import PromiseKit
@objc(LKAPI)
public final class LokiAPI : NSObject {
private static let stateQueue = DispatchQueue(label: "LokiAPI.stateQueue")
/// Only ever modified from the message processing queue (`OWSBatchMessageProcessor.processingQueue`).
private static var syncMessageTimestamps: [String:Set<UInt64>] = [:]
private static var _lastDeviceLinkUpdate: [String:Date] = [:]
/// A mapping from hex encoded public key to date updated.
public static var lastDeviceLinkUpdate: [String:Date] {
get { stateQueue.sync { _lastDeviceLinkUpdate } }
set { stateQueue.sync { _lastDeviceLinkUpdate = newValue } }
}
private static var _userHexEncodedPublicKeyCache: [String:Set<String>] = [:]
/// A mapping from thread ID to set of user hex encoded public keys.
@objc public static var userHexEncodedPublicKeyCache: [String:Set<String>] {
@ -26,6 +18,7 @@ public final class LokiAPI : NSObject {
set { stateQueue.sync { _userHexEncodedPublicKeyCache = newValue } }
}
internal static let stateQueue = DispatchQueue(label: "LokiAPI.stateQueue")
internal static let workQueue = DispatchQueue(label: "LokiAPI.workQueue", qos: .userInitiated)
internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
@ -42,8 +35,6 @@ public final class LokiAPI : NSObject {
private static var userIDScanLimit: UInt = 4096
internal static var powDifficulty: UInt = 2
public static let deviceLinkUpdateInterval: TimeInterval = 20
// MARK: Nested Types
public typealias RawResponse = Any
@ -57,40 +48,6 @@ public final class LokiAPI : NSObject {
@objc public static let missingSnodeVersion = LokiAPIError(domain: "LokiAPIErrorDomain", code: 5, userInfo: [ NSLocalizedDescriptionKey : "Missing service node version." ])
}
@objc(LKDestination)
public final class Destination : NSObject {
@objc public let hexEncodedPublicKey: String
@objc(kind)
public let objc_kind: String
public var kind: Kind {
return Kind(rawValue: objc_kind)!
}
public enum Kind : String { case master, slave }
public init(hexEncodedPublicKey: String, kind: Kind) {
self.hexEncodedPublicKey = hexEncodedPublicKey
self.objc_kind = kind.rawValue
}
@objc public init(hexEncodedPublicKey: String, kind: String) {
self.hexEncodedPublicKey = hexEncodedPublicKey
self.objc_kind = kind
}
override public func isEqual(_ other: Any?) -> Bool {
guard let other = other as? Destination else { return false }
return hexEncodedPublicKey == other.hexEncodedPublicKey && kind == other.kind
}
override public var hash: Int { // Override NSObject.hash and not Hashable.hashValue or Hashable.hash(into:)
return hexEncodedPublicKey.hashValue ^ kind.hashValue
}
override public var description: String { return "\(kind.rawValue)(\(hexEncodedPublicKey))" }
}
public typealias MessageListPromise = Promise<[SSKProtoEnvelope]>
public typealias RawResponsePromise = Promise<RawResponse>
@ -132,74 +89,6 @@ public final class LokiAPI : NSObject {
}
}
@objc(getDestinationsFor:)
public static func objc_getDestinations(for hexEncodedPublicKey: String) -> AnyPromise {
let promise = getDestinations(for: hexEncodedPublicKey)
return AnyPromise.from(promise)
}
public static func getDestinations(for hexEncodedPublicKey: String) -> Promise<[Destination]> {
var result: Promise<[Destination]>!
storage.dbReadWriteConnection.readWrite { transaction in
result = getDestinations(for: hexEncodedPublicKey, in: transaction)
}
return result
}
@objc(getDestinationsFor:inTransaction:)
public static func objc_getDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> AnyPromise {
let promise = getDestinations(for: hexEncodedPublicKey, in: transaction)
return AnyPromise.from(promise)
}
public static func getDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> Promise<[Destination]> {
let (promise, seal) = Promise<[Destination]>.pending()
func getDestinations(in transaction: YapDatabaseReadTransaction? = nil) {
func getDestinationsInternal(in transaction: YapDatabaseReadTransaction) {
var destinations: [Destination] = []
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
let masterDestination = Destination(hexEncodedPublicKey: masterHexEncodedPublicKey, kind: .master)
destinations.append(masterDestination)
let deviceLinks = storage.getDeviceLinks(for: masterHexEncodedPublicKey, in: transaction)
let slaveDestinations = deviceLinks.map { Destination(hexEncodedPublicKey: $0.slave.hexEncodedPublicKey, kind: .slave) }
destinations.append(contentsOf: slaveDestinations)
seal.fulfill(destinations)
}
if let transaction = transaction, transaction.connection.pendingTransactionCount != 0 {
getDestinationsInternal(in: transaction)
} else {
storage.dbReadConnection.read { transaction in
getDestinationsInternal(in: transaction)
}
}
}
let timeSinceLastUpdate: TimeInterval
if let lastDeviceLinkUpdate = lastDeviceLinkUpdate[hexEncodedPublicKey] {
timeSinceLastUpdate = Date().timeIntervalSince(lastDeviceLinkUpdate)
} else {
timeSinceLastUpdate = .infinity
}
if timeSinceLastUpdate > deviceLinkUpdateInterval {
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
LokiFileServerAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey, in: transaction).done(on: workQueue) { _ in
getDestinations()
lastDeviceLinkUpdate[hexEncodedPublicKey] = Date()
}.catch(on: workQueue) { error in
if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed {
// Don't immediately re-fetch in case of failure due to a parsing error
lastDeviceLinkUpdate[hexEncodedPublicKey] = Date()
getDestinations()
} else {
print("[Loki] Failed to get device links due to error: \(error).")
seal.reject(error)
}
}
} else {
getDestinations(in: transaction)
}
return promise
}
@objc(sendSignalMessage:onP2PSuccess:)
public static func objc_sendSignalMessage(_ signalMessage: SignalMessage, onP2PSuccess: @escaping () -> Void) -> AnyPromise {
let promise = sendSignalMessage(signalMessage, onP2PSuccess: onP2PSuccess).mapValues { AnyPromise.from($0) }.map { Set($0) }

@ -1,4 +1,5 @@
/// Either a service node or another client if P2P is enabled.
internal final class LokiAPITarget : NSObject, NSCoding {
internal let address: String
internal let port: UInt16

@ -46,7 +46,7 @@ public class LokiDotNetAPI : NSObject {
return Promise.value(token)
} else {
return requestNewAuthToken(for: server).then(on: LokiAPI.workQueue) { submitAuthToken($0, for: server) }.map { token -> String in
setAuthToken(for: server, to: token, in: transaction)
setAuthToken(for: server, to: token, in: transaction) // TODO: Does keeping the transaction this long even make sense?
return token
}
}

@ -83,7 +83,7 @@ public final class LokiFileServerAPI : LokiDotNetAPI {
func setDeviceLinks(in transaction: YapDatabaseReadWriteTransaction) {
storage.setDeviceLinks(deviceLinks, in: transaction)
}
if let transaction = transaction, transaction.connection.pendingTransactionCount != 0 {
if let transaction = transaction, transaction.connection.pendingTransactionCount != 0 { // TODO: Does keeping the transaction this long even make sense?
setDeviceLinks(in: transaction)
} else {
storage.dbReadWriteConnection.readWrite { transaction in

@ -1,4 +0,0 @@
public struct LokiPublicChannel {
public let name: String
}

@ -180,30 +180,27 @@ public final class LokiPublicChatPoller : NSObject {
}
let hexEncodedPublicKeysToUpdate = uniqueHexEncodedPublicKeys.filter { hexEncodedPublicKey in
let timeSinceLastUpdate: TimeInterval
if let lastDeviceLinkUpdate = LokiAPI.lastDeviceLinkUpdate[hexEncodedPublicKey] {
if let lastDeviceLinkUpdate = SessionProtocol.lastDeviceLinkUpdate[hexEncodedPublicKey] {
timeSinceLastUpdate = Date().timeIntervalSince(lastDeviceLinkUpdate)
} else {
timeSinceLastUpdate = .infinity
}
return timeSinceLastUpdate > LokiAPI.deviceLinkUpdateInterval
return timeSinceLastUpdate > SessionProtocol.deviceLinkUpdateInterval
}
if !hexEncodedPublicKeysToUpdate.isEmpty {
let storage = OWSPrimaryStorage.shared()
storage.dbReadConnection.read { transaction in
LokiFileServerAPI.getDeviceLinks(associatedWith: hexEncodedPublicKeysToUpdate).done(on: DispatchQueue.global()) { _ in
proceed()
LokiFileServerAPI.getDeviceLinks(associatedWith: hexEncodedPublicKeysToUpdate).done(on: DispatchQueue.global()) { _ in
proceed()
hexEncodedPublicKeysToUpdate.forEach {
SessionProtocol.lastDeviceLinkUpdate[$0] = Date() // TODO: Doing this from a global queue seems a bit iffy
}
}.catch(on: DispatchQueue.global()) { error in
if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed {
// Don't immediately re-fetch in case of failure due to a parsing error
hexEncodedPublicKeysToUpdate.forEach {
LokiAPI.lastDeviceLinkUpdate[$0] = Date()
SessionProtocol.lastDeviceLinkUpdate[$0] = Date() // TODO: Doing this from a global queue seems a bit iffy
}
}.catch(on: DispatchQueue.global()) { error in
if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed {
// Don't immediately re-fetch in case of failure due to a parsing error
hexEncodedPublicKeysToUpdate.forEach {
LokiAPI.lastDeviceLinkUpdate[$0] = Date()
}
}
proceed()
}
proceed()
}
} else {
DispatchQueue.global().async {

@ -0,0 +1,367 @@
import PromiseKit
// A few notes about making changes in this file:
//
// Don't use a database transaction if you can avoid it.
// If you do need to use a database transaction, use a read transaction if possible.
// Consider making it the caller's responsibility to manage the database transaction (this helps avoid nested or unnecessary transactions).
// Think carefully about adding a function; there might already be one for what you need.
// TODO: Document the expected cases for everything and then express those cases in tests
public final class SessionProtocol : NSObject {
private static var _lastDeviceLinkUpdate: [String:Date] = [:]
/// A mapping from hex encoded public key to date updated.
public static var lastDeviceLinkUpdate: [String:Date] {
get { LokiAPI.stateQueue.sync { _lastDeviceLinkUpdate } }
set { LokiAPI.stateQueue.sync { _lastDeviceLinkUpdate = newValue } }
}
// TODO: I don't think this stateQueue stuff actually helps avoid race conditions
private static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
// MARK: - Initialization
private override init() { }
// MARK: - Settings
public static let deviceLinkUpdateInterval: TimeInterval = 20
// MARK: - Multi Device Destination
public struct MultiDeviceDestination : Hashable {
public let hexEncodedPublicKey: String
public let kind: Kind
public enum Kind : String { case master, slave }
}
// MARK: - Message Destination
@objc(getDestinationsForOutgoingSyncMessage:)
public static func getDestinations(for outgoingSyncMessage: OWSOutgoingSyncMessage) -> Set<String> {
var result: Set<String> = []
storage.dbReadConnection.read { transaction in
// NOTE: Aim the message at all linked devices, including this one
// TODO: Should we exclude the current device?
result = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction)
}
return result
}
@objc(getDestinationsForOutgoingGroupMessage:inThread:)
public static func getDestinations(for outgoingGroupMessage: TSOutgoingMessage, in thread: TSThread) -> Set<String> {
guard let thread = thread as? TSGroupThread else { preconditionFailure("Can't get destinations for group message in non-group thread.") }
var result: Set<String> = []
if thread.isPublicChat {
storage.dbReadConnection.read { transaction in
if let openGroup = LokiDatabaseUtilities.getPublicChat(for: thread.uniqueId!, in: transaction) {
result = [ openGroup.server ] // Aim the message at the open group server
} else {
// TODO: Handle
}
}
} else {
result = Set(outgoingGroupMessage.sendingRecipientIds()).intersection(thread.groupModel.groupMemberIds) // This is what Signal does
}
return result
}
// MARK: - Note to Self
// BEHAVIOR NOTE: OWSMessageSender.sendMessageToService:senderCertificate:success:failure: aborts early and just sends
// a sync message instead if the message it's supposed to send is considered a note to self (INCLUDING linked devices).
// BEHAVIOR NOTE: OWSMessageSender.sendMessage: aborts early and does nothing if the message is target at
// the current user (EXCLUDING linked devices).
// BEHAVIOR NOTE: OWSMessageSender.handleMessageSentLocally:success:failure: doesn't send a sync transcript if the message
// that was sent is considered a note to self (INCLUDING linked devices) but it does then mark the message as read.
// TODO: Check that the behaviors described above make sense
@objc(isMessageNoteToSelf:inThread:)
public static func isMessageNoteToSelf(_ message: TSOutgoingMessage, in thread: TSThread) -> Bool {
guard let thread = thread as? TSContactThread, !(message is OWSOutgoingSyncMessage) && !(message is DeviceLinkMessage) else { return false }
var isNoteToSelf = false
storage.dbReadConnection.read { transaction in
isNoteToSelf = LokiDatabaseUtilities.isUserLinkedDevice(thread.contactIdentifier(), transaction: transaction)
}
return isNoteToSelf
}
// MARK: - Friend Requests
@objc(acceptFriendRequest:in:using:)
public static func acceptFriendRequest(_ friendRequest: TSIncomingMessage, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
// Accept all outstanding friend requests associated with this user and try to establish sessions with the
// subset of their devices that haven't sent a friend request.
let senderID = friendRequest.authorId
let linkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: senderID, in: transaction)
for thread in linkedDeviceThreads {
if thread.hasPendingFriendRequest {
// TODO: The Obj-C implementation was actually sending this to self.thread. I'm assuming that's not what we meant.
sendFriendRequestAcceptanceMessage(to: senderID, in: thread, using: transaction)
} else {
let autoGeneratedFRMessageSend = getAutoGeneratedMultiDeviceFRMessageSend(for: senderID, in: transaction)
OWSDispatch.sendingQueue().async {
let messageSender = SSKEnvironment.shared.messageSender
messageSender.sendMessage(autoGeneratedFRMessageSend)
}
}
}
thread.saveFriendRequestStatus(.friends, with: transaction)
}
@objc(sendFriendRequestAcceptanceMessageToHexEncodedPublicKey:in:using:)
public static func sendFriendRequestAcceptanceMessage(to hexEncodedPublicKey: String, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
let ephemeralMessage = EphemeralMessage(in: thread)
let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue
messageSenderJobQueue.add(message: ephemeralMessage, transaction: transaction)
}
@objc(declineFriendRequest:in:using:)
public static func declineFriendRequest(_ friendRequest: TSIncomingMessage, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
thread.saveFriendRequestStatus(.none, with: transaction)
// Delete pre keys
let senderID = friendRequest.authorId
storage.removePreKeyBundle(forContact: senderID, transaction: transaction)
}
// MARK: - Multi Device
@objc(sendMessageToDestinationAndLinkedDevices:in:)
public static func sendMessageToDestinationAndLinkedDevices(_ messageSend: OWSMessageSend, in transaction: YapDatabaseReadWriteTransaction) {
// TODO: I'm pretty sure there are quite a few holes in this logic
let message = messageSend.message
let recipientID = messageSend.recipient.recipientId()
let thread = messageSend.thread!
let isGroupMessage = thread.isGroupThread()
let isOpenGroupMessage = (thread as? TSGroupThread)?.isPublicChat == true
let isDeviceLinkMessage = message is DeviceLinkMessage
let messageSender = SSKEnvironment.shared.messageSender
guard !isOpenGroupMessage && !isDeviceLinkMessage else {
return messageSender.sendMessage(messageSend)
}
let isSilentMessage = message.isSilent || message is EphemeralMessage || message is OWSOutgoingSyncMessage
let isFriendRequestMessage = message is FriendRequestMessage
let isSessionRequestMessage = message is LKSessionRequestMessage
getMultiDeviceDestinations(for: recipientID, in: transaction).done(on: OWSDispatch.sendingQueue()) { destinations in
// Send to master destination
if let masterDestination = destinations.first(where: { $0.kind == .master }) {
let thread = TSContactThread.getOrCreateThread(contactId: masterDestination.hexEncodedPublicKey) // TODO: I guess it's okay this starts a new transaction?
if thread.isContactFriend || isSilentMessage || isFriendRequestMessage || isSessionRequestMessage || isGroupMessage {
let messageSendCopy = messageSend.copy(with: masterDestination)
messageSender.sendMessage(messageSendCopy)
} else {
var frMessageSend: OWSMessageSend!
storage.dbReadWriteConnection.readWrite { transaction in // TODO: Yet another transaction
frMessageSend = getAutoGeneratedMultiDeviceFRMessageSend(for: masterDestination.hexEncodedPublicKey, in: transaction)
}
messageSender.sendMessage(frMessageSend)
}
}
// Send to slave destinations (using a best attempt approach (i.e. ignoring the message send result) for now)
let slaveDestinations = destinations.filter { $0.kind == .slave }
for slaveDestination in slaveDestinations {
let thread = TSContactThread.getOrCreateThread(contactId: slaveDestination.hexEncodedPublicKey) // TODO: I guess it's okay this starts a new transaction?
if thread.isContactFriend || isSilentMessage || isFriendRequestMessage || isSessionRequestMessage || isGroupMessage {
let messageSendCopy = messageSend.copy(with: slaveDestination)
messageSender.sendMessage(messageSendCopy)
} else {
var frMessageSend: OWSMessageSend!
storage.dbReadWriteConnection.readWrite { transaction in // TODO: Yet another transaction
frMessageSend = getAutoGeneratedMultiDeviceFRMessageSend(for: slaveDestination.hexEncodedPublicKey, in: transaction)
}
messageSender.sendMessage(frMessageSend)
}
}
}.catch(on: OWSDispatch.sendingQueue()) { error in
// Proceed even if updating the linked devices map failed so that message sending
// is independent of whether the file server is up
messageSender.sendMessage(messageSend)
}.retainUntilComplete()
}
@objc(updateDeviceLinksIfNeededForHexEncodedPublicKey:in:)
public static func updateDeviceLinksIfNeeded(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> AnyPromise {
let promise = getMultiDeviceDestinations(for: hexEncodedPublicKey, in: transaction)
return AnyPromise.from(promise)
}
private static func getMultiDeviceDestinations(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> Promise<Set<MultiDeviceDestination>> {
// FIXME: Threading
let (promise, seal) = Promise<Set<MultiDeviceDestination>>.pending()
func getDestinations(in transaction: YapDatabaseReadTransaction? = nil) {
storage.dbReadConnection.read { transaction in
var destinations: Set<MultiDeviceDestination> = []
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
let masterDestination = MultiDeviceDestination(hexEncodedPublicKey: masterHexEncodedPublicKey, kind: .master)
destinations.insert(masterDestination)
let deviceLinks = storage.getDeviceLinks(for: masterHexEncodedPublicKey, in: transaction)
let slaveDestinations = deviceLinks.map { MultiDeviceDestination(hexEncodedPublicKey: $0.slave.hexEncodedPublicKey, kind: .slave) }
destinations.formUnion(slaveDestinations)
seal.fulfill(destinations)
}
}
let timeSinceLastUpdate: TimeInterval
if let lastDeviceLinkUpdate = lastDeviceLinkUpdate[hexEncodedPublicKey] {
timeSinceLastUpdate = Date().timeIntervalSince(lastDeviceLinkUpdate)
} else {
timeSinceLastUpdate = .infinity
}
if timeSinceLastUpdate > deviceLinkUpdateInterval {
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction) ?? hexEncodedPublicKey
LokiFileServerAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey, in: transaction).done(on: LokiAPI.workQueue) { _ in
getDestinations()
lastDeviceLinkUpdate[hexEncodedPublicKey] = Date()
}.catch(on: LokiAPI.workQueue) { error in
if (error as? LokiDotNetAPI.LokiDotNetAPIError) == LokiDotNetAPI.LokiDotNetAPIError.parsingFailed {
// Don't immediately re-fetch in case of failure due to a parsing error
lastDeviceLinkUpdate[hexEncodedPublicKey] = Date()
getDestinations()
} else {
print("[Loki] Failed to get device links due to error: \(error).")
seal.reject(error)
}
}
} else {
getDestinations()
}
return promise
}
@objc(getAutoGeneratedMultiDeviceFRMessageForHexEncodedPublicKey:in:)
public static func getAutoGeneratedMultiDeviceFRMessage(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> FriendRequestMessage {
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction)
let isSlaveDeviceThread = masterHexEncodedPublicKey != hexEncodedPublicKey
thread.isForceHidden = isSlaveDeviceThread
if thread.friendRequestStatus == .none || thread.friendRequestStatus == .requestExpired {
thread.saveFriendRequestStatus(.requestSent, with: transaction) // TODO: Should we always immediately mark the slave device as a friend?
}
thread.save(with: transaction)
let result = FriendRequestMessage(outgoingMessageWithTimestamp: NSDate.ows_millisecondTimeStamp(), in: thread,
messageBody: "Please accept to enable messages to be synced across devices",
attachmentIds: [], expiresInSeconds: 0, expireStartedAt: 0, isVoiceMessage: false,
groupMetaMessage: .unspecified, quotedMessage: nil, contactShare: nil, linkPreview: nil)
result.skipSave = true // TODO: Why is this necessary again?
return result
}
@objc(getAutoGeneratedMultiDeviceFRMessageSendForHexEncodedPublicKey:in:)
public static func getAutoGeneratedMultiDeviceFRMessageSend(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> OWSMessageSend {
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
let message = getAutoGeneratedMultiDeviceFRMessage(for: hexEncodedPublicKey, in: transaction)
let recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: hexEncodedPublicKey, transaction: transaction)
let udManager = SSKEnvironment.shared.udManager
let senderCertificate = udManager.getSenderCertificate()
var recipientUDAccess: OWSUDAccess?
if let senderCertificate = senderCertificate {
recipientUDAccess = udManager.udAccess(forRecipientId: hexEncodedPublicKey, requireSyncAccess: true)
}
return OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate,
udAccess: recipientUDAccess, localNumber: getUserHexEncodedPublicKey(), success: {
}, failure: { error in
})
}
// MARK: - Session Reset
@objc(startSessionResetInThread:using:)
public static func startSessionReset(in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
guard let thread = thread as? TSContactThread else {
print("[Loki] Can't restore session for non contact thread.")
return
}
let messageSender = SSKEnvironment.shared.messageSender
let devices = thread.sessionRestoreDevices // TODO: Rename this
for device in devices {
guard device.count != 0 else { continue }
let sessionResetMessageSend = getSessionResetMessageSend(for: device, in: transaction)
OWSDispatch.sendingQueue().async {
messageSender.sendMessage(sessionResetMessageSend)
}
}
let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeLokiSessionResetInProgress)
infoMessage.save(with: transaction)
thread.sessionResetStatus = .requestReceived
thread.save(with: transaction)
thread.removeAllSessionRestoreDevices(with: transaction)
}
@objc(getSessionResetMessageForHexEncodedPublicKey:in:)
public static func getSessionResetMessage(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> SessionRestoreMessage {
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
let result = SessionRestoreMessage(thread: thread)!
result.skipSave = true // TODO: Why is this necessary again?
return result
}
@objc(getSessionResetMessageSendForHexEncodedPublicKey:in:)
public static func getSessionResetMessageSend(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> OWSMessageSend {
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
let masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: hexEncodedPublicKey, in: transaction)
let isSlaveDeviceThread = masterHexEncodedPublicKey != hexEncodedPublicKey
thread.isForceHidden = isSlaveDeviceThread
thread.save(with: transaction)
let message = getSessionResetMessage(for: hexEncodedPublicKey, in: transaction)
let recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: hexEncodedPublicKey, transaction: transaction)
let udManager = SSKEnvironment.shared.udManager
let senderCertificate = udManager.getSenderCertificate()
var recipientUDAccess: OWSUDAccess?
if let senderCertificate = senderCertificate {
recipientUDAccess = udManager.udAccess(forRecipientId: hexEncodedPublicKey, requireSyncAccess: true)
}
return OWSMessageSend(message: message, thread: thread, recipient: recipient, senderCertificate: senderCertificate,
udAccess: recipientUDAccess, localNumber: getUserHexEncodedPublicKey(), success: {
}, failure: { error in
})
}
// MARK: - Transcripts
@objc(shouldSendTranscriptForMessage:in:)
public static func shouldSendTranscript(for message: TSOutgoingMessage, in thread: TSThread) -> Bool {
let isNoteToSelf = isMessageNoteToSelf(message, in: thread)
let isOpenGroupMessage = (thread as? TSGroupThread)?.isPublicChat == true
let wouldSignalRequireTranscript = (AreRecipientUpdatesEnabled() || !message.hasSyncedTranscript)
return wouldSignalRequireTranscript && !isNoteToSelf && !isOpenGroupMessage && !(message is DeviceLinkMessage)
}
// MARK: - Sessions
// BEHAVIOR NOTE: OWSMessageSender.throws_encryptedMessageForMessageSend:recipientId:plaintext:transaction: sets
// isFriendRequest to true if the message in question is a friend request or a device linking request, but NOT if
// it's a session request.
// TODO: Does the above make sense?
public static func shouldUseFallbackEncryption(_ message: TSOutgoingMessage) -> Bool {
return !isSessionRequired(for: message)
}
@objc(isSessionRequiredForMessage:)
public static func isSessionRequired(for message: TSOutgoingMessage) -> Bool {
if message is FriendRequestMessage { return false }
else if message is LKSessionRequestMessage { return false }
else if let message = message as? DeviceLinkMessage, message.kind == .request { return false }
return true
}
}

@ -1093,17 +1093,17 @@ NS_ASSUME_NONNULL_BEGIN
switch (friendRequestStatus) {
case LKThreadFriendRequestStatusNone: {
OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender;
LKFriendRequestMessage *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey transaction:transaction];
LKFriendRequestMessage *autoGeneratedFRMessage = [SessionProtocol getAutoGeneratedMultiDeviceFRMessageForHexEncodedPublicKey:hexEncodedPublicKey in:transaction];
thread.isForceHidden = true;
[thread saveWithTransaction:transaction];
[messageSender sendMessage:automatedFriendRequestMessage
[messageSender sendMessage:autoGeneratedFRMessage
success:^{
[automatedFriendRequestMessage remove];
[autoGeneratedFRMessage remove];
thread.isForceHidden = false;
[thread save];
}
failure:^(NSError *error) {
[automatedFriendRequestMessage remove];
[autoGeneratedFRMessage remove];
thread.isForceHidden = false;
[thread save];
}];
@ -1440,7 +1440,7 @@ NS_ASSUME_NONNULL_BEGIN
if ([ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source] && dataMessage.publicChatInfo == nil) { // Handled in LokiPublicChatPoller for open group messages
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[LKAPI getDestinationsFor:envelope.source inTransaction:transaction].ensureOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
[[SessionProtocol updateDeviceLinksIfNeededForHexEncodedPublicKey:envelope.source in:transaction].ensureOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
dispatch_semaphore_signal(semaphore);
}).catchOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(NSError *error) {
dispatch_semaphore_signal(semaphore);

@ -94,8 +94,7 @@ public class OWSMessageSend: NSObject {
disableUD()
}
@objc(copyWithDestination:)
public func copy(with destination: LokiAPI.Destination) -> OWSMessageSend {
public func copy(with destination: SessionProtocol.MultiDeviceDestination) -> OWSMessageSend {
var recipient: SignalRecipient!
OWSPrimaryStorage.shared().dbReadConnection.read { transaction in
recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: destination.hexEncodedPublicKey, transaction: transaction)

@ -99,9 +99,6 @@ NS_SWIFT_NAME(MessageSender)
success:(void (^)(void))successHandler
failure:(void (^)(NSError *error))failureHandler;
- (OWSMessageSend *)getSessionRestoreMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey;
- (OWSMessageSend *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey;
- (LKFriendRequestMessage *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction;
- (void)sendMessage:(OWSMessageSend *)messageSend;
@end

@ -445,9 +445,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
sourceFilename:sourceFilename
caption:nil
albumMessageId:albumMessageId];
[self sendAttachments:@[
attachmentInfo,
]
[self sendAttachments:@[ attachmentInfo, ]
inMessage:message
success:success
failure:failure];
@ -497,27 +495,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
__block NSMutableSet<NSString *> *recipientIds = [NSMutableSet new];
if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userHexEncodedPublicKey in:transaction] ?: userHexEncodedPublicKey;
recipientIds = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userHexEncodedPublicKey in:transaction].mutableCopy;
}];
recipientIds = [SessionProtocol getDestinationsForOutgoingSyncMessage:message];
} else if (thread.isGroupThread) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
if (groupThread.isPublicChat) {
[self.primaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
LKPublicChat *publicChat = [LKDatabaseUtilities getPublicChatForThreadID:thread.uniqueId transaction:transaction];
if (publicChat != nil) {
[recipientIds addObject:publicChat.server];
} else {
// TODO: Handle
}
}];
} else {
[recipientIds addObjectsFromArray:message.sendingRecipientIds];
// Only send to members in the latest known group member list
[recipientIds intersectSet:[NSSet setWithArray:groupThread.groupModel.groupMemberIds]];
}
recipientIds = [SessionProtocol getDestinationsForOutgoingGroupMessage:message inThread:thread];
} else if ([thread isKindOfClass:[TSContactThread class]]) {
NSString *recipientContactId = ((TSContactThread *)thread).contactIdentifier;
@ -528,7 +509,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// you block them.
OWSAssertDebug(recipientContactId.length > 0);
if ([self.blockingManager isRecipientIdBlocked:recipientContactId]) {
OWSLogInfo(@"skipping 1:1 send to blocked contact: %@", recipientContactId);
OWSLogInfo(@"Skipping 1:1 send to blocked contact: %@", recipientContactId);
NSError *error = OWSErrorMakeMessageSendFailedDueToBlockListError();
[error setIsRetryable:NO];
*errorHandle = error;
@ -541,7 +522,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
OWSFailDebug(@"Message send recipients should not include self.");
}
} else {
// Neither a group nor contact thread? This should never happen.
// Neither a group nor a contact thread? This should never happen.
OWSFailDebug(@"Unknown message type: %@", [message class]);
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
[error setIsRetryable:NO];
@ -605,7 +586,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
resolve(error);
}];
[self sendMessageToDestinationAndLinkedDevices:messageSend];
[self.primaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[SessionProtocol sendMessageToDestinationAndLinkedDevices:messageSend in:transaction];
}];
}];
[sendPromises addObject:sendPromise];
}
@ -670,23 +653,16 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// This thread has been deleted since the message was enqueued.
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients,
NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
@"Error indicating that an outgoing message had no valid recipients."));
NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS", @"Error indicating that an outgoing message had no valid recipients."));
[error setIsRetryable:NO];
return failureHandler(error);
}
// Loki: Handle note to self case
if ([thread isKindOfClass:[TSContactThread class]] && ![message isKindOfClass:OWSOutgoingSyncMessage.class] && ![message isKindOfClass:LKDeviceLinkMessage.class]) {
__block BOOL isNoteToSelf;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
isNoteToSelf = [LKDatabaseUtilities isUserLinkedDevice:((TSContactThread *)thread).contactIdentifier in:transaction];
}];
if (isNoteToSelf) {
[self sendSyncTranscriptForMessage:message isRecipientUpdate:NO success:^{ } failure:^(NSError *error) { }];
successHandler();
return;
}
// Loki: Abort early and send a sync transcript if this is a note to self
if ([SessionProtocol isMessageNoteToSelf:message inThread:thread]) {
[self sendSyncTranscriptForMessage:message isRecipientUpdate:NO success:^{ } failure:^(NSError *error) { }];
successHandler();
return;
}
if (thread.isGroupThread) {
@ -737,6 +713,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
.then(^(id value) {
successHandler();
});
sendPromise.catch(^(id failure) {
NSError *firstRetryableError = nil;
NSError *firstNonRetryableError = nil;
@ -783,8 +760,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// not be sent to any recipient.
if (message.sentRecipientsCount == 0) {
NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageSendNoValidRecipients,
NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
@"Error indicating that an outgoing message had no valid recipients."));
NSLocalizedString(@"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS", @"Error indicating that an outgoing message had no valid recipients."));
[error setIsRetryable:NO];
failureHandler(error);
} else {
@ -792,6 +768,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
}
});
[sendPromise retainUntilComplete];
}
@ -919,124 +896,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return deviceMessages;
}
- (OWSMessageSend *)getSessionRestoreMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey
{
__block TSContactThread *thread;
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
// Force hide slave device thread
NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:hexEncodedPublicKey in:transaction];
thread.isForceHidden = masterHexEncodedPublicKey != nil && ![masterHexEncodedPublicKey isEqualToString:hexEncodedPublicKey];
[thread saveWithTransaction:transaction];
}];
if (thread == nil) { return nil; }
LKSessionRestoreMessage *message = [[LKSessionRestoreMessage alloc] initWithThread:thread];
message.skipSave = YES;
SignalRecipient *recipient = [[SignalRecipient alloc] initWithUniqueId:hexEncodedPublicKey];
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
SMKSenderCertificate *senderCertificate = [self.udManager getSenderCertificate];
OWSUDAccess *theirUDAccess = nil;
if (senderCertificate != nil) {
theirUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES];
}
return [[OWSMessageSend alloc] initWithMessage:message thread:thread recipient:recipient senderCertificate:senderCertificate udAccess:theirUDAccess localNumber:userHexEncodedPublicKey success:^{ } failure:^(NSError *error) { }];
}
- (LKFriendRequestMessage *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction
{
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
// Force hide slave device thread
NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:hexEncodedPublicKey in:transaction];
thread.isForceHidden = masterHexEncodedPublicKey != nil && ![masterHexEncodedPublicKey isEqualToString:hexEncodedPublicKey];
if (thread.friendRequestStatus == LKThreadFriendRequestStatusNone || thread.friendRequestStatus == LKThreadFriendRequestStatusRequestExpired) {
[thread saveFriendRequestStatus:LKThreadFriendRequestStatusRequestSent withTransaction:transaction];
}
[thread saveWithTransaction:transaction];
LKFriendRequestMessage *message = [[LKFriendRequestMessage alloc] initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"Please accept to enable messages to be synced across devices" attachmentIds:[NSMutableArray new]
expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil];
message.skipSave = YES;
return message;
}
- (OWSMessageSend *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey
{
__block TSContactThread *thread;
__block LKFriendRequestMessage *message;
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
message = [self getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey transaction:transaction];
}];
SignalRecipient *recipient = [[SignalRecipient alloc] initWithUniqueId:hexEncodedPublicKey];
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
SMKSenderCertificate *senderCertificate = [self.udManager getSenderCertificate];
OWSUDAccess *theirUDAccess = nil;
if (senderCertificate != nil) {
theirUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES];
}
return [[OWSMessageSend alloc] initWithMessage:message thread:thread recipient:recipient senderCertificate:senderCertificate udAccess:theirUDAccess localNumber:userHexEncodedPublicKey success:^{ } failure:^(NSError *error) { }];
}
- (OWSMessageSend *)getMultiDeviceSessionRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey forThread:(TSThread *)thread
{
LKSessionRequestMessage *message = [[LKSessionRequestMessage alloc]initWithThread:thread];
message.skipSave = YES;
SignalRecipient *recipient = [[SignalRecipient alloc] initWithUniqueId:hexEncodedPublicKey];
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
return [[OWSMessageSend alloc] initWithMessage:message thread:thread recipient:recipient senderCertificate:nil udAccess:nil localNumber:userHexEncodedPublicKey success:^{ } failure:^(NSError *error) { }];
}
- (void)sendMessageToDestinationAndLinkedDevices:(OWSMessageSend *)messageSend
{
TSOutgoingMessage *message = messageSend.message;
NSString *contactID = messageSend.recipient.recipientId;
BOOL isGroupMessage = messageSend.thread.isGroupThread;
BOOL isPublicChatMessage = isGroupMessage && ((TSGroupThread *)messageSend.thread).isPublicChat;
BOOL isDeviceLinkMessage = [message isKindOfClass:LKDeviceLinkMessage.class];
if (isPublicChatMessage || isDeviceLinkMessage) {
[self sendMessage:messageSend];
} else {
BOOL isSilentMessage = message.isSilent || [message isKindOfClass:LKEphemeralMessage.class] || [message isKindOfClass:OWSOutgoingSyncMessage.class];
BOOL isFriendRequestMessage = [message isKindOfClass:LKFriendRequestMessage.class];
BOOL isSessionRequestMessage = [message isKindOfClass:LKSessionRequestMessage.class];
[[LKAPI getDestinationsFor:contactID]
.thenOn(OWSDispatch.sendingQueue, ^(NSArray<LKDestination *> *destinations) {
// Get master destination
LKDestination *masterDestination = [destinations filtered:^BOOL(LKDestination *destination) {
return [destination.kind isEqual:@"master"];
}].firstObject;
// Send to master destination
if (masterDestination != nil) {
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:masterDestination.hexEncodedPublicKey];
if (thread.isContactFriend || isSilentMessage || isFriendRequestMessage || isSessionRequestMessage || isGroupMessage) {
OWSMessageSend *messageSendCopy = [messageSend copyWithDestination:masterDestination];
[self sendMessage:messageSendCopy];
} else {
OWSMessageSend *friendRequestMessage = [self getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:masterDestination.hexEncodedPublicKey];
[self sendMessage:friendRequestMessage];
}
}
// Get slave destinations
NSArray *slaveDestinations = [destinations filtered:^BOOL(LKDestination *destination) {
return [destination.kind isEqual:@"slave"];
}];
// Send to slave destinations (using a best attempt approach (i.e. ignoring the message send result) for now)
for (LKDestination *slaveDestination in slaveDestinations) {
TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:slaveDestination.hexEncodedPublicKey];
if (thread.isContactFriend || isSilentMessage || isFriendRequestMessage || isSessionRequestMessage || isGroupMessage) {
OWSMessageSend *messageSendCopy = [messageSend copyWithDestination:slaveDestination];
[self sendMessage:messageSendCopy];
} else {
OWSMessageSend *friendRequestMessage = [self getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:slaveDestination.hexEncodedPublicKey];
[self sendMessage:friendRequestMessage];
}
}
})
.catchOn(OWSDispatch.sendingQueue, ^(NSError *error) {
[self sendMessage:messageSend]; // Proceed even if updating the linked devices map failed so that message sending is independent of whether the file server is up
}) retainUntilComplete];
}
}
- (void)sendMessage:(OWSMessageSend *)messageSend
{
OWSAssertDebug(messageSend);
@ -1206,7 +1065,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
}
if (deviceMessages.count == 0 && !message.thread.isGroupThread) {
if (deviceMessages.count == 0 && !(message.thread.isGroupThread && ((TSGroupThread *)message.thread).isPublicChat)) {
// This might happen:
//
// * The first (after upgrading?) time we send a sync message to our linked devices.
@ -1243,7 +1102,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
};
__block LKPublicChat *publicChat;
[OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
publicChat = [LKDatabaseUtilities getPublicChatForThreadID:message.uniqueThreadId transaction: transaction];
}];
if (publicChat != nil) {
@ -1281,7 +1140,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
.thenOn(OWSDispatch.sendingQueue, ^(LKGroupMessage *groupMessage) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[message saveOpenGroupServerMessageID:groupMessage.serverID in:transaction];
[OWSPrimaryStorage.sharedManager setIDForMessageWithServerID:groupMessage.serverID to:message.uniqueId in:transaction];
[self.primaryStorage setIDForMessageWithServerID:groupMessage.serverID to:message.uniqueId in:transaction];
}];
[self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:messageSend.isUDSend wasSentByWebsocket:false];
})
@ -1614,15 +1473,15 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
failure:(RetryableFailureHandler)failure
{
dispatch_block_t success = ^{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
TSThread *thread = message.thread;
// Loki: Handle note to self case
if (thread && [thread isKindOfClass:[TSContactThread class]] && [LKDatabaseUtilities isUserLinkedDevice:thread.contactIdentifier in:transaction]) {
// Loki: Handle note to self case
BOOL isNoteToSelf = [SessionProtocol isMessageNoteToSelf:message inThread:message.thread];
if (isNoteToSelf) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *recipientId in message.sendingRecipientIds) {
[message updateWithReadRecipientId:recipientId readTimestamp:message.timestamp transaction:transaction];
}
}
}];
}];
}
successParam();
};
@ -1637,18 +1496,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return success();
}
// Loki: Handle note to self case
__block BOOL isNoteToSelf = NO;
[self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
TSThread *thread = message.thread;
if (thread && [thread isKindOfClass:[TSContactThread class]] && [LKDatabaseUtilities isUserLinkedDevice:thread.contactIdentifier in:transaction]) {
isNoteToSelf = YES;
}
}];
BOOL isPublicChatMessage = message.thread.isGroupThread && ((TSGroupThread *)message.thread).isPublicChat;
BOOL shouldSendTranscript = (AreRecipientUpdatesEnabled() || !message.hasSyncedTranscript) && !isNoteToSelf && !isPublicChatMessage && !([message isKindOfClass:LKDeviceLinkMessage.class]);
BOOL shouldSendTranscript = [SessionProtocol shouldSendTranscriptForMessage:message in:message.thread];
if (!shouldSendTranscript) {
return success();
}
@ -1682,16 +1530,16 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}];
SMKSenderCertificate *senderCertificate = [self.udManager getSenderCertificate];
OWSUDAccess *theirUDAccess = nil;
OWSUDAccess *recipientUDAccess = nil;
if (senderCertificate != nil) {
theirUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES];
recipientUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES];
}
OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:sentMessageTranscript
thread:message.thread
recipient:recipient
senderCertificate:senderCertificate
udAccess:theirUDAccess
udAccess:recipientUDAccess
localNumber:self.tsAccountManager.localNumber
success:^{
OWSLogInfo(@"Successfully sent sync transcript.");
@ -1703,7 +1551,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
failure(error);
}];
[self sendMessageToDestinationAndLinkedDevices:messageSend];
[self.primaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[SessionProtocol sendMessageToDestinationAndLinkedDevices:messageSend in:transaction];
}];
}
- (NSArray<NSDictionary *> *)throws_deviceMessagesForMessageSend:(OWSMessageSend *)messageSend
@ -1734,12 +1584,8 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
@try {
// This may involve blocking network requests, so we do it _before_
// we open a transaction.
// Loki: We don't require a session for friend requests, session requests and device link requests
BOOL isFriendRequest = [messageSend.message isKindOfClass:LKFriendRequestMessage.class];
BOOL isSessionRequest = [messageSend.message isKindOfClass:LKSessionRequestMessage.class];
BOOL isDeviceLinkMessage = [messageSend.message isKindOfClass:LKDeviceLinkMessage.class];
if (!isFriendRequest && !isSessionRequest && !(isDeviceLinkMessage && ((LKDeviceLinkMessage *)messageSend.message).kind == LKDeviceLinkMessageKindRequest)) {
if ([SessionProtocol isSessionRequiredForMessage:messageSend.message]) {
[self throws_ensureRecipientHasSessionForMessageSend:messageSend recipientID:recipientID deviceId:@(OWSDevicePrimaryDeviceId)];
}
@ -1943,7 +1789,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// This will return nil if encryption failed
NSData *_Nullable serializedMessage = [cipher encryptWithMessage:[plainText paddedMessageBody]];
if (!serializedMessage) {
OWSFailDebug(@"Failed to encrypt friend message to: %@", recipientId);
OWSFailDebug(@"Failed to encrypt friend request for: %@.", recipientId);
return nil;
}
@ -1982,14 +1828,9 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
OWSPrimaryStorage *storage = self.primaryStorage;
TSOutgoingMessage *message = messageSend.message;
// Loki: Use fallback encryption for friend requests, session requests and device link requests
BOOL isFriendRequest = [messageSend.message isKindOfClass:LKFriendRequestMessage.class];
BOOL isSessionRequest = [messageSend.message isKindOfClass:LKSessionRequestMessage.class];
BOOL isDeviceLinkMessage = [messageSend.message isKindOfClass:LKDeviceLinkMessage.class] && ((LKDeviceLinkMessage *)messageSend.message).kind == LKDeviceLinkMessageKindRequest;
// This may throw an exception
if (!isFriendRequest && !isSessionRequest && !isDeviceLinkMessage && ![storage containsSession:recipientID deviceId:@(OWSDevicePrimaryDeviceId).intValue protocolContext:transaction]) {
if ([SessionProtocol isSessionRequiredForMessage:messageSend.message] && ![storage containsSession:recipientID deviceId:@(OWSDevicePrimaryDeviceId).intValue protocolContext:transaction]) {
NSString *missingSessionException = @"missingSessionException";
OWSRaiseException(missingSessionException,
@"Unexpectedly missing session for recipient: %@, device: %@.",
@ -1997,6 +1838,10 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
@(OWSDevicePrimaryDeviceId));
}
BOOL isFriendRequest = [messageSend.message isKindOfClass:LKFriendRequestMessage.class];
BOOL isDeviceLinkMessage = [messageSend.message isKindOfClass:LKDeviceLinkMessage.class]
&& ((LKDeviceLinkMessage *)messageSend.message).kind == LKDeviceLinkMessageKindRequest;
SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storage
preKeyStore:storage
signedPreKeyStore:storage
@ -2042,7 +1887,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
BOOL isSilent = message.isSilent;
BOOL isOnline = message.isOnline;
LKAddressMessage *addressMessage = [message as:[LKAddressMessage class]];
BOOL isPing = addressMessage != nil && addressMessage.isPing;
OWSMessageServiceParams *messageParams =

Loading…
Cancel
Save