223 lines
9.3 KiB
Swift
223 lines
9.3 KiB
Swift
//
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import LibSignalClient
|
|
|
|
@objc(OWSOutgoingResendResponse)
|
|
final class OWSOutgoingResendResponse: TransientOutgoingMessage {
|
|
override class var supportsSecureCoding: Bool { true }
|
|
|
|
required init?(coder: NSCoder) {
|
|
self.derivedContentHint = (coder.decodeObject(of: NSNumber.self, forKey: "derivedContentHint")?.intValue).flatMap(SealedSenderContentHint.init(rawValue:)) ?? .default
|
|
self.didAppendSKDM = coder.decodeObject(of: NSNumber.self, forKey: "didAppendSKDM")?.boolValue ?? false
|
|
self.originalGroupId = coder.decodeObject(of: NSData.self, forKey: "originalGroupId") as Data?
|
|
self.originalMessagePlaintext = coder.decodeObject(of: NSData.self, forKey: "originalMessagePlaintext") as Data?
|
|
self.originalThreadId = coder.decodeObject(of: NSString.self, forKey: "originalThreadId") as String?
|
|
super.init(coder: coder)
|
|
}
|
|
|
|
override func encode(with coder: NSCoder) {
|
|
super.encode(with: coder)
|
|
coder.encode(NSNumber(value: self.derivedContentHint.rawValue), forKey: "derivedContentHint")
|
|
coder.encode(NSNumber(value: self.didAppendSKDM), forKey: "didAppendSKDM")
|
|
if let originalGroupId {
|
|
coder.encode(originalGroupId, forKey: "originalGroupId")
|
|
}
|
|
if let originalMessagePlaintext {
|
|
coder.encode(originalMessagePlaintext, forKey: "originalMessagePlaintext")
|
|
}
|
|
if let originalThreadId {
|
|
coder.encode(originalThreadId, forKey: "originalThreadId")
|
|
}
|
|
}
|
|
|
|
override var hash: Int {
|
|
var hasher = Hasher()
|
|
hasher.combine(super.hash)
|
|
hasher.combine(derivedContentHint)
|
|
hasher.combine(didAppendSKDM)
|
|
hasher.combine(originalGroupId)
|
|
hasher.combine(originalMessagePlaintext)
|
|
hasher.combine(originalThreadId)
|
|
return hasher.finalize()
|
|
}
|
|
|
|
override func isEqual(_ object: Any?) -> Bool {
|
|
guard let object = object as? Self else { return false }
|
|
guard super.isEqual(object) else { return false }
|
|
guard self.derivedContentHint == object.derivedContentHint else { return false }
|
|
guard self.didAppendSKDM == object.didAppendSKDM else { return false }
|
|
guard self.originalGroupId == object.originalGroupId else { return false }
|
|
guard self.originalMessagePlaintext == object.originalMessagePlaintext else { return false }
|
|
guard self.originalThreadId == object.originalThreadId else { return false }
|
|
return true
|
|
}
|
|
|
|
private(set) var originalMessagePlaintext: Data?
|
|
private(set) var originalThreadId: String?
|
|
private(set) var originalGroupId: Data?
|
|
private var derivedContentHint: SealedSenderContentHint
|
|
private(set) var didAppendSKDM: Bool = false
|
|
|
|
private init(
|
|
outgoingMessageBuilder: TSOutgoingMessageBuilder,
|
|
originalMessagePlaintext: Data?,
|
|
originalThreadId: String?,
|
|
originalGroupId: Data?,
|
|
derivedContentHint: SealedSenderContentHint,
|
|
tx: DBWriteTransaction,
|
|
) {
|
|
self.derivedContentHint = derivedContentHint
|
|
super.init(
|
|
outgoingMessageWith: outgoingMessageBuilder,
|
|
additionalRecipients: [],
|
|
explicitRecipients: [],
|
|
skippedRecipients: [],
|
|
transaction: tx,
|
|
)
|
|
self.originalMessagePlaintext = originalMessagePlaintext
|
|
self.originalThreadId = originalThreadId
|
|
self.originalGroupId = originalGroupId
|
|
}
|
|
|
|
convenience init?(
|
|
aci: Aci,
|
|
deviceId: DeviceId,
|
|
failedTimestamp: UInt64,
|
|
didResetSession: Bool,
|
|
tx: DBWriteTransaction,
|
|
) {
|
|
let targetThread = TSContactThread.getOrCreateThread(withContactAddress: SignalServiceAddress(aci), transaction: tx)
|
|
let builder: TSOutgoingMessageBuilder = .withDefaultValues(thread: targetThread)
|
|
|
|
let messageSendLog = SSKEnvironment.shared.messageSendLogRef
|
|
if
|
|
let payloadRecord = messageSendLog.fetchPayload(
|
|
recipientAci: aci,
|
|
recipientDeviceId: deviceId,
|
|
timestamp: failedTimestamp,
|
|
tx: tx,
|
|
)
|
|
{
|
|
let originalThread = TSThread.fetchViaCache(uniqueId: payloadRecord.uniqueThreadId, transaction: tx)
|
|
|
|
// We should inherit the timestamp of the failed message. This allows the
|
|
// recipient of this message to correlate the resend response with the
|
|
// original failed message.
|
|
builder.timestamp = payloadRecord.sentTimestamp
|
|
|
|
// We also want to reset the delivery record for the failing address if
|
|
// this was a sender key group. This will be re-marked as delivered on
|
|
// success if we included an SKDM in the resend response
|
|
if let originalThread, originalThread.isGroupThread {
|
|
SSKEnvironment.shared.senderKeyStoreRef.resetSenderKeyDeliveryRecord(for: originalThread, serviceId: aci, writeTx: tx)
|
|
}
|
|
|
|
self.init(
|
|
outgoingMessageBuilder: builder,
|
|
originalMessagePlaintext: payloadRecord.plaintextContent,
|
|
originalThreadId: payloadRecord.uniqueThreadId,
|
|
originalGroupId: (originalThread as? TSGroupThread)?.groupId,
|
|
derivedContentHint: payloadRecord.contentHint,
|
|
tx: tx,
|
|
)
|
|
} else if didResetSession {
|
|
Logger.info("Failed to find MSL record for resend request: \(failedTimestamp). Will reply with Null message")
|
|
self.init(
|
|
outgoingMessageBuilder: builder,
|
|
originalMessagePlaintext: nil,
|
|
originalThreadId: nil,
|
|
originalGroupId: nil,
|
|
derivedContentHint: .implicit,
|
|
tx: tx,
|
|
)
|
|
} else {
|
|
Logger.warn("Failed to find MSL record for resend request: \(failedTimestamp). Declining to respond.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
override var shouldRecordSendLog: Bool { false }
|
|
|
|
override func shouldSyncTranscript() -> Bool { false }
|
|
|
|
override var contentHint: SealedSenderContentHint { self.derivedContentHint }
|
|
|
|
override func envelopeGroupIdWithTransaction(_ transaction: DBReadTransaction) -> Data? { self.originalGroupId }
|
|
|
|
override func buildPlaintextData(inThread thread: TSThread, tx: DBWriteTransaction) throws -> Data {
|
|
owsAssertDebug(self.recipientAddresses().count == 1)
|
|
|
|
let contentBuilder: SSKProtoContentBuilder = {
|
|
if let originalMessagePlaintext {
|
|
do {
|
|
return try resentProtoBuilder(from: originalMessagePlaintext)
|
|
} catch {
|
|
owsFailDebug("Failed to build resent content: \(error)")
|
|
// fallthrough
|
|
}
|
|
}
|
|
return nullMessageProtoBuilder()
|
|
}()
|
|
|
|
let tsAccountManager = DependenciesBridge.shared.tsAccountManager
|
|
if
|
|
let originalThreadId,
|
|
let originalThread = TSThread.fetchViaCache(uniqueId: originalThreadId, transaction: tx),
|
|
originalThread.usesSenderKey,
|
|
let recipientAddress = self.recipientAddresses().first,
|
|
originalThread.recipientAddresses(with: tx).contains(recipientAddress),
|
|
let registeredState = try? tsAccountManager.registeredState(tx: tx),
|
|
let deviceId = tsAccountManager.storedDeviceId(tx: tx).ifValid
|
|
{
|
|
let senderKeyStore = SSKEnvironment.shared.senderKeyStoreRef
|
|
do {
|
|
let senderKeyDistributionMessage = try senderKeyStore.senderKeyDistributionMessage(
|
|
forThread: originalThread,
|
|
localAci: registeredState.localIdentifiers.aci,
|
|
localDeviceId: deviceId,
|
|
tx: tx,
|
|
)
|
|
contentBuilder.setSenderKeyDistributionMessage(senderKeyDistributionMessage.serialize())
|
|
self.didAppendSKDM = true
|
|
} catch {
|
|
owsFailDebug("couldn't append SKDM: \(error)")
|
|
}
|
|
}
|
|
|
|
return try contentBuilder.buildSerializedData()
|
|
}
|
|
|
|
private func resentProtoBuilder(from plaintextData: Data) throws -> SSKProtoContentBuilder {
|
|
return try SSKProtoContent(serializedData: plaintextData).asBuilder()
|
|
}
|
|
|
|
private func nullMessageProtoBuilder() -> SSKProtoContentBuilder {
|
|
let contentBuilder = SSKProtoContent.builder()
|
|
contentBuilder.setNullMessage(SSKProtoNullMessage.builder().buildInfallibly())
|
|
return contentBuilder
|
|
}
|
|
|
|
func didPerformMessageSend(_ sentMessages: [SentDeviceMessage], to serviceId: ServiceId, tx: DBWriteTransaction) {
|
|
if
|
|
self.didAppendSKDM,
|
|
let originalThreadId,
|
|
let originalThread = TSThread.fetchViaCache(uniqueId: originalThreadId, transaction: tx),
|
|
originalThread.usesSenderKey
|
|
{
|
|
do {
|
|
try SSKEnvironment.shared.senderKeyStoreRef.recordSentSenderKeys(
|
|
[SentSenderKey(recipient: serviceId, messages: sentMessages)],
|
|
for: originalThread,
|
|
writeTx: tx,
|
|
)
|
|
} catch {
|
|
owsFailDebug("Couldn't update sender key after resend: \(error)")
|
|
}
|
|
}
|
|
}
|
|
}
|