diff --git a/SessionMessagingKit/Jobs/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/MessageReceiveJob.swift index e15593475..9b95f97ea 100644 --- a/SessionMessagingKit/Jobs/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/MessageReceiveJob.swift @@ -9,7 +9,7 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NS public static let maxFailureCount: UInt = 10 // MARK: Initialization - init(data: Data) { + public init(data: Data) { self.data = data } diff --git a/SessionMessagingKit/Jobs/MessageSendJob.swift b/SessionMessagingKit/Jobs/MessageSendJob.swift index 53290e7fb..c0a2b5d33 100644 --- a/SessionMessagingKit/Jobs/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/MessageSendJob.swift @@ -14,7 +14,7 @@ public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCodi @objc public convenience init(message: Message, publicKey: String) { self.init(message: message, destination: .contact(publicKey: publicKey)) } @objc public convenience init(message: Message, groupPublicKey: String) { self.init(message: message, destination: .closedGroup(groupPublicKey: groupPublicKey)) } - init(message: Message, destination: Message.Destination) { + public init(message: Message, destination: Message.Destination) { self.message = message self.destination = destination } diff --git a/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift b/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift index 2d5fdf176..0d525a271 100644 --- a/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift +++ b/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift @@ -14,6 +14,8 @@ public final class ClosedGroupUpdate : ControlMessage { } // MARK: Initialization + public override init() { super.init() } + internal init(kind: Kind) { super.init() self.kind = kind diff --git a/SessionMessagingKit/Messages/Control Message/NullMessage.swift b/SessionMessagingKit/Messages/Control Message/NullMessage.swift new file mode 100644 index 000000000..a66b93192 --- /dev/null +++ b/SessionMessagingKit/Messages/Control Message/NullMessage.swift @@ -0,0 +1,39 @@ +import SessionProtocolKit +import SessionUtilitiesKit + +@objc(SNNullMessage) +public final class NullMessage : ControlMessage { + + // MARK: Initialization + public override init() { super.init() } + + // MARK: Coding + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + public override func encode(with coder: NSCoder) { + super.encode(with: coder) + } + + // MARK: Proto Conversion + public override class func fromProto(_ proto: SNProtoContent) -> NullMessage? { + guard proto.nullMessage != nil else { return nil } + return NullMessage() + } + + public override func toProto() -> SNProtoContent? { + let nullMessageProto = SNProtoNullMessage.builder() + let paddingSize = UInt.random(in: 0..<512) // random(in:) uses the system's default random generator, which is cryptographically secure + let padding = Data.getSecureRandomData(ofSize: paddingSize)! + nullMessageProto.setPadding(padding) + let contentProto = SNProtoContent.builder() + do { + contentProto.setNullMessage(try nullMessageProto.build()) + return try contentProto.build() + } catch { + SNLog("Couldn't construct null message proto from: \(self).") + return nil + } + } +} diff --git a/SessionMessagingKit/Messages/Control Message/SessionRequest.swift b/SessionMessagingKit/Messages/Control Message/SessionRequest.swift index 36d1667e2..060ea1754 100644 --- a/SessionMessagingKit/Messages/Control Message/SessionRequest.swift +++ b/SessionMessagingKit/Messages/Control Message/SessionRequest.swift @@ -3,9 +3,11 @@ import SessionUtilitiesKit @objc(SNSessionRequest) public final class SessionRequest : ControlMessage { - private var preKeyBundle: PreKeyBundle? + public var preKeyBundle: PreKeyBundle? // MARK: Initialization + public override init() { super.init() } + internal init(preKeyBundle: PreKeyBundle) { super.init() self.preKeyBundle = preKeyBundle diff --git a/SessionMessagingKit/Messages/Control Message/TypingIndicator.swift b/SessionMessagingKit/Messages/Control Message/TypingIndicator.swift index 0e150ce73..e623868e0 100644 --- a/SessionMessagingKit/Messages/Control Message/TypingIndicator.swift +++ b/SessionMessagingKit/Messages/Control Message/TypingIndicator.swift @@ -29,6 +29,8 @@ public final class TypingIndicator : ControlMessage { public override var isValid: Bool { kind != nil } // MARK: Initialization + public override init() { super.init() } + internal init(kind: Kind) { super.init() self.kind = kind diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 93f7328c6..203169e2a 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -20,7 +20,7 @@ public enum MessageSender { } } - internal static func send(_ message: Message, to destination: Message.Destination, using transaction: Any) -> Promise { + public static func send(_ message: Message, to destination: Message.Destination, using transaction: Any) -> Promise { switch destination { case .contact(_), .closedGroup(_): return sendToSnodeDestination(destination, message: message, using: transaction) case .openGroup(_, _): return sendToOpenGroupDestination(destination, message: message, using: transaction) @@ -141,6 +141,12 @@ public enum MessageSender { } internal static func sendToOpenGroupDestination(_ destination: Message.Destination, message: Message, using transaction: Any) -> Promise { + message.sentTimestamp = NSDate.millisecondTimestamp() + switch destination { + case .contact(_): preconditionFailure() + case .closedGroup(_): preconditionFailure() + case .openGroup(let channel, let server): message.recipient = "\(server).\(channel)" + } guard message.isValid else { return Promise(error: Error.invalidMessage) } let (channel, server) = { () -> (UInt64, String) in switch destination { diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 0a7ef7111..266471fa2 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -362,7 +362,6 @@ C33FDC21255A581F00E217F9 /* OWSPrimaryStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDA67255A57F900E217F9 /* OWSPrimaryStorage.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDC22255A581F00E217F9 /* OWSBlockingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA68255A57F900E217F9 /* OWSBlockingManager.m */; }; C33FDC23255A581F00E217F9 /* SSKPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA69255A57F900E217F9 /* SSKPreferences.swift */; }; - C33FDC24255A581F00E217F9 /* OWSMessageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA6A255A57F900E217F9 /* OWSMessageManager.m */; }; C33FDC25255A581F00E217F9 /* OWSDisappearingConfigurationUpdateInfoMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA6B255A57FA00E217F9 /* OWSDisappearingConfigurationUpdateInfoMessage.m */; }; C33FDC26255A581F00E217F9 /* ProtoUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA6C255A57FA00E217F9 /* ProtoUtils.m */; }; C33FDC27255A581F00E217F9 /* YapDatabase+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA6D255A57FA00E217F9 /* YapDatabase+Promise.swift */; }; @@ -401,7 +400,6 @@ C33FDC49255A581F00E217F9 /* NSTimer+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA8F255A57FD00E217F9 /* NSTimer+OWS.m */; }; C33FDC4A255A582000E217F9 /* TSYapDatabaseObject.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA90255A57FD00E217F9 /* TSYapDatabaseObject.m */; }; C33FDC4B255A582000E217F9 /* LKSyncOpenGroupsMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDA91255A57FD00E217F9 /* LKSyncOpenGroupsMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDC4C255A582000E217F9 /* OWSMessageSender.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDA92255A57FE00E217F9 /* OWSMessageSender.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDC4E255A582000E217F9 /* Data+Streaming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA94255A57FE00E217F9 /* Data+Streaming.swift */; }; C33FDC4F255A582000E217F9 /* OWSChunkedOutputStream.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDA95255A57FE00E217F9 /* OWSChunkedOutputStream.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDC50255A582000E217F9 /* OWSDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDA96255A57FE00E217F9 /* OWSDispatch.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -426,7 +424,6 @@ C33FDC63255A582000E217F9 /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAA9255A580000E217F9 /* Mnemonic.swift */; }; C33FDC64255A582000E217F9 /* NSObject+Casting.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAAA255A580000E217F9 /* NSObject+Casting.m */; }; C33FDC65255A582000E217F9 /* OWSWebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAAB255A580000E217F9 /* OWSWebSocket.m */; }; - C33FDC66255A582000E217F9 /* OWSMessageHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAAC255A580000E217F9 /* OWSMessageHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDC67255A582000E217F9 /* OWSDeviceProvisioner.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAAD255A580000E217F9 /* OWSDeviceProvisioner.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDC68255A582000E217F9 /* OWSReceiptsForSenderMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAAE255A580000E217F9 /* OWSReceiptsForSenderMessage.m */; }; C33FDC69255A582000E217F9 /* String+Trimming.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAAF255A580000E217F9 /* String+Trimming.swift */; }; @@ -434,7 +431,6 @@ C33FDC6B255A582000E217F9 /* OWSStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAB1255A580000E217F9 /* OWSStorage.m */; }; C33FDC6C255A582000E217F9 /* TSNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAB2255A580000E217F9 /* TSNetworkManager.m */; }; C33FDC6D255A582000E217F9 /* TSContactThread.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAB3255A580000E217F9 /* TSContactThread.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDC6E255A582000E217F9 /* OWSMessageReceiver.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAB4255A580000E217F9 /* OWSMessageReceiver.m */; }; C33FDC6F255A582000E217F9 /* TSNetworkManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAB5255A580000E217F9 /* TSNetworkManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDC70255A582000E217F9 /* SyncMessagesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAB6255A580100E217F9 /* SyncMessagesProtocol.swift */; }; C33FDC71255A582000E217F9 /* OWSFailedMessagesJob.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAB7255A580100E217F9 /* OWSFailedMessagesJob.m */; }; @@ -456,7 +452,6 @@ C33FDC83255A582000E217F9 /* OWSContact.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDAC9255A580200E217F9 /* OWSContact.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDC84255A582000E217F9 /* LokiMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDACA255A580200E217F9 /* LokiMessage.swift */; }; C33FDC85255A582000E217F9 /* CDSSigningCertificate.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDACB255A580200E217F9 /* CDSSigningCertificate.m */; }; - C33FDC86255A582000E217F9 /* OWSMessageSend.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDACC255A580200E217F9 /* OWSMessageSend.swift */; }; C33FDC87255A582000E217F9 /* SSKJobRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDACD255A580200E217F9 /* SSKJobRecord.m */; }; C33FDC88255A582000E217F9 /* OWSVerificationStateSyncMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDACE255A580300E217F9 /* OWSVerificationStateSyncMessage.m */; }; C33FDC89255A582000E217F9 /* OWSAttachmentDownloads.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDACF255A580300E217F9 /* OWSAttachmentDownloads.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -563,7 +558,6 @@ C33FDCF4255A582000E217F9 /* Poller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB3A255A580B00E217F9 /* Poller.swift */; }; C33FDCF5255A582000E217F9 /* NSNotificationCenter+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB3B255A580B00E217F9 /* NSNotificationCenter+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDCF7255A582000E217F9 /* OWSProfileKeyMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB3D255A580B00E217F9 /* OWSProfileKeyMessage.m */; }; - C33FDCF8255A582000E217F9 /* OWSMessageSender.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB3E255A580B00E217F9 /* OWSMessageSender.m */; }; C33FDCF9255A582000E217F9 /* String+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB3F255A580C00E217F9 /* String+SSK.swift */; }; C33FDCFA255A582000E217F9 /* SignalIOSProto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB40255A580C00E217F9 /* SignalIOSProto.swift */; }; C33FDCFB255A582000E217F9 /* MIMETypeUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB41255A580C00E217F9 /* MIMETypeUtil.m */; }; @@ -623,7 +617,6 @@ C33FDD33255A582000E217F9 /* PhoneNumberUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB79255A581000E217F9 /* PhoneNumberUtil.m */; }; C33FDD34255A582000E217F9 /* NotificationsProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB7A255A581000E217F9 /* NotificationsProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD36255A582000E217F9 /* OWSVerificationStateChangeMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB7C255A581000E217F9 /* OWSVerificationStateChangeMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDD37255A582000E217F9 /* OWSMessageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB7D255A581100E217F9 /* OWSMessageManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD38255A582000E217F9 /* TSPreKeyManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB7E255A581100E217F9 /* TSPreKeyManager.m */; }; C33FDD39255A582000E217F9 /* FullTextSearchFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB7F255A581100E217F9 /* FullTextSearchFinder.swift */; }; C33FDD3A255A582000E217F9 /* Notification+Loki.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB80255A581100E217F9 /* Notification+Loki.swift */; }; @@ -642,7 +635,6 @@ C33FDD47255A582000E217F9 /* DeviceLinkingSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB8D255A581200E217F9 /* DeviceLinkingSessionDelegate.swift */; }; C33FDD48255A582000E217F9 /* OWSContact+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB8E255A581200E217F9 /* OWSContact+Private.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB8F255A581200E217F9 /* ParamParser.swift */; }; - C33FDD4A255A582000E217F9 /* OWSMessageDecrypter.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB90255A581200E217F9 /* OWSMessageDecrypter.m */; }; C33FDD4B255A582000E217F9 /* ProtoUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB91255A581200E217F9 /* ProtoUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD4C255A582000E217F9 /* OWSDeviceProvisioningService.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB92255A581200E217F9 /* OWSDeviceProvisioningService.m */; }; C33FDD4D255A582000E217F9 /* PreKeyBundle+jsonDict.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB93255A581200E217F9 /* PreKeyBundle+jsonDict.m */; }; @@ -653,7 +645,6 @@ C33FDD52255A582000E217F9 /* DeviceNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB98255A581300E217F9 /* DeviceNames.swift */; }; C33FDD53255A582000E217F9 /* OWSPrimaryStorage+keyFromIntLong.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB99255A581300E217F9 /* OWSPrimaryStorage+keyFromIntLong.m */; }; C33FDD54255A582000E217F9 /* OWS2FAManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB9A255A581300E217F9 /* OWS2FAManager.m */; }; - C33FDD55255A582000E217F9 /* MessageSenderJobQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB9B255A581300E217F9 /* MessageSenderJobQueue.swift */; }; C33FDD56255A582000E217F9 /* TSIncomingMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB9C255A581300E217F9 /* TSIncomingMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD57255A582000E217F9 /* OWSCallMessageHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB9D255A581300E217F9 /* OWSCallMessageHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD58255A582000E217F9 /* TSAttachmentPointer.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB9E255A581400E217F9 /* TSAttachmentPointer.m */; }; @@ -691,12 +682,10 @@ C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBC2255A581700E217F9 /* SSKAsserts.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD7D255A582000E217F9 /* AnyPromise+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBC3255A581700E217F9 /* AnyPromise+Conversion.swift */; }; C33FDD7E255A582000E217F9 /* TypingIndicatorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBC4255A581700E217F9 /* TypingIndicatorMessage.swift */; }; - C33FDD80255A582000E217F9 /* OWSMessageReceiver.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBC6255A581700E217F9 /* OWSMessageReceiver.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD82255A582000E217F9 /* OWSFingerprint.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBC8255A581700E217F9 /* OWSFingerprint.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD83255A582000E217F9 /* CreatePreKeysOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBC9255A581700E217F9 /* CreatePreKeysOperation.swift */; }; C33FDD84255A582000E217F9 /* LKGroupUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBCA255A581700E217F9 /* LKGroupUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD85255A582000E217F9 /* TSInvalidIdentityKeyReceivingErrorMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBCB255A581800E217F9 /* TSInvalidIdentityKeyReceivingErrorMessage.m */; }; - C33FDD86255A582000E217F9 /* MultiDeviceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBCC255A581800E217F9 /* MultiDeviceProtocol.swift */; }; C33FDD88255A582000E217F9 /* OWSMessageServiceParams.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBCE255A581800E217F9 /* OWSMessageServiceParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD89255A582000E217F9 /* OWSFingerprintBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBCF255A581800E217F9 /* OWSFingerprintBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDD8A255A582000E217F9 /* OnionRequestAPI+Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD0255A581800E217F9 /* OnionRequestAPI+Encryption.swift */; }; @@ -733,7 +722,6 @@ C33FDDA9255A582000E217F9 /* TSStorageKeys.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBEF255A581B00E217F9 /* TSStorageKeys.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDDAA255A582000E217F9 /* LokiDatabaseUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBF0255A581B00E217F9 /* LokiDatabaseUtilities.swift */; }; C33FDDAB255A582000E217F9 /* OWSIdentityManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF1255A581B00E217F9 /* OWSIdentityManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDDAC255A582000E217F9 /* MessageSender+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBF2255A581B00E217F9 /* MessageSender+Promise.swift */; }; C33FDDAD255A582000E217F9 /* OWSBlockedPhoneNumbersMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF3255A581B00E217F9 /* OWSBlockedPhoneNumbersMessage.h */; }; C33FDDAE255A582000E217F9 /* DisplayNameUtilities2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBF4255A581B00E217F9 /* DisplayNameUtilities2.swift */; }; C33FDDAF255A582000E217F9 /* OWSDeviceProvisioningService.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF5255A581B00E217F9 /* OWSDeviceProvisioningService.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -743,7 +731,6 @@ C33FDDB3255A582000E217F9 /* OWSError.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF9255A581C00E217F9 /* OWSError.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDDB4255A582000E217F9 /* PhoneNumberUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBFA255A581C00E217F9 /* PhoneNumberUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDDB5255A582000E217F9 /* Storage+PublicChats.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBFB255A581C00E217F9 /* Storage+PublicChats.swift */; }; - C33FDDB6255A582000E217F9 /* OWSMessageHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBFC255A581C00E217F9 /* OWSMessageHandler.m */; }; C33FDDB7255A582000E217F9 /* OWSPrimaryStorage+Calling.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBFD255A581C00E217F9 /* OWSPrimaryStorage+Calling.m */; }; C33FDDB8255A582000E217F9 /* NSSet+Functional.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBFE255A581C00E217F9 /* NSSet+Functional.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDDB9255A582000E217F9 /* OWSOutgoingSyncMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBFF255A581C00E217F9 /* OWSOutgoingSyncMessage.m */; }; @@ -761,7 +748,6 @@ C33FDDC5255A582000E217F9 /* OWSError.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC0B255A581D00E217F9 /* OWSError.m */; }; C33FDDC6255A582000E217F9 /* TSInfoMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC0C255A581E00E217F9 /* TSInfoMessage.m */; }; C33FDDC7255A582000E217F9 /* OWSProvisioningMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC0D255A581E00E217F9 /* OWSProvisioningMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C33FDDC8255A582000E217F9 /* OWSMessageDecrypter.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC0E255A581E00E217F9 /* OWSMessageDecrypter.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDDC9255A582000E217F9 /* LKDeviceLinkMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDC0F255A581E00E217F9 /* LKDeviceLinkMessage.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33FDDCA255A582000E217F9 /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC10255A581E00E217F9 /* ProofOfWork.swift */; }; C33FDDCB255A582000E217F9 /* TSSocketManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDC11255A581E00E217F9 /* TSSocketManager.m */; }; @@ -1156,6 +1142,9 @@ C3CA3AB4255CDAE600F4C6D4 /* japanese.txt in Resources */ = {isa = PBXBuildFile; fileRef = C3CA3AB3255CDAE600F4C6D4 /* japanese.txt */; }; C3CA3ABE255CDB0D00F4C6D4 /* portuguese.txt in Resources */ = {isa = PBXBuildFile; fileRef = C3CA3ABD255CDB0D00F4C6D4 /* portuguese.txt */; }; C3CA3AC8255CDB2900F4C6D4 /* spanish.txt in Resources */ = {isa = PBXBuildFile; fileRef = C3CA3AC7255CDB2900F4C6D4 /* spanish.txt */; }; + C3CA3B13255CF18200F4C6D4 /* Destination+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3CA3B12255CF18200F4C6D4 /* Destination+Conversion.swift */; }; + C3CA3B1D255CF3C800F4C6D4 /* MessageSender+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3CA3B1C255CF3C800F4C6D4 /* MessageSender+Utilities.swift */; }; + C3CA3B2F255CF84E00F4C6D4 /* NullMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3CA3B2E255CF84E00F4C6D4 /* NullMessage.swift */; }; C3D0972B2510499C00F6E3E4 /* BackgroundPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */; }; C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; @@ -1728,7 +1717,6 @@ C33FDA67255A57F900E217F9 /* OWSPrimaryStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSPrimaryStorage.h; sourceTree = ""; }; C33FDA68255A57F900E217F9 /* OWSBlockingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBlockingManager.m; sourceTree = ""; }; C33FDA69255A57F900E217F9 /* SSKPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKPreferences.swift; sourceTree = ""; }; - C33FDA6A255A57F900E217F9 /* OWSMessageManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageManager.m; sourceTree = ""; }; C33FDA6B255A57FA00E217F9 /* OWSDisappearingConfigurationUpdateInfoMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDisappearingConfigurationUpdateInfoMessage.m; sourceTree = ""; }; C33FDA6C255A57FA00E217F9 /* ProtoUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ProtoUtils.m; sourceTree = ""; }; C33FDA6D255A57FA00E217F9 /* YapDatabase+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "YapDatabase+Promise.swift"; sourceTree = ""; }; @@ -1767,7 +1755,6 @@ C33FDA8F255A57FD00E217F9 /* NSTimer+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+OWS.m"; sourceTree = ""; }; C33FDA90255A57FD00E217F9 /* TSYapDatabaseObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSYapDatabaseObject.m; sourceTree = ""; }; C33FDA91255A57FD00E217F9 /* LKSyncOpenGroupsMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LKSyncOpenGroupsMessage.h; sourceTree = ""; }; - C33FDA92255A57FE00E217F9 /* OWSMessageSender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageSender.h; sourceTree = ""; }; C33FDA94255A57FE00E217F9 /* Data+Streaming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Streaming.swift"; sourceTree = ""; }; C33FDA95255A57FE00E217F9 /* OWSChunkedOutputStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSChunkedOutputStream.h; sourceTree = ""; }; C33FDA96255A57FE00E217F9 /* OWSDispatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDispatch.h; sourceTree = ""; }; @@ -1792,7 +1779,6 @@ C33FDAA9255A580000E217F9 /* Mnemonic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = ""; }; C33FDAAA255A580000E217F9 /* NSObject+Casting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Casting.m"; sourceTree = ""; }; C33FDAAB255A580000E217F9 /* OWSWebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSWebSocket.m; sourceTree = ""; }; - C33FDAAC255A580000E217F9 /* OWSMessageHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageHandler.h; sourceTree = ""; }; C33FDAAD255A580000E217F9 /* OWSDeviceProvisioner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDeviceProvisioner.h; sourceTree = ""; }; C33FDAAE255A580000E217F9 /* OWSReceiptsForSenderMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSReceiptsForSenderMessage.m; sourceTree = ""; }; C33FDAAF255A580000E217F9 /* String+Trimming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Trimming.swift"; sourceTree = ""; }; @@ -1800,7 +1786,6 @@ C33FDAB1255A580000E217F9 /* OWSStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSStorage.m; sourceTree = ""; }; C33FDAB2255A580000E217F9 /* TSNetworkManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSNetworkManager.m; sourceTree = ""; }; C33FDAB3255A580000E217F9 /* TSContactThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSContactThread.h; sourceTree = ""; }; - C33FDAB4255A580000E217F9 /* OWSMessageReceiver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageReceiver.m; sourceTree = ""; }; C33FDAB5255A580000E217F9 /* TSNetworkManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSNetworkManager.h; sourceTree = ""; }; C33FDAB6255A580100E217F9 /* SyncMessagesProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncMessagesProtocol.swift; sourceTree = ""; }; C33FDAB7255A580100E217F9 /* OWSFailedMessagesJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSFailedMessagesJob.m; sourceTree = ""; }; @@ -1822,7 +1807,6 @@ C33FDAC9255A580200E217F9 /* OWSContact.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContact.h; sourceTree = ""; }; C33FDACA255A580200E217F9 /* LokiMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LokiMessage.swift; sourceTree = ""; }; C33FDACB255A580200E217F9 /* CDSSigningCertificate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDSSigningCertificate.m; sourceTree = ""; }; - C33FDACC255A580200E217F9 /* OWSMessageSend.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSMessageSend.swift; sourceTree = ""; }; C33FDACD255A580200E217F9 /* SSKJobRecord.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SSKJobRecord.m; sourceTree = ""; }; C33FDACE255A580300E217F9 /* OWSVerificationStateSyncMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSVerificationStateSyncMessage.m; sourceTree = ""; }; C33FDACF255A580300E217F9 /* OWSAttachmentDownloads.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAttachmentDownloads.h; sourceTree = ""; }; @@ -1929,7 +1913,6 @@ C33FDB3A255A580B00E217F9 /* Poller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Poller.swift; sourceTree = ""; }; C33FDB3B255A580B00E217F9 /* NSNotificationCenter+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+OWS.h"; sourceTree = ""; }; C33FDB3D255A580B00E217F9 /* OWSProfileKeyMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProfileKeyMessage.m; sourceTree = ""; }; - C33FDB3E255A580B00E217F9 /* OWSMessageSender.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageSender.m; sourceTree = ""; }; C33FDB3F255A580C00E217F9 /* String+SSK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SSK.swift"; sourceTree = ""; }; C33FDB40255A580C00E217F9 /* SignalIOSProto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalIOSProto.swift; sourceTree = ""; }; C33FDB41255A580C00E217F9 /* MIMETypeUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIMETypeUtil.m; sourceTree = ""; }; @@ -1989,7 +1972,6 @@ C33FDB79255A581000E217F9 /* PhoneNumberUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhoneNumberUtil.m; sourceTree = ""; }; C33FDB7A255A581000E217F9 /* NotificationsProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationsProtocol.h; sourceTree = ""; }; C33FDB7C255A581000E217F9 /* OWSVerificationStateChangeMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSVerificationStateChangeMessage.h; sourceTree = ""; }; - C33FDB7D255A581100E217F9 /* OWSMessageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageManager.h; sourceTree = ""; }; C33FDB7E255A581100E217F9 /* TSPreKeyManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSPreKeyManager.m; sourceTree = ""; }; C33FDB7F255A581100E217F9 /* FullTextSearchFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullTextSearchFinder.swift; sourceTree = ""; }; C33FDB80255A581100E217F9 /* Notification+Loki.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Notification+Loki.swift"; sourceTree = ""; }; @@ -2008,7 +1990,6 @@ C33FDB8D255A581200E217F9 /* DeviceLinkingSessionDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceLinkingSessionDelegate.swift; sourceTree = ""; }; C33FDB8E255A581200E217F9 /* OWSContact+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSContact+Private.h"; sourceTree = ""; }; C33FDB8F255A581200E217F9 /* ParamParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParamParser.swift; sourceTree = ""; }; - C33FDB90255A581200E217F9 /* OWSMessageDecrypter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageDecrypter.m; sourceTree = ""; }; C33FDB91255A581200E217F9 /* ProtoUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProtoUtils.h; sourceTree = ""; }; C33FDB92255A581200E217F9 /* OWSDeviceProvisioningService.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDeviceProvisioningService.m; sourceTree = ""; }; C33FDB93255A581200E217F9 /* PreKeyBundle+jsonDict.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "PreKeyBundle+jsonDict.m"; sourceTree = ""; }; @@ -2019,7 +2000,6 @@ C33FDB98255A581300E217F9 /* DeviceNames.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceNames.swift; sourceTree = ""; }; C33FDB99255A581300E217F9 /* OWSPrimaryStorage+keyFromIntLong.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OWSPrimaryStorage+keyFromIntLong.m"; sourceTree = ""; }; C33FDB9A255A581300E217F9 /* OWS2FAManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWS2FAManager.m; sourceTree = ""; }; - C33FDB9B255A581300E217F9 /* MessageSenderJobQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageSenderJobQueue.swift; sourceTree = ""; }; C33FDB9C255A581300E217F9 /* TSIncomingMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSIncomingMessage.h; sourceTree = ""; }; C33FDB9D255A581300E217F9 /* OWSCallMessageHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSCallMessageHandler.h; sourceTree = ""; }; C33FDB9E255A581400E217F9 /* TSAttachmentPointer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSAttachmentPointer.m; sourceTree = ""; }; @@ -2057,12 +2037,10 @@ C33FDBC2255A581700E217F9 /* SSKAsserts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKAsserts.h; sourceTree = ""; }; C33FDBC3255A581700E217F9 /* AnyPromise+Conversion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AnyPromise+Conversion.swift"; sourceTree = ""; }; C33FDBC4255A581700E217F9 /* TypingIndicatorMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicatorMessage.swift; sourceTree = ""; }; - C33FDBC6255A581700E217F9 /* OWSMessageReceiver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageReceiver.h; sourceTree = ""; }; C33FDBC8255A581700E217F9 /* OWSFingerprint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSFingerprint.h; sourceTree = ""; }; C33FDBC9255A581700E217F9 /* CreatePreKeysOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreatePreKeysOperation.swift; sourceTree = ""; }; C33FDBCA255A581700E217F9 /* LKGroupUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LKGroupUtilities.h; sourceTree = ""; }; C33FDBCB255A581800E217F9 /* TSInvalidIdentityKeyReceivingErrorMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSInvalidIdentityKeyReceivingErrorMessage.m; sourceTree = ""; }; - C33FDBCC255A581800E217F9 /* MultiDeviceProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiDeviceProtocol.swift; sourceTree = ""; }; C33FDBCE255A581800E217F9 /* OWSMessageServiceParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageServiceParams.h; sourceTree = ""; }; C33FDBCF255A581800E217F9 /* OWSFingerprintBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSFingerprintBuilder.h; sourceTree = ""; }; C33FDBD0255A581800E217F9 /* OnionRequestAPI+Encryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OnionRequestAPI+Encryption.swift"; sourceTree = ""; }; @@ -2099,7 +2077,6 @@ C33FDBEF255A581B00E217F9 /* TSStorageKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSStorageKeys.h; sourceTree = ""; }; C33FDBF0255A581B00E217F9 /* LokiDatabaseUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LokiDatabaseUtilities.swift; sourceTree = ""; }; C33FDBF1255A581B00E217F9 /* OWSIdentityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSIdentityManager.h; sourceTree = ""; }; - C33FDBF2255A581B00E217F9 /* MessageSender+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MessageSender+Promise.swift"; sourceTree = ""; }; C33FDBF3255A581B00E217F9 /* OWSBlockedPhoneNumbersMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBlockedPhoneNumbersMessage.h; sourceTree = ""; }; C33FDBF4255A581B00E217F9 /* DisplayNameUtilities2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayNameUtilities2.swift; sourceTree = ""; }; C33FDBF5255A581B00E217F9 /* OWSDeviceProvisioningService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDeviceProvisioningService.h; sourceTree = ""; }; @@ -2109,7 +2086,6 @@ C33FDBF9255A581C00E217F9 /* OWSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSError.h; sourceTree = ""; }; C33FDBFA255A581C00E217F9 /* PhoneNumberUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhoneNumberUtil.h; sourceTree = ""; }; C33FDBFB255A581C00E217F9 /* Storage+PublicChats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Storage+PublicChats.swift"; sourceTree = ""; }; - C33FDBFC255A581C00E217F9 /* OWSMessageHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageHandler.m; sourceTree = ""; }; C33FDBFD255A581C00E217F9 /* OWSPrimaryStorage+Calling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OWSPrimaryStorage+Calling.m"; sourceTree = ""; }; C33FDBFE255A581C00E217F9 /* NSSet+Functional.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSSet+Functional.h"; sourceTree = ""; }; C33FDBFF255A581C00E217F9 /* OWSOutgoingSyncMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOutgoingSyncMessage.m; sourceTree = ""; }; @@ -2127,7 +2103,6 @@ C33FDC0B255A581D00E217F9 /* OWSError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSError.m; sourceTree = ""; }; C33FDC0C255A581E00E217F9 /* TSInfoMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSInfoMessage.m; sourceTree = ""; }; C33FDC0D255A581E00E217F9 /* OWSProvisioningMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProvisioningMessage.h; sourceTree = ""; }; - C33FDC0E255A581E00E217F9 /* OWSMessageDecrypter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageDecrypter.h; sourceTree = ""; }; C33FDC0F255A581E00E217F9 /* LKDeviceLinkMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LKDeviceLinkMessage.h; sourceTree = ""; }; C33FDC10255A581E00E217F9 /* ProofOfWork.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = ""; }; C33FDC11255A581E00E217F9 /* TSSocketManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSSocketManager.m; sourceTree = ""; }; @@ -2525,6 +2500,9 @@ C3CA3AB3255CDAE600F4C6D4 /* japanese.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = japanese.txt; sourceTree = ""; }; C3CA3ABD255CDB0D00F4C6D4 /* portuguese.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = portuguese.txt; sourceTree = ""; }; C3CA3AC7255CDB2900F4C6D4 /* spanish.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = spanish.txt; sourceTree = ""; }; + C3CA3B12255CF18200F4C6D4 /* Destination+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Destination+Conversion.swift"; sourceTree = ""; }; + C3CA3B1C255CF3C800F4C6D4 /* MessageSender+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageSender+Utilities.swift"; sourceTree = ""; }; + C3CA3B2E255CF84E00F4C6D4 /* NullMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NullMessage.swift; sourceTree = ""; }; C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundPoller.swift; sourceTree = ""; }; C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCopyableLabel.swift; sourceTree = ""; }; C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; @@ -3230,6 +3208,7 @@ C3C2A7702553A41E00C340D1 /* ControlMessage.swift */, C300A5BC2554B00D00555489 /* ReadReceipt.swift */, C300A5C82554B04E00555489 /* SessionRequest.swift */, + C3CA3B2E255CF84E00F4C6D4 /* NullMessage.swift */, C300A5D22554B05A00555489 /* TypingIndicator.swift */, C300A5DC2554B06600555489 /* ClosedGroupUpdate.swift */, C300A5E62554B07300555489 /* ExpirationTimerUpdate.swift */, @@ -3350,6 +3329,7 @@ C33FD9AC255A548A00E217F9 /* SignalUtilitiesKit */ = { isa = PBXGroup; children = ( + C3CA3B11255CF17200F4C6D4 /* Utilities */, C33FDC09255A581D00E217F9 /* AccountServiceClient.swift */, C33FDBC3255A581700E217F9 /* AnyPromise+Conversion.swift */, C33FDB8A255A581200E217F9 /* AppContext.h */, @@ -3609,13 +3589,10 @@ C33FDAFD255A580600E217F9 /* LRUCache.swift */, C33FDA7E255A57FB00E217F9 /* Mention.swift */, C33FDA81255A57FC00E217F9 /* MentionsManager.swift */, - C33FDBF2255A581B00E217F9 /* MessageSender+Promise.swift */, - C33FDB9B255A581300E217F9 /* MessageSenderJobQueue.swift */, C33FDB82255A581100E217F9 /* MessageWrapper.swift */, C33FDAFC255A580600E217F9 /* MIMETypeUtil.h */, C33FDB41255A580C00E217F9 /* MIMETypeUtil.m */, C33FDAA9255A580000E217F9 /* Mnemonic.swift */, - C33FDBCC255A581800E217F9 /* MultiDeviceProtocol.swift */, C33FDA9F255A57FF00E217F9 /* NetworkManager.swift */, C33FDB80255A581100E217F9 /* Notification+Loki.swift */, C33FDB7A255A581000E217F9 /* NotificationsProtocol.h */, @@ -3736,17 +3713,6 @@ C33FDB67255A580F00E217F9 /* OWSMediaGalleryFinder.h */, C33FDB71255A581000E217F9 /* OWSMediaGalleryFinder.m */, C33FDB22255A580900E217F9 /* OWSMediaUtils.swift */, - C33FDC0E255A581E00E217F9 /* OWSMessageDecrypter.h */, - C33FDB90255A581200E217F9 /* OWSMessageDecrypter.m */, - C33FDAAC255A580000E217F9 /* OWSMessageHandler.h */, - C33FDBFC255A581C00E217F9 /* OWSMessageHandler.m */, - C33FDB7D255A581100E217F9 /* OWSMessageManager.h */, - C33FDA6A255A57F900E217F9 /* OWSMessageManager.m */, - C33FDBC6255A581700E217F9 /* OWSMessageReceiver.h */, - C33FDAB4255A580000E217F9 /* OWSMessageReceiver.m */, - C33FDACC255A580200E217F9 /* OWSMessageSend.swift */, - C33FDA92255A57FE00E217F9 /* OWSMessageSender.h */, - C33FDB3E255A580B00E217F9 /* OWSMessageSender.m */, C33FDBCE255A581800E217F9 /* OWSMessageServiceParams.h */, C33FDB70255A580F00E217F9 /* OWSMessageServiceParams.m */, C33FDAE8255A580500E217F9 /* OWSMessageUtils.h */, @@ -4364,6 +4330,15 @@ path = Mnemonic; sourceTree = ""; }; + C3CA3B11255CF17200F4C6D4 /* Utilities */ = { + isa = PBXGroup; + children = ( + C3CA3B12255CF18200F4C6D4 /* Destination+Conversion.swift */, + C3CA3B1C255CF3C800F4C6D4 /* MessageSender+Utilities.swift */, + ); + path = Utilities; + sourceTree = ""; + }; C3F0A58F255C8E3D007BE2A3 /* Meta */ = { isa = PBXGroup; children = ( @@ -4542,7 +4517,6 @@ C33FDD65255A582000E217F9 /* OWSFileSystem.h in Headers */, C33FDCD6255A582000E217F9 /* UIImage+OWS.h in Headers */, C38EF219255B6D3B007E1867 /* OWSConversationColor.h in Headers */, - C33FDD80255A582000E217F9 /* OWSMessageReceiver.h in Headers */, C38EF369255B6DCC007E1867 /* ViewControllerUtils.h in Headers */, C33FDC6D255A582000E217F9 /* TSContactThread.h in Headers */, C33FDD7A255A582000E217F9 /* OWSRequestFactory.h in Headers */, @@ -4572,7 +4546,6 @@ C33FDCDF255A582000E217F9 /* TSDatabaseSecondaryIndexes.h in Headers */, C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */, C33FDCBC255A582000E217F9 /* TSCall.h in Headers */, - C33FDD37255A582000E217F9 /* OWSMessageManager.h in Headers */, C38EF2C3255B6DA6007E1867 /* OWSContactOffersInteraction.h in Headers */, C33FDD95255A582000E217F9 /* OWSDevicesService.h in Headers */, C38EF243255B6D67007E1867 /* UIViewController+OWS.h in Headers */, @@ -4616,7 +4589,6 @@ C33FDC42255A581F00E217F9 /* YapDatabaseTransaction+OWS.h in Headers */, C33FDD48255A582000E217F9 /* OWSContact+Private.h in Headers */, C33FDCA6255A582000E217F9 /* SignalRecipient.h in Headers */, - C33FDC66255A582000E217F9 /* OWSMessageHandler.h in Headers */, C33FDDA1255A582000E217F9 /* NSTimer+OWS.h in Headers */, C38EF277255B6D7A007E1867 /* OWSDatabaseMigration.h in Headers */, C38EF294255B6D86007E1867 /* OWSSounds.h in Headers */, @@ -4629,7 +4601,6 @@ C33FDC83255A582000E217F9 /* OWSContact.h in Headers */, C33FDCB2255A582000E217F9 /* OWSSyncGroupsRequestMessage.h in Headers */, C33FDCA7255A582000E217F9 /* SSKMessageSenderJobRecord.h in Headers */, - C33FDDC8255A582000E217F9 /* OWSMessageDecrypter.h in Headers */, C38EF262255B6D6F007E1867 /* OWSContactsManager.h in Headers */, C33FDCC2255A582000E217F9 /* OWSProfileKeyMessage.h in Headers */, C33FDCDE255A582000E217F9 /* OWSOutgoingSentMessageTranscript.h in Headers */, @@ -4653,7 +4624,6 @@ C33FDDB3255A582000E217F9 /* OWSError.h in Headers */, C33FDD57255A582000E217F9 /* OWSCallMessageHandler.h in Headers */, C33FDCC8255A582000E217F9 /* NSError+MessageSending.h in Headers */, - C33FDC4C255A582000E217F9 /* OWSMessageSender.h in Headers */, C38EF403255B6DF7007E1867 /* ContactCellView.h in Headers */, C33FDD6D255A582000E217F9 /* OWSOutgoingNullMessage.h in Headers */, C33FDCA0255A582000E217F9 /* TSInteraction.h in Headers */, @@ -5649,7 +5619,6 @@ C33FDDA8255A582000E217F9 /* ClosedGroupUpdateMessage.swift in Sources */, C33FDCF9255A582000E217F9 /* String+SSK.swift in Sources */, C38EF389255B6DD2007E1867 /* AttachmentTextView.swift in Sources */, - C33FDDAC255A582000E217F9 /* MessageSender+Promise.swift in Sources */, C38EF36A255B6DCC007E1867 /* NewNonContactConversationViewController.m in Sources */, C38EF321255B6DBF007E1867 /* OWSAudioPlayer.m in Sources */, C33FDD0A255A582000E217F9 /* OWSPrimaryStorage+PreKeyStore.m in Sources */, @@ -5664,7 +5633,6 @@ C38EF3C2255B6DE7007E1867 /* ImageEditorPaletteView.swift in Sources */, C38EF245255B6D67007E1867 /* UIFont+OWS.m in Sources */, C33FDD26255A582000E217F9 /* NSNotificationCenter+OWS.m in Sources */, - C33FDDB6255A582000E217F9 /* OWSMessageHandler.m in Sources */, C38EF36F255B6DCC007E1867 /* OWSViewController.m in Sources */, C33FDC80255A582000E217F9 /* Fingerprint.pb.swift in Sources */, C33FDD42255A582000E217F9 /* TSAccountManager.m in Sources */, @@ -5678,7 +5646,6 @@ C33FDD5F255A582000E217F9 /* SignalServiceProfile.swift in Sources */, C33FDD83255A582000E217F9 /* CreatePreKeysOperation.swift in Sources */, C33FDDD5255A582000E217F9 /* OWSBackgroundTask.m in Sources */, - C33FDC6E255A582000E217F9 /* OWSMessageReceiver.m in Sources */, C33FDC7E255A582000E217F9 /* TSAttachmentStream.m in Sources */, C33FDC62255A582000E217F9 /* BuildConfiguration.swift in Sources */, C38EF40A255B6DF7007E1867 /* OWSFlatButton.swift in Sources */, @@ -5753,7 +5720,6 @@ C33FDC7C255A582000E217F9 /* TSAttachment.m in Sources */, C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */, C38EF3F3255B6DF7007E1867 /* ContactsViewHelper.m in Sources */, - C33FDC24255A581F00E217F9 /* OWSMessageManager.m in Sources */, C33FDC46255A581F00E217F9 /* PublicChatPoller.swift in Sources */, C38EF400255B6DF7007E1867 /* GalleryRailView.swift in Sources */, C38EF32E255B6DBF007E1867 /* ImageCache.swift in Sources */, @@ -5796,6 +5762,7 @@ C38EF3FC255B6DF7007E1867 /* AvatarImageView.swift in Sources */, C33FDD13255A582000E217F9 /* OWSFailedAttachmentDownloadsJob.m in Sources */, C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */, + C3CA3B13255CF18200F4C6D4 /* Destination+Conversion.swift in Sources */, C33FDCB5255A582000E217F9 /* SessionMetaProtocol.swift in Sources */, C33FDC91255A582000E217F9 /* OWSDeviceProvisioner.m in Sources */, C33FDC6A255A582000E217F9 /* ProvisioningProto.swift in Sources */, @@ -5811,7 +5778,6 @@ C38EF310255B6DBF007E1867 /* DebugLogger.m in Sources */, C38EF38B255B6DD2007E1867 /* AttachmentPrepViewController.swift in Sources */, C33FDD98255A582000E217F9 /* LokiPushNotificationManager.swift in Sources */, - C33FDCF8255A582000E217F9 /* OWSMessageSender.m in Sources */, C33FDD70255A582000E217F9 /* DataSource.m in Sources */, C33FDD2F255A582000E217F9 /* AppReadiness.m in Sources */, C33FDCEC255A582000E217F9 /* SSKIncrementingIdFinder.swift in Sources */, @@ -5824,7 +5790,6 @@ C33FDC3B255A581F00E217F9 /* MentionsManager.swift in Sources */, C33FDC49255A581F00E217F9 /* NSTimer+OWS.m in Sources */, C38EF260255B6D6F007E1867 /* OWSContactsManager.m in Sources */, - C33FDD55255A582000E217F9 /* MessageSenderJobQueue.swift in Sources */, C33FDCFD255A582000E217F9 /* YapDatabaseConnection+OWS.m in Sources */, C38EF3BC255B6DE7007E1867 /* ImageEditorPanGestureRecognizer.swift in Sources */, C33FDC23255A581F00E217F9 /* SSKPreferences.swift in Sources */, @@ -5867,7 +5832,6 @@ C33FDD85255A582000E217F9 /* TSInvalidIdentityKeyReceivingErrorMessage.m in Sources */, C33FDD2E255A582000E217F9 /* TSInvalidIdentityKeyErrorMessage.m in Sources */, C33FDDBB255A582000E217F9 /* TSGroupThread.m in Sources */, - C33FDD4A255A582000E217F9 /* OWSMessageDecrypter.m in Sources */, C38EF40C255B6DF7007E1867 /* GradientView.swift in Sources */, C33FDD14255A582000E217F9 /* OWSUDManager.swift in Sources */, C33FDC6B255A582000E217F9 /* OWSStorage.m in Sources */, @@ -5946,16 +5910,15 @@ C33FDCBA255A582000E217F9 /* OWSRequestBuilder.m in Sources */, C33FDC2B255A581F00E217F9 /* OWSReadReceiptManager.m in Sources */, C38EF326255B6DBF007E1867 /* ConversationStyle.swift in Sources */, - C33FDC86255A582000E217F9 /* OWSMessageSend.swift in Sources */, C38EF312255B6DBF007E1867 /* OWSGroupAvatarBuilder.m in Sources */, C38EF3B8255B6DE7007E1867 /* ImageEditorTextViewController.swift in Sources */, C33FDD91255A582000E217F9 /* OWSMessageUtils.m in Sources */, C38EF40B255B6DF7007E1867 /* TappableStackView.swift in Sources */, C33FDCF7255A582000E217F9 /* OWSProfileKeyMessage.m in Sources */, + C3CA3B1D255CF3C800F4C6D4 /* MessageSender+Utilities.swift in Sources */, C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */, C38EF359255B6DCC007E1867 /* SheetViewController.swift in Sources */, C38EF362255B6DCC007E1867 /* ContactShareApprovalViewController.swift in Sources */, - C33FDD86255A582000E217F9 /* MultiDeviceProtocol.swift in Sources */, C33FDCAD255A582000E217F9 /* OWSPrimaryStorage+SessionStore.m in Sources */, C38EF386255B6DD2007E1867 /* AttachmentApprovalInputAccessoryView.swift in Sources */, C33FDC92255A582000E217F9 /* OWSGroupsOutputStream.m in Sources */, @@ -6037,6 +6000,7 @@ C3471F4C25553AB000297E91 /* MessageReceiver+Decryption.swift in Sources */, C3A722802558C4E10043A11F /* AttachmentStream.swift in Sources */, C300A5D32554B05A00555489 /* TypingIndicator.swift in Sources */, + C3CA3B2F255CF84E00F4C6D4 /* NullMessage.swift in Sources */, C3471ECB2555356A00297E91 /* MessageSender+Encryption.swift in Sources */, C352A32F2557549C00338F3E /* NotifyPNServerJob.swift in Sources */, C300A5F22554B09800555489 /* MessageSender.swift in Sources */, diff --git a/SignalUtilitiesKit/ClosedGroupPoller.swift b/SignalUtilitiesKit/ClosedGroupPoller.swift index c9ec1c9b5..0444b8656 100644 --- a/SignalUtilitiesKit/ClosedGroupPoller.swift +++ b/SignalUtilitiesKit/ClosedGroupPoller.swift @@ -27,7 +27,7 @@ public final class ClosedGroupPoller : NSObject { guard !isPolling else { return } isPolling = true timer = Timer.scheduledTimer(withTimeInterval: ClosedGroupPoller.pollInterval, repeats: true) { [weak self] _ in - self?.poll() + let _ = self?.poll() } } @@ -64,7 +64,10 @@ public final class ClosedGroupPoller : NSObject { guard let envelope = SSKProtoEnvelope.from(json) else { return } do { let data = try envelope.serializedData() - SSKEnvironment.shared.messageReceiver.handleReceivedEnvelopeData(data) + let job = MessageReceiveJob(data: data) + Storage.write { transaction in + SessionMessagingKit.JobQueue.shared.add(job, using: transaction) + } } catch { print("[Loki] Failed to deserialize envelope due to error: \(error).") } diff --git a/SignalUtilitiesKit/ClosedGroupsProtocol.swift b/SignalUtilitiesKit/ClosedGroupsProtocol.swift index 0d62b1750..067bdfed6 100644 --- a/SignalUtilitiesKit/ClosedGroupsProtocol.swift +++ b/SignalUtilitiesKit/ClosedGroupsProtocol.swift @@ -37,7 +37,6 @@ public final class ClosedGroupsProtocol : NSObject { public static func createClosedGroup(name: String, members: Set, transaction: YapDatabaseReadWriteTransaction) -> Promise { // Prepare var members = members - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue let userPublicKey = getUserHexEncodedPublicKey() // Generate a key pair for the group let groupKeyPair = Curve25519.generateKeyPair() @@ -73,10 +72,12 @@ public final class ClosedGroupsProtocol : NSObject { guard member != userPublicKey else { continue } let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, groupPrivateKey: groupKeyPair.privateKey, senderKeys: senderKeys, members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - promises.append(SSKEnvironment.shared.messageSender.sendPromise(message: closedGroupUpdateMessage)) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + let promise = MessageSender.sendNonDurably(closedGroupUpdate, in: thread, using: transaction) + promises.append(promise) } // Add the group to the user's set of public keys to poll for Storage.setClosedGroupPrivateKey(groupKeyPair.privateKey.toHexString(), for: groupPublicKey, using: transaction) @@ -92,7 +93,6 @@ public final class ClosedGroupsProtocol : NSObject { /// - Note: The returned promise is only relevant for group leaving. public static func update(_ groupPublicKey: String, with members: Set, name: String, transaction: YapDatabaseReadWriteTransaction) -> Promise { let (promise, seal) = Promise.pending() - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue let userPublicKey = getUserHexEncodedPublicKey() let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { @@ -124,13 +124,14 @@ public final class ClosedGroupsProtocol : NSObject { let promises: [Promise] = oldMembers.map { member in let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: [], + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: [], members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - return SSKEnvironment.shared.messageSender.sendPromise(message: closedGroupUpdateMessage) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + return MessageSender.sendNonDurably(closedGroupUpdate, in: thread, using: transaction) } when(resolved: promises).done2 { _ in seal.fulfill(()) }.catch2 { seal.reject($0) } - promise.done { + let _ = promise.done { Storage.writeSync { transaction in let allOldRatchets = Storage.getAllClosedGroupRatchets(for: groupPublicKey) for (senderPublicKey, oldRatchet) in allOldRatchets { @@ -144,16 +145,17 @@ public final class ClosedGroupsProtocol : NSObject { if isUserLeaving { Storage.removeClosedGroupPrivateKey(for: groupPublicKey, using: transaction) // Notify the PN server - LokiPushNotificationManager.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) + let _ = LokiPushNotificationManager.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) } else { // Send closed group update messages to any new members using established channels for member in newMembers { let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [], members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) } // Send out the user's new ratchet to all members (minus the removed ones) using established channels let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) @@ -162,9 +164,10 @@ public final class ClosedGroupsProtocol : NSObject { guard member != userPublicKey else { continue } let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) } } } @@ -177,10 +180,11 @@ public final class ClosedGroupsProtocol : NSObject { return ClosedGroupSenderKey(chainKey: Data(hex: ratchet.chainKey), keyIndex: ratchet.keyIndex, publicKey: Data(hex: publicKey)) } // Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: newSenderKeys, + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: newSenderKeys, members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) // Establish sessions if needed establishSessionsIfNeeded(with: [String](newMembers), using: transaction) // Send closed group update messages to the new members using established channels @@ -189,18 +193,20 @@ public final class ClosedGroupsProtocol : NSObject { for member in newMembers { let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.new(groupPublicKey: Data(hex: groupPublicKey), name: name, groupPrivateKey: Data(hex: groupPrivateKey), senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) } } else { seal.fulfill(()) let allSenderKeys = Storage.getAllClosedGroupSenderKeys(for: groupPublicKey) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.info(groupPublicKey: Data(hex: groupPublicKey), name: name, senderKeys: [ClosedGroupSenderKey](allSenderKeys), members: membersAsData, admins: adminsAsData) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) } // Update the group let newGroupModel = TSGroupModel(title: name, memberIds: [String](members), image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) @@ -240,10 +246,10 @@ public final class ClosedGroupsProtocol : NSObject { // Send the request let thread = TSContactThread.getOrCreateThread(withContactId: senderPublicKey, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKeyRequest(groupPublicKey: Data(hex: groupPublicKey)) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.senderKeyRequest(groupPublicKey: Data(hex: groupPublicKey)) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) } // MARK: - Receiving @@ -273,7 +279,6 @@ public final class ClosedGroupsProtocol : NSObject { } private static func handleNewGroupMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, using transaction: YapDatabaseReadWriteTransaction) { - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue // Unwrap the message let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() let name = closedGroupUpdate.name @@ -298,9 +303,10 @@ public final class ClosedGroupsProtocol : NSObject { guard member != userPublicKey else { continue } let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) } } for publicKey in missingSenderKeys.subtracting([ userPublicKey ]) { @@ -322,7 +328,7 @@ public final class ClosedGroupsProtocol : NSObject { // Add the group to the user's set of public keys to poll for Storage.setClosedGroupPrivateKey(groupPrivateKey.toHexString(), for: groupPublicKey, using: transaction) // Notify the PN server - LokiPushNotificationManager.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey()) + let _ = LokiPushNotificationManager.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey()) // Notify the user let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate) infoMessage.save(with: transaction) @@ -335,7 +341,6 @@ public final class ClosedGroupsProtocol : NSObject { private static func handleInfoMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, from senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { // Unwrap the message - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() let name = closedGroupUpdate.name let senderKeys = closedGroupUpdate.senderKeys @@ -377,7 +382,7 @@ public final class ClosedGroupsProtocol : NSObject { if wasUserRemoved { Storage.removeClosedGroupPrivateKey(for: groupPublicKey, using: transaction) // Notify the PN server - LokiPushNotificationManager.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) + let _ = LokiPushNotificationManager.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) } else { establishSessionsIfNeeded(with: members, using: transaction) // This internally takes care of multi device let userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) @@ -386,9 +391,10 @@ public final class ClosedGroupsProtocol : NSObject { guard member != userPublicKey else { continue } let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) } } } @@ -406,7 +412,6 @@ public final class ClosedGroupsProtocol : NSObject { private static func handleSenderKeyRequestMessage(_ closedGroupUpdate: SSKProtoDataMessageClosedGroupUpdate, from senderPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) { // Prepare - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue let userPublicKey = getUserHexEncodedPublicKey() let groupPublicKey = closedGroupUpdate.groupPublicKey.toHexString() let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) @@ -431,9 +436,10 @@ public final class ClosedGroupsProtocol : NSObject { let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) let thread = TSContactThread.getOrCreateThread(withContactId: senderPublicKey, transaction: transaction) thread.save(with: transaction) - let closedGroupUpdateMessageKind = ClosedGroupUpdateMessage.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) - let closedGroupUpdateMessage = ClosedGroupUpdateMessage(thread: thread, kind: closedGroupUpdateMessageKind) - messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) } /// Invoked upon receiving a sender key from another user. diff --git a/SignalUtilitiesKit/MessageSender+Promise.swift b/SignalUtilitiesKit/MessageSender+Promise.swift deleted file mode 100644 index ac426fbb8..000000000 --- a/SignalUtilitiesKit/MessageSender+Promise.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import PromiseKit - -public extension MessageSender { - - /** - * Wrap message sending in a Promise for easier callback chaining. - */ - func sendPromise(message: TSOutgoingMessage) -> Promise { - let promise: Promise = Promise { resolver in - self.send(message, success: { resolver.fulfill(()) }, failure: resolver.reject) - } - - // Ensure sends complete before they're GC'd. - // This *should* be redundant, since we should be calling retainUntilComplete - // at all call sites where the promise isn't otherwise retained. - promise.retainUntilComplete() - - return promise - } -} diff --git a/SignalUtilitiesKit/MessageSenderJobQueue.swift b/SignalUtilitiesKit/MessageSenderJobQueue.swift deleted file mode 100644 index c6136f0bd..000000000 --- a/SignalUtilitiesKit/MessageSenderJobQueue.swift +++ /dev/null @@ -1,256 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -/// Durably enqueues a message for sending. -/// -/// The queue's operations (`MessageSenderOperation`) uses `MessageSender` to send a message. -/// -/// ## Retry behavior -/// -/// Like all JobQueue's, MessageSenderJobQueue implements retry handling for operation errors. -/// -/// `MessageSender` also includes it's own retry logic necessary to encapsulate business logic around -/// a user changing their Registration ID, or adding/removing devices. That is, it is sometimes *normal* -/// for MessageSender to have to resend to a recipient multiple times before it is accepted, and doesn't -/// represent a "failure" from the application standpoint. -/// -/// So we have an inner non-durable retry (MessageSender) and an outer durable retry (MessageSenderJobQueue). -/// -/// Both respect the `error.isRetryable` convention to be sure we don't keep retrying in some situations -/// (e.g. rate limiting) - -@objc(SSKMessageSenderJobQueue) -public class MessageSenderJobQueue: NSObject, JobQueue { - - @objc - public override init() { - super.init() - - AppReadiness.runNowOrWhenAppWillBecomeReady { - self.setup() - } - } - - @objc(addMessage:transaction:) - public func add(message: TSOutgoingMessage, transaction: YapDatabaseReadWriteTransaction) { - self.add(message: message, removeMessageAfterSending: false, transaction: transaction) - } - - @objc(addMediaMessage:dataSource:contentType:sourceFilename:caption:albumMessageId:isTemporaryAttachment:) - public func add(mediaMessage: TSOutgoingMessage, dataSource: DataSource, contentType: String, sourceFilename: String?, caption: String?, albumMessageId: String?, isTemporaryAttachment: Bool) { - let attachmentInfo = OutgoingAttachmentInfo(dataSource: dataSource, contentType: contentType, sourceFilename: sourceFilename, caption: caption, albumMessageId: albumMessageId) - add(mediaMessage: mediaMessage, attachmentInfos: [attachmentInfo], isTemporaryAttachment: isTemporaryAttachment) - } - - @objc(addMediaMessage:attachmentInfos:isTemporaryAttachment:) - public func add(mediaMessage: TSOutgoingMessage, attachmentInfos: [OutgoingAttachmentInfo], isTemporaryAttachment: Bool) { - OutgoingMessagePreparer.prepareAttachments(attachmentInfos, - inMessage: mediaMessage, - completionHandler: { error in - if let error = error { - Storage.writeSync { transaction in - mediaMessage.update(sendingError: error, transaction: transaction) - } - } else { - Storage.writeSync { transaction in - self.add(message: mediaMessage, removeMessageAfterSending: isTemporaryAttachment, transaction: transaction) - } - } - }) - } - - private func add(message: TSOutgoingMessage, removeMessageAfterSending: Bool, transaction: YapDatabaseReadWriteTransaction) { - assert(AppReadiness.isAppReady() || CurrentAppContext().isRunningTests) - - let jobRecord: SSKMessageSenderJobRecord - do { - jobRecord = try SSKMessageSenderJobRecord(message: message, removeMessageAfterSending: false, label: self.jobRecordLabel) - } catch { - owsFailDebug("Failed to build job due to error: \(error).") - return - } - self.add(jobRecord: jobRecord, transaction: transaction) - } - - // MARK: JobQueue - - public typealias DurableOperationType = MessageSenderOperation - public static let jobRecordLabel: String = "MessageSender" - public static let maxRetries: UInt = 1 // Loki: We have our own retrying - public let requiresInternet: Bool = true - public var runningOperations: [MessageSenderOperation] = [] - - public var jobRecordLabel: String { - return type(of: self).jobRecordLabel - } - - @objc - public func setup() { - defaultSetup() - } - - public var isSetup: Bool = false - - /// Used when the user clears their database to cancel any outstanding jobs. - @objc public func clearAllJobs() { - Storage.writeSync { transaction in - let statuses: [SSKJobRecordStatus] = [ .unknown, .ready, .running, .permanentlyFailed ] - var records: [SSKJobRecord] = [] - statuses.forEach { - records += self.finder.allRecords(label: self.jobRecordLabel, status: $0, transaction: transaction) - } - records.forEach { $0.remove(with: transaction) } - } - } - - public func didMarkAsReady(oldJobRecord: SSKMessageSenderJobRecord, transaction: YapDatabaseReadWriteTransaction) { - if let messageId = oldJobRecord.messageId, let message = TSOutgoingMessage.fetch(uniqueId: messageId, transaction: transaction) { - message.updateWithMarkingAllUnsentRecipientsAsSending(with: transaction) - } - } - - public func buildOperation(jobRecord: SSKMessageSenderJobRecord, transaction: YapDatabaseReadTransaction) throws -> MessageSenderOperation { - let message: TSOutgoingMessage - if let invisibleMessage = jobRecord.invisibleMessage { - message = invisibleMessage - } else if let messageId = jobRecord.messageId, let fetchedMessage = TSOutgoingMessage.fetch(uniqueId: messageId, transaction: transaction) { - message = fetchedMessage - } else { - assert(jobRecord.messageId != nil) - throw JobError.obsolete(description: "Message no longer exists.") - } - - return MessageSenderOperation(message: message, jobRecord: jobRecord) - } - - var senderQueues: [String: OperationQueue] = [:] - let defaultQueue: OperationQueue = { - let operationQueue = OperationQueue() - operationQueue.name = "DefaultSendingQueue" - operationQueue.maxConcurrentOperationCount = 1 - operationQueue.qualityOfService = .userInitiated - - return operationQueue - }() - - // We use a per-thread serial OperationQueue to ensure messages are delivered to the - // service in the order the user sent them. - public func operationQueue(jobRecord: SSKMessageSenderJobRecord) -> OperationQueue { - guard let threadId = jobRecord.threadId else { - return defaultQueue - } - - guard let existingQueue = senderQueues[threadId] else { - let operationQueue = OperationQueue() - operationQueue.name = "SendingQueue:\(threadId)" - operationQueue.maxConcurrentOperationCount = 1 - operationQueue.qualityOfService = .userInitiated - - senderQueues[threadId] = operationQueue - - return operationQueue - } - - return existingQueue - } -} - -public class MessageSenderOperation: OWSOperation, DurableOperation { - - // MARK: DurableOperation - - public let jobRecord: SSKMessageSenderJobRecord - - weak public var durableOperationDelegate: MessageSenderJobQueue? - - public var operation: OWSOperation { - return self - } - - // MARK: Init - - let message: TSOutgoingMessage - - init(message: TSOutgoingMessage, jobRecord: SSKMessageSenderJobRecord) { - self.message = message - self.jobRecord = jobRecord - super.init() - } - - // MARK: Dependencies - - var messageSender: MessageSender { - return SSKEnvironment.shared.messageSender - } - - // MARK: OWSOperation - - override public func run() { - self.messageSender.send(message, success: reportSuccess, failure: reportError) - } - - override public func didSucceed() { - Storage.writeSync { transaction in - self.durableOperationDelegate?.durableOperationDidSucceed(self, transaction: transaction) - - if self.jobRecord.removeMessageAfterSending { - self.message.remove(with: transaction) - } - } - } - - override public func didReportError(_ error: Error) { - let message = self.message - var isFailedSessionRequest = false - if message is SessionRequestMessage, let publicKey = message.thread.contactIdentifier() { - isFailedSessionRequest = (Storage.getSessionRequestSentTimestamp(for: publicKey) == message.timestamp) - } - Storage.writeSync { transaction in - if isFailedSessionRequest, let publicKey = message.thread.contactIdentifier() { - Storage.setSessionRequestSentTimestamp(for: publicKey, to: 0, using: transaction) - } - - self.durableOperationDelegate?.durableOperation(self, didReportError: error, transaction: transaction) - } - } - - override public func retryInterval() -> TimeInterval { - // Arbitrary backoff factor... - // With backOffFactor of 1.9 - // try 1 delay: 0.00s - // try 2 delay: 0.19s - // ... - // try 5 delay: 1.30s - // ... - // try 11 delay: 61.31s - let backoffFactor = 1.9 - let maxBackoff = 15 * kMinuteInterval - - let seconds = 0.1 * min(maxBackoff, pow(backoffFactor, Double(self.jobRecord.failureCount))) - return seconds - } - - override public func didFail(error: Error) { - let message = self.message - var isFailedSessionRequest = false - if message is SessionRequestMessage, let publicKey = message.thread.contactIdentifier() { - isFailedSessionRequest = (Storage.getSessionRequestSentTimestamp(for: publicKey) == message.timestamp) - } - Storage.writeSync { transaction in - if isFailedSessionRequest, let publicKey = message.thread.contactIdentifier() { - Storage.setSessionRequestSentTimestamp(for: publicKey, to: 0, using: transaction) - } - - self.durableOperationDelegate?.durableOperation(self, didFailWithError: error, transaction: transaction) - - self.message.update(sendingError: error, transaction: transaction) - - if self.jobRecord.removeMessageAfterSending { - self.message.remove(with: transaction) - } - } - } -} diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h index d6add33f3..309c405a0 100644 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h +++ b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h @@ -64,10 +64,6 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; #import #import #import -#import -#import -#import -#import #import #import #import diff --git a/SignalUtilitiesKit/MultiDeviceProtocol.swift b/SignalUtilitiesKit/MultiDeviceProtocol.swift deleted file mode 100644 index 64a283729..000000000 --- a/SignalUtilitiesKit/MultiDeviceProtocol.swift +++ /dev/null @@ -1,274 +0,0 @@ -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. -// • For write transactions, consider making it the caller's responsibility to manage the database transaction (this helps avoid unnecessary transactions). -// • Think carefully about adding a function; there might already be one for what you need. -// • Document the expected cases in which a function will be used -// • Express those cases in tests. - -@objc(LKMultiDeviceProtocol) -public final class MultiDeviceProtocol : NSObject { - - /// A mapping from hex encoded public key to date updated. - /// - /// - Note: Should only be accessed from `LokiAPI.workQueue` to avoid race conditions. - public static var lastDeviceLinkUpdate: [String:Date] = [:] - - internal static var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() } - - // MARK: Settings - public static let deviceLinkUpdateInterval: TimeInterval = 60 - - // MARK: Multi Device Destination - public struct MultiDeviceDestination : Hashable { - public let publicKey: String - public let isMaster: Bool - } - - // MARK: - General - - @objc(isUnlinkDeviceMessage:) - public static func isUnlinkDeviceMessage(_ dataMessage: SSKProtoDataMessage) -> Bool { - let unlinkDeviceFlag = SSKProtoDataMessage.SSKProtoDataMessageFlags.unlinkDevice - return dataMessage.flags & UInt32(unlinkDeviceFlag.rawValue) != 0 - } - - public static func getUserLinkedDevices() -> Set { - var result: Set = [] - storage.dbReadConnection.read { transaction in - result = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: getUserHexEncodedPublicKey(), in: transaction) - } - return result - } - - @objc public static func isSlaveThread(_ thread: TSThread) -> Bool { - guard let thread = thread as? TSContactThread else { return false } - var isSlaveThread = false - storage.dbReadConnection.read { transaction in - isSlaveThread = storage.getMasterHexEncodedPublicKey(for: thread.contactIdentifier(), in: transaction) != nil - } - return isSlaveThread - } - - // MARK: - Sending (Part 1) - - @objc(isMultiDeviceRequiredForMessage:toPublicKey:) - public static func isMultiDeviceRequired(for message: TSOutgoingMessage, to publicKey: String) -> Bool { - return !(message is DeviceLinkMessage) && !(message is UnlinkDeviceMessage) && (message.thread as? TSGroupThread)?.groupModel.groupType != .openGroup - && !Storage.getUserClosedGroupPublicKeys().contains(publicKey) - } - - private static func copy(_ messageSend: OWSMessageSend, for destination: MultiDeviceDestination, with seal: Resolver) -> OWSMessageSend { - var recipient: SignalRecipient! - storage.dbReadConnection.read { transaction in - recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: destination.publicKey, transaction: transaction) - } - // TODO: Why is it okay that the thread, sender certificate, etc. don't get changed? - return OWSMessageSend(message: messageSend.message, thread: messageSend.thread, recipient: recipient, - senderCertificate: messageSend.senderCertificate, udAccess: messageSend.udAccess, localNumber: messageSend.localNumber, success: { - seal.fulfill(()) - }, failure: { error in - seal.reject(error) - }) - } - - private static func sendMessage(_ messageSend: OWSMessageSend, to destination: MultiDeviceDestination, in transaction: YapDatabaseReadTransaction) -> Promise { - let (threadPromise, threadPromiseSeal) = Promise.pending() - if messageSend.message.thread.isGroupThread() { - threadPromiseSeal.fulfill(messageSend.message.thread) - } else if let thread = TSContactThread.getWithContactId(destination.publicKey, transaction: transaction) { - threadPromiseSeal.fulfill(thread) - } else { - Storage.write { transaction in - let thread = TSContactThread.getOrCreateThread(withContactId: destination.publicKey, transaction: transaction) - threadPromiseSeal.fulfill(thread) - } - } - return threadPromise.then2 { thread -> Promise in - let message = messageSend.message - let messageSender = SSKEnvironment.shared.messageSender - let (promise, seal) = Promise.pending() - let messageSendCopy = copy(messageSend, for: destination, with: seal) - OWSDispatch.sendingQueue().async { - messageSender.sendMessage(messageSendCopy) - } - return promise - } - } - - /// See [Multi Device Message Sending](https://github.com/loki-project/session-protocol-docs/wiki/Multi-Device-Message-Sending) for more information. - @objc(sendMessageToDestinationAndLinkedDevices:transaction:) - public static func sendMessageToDestinationAndLinkedDevices(_ messageSend: OWSMessageSend, in transaction: YapDatabaseReadTransaction) { -// if !messageSend.isUDSend && messageSend.recipient.recipientId() != getUserHexEncodedPublicKey() { -// #if DEBUG -// preconditionFailure() -// #endif -// } - let message = messageSend.message - let messageSender = SSKEnvironment.shared.messageSender - if !isMultiDeviceRequired(for: message, to: messageSend.recipient.recipientId()) { - print("[Loki] sendMessageToDestinationAndLinkedDevices(_:in:) invoked for a message that doesn't require multi device routing.") - OWSDispatch.sendingQueue().async { - messageSender.sendMessage(messageSend) - } - return - } - print("[Loki] Sending \(type(of: message)) message using multi device routing.") - let publicKey = messageSend.recipient.recipientId() - getMultiDeviceDestinations(for: publicKey, in: transaction).done2 { destinations in - var promises: [Promise] = [] - let masterDestination = destinations.first { $0.isMaster } - if let masterDestination = masterDestination { - storage.dbReadConnection.read { transaction in - promises.append(sendMessage(messageSend, to: masterDestination, in: transaction)) - } - } - let slaveDestinations = destinations.filter { !$0.isMaster } - slaveDestinations.forEach { slaveDestination in - storage.dbReadConnection.read { transaction in - promises.append(sendMessage(messageSend, to: slaveDestination, in: transaction)) - } - } - when(resolved: promises).done(on: OWSDispatch.sendingQueue()) { results in - let errors = results.compactMap { result -> Error? in - if case PromiseKit.Result.rejected(let error) = result { - return error - } else { - return nil - } - } - if errors.isEmpty { - messageSend.success() - } else { - messageSend.failure(errors.first!) - } - } - }.catch2 { error in - // Proceed even if updating the recipient's device links failed, so that message sending - // is independent of whether the file server is online - OWSDispatch.sendingQueue().async { - messageSender.sendMessage(messageSend) - } - } - } - - @objc(updateDeviceLinksIfNeededForPublicKey:transaction:) - public static func updateDeviceLinksIfNeeded(for publicKey: String, in transaction: YapDatabaseReadTransaction) -> AnyPromise { - return AnyPromise.from(getMultiDeviceDestinations(for: publicKey, in: transaction)) - } - - // MARK: - Receiving - - @objc(handleDeviceLinkMessageIfNeeded:wrappedIn:transaction:) - public static func handleDeviceLinkMessageIfNeeded(_ protoContent: SSKProtoContent, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = envelope.source! // Set during UD decryption - guard let deviceLinkMessage = protoContent.lokiDeviceLinkMessage, let master = deviceLinkMessage.masterPublicKey, - let slave = deviceLinkMessage.slavePublicKey, let slaveSignature = deviceLinkMessage.slaveSignature else { - return print("[Loki] Received an invalid device link message.") - } - let deviceLinkingSession = DeviceLinkingSession.current - if let masterSignature = deviceLinkMessage.masterSignature { // Authorization - print("[Loki] Received a device link authorization from: \(publicKey).") // Intentionally not `master` - if let deviceLinkingSession = deviceLinkingSession { - deviceLinkingSession.processLinkingAuthorization(from: master, for: slave, masterSignature: masterSignature, slaveSignature: slaveSignature) - } else { - print("[Loki] Received a device link authorization without a session; ignoring.") - } - // Set any profile info (the device link authorization also includes the master device's profile info) - if let dataMessage = protoContent.dataMessage { - SessionMetaProtocol.updateDisplayNameIfNeeded(for: master, using: dataMessage, in: transaction) - SessionMetaProtocol.updateProfileKeyIfNeeded(for: master, using: dataMessage) - } - } else { // Request - print("[Loki] Received a device link request from: \(publicKey).") // Intentionally not `slave` - if let deviceLinkingSession = deviceLinkingSession { - deviceLinkingSession.processLinkingRequest(from: slave, to: master, with: slaveSignature) - } else { - NotificationCenter.default.post(name: .unexpectedDeviceLinkRequestReceived, object: nil) - } - } - } - - @objc(handleUnlinkDeviceMessage:wrappedIn:transaction:) - public static func handleUnlinkDeviceMessage(_ dataMessage: SSKProtoDataMessage, wrappedIn envelope: SSKProtoEnvelope, using transaction: YapDatabaseReadWriteTransaction) { - let publicKey = envelope.source! // Set during UD decryption - // Check that the request was sent by our master device - let userPublicKey = getUserHexEncodedPublicKey() - guard let userMasterPublicKey = storage.getMasterHexEncodedPublicKey(for: userPublicKey, in: transaction) else { return } - let wasSentByMasterDevice = (userMasterPublicKey == publicKey) - guard wasSentByMasterDevice else { return } - // Ignore the request if we don't know about the device link in question - let masterDeviceLinks = storage.getDeviceLinks(for: userMasterPublicKey, in: transaction) - if !masterDeviceLinks.contains(where: { - $0.master.publicKey == userMasterPublicKey && $0.slave.publicKey == userPublicKey - }) { - return - } - FileServerAPI.getDeviceLinks(associatedWith: userPublicKey).done2 { slaveDeviceLinks in - // Check that the device link IS present on the file server. - // Note that the device link as seen from the master device's perspective has been deleted at this point, but the - // device link as seen from the slave perspective hasn't. - if slaveDeviceLinks.contains(where: { - $0.master.publicKey == userMasterPublicKey && $0.slave.publicKey == userPublicKey - }) { - for deviceLink in slaveDeviceLinks { // In theory there should only be one - FileServerAPI.removeDeviceLink(deviceLink) // Attempt to clean up on the file server - } - UserDefaults.standard[.wasUnlinked] = true - DispatchQueue.main.async { - NotificationCenter.default.post(name: .dataNukeRequested, object: nil) - } - } - } - } -} - -// MARK: - Sending (Part 2) - -// Here (in a non-@objc extension) because it doesn't interoperate well with Obj-C -public extension MultiDeviceProtocol { - - fileprivate static func getMultiDeviceDestinations(for publicKey: String, in transaction: YapDatabaseReadTransaction) -> Promise> { - let (promise, seal) = Promise>.pending() - func getDestinations(in transaction: YapDatabaseReadTransaction? = nil) { - storage.dbReadConnection.read { transaction in - var destinations: Set = [] - let masterPublicKey = storage.getMasterHexEncodedPublicKey(for: publicKey, in: transaction) ?? publicKey - let masterDestination = MultiDeviceDestination(publicKey: masterPublicKey, isMaster: true) - destinations.insert(masterDestination) - let deviceLinks = storage.getDeviceLinks(for: masterPublicKey, in: transaction) - let slaveDestinations = deviceLinks.map { MultiDeviceDestination(publicKey: $0.slave.publicKey, isMaster: false) } - destinations.formUnion(slaveDestinations) - seal.fulfill(destinations) - } - } - let timeSinceLastUpdate: TimeInterval - if let lastDeviceLinkUpdate = lastDeviceLinkUpdate[publicKey] { - timeSinceLastUpdate = Date().timeIntervalSince(lastDeviceLinkUpdate) - } else { - timeSinceLastUpdate = .infinity - } - if timeSinceLastUpdate > deviceLinkUpdateInterval { - let masterPublicKey = storage.getMasterHexEncodedPublicKey(for: publicKey, in: transaction) ?? publicKey - FileServerAPI.getDeviceLinks(associatedWith: masterPublicKey).done2 { _ in - getDestinations() - lastDeviceLinkUpdate[publicKey] = Date() - }.catch2 { error in - if (error as? DotNetAPI.Error) == DotNetAPI.Error.parsingFailed { - // Don't immediately re-fetch in case of failure due to a parsing error - lastDeviceLinkUpdate[publicKey] = Date() - getDestinations() - } else { - print("[Loki] Failed to get device links due to error: \(error).") - seal.reject(error) - } - } - } else { - getDestinations() - } - return promise - } -} diff --git a/SignalUtilitiesKit/OWSMessageDecrypter.h b/SignalUtilitiesKit/OWSMessageDecrypter.h deleted file mode 100644 index 57cf87277..000000000 --- a/SignalUtilitiesKit/OWSMessageDecrypter.h +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class SSKProtoEnvelope; -@class YapDatabaseReadWriteTransaction; - -@interface OWSMessageDecryptResult : NSObject - -@property (nonatomic, readonly) NSData *envelopeData; -@property (nonatomic, readonly, nullable) NSData *plaintextData; -@property (nonatomic, readonly) NSString *source; -@property (nonatomic, readonly) UInt32 sourceDevice; -@property (nonatomic, readonly) BOOL isUDMessage; - -@end - -#pragma mark - - -// Decryption result includes the envelope since the envelope -// may be altered by the decryption process. -typedef void (^DecryptSuccessBlock)(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction); -typedef void (^DecryptFailureBlock)(void); - -@interface OWSMessageDecrypter : OWSMessageHandler - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -// decryptEnvelope: can be called from any thread. -// successBlock & failureBlock will be called an arbitrary thread. -// -// Exactly one of successBlock & failureBlock will be called, -// once. -- (void)decryptEnvelope:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(DecryptFailureBlock)failureBlock; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/OWSMessageDecrypter.m b/SignalUtilitiesKit/OWSMessageDecrypter.m deleted file mode 100644 index 968fadf57..000000000 --- a/SignalUtilitiesKit/OWSMessageDecrypter.m +++ /dev/null @@ -1,686 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageDecrypter.h" -#import "NSData+messagePadding.h" -#import "NSString+SSK.h" -#import "NotificationsProtocol.h" -#import "OWSAnalytics.h" -#import "OWSBlockingManager.h" -#import "OWSDevice.h" -#import "OWSError.h" -#import "OWSIdentityManager.h" -#import "OWSPrimaryStorage+PreKeyStore.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "OWSPrimaryStorage.h" -#import "SSKEnvironment.h" -#import "SignalRecipient.h" -#import "TSAccountManager.h" -#import "TSContactThread.h" -#import "TSErrorMessage.h" -#import "TSPreKeyManager.h" -#import -#import -#import -#import -#import -#import -#import -#import "SSKAsserts.h" - -NS_ASSUME_NONNULL_BEGIN - -NSError *EnsureDecryptError(NSError *_Nullable error, NSString *fallbackErrorDescription) -{ - if (error) { - return error; - } - OWSCFailDebug(@"Caller should provide specific error"); - return OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, fallbackErrorDescription); -} - -#pragma mark - - -@interface OWSMessageDecryptResult () - -@property (nonatomic) NSData *envelopeData; -@property (nonatomic, nullable) NSData *plaintextData; -@property (nonatomic) NSString *source; -@property (nonatomic) UInt32 sourceDevice; -@property (nonatomic) BOOL isUDMessage; - -@end - -#pragma mark - - -@implementation OWSMessageDecryptResult - -+ (OWSMessageDecryptResult *)resultWithEnvelopeData:(NSData *)envelopeData - plaintextData:(nullable NSData *)plaintextData - source:(NSString *)source - sourceDevice:(UInt32)sourceDevice - isUDMessage:(BOOL)isUDMessage -{ - OWSAssertDebug(envelopeData); - OWSAssertDebug(source.length > 0); - OWSAssertDebug(sourceDevice > 0); - - OWSMessageDecryptResult *result = [OWSMessageDecryptResult new]; - result.envelopeData = envelopeData; - result.plaintextData = plaintextData; - result.source = source; - result.sourceDevice = sourceDevice; - result.isUDMessage = isUDMessage; - return result; -} - -@end - -#pragma mark - - -@interface OWSMessageDecrypter () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; -@property (nonatomic, readonly) SNSessionRestorationImplementation *sessionResetImplementation; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSMessageDecrypter - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - _sessionResetImplementation = [SNSessionRestorationImplementation new]; - _dbConnection = primaryStorage.newDatabaseConnection; - - OWSSingletonAssert(); - - return self; -} - -#pragma mark - Dependencies - -- (OWSBlockingManager *)blockingManager -{ - OWSAssertDebug(SSKEnvironment.shared.blockingManager); - - return SSKEnvironment.shared.blockingManager; -} - -- (OWSIdentityManager *)identityManager -{ - OWSAssertDebug(SSKEnvironment.shared.identityManager); - - return SSKEnvironment.shared.identityManager; -} - -- (id)udManager -{ - OWSAssertDebug(SSKEnvironment.shared.udManager); - - return SSKEnvironment.shared.udManager; -} - -- (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -#pragma mark - Blocking - -- (BOOL)isEnvelopeSenderBlocked:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(envelope); - - return [self.blockingManager.blockedPhoneNumbers containsObject:envelope.source]; -} - -#pragma mark - Decryption - -- (void)decryptEnvelope:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - successBlock:(DecryptSuccessBlock)successBlockParameter - failureBlock:(DecryptFailureBlock)failureBlockParameter -{ - OWSAssertDebug(envelope); - OWSAssertDebug(envelopeData); - OWSAssertDebug(successBlockParameter); - OWSAssertDebug(failureBlockParameter); - OWSAssertDebug([self.tsAccountManager isRegistered]); - - // successBlock is called synchronously so that we can avail ourselves of - // the transaction. - // - // Ensure that failureBlock is called on a worker queue. - DecryptFailureBlock failureBlock = ^() { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - failureBlockParameter(); - }); - }; - - NSString *localRecipientId = self.tsAccountManager.localNumber; - uint32_t localDeviceId = OWSDevicePrimaryDeviceId; - DecryptSuccessBlock successBlock = ^( - OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - // Ensure all blocked messages are discarded. - if ([self isEnvelopeSenderBlocked:envelope]) { - OWSLogInfo(@"Ignoring blocked envelope from: %@.", envelope.source); - return failureBlock(); - } - - if ([result.source isEqualToString:localRecipientId] && result.sourceDevice == localDeviceId) { - // Self-sent messages should be discarded during the decryption process. - OWSFailDebug(@"Unexpected self-sent sync message."); - return failureBlock(); - } - - // Having received a valid (decryptable) message from this user, - // make note of the fact that they have a valid Signal account. - [SignalRecipient markRecipientAsRegistered:result.source deviceId:result.sourceDevice transaction:transaction]; - - successBlockParameter(result, transaction); - }; - - @try { - OWSLogInfo(@"Decrypting envelope: %@.", [self descriptionForEnvelope:envelope]); - - if (envelope.type != SSKProtoEnvelopeTypeUnidentifiedSender) { - if (!envelope.hasSource || envelope.source.length < 1 || ![ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source]) { - OWSFailDebug(@"Incoming envelope with invalid source."); - return failureBlock(); - } - if (!envelope.hasSourceDevice || envelope.sourceDevice < 1) { - OWSFailDebug(@"Incoming envelope with invalid source device."); - return failureBlock(); - } - - // We block UD messages later, after they are decrypted. - if ([self isEnvelopeSenderBlocked:envelope]) { - OWSLogInfo(@"Ignoring blocked envelope from: %@.", envelope.source); - return failureBlock(); - } - } - - switch (envelope.type) { - case SSKProtoEnvelopeTypeCiphertext: { - [self throws_decryptSecureMessage:envelope - envelopeData:envelopeData - successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - OWSLogDebug(@"Decrypted secure message."); - successBlock(result, transaction); - } - failureBlock:^(NSError *_Nullable error) { - OWSLogError(@"Decrypting secure message from: %@ failed with error: %@.", - envelopeAddress(envelope), - error); - OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandleSecureMessage]); - failureBlock(); - }]; - // Return to avoid double-acknowledging. - return; - } - case SSKProtoEnvelopeTypePrekeyBundle: { - [self throws_decryptPreKeyBundle:envelope - envelopeData:envelopeData - successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - OWSLogDebug(@"Decrypted pre key bundle message."); - successBlock(result, transaction); - } - failureBlock:^(NSError *_Nullable error) { - OWSLogError(@"Decrypting pre key bundle message from: %@ failed with error: %@.", - envelopeAddress(envelope), - error); - OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandlePrekeyBundle]); - failureBlock(); - }]; - // Return to avoid double-acknowledging. - return; - } - // These message types don't have a payload to decrypt. - case SSKProtoEnvelopeTypeReceipt: - case SSKProtoEnvelopeTypeKeyExchange: - case SSKProtoEnvelopeTypeUnknown: { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - OWSMessageDecryptResult *result = - [OWSMessageDecryptResult resultWithEnvelopeData:envelopeData - plaintextData:nil - source:envelope.source - sourceDevice:envelope.sourceDevice - isUDMessage:NO]; - successBlock(result, transaction); - }]; - // Return to avoid double-acknowledging. - return; - } - case SSKProtoEnvelopeTypeClosedGroupCiphertext: { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - NSError *error = nil; - NSArray *plaintextAndSenderPublicKey = [LKClosedGroupUtilities decryptEnvelope:envelope transaction:transaction error:&error]; - if (error != nil) { return failureBlock(); } - NSData *plaintext = plaintextAndSenderPublicKey[0]; - NSString *senderPublicKey = plaintextAndSenderPublicKey[1]; - SSKProtoEnvelopeBuilder *newEnvelope = [envelope asBuilder]; - [newEnvelope setSource:senderPublicKey]; - NSData *newEnvelopeAsData = [newEnvelope buildSerializedDataAndReturnError:&error]; - if (error != nil) { return failureBlock(); } - NSString *userPublicKey = [OWSIdentityManager.sharedManager.identityKeyPair hexEncodedPublicKey]; - if ([senderPublicKey isEqual:userPublicKey]) { return failureBlock(); } - OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:newEnvelopeAsData - plaintextData:[plaintext removePadding] - source:senderPublicKey - sourceDevice:OWSDevicePrimaryDeviceId - isUDMessage:NO]; - successBlock(result, transaction); - }]; - return; - } - case SSKProtoEnvelopeTypeUnidentifiedSender: { - [self decryptUnidentifiedSender:envelope - successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - OWSLogDebug(@"Decrypted unidentified sender message."); - successBlock(result, transaction); - } - failureBlock:^(NSError *_Nullable error) { - OWSLogError(@"Decrypting unidentified sender message from: %@ failed with error: %@.", - envelopeAddress(envelope), - error); - OWSProdError([OWSAnalyticsEvents messageManagerErrorCouldNotHandleUnidentifiedSenderMessage]); - failureBlock(); - }]; - // Return to avoid double-acknowledging. - return; - } - default: - OWSLogWarn(@"Received unhandled envelope type: %d.", (int)envelope.type); - break; - } - } @catch (NSException *exception) { - OWSFailDebug(@"Received an invalid envelope: %@.", exception.debugDescription); - OWSProdFail([OWSAnalyticsEvents messageManagerErrorInvalidProtocolMessage]); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; - [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage - transaction:transaction]; - }]; - } - - failureBlock(); -} - -- (void)throws_decryptSecureMessage:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(void (^)(NSError *_Nullable error))failureBlock -{ - OWSAssertDebug(envelope); - OWSAssertDebug(envelopeData); - OWSAssertDebug(successBlock); - OWSAssertDebug(failureBlock); - - [self decryptEnvelope:envelope - envelopeData:envelopeData - cipherTypeName:@"Secure Message" - cipherMessageBlock:^(NSData *encryptedData) { - return [[WhisperMessage alloc] init_throws_withData:encryptedData]; - } - successBlock:successBlock - failureBlock:failureBlock]; -} - -- (void)throws_decryptPreKeyBundle:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(void (^)(NSError *_Nullable error))failureBlock -{ - OWSAssertDebug(envelope); - OWSAssertDebug(envelopeData); - OWSAssertDebug(successBlock); - OWSAssertDebug(failureBlock); - - // Check whether we need to refresh our PreKeys every time we receive a PreKeyWhisperMessage. - [TSPreKeyManager checkPreKeys]; - - [self decryptEnvelope:envelope - envelopeData:envelopeData - cipherTypeName:@"PreKey Bundle" - cipherMessageBlock:^(NSData *encryptedData) { - return [[PreKeyWhisperMessage alloc] init_throws_withData:encryptedData]; - } - successBlock:successBlock - failureBlock:failureBlock]; -} - -- (void)decryptEnvelope:(SSKProtoEnvelope *)envelope - envelopeData:(NSData *)envelopeData - cipherTypeName:(NSString *)cipherTypeName - cipherMessageBlock:(id (^_Nonnull)(NSData *))cipherMessageBlock - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(void (^)(NSError *_Nullable error))failureBlock -{ - OWSAssertDebug(envelope); - OWSAssertDebug(envelopeData); - OWSAssertDebug(cipherTypeName.length > 0); - OWSAssertDebug(cipherMessageBlock); - OWSAssertDebug(successBlock); - OWSAssertDebug(failureBlock); - - NSString *recipientId = envelope.source; - int deviceId = envelope.sourceDevice; - - // DEPRECATED - Remove `legacyMessage` after all clients have been upgraded. - NSData *encryptedData = envelope.content ?: envelope.legacyMessage; - if (!encryptedData) { - OWSProdFail([OWSAnalyticsEvents messageManagerErrorMessageEnvelopeHasNoContent]); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, @"Envelope has no content."); - return failureBlock(error); - } - - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - @try { - id cipherMessage = cipherMessageBlock(encryptedData); - SNSessionCipher *cipher = [[SNSessionCipher alloc] - initWithSessionResetImplementation:self.sessionResetImplementation - sessionStore:self.primaryStorage - preKeyStore:self.primaryStorage - signedPreKeyStore:self.primaryStorage - identityKeyStore:self.identityManager - recipientID:recipientId - deviceID:deviceId]; - - // plaintextData may be nil for some envelope types. - NSError *error = nil; - NSData *_Nullable decryptedData = [cipher decrypt:cipherMessage protocolContext:transaction error:&error]; - // Throw if we got an error - SCKRaiseIfExceptionWrapperError(error); - NSData *_Nullable plaintextData = decryptedData != nil ? [decryptedData removePadding] : nil; - - OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:envelopeData - plaintextData:plaintextData - source:envelope.source - sourceDevice:envelope.sourceDevice - isUDMessage:NO]; - successBlock(result, transaction); - } @catch (NSException *exception) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self processException:exception envelope:envelope]; - NSString *errorDescription = [NSString - stringWithFormat:@"Exception while decrypting %@: %@.", cipherTypeName, exception.description]; - OWSLogError(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, errorDescription); - failureBlock(error); - }); - } - }]; -} - -- (void)decryptUnidentifiedSender:(SSKProtoEnvelope *)envelope - successBlock:(DecryptSuccessBlock)successBlock - failureBlock:(void (^)(NSError *_Nullable error))failureBlock -{ - OWSAssertDebug(envelope); - OWSAssertDebug(successBlock); - OWSAssertDebug(failureBlock); - - // NOTE: We don't need to bother with `legacyMessage` for UD messages. - NSData *encryptedData = envelope.content; - if (!encryptedData) { - NSString *errorDescription = @"UD Envelope is missing content."; - OWSFailDebug(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, errorDescription); - return failureBlock(error); - } - - UInt64 serverTimestamp = envelope.timestamp; - - id certificateValidator = - [[SMKCertificateDefaultValidator alloc] initWithTrustRoot:self.udManager.trustRoot]; - - NSString *localRecipientId = self.tsAccountManager.localNumber; - uint32_t localDeviceId = OWSDevicePrimaryDeviceId; - - [LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - NSError *cipherError; - SMKSecretSessionCipher *_Nullable cipher = - [[SMKSecretSessionCipher alloc] initWithSessionResetImplementation:self.sessionResetImplementation - sessionStore:self.primaryStorage - preKeyStore:self.primaryStorage - signedPreKeyStore:self.primaryStorage - identityStore:self.identityManager - error:&cipherError]; - - if (cipherError || !cipher) { - OWSFailDebug(@"Could not create secret session cipher: %@.", cipherError); - cipherError = EnsureDecryptError(cipherError, @"Could not create secret session cipher."); - return failureBlock(cipherError); - } - - NSError *decryptError; - SMKDecryptResult *_Nullable decryptResult = - [cipher throwswrapped_decryptMessageWithCertificateValidator:certificateValidator - cipherTextData:encryptedData - timestamp:serverTimestamp - localRecipientId:localRecipientId - localDeviceId:localDeviceId - protocolContext:transaction - error:&decryptError]; - - if (!decryptResult) { - if (!decryptError) { - OWSFailDebug(@"Caller should provide specific error."); - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeFailedToDecryptUDMessage, @"Could not decrypt UD message."); - return failureBlock(error); - } - - // Decrypt Failure Part 1: Unwrap failure details - - NSError *_Nullable underlyingError; - SSKProtoEnvelope *_Nullable identifiedEnvelope; - - if (![decryptError.domain isEqualToString:@"SessionMetadataKit.SecretSessionKnownSenderError"]) { - underlyingError = decryptError; - identifiedEnvelope = envelope; - } else { - underlyingError = decryptError.userInfo[NSUnderlyingErrorKey]; - - NSString *senderRecipientId - = decryptError.userInfo[SecretSessionKnownSenderError.kSenderRecipientIdKey]; - OWSAssert(senderRecipientId); - - NSNumber *senderDeviceId = decryptError.userInfo[SecretSessionKnownSenderError.kSenderDeviceIdKey]; - OWSAssert(senderDeviceId); - - SSKProtoEnvelopeBuilder *identifiedEnvelopeBuilder = envelope.asBuilder; - identifiedEnvelopeBuilder.source = senderRecipientId; - identifiedEnvelopeBuilder.sourceDevice = senderDeviceId.unsignedIntValue; - NSError *identifiedEnvelopeBuilderError; - - identifiedEnvelope = [identifiedEnvelopeBuilder buildAndReturnError:&identifiedEnvelopeBuilderError]; - if (identifiedEnvelopeBuilderError) { - OWSFailDebug(@"identifiedEnvelopeBuilderError: %@", identifiedEnvelopeBuilderError); - } - } - OWSAssert(underlyingError); - OWSAssert(identifiedEnvelope); - - NSException *_Nullable underlyingException; - if ([underlyingError.domain isEqualToString:SCKExceptionWrapperErrorDomain] - && underlyingError.code == SCKExceptionWrapperErrorThrown) { - - underlyingException = underlyingError.userInfo[SCKExceptionWrapperUnderlyingExceptionKey]; - OWSAssert(underlyingException); - } - - // Decrypt Failure Part 2: Handle unwrapped failure details - - if (underlyingException) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self processException:underlyingException envelope:identifiedEnvelope]; - NSString *errorDescription = [NSString - stringWithFormat:@"Exception while decrypting UD message: %@.", underlyingException.description]; - OWSLogError(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage, errorDescription); - failureBlock(error); - }); - return; - } - - if ([underlyingError.domain isEqualToString:@"SessionMetadataKit.SMKSecretSessionCipherError"] - && underlyingError.code == SMKSecretSessionCipherErrorSelfSentMessage) { - // Self-sent messages can be safely discarded. - failureBlock(underlyingError); - return; - } - - // Attempt to recover automatically - if ([decryptError userInfo][NSUnderlyingErrorKey] != nil) { - NSDictionary *underlyingErrorUserInfo = [[decryptError userInfo][NSUnderlyingErrorKey] userInfo]; - if (underlyingErrorUserInfo[SCKExceptionWrapperUnderlyingExceptionKey] != nil) { - NSException *underlyingUnderlyingError = underlyingErrorUserInfo[SCKExceptionWrapperUnderlyingExceptionKey]; - if ([[underlyingUnderlyingError reason] hasPrefix:@"Bad Mac!"]) { - if ([underlyingError userInfo][@"kSenderRecipientIdKey"] != nil) { - NSString *senderPublicKey = [underlyingError userInfo][@"kSenderRecipientIdKey"]; - TSContactThread *thread = [TSContactThread getThreadWithContactId:senderPublicKey transaction:transaction]; - if (thread != nil) { - [thread addSessionRestoreDevice:senderPublicKey transaction:transaction]; - [LKSessionManagementProtocol startSessionResetInThread:thread transaction:transaction]; - } - } - } - } - } - - failureBlock(underlyingError); - return; - } - - if (decryptResult.messageType == SMKMessageTypePrekey) { - [TSPreKeyManager checkPreKeys]; - } - - NSString *source = decryptResult.senderRecipientId; - if (source.length < 1) { - NSString *errorDescription = @"Invalid UD sender."; - OWSFailDebug(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, errorDescription); - return failureBlock(error); - } - - long sourceDeviceId = decryptResult.senderDeviceId; - if (sourceDeviceId < 1 || sourceDeviceId > UINT32_MAX) { - NSString *errorDescription = @"Invalid UD sender device ID."; - OWSFailDebug(@"%@", errorDescription); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptUDMessage, errorDescription); - return failureBlock(error); - } - NSData *plaintextData = [decryptResult.paddedPayload removePadding]; - - SSKProtoEnvelopeBuilder *envelopeBuilder = [envelope asBuilder]; - [envelopeBuilder setSource:source]; - [envelopeBuilder setSourceDevice:(uint32_t)sourceDeviceId]; - if (decryptResult.messageType == SMKMessageTypeFallback) { - [envelopeBuilder setType:SSKProtoEnvelopeTypeFallbackMessage]; - OWSLogInfo(@"SMKMessageTypeFallback"); - } - NSError *envelopeBuilderError; - NSData *_Nullable newEnvelopeData = [envelopeBuilder buildSerializedDataAndReturnError:&envelopeBuilderError]; - if (envelopeBuilderError || !newEnvelopeData) { - OWSFailDebug(@"Could not update UD envelope data: %@", envelopeBuilderError); - NSError *error = EnsureDecryptError(envelopeBuilderError, @"Could not update UD envelope data"); - return failureBlock(error); - } - - OWSMessageDecryptResult *result = [OWSMessageDecryptResult resultWithEnvelopeData:newEnvelopeData - plaintextData:plaintextData - source:source - sourceDevice:(uint32_t)sourceDeviceId - isUDMessage:YES]; - successBlock(result, transaction); - }]; -} - -- (void)processException:(NSException *)exception envelope:(SSKProtoEnvelope *)envelope -{ - OWSLogError( - @"Got exception: %@ of type: %@ with reason: %@", exception.description, exception.name, exception.reason); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSErrorMessage *errorMessage; - - if (envelope.source.length == 0) { - TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; - [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage - transaction:transaction]; - return; - } - - if ([exception.name isEqualToString:NoSessionException]) { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorNoSession], envelope); - errorMessage = [TSErrorMessage missingSessionWithEnvelope:envelope withTransaction:transaction]; - } else if ([exception.name isEqualToString:InvalidKeyException]) { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidKey], envelope); - errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction]; - } else if ([exception.name isEqualToString:InvalidKeyIdException]) { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidKeyId], envelope); - errorMessage = [TSErrorMessage invalidKeyExceptionWithEnvelope:envelope withTransaction:transaction]; - } else if ([exception.name isEqualToString:DuplicateMessageException]) { - // Duplicate messages are silently discarded. - return; - } else if ([exception.name isEqualToString:InvalidVersionException]) { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorInvalidMessageVersion], envelope); - errorMessage = [TSErrorMessage invalidVersionWithEnvelope:envelope withTransaction:transaction]; - } else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) { - // Should no longer get here, since we now record the new identity for incoming messages. - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorUntrustedIdentityKeyException], envelope); - OWSFailDebug(@"Failed to trust identity on incoming message from: %@", envelopeAddress(envelope)); - return; - } else { - OWSProdErrorWEnvelope([OWSAnalyticsEvents messageManagerErrorCorruptMessage], envelope); - errorMessage = [TSErrorMessage corruptedMessageWithEnvelope:envelope withTransaction:transaction]; - } - - OWSAssertDebug(errorMessage); - if (errorMessage != nil) { - [LKSessionManagementProtocol handleDecryptionError:errorMessage forPublicKey:envelope.source transaction:transaction]; - if (![LKSessionMetaProtocol isErrorMessageFromBeforeRestoration:errorMessage]) { - [errorMessage saveWithTransaction:transaction]; - [self notifyUserForErrorMessage:errorMessage envelope:envelope transaction:transaction]; - } else { - // Show the thread if it exists before restoration - NSString *masterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source; - TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:masterPublicKey transaction:transaction]; - contactThread.shouldThreadBeVisible = true; - [contactThread saveWithTransaction:transaction]; - } - } - }]; -} - -- (void)notifyUserForErrorMessage:(TSErrorMessage *)errorMessage - envelope:(SSKProtoEnvelope *)envelope - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - NSString *masterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source; - TSThread *contactThread = [TSContactThread getOrCreateThreadWithContactId:masterPublicKey transaction:transaction]; - [SSKEnvironment.shared.notificationsManager notifyUserForErrorMessage:errorMessage - thread:contactThread - transaction:transaction]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/OWSMessageHandler.h b/SignalUtilitiesKit/OWSMessageHandler.h deleted file mode 100644 index 8722028b7..000000000 --- a/SignalUtilitiesKit/OWSMessageHandler.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class SSKProtoContent; -@class SSKProtoDataMessage; -@class SSKProtoEnvelope; - -NSString *envelopeAddress(SSKProtoEnvelope *envelope); - -@interface OWSMessageHandler : NSObject - -- (NSString *)descriptionForEnvelopeType:(SSKProtoEnvelope *)envelope; -- (NSString *)descriptionForEnvelope:(SSKProtoEnvelope *)envelope; -- (NSString *)descriptionForContent:(SSKProtoContent *)content; -- (NSString *)descriptionForDataMessage:(SSKProtoDataMessage *)dataMessage; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/OWSMessageHandler.m b/SignalUtilitiesKit/OWSMessageHandler.m deleted file mode 100644 index 892540eae..000000000 --- a/SignalUtilitiesKit/OWSMessageHandler.m +++ /dev/null @@ -1,183 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageHandler.h" -#import - -NS_ASSUME_NONNULL_BEGIN - -// used in log formatting -NSString *envelopeAddress(SSKProtoEnvelope *envelope) -{ - return [NSString stringWithFormat:@"%@.%d", envelope.source, (unsigned int)envelope.sourceDevice]; -} - -@implementation OWSMessageHandler - -- (NSString *)descriptionForEnvelopeType:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(envelope != nil); - - switch (envelope.type) { - case SSKProtoEnvelopeTypeReceipt: - return @"DeliveryReceipt"; - case SSKProtoEnvelopeTypeUnknown: - // Shouldn't happen - return @"Unknown"; - case SSKProtoEnvelopeTypeCiphertext: - return @"SignalEncryptedMessage"; - case SSKProtoEnvelopeTypeKeyExchange: - // Unsupported - return @"KeyExchange"; - case SSKProtoEnvelopeTypePrekeyBundle: - return @"PreKeyEncryptedMessage"; - case SSKProtoEnvelopeTypeUnidentifiedSender: - return @"UnidentifiedSender"; - case SSKProtoEnvelopeTypeFallbackMessage: - return @"FallbackMessage"; - case SSKProtoEnvelopeTypeClosedGroupCiphertext: - return @"ClosedGroupCiphertext"; - default: - // Shouldn't happen - return @"Other"; - } -} - -- (NSString *)descriptionForEnvelope:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(envelope != nil); - - return [NSString stringWithFormat:@"", - [self descriptionForEnvelopeType:envelope], - envelopeAddress(envelope), - envelope.timestamp, - (unsigned long)envelope.content.length]; -} - -/** - * We don't want to just log `content.description` because we'd potentially log message bodies for dataMesssages and - * sync transcripts - */ -- (NSString *)descriptionForContent:(SSKProtoContent *)content -{ - if (content.syncMessage) { - return [NSString stringWithFormat:@"", [self descriptionForSyncMessage:content.syncMessage]]; - } else if (content.dataMessage) { - return [NSString stringWithFormat:@"", [self descriptionForDataMessage:content.dataMessage]]; - } else if (content.callMessage) { - NSString *callMessageDescription = [self descriptionForCallMessage:content.callMessage]; - return [NSString stringWithFormat:@"", callMessageDescription]; - } else if (content.nullMessage) { - return [NSString stringWithFormat:@"", content.nullMessage]; - } else if (content.receiptMessage) { - return [NSString stringWithFormat:@"", content.receiptMessage]; - } else if (content.typingMessage) { - return [NSString stringWithFormat:@"", content.typingMessage]; - } else { - // Don't fire an analytics event; if we ever add a new content type, we'd generate a ton of - // analytics traffic. - return @"UnknownContent"; - } -} - -- (NSString *)descriptionForCallMessage:(SSKProtoCallMessage *)callMessage -{ - NSString *messageType; - UInt64 callId; - - if (callMessage.offer) { - messageType = @"Offer"; - callId = callMessage.offer.id; - } else if (callMessage.busy) { - messageType = @"Busy"; - callId = callMessage.busy.id; - } else if (callMessage.answer) { - messageType = @"Answer"; - callId = callMessage.answer.id; - } else if (callMessage.hangup) { - messageType = @"Hangup"; - callId = callMessage.hangup.id; - } else if (callMessage.iceUpdate.count > 0) { - messageType = [NSString stringWithFormat:@"Ice Updates (%lu)", (unsigned long)callMessage.iceUpdate.count]; - callId = callMessage.iceUpdate.firstObject.id; - } else { - OWSFailDebug(@"failure: unexpected call message type: %@", callMessage); - messageType = @"Unknown"; - callId = 0; - } - - return [NSString stringWithFormat:@"type: %@, id: %llu", messageType, callId]; -} - -/** - * We don't want to just log `dataMessage.description` because we'd potentially log message contents - */ -- (NSString *)descriptionForDataMessage:(SSKProtoDataMessage *)dataMessage -{ - NSMutableString *description = [NSMutableString new]; - - if (dataMessage.group) { - [description appendString:@"(Group:YES) "]; - } - - if ((dataMessage.flags & SSKProtoDataMessageFlagsEndSession) != 0) { - [description appendString:@"EndSession"]; - } else if ((dataMessage.flags & SSKProtoDataMessageFlagsExpirationTimerUpdate) != 0) { - [description appendString:@"ExpirationTimerUpdate"]; - } else if ((dataMessage.flags & SSKProtoDataMessageFlagsProfileKeyUpdate) != 0) { - [description appendString:@"ProfileKey"]; - } else if (dataMessage.attachments.count > 0) { - [description appendString:@"MessageWithAttachment"]; - } else { - [description appendString:@"Plain"]; - } - - return [NSString stringWithFormat:@"<%@ />", description]; -} - -/** - * We don't want to just log `syncMessage.description` because we'd potentially log message contents in sent transcripts - */ -- (NSString *)descriptionForSyncMessage:(SSKProtoSyncMessage *)syncMessage -{ - NSMutableString *description = [NSMutableString new]; - if (syncMessage.sent) { - [description appendString:@"SentTranscript"]; - } else if (syncMessage.request) { - if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeContacts) { - [description appendString:@"ContactRequest"]; - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeGroups) { - [description appendString:@"GroupRequest"]; - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeBlocked) { - [description appendString:@"BlockedRequest"]; - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeConfiguration) { - [description appendString:@"ConfigurationRequest"]; - } else { - OWSFailDebug(@"Unknown sync message request type"); - [description appendString:@"UnknownRequest"]; - } - } else if (syncMessage.blocked) { - [description appendString:@"Blocked"]; - } else if (syncMessage.read.count > 0) { - [description appendString:@"ReadReceipt"]; - } else if (syncMessage.verified) { - NSString *verifiedString = - [NSString stringWithFormat:@"Verification for: %@", syncMessage.verified.destination]; - [description appendString:verifiedString]; - } else if (syncMessage.contacts) { - [description appendString:@"Contacts"]; - } else if (syncMessage.groups) { - [description appendString:@"ClosedGroups"]; - } else if (syncMessage.openGroups) { - [description appendString:@"OpenGroups"]; - } else { - [description appendString:@"Unknown"]; - } - - return description; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/OWSMessageManager.h b/SignalUtilitiesKit/OWSMessageManager.h deleted file mode 100644 index f8624d0a5..000000000 --- a/SignalUtilitiesKit/OWSMessageManager.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class SSKProtoEnvelope; -@class TSThread; -@class YapDatabaseReadWriteTransaction; - -@interface OWSMessageManager : OWSMessageHandler - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)sharedManager; - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -// processEnvelope: can be called from any thread. -- (void)throws_processEnvelope:(SSKProtoEnvelope *)envelope - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction - serverID:(uint64_t)serverID; - -// This should be invoked by the main app when the app is ready. -- (void)startObserving; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/OWSMessageManager.m b/SignalUtilitiesKit/OWSMessageManager.m deleted file mode 100644 index d839a282e..000000000 --- a/SignalUtilitiesKit/OWSMessageManager.m +++ /dev/null @@ -1,1735 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageManager.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "ContactsManagerProtocol.h" -#import "MimeTypeUtil.h" -#import "NSNotificationCenter+OWS.h" -#import "NSString+SSK.h" -#import "NotificationsProtocol.h" -#import "OWSAttachmentDownloads.h" -#import "OWSBlockingManager.h" -#import "OWSCallMessageHandler.h" -#import "OWSContact.h" -#import "OWSDevice.h" -#import "OWSDevicesService.h" -#import "OWSDisappearingConfigurationUpdateInfoMessage.h" -#import "OWSDisappearingMessagesConfiguration.h" -#import "OWSDisappearingMessagesJob.h" -#import "LKDeviceLinkMessage.h" -#import "OWSIdentityManager.h" -#import "OWSIncomingMessageFinder.h" -#import "OWSIncomingSentMessageTranscript.h" -#import "OWSMessageSender.h" -#import "OWSMessageUtils.h" -#import "OWSOutgoingNullMessage.h" -#import "OWSOutgoingReceiptManager.h" -#import "OWSPrimaryStorage+SessionStore.h" -#import "OWSPrimaryStorage+Loki.h" -#import "OWSPrimaryStorage.h" -#import "OWSReadReceiptManager.h" -#import "OWSRecordTranscriptJob.h" -#import "OWSSyncGroupsMessage.h" -#import "OWSSyncGroupsRequestMessage.h" -#import "ProfileManagerProtocol.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "SSKAsserts.h" -#import "TSAttachment.h" -#import "TSAttachmentPointer.h" -#import "TSAttachmentStream.h" -#import "TSContactThread.h" -#import "TSDatabaseView.h" -#import "TSGroupModel.h" -#import "TSGroupThread.h" -#import "TSIncomingMessage.h" -#import "TSInfoMessage.h" -#import "TSNetworkManager.h" -#import "TSOutgoingMessage.h" -#import "TSQuotedMessage.h" -#import -#import -#import -#import -#import -#import -#import "OWSDispatch.h" -#import "OWSBatchMessageProcessor.h" -#import "OWSQueues.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSMessageManager () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) OWSIncomingMessageFinder *incomingMessageFinder; - -@end - -#pragma mark - - -@implementation OWSMessageManager - -+ (instancetype)sharedManager -{ - OWSAssertDebug(SSKEnvironment.shared.messageManager); - - return SSKEnvironment.shared.messageManager; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - _dbConnection = primaryStorage.newDatabaseConnection; - _incomingMessageFinder = [[OWSIncomingMessageFinder alloc] initWithPrimaryStorage:primaryStorage]; - - OWSSingletonAssert(); - - return self; -} - -- (void)dealloc { - [NSNotificationCenter.defaultCenter removeObserver:self]; -} - -#pragma mark - - -- (id)callMessageHandler -{ - OWSAssertDebug(SSKEnvironment.shared.callMessageHandler); - - return SSKEnvironment.shared.callMessageHandler; -} - -- (id)contactsManager -{ - OWSAssertDebug(SSKEnvironment.shared.contactsManager); - - return SSKEnvironment.shared.contactsManager; -} - -- (SSKMessageSenderJobQueue *)messageSenderJobQueue -{ - return SSKEnvironment.shared.messageSenderJobQueue; -} - -- (OWSBlockingManager *)blockingManager -{ - OWSAssertDebug(SSKEnvironment.shared.blockingManager); - - return SSKEnvironment.shared.blockingManager; -} - -- (OWSIdentityManager *)identityManager -{ - OWSAssertDebug(SSKEnvironment.shared.identityManager); - - return SSKEnvironment.shared.identityManager; -} - -- (TSNetworkManager *)networkManager -{ - OWSAssertDebug(SSKEnvironment.shared.networkManager); - - return SSKEnvironment.shared.networkManager; -} - -- (OWSOutgoingReceiptManager *)outgoingReceiptManager -{ - OWSAssertDebug(SSKEnvironment.shared.outgoingReceiptManager); - - return SSKEnvironment.shared.outgoingReceiptManager; -} - -- (id)syncManager -{ - OWSAssertDebug(SSKEnvironment.shared.syncManager); - - return SSKEnvironment.shared.syncManager; -} - -- (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -- (id)profileManager -{ - return SSKEnvironment.shared.profileManager; -} - -- (id)typingIndicators -{ - return SSKEnvironment.shared.typingIndicators; -} - -- (OWSAttachmentDownloads *)attachmentDownloads -{ - return SSKEnvironment.shared.attachmentDownloads; -} - -#pragma mark - - -- (void)startObserving -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(yapDatabaseModified:) - name:YapDatabaseModifiedNotification - object:OWSPrimaryStorage.sharedManager.dbNotificationObject]; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(yapDatabaseModified:) - name:YapDatabaseModifiedExternallyNotification - object:nil]; -} - -- (void)yapDatabaseModified:(NSNotification *)notification -{ - if (AppReadiness.isAppReady) { - [OWSMessageUtils.sharedManager updateApplicationBadgeCount]; - } else { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - [OWSMessageUtils.sharedManager updateApplicationBadgeCount]; - }]; - }); - } -} - -#pragma mark - - -- (BOOL)isEnvelopeSenderBlocked:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(envelope); - - return [self.blockingManager isRecipientIdBlocked:envelope.source]; -} - -- (BOOL)isDataMessageBlocked:(SSKProtoDataMessage *)dataMessage envelope:(SSKProtoEnvelope *)envelope -{ - OWSAssertDebug(dataMessage); - OWSAssertDebug(envelope); - - if (dataMessage.group) { - return [self.blockingManager isGroupIdBlocked:dataMessage.group.id]; - } else { - BOOL senderBlocked = [self isEnvelopeSenderBlocked:envelope]; - - // If the envelopeSender was blocked, we never should have gotten as far as decrypting the dataMessage. - OWSAssertDebug(!senderBlocked); - - return senderBlocked; - } -} - -#pragma mark - - -- (void)throws_processEnvelope:(SSKProtoEnvelope *)envelope - plaintextData:(NSData *_Nullable)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction - serverID:(uint64_t)serverID -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - if (!self.tsAccountManager.isRegistered) { - OWSFailDebug(@"Not registered."); - return; - } - - OWSLogInfo(@"Handling decrypted envelope: %@.", [self descriptionForEnvelope:envelope]); - - if (!envelope.hasSource || envelope.source.length < 1) { - OWSFailDebug(@"Incoming envelope with invalid source."); - return; - } - if (!envelope.hasSourceDevice || envelope.sourceDevice < 1) { - OWSFailDebug(@"Incoming envelope with invalid source device."); - return; - } - - if ([self isEnvelopeSenderBlocked:envelope]) { - return; - } - - [self checkForUnknownLinkedDevice:envelope transaction:transaction]; - - switch (envelope.type) { - case SSKProtoEnvelopeTypeFallbackMessage: - case SSKProtoEnvelopeTypeCiphertext: - case SSKProtoEnvelopeTypePrekeyBundle: - case SSKProtoEnvelopeTypeClosedGroupCiphertext: - case SSKProtoEnvelopeTypeUnidentifiedSender: - if (!plaintextData) { - OWSFailDebug(@"missing decrypted data for envelope: %@", [self descriptionForEnvelope:envelope]); - return; - } - [self throws_handleEnvelope:envelope - plaintextData:plaintextData - wasReceivedByUD:wasReceivedByUD - transaction:transaction - serverID:serverID]; - break; - case SSKProtoEnvelopeTypeReceipt: - OWSAssertDebug(!plaintextData); - [self handleDeliveryReceipt:envelope transaction:transaction]; - break; - // Other messages are just dismissed for now. - case SSKProtoEnvelopeTypeKeyExchange: - OWSLogWarn(@"Received Key Exchange Message, not supported"); - break; - case SSKProtoEnvelopeTypeUnknown: - OWSLogWarn(@"Received an unknown message type"); - break; - default: - OWSLogWarn(@"Received unhandled envelope type: %d", (int)envelope.type); - break; - } -} - -- (void)handleDeliveryReceipt:(SSKProtoEnvelope *)envelope transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - // Old-style delivery notices don't include a "delivery timestamp". - [self processDeliveryReceiptsFromRecipientId:envelope.source - sentTimestamps:@[ - @(envelope.timestamp), - ] - deliveryTimestamp:nil - transaction:transaction]; -} - -// deliveryTimestamp is an optional parameter, since legacy -// delivery receipts don't have a "delivery timestamp". Those -// messages repurpose the "timestamp" field to indicate when the -// corresponding message was originally sent. -- (void)processDeliveryReceiptsFromRecipientId:(NSString *)recipientId - sentTimestamps:(NSArray *)sentTimestamps - deliveryTimestamp:(NSNumber *_Nullable)deliveryTimestamp - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (recipientId.length < 1) { - OWSFailDebug(@"Empty recipientId."); - return; - } - if (sentTimestamps.count < 1) { - OWSFailDebug(@"Missing sentTimestamps."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - for (NSNumber *nsTimestamp in sentTimestamps) { - uint64_t timestamp = [nsTimestamp unsignedLongLongValue]; - - NSArray *messages - = (NSArray *)[TSInteraction interactionsWithTimestamp:timestamp - ofClass:[TSOutgoingMessage class] - withTransaction:transaction]; - if (messages.count < 1) { - // The service sends delivery receipts for "unpersisted" messages - // like group updates, so these errors are expected to a certain extent. - // - // TODO: persist "early" delivery receipts. - OWSLogInfo(@"Missing message for delivery receipt: %llu", timestamp); - } else { - if (messages.count > 1) { - OWSLogInfo(@"More than one message (%lu) for delivery receipt: %llu", - (unsigned long)messages.count, - timestamp); - } - for (TSOutgoingMessage *outgoingMessage in messages) { - [outgoingMessage updateWithDeliveredRecipient:recipientId - deliveryTimestamp:deliveryTimestamp - transaction:transaction]; - } - } - } -} - -- (void)throws_handleEnvelope:(SSKProtoEnvelope *)envelope - plaintextData:(NSData *)plaintextData - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction - serverID:(uint64_t)serverID -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!plaintextData) { - OWSFailDebug(@"Missing plaintextData."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - if (envelope.timestamp < 1) { - OWSFailDebug(@"Invalid timestamp."); - return; - } - if (envelope.source.length < 1) { - OWSFailDebug(@"Missing source."); - return; - } - if (envelope.sourceDevice < 1) { - OWSFailDebug(@"Invalid source device."); - return; - } - - OWSPrimaryStorage *storage = OWSPrimaryStorage.sharedManager; - __block NSSet *senderLinkedDevices; - [storage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - senderLinkedDevices = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:envelope.source in:transaction]; - }]; - - BOOL duplicateEnvelope = NO; - for (NSString *publicKey in senderLinkedDevices) { - duplicateEnvelope = duplicateEnvelope - || [self.incomingMessageFinder existsMessageWithTimestamp:envelope.timestamp - sourceId:publicKey - sourceDeviceId:envelope.sourceDevice - transaction:transaction]; - } - - if (duplicateEnvelope) { - OWSLogInfo(@"Ignoring previously received envelope from: %@ with timestamp: %llu.", - envelopeAddress(envelope), - envelope.timestamp); - return; - } - - if (envelope.content != nil) { - NSError *error; - SSKProtoContent *_Nullable contentProto = [SSKProtoContent parseData:plaintextData error:&error]; - if (error != nil || contentProto == nil) { - OWSFailDebug(@"Couldn't parse proto due to error: %@.", error); - return; - } - OWSLogInfo(@"Handling content: .", [self descriptionForContent:contentProto]); - - if ([LKSyncMessagesProtocol isDuplicateSyncMessage:contentProto fromPublicKey:envelope.source]) { - [LKLogger print:@"[Loki] Ignoring duplicate sync transcript."]; - return; - } - - [LKSessionManagementProtocol handlePreKeyBundleMessageIfNeeded:contentProto wrappedIn:envelope transaction:transaction]; - - if (contentProto.lokiDeviceLinkMessage != nil) { - [LKMultiDeviceProtocol handleDeviceLinkMessageIfNeeded:contentProto wrappedIn:envelope transaction:transaction]; - } else if (contentProto.syncMessage) { - [self throws_handleIncomingEnvelope:envelope - withSyncMessage:contentProto.syncMessage - transaction:transaction - serverID:serverID]; - - [[OWSDeviceManager sharedManager] setHasReceivedSyncMessage]; - } else if (contentProto.dataMessage) { - [self handleIncomingEnvelope:envelope - withDataMessage:contentProto.dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - } else if (contentProto.callMessage) { - [self handleIncomingEnvelope:envelope withCallMessage:contentProto.callMessage]; - } else if (contentProto.typingMessage) { - [self handleIncomingEnvelope:envelope withTypingMessage:contentProto.typingMessage transaction:transaction]; - } else if (contentProto.receiptMessage) { - [self handleIncomingEnvelope:envelope - withReceiptMessage:contentProto.receiptMessage - transaction:transaction]; - } else { - OWSLogWarn(@"Ignoring envelope. Content with no known payload"); - } - } else if (envelope.legacyMessage != nil) { // DEPRECATED - Remove after all clients have been upgraded. - NSError *error; - SSKProtoDataMessage *_Nullable dataMessageProto = [SSKProtoDataMessage parseData:plaintextData error:&error]; - if (error || !dataMessageProto) { - OWSFailDebug(@"could not parse proto: %@", error); - return; - } - OWSLogInfo(@"handling message: ", [self descriptionForDataMessage:dataMessageProto]); - - [self handleIncomingEnvelope:envelope - withDataMessage:dataMessageProto - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - } else { - - } -} - -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withDataMessage:(SSKProtoDataMessage *)dataMessage - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - if ([self isDataMessageBlocked:dataMessage envelope:envelope]) { - NSString *logMessage = [NSString stringWithFormat:@"Ignoring blocked message from sender: %@.", envelope.source]; - if (dataMessage.group) { - logMessage = [logMessage stringByAppendingFormat:@" in group: %@", dataMessage.group.id]; - } - OWSLogError(@"%@", logMessage); - return; - } - - if (dataMessage.hasTimestamp) { - if (dataMessage.timestamp <= 0) { - OWSFailDebug(@"Ignoring data message with invalid timestamp: %@.", envelope.source); - return; - } - // This prevents replay attacks by the service. - if (dataMessage.timestamp != envelope.timestamp) { - OWSFailDebug(@"Ignoring data message with non-matching timestamp: %@.", envelope.source); - return; - } - } - - [LKClosedGroupsProtocol handleSharedSenderKeysUpdateIfNeeded:dataMessage from:envelope.source transaction:transaction]; - - if (dataMessage.group) { - TSGroupThread *_Nullable groupThread = - [TSGroupThread threadWithGroupId:dataMessage.group.id transaction:transaction]; - - if (groupThread) { - if (groupThread.groupModel.groupType == closedGroup) { - if ([LKClosedGroupsProtocol shouldIgnoreClosedGroupMessage:dataMessage inThread:groupThread wrappedIn:envelope]) { return; } - } - - if (dataMessage.group.type != SSKProtoGroupContextTypeUpdate) { - if (![groupThread isCurrentUserInGroupWithTransaction:transaction]) { - OWSLogInfo(@"Ignoring messages for left group."); - return; - } - } - } else { - // Unknown group - if (dataMessage.group.type == SSKProtoGroupContextTypeUpdate) { - // Accept group updates for unknown groups - } else if (dataMessage.group.type == SSKProtoGroupContextTypeDeliver) { - [self sendGroupInfoRequest:dataMessage.group.id envelope:envelope transaction:transaction]; - return; - } else { - OWSLogInfo(@"Ignoring group message for unknown group from: %@.", envelope.source); - return; - } - } - } - - if ((dataMessage.flags & SSKProtoDataMessageFlagsEndSession) != 0) { - [self handleEndSessionMessageWithEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - } else if ((dataMessage.flags & SSKProtoDataMessageFlagsExpirationTimerUpdate) != 0) { - [self handleExpirationTimerUpdateMessageWithEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - } else if ((dataMessage.flags & SSKProtoDataMessageFlagsProfileKeyUpdate) != 0) { - [self handleProfileKeyMessageWithEnvelope:envelope dataMessage:dataMessage]; - } else if ([LKMultiDeviceProtocol isUnlinkDeviceMessage:dataMessage]) { - [LKMultiDeviceProtocol handleUnlinkDeviceMessage:dataMessage wrappedIn:envelope transaction:transaction]; - } else if (dataMessage.attachments.count > 0) { - [self handleReceivedMediaWithEnvelope:envelope - dataMessage:dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - } else { - [self handleReceivedTextMessageWithEnvelope:envelope - dataMessage:dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - - if ([self isDataMessageGroupAvatarUpdate:dataMessage]) { - OWSLogVerbose(@"Data message had group avatar attachment"); - [self handleReceivedGroupAvatarUpdateWithEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - } - } - - // Send delivery receipts for "valid data" messages received via UD. - if (wasReceivedByUD) { - [self.outgoingReceiptManager enqueueDeliveryReceiptForEnvelope:envelope]; - } -} - -- (void)sendGroupInfoRequest:(NSData *)groupId - envelope:(SSKProtoEnvelope *)envelope - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - if (groupId.length < 1) { - OWSFailDebug(@"Invalid groupId."); - return; - } - - // FIXME: https://github.com/signalapp/Signal-iOS/issues/1340 - OWSLogInfo(@"Sending group info request: %@", envelopeAddress(envelope)); - - NSString *recipientId = envelope.source; - - TSThread *thread = [TSContactThread getOrCreateThreadWithContactId:recipientId transaction:transaction]; - - OWSSyncGroupsRequestMessage *syncGroupsRequestMessage = - [[OWSSyncGroupsRequestMessage alloc] initWithThread:thread groupId:groupId]; - - [self.messageSenderJobQueue addMessage:syncGroupsRequestMessage transaction:transaction]; -} - -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withReceiptMessage:(SSKProtoReceiptMessage *)receiptMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!receiptMessage) { - OWSFailDebug(@"Missing receiptMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - NSArray *sentTimestamps = receiptMessage.timestamp; - - switch (receiptMessage.type) { - case SSKProtoReceiptMessageTypeDelivery: - OWSLogVerbose(@"Processing receipt message with delivery receipts."); - [self processDeliveryReceiptsFromRecipientId:envelope.source - sentTimestamps:sentTimestamps - deliveryTimestamp:@(envelope.timestamp) - transaction:transaction]; - return; - case SSKProtoReceiptMessageTypeRead: - OWSLogVerbose(@"Processing receipt message with read receipts."); - [OWSReadReceiptManager.sharedManager processReadReceiptsFromRecipientId:envelope.source - sentTimestamps:sentTimestamps - readTimestamp:envelope.timestamp]; - break; - default: - OWSLogInfo(@"Ignoring receipt message of unknown type: %d.", (int)receiptMessage.type); - return; - } -} - -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withCallMessage:(SSKProtoCallMessage *)callMessage -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!callMessage) { - OWSFailDebug(@"Missing callMessage."); - return; - } - - if ([callMessage hasProfileKey]) { - NSData *profileKey = [callMessage profileKey]; - NSString *recipientId = envelope.source; - [self.profileManager setProfileKeyData:profileKey forRecipientId:recipientId]; - } - - // By dispatching async, we introduce the possibility that these messages might be lost - // if the app exits before this block is executed. This is fine, since the call by - // definition will end if the app exits. - dispatch_async(dispatch_get_main_queue(), ^{ - if (callMessage.offer) { - [self.callMessageHandler receivedOffer:callMessage.offer fromCallerId:envelope.source]; - } else if (callMessage.answer) { - [self.callMessageHandler receivedAnswer:callMessage.answer fromCallerId:envelope.source]; - } else if (callMessage.iceUpdate.count > 0) { - for (SSKProtoCallMessageIceUpdate *iceUpdate in callMessage.iceUpdate) { - [self.callMessageHandler receivedIceUpdate:iceUpdate fromCallerId:envelope.source]; - } - } else if (callMessage.hangup) { - OWSLogVerbose(@"Received CallMessage with Hangup."); - [self.callMessageHandler receivedHangup:callMessage.hangup fromCallerId:envelope.source]; - } else if (callMessage.busy) { - [self.callMessageHandler receivedBusy:callMessage.busy fromCallerId:envelope.source]; - } else { - - } - }); -} - -- (void)handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withTypingMessage:(SSKProtoTypingMessage *)typingMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(transaction); - - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!typingMessage) { - OWSFailDebug(@"Missing typingMessage."); - return; - } - if (typingMessage.timestamp != envelope.timestamp) { - OWSFailDebug(@"typingMessage has invalid timestamp."); - return; - } - NSString *localNumber = self.tsAccountManager.localNumber; - if ([localNumber isEqualToString:envelope.source]) { - OWSLogVerbose(@"Ignoring typing indicators from self or linked device."); - return; - } else if ([self.blockingManager isRecipientIdBlocked:envelope.source] - || (typingMessage.hasGroupID && [self.blockingManager isGroupIdBlocked:typingMessage.groupID])) { - NSString *logMessage = [NSString stringWithFormat:@"Ignoring blocked message from sender: %@", envelope.source]; - if (typingMessage.hasGroupID) { - logMessage = [logMessage stringByAppendingFormat:@" in group: %@", typingMessage.groupID]; - } - OWSLogError(@"%@", logMessage); - return; - } - - TSThread *_Nullable thread; - if (typingMessage.hasGroupID) { - TSGroupThread *groupThread = [TSGroupThread threadWithGroupId:typingMessage.groupID transaction:transaction]; - - if (![groupThread isCurrentUserInGroupWithTransaction:transaction]) { - OWSLogInfo(@"Ignoring messages for left group."); - return; - } - - thread = groupThread; - } else { - thread = [TSContactThread getThreadWithContactId:envelope.source transaction:transaction]; - } - - if (!thread) { - // This isn't neccesarily an error. We might not yet know about the thread, - // in which case we don't need to display the typing indicators. - OWSLogWarn(@"Could not locate thread for typingMessage."); - return; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - switch (typingMessage.action) { - case SSKProtoTypingMessageActionStarted: - [self.typingIndicators didReceiveTypingStartedMessageInThread:thread - recipientId:envelope.source - deviceId:envelope.sourceDevice]; - break; - case SSKProtoTypingMessageActionStopped: - [self.typingIndicators didReceiveTypingStoppedMessageInThread:thread - recipientId:envelope.source - deviceId:envelope.sourceDevice]; - break; - default: - OWSFailDebug(@"Typing message has unexpected action."); - break; - } - }); -} - -- (void)handleReceivedGroupAvatarUpdateWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - TSGroupThread *_Nullable groupThread = - [TSGroupThread threadWithGroupId:dataMessage.group.id transaction:transaction]; - if (!groupThread) { - OWSFailDebug(@"Missing group for group avatar update"); - return; - } - - TSAttachmentPointer *_Nullable avatarPointer = - [TSAttachmentPointer attachmentPointerFromProto:dataMessage.group.avatar albumMessage:nil]; - - if (!avatarPointer) { - OWSLogWarn(@"received unsupported group avatar envelope"); - return; - } - [self.attachmentDownloads downloadAttachmentPointer:avatarPointer - success:^(NSArray *attachmentStreams) { - OWSAssertDebug(attachmentStreams.count == 1); - TSAttachmentStream *attachmentStream = attachmentStreams.firstObject; - [groupThread updateAvatarWithAttachmentStream:attachmentStream]; - } - failure:^(NSError *error) { - OWSLogError(@"failed to fetch attachments for group avatar sent at: %llu. with error: %@", - envelope.timestamp, - error); - }]; -} - -- (void)handleReceivedMediaWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - TSThread *_Nullable thread = [self threadForEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - - if (!thread) { - OWSFailDebug(@"Ignoring media message for unknown group."); - return; - } - - TSIncomingMessage *_Nullable message = [self handleReceivedEnvelope:envelope - withDataMessage:dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - - if (!message) { - return; - } - - [message saveWithTransaction:transaction]; - - OWSLogDebug(@"Incoming attachment message: %@.", message.debugDescription); - - [self.attachmentDownloads downloadAttachmentsForMessage:message - transaction:transaction - success:^(NSArray *attachmentStreams) { - OWSLogDebug(@"Successfully fetched attachments: %lu for message: %@.", - (unsigned long)attachmentStreams.count, - message); - } - failure:^(NSError *error) { - OWSLogError(@"Failed to fetch attachments for message: %@ with error: %@.", message, error); - }]; -} - -- (void)throws_handleIncomingEnvelope:(SSKProtoEnvelope *)envelope - withSyncMessage:(SSKProtoSyncMessage *)syncMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction - serverID:(uint64_t)serverID -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!syncMessage) { - OWSFailDebug(@"Missing syncMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - if (![LKSyncMessagesProtocol isValidSyncMessage:envelope transaction:transaction]) { - return; - } - - if (syncMessage.sent) { - OWSIncomingSentMessageTranscript *transcript = - [[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction]; - - SSKProtoDataMessage *_Nullable dataMessage = syncMessage.sent.message; - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - - // Loki: Update profile if needed (i.e. if the sync message came from the master device) - [LKSyncMessagesProtocol updateProfileFromSyncMessageIfNeeded:dataMessage wrappedIn:envelope transaction:transaction]; - - NSString *destination = syncMessage.sent.destination; - if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) { - // If we observe a linked device sending our profile key to another - // user, we can infer that that user belongs in our profile whitelist. - if (dataMessage.group) { - [self.profileManager addGroupIdToProfileWhitelist:dataMessage.group.id]; - } else { - [self.profileManager addUserToProfileWhitelist:destination]; - } - } - - if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) { - [OWSRecordTranscriptJob - processIncomingSentMessageTranscript:transcript - serverID:0 - serverTimestamp:0 - attachmentHandler:^(NSArray *attachmentStreams) { - OWSAssertDebug(attachmentStreams.count == 1); - TSAttachmentStream *attachmentStream = attachmentStreams.firstObject; - [LKStorage writeSyncWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - TSGroupThread *_Nullable groupThread = - [TSGroupThread threadWithGroupId:dataMessage.group.id - transaction:transaction]; - if (!groupThread) { - OWSFailDebug(@"ignoring sync group avatar update for unknown group."); - return; - } - - [groupThread updateAvatarWithAttachmentStream:attachmentStream - transaction:transaction]; - }]; - } - transaction:transaction - ]; - } else { - if (transcript.isGroupUpdate) { - [LKSyncMessagesProtocol handleClosedGroupUpdateSyncMessageIfNeeded:transcript wrappedIn:envelope transaction:transaction]; - } else if (transcript.isGroupQuit) { - [LKSyncMessagesProtocol handleClosedGroupQuitSyncMessageIfNeeded:transcript wrappedIn:envelope transaction:transaction]; - } else { - [OWSRecordTranscriptJob - processIncomingSentMessageTranscript:transcript - serverID:(serverID ?: 0) - serverTimestamp:(uint64_t)envelope.serverTimestamp - attachmentHandler:^(NSArray *attachmentStreams) { - OWSLogDebug(@"successfully fetched transcript attachments: %lu", - (unsigned long)attachmentStreams.count); - } - transaction:transaction]; - } - } - } else if (syncMessage.request) { - if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeContacts) { - // We respond asynchronously because populating the sync message will - // create transactions and it's not practical (due to locking in the OWSIdentityManager) - // to plumb our transaction through. - // - // In rare cases this means we won't respond to the sync request, but that's - // acceptable. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [[self.syncManager syncAllContacts] retainUntilComplete]; - }); - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeGroups) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [[self.syncManager syncAllGroups] retainUntilComplete]; - }); - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeBlocked) { - OWSLogInfo(@"Received request for block list"); - [self.blockingManager syncBlockList]; - } else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeConfiguration) { - [SSKEnvironment.shared.syncManager sendConfigurationSyncMessage]; - } else { - OWSLogWarn(@"ignoring unsupported sync request message"); - } - } else if (syncMessage.blocked) { - NSArray *blockedPhoneNumbers = [syncMessage.blocked.numbers copy]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self.blockingManager setBlockedPhoneNumbers:blockedPhoneNumbers sendSyncMessage:NO]; - dispatch_async(dispatch_get_main_queue(), ^{ - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.blockedContactsUpdated object:nil]; - }); - }); - } else if (syncMessage.read.count > 0) { - OWSLogInfo(@"Received %lu read receipt(s)", (unsigned long)syncMessage.read.count); - [OWSReadReceiptManager.sharedManager processReadReceiptsFromLinkedDevice:syncMessage.read - readTimestamp:envelope.timestamp - transaction:transaction]; - } else if (syncMessage.verified) { - OWSLogInfo(@"Received verification state for %@", syncMessage.verified.destination); - [self.identityManager throws_processIncomingSyncMessage:syncMessage.verified transaction:transaction]; - } else if (syncMessage.contacts != nil) { - [LKSyncMessagesProtocol handleContactSyncMessageIfNeeded:syncMessage wrappedIn:envelope transaction:transaction]; - } else if (syncMessage.groups != nil) { - [LKSyncMessagesProtocol handleClosedGroupSyncMessageIfNeeded:syncMessage wrappedIn:envelope transaction:transaction]; - } else if (syncMessage.openGroups != nil) { - [LKSyncMessagesProtocol handleOpenGroupSyncMessageIfNeeded:syncMessage wrappedIn:envelope transaction:transaction]; - } else { - OWSLogWarn(@"Ignoring unsupported sync message."); - } -} - -- (void)handleEndSessionMessageWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction]; - [LKSessionManagementProtocol handleEndSessionMessageReceivedInThread:thread using:transaction]; -} - -- (void)handleExpirationTimerUpdateMessageWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - TSThread *_Nullable thread = [self threadForEnvelope:envelope dataMessage:dataMessage transaction:transaction]; - if (!thread) { - OWSFailDebug(@"Ignoring expiring messages update for unknown group."); - return; - } - - OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration; - if (dataMessage.hasExpireTimer && dataMessage.expireTimer > 0) { - OWSLogInfo( - @"Expiring messages duration turned to %u for thread %@", (unsigned int)dataMessage.expireTimer, thread); - disappearingMessagesConfiguration = - [[OWSDisappearingMessagesConfiguration alloc] initWithThreadId:thread.uniqueId - enabled:YES - durationSeconds:dataMessage.expireTimer]; - } else { - OWSLogInfo(@"Expiring messages have been turned off for thread %@", thread); - disappearingMessagesConfiguration = [[OWSDisappearingMessagesConfiguration alloc] - initWithThreadId:thread.uniqueId - enabled:NO - durationSeconds:OWSDisappearingMessagesConfigurationDefaultExpirationDuration]; - } - OWSAssertDebug(disappearingMessagesConfiguration); - [disappearingMessagesConfiguration saveWithTransaction:transaction]; - NSString *name = [dataMessage.profile displayName] ?: [SSKEnvironment.shared.profileManager profileNameForRecipientWithID:envelope.source transaction:transaction] ?: envelope.source; - - // MJK TODO - safe to remove senderTimestamp - OWSDisappearingConfigurationUpdateInfoMessage *message = - [[OWSDisappearingConfigurationUpdateInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - thread:thread - configuration:disappearingMessagesConfiguration - createdByRemoteName:name - createdInExistingGroup:NO]; - [message saveWithTransaction:transaction]; -} - -- (void)handleProfileKeyMessageWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - - NSString *recipientId = envelope.source; - if (!dataMessage.hasProfileKey) { - OWSFailDebug(@"Received a profile key message without a profile key from: %@.", envelopeAddress(envelope)); - return; - } - - NSData *profileKey = dataMessage.profileKey; - if (profileKey.length != kAES256_KeyByteLength) { - OWSFailDebug(@"received profile key of unexpected length: %lu, from: %@", - (unsigned long)profileKey.length, - envelopeAddress(envelope)); - return; - } - - if (dataMessage.profile == nil) { - OWSFailDebug(@"received profile key message without loki profile attached from: %@", envelopeAddress(envelope)); - return; - } - - id profileManager = SSKEnvironment.shared.profileManager; - [profileManager setProfileKeyData:profileKey forRecipientId:recipientId avatarURL:dataMessage.profile.profilePicture]; -} - -- (void)handleReceivedTextMessageWithEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - [self handleReceivedEnvelope:envelope - withDataMessage:dataMessage - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; -} - -- (void)handleGroupInfoRequest:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - if (dataMessage.group.type != SSKProtoGroupContextTypeRequestInfo) { - OWSFailDebug(@"Unexpected group message type."); - return; - } - - NSData *groupId = dataMessage.group ? dataMessage.group.id : nil; - if (!groupId) { - OWSFailDebug(@"Group info request is missing group id."); - return; - } - - OWSLogInfo(@"Received 'Request Group Info' message for group: %@ from: %@", groupId, envelope.source); - - TSGroupThread *_Nullable gThread = [TSGroupThread threadWithGroupId:dataMessage.group.id transaction:transaction]; - if (!gThread) { - OWSLogWarn(@"Unknown group: %@", groupId); - return; - } - - // Ensure sender is in the group. - if (![gThread isUserMemberInGroup:envelope.source transaction:transaction]) { - OWSLogWarn(@"Ignoring 'Request Group Info' message for non-member of group. %@ not in %@", - envelope.source, - gThread.groupModel.groupMemberIds); - return; - } - - // Ensure we are in the group. - if (![gThread isCurrentUserInGroupWithTransaction:transaction]) { - OWSLogWarn(@"Ignoring 'Request Group Info' message for group we no longer belong to."); - return; - } - - NSString *updateGroupInfo = - [gThread.groupModel getInfoStringAboutUpdateTo:gThread.groupModel contactsManager:self.contactsManager]; - - uint32_t expiresInSeconds = [gThread disappearingMessagesDurationWithTransaction:transaction]; - TSOutgoingMessage *message = [TSOutgoingMessage outgoingMessageInThread:gThread - groupMetaMessage:TSGroupMetaMessageUpdate - expiresInSeconds:expiresInSeconds]; - - [message updateWithCustomMessage:updateGroupInfo transaction:transaction]; - // Only send this group update to the requester. - [message updateWithSendingToSingleGroupRecipient:envelope.source transaction:transaction]; - - if (gThread.groupModel.groupImage) { - NSData *_Nullable data = UIImagePNGRepresentation(gThread.groupModel.groupImage); - OWSAssertDebug(data); - if (data) { - DataSource *_Nullable dataSource = [DataSourceValue dataSourceWithData:data fileExtension:@"png"]; - [self.messageSenderJobQueue addMediaMessage:message - dataSource:dataSource - contentType:OWSMimeTypeImagePng - sourceFilename:nil - caption:nil - albumMessageId:nil - isTemporaryAttachment:YES]; - } - } else { - [self.messageSenderJobQueue addMessage:message transaction:transaction]; - } -} - -- (TSIncomingMessage *_Nullable)handleReceivedEnvelope:(SSKProtoEnvelope *)envelope - withDataMessage:(SSKProtoDataMessage *)dataMessage - wasReceivedByUD:(BOOL)wasReceivedByUD - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return nil; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return nil; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return nil; - } - - uint64_t timestamp = envelope.timestamp; - NSString *body = dataMessage.body; - NSData *groupId = dataMessage.group ? dataMessage.group.id : nil; - OWSContact *_Nullable contact = [OWSContacts contactForDataMessage:dataMessage transaction:transaction]; - NSNumber *_Nullable serverTimestamp = (envelope.hasServerTimestamp ? @(envelope.serverTimestamp) : nil); - - if (dataMessage.group.type == SSKProtoGroupContextTypeRequestInfo) { - [self handleGroupInfoRequest:envelope dataMessage:dataMessage transaction:transaction]; - return nil; - } - - /* - // Loki: Update device links in a blocking way - // FIXME: This is horrible for performance - // FIXME: ======== - // The envelope source is set during UD decryption - if ([ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source] && dataMessage.publicChatInfo == nil // Handled in LokiPublicChatPoller for open group messages - && envelope.type != SSKProtoEnvelopeTypeClosedGroupCiphertext) { - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - [[LKMultiDeviceProtocol updateDeviceLinksIfNeededForPublicKey:envelope.source transaction:transaction].ensureOn(queue, ^() { - dispatch_semaphore_signal(semaphore); - }).catchOn(queue, ^(NSError *error) { - dispatch_semaphore_signal(semaphore); - }) retainUntilComplete]; - dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)); - } - // FIXME: ======== - */ - - if (groupId.length > 0) { - NSMutableSet *newMemberIds = [NSMutableSet setWithArray:dataMessage.group.members]; - NSMutableSet *removedMemberIds = [NSMutableSet new]; - for (NSString *recipientId in newMemberIds) { - if (![ECKeyPair isValidHexEncodedPublicKeyWithCandidate:recipientId]) { - OWSLogVerbose(@"Incoming group update has invalid group member: %@", [self descriptionForEnvelope:envelope]); - OWSFailDebug(@"Incoming group update has invalid group member"); - return nil; - } - } - - NSString *senderMasterPublicKey = ([LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source); - NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - NSString *userMasterPublicKey = ([LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userPublicKey in:transaction] ?: userPublicKey); - - // Group messages create the group if it doesn't already exist. - // - // We distinguish between the old group state (if any) and the new group state. - TSGroupThread *_Nullable oldGroupThread = [TSGroupThread threadWithGroupId:groupId transaction:transaction]; - if (oldGroupThread) { - // Loki: Determine removed members - removedMemberIds = [NSMutableSet setWithArray:oldGroupThread.groupModel.groupMemberIds]; - [removedMemberIds minusSet:newMemberIds]; - // TODO: Below is the original code. Is it safe that we modified it like this? - // ======== - // Don't trust other clients; ensure all known group members remain in the - // group unless it is a "quit" message in which case we should only remove - // the quiting member below. -// [newMemberIds addObjectsFromArray:oldGroupThread.groupModel.groupMemberIds]; - // ======== - } - - [LKSessionMetaProtocol updateProfileKeyIfNeededForPublicKey:senderMasterPublicKey using:dataMessage]; - - [LKSessionMetaProtocol updateDisplayNameIfNeededForPublicKey:senderMasterPublicKey using:dataMessage transaction:transaction]; - - switch (dataMessage.group.type) { - case SSKProtoGroupContextTypeUpdate: { - if (oldGroupThread != nil && oldGroupThread.groupModel.groupType == closedGroup - && [LKClosedGroupsProtocol shouldIgnoreClosedGroupUpdateMessage:dataMessage inThread:oldGroupThread wrappedIn:envelope]) { - return nil; - } - // Ensures that the thread exists but don't update it. - TSGroupThread *newGroupThread = - [TSGroupThread getOrCreateThreadWithGroupId:groupId groupType:oldGroupThread.groupModel.groupType transaction:transaction]; - - TSGroupModel *newGroupModel = [[TSGroupModel alloc] initWithTitle:dataMessage.group.name - memberIds:newMemberIds.allObjects - image:oldGroupThread.groupModel.groupImage - groupId:dataMessage.group.id - groupType:oldGroupThread.groupModel.groupType - adminIds:dataMessage.group.admins]; - newGroupModel.removedMembers = removedMemberIds; - NSString *updateGroupInfo = [newGroupThread.groupModel getInfoStringAboutUpdateTo:newGroupModel - contactsManager:self.contactsManager]; - - [newGroupThread setGroupModel:newGroupModel withTransaction:transaction]; - - BOOL wasCurrentUserRemovedFromGroup = [removedMemberIds containsObject:userMasterPublicKey]; - if (!wasCurrentUserRemovedFromGroup) { - [LKClosedGroupsProtocol establishSessionsIfNeededWithClosedGroupMembers:newMemberIds.allObjects transaction:transaction]; - } - - [[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer - thread:newGroupThread - createdByRemoteRecipientId:nil - createdInExistingGroup:YES - transaction:transaction]; - - // MJK TODO - should be safe to remove senderTimestamp - TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:newGroupThread - messageType:TSInfoMessageTypeGroupUpdate - customMessage:updateGroupInfo]; - [infoMessage saveWithTransaction:transaction]; - - // If we were the one that was removed then we need to leave the group - if (wasCurrentUserRemovedFromGroup) { - [newGroupThread leaveGroupWithTransaction:transaction]; - } - - return nil; - } - case SSKProtoGroupContextTypeQuit: { - if (!oldGroupThread) { - OWSLogWarn(@"Ignoring quit group message from unknown group."); - return nil; - } - newMemberIds = [NSMutableSet setWithArray:oldGroupThread.groupModel.groupMemberIds]; - [newMemberIds removeObject:senderMasterPublicKey]; - oldGroupThread.groupModel.groupMemberIds = [newMemberIds.allObjects mutableCopy]; - [oldGroupThread saveWithTransaction:transaction]; - - NSString *nameString = [SSKEnvironment.shared.profileManager profileNameForRecipientWithID:senderMasterPublicKey transaction:transaction] ?: - [self.contactsManager displayNameForPhoneIdentifier:senderMasterPublicKey transaction:transaction]; - NSString *updateGroupInfo = - [NSString stringWithFormat:NSLocalizedString(@"GROUP_MEMBER_LEFT", @""), nameString]; - // MJK TODO - should be safe to remove senderTimestamp - [[[TSInfoMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] - inThread:oldGroupThread - messageType:TSInfoMessageTypeGroupUpdate - customMessage:updateGroupInfo] saveWithTransaction:transaction]; - - // If we were the one that quit then we need to leave the group (only relevant for slave - // devices in a multi device context) - // TODO: This needs more documentation - if (![newMemberIds containsObject:userMasterPublicKey]) { - [oldGroupThread leaveGroupWithTransaction:transaction]; - } - - return nil; - } - case SSKProtoGroupContextTypeDeliver: { - if (!oldGroupThread) { - OWSFailDebug(@"Ignoring deliver group message from unknown group."); - return nil; - } - - [[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer - thread:oldGroupThread - createdByRemoteRecipientId:senderMasterPublicKey - createdInExistingGroup:NO - transaction:transaction]; - - TSQuotedMessage *_Nullable quotedMessage = [TSQuotedMessage quotedMessageForDataMessage:dataMessage - thread:oldGroupThread - transaction:transaction]; - - NSError *linkPreviewError; - OWSLinkPreview *_Nullable linkPreview = - [OWSLinkPreview buildValidatedLinkPreviewWithDataMessage:dataMessage - body:body - transaction:transaction - error:&linkPreviewError]; - if (linkPreviewError && ![OWSLinkPreview isNoPreviewError:linkPreviewError]) { - OWSLogError(@"linkPreviewError: %@", linkPreviewError); - } - - OWSLogDebug(@"Incoming message from: %@ for group: %@ with timestamp: %lu", - envelopeAddress(envelope), - groupId, - (unsigned long)timestamp); - - // Legit usage of senderTimestamp when creating an incoming group message record - TSIncomingMessage *incomingMessage = - [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp - inThread:oldGroupThread - authorId:senderMasterPublicKey - sourceDeviceId:envelope.sourceDevice - messageBody:body - attachmentIds:@[] - expiresInSeconds:dataMessage.expireTimer - quotedMessage:quotedMessage - contactShare:contact - linkPreview:linkPreview - serverTimestamp:serverTimestamp - wasReceivedByUD:wasReceivedByUD]; - - // For open group messages, use the server timestamp as the received timestamp - if (oldGroupThread.isPublicChat) { - [incomingMessage setServerTimestampToReceivedTimestamp:(uint64_t)envelope.serverTimestamp]; - } - - // Loki: Set open group server ID if needed - if (dataMessage.publicChatInfo != nil && dataMessage.publicChatInfo.hasServerID) { - incomingMessage.openGroupServerMessageID = dataMessage.publicChatInfo.serverID; - } - - NSArray *attachmentPointers = - [TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments - albumMessage:incomingMessage]; - for (TSAttachmentPointer *pointer in attachmentPointers) { - [pointer saveWithTransaction:transaction]; - [incomingMessage.attachmentIds addObject:pointer.uniqueId]; - } - - if (body.length == 0 && attachmentPointers.count < 1 && !contact) { - OWSLogWarn(@"Ignoring empty incoming message from: %@ for group: %@ with timestamp: %lu.", - senderMasterPublicKey, - groupId, - (unsigned long)timestamp); - return nil; - } - - // Loki: Cache the user public key (for mentions) - dispatch_async(dispatch_get_main_queue(), ^{ - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - [LKMentionsManager populateUserPublicKeyCacheIfNeededFor:oldGroupThread.uniqueId in:transaction]; - [LKMentionsManager cache:incomingMessage.authorId for:oldGroupThread.uniqueId]; - }]; - }); - - [self finalizeIncomingMessage:incomingMessage - thread:oldGroupThread - masterThread:oldGroupThread - envelope:envelope - transaction:transaction]; - - // Loki: Map the message ID to the message server ID if needed - if (dataMessage.publicChatInfo != nil && dataMessage.publicChatInfo.hasServerID) { - [self.primaryStorage setIDForMessageWithServerID:dataMessage.publicChatInfo.serverID to:incomingMessage.uniqueId in:transaction]; - } - - return incomingMessage; - } - default: { - OWSLogWarn(@"Ignoring unknown group message type: %d.", (int)dataMessage.group.type); - return nil; - } - } - } else { - - // Loki: A message from a slave device should appear as if it came from the master device; the underlying - // friend request logic, however, should still be specific to the slave device. - - NSString *publicKey = envelope.source; - NSString *masterPublicKey = ([LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:envelope.source in:transaction] ?: envelope.source); - TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:publicKey transaction:transaction]; - TSContactThread *masterThread = [TSContactThread getOrCreateThreadWithContactId:masterPublicKey transaction:transaction]; - - OWSLogDebug(@"Incoming message from: %@ with timestamp: %lu.", publicKey, (unsigned long)timestamp); - - [[OWSDisappearingMessagesJob sharedJob] becomeConsistentWithDisappearingDuration:dataMessage.expireTimer - thread:masterThread - createdByRemoteRecipientId:publicKey - createdInExistingGroup:NO - transaction:transaction]; - - TSQuotedMessage *_Nullable quotedMessage = [TSQuotedMessage quotedMessageForDataMessage:dataMessage - thread:masterThread - transaction:transaction]; - - NSError *linkPreviewError; - OWSLinkPreview *_Nullable linkPreview = - [OWSLinkPreview buildValidatedLinkPreviewWithDataMessage:dataMessage - body:body - transaction:transaction - error:&linkPreviewError]; - if (linkPreviewError && ![OWSLinkPreview isNoPreviewError:linkPreviewError]) { - OWSLogError(@"linkPreviewError: %@", linkPreviewError); - } - - // Legit usage of senderTimestamp when creating incoming message from received envelope - TSIncomingMessage *incomingMessage = - [[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp - inThread:masterThread - authorId:masterThread.contactIdentifier - sourceDeviceId:envelope.sourceDevice - messageBody:body - attachmentIds:@[] - expiresInSeconds:dataMessage.expireTimer - quotedMessage:quotedMessage - contactShare:contact - linkPreview:linkPreview - serverTimestamp:serverTimestamp - wasReceivedByUD:wasReceivedByUD]; - - [LKSessionMetaProtocol updateProfileKeyIfNeededForPublicKey:masterPublicKey using:dataMessage]; - - [LKSessionMetaProtocol updateDisplayNameIfNeededForPublicKey:masterPublicKey using:dataMessage transaction:transaction]; - - NSArray *attachmentPointers = - [TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments albumMessage:incomingMessage]; - for (TSAttachmentPointer *pointer in attachmentPointers) { - [pointer saveWithTransaction:transaction]; - [incomingMessage.attachmentIds addObject:pointer.uniqueId]; - } - - if (body.length == 0 && attachmentPointers.count < 1 && !contact) { return nil; } - - [self finalizeIncomingMessage:incomingMessage - thread:thread - masterThread:thread - envelope:envelope - transaction:transaction]; - - return incomingMessage; - } -} - -- (void)finalizeIncomingMessage:(TSIncomingMessage *)incomingMessage - thread:(TSThread *)thread - masterThread:(TSThread *)masterThread - envelope:(SSKProtoEnvelope *)envelope - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return; - } - if (!thread) { - OWSFailDebug(@"Missing thread."); - return; - } - if (!incomingMessage) { - OWSFailDebug(@"Missing incomingMessage."); - return; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return; - } - - [incomingMessage saveWithTransaction:transaction]; - - // Any messages sent from the current user - from this device or another - should be automatically marked as read. - if ([(masterThread.contactIdentifier ?: envelope.source) isEqualToString:self.tsAccountManager.localNumber]) { - // Don't send a read receipt for messages sent by ourselves. - [incomingMessage markAsReadAtTimestamp:envelope.timestamp sendReadReceipt:NO transaction:transaction]; - } - - // Download the "non-message body" attachments. - NSMutableArray *otherAttachmentIds = [incomingMessage.allAttachmentIds mutableCopy]; - if (incomingMessage.attachmentIds) { - [otherAttachmentIds removeObjectsInArray:incomingMessage.attachmentIds]; - } - for (NSString *attachmentId in otherAttachmentIds) { - TSAttachment *_Nullable attachment = - [TSAttachment fetchObjectWithUniqueID:attachmentId transaction:transaction]; - if (![attachment isKindOfClass:[TSAttachmentPointer class]]) { - OWSLogInfo(@"Skipping attachment stream."); - continue; - } - TSAttachmentPointer *_Nullable attachmentPointer = (TSAttachmentPointer *)attachment; - - OWSLogDebug(@"Downloading attachment for message: %lu", (unsigned long)incomingMessage.timestamp); - - // Use a separate download for each attachment so that: - // - // * We update the message as each comes in. - // * Failures don't interfere with successes. - [self.attachmentDownloads downloadAttachmentPointer:attachmentPointer - success:^(NSArray *attachmentStreams) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSAttachmentStream *_Nullable attachmentStream = attachmentStreams.firstObject; - OWSAssertDebug(attachmentStream); - if (attachmentStream && incomingMessage.quotedMessage.thumbnailAttachmentPointerId.length > 0 && - [attachmentStream.uniqueId - isEqualToString:incomingMessage.quotedMessage.thumbnailAttachmentPointerId]) { - [incomingMessage setQuotedMessageThumbnailAttachmentStream:attachmentStream]; - [incomingMessage saveWithTransaction:transaction]; - } else { - // We touch the message to trigger redraw of any views displaying it, - // since the attachment might be a contact avatar, etc. - [incomingMessage touchWithTransaction:transaction]; - } - }]; - } - failure:^(NSError *error) { - OWSLogWarn(@"Failed to download attachment for message: %lu with error: %@.", - (unsigned long)incomingMessage.timestamp, - error); - }]; - } - - // In case we already have a read receipt for this new message (this happens sometimes). - [OWSReadReceiptManager.sharedManager applyEarlyReadReceiptsForIncomingMessage:incomingMessage - transaction:transaction]; - - // Update thread preview in inbox - [masterThread touchWithTransaction:transaction]; - - if (CurrentAppContext().isMainApp) { - [SSKEnvironment.shared.notificationsManager notifyUserForIncomingMessage:incomingMessage inThread:masterThread transaction:transaction]; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - [self.typingIndicators didReceiveIncomingMessageInThread:masterThread - recipientId:(masterThread.contactIdentifier ?: envelope.source) - deviceId:envelope.sourceDevice]; - }); -} - -#pragma mark - helpers - -- (BOOL)isDataMessageGroupAvatarUpdate:(SSKProtoDataMessage *)dataMessage -{ - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return NO; - } - - return (dataMessage.group != nil && dataMessage.group.type == SSKProtoGroupContextTypeUpdate - && dataMessage.group.avatar != nil); -} - -/** - * @returns - * Group or Contact thread for message, creating a new contact thread if necessary, - * but never creating a new group thread. - */ -- (nullable TSThread *)threadForEnvelope:(SSKProtoEnvelope *)envelope - dataMessage:(SSKProtoDataMessage *)dataMessage - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - if (!envelope) { - OWSFailDebug(@"Missing envelope."); - return nil; - } - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return nil; - } - if (!transaction) { - OWSFail(@"Missing transaction."); - return nil; - } - - if (dataMessage.group) { - NSData *groupId = dataMessage.group.id; - OWSAssertDebug(groupId.length > 0); - TSGroupThread *_Nullable groupThread = [TSGroupThread threadWithGroupId:groupId transaction:transaction]; - // This method should only be called from a code path that has already verified - // that this is a "known" group. - OWSAssertDebug(groupThread); - return groupThread; - } else { - return [TSContactThread getOrCreateThreadWithContactId:envelope.source transaction:transaction]; - } -} - -#pragma mark - - -- (void)checkForUnknownLinkedDevice:(SSKProtoEnvelope *)envelope - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(envelope); - OWSAssertDebug(transaction); - - NSString *localNumber = self.tsAccountManager.localNumber; - if (![localNumber isEqualToString:envelope.source]) { - return; - } - - // Consult the device list cache we use for message sending - // whether or not we know about this linked device. - SignalRecipient *_Nullable recipient = - [SignalRecipient registeredRecipientForRecipientId:localNumber mustHaveDevices:NO transaction:transaction]; - if (!recipient) { - - } else { - BOOL isRecipientDevice = [recipient.devices containsObject:@(envelope.sourceDevice)]; - if (!isRecipientDevice) { - OWSLogInfo(@"Message received from unknown linked device; adding to local SignalRecipient: %lu.", - (unsigned long) envelope.sourceDevice); - - [recipient updateRegisteredRecipientWithDevicesToAdd:@[ @(envelope.sourceDevice) ] - devicesToRemove:nil - transaction:transaction]; - } - } - - // Consult the device list cache we use for the "linked device" UI - // whether or not we know about this linked device. - NSMutableSet *deviceIdSet = [NSMutableSet new]; - for (OWSDevice *device in [OWSDevice currentDevicesWithTransaction:transaction]) { - [deviceIdSet addObject:@(device.deviceId)]; - } - BOOL isInDeviceList = [deviceIdSet containsObject:@(envelope.sourceDevice)]; - if (!isInDeviceList) { - OWSLogInfo(@"Message received from unknown linked device; refreshing device list: %lu.", - (unsigned long) envelope.sourceDevice); - - [OWSDevicesService refreshDevices]; - dispatch_async(dispatch_get_main_queue(), ^{ - [self.profileManager fetchLocalUsersProfile]; - }); - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/OWSMessageReceiver.h b/SignalUtilitiesKit/OWSMessageReceiver.h deleted file mode 100644 index f8946d0f2..000000000 --- a/SignalUtilitiesKit/OWSMessageReceiver.h +++ /dev/null @@ -1,28 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class OWSPrimaryStorage; -@class OWSStorage; - -// This class is used to write incoming (encrypted, unprocessed) -// messages to a durable queue and then decrypt them in the order -// in which they were received. Successfully decrypted messages -// are forwarded to OWSBatchMessageProcessor. -@interface OWSMessageReceiver : NSObject - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -+ (NSString *)databaseExtensionName; -+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage; - -- (void)handleReceivedEnvelopeData:(NSData *)envelopeData; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/OWSMessageReceiver.m b/SignalUtilitiesKit/OWSMessageReceiver.m deleted file mode 100644 index ecf4abba6..000000000 --- a/SignalUtilitiesKit/OWSMessageReceiver.m +++ /dev/null @@ -1,513 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageReceiver.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "NSArray+OWS.h" -#import "NotificationsProtocol.h" -#import "OWSBackgroundTask.h" -#import "OWSBatchMessageProcessor.h" -#import "OWSMessageDecrypter.h" -#import "OWSPrimaryStorage+Loki.h" -#import "OWSQueues.h" -#import "OWSStorage.h" -#import "OWSIdentityManager.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSDatabaseView.h" -#import "TSErrorMessage.h" -#import "TSYapDatabaseObject.h" -#import -#import -#import -#import -#import -#import -#import - -#import "SSKAsserts.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSMessageDecryptJob : TSYapDatabaseObject - -@property (nonatomic, readonly) NSDate *createdAt; -@property (nonatomic, readonly) NSData *envelopeData; -@property (nonatomic, readonly, nullable) SSKProtoEnvelope *envelopeProto; - -- (instancetype)initWithEnvelopeData:(NSData *)envelopeData NS_DESIGNATED_INITIALIZER; -- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithUniqueId:(NSString *_Nullable)uniqueId NS_UNAVAILABLE; - -@end - -#pragma mark - - -@implementation OWSMessageDecryptJob - -+ (NSString *)collection -{ - return @"OWSMessageProcessingJob"; -} - -- (instancetype)initWithEnvelopeData:(NSData *)envelopeData -{ - OWSAssertDebug(envelopeData); - - self = [super initWithUniqueId:[NSUUID new].UUIDString]; - if (!self) { - return self; - } - - _envelopeData = envelopeData; - _createdAt = [NSDate new]; - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - return [super initWithCoder:coder]; -} - -- (nullable SSKProtoEnvelope *)envelopeProto -{ - NSError *error; - SSKProtoEnvelope *_Nullable envelope = [SSKProtoEnvelope parseData:self.envelopeData error:&error]; - if (error || envelope == nil) { - OWSFailDebug(@"failed to parse envelope with error: %@", error); - return nil; - } - - return envelope; -} - -@end - -#pragma mark - Finder - -NSString *const OWSMessageDecryptJobFinderExtensionName = @"OWSMessageProcessingJobFinderExtensionName2"; -NSString *const OWSMessageDecryptJobFinderExtensionGroup = @"OWSMessageProcessingJobFinderExtensionGroup2"; - -@interface OWSMessageDecryptJobFinder : NSObject - -@end - -#pragma mark - - -@interface OWSMessageDecryptJobFinder () - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSMessageDecryptJobFinder - -- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection -{ - OWSSingletonAssert(); - - self = [super init]; - if (!self) { - return self; - } - - _dbConnection = dbConnection; - - [OWSMessageDecryptJobFinder registerLegacyClasses]; - - return self; -} - -- (OWSMessageDecryptJob *_Nullable)nextJob -{ - __block OWSMessageDecryptJob *_Nullable job = nil; - - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - YapDatabaseViewTransaction *viewTransaction = [transaction ext:OWSMessageDecryptJobFinderExtensionName]; - OWSAssertDebug(viewTransaction != nil); - job = [viewTransaction firstObjectInGroup:OWSMessageDecryptJobFinderExtensionGroup]; - }]; - - return job; -} - -- (void)addJobForEnvelopeData:(NSData *)envelopeData -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - OWSMessageDecryptJob *job = [[OWSMessageDecryptJob alloc] initWithEnvelopeData:envelopeData]; - [job saveWithTransaction:transaction]; - }]; -} - -- (void)removeJobWithId:(NSString *)uniqueId -{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [transaction removeObjectForKey:uniqueId inCollection:[OWSMessageDecryptJob collection]]; - }]; -} - -+ (YapDatabaseView *)databaseExtension -{ - YapDatabaseViewSorting *sorting = - [YapDatabaseViewSorting withObjectBlock:^NSComparisonResult(YapDatabaseReadTransaction *transaction, - NSString *group, - NSString *collection1, - NSString *key1, - id object1, - NSString *collection2, - NSString *key2, - id object2) { - - if (![object1 isKindOfClass:[OWSMessageDecryptJob class]]) { - OWSFailDebug(@"Unexpected object: %@ in collection: %@", [object1 class], collection1); - return NSOrderedSame; - } - OWSMessageDecryptJob *job1 = (OWSMessageDecryptJob *)object1; - - if (![object2 isKindOfClass:[OWSMessageDecryptJob class]]) { - OWSFailDebug(@"Unexpected object: %@ in collection: %@", [object2 class], collection2); - return NSOrderedSame; - } - OWSMessageDecryptJob *job2 = (OWSMessageDecryptJob *)object2; - - return [job1.createdAt compare:job2.createdAt]; - }]; - - YapDatabaseViewGrouping *grouping = - [YapDatabaseViewGrouping withObjectBlock:^NSString *_Nullable(YapDatabaseReadTransaction *_Nonnull transaction, - NSString *_Nonnull collection, - NSString *_Nonnull key, - id _Nonnull object) { - if (![object isKindOfClass:[OWSMessageDecryptJob class]]) { - OWSFailDebug(@"Unexpected object: %@ in collection: %@", object, collection); - return nil; - } - - // Arbitrary string - all in the same group. We're only using the view for sorting. - return OWSMessageDecryptJobFinderExtensionGroup; - }]; - - YapDatabaseViewOptions *options = [YapDatabaseViewOptions new]; - options.allowedCollections = - [[YapWhitelistBlacklist alloc] initWithWhitelist:[NSSet setWithObject:[OWSMessageDecryptJob collection]]]; - - return [[YapDatabaseAutoView alloc] initWithGrouping:grouping sorting:sorting versionTag:@"1" options:options]; -} - -+ (void)registerLegacyClasses -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // We've renamed OWSMessageProcessingJob to OWSMessageDecryptJob. - [NSKeyedUnarchiver setClass:[OWSMessageDecryptJob class] forClassName:[OWSMessageDecryptJob collection]]; - }); -} - -+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage -{ - [self registerLegacyClasses]; - - YapDatabaseView *existingView = [storage registeredExtension:OWSMessageDecryptJobFinderExtensionName]; - if (existingView) { - OWSFailDebug(@"%@ was already initialized.", OWSMessageDecryptJobFinderExtensionName); - // already initialized - return; - } - [storage asyncRegisterExtension:[self databaseExtension] withName:OWSMessageDecryptJobFinderExtensionName]; -} - -@end - -#pragma mark - Queue Processing - -@interface OWSMessageDecryptQueue : NSObject - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) OWSMessageDecryptJobFinder *finder; -@property (nonatomic) BOOL isDrainingQueue; - -- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection - finder:(OWSMessageDecryptJobFinder *)finder NS_DESIGNATED_INITIALIZER; -- (instancetype)init NS_UNAVAILABLE; - -@end - -#pragma mark - - -@implementation OWSMessageDecryptQueue - -- (instancetype)initWithDBConnection:(YapDatabaseConnection *)dbConnection finder:(OWSMessageDecryptJobFinder *)finder -{ - OWSSingletonAssert(); - - self = [super init]; - if (!self) { - return self; - } - - _dbConnection = dbConnection; - _finder = finder; - _isDrainingQueue = NO; - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - [self drainQueue]; - } - }]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(registrationStateDidChange:) - name:RegistrationStateDidChangeNotification - object:nil]; - - return self; -} - -#pragma mark - Singletons - -- (OWSMessageDecrypter *)messageDecrypter -{ - OWSAssertDebug(SSKEnvironment.shared.messageDecrypter); - - return SSKEnvironment.shared.messageDecrypter; -} - -- (OWSBatchMessageProcessor *)batchMessageProcessor -{ - OWSAssertDebug(SSKEnvironment.shared.batchMessageProcessor); - - return SSKEnvironment.shared.batchMessageProcessor; -} - -- (TSAccountManager *)tsAccountManager -{ - OWSAssertDebug(SSKEnvironment.shared.tsAccountManager); - - return SSKEnvironment.shared.tsAccountManager; -} - -#pragma mark - Notifications - -- (void)registrationStateDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - [self drainQueue]; - } - }]; -} - -#pragma mark - Instance methods - -- (dispatch_queue_t)serialQueue -{ - static dispatch_queue_t queue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - queue = dispatch_queue_create("org.whispersystems.message.decrypt", DISPATCH_QUEUE_SERIAL); - }); - return queue; -} - -- (void)enqueueEnvelopeData:(NSData *)envelopeData -{ - [self.finder addJobForEnvelopeData:envelopeData]; -} - -- (void)drainQueue -{ - OWSAssertDebug(AppReadiness.isAppReady); - - if (!CurrentAppContext().isMainApp) { return; } - if (!self.tsAccountManager.isRegisteredAndReady) { return; } - - dispatch_async(self.serialQueue, ^{ - if (self.isDrainingQueue) { return; } - self.isDrainingQueue = YES; - [self drainQueueWorkStep]; - }); -} - -- (void)drainQueueWorkStep -{ - AssertOnDispatchQueue(self.serialQueue); - - OWSMessageDecryptJob *_Nullable job = [self.finder nextJob]; - - if (!job) { - self.isDrainingQueue = NO; - OWSLogVerbose(@"Queue is drained."); - return; - } - - __block OWSBackgroundTask *_Nullable backgroundTask = - [OWSBackgroundTask backgroundTaskWithLabelStr:__PRETTY_FUNCTION__]; - - [self processJob:job - completion:^(BOOL success) { - [self.finder removeJobWithId:job.uniqueId]; - OWSLogVerbose(@"%@ job. %lu jobs left.", - success ? @"decrypted" : @"failed to decrypt", - (unsigned long)[OWSMessageDecryptJob numberOfKeysInCollection]); - [self drainQueueWorkStep]; - OWSAssertDebug(backgroundTask); - backgroundTask = nil; - }]; -} - -- (BOOL)wasReceivedByUD:(SSKProtoEnvelope *)envelope -{ - return (envelope.type == SSKProtoEnvelopeTypeUnidentifiedSender && (!envelope.hasSource || envelope.source.length < 1)); -} - -- (void)processJob:(OWSMessageDecryptJob *)job completion:(void (^)(BOOL))completion -{ - AssertOnDispatchQueue(self.serialQueue); - OWSAssertDebug(job); - - SSKProtoEnvelope *_Nullable envelope = job.envelopeProto; - - if (!envelope) { - OWSFailDebug(@"Couldn't parse proto."); - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - TSErrorMessage *errorMessage = [TSErrorMessage corruptedMessageInUnknownThread]; - [SSKEnvironment.shared.notificationsManager notifyUserForThreadlessErrorMessage:errorMessage - transaction:transaction]; - }]; - - dispatch_async(self.serialQueue, ^{ - completion(NO); - }); - - return; - } - - // We use the original envelope for this check; - // the decryption process might rewrite the envelope. - BOOL wasReceivedByUD = [self wasReceivedByUD:envelope]; - - [self.messageDecrypter decryptEnvelope:envelope - envelopeData:job.envelopeData - successBlock:^(OWSMessageDecryptResult *result, YapDatabaseReadWriteTransaction *transaction) { - OWSAssertDebug(transaction); - - if ([LKSessionMetaProtocol shouldSkipMessageDecryptResult:result wrappedIn:envelope]) { - dispatch_async(self.serialQueue, ^{ - completion(YES); - }); - return; - } - - // We persist the decrypted envelope data in the same transaction within which - // it was decrypted to prevent data loss. If the new job isn't persisted, - // the session state side effects of its decryption are also rolled back. - // - // NOTE: We use envelopeData from the decrypt result, not job.envelopeData, - // since the envelope may be altered by the decryption process in the UD case. - [self.batchMessageProcessor enqueueEnvelopeData:result.envelopeData - plaintextData:result.plaintextData - wasReceivedByUD:wasReceivedByUD - transaction:transaction]; - - dispatch_async(self.serialQueue, ^{ - completion(YES); - }); - } - failureBlock:^{ - dispatch_async(self.serialQueue, ^{ - completion(NO); - }); - }]; -} - -@end - -#pragma mark - OWSMessageReceiver - -@interface OWSMessageReceiver () - -@property (nonatomic, readonly) OWSMessageDecryptQueue *processingQueue; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -@end - -#pragma mark - - -@implementation OWSMessageReceiver - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - OWSSingletonAssert(); - - self = [super init]; - - if (!self) { - return self; - } - - // For coherency we use the same dbConnection to persist and read the unprocessed envelopes - YapDatabaseConnection *dbConnection = [primaryStorage newDatabaseConnection]; - OWSMessageDecryptJobFinder *finder = [[OWSMessageDecryptJobFinder alloc] initWithDBConnection:dbConnection]; - OWSMessageDecryptQueue *processingQueue = [[OWSMessageDecryptQueue alloc] initWithDBConnection:dbConnection finder:finder]; - - _processingQueue = processingQueue; - - [AppReadiness runNowOrWhenAppDidBecomeReady:^{ - if (CurrentAppContext().isMainApp) { - [self.processingQueue drainQueue]; - } - }]; - - return self; -} - -#pragma mark - class methods - -+ (NSString *)databaseExtensionName -{ - return OWSMessageDecryptJobFinderExtensionName; -} - -+ (void)asyncRegisterDatabaseExtension:(OWSStorage *)storage -{ - [OWSMessageDecryptJobFinder asyncRegisterDatabaseExtension:storage]; -} - -#pragma mark - instance methods - -- (void)handleReceivedEnvelopeData:(NSData *)envelopeData -{ - if (envelopeData.length < 1) { - OWSFailDebug(@"Received an empty envelope."); - return; - } - - // Drop any too-large messages on the floor. Well behaving clients should never send them. - NSUInteger kMaxEnvelopeByteCount = 250 * 1024; - if (envelopeData.length > kMaxEnvelopeByteCount) { - OWSFailDebug(@"Received an oversized message."); - return; - } - - // Take note of any messages larger than we expect, but still process them. - // This likely indicates a misbehaving sending client. - NSUInteger kLargeEnvelopeWarningByteCount = 25 * 1024; - if (envelopeData.length > kLargeEnvelopeWarningByteCount) { - OWSFailDebug(@"Received an unexpectedly large message."); - } - - [self.processingQueue enqueueEnvelopeData:envelopeData]; - [self.processingQueue drainQueue]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/OWSMessageSend.swift b/SignalUtilitiesKit/OWSMessageSend.swift deleted file mode 100644 index 36dd9241f..000000000 --- a/SignalUtilitiesKit/OWSMessageSend.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation - - -// Corresponds to a single effort to send a message to a given recipient, -// which may span multiple attempts. Note that group messages may be sent -// to multiple recipients and therefore require multiple instances of -// OWSMessageSend. -@objc -public class OWSMessageSend: NSObject { - @objc - public let message: TSOutgoingMessage - - // thread may be nil if message is an OWSOutgoingSyncMessage. - @objc - public let thread: TSThread? - - @objc - public let recipient: SignalRecipient - - private static let kMaxRetriesPerRecipient: Int = 1 // Loki: We have our own retrying - - @objc - public var remainingAttempts = OWSMessageSend.kMaxRetriesPerRecipient - - // We "fail over" to REST sends after _any_ error sending - // via the web socket. - @objc - public var hasWebsocketSendFailed = false - - @objc - public var udAccess: OWSUDAccess? - - @objc - public var senderCertificate: SMKSenderCertificate? - - @objc - public let localNumber: String - - @objc - public let isLocalNumber: Bool - - @objc - public let success: () -> Void - - @objc - public let failure: (Error) -> Void - - @objc - public init(message: TSOutgoingMessage, - thread: TSThread?, - recipient: SignalRecipient, - senderCertificate: SMKSenderCertificate?, - udAccess: OWSUDAccess?, - localNumber: String, - success: @escaping () -> Void, - failure: @escaping (Error) -> Void) { - self.message = message - self.thread = thread - self.recipient = recipient - self.localNumber = localNumber - self.senderCertificate = senderCertificate - self.udAccess = udAccess - - if let recipientId = recipient.uniqueId { - self.isLocalNumber = localNumber == recipientId - } else { - owsFailDebug("SignalRecipient missing recipientId") - self.isLocalNumber = false - } - - self.success = success - self.failure = failure - } - - @objc - public var isUDSend: Bool { - return udAccess != nil && senderCertificate != nil - } - - @objc - public func disableUD() { - Logger.verbose("\(recipient.recipientId)") - udAccess = nil - } - - @objc - public func setHasUDAuthFailed() { - Logger.verbose("\(recipient.recipientId)") - // We "fail over" to non-UD sends after auth errors sending via UD. - disableUD() - } -} diff --git a/SignalUtilitiesKit/OWSMessageSender.h b/SignalUtilitiesKit/OWSMessageSender.h deleted file mode 100644 index ed52d2019..000000000 --- a/SignalUtilitiesKit/OWSMessageSender.h +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -extern const NSUInteger kOversizeTextMessageSizeThreshold; - -@class OWSBlockingManager; -@class OWSPrimaryStorage; -@class TSAttachmentStream; -@class TSInvalidIdentityKeySendingErrorMessage; -@class TSNetworkManager; -@class TSOutgoingMessage; -@class TSThread; -@class YapDatabaseReadWriteTransaction; -@class OWSMessageSend; - -@protocol ContactsManagerProtocol; - -/** - * Useful for when you *sometimes* want to retry before giving up and calling the failure handler - * but *sometimes* we don't want to retry when we know it's a terminal failure, so we allow the - * caller to indicate this with isRetryable=NO. - */ -typedef void (^RetryableFailureHandler)(NSError *_Nonnull error); - -// Message send error handling is slightly different for contact and group messages. -// -// For example, If one member of a group deletes their account, the group should -// ignore errors when trying to send messages to this ex-member. - -#pragma mark - - -NS_SWIFT_NAME(OutgoingAttachmentInfo) -@interface OWSOutgoingAttachmentInfo : NSObject - -@property (nonatomic, readonly) DataSource *dataSource; -@property (nonatomic, readonly) NSString *contentType; -@property (nonatomic, readonly, nullable) NSString *sourceFilename; -@property (nonatomic, readonly, nullable) NSString *caption; -@property (nonatomic, readonly, nullable) NSString *albumMessageId; - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithDataSource:(DataSource *)dataSource - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId NS_DESIGNATED_INITIALIZER; - -@end - -#pragma mark - - -NS_SWIFT_NAME(MessageSender) -@interface OWSMessageSender : NSObject - -- (instancetype)init NS_UNAVAILABLE; - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -/** - * Send and resend text messages or resend messages with existing attachments. - * If you haven't yet created the attachment, see the `sendAttachment:` variants. - */ -- (void)sendMessage:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler; - -/** - * Takes care of allocating and uploading the attachment, then sends the message. - * Only necessary to call once. If sending fails, retry with `sendMessage:`. - */ -- (void)sendAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - albumMessageId:(nullable NSString *)albumMessageId - inMessage:(TSOutgoingMessage *)outgoingMessage - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler; - -- (void)sendAttachments:(NSArray *)attachmentInfos - inMessage:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler; - -/** - * Same as `sendAttachment:`, but deletes the local copy of the attachment after sending. - * Used for sending sync request data, not for user visible attachments. - */ -- (void)sendTemporaryAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - inMessage:(TSOutgoingMessage *)outgoingMessage - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler; - -- (void)sendMessage:(OWSMessageSend *)messageSend; - -@end - -#pragma mark - - -@interface OutgoingMessagePreparer : NSObject - -/// Persists all necessary data to disk before sending, e.g. generate thumbnails -+ (NSArray *)prepareMessageForSending:(TSOutgoingMessage *)message - transaction:(YapDatabaseReadWriteTransaction *)transaction; - -/// Writes attachment to disk and applies original filename to message attributes -+ (void)prepareAttachments:(NSArray *)attachmentInfos - inMessage:(TSOutgoingMessage *)outgoingMessage - completionHandler:(void (^)(NSError *_Nullable error))completionHandler - NS_SWIFT_NAME(prepareAttachments(_:inMessage:completionHandler:)); - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/OWSMessageSender.m b/SignalUtilitiesKit/OWSMessageSender.m deleted file mode 100644 index a0f889952..000000000 --- a/SignalUtilitiesKit/OWSMessageSender.m +++ /dev/null @@ -1,1744 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSMessageSender.h" -#import "AppContext.h" -#import "NSData+keyVersionByte.h" -#import "NSData+messagePadding.h" -#import "NSError+MessageSending.h" -#import "OWSBackgroundTask.h" -#import "OWSBlockingManager.h" -#import "OWSContact.h" -#import "OWSDevice.h" -#import "OWSDisappearingMessagesJob.h" -#import "OWSDispatch.h" -#import "OWSEndSessionMessage.h" -#import "OWSError.h" -#import "OWSIdentityManager.h" -#import "OWSMessageServiceParams.h" -#import "OWSOperation.h" -#import "OWSOutgoingSentMessageTranscript.h" -#import "OWSOutgoingSyncMessage.h" -#import "OWSPrimaryStorage+PreKeyStore.h" -#import "OWSPrimaryStorage+SignedPreKeyStore.h" -#import "OWSPrimaryStorage+sessionStore.h" -#import "OWSPrimaryStorage+Loki.h" -#import "OWSPrimaryStorage.h" -#import "OWSRequestFactory.h" -#import "OWSUploadOperation.h" -#import "PreKeyBundle+jsonDict.h" -#import "SSKEnvironment.h" -#import "SignalRecipient.h" -#import "TSAccountManager.h" -#import "TSAttachmentStream.h" -#import "TSContactThread.h" -#import "TSGroupThread.h" -#import "TSIncomingMessage.h" -#import "TSInfoMessage.h" -#import "TSInvalidIdentityKeySendingErrorMessage.h" -#import "TSNetworkManager.h" -#import "TSOutgoingMessage.h" -#import "TSPreKeyManager.h" -#import "TSQuotedMessage.h" -#import "TSRequest.h" -#import "TSSocketManager.h" -#import "TSThread.h" -#import "TSContactThread.h" -#import "LKDeviceLinkMessage.h" -#import "LKUnlinkDeviceMessage.h" -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import -#import "SSKAsserts.h" -#import "SignalRecipient.h" - -NS_ASSUME_NONNULL_BEGIN - -NSString *NoSessionForTransientMessageException = @"NoSessionForTransientMessageException"; - -const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024; - -NSError *SSKEnsureError(NSError *_Nullable error, OWSErrorCode fallbackCode, NSString *fallbackErrorDescription) -{ - if (error) { - return error; - } - OWSCFailDebug(@"Using fallback error."); - return OWSErrorWithCodeDescription(fallbackCode, fallbackErrorDescription); -} - -#pragma mark - - -void AssertIsOnSendingQueue() -{ -#ifdef DEBUG - if (@available(iOS 10.0, *)) { - dispatch_assert_queue([OWSDispatch sendingQueue]); - } // else, skip assert as it's a development convenience. -#endif -} - -#pragma mark - - -@implementation OWSOutgoingAttachmentInfo - -- (instancetype)initWithDataSource:(DataSource *)dataSource - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - caption:(nullable NSString *)caption - albumMessageId:(nullable NSString *)albumMessageId -{ - self = [super init]; - if (!self) { - return self; - } - - _dataSource = dataSource; - _contentType = contentType; - _sourceFilename = sourceFilename; - _caption = caption; - _albumMessageId = albumMessageId; - - return self; -} - -@end - -#pragma mark - - -/** - * OWSSendMessageOperation encapsulates all the work associated with sending a message, e.g. uploading attachments, - * getting proper keys, and retrying upon failure. - * - * Used by `OWSMessageSender` to serialize message sending, ensuring that messages are emitted in the order they - * were sent. - */ -@interface OWSSendMessageOperation : OWSOperation - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithMessage:(TSOutgoingMessage *)message - messageSender:(OWSMessageSender *)messageSender - dbConnection:(YapDatabaseConnection *)dbConnection - success:(void (^)(void))aSuccessHandler - failure:(void (^)(NSError * error))aFailureHandler NS_DESIGNATED_INITIALIZER; - -@end - -#pragma mark - - -@interface OWSMessageSender (OWSSendMessageOperation) - -- (void)sendMessageToService:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(RetryableFailureHandler)failureHandler; - -@end - -#pragma mark - - -@interface OWSSendMessageOperation () - -@property (nonatomic, readonly) TSOutgoingMessage *message; -@property (nonatomic, readonly) OWSMessageSender *messageSender; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) void (^successHandler)(void); -@property (nonatomic, readonly) void (^failureHandler)(NSError *error); - -@end - -#pragma mark - - -@implementation OWSSendMessageOperation - -- (instancetype)initWithMessage:(TSOutgoingMessage *)message - messageSender:(OWSMessageSender *)messageSender - dbConnection:(YapDatabaseConnection *)dbConnection - success:(void (^)(void))successHandler - failure:(void (^)(NSError * error))failureHandler -{ - self = [super init]; - - if (!self) { - return self; - } - - _message = message; - _messageSender = messageSender; - _dbConnection = dbConnection; - _successHandler = successHandler; - _failureHandler = failureHandler; - - return self; -} - -#pragma mark - OWSOperation overrides - -- (nullable NSError *)checkForPreconditionError -{ - __block NSError *_Nullable error = [super checkForPreconditionError]; - if (error) { return error; } - - if (self.message.hasAttachments) { - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - for (TSAttachment *attachment in [self.message attachmentsWithTransaction:transaction]) { - if (![attachment isKindOfClass:[TSAttachmentStream class]]) { - error = OWSErrorMakeFailedToSendOutgoingMessageError(); - break; - } - - TSAttachmentStream *attachmentStream = (TSAttachmentStream *)attachment; - OWSAssertDebug(attachmentStream); - OWSAssertDebug(attachmentStream.serverId); - OWSAssertDebug(attachmentStream.isUploaded); - } - }]; - } - - return error; -} - -- (void)run -{ - if (self.message.shouldBeSaved && ![TSOutgoingMessage fetchObjectWithUniqueID:self.message.uniqueId]) { - OWSLogInfo(@"Aborting message send; message deleted."); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeMessageDeletedBeforeSent, @"Message was deleted before it could be sent."); - error.isFatal = YES; - [self reportError:error]; - return; - } - - [self.messageSender sendMessageToService:self.message - success:^{ - [self reportSuccess]; - } - failure:^(NSError *error) { - [self reportError:error]; - }]; -} - -- (void)didSucceed -{ - if (self.message.messageState != TSOutgoingMessageStateSent) { - [LKLogger print:@"[Loki] Succeeded with sending a message, but the message state isn't TSOutgoingMessageStateSent."]; - } - - self.successHandler(); -} - -- (void)didFailWithError:(NSError *)error -{ - OWSLogError(@"Message failed to send due to error: %@.", error); - self.failureHandler(error); -} - -@end - -#pragma mark - - -NSString *const OWSMessageSenderInvalidDeviceException = @"InvalidDeviceException"; -NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; - -@interface OWSMessageSender () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (atomic, readonly) NSMutableDictionary *sendingQueueMap; - -@end - -#pragma mark - - -@implementation OWSMessageSender - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - _sendingQueueMap = [NSMutableDictionary new]; - _dbConnection = primaryStorage.newDatabaseConnection; - - OWSSingletonAssert(); - - return self; -} - -#pragma mark - Dependencies - -- (id)contactsManager -{ - OWSAssertDebug(SSKEnvironment.shared.contactsManager); - - return SSKEnvironment.shared.contactsManager; -} - -- (OWSBlockingManager *)blockingManager -{ - OWSAssertDebug(SSKEnvironment.shared.blockingManager); - - return SSKEnvironment.shared.blockingManager; -} - -- (TSNetworkManager *)networkManager -{ - OWSAssertDebug(SSKEnvironment.shared.networkManager); - - return SSKEnvironment.shared.networkManager; -} - -- (id)udManager -{ - OWSAssertDebug(SSKEnvironment.shared.udManager); - - return SSKEnvironment.shared.udManager; -} - -- (TSAccountManager *)tsAccountManager -{ - return TSAccountManager.sharedInstance; -} - -- (OWSIdentityManager *)identityManager -{ - return SSKEnvironment.shared.identityManager; -} - -#pragma mark - - -- (NSOperationQueue *)sendingQueueForMessage:(TSOutgoingMessage *)message -{ - OWSAssertDebug(message); - - - NSString *kDefaultQueueKey = @"kDefaultQueueKey"; - NSString *queueKey = message.uniqueThreadId ?: kDefaultQueueKey; - OWSAssertDebug(queueKey.length > 0); - - if ([kDefaultQueueKey isEqualToString:queueKey]) { - // when do we get here? - OWSLogDebug(@"using default message queue"); - } - - @synchronized(self) - { - NSOperationQueue *sendingQueue = self.sendingQueueMap[queueKey]; - - if (!sendingQueue) { - sendingQueue = [NSOperationQueue new]; - sendingQueue.qualityOfService = NSOperationQualityOfServiceUserInitiated; - sendingQueue.maxConcurrentOperationCount = 1; - sendingQueue.name = [NSString stringWithFormat:@"%@:%@", self.logTag, queueKey]; - self.sendingQueueMap[queueKey] = sendingQueue; - } - - return sendingQueue; - } -} - -- (void)sendMessage:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - OWSAssertDebug(message); - - if (message.body.length > 0) { - OWSAssertDebug([message.body lengthOfBytesUsingEncoding:NSUTF8StringEncoding] <= kOversizeTextMessageSizeThreshold); - } - - if (message.shouldBeSaved && !message.thread.isGroupThread && ![LKSessionMetaProtocol isThreadNoteToSelf:message.thread]) { - // Loki: Not strictly true but nice from a UI point of view - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.calculatingPoW object:[[NSNumber alloc] initWithUnsignedLongLong:message.timestamp]]; - } - - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ - NSMutableArray *allAttachmentIds = [NSMutableArray new]; - - // This method will use a read/write transaction. This transaction - // will block until any open read/write transactions are complete. - // - // That's key - we don't want to send any messages in response - // to an incoming message until processing of that batch of messages - // is complete. For example, we wouldn't want to auto-reply to a - // group info request before that group info request's batch was - // finished processing. Otherwise, we might receive a delivery - // notice for a group update we hadn't yet saved to the database. - // - // So we're using YDB behavior to ensure this invariant, which is a bit - // unorthodox. - if (message.allAttachmentIds.count > 0) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [allAttachmentIds addObjectsFromArray:[OutgoingMessagePreparer prepareMessageForSending:message transaction:transaction]]; - }]; - } - - NSOperationQueue *sendingQueue = [self sendingQueueForMessage:message]; - - OWSSendMessageOperation *sendMessageOperation = - [[OWSSendMessageOperation alloc] initWithMessage:message - messageSender:self - dbConnection:self.dbConnection - success:successHandler - failure:failureHandler]; - - for (NSString *attachmentId in allAttachmentIds) { - OWSUploadOperation *uploadAttachmentOperation = - [[OWSUploadOperation alloc] initWithAttachmentId:attachmentId - threadID:message.thread.uniqueId - dbConnection:self.dbConnection]; - - [sendMessageOperation addDependency:uploadAttachmentOperation]; - [sendingQueue addOperation:uploadAttachmentOperation]; - } - - [sendingQueue addOperation:sendMessageOperation]; - }); -} - -- (void)sendTemporaryAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - inMessage:(TSOutgoingMessage *)message - success:(void (^)(void))successHandler - failure:(void (^)(NSError *error))failureHandler -{ - OWSAssertDebug(dataSource); - - void (^successWithDeleteHandler)(void) = ^() { - successHandler(); - - OWSLogDebug(@"Removing successful temporary attachment message with attachment ids: %@", message.attachmentIds); - [message remove]; - }; - - void (^failureWithDeleteHandler)(NSError *error) = ^(NSError *error) { - failureHandler(error); - - OWSLogDebug(@"Removing failed temporary attachment message with attachment ids: %@", message.attachmentIds); - [message remove]; - }; - - [self sendAttachment:dataSource - contentType:contentType - sourceFilename:nil - albumMessageId:nil - inMessage:message - success:successWithDeleteHandler - failure:failureWithDeleteHandler]; -} - -- (void)sendAttachment:(DataSource *)dataSource - contentType:(NSString *)contentType - sourceFilename:(nullable NSString *)sourceFilename - albumMessageId:(nullable NSString *)albumMessageId - inMessage:(TSOutgoingMessage *)message - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure -{ - OWSAssertDebug(dataSource); - - OWSOutgoingAttachmentInfo *attachmentInfo = [[OWSOutgoingAttachmentInfo alloc] initWithDataSource:dataSource - contentType:contentType - sourceFilename:sourceFilename - caption:nil - albumMessageId:albumMessageId]; - [self sendAttachments:@[ attachmentInfo, ] - inMessage:message - success:success - failure:failure]; -} - -- (void)sendAttachments:(NSArray *)attachmentInfos - inMessage:(TSOutgoingMessage *)message - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure -{ - OWSAssertDebug(attachmentInfos.count > 0); - - [OutgoingMessagePreparer prepareAttachments:attachmentInfos - inMessage:message - completionHandler:^(NSError *_Nullable error) { - if (error) { - failure(error); - return; - } - [self sendMessage:message success:success failure:failure]; - }]; -} - -- (void)sendMessageToService:(TSOutgoingMessage *)message - success:(void (^)(void))success - failure:(RetryableFailureHandler)failure -{ - [self.udManager ensureSenderCertificateWithSuccess:^(SMKSenderCertificate *senderCertificate) { - OWSAssertDebug(senderCertificate != nil); - dispatch_async(OWSDispatch.sendingQueue, ^{ - [self sendMessageToService:message senderCertificate:senderCertificate success:success failure:failure]; - }); - } - failure:^(NSError *error) { // Should never occur - dispatch_async(OWSDispatch.sendingQueue, ^{ - [self sendMessageToService:message senderCertificate:nil success:success failure:failure]; - }); - }]; -} - -- (nullable NSArray *)unsentRecipientsForMessage:(TSOutgoingMessage *)message - thread:(nullable TSThread *)thread - error:(NSError **)errorHandle -{ - OWSAssertDebug(message); - OWSAssertDebug(errorHandle); - - NSString *userPublicKey = self.tsAccountManager.localNumber; - - __block NSMutableSet *recipientIds = [NSMutableSet new]; - if ([message isKindOfClass:OWSOutgoingSyncMessage.class]) { - recipientIds = [LKSessionMetaProtocol getDestinationsForOutgoingSyncMessage]; - } else if (thread.isGroupThread) { - TSGroupThread *groupThread = (TSGroupThread *)thread; - recipientIds = [LKSessionMetaProtocol getDestinationsForOutgoingGroupMessage:message inThread:thread]; - __block NSString *userMasterPublicKey; - [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - userMasterPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userPublicKey in:transaction] ?: userPublicKey; - }]; - if ([recipientIds containsObject:userMasterPublicKey]) { - OWSFailDebug(@"Message send recipients should not include self."); - } - } else if ([thread isKindOfClass:TSContactThread.class]) { - NSString *recipientContactId = ((TSContactThread *)thread).contactIdentifier; - - // Treat 1:1 sends to blocked contacts as failures. - // If we block a user, don't send 1:1 messages to them. The UI - // should prevent this from occurring, but in some edge cases - // you might, for example, have a pending outgoing message when - // you block them. - OWSAssertDebug(recipientContactId.length > 0); - if ([self.blockingManager isRecipientIdBlocked:recipientContactId]) { - OWSLogInfo(@"Skipping 1:1 send to blocked contact: %@", recipientContactId); - NSError *error = OWSErrorMakeMessageSendFailedDueToBlockListError(); - [error setIsRetryable:NO]; - *errorHandle = error; - return nil; - } - - [recipientIds addObject:recipientContactId]; - } else { - OWSFailDebug(@"Unknown message type: %@", [message class]); - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:NO]; - *errorHandle = error; - return nil; - } - - [recipientIds minusSet:[NSSet setWithArray:self.blockingManager.blockedPhoneNumbers]]; - return recipientIds.allObjects; -} - -- (NSArray *)recipientsForRecipientIds:(NSArray *)recipientIds -{ - OWSAssertDebug(recipientIds.count > 0); - - NSMutableArray *recipients = [NSMutableArray new]; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - for (NSString *recipientId in recipientIds) { - SignalRecipient *recipient = - [SignalRecipient getOrBuildUnsavedRecipientForRecipientId:recipientId transaction:transaction]; - [recipients addObject:recipient]; - } - }]; - return [recipients copy]; -} - -- (AnyPromise *)sendPromiseForRecipients:(NSArray *)recipients - message:(TSOutgoingMessage *)message - thread:(nullable TSThread *)thread - senderCertificate:(nullable SMKSenderCertificate *)senderCertificate - sendErrors:(NSMutableArray *)sendErrors -{ - OWSAssertDebug(recipients.count > 0); - OWSAssertDebug(message); - OWSAssertDebug(sendErrors); - - NSMutableArray *sendPromises = [NSMutableArray array]; - - for (SignalRecipient *recipient in recipients) { - AnyPromise *sendPromise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { - NSString *localNumber = self.tsAccountManager.localNumber; - - OWSUDAccess *_Nullable theirUDAccess; - if (senderCertificate != nil && ![recipient.recipientId isEqualToString:localNumber]) { - theirUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES]; - } - - OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:message - thread:thread - recipient:recipient - senderCertificate:senderCertificate - udAccess:theirUDAccess - localNumber:self.tsAccountManager.localNumber - success:^{ - // The value doesn't matter, we just need any non-NSError value. - resolve(@(1)); - } - failure:^(NSError *error) { - @synchronized(sendErrors) { - [sendErrors addObject:error]; - } - resolve(error); - }]; - -// NSString *publicKey = recipients.firstObject.recipientId; -// if ([LKMultiDeviceProtocol isMultiDeviceRequiredForMessage:message toPublicKey:publicKey]) { // Avoid the write transaction if possible -// [self.primaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { -// [LKMultiDeviceProtocol sendMessageToDestinationAndLinkedDevices:messageSend transaction:transaction]; -// }]; -// } else { - [self sendMessage:messageSend]; -// } - }]; - [sendPromises addObject:sendPromise]; - } - - // We use PMKJoin(), not PMKWhen(), because we don't want the - // completion promise to execute until _all_ send promises - // have either succeeded or failed. PMKWhen() executes as - // soon as any of its input promises fail. - return PMKJoin(sendPromises); -} - -- (void)sendMessageToService:(TSOutgoingMessage *)message - senderCertificate:(nullable SMKSenderCertificate *)senderCertificate - success:(void (^)(void))successHandlerParam - failure:(RetryableFailureHandler)failureHandlerParam -{ - AssertIsOnSendingQueue(); - OWSAssert(senderCertificate); - - void (^successHandler)(void) = ^() { - dispatch_async(OWSDispatch.sendingQueue, ^{ - [self handleMessageSentLocally:message - success:^{ - successHandlerParam(); - } - failure:^(NSError *error) { - OWSLogError(@"Error sending sync message for message: %@ timestamp: %llu.", - message.class, - message.timestamp); - - failureHandlerParam(error); - }]; - }); - }; - void (^failureHandler)(NSError *) = ^(NSError *error) { - if (message.wasSentToAnyRecipient) { - dispatch_async(OWSDispatch.sendingQueue, ^{ - [self handleMessageSentLocally:message - success:^{ - failureHandlerParam(error); - } - failure:^(NSError *syncError) { - OWSLogError(@"Error sending sync message for message: %@ timestamp: %llu, %@.", - message.class, - message.timestamp, - syncError); - - // Discard the sync message error in favor of the original error - failureHandlerParam(error); - }]; - }); - return; - } - - failureHandlerParam(error); - }; - - TSThread *_Nullable thread = message.thread; - - BOOL isSyncMessage = [message isKindOfClass:[OWSOutgoingSyncMessage class]]; - if (thread == nil && !isSyncMessage) { - - // The 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.")); - [error setIsRetryable:NO]; - return failureHandler(error); - } - - // In the "self-send" special case, we ony need to send a sync message with a delivery receipt - // Loki: Take into account multi device - if ([LKSessionMetaProtocol isThreadNoteToSelf:thread] - && !([message isKindOfClass:LKDeviceLinkMessage.class]) && !([message isKindOfClass:SNClosedGroupUpdate.class])) { - // Don't mark self-sent messages as read (or sent) until the sync transcript is sent - successHandler(); - return; - } - - if (thread.isGroupThread) { - [self saveInfoMessageForGroupMessage:message inThread:thread]; - } - - NSError *error; - NSArray *_Nullable recipientIds = [self unsentRecipientsForMessage:message thread:thread error:&error]; - if (error || !recipientIds) { - error = SSKEnsureError( - error, OWSErrorCodeMessageSendNoValidRecipients, @"Couldn't build recipient list for message."); - [error setIsRetryable:NO]; - return failureHandler(error); - } - - // Mark skipped recipients as such. We skip because: - // - // * Recipient is no longer in the group. - // * Recipient is blocked. - // - // Elsewhere, we skip recipient if their Signal account has been deactivated. - NSMutableSet *obsoleteRecipientIds = [NSMutableSet setWithArray:message.sendingRecipientIds]; - [obsoleteRecipientIds minusSet:[NSSet setWithArray:recipientIds]]; - if (obsoleteRecipientIds.count > 0) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - for (NSString *recipientId in obsoleteRecipientIds) { - [message updateWithSkippedRecipient:recipientId transaction:transaction]; - } - }]; - } - - if (recipientIds.count < 1) { - successHandler(); - return; - } - - NSArray *recipients = [self recipientsForRecipientIds:recipientIds]; - - BOOL isGroupSend = (thread && thread.isGroupThread); - NSMutableArray *sendErrors = [NSMutableArray array]; - AnyPromise *sendPromise = [self sendPromiseForRecipients:recipients - message:message - thread:thread - senderCertificate:senderCertificate - sendErrors:sendErrors] - .then(^(id value) { - successHandler(); - }); - - sendPromise.catch(^(id failure) { - NSError *firstRetryableError = nil; - NSError *firstNonRetryableError = nil; - - NSArray *sendErrorsCopy; - @synchronized(sendErrors) { - sendErrorsCopy = [sendErrors copy]; - } - - for (NSError *error in sendErrorsCopy) { - // Some errors should be ignored when sending messages - // to groups. See discussion on - // NSError (OWSMessageSender) category. - if (isGroupSend && error.shouldBeIgnoredForGroups) { - continue; - } - - // Some errors should never be retried, in order to avoid - // hitting rate limits, for example. Unfortunately, since - // group send retry is all-or-nothing, we need to fail - // immediately even if some of the other recipients had - // retryable errors. - if (error.isFatal) { - failureHandler(error); - return; - } - - if ([error isRetryable] && !firstRetryableError) { - firstRetryableError = error; - } else if (![error isRetryable] && !firstNonRetryableError) { - firstNonRetryableError = error; - } - } - - // If any of the send errors are retryable, we want to retry. - // Therefore, prefer to propagate a retryable error. - if (firstRetryableError) { - return failureHandler(firstRetryableError); - } else if (firstNonRetryableError) { - return failureHandler(firstNonRetryableError); - } else { - // If we only received errors that we should ignore, - // consider this send a success, unless the message could - // 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.")); - [error setIsRetryable:NO]; - failureHandler(error); - } else { - successHandler(); - } - } - }); - - [sendPromise retainUntilComplete]; -} - -- (nullable NSArray *)deviceMessagesForMessageSend:(OWSMessageSend *)messageSend - error:(NSError **)errorHandle -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(errorHandle); - AssertIsOnSendingQueue(); - - SignalRecipient *recipient = messageSend.recipient; - - NSArray *deviceMessages; - @try { - deviceMessages = [self throws_deviceMessagesForMessageSend:messageSend]; - } @catch (NSException *exception) { - if ([exception.name isEqualToString:NoSessionForTransientMessageException]) { - // When users re-register, we don't want transient messages (like typing - // indicators) to cause users to hit the prekey fetch rate limit. So - // we silently discard these message if there is no pre-existing session - // for the recipient. - NSError *error = OWSErrorWithCodeDescription( - OWSErrorCodeNoSessionForTransientMessage, @"No session for transient message."); - [error setIsRetryable:NO]; - [error setIsFatal:YES]; - *errorHandle = error; - return nil; - } else if ([exception.name isEqualToString:UntrustedIdentityKeyException]) { - NSString *localizedErrorDescriptionFormat - = NSLocalizedString(@"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY", - @"action sheet header when re-sending message which failed because of untrusted identity keys"); - - NSString *localizedErrorDescription = - [NSString stringWithFormat:localizedErrorDescriptionFormat, - [self.contactsManager displayNameForPhoneIdentifier:recipient.recipientId]]; - NSError *error = OWSErrorMakeUntrustedIdentityError(localizedErrorDescription, recipient.recipientId); - - // Key will continue to be unaccepted, so no need to retry. It'll only cause us to hit the Pre-Key request - // rate limit - [error setIsRetryable:NO]; - // Avoid the "Too many failures with this contact" error rate limiting. - [error setIsFatal:YES]; - *errorHandle = error; - - PreKeyBundle *_Nullable newKeyBundle = exception.userInfo[TSInvalidPreKeyBundleKey]; - if (newKeyBundle == nil) { - return nil; - } - - if (![newKeyBundle isKindOfClass:[PreKeyBundle class]]) { - return nil; - } - - NSData *newIdentityKeyWithVersion = newKeyBundle.identityKey; - - if (![newIdentityKeyWithVersion isKindOfClass:[NSData class]]) { - return nil; - } - - // TODO migrate to storing the full 33 byte representation of the identity key. - if (newIdentityKeyWithVersion.length != kIdentityKeyLength) { - return nil; - } - - NSData *newIdentityKey = [newIdentityKeyWithVersion throws_removeKeyType]; - [self.identityManager saveRemoteIdentity:newIdentityKey recipientId:recipient.recipientId]; - - return nil; - } - - if ([exception.name isEqualToString:OWSMessageSenderRateLimitedException]) { - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceRateLimited, - NSLocalizedString(@"FAILED_SENDING_BECAUSE_RATE_LIMIT", - @"action sheet header when re-sending message which failed because of too many attempts")); - // We're already rate-limited. No need to exacerbate the problem. - [error setIsRetryable:NO]; - // Avoid exacerbating the rate limiting. - [error setIsFatal:YES]; - *errorHandle = error; - return nil; - } - - OWSLogWarn(@"Could not build device messages: %@", exception); - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:YES]; - *errorHandle = error; - return nil; - } - - return deviceMessages; -} - -- (void)sendMessage:(OWSMessageSend *)messageSend -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]); - NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - if (!messageSend.isUDSend && ![messageSend.recipient.recipientId isEqual:userPublicKey]) { - [LKLogger print:@"[Loki] Non-UD send"]; - } - - TSOutgoingMessage *message = messageSend.message; - SignalRecipient *recipient = messageSend.recipient; - - BOOL notifyPNServer = ((message.body != nil && message.body.length > 0) || message.hasAttachments); - - OWSLogInfo(@"Attempting to send message: %@, timestamp: %llu, recipient: %@.", - message.class, - message.timestamp, - recipient.uniqueId); - - AssertIsOnSendingQueue(); - - if ([TSPreKeyManager isAppLockedDueToPreKeyUpdateFailures]) { - // Retry pre key update every time user tries to send a message while the app - // is disabled due to pre key update failures. - // - // Only try to update the signed pre key; updating it is sufficient to - // re-enable message sending. - [TSPreKeyManager - rotateSignedPreKeyWithSuccess:^{ - OWSLogInfo(@"New pre keys registered with server."); - NSError *error = OWSErrorMakeMessageSendDisabledDueToPreKeyUpdateFailuresError(); - [error setIsRetryable:YES]; - return messageSend.failure(error); - } - failure:^(NSError *error) { - OWSLogWarn(@"Failed to update pre keys with the server due to error: %@.", error); - return messageSend.failure(error); - }]; - } - - if (messageSend.remainingAttempts <= 0) { - // We should always fail with a specific error. - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:YES]; - return messageSend.failure(error); - } - - // Consume an attempt. - messageSend.remainingAttempts = messageSend.remainingAttempts - 1; - - // We need to disable UD for sync messages before we build the device messages, - // since we don't want to build a device message for the local device in the - // non-UD auth case. - if ([message isKindOfClass:[OWSOutgoingSyncMessage class]] - && ![message isKindOfClass:[OWSOutgoingSentMessageTranscript class]]) { - [messageSend disableUD]; - } - - NSError *deviceMessagesError; - NSArray *_Nullable deviceMessages; - if (message.thread.isGroupThread && ((TSGroupThread *)message.thread).isPublicChat) { - deviceMessages = @[]; - } else { - deviceMessages = [self deviceMessagesForMessageSend:messageSend error:&deviceMessagesError]; - - // Loki: Remove this when we have shared sender keys - // ======== - if (deviceMessages.count == 0) { - return messageSend.success(); - } - // ======== - } - - if (deviceMessagesError || !deviceMessages) { - OWSAssertDebug(deviceMessagesError); - return messageSend.failure(deviceMessagesError); - } - - for (NSDictionary *deviceMessage in deviceMessages) { - NSNumber *_Nullable messageType = deviceMessage[@"type"]; - OWSAssertDebug(messageType); - BOOL hasValidMessageType; - if (messageSend.isUDSend) { - hasValidMessageType = [messageType isEqualToNumber:@(TSUnidentifiedSenderMessageType)]; - } else { - NSArray *validMessageTypes = @[ @(TSEncryptedWhisperMessageType), @(TSPreKeyWhisperMessageType), @(TSFallbackMessageType), @(TSClosedGroupCiphertextMessageType) ]; - hasValidMessageType = [validMessageTypes containsObject:messageType]; - } - - if (!hasValidMessageType) { - OWSFailDebug(@"Invalid message type: %@.", messageType); - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:NO]; - return messageSend.failure(error); - } - } - - 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. - // * After unlinking all linked devices. - // * After trying and failing to link a device. - // * The first time we send a message to a user, if they don't have their - // default device. For example, if they have unregistered - // their primary but still have a linked device. Or later, when they re-register. - // - // When we're not sure if we have linked devices, we need to try - // to send self-sync messages even if they have no device messages - // so that we can learn from the service whether or not there are - // linked devices that we don't know about. - OWSLogWarn(@"Sending a message with no device messages."); - - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:NO]; - return messageSend.failure(error); - } - - void (^failedMessageSend)(NSError *error) = ^(NSError *error) { - NSUInteger statusCode = 0; - NSData *_Nullable responseData = nil; - if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) { - statusCode = error.code; - NSError *_Nullable underlyingError = error.userInfo[NSUnderlyingErrorKey]; - if (underlyingError) { - responseData = underlyingError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]; - } else { - OWSFailDebug(@"Missing underlying error: %@.", error); - } - } - [self messageSendDidFail:messageSend deviceMessages:deviceMessages statusCode:statusCode error:error responseData:responseData]; - }; - - __block SNOpenGroup *publicChat; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - publicChat = [LKDatabaseUtilities getPublicChatForThreadID:message.uniqueThreadId transaction: transaction]; - }]; - if (publicChat != nil) { - NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - NSString *displayName = SSKEnvironment.shared.profileManager.localProfileName; - if (displayName == nil) { displayName = @"Anonymous"; } - TSQuotedMessage *quote = message.quotedMessage; - uint64_t quoteID = quote.timestamp; - NSString *quoteePublicKey = quote.authorId; - __block uint64_t quotedMessageServerID = 0; - if (quoteID != 0) { - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - quotedMessageServerID = [LKDatabaseUtilities getServerIDForQuoteWithID:quoteID quoteeHexEncodedPublicKey:quoteePublicKey threadID:messageSend.thread.uniqueId transaction:transaction]; - }]; - } - NSString *body = (message.body != nil && message.body.length > 0) ? message.body : [NSString stringWithFormat:@"%@", @(message.timestamp)]; // Workaround for the fact that the back-end doesn't accept messages without a body - SNOpenGroupMessage *groupMessage = [[SNOpenGroupMessage alloc] initWithSenderPublicKey:userPublicKey displayName:displayName body:body type:SNOpenGroupAPI.openGroupMessageType - timestamp:message.timestamp quotedMessageTimestamp:quoteID quoteePublicKey:quoteePublicKey quotedMessageBody:quote.body quotedMessageServerID:quotedMessageServerID signatureData:nil signatureVersion:0 serverTimestamp:0]; - OWSLinkPreview *linkPreview = message.linkPreview; - if (linkPreview != nil) { - TSAttachmentStream *attachment = [TSAttachmentStream fetchObjectWithUniqueID:linkPreview.imageAttachmentId]; - if (attachment != nil) { - [groupMessage addAttachmentWithKind:@"preview" server:publicChat.server serverID:attachment.serverId contentType:attachment.contentType size:attachment.byteCount fileName:attachment.sourceFilename flags:0 width:@(attachment.imageSize.width).unsignedIntegerValue height:@(attachment.imageSize.height).unsignedIntegerValue caption:attachment.caption url:attachment.downloadURL linkPreviewURL:linkPreview.urlString linkPreviewTitle:linkPreview.title]; - } - } - for (NSString *attachmentID in message.attachmentIds) { - TSAttachmentStream *attachment = [TSAttachmentStream fetchObjectWithUniqueID:attachmentID]; - if (attachment == nil) { continue; } - NSUInteger width = attachment.shouldHaveImageSize ? @(attachment.imageSize.width).unsignedIntegerValue : 0; - NSUInteger height = attachment.shouldHaveImageSize ? @(attachment.imageSize.height).unsignedIntegerValue : 0; - [groupMessage addAttachmentWithKind:@"attachment" server:publicChat.server serverID:attachment.serverId contentType:attachment.contentType size:attachment.byteCount fileName:attachment.sourceFilename flags:0 width:width height:height caption:attachment.caption url:attachment.downloadURL linkPreviewURL:nil linkPreviewTitle:nil]; - } - message.actualSenderHexEncodedPublicKey = userPublicKey; - [[SNOpenGroupAPI sendMessage:groupMessage toGroup:publicChat.channel onServer:publicChat.server] - .thenOn(OWSDispatch.sendingQueue, ^(SNOpenGroupMessage *groupMessage) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [message saveOpenGroupServerMessageID:groupMessage.serverID in:transaction]; - [self.primaryStorage setIDForMessageWithServerID:groupMessage.serverID to:message.uniqueId in:transaction]; - }]; - [self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:messageSend.isUDSend wasSentByWebsocket:false]; - }) - .catchOn(OWSDispatch.sendingQueue, ^(NSError *error) { - failedMessageSend(error); - }) retainUntilComplete]; - } else { - NSString *targetPublicKey = recipient.recipientId; - NSString *userPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - __block BOOL isUserLinkedDevice; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - isUserLinkedDevice = [LKDatabaseUtilities isUserLinkedDevice:targetPublicKey in:transaction]; - }]; - BOOL isSSKBasedClosedGroup = [messageSend.thread isKindOfClass:TSGroupThread.class] && ((TSGroupThread *)messageSend.thread).usesSharedSenderKeys; - if (isSSKBasedClosedGroup) { - [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to SSK based closed group.", message.class]]; - } else if ([targetPublicKey isEqual:userPublicKey]) { - [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to self.", message.class]]; - } else if (isUserLinkedDevice) { - [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to %@ (one of the current user's linked devices).", message.class, recipient.recipientId]]; - } else { - [LKLogger print:[NSString stringWithFormat:@"[Loki] Sending %@ to %@.", message.class, recipient.recipientId]]; - } - NSDictionary *signalMessageInfo = deviceMessages.firstObject; - SSKProtoEnvelopeType type = ((NSNumber *)signalMessageInfo[@"type"]).integerValue; - uint32_t senderDeviceID = (type == SSKProtoEnvelopeTypeUnidentifiedSender) ? 0 : OWSDevicePrimaryDeviceId; - NSString *content = signalMessageInfo[@"content"]; - NSString *recipientID = signalMessageInfo[@"destination"]; - uint64_t ttl = ((NSNumber *)signalMessageInfo[@"ttl"]).unsignedIntegerValue; - BOOL isPing = ((NSNumber *)signalMessageInfo[@"isPing"]).boolValue; - uint64_t timestamp = message.timestamp; - NSString *senderID; - if (type == SSKProtoEnvelopeTypeClosedGroupCiphertext) { - senderID = recipientID; - } else if (type == SSKProtoEnvelopeTypeUnidentifiedSender) { - senderID = @""; - } else { - senderID = userPublicKey; - [LKLogger print:@"[Loki] Non-UD send"]; - } - LKSignalMessage *signalMessage = [[LKSignalMessage alloc] initWithType:type timestamp:timestamp senderID:senderID senderDeviceID:senderDeviceID content:content recipientID:recipientID ttl:ttl isPing:isPing]; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - if (!message.skipSave) { - // Update the PoW calculation status - [message saveIsCalculatingProofOfWork:YES withTransaction:transaction]; - } - }]; - // Convenience - void (^handleError)(NSError *error) = ^(NSError *error) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - if (!message.skipSave) { - // Update the PoW calculation status - [message saveIsCalculatingProofOfWork:NO withTransaction:transaction]; - } - }]; - // Handle the error - failedMessageSend(error); - }; - // Send the message - [[LKSnodeAPI sendSignalMessage:signalMessage] - .thenOn(OWSDispatch.sendingQueue, ^(id result) { - NSSet *promises = (NSSet *)result; - __block BOOL isSuccess = NO; - NSUInteger promiseCount = promises.count; - __block NSUInteger errorCount = 0; - for (AnyPromise *promise in promises) { - [promise - .thenOn(OWSDispatch.sendingQueue, ^(id result) { - if (isSuccess) { return; } // Succeed as soon as the first promise succeeds - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.messageSent object:[[NSNumber alloc] initWithUnsignedLongLong:signalMessage.timestamp]]; - isSuccess = YES; - if (notifyPNServer) { - [LKPushNotificationManager notifyForMessage:signalMessage]; - } - [self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:messageSend.isUDSend wasSentByWebsocket:false]; - }) - .catchOn(OWSDispatch.sendingQueue, ^(NSError *error) { - errorCount += 1; - if (errorCount != promiseCount) { return; } // Only error out if all promises failed - [NSNotificationCenter.defaultCenter postNotificationName:NSNotification.messageFailed object:[[NSNumber alloc] initWithUnsignedLongLong:signalMessage.timestamp]]; - handleError(error); - }) retainUntilComplete]; - } - }) - .catchOn(OWSDispatch.sendingQueue, ^(NSError *error) { - handleError(error); - }) retainUntilComplete]; - } -} - -- (void)messageSendDidSucceed:(OWSMessageSend *)messageSend - deviceMessages:(NSArray *)deviceMessages - wasSentByUD:(BOOL)wasSentByUD - wasSentByWebsocket:(BOOL)wasSentByWebsocket -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(deviceMessages); - - SignalRecipient *recipient = messageSend.recipient; - - OWSLogInfo(@"Successfully sent message: %@ timestamp: %llu, wasSentByUD: %d.", - messageSend.message.class, messageSend.message.timestamp, wasSentByUD); - - if (messageSend.isLocalNumber && deviceMessages.count == 0) { - OWSLogInfo(@"Sent a message with no device messages; clearing 'mayHaveLinkedDevices'."); - // In order to avoid skipping necessary sync messages, the default value - // for mayHaveLinkedDevices is YES. Once we've successfully sent a - // sync message with no device messages (e.g. the service has confirmed - // that we have no linked devices), we can set mayHaveLinkedDevices to NO - // to avoid unnecessary message sends for sync messages until we learn - // of a linked device (e.g. through the device linking UI or by receiving - // a sync message, etc.). - [OWSDeviceManager.sharedManager clearMayHaveLinkedDevices]; - } - - dispatch_async(OWSDispatch.sendingQueue, ^{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [messageSend.message updateWithSentRecipient:messageSend.recipient.uniqueId - wasSentByUD:wasSentByUD - transaction:transaction]; - - // If we've just delivered a message to a user, we know they - // have a valid Signal account. - [SignalRecipient markRecipientAsRegisteredAndGet:recipient.recipientId transaction:transaction]; - }]; - - messageSend.success(); - }); -} - -- (void)messageSendDidFail:(OWSMessageSend *)messageSend - deviceMessages:(NSArray *)deviceMessages - statusCode:(NSInteger)statusCode - error:(NSError *)responseError - responseData:(nullable NSData *)responseData -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]); - OWSAssertDebug(deviceMessages); - OWSAssertDebug(responseError); - - TSOutgoingMessage *message = messageSend.message; - SignalRecipient *recipient = messageSend.recipient; - - OWSLogInfo(@"Failed to send message: %@, timestamp: %llu, to recipient: %@.", - message.class, - message.timestamp, - recipient.uniqueId); - - void (^retrySend)(void) = ^void() { - if (messageSend.remainingAttempts <= 0) { - return messageSend.failure(responseError); - } - - dispatch_async(OWSDispatch.sendingQueue, ^{ - OWSLogDebug(@"Retrying: %@.", message.debugDescription); - [self sendMessage:messageSend]; - }); - }; - - switch (statusCode) { - case 0: { // Loki - NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError(); - [error setIsRetryable:NO]; - return messageSend.failure(error); - } - case 401: { - OWSLogWarn(@"Unable to send due to invalid credentials. Did the user's client get de-authed by " - @"registering elsewhere?"); - NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeSignalServiceFailure, - NSLocalizedString(@"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED", @"Error message when attempting to send message")); - // No need to retry if we've been de-authed. - [error setIsRetryable:NO]; - return messageSend.failure(error); - } - default: - retrySend(); - break; - } -} - -- (void)handleMessageSentLocally:(TSOutgoingMessage *)message - success:(void (^)(void))successParam - failure:(RetryableFailureHandler)failure -{ - dispatch_block_t success = ^{ - // Don't mark self-sent messages as read (or sent) until the sync transcript is sent - // Loki: Take into account multi device - BOOL isNoteToSelf = [LKSessionMetaProtocol isThreadNoteToSelf:message.thread]; - if (isNoteToSelf && !([message isKindOfClass:LKDeviceLinkMessage.class]) - && ![message isKindOfClass:SNClosedGroupUpdate.class]) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - for (NSString *recipientId in message.sendingRecipientIds) { - [message updateWithReadRecipientId:recipientId readTimestamp:message.timestamp transaction:transaction]; - } - }]; - } - - successParam(); - }; - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [[OWSDisappearingMessagesJob sharedJob] startAnyExpirationForMessage:message - expirationStartedAt:[NSDate ows_millisecondTimeStamp] - transaction:transaction]; - }]; - - if (!message.shouldSyncTranscript) { - return success(); - } - - BOOL shouldSendTranscript = [LKSessionMetaProtocol shouldSendTranscriptForMessage:message inThread:message.thread]; - if (!shouldSendTranscript) { - return success(); - } - - BOOL isRecipientUpdate = message.hasSyncedTranscript; - [self - sendSyncTranscriptForMessage:message - isRecipientUpdate:isRecipientUpdate - success:^{ - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [message updateWithHasSyncedTranscript:YES transaction:transaction]; - }]; - - success(); - } - failure:failure]; -} - -- (void)sendSyncTranscriptForMessage:(TSOutgoingMessage *)message - isRecipientUpdate:(BOOL)isRecipientUpdate - success:(void (^)(void))success - failure:(RetryableFailureHandler)failure -{ - OWSOutgoingSentMessageTranscript *sentMessageTranscript = - [[OWSOutgoingSentMessageTranscript alloc] initWithOutgoingMessage:message isRecipientUpdate:isRecipientUpdate]; - - NSString *userPublicKey = self.tsAccountManager.localNumber; - - // Loki: Send to the user's other device - __block NSSet *userLinkedDevices; - [self.primaryStorage.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - userLinkedDevices = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userPublicKey in:transaction]; - }]; - NSString *otherUserDevice; - for (NSString *device in userLinkedDevices) { - if (![device isEqual:userPublicKey]) { - otherUserDevice = device; - break; - } - } - - NSString *recipientId = otherUserDevice ?: userPublicKey; - __block SignalRecipient *recipient; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - recipient = [SignalRecipient markRecipientAsRegisteredAndGet:recipientId transaction:transaction]; - }]; - - SMKSenderCertificate *senderCertificate = [self.udManager getSenderCertificate]; - OWSUDAccess *recipientUDAccess = nil; - if (senderCertificate != nil) { - recipientUDAccess = [self.udManager udAccessForRecipientId:recipient.recipientId requireSyncAccess:YES]; - } - - // Loki: If the message was aimed at an SSK based closed group, aim the sync transcript at - // the contact thread with the other device rather than also sending it to the group. - __block TSThread *thread = message.thread; - if ([thread isKindOfClass:TSGroupThread.class] && ((TSGroupThread *)thread).usesSharedSenderKeys) { - [LKStorage readWithBlock:^(YapDatabaseReadTransaction *transaction) { - thread = [TSContactThread getThreadWithContactId:otherUserDevice transaction:transaction]; - }]; - } - - OWSMessageSend *messageSend = [[OWSMessageSend alloc] initWithMessage:sentMessageTranscript - thread:thread - recipient:recipient - senderCertificate:senderCertificate - udAccess:recipientUDAccess - localNumber:self.tsAccountManager.localNumber - success:^{ - OWSLogInfo(@"Successfully sent sync transcript."); - - success(); - } - failure:^(NSError *error) { - OWSLogInfo(@"Failed to send sync transcript: %@ (isRetryable: %d).", error, error.isRetryable); - - failure(error); - }]; - - [self sendMessage:messageSend]; -} - -- (NSArray *)throws_deviceMessagesForMessageSend:(OWSMessageSend *)messageSend -{ - // Loki: Multi device is handled elsewhere so just send to the provided recipient ID (Signal used - // to send to each of the recipient's devices here) - OWSAssertDebug(messageSend.message != nil); - OWSAssertDebug(messageSend.recipient != nil); - - SignalRecipient *recipient = messageSend.recipient; - NSMutableArray *messagesArray = [NSMutableArray new]; - - NSData *_Nullable plainText = [messageSend.message buildPlainTextData:recipient]; - if (!plainText) { - OWSRaiseException(InvalidMessageException, @"Failed to build message proto."); - } - OWSLogDebug(@"Built message: %@ plainTextData.length: %lu", [messageSend.message class], (unsigned long)plainText.length); - - NSString *recipientID = recipient.recipientId; - - OWSLogVerbose(@"Building device messages for: %@ %@ (isLocalNumber: %d, isUDSend: %d).", - recipientID, - recipient.devices, - messageSend.isLocalNumber, - messageSend.isUDSend); - - @try { - __block BOOL isSessionRequired; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - isSessionRequired = [LKSessionManagementProtocol isSessionRequiredForMessage:messageSend.message recipientID:recipientID transaction:transaction]; - }]; - if (isSessionRequired) { - BOOL hasSession = [self throws_ensureRecipientHasSessionForMessageSend:messageSend recipientID:recipientID deviceId:@(OWSDevicePrimaryDeviceId)]; - - // Loki: Remove this when shared sender keys has been widely rolled out - // ======== - if (!hasSession && [LKSessionManagementProtocol shouldIgnoreMissingPreKeyBundleExceptionForMessage:messageSend.message to:recipientID]) { - return @[ [NSDictionary new] ]; - } - // ======== - } - - __block NSDictionary *_Nullable messageDict; - __block NSException *encryptionException; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - @try { - messageDict = [self throws_encryptedMessageForMessageSend:messageSend - recipientID:recipientID - plainText:plainText - transaction:transaction]; - } @catch (NSException *exception) { - encryptionException = exception; - } - }]; - - if (encryptionException) { - OWSLogInfo(@"Exception during encryption: %@.", encryptionException); - @throw encryptionException; - } - - if (messageDict) { - [messagesArray addObject:messageDict]; - } else { - OWSRaiseException(InvalidMessageException, @"Failed to encrypt message."); - } - } @catch (NSException *exception) { - if ([exception.name isEqualToString:OWSMessageSenderInvalidDeviceException]) { - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - [recipient updateRegisteredRecipientWithDevicesToAdd:nil - devicesToRemove:@[ @(OWSDevicePrimaryDeviceId) ] - transaction:transaction]; - }]; - } else { - @throw exception; - } - } - - return [messagesArray copy]; -} - -- (BOOL)throws_ensureRecipientHasSessionForMessageSend:(OWSMessageSend *)messageSend recipientID:(NSString *)recipientID deviceId:(NSNumber *)deviceId -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(deviceId); - - OWSPrimaryStorage *storage = self.primaryStorage; - SignalRecipient *recipient = messageSend.recipient; - OWSAssertDebug(recipientID.length > 0); - - // Discard "typing indicator" messages if there is no existing session with the user. - BOOL canSafelyBeDiscarded = messageSend.message.isOnline; - if (canSafelyBeDiscarded) { - OWSRaiseException(NoSessionForTransientMessageException, @"No session for transient message."); - } - - PreKeyBundle *_Nullable bundle = [storage getPreKeyBundleForContact:recipientID]; - __block NSException *exception; - - if (!bundle) { - __block BOOL hasSession; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - hasSession = [storage containsSession:recipientID deviceId:[deviceId intValue] protocolContext:transaction]; - }]; - if (hasSession) { return YES; } - - TSOutgoingMessage *message = messageSend.message; - // Loki: Remove this when we have shared sender keys - // ======== - if ([LKSessionManagementProtocol shouldIgnoreMissingPreKeyBundleExceptionForMessage:message to:recipientID]) { return NO; } - // ======== - NSString *missingPrekeyBundleException = @"missingPrekeyBundleException"; - OWSRaiseException(missingPrekeyBundleException, @"Missing pre key bundle for: %@.", recipientID); - } else { - SessionBuilder *builder = [[SessionBuilder alloc] initWithSessionStore:storage - preKeyStore:storage - signedPreKeyStore:storage - identityKeyStore:self.identityManager - recipientId:recipientID - deviceId:[deviceId intValue]]; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - @try { - [builder throws_processPrekeyBundle:bundle protocolContext:transaction]; - - // Loki: Discard the pre key bundle as the session has now been established - [storage removePreKeyBundleForContact:recipientID transaction:transaction]; - } @catch (NSException *caughtException) { - exception = caughtException; - } - }]; - if (exception) { - if ([exception.name isEqualToString:UntrustedIdentityKeyException]) { - OWSRaiseExceptionWithUserInfo(UntrustedIdentityKeyException, (@{ TSInvalidPreKeyBundleKey : bundle, TSInvalidRecipientKey : recipientID }), @""); - } - @throw exception; - } - return YES; - } -} - -- (nullable NSDictionary *)throws_encryptedMessageForMessageSend:(OWSMessageSend *)messageSend - recipientID:(NSString *)recipientID - plainText:(NSData *)plainText - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(messageSend); - OWSAssertDebug(recipientID); - OWSAssertDebug(plainText); - OWSAssertDebug(transaction); - - OWSPrimaryStorage *storage = self.primaryStorage; - TSOutgoingMessage *message = messageSend.message; - - SessionCipher *cipher = [[SessionCipher alloc] initWithSessionStore:storage - preKeyStore:storage - signedPreKeyStore:storage - identityKeyStore:self.identityManager - recipientId:recipientID - deviceId:@(OWSDevicePrimaryDeviceId).intValue]; - - NSData *_Nullable serializedMessage; - TSWhisperMessageType messageType; - if ([LKSharedSenderKeysImplementation.shared isClosedGroup:recipientID]) { - NSError *error; - serializedMessage = [LKClosedGroupUtilities encryptData:plainText.paddedMessageBody usingGroupPublicKey:recipientID transaction:transaction error:&error]; - - if (error != nil) { - OWSFailDebug(@"Couldn't encrypt message for SSK based closed group due to error: %@.", error); - return nil; - } - - messageType = TSClosedGroupCiphertextMessageType; - - messageSend.udAccess = nil; - } else if (messageSend.isUDSend) { - NSError *error; - SNSessionRestorationImplementation *sessionResetImplementation = [SNSessionRestorationImplementation new]; - - SMKSecretSessionCipher *_Nullable secretCipher = - [[SMKSecretSessionCipher alloc] initWithSessionResetImplementation:sessionResetImplementation - sessionStore:self.primaryStorage - preKeyStore:self.primaryStorage - signedPreKeyStore:self.primaryStorage - identityStore:self.identityManager - error:&error]; - if (error || !secretCipher) { - OWSRaiseException(@"SecretSessionCipherFailure", @"Can't create secret session cipher."); - } - - // Loki: The way this works is: - // • Alice sends a session request (i.e. a pre key bundle) to Bob using fallback encryption. - // • She may send any number of subsequent messages also encrypted using fallback encryption. - // • When Bob receives the session request, he sets up his Signal cipher session locally and sends back a null message, - // now encrypted using Signal encryption. - // • Alice receives this, sets up her Signal cipher session locally, and sends any subsequent messages - // using Signal encryption. - - BOOL shouldUseFallbackEncryption = [LKSessionManagementProtocol shouldUseFallbackEncryptionForMessage:message recipientID:recipientID transaction:transaction]; - - if (shouldUseFallbackEncryption) { - [LKLogger print:@"[Loki] Using fallback encryption"]; - } else { - [LKLogger print:@"[Loki] Using Signal Encryption"]; - } - - serializedMessage = [secretCipher throwswrapped_encryptMessageWithRecipientPublicKey:recipientID - deviceID:@(OWSDevicePrimaryDeviceId).intValue - paddedPlaintext:plainText.paddedMessageBody - senderCertificate:messageSend.senderCertificate - protocolContext:transaction - useFallbackSessionCipher:shouldUseFallbackEncryption - error:&error]; - - SCKRaiseIfExceptionWrapperError(error); - if (serializedMessage == nil || error != nil) { - OWSFailDebug(@"Error while UD encrypting message: %@.", error); - return nil; - } - messageType = TSUnidentifiedSenderMessageType; - } else { - id encryptedMessage = - [cipher throws_encryptMessage:[plainText paddedMessageBody] protocolContext:transaction]; - serializedMessage = encryptedMessage.serialized; - messageType = [self messageTypeForCipherMessage:encryptedMessage]; - } - - BOOL isSilent = message.isSilent; - BOOL isOnline = message.isOnline; - - OWSMessageServiceParams *messageParams = - [[OWSMessageServiceParams alloc] initWithType:messageType - recipientId:recipientID - device:@(OWSDevicePrimaryDeviceId).intValue - content:serializedMessage - isSilent:isSilent - isOnline:isOnline - registrationId:[cipher throws_remoteRegistrationId:transaction] - ttl:message.ttl - isPing:NO]; - - NSError *error; - NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:messageParams error:&error]; - - if (error != nil) { - return nil; - } - - return jsonDict; -} - -- (TSWhisperMessageType)messageTypeForCipherMessage:(id)cipherMessage -{ - switch (cipherMessage.cipherMessageType) { - case CipherMessageType_Whisper: - return TSEncryptedWhisperMessageType; - case CipherMessageType_Prekey: - return TSPreKeyWhisperMessageType; - default: - return TSUnknownMessageType; - } -} - -- (void)saveInfoMessageForGroupMessage:(TSOutgoingMessage *)message inThread:(TSThread *)thread -{ - OWSAssertDebug(message); - OWSAssertDebug(thread); - - if (message.groupMetaMessage == TSGroupMetaMessageDeliver) { - // TODO: Why is this necessary? - [message save]; - } else if (message.groupMetaMessage == TSGroupMetaMessageQuit) { - // MJK TODO - remove senderTimestamp - [[[TSInfoMessage alloc] initWithTimestamp:message.timestamp - inThread:thread - messageType:TSInfoMessageTypeGroupQuit - customMessage:message.customMessage] save]; - } else { - // MJK TODO - remove senderTimestamp - [[[TSInfoMessage alloc] initWithTimestamp:message.timestamp - inThread:thread - messageType:TSInfoMessageTypeGroupUpdate - customMessage:message.customMessage] save]; - } -} - -@end - -@implementation OutgoingMessagePreparer - -#pragma mark - Dependencies - -+ (YapDatabaseConnection *)dbConnection -{ - return SSKEnvironment.shared.primaryStorage.dbReadWriteConnection; -} - -#pragma mark - - -+ (NSArray *)prepareMessageForSending:(TSOutgoingMessage *)message - transaction:(YapDatabaseReadWriteTransaction *)transaction -{ - OWSAssertDebug(message); - OWSAssertDebug(transaction); - - NSMutableArray *attachmentIds = [NSMutableArray new]; - - if (message.attachmentIds) { - [attachmentIds addObjectsFromArray:message.attachmentIds]; - } - - if (message.quotedMessage) { - // Though we currently only ever expect at most one thumbnail, the proto data model - // suggests this could change. The logic is intended to work with multiple, but - // if we ever actually want to send multiple, we should do more testing. - NSArray *quotedThumbnailAttachments = - [message.quotedMessage createThumbnailAttachmentsIfNecessaryWithTransaction:transaction]; - for (TSAttachmentStream *attachment in quotedThumbnailAttachments) { - [attachmentIds addObject:attachment.uniqueId]; - } - } - - if (message.contactShare.avatarAttachmentId != nil) { - TSAttachment *attachment = [message.contactShare avatarAttachmentWithTransaction:transaction]; - if ([attachment isKindOfClass:[TSAttachmentStream class]]) { - [attachmentIds addObject:attachment.uniqueId]; - } else { - OWSFailDebug(@"Unexpected avatarAttachment: %@.", attachment); - } - } - - if (message.linkPreview.imageAttachmentId != nil) { - TSAttachment *attachment = - [TSAttachment fetchObjectWithUniqueID:message.linkPreview.imageAttachmentId transaction:transaction]; - if ([attachment isKindOfClass:[TSAttachmentStream class]]) { - [attachmentIds addObject:attachment.uniqueId]; - } else { - OWSFailDebug(@"Unexpected attachment: %@.", attachment); - } - } - - // All outgoing messages should be saved at the time they are enqueued. - [message saveWithTransaction:transaction]; - // When we start a message send, all "failed" recipients should be marked as "sending". - [message updateWithMarkingAllUnsentRecipientsAsSendingWithTransaction:transaction]; - - return attachmentIds; -} - -+ (void)prepareAttachments:(NSArray *)attachmentInfos - inMessage:(TSOutgoingMessage *)outgoingMessage - completionHandler:(void (^)(NSError *_Nullable error))completionHandler -{ - OWSAssertDebug(attachmentInfos.count > 0); - OWSAssertDebug(outgoingMessage); - - dispatch_async([OWSDispatch attachmentsQueue], ^{ - NSMutableArray *attachmentStreams = [NSMutableArray new]; - for (OWSOutgoingAttachmentInfo *attachmentInfo in attachmentInfos) { - TSAttachmentStream *attachmentStream = - [[TSAttachmentStream alloc] initWithContentType:attachmentInfo.contentType - byteCount:(UInt32)attachmentInfo.dataSource.dataLength - sourceFilename:attachmentInfo.sourceFilename - caption:attachmentInfo.caption - albumMessageId:attachmentInfo.albumMessageId]; - - if (outgoingMessage.isVoiceMessage) { - attachmentStream.attachmentType = TSAttachmentTypeVoiceMessage; - } - - if (![attachmentStream writeDataSource:attachmentInfo.dataSource]) { - NSError *error = OWSErrorMakeWriteAttachmentDataError(); - completionHandler(error); - return; - } - - [attachmentStreams addObject:attachmentStream]; - } - - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - for (TSAttachmentStream *attachmentStream in attachmentStreams) { - [outgoingMessage.attachmentIds addObject:attachmentStream.uniqueId]; - if (attachmentStream.sourceFilename) { - outgoingMessage.attachmentFilenameMap[attachmentStream.uniqueId] = attachmentStream.sourceFilename; - } - } - [outgoingMessage saveWithTransaction:transaction]; - for (TSAttachmentStream *attachmentStream in attachmentStreams) { - [attachmentStream saveWithTransaction:transaction]; - } - }]; - - completionHandler(nil); - }); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Poller.swift b/SignalUtilitiesKit/Poller.swift index 1f5ef36c9..8387361ca 100644 --- a/SignalUtilitiesKit/Poller.swift +++ b/SignalUtilitiesKit/Poller.swift @@ -44,7 +44,7 @@ public final class Poller : NSObject { // MARK: Private API private func setUpPolling() { guard isPolling else { return } - SnodeAPI.getSwarm(for: getUserHexEncodedPublicKey(), isForcedReload: true).then2 { [weak self] _ -> Promise in + let _ = SnodeAPI.getSwarm(for: getUserHexEncodedPublicKey(), isForcedReload: true).then2 { [weak self] _ -> Promise in guard let strongSelf = self else { return Promise { $0.fulfill(()) } } strongSelf.usedSnodes.removeAll() let (promise, seal) = Promise.pending() @@ -96,7 +96,10 @@ public final class Poller : NSObject { guard let envelope = SSKProtoEnvelope.from(json) else { return } do { let data = try envelope.serializedData() - SSKEnvironment.shared.messageReceiver.handleReceivedEnvelopeData(data) + let job = MessageReceiveJob(data: data) + Storage.write { transaction in + SessionMessagingKit.JobQueue.shared.add(job, using: transaction) + } } catch { print("[Loki] Failed to deserialize envelope due to error: \(error).") } diff --git a/SignalUtilitiesKit/SessionManagementProtocol.swift b/SignalUtilitiesKit/SessionManagementProtocol.swift index 17fdabd77..f8f5b13a4 100644 --- a/SignalUtilitiesKit/SessionManagementProtocol.swift +++ b/SignalUtilitiesKit/SessionManagementProtocol.swift @@ -105,20 +105,17 @@ public final class SessionManagementProtocol : NSObject { // Send the session request print("[Loki] Sending session request to: \(publicKey).") Storage.setSessionRequestSentTimestamp(for: publicKey, to: NSDate.ows_millisecondTimeStamp(), using: transaction) - let sessionRequestMessage = SessionRequestMessage(thread: thread) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: sessionRequestMessage, transaction: transaction) + let sessionRequest = SessionRequest() + sessionRequest.preKeyBundle = storage.generatePreKeyBundle(forContact: publicKey) + MessageSender.send(sessionRequest, in: thread, using: transaction) } @objc(sendNullMessageToPublicKey:transaction:) public static func sendNullMessage(to publicKey: String, in transaction: YapDatabaseReadWriteTransaction) { let thread = TSContactThread.getOrCreateThread(withContactId: publicKey, transaction: transaction) thread.save(with: transaction) - let nullMessage = OWSOutgoingNullMessage(outgoingMessageWithTimestamp: NSDate.millisecondTimestamp(), in: thread, messageBody: nil, - attachmentIds: [], expiresInSeconds: 0, expireStartedAt: 0, isVoiceMessage: false, groupMetaMessage: .unspecified, quotedMessage: nil, - contactShare: nil, linkPreview: nil) - let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue - messageSenderJobQueue.add(message: nullMessage, transaction: transaction) + let nullMessage = NullMessage() + MessageSender.send(nullMessage, in: thread, using: transaction) } /// - Note: Deprecated. @@ -147,9 +144,11 @@ public final class SessionManagementProtocol : NSObject { guard ECKeyPair.isValidHexEncodedPublicKey(candidate: device) else { continue } let thread = TSContactThread.getOrCreateThread(withContactId: device, transaction: transaction) thread.save(with: transaction) + /* let endSessionMessage = EndSessionMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread) let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue messageSenderJobQueue.add(message: endSessionMessage, transaction: transaction) + */ } thread.removeAllSessionRestoreDevices(with: transaction) // Notify the user diff --git a/SignalUtilitiesKit/SessionMetaProtocol.swift b/SignalUtilitiesKit/SessionMetaProtocol.swift index dde938992..d15e4802b 100644 --- a/SignalUtilitiesKit/SessionMetaProtocol.swift +++ b/SignalUtilitiesKit/SessionMetaProtocol.swift @@ -101,19 +101,6 @@ public final class SessionMetaProtocol : NSObject { return errorMessage.timestamp < restorationTimeInMs } - @objc(shouldSkipMessageDecryptResult:wrappedIn:) - public static func shouldSkipMessageDecryptResult(_ result: OWSMessageDecryptResult, wrappedIn envelope: SSKProtoEnvelope) -> Bool { - return result.source == getUserHexEncodedPublicKey() - /* - if result.source == getUserHexEncodedPublicKey() { return true } - var isLinkedDevice = false - Storage.read { transaction in - isLinkedDevice = LokiDatabaseUtilities.isUserLinkedDevice(result.source, transaction: transaction) - } - return isLinkedDevice && envelope.type == .closedGroupCiphertext - */ - } - @objc(updateDisplayNameIfNeededForPublicKey:using:transaction:) public static func updateDisplayNameIfNeeded(for publicKey: String, using dataMessage: SSKProtoDataMessage, in transaction: YapDatabaseReadWriteTransaction) { guard let profile = dataMessage.profile, let displayName = profile.displayName, !displayName.isEmpty else { return } diff --git a/SignalUtilitiesKit/SyncMessagesProtocol.swift b/SignalUtilitiesKit/SyncMessagesProtocol.swift index ba159ef62..077354442 100644 --- a/SignalUtilitiesKit/SyncMessagesProtocol.swift +++ b/SignalUtilitiesKit/SyncMessagesProtocol.swift @@ -28,6 +28,7 @@ public final class SyncMessagesProtocol : NSObject { // MARK: - Sending @objc public static func syncProfile() { + /* Storage.writeSync { transaction in let userPublicKey = getUserHexEncodedPublicKey() let userLinkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: userPublicKey, in: transaction) @@ -41,6 +42,7 @@ public final class SyncMessagesProtocol : NSObject { messageSenderJobQueue.add(message: syncMessage, transaction: transaction) } } + */ } @objc(syncContactWithPublicKey:) @@ -73,6 +75,7 @@ public final class SyncMessagesProtocol : NSObject { @objc(syncClosedGroup:transaction:) public static func syncClosedGroup(_ thread: TSGroupThread, using transaction: YapDatabaseReadWriteTransaction) -> AnyPromise { + /* // Prepare let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue let group = thread.groupModel @@ -113,6 +116,7 @@ public final class SyncMessagesProtocol : NSObject { messageSenderJobQueue.add(message: closedGroupUpdateMessage, transaction: transaction) // This internally takes care of multi device } sendMessageToLinkedDevices() + */ // Return a dummy promise return AnyPromise.from(Promise { $0.fulfill(()) }) } @@ -132,15 +136,7 @@ public final class SyncMessagesProtocol : NSObject { } @objc public static func syncAllOpenGroups() -> AnyPromise { - let openGroupSyncMessage = SyncOpenGroupsMessage() - let (promise, seal) = Promise.pending() - let messageSender = SSKEnvironment.shared.messageSender - messageSender.send(openGroupSyncMessage, success: { - seal.fulfill(()) - }, failure: { error in - seal.reject(error) - }) - return AnyPromise.from(promise) + fatalError("Not implemented.") } // MARK: - Receiving diff --git a/SignalUtilitiesKit/TSConstants.h b/SignalUtilitiesKit/TSConstants.h index 26962c8cc..8ddac95c1 100644 --- a/SignalUtilitiesKit/TSConstants.h +++ b/SignalUtilitiesKit/TSConstants.h @@ -9,6 +9,8 @@ NS_ASSUME_NONNULL_BEGIN #ifndef TextSecureKit_Constants_h #define TextSecureKit_Constants_h +extern const NSUInteger kOversizeTextMessageSizeThreshold; + typedef NS_ENUM(NSInteger, TSWhisperMessageType) { TSUnknownMessageType = 0, TSEncryptedWhisperMessageType = 1, diff --git a/SignalUtilitiesKit/TSConstants.m b/SignalUtilitiesKit/TSConstants.m index b82b9e0e7..fbd6607e9 100644 --- a/SignalUtilitiesKit/TSConstants.m +++ b/SignalUtilitiesKit/TSConstants.m @@ -6,6 +6,8 @@ NS_ASSUME_NONNULL_BEGIN +const NSUInteger kOversizeTextMessageSizeThreshold = 2 * 1024; + BOOL IsUsingProductionService() { #ifdef USING_PRODUCTION_SERVICE diff --git a/SignalUtilitiesKit/TypingIndicators.swift b/SignalUtilitiesKit/TypingIndicators.swift index d0e7ebd73..e05cb43ff 100644 --- a/SignalUtilitiesKit/TypingIndicators.swift +++ b/SignalUtilitiesKit/TypingIndicators.swift @@ -226,12 +226,6 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators { self.thread = thread } - // MARK: - Dependencies - - private var messageSender: MessageSender { - return SSKEnvironment.shared.messageSender - } - // MARK: - func didStartTypingOutgoingInput() { @@ -325,8 +319,19 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators { if !SessionMetaProtocol.shouldSendTypingIndicator(in: thread) { return } - let message = TypingIndicatorMessage(thread: thread, action: action) - messageSender.sendPromise(message: message).retainUntilComplete() + let typingIndicator = TypingIndicator() + typingIndicator.kind = { + switch action { + case .started: return .started + case .stopped: return .stopped + } + }() + typingIndicator.threadID = thread.uniqueId! + let destination = Message.Destination.from(thread) + let job = MessageSendJob(message: typingIndicator, destination: destination) + Storage.write { transaction in + SessionMessagingKit.JobQueue.shared.add(job, using: transaction) + } } } diff --git a/SignalUtilitiesKit/Utilities/Destination+Conversion.swift b/SignalUtilitiesKit/Utilities/Destination+Conversion.swift new file mode 100644 index 000000000..f0de30135 --- /dev/null +++ b/SignalUtilitiesKit/Utilities/Destination+Conversion.swift @@ -0,0 +1,21 @@ + +public extension Message.Destination { + + static func from(_ thread: TSThread) -> Message.Destination { + if let thread = thread as? TSContactThread { + return .contact(publicKey: thread.uniqueId!) + } else if let thread = thread as? TSGroupThread, thread.usesSharedSenderKeys { + let groupID = thread.groupModel.groupId + let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID) + return .closedGroup(groupPublicKey: groupPublicKey) + } else if let thread = thread as? TSGroupThread, thread.isPublicChat { + var openGroup: OpenGroup! + Storage.read { transaction in + openGroup = LokiDatabaseUtilities.getPublicChat(for: thread.uniqueId!, in: transaction) + } + return .openGroup(channel: openGroup.channel, server: openGroup.server) + } else { + preconditionFailure("TODO: Handle legacy closed groups.") + } + } +} diff --git a/SignalUtilitiesKit/Utilities/MessageSender+Utilities.swift b/SignalUtilitiesKit/Utilities/MessageSender+Utilities.swift new file mode 100644 index 000000000..a0d635917 --- /dev/null +++ b/SignalUtilitiesKit/Utilities/MessageSender+Utilities.swift @@ -0,0 +1,17 @@ +import PromiseKit + +public extension MessageSender { + + static func send(_ message: Message, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) { + message.threadID = thread.uniqueId! + let destination = Message.Destination.from(thread) + let job = MessageSendJob(message: message, destination: destination) + SessionMessagingKit.JobQueue.shared.add(job, using: transaction) + } + + static func sendNonDurably(_ message: Message, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) -> Promise { + message.threadID = thread.uniqueId! + let destination = Message.Destination.from(thread) + return MessageSender.send(message, to: destination, using: transaction) + } +}