Signal-iOS/SignalServiceKit/src/Network/Receiving/MessageDecryptJobQueue.swift
Matthew Chen bcfba85356 Add message processing class.
* Modify message processing to allow observation of websocket queue being drained.
* Extend MessageProcessing to allow observation of REST message fetching and "all message fetching and processing".
2020-01-27 10:42:03 -03:00

210 lines
7.4 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
/// Durably Decrypt a received signal message enevelope
///
/// The queue's operations (`SSKMessageDecryptOperation`) uses `SSKMessageDecrypt` to decrypt
/// Signal Message envelope data
///
@objc
public class SSKMessageDecryptJobQueue: NSObject, JobQueue {
@objc
public override init() {
super.init()
AppReadiness.runNowOrWhenAppDidBecomeReady {
self.setup()
}
}
// MARK:
@objc(enqueueEnvelopeData:)
public func add(envelopeData: Data) {
databaseStorage.write { transaction in
self.add(envelopeData: envelopeData, transaction: transaction)
}
}
@objc(enqueueEnvelopeData:transaction:)
public func add(envelopeData: Data, transaction: SDSAnyWriteTransaction) {
let jobRecord = SSKMessageDecryptJobRecord(envelopeData: envelopeData, label: jobRecordLabel)
self.add(jobRecord: jobRecord, transaction: transaction)
}
// MARK: JobQueue
public typealias DurableOperationType = SSKMessageDecryptOperation
@objc
public static let jobRecordLabel: String = "SSKMessageDecrypt"
public static let maxRetries: UInt = 1
public let requiresInternet: Bool = false
public var runningOperations = AtomicArray<SSKMessageDecryptOperation>()
public var jobRecordLabel: String {
return type(of: self).jobRecordLabel
}
@objc
public func setup() {
guard CurrentAppContext().shouldProcessIncomingMessages else {
return
}
// GRDB TODO: Is it really a concern to run the decrypt queue when we're unregistered?
// If we want this behavior, we should observe registration state changes, and rerun
// setup whenever it changes.
// if !self.tsAccountManager.isRegisteredAndReady {
// return;
// }
defaultSetup()
}
public var isSetup = AtomicBool(false)
public func didMarkAsReady(oldJobRecord: SSKMessageDecryptJobRecord, transaction: SDSAnyWriteTransaction) {
// Do nothing.
}
public func didFlushQueue(transaction: SDSAnyWriteTransaction) {
NotificationCenter.default.postNotificationNameAsync(.messageDecryptionDidFlushQueue, object: nil, userInfo: nil)
}
public func buildOperation(jobRecord: SSKMessageDecryptJobRecord, transaction: SDSAnyReadTransaction) throws -> SSKMessageDecryptOperation {
return SSKMessageDecryptOperation(jobRecord: jobRecord)
}
let defaultQueue: OperationQueue = {
let operationQueue = OperationQueue()
operationQueue.name = "MessageDecryptQueue"
operationQueue.maxConcurrentOperationCount = 1
return operationQueue
}()
public func operationQueue(jobRecord: SSKMessageDecryptJobRecord) -> OperationQueue {
return defaultQueue
}
@objc
public func hasPendingJobsObjc(transaction: SDSAnyReadTransaction) -> Bool {
return hasPendingJobs(transaction: transaction)
}
}
enum SSKMessageDecryptOperationError: Error {
case unspecifiedError
}
extension SSKMessageDecryptOperationError: OperationError {
var isRetryable: Bool { return false }
}
public class SSKMessageDecryptOperation: OWSOperation, DurableOperation {
// MARK: DurableOperation
public let jobRecord: SSKMessageDecryptJobRecord
weak public var durableOperationDelegate: SSKMessageDecryptJobQueue?
public var operation: OWSOperation {
return self
}
// MARK: Init
init(jobRecord: SSKMessageDecryptJobRecord) {
self.jobRecord = jobRecord
super.init()
}
// MARK: Dependencies
var databaseStorage: SDSDatabaseStorage {
return SDSDatabaseStorage.shared
}
var messageDecrypter: OWSMessageDecrypter {
return SSKEnvironment.shared.messageDecrypter
}
var batchMessageProcessor: OWSBatchMessageProcessor {
return SSKEnvironment.shared.batchMessageProcessor
}
// MARK: OWSOperation
override public func run() {
do {
guard let envelopeData = jobRecord.envelopeData else {
reportError(OWSAssertionError("envelopeData was unexpectedly nil"))
return
}
let envelope = try SSKProtoEnvelope.parseData(envelopeData)
let wasReceivedByUD = self.wasReceivedByUD(envelope: envelope)
messageDecrypter.decryptEnvelope(envelope,
envelopeData: envelopeData,
successBlock: { (result: OWSMessageDecryptResult, transaction: SDSAnyWriteTransaction) in
// 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)
DispatchQueue.global().async {
self.reportSuccess()
}
},
failureBlock: {
// TODO: failureBlock should propagate specific error.
self.reportError(SSKMessageDecryptOperationError.unspecifiedError)
})
} catch {
reportError(withUndefinedRetry: error)
}
}
override public func didSucceed() {
databaseStorage.write { transaction in
self.durableOperationDelegate?.durableOperationDidSucceed(self, transaction: transaction)
}
}
override public func didReportError(_ error: Error) {
Logger.debug("remainingRetries: \(self.remainingRetries)")
databaseStorage.write { transaction in
self.durableOperationDelegate?.durableOperation(self, didReportError: error, transaction: transaction)
}
}
override public func didFail(error: Error) {
databaseStorage.write { transaction in
self.durableOperationDelegate?.durableOperation(self, didFailWithError: error, transaction: transaction)
}
}
// MARK: -
private func wasReceivedByUD(envelope: SSKProtoEnvelope) -> Bool {
let hasSenderSource: Bool
if envelope.hasValidSource {
hasSenderSource = true
} else {
hasSenderSource = false
}
return envelope.type == .unidentifiedSender && !hasSenderSource
}
}