Add SendableMessage protocol

This commit is contained in:
Max Radermacher 2026-01-26 12:18:56 -06:00 committed by GitHub
parent 238588a6d6
commit 148b493903
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 131 additions and 38 deletions

View File

@ -606,6 +606,7 @@
500B25832DD6A08300E9ECAA /* BackgroundMessageFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500B25822DD6A08300E9ECAA /* BackgroundMessageFetcher.swift */; };
500BAD802C519F2D00B4CD7F /* MessageTimestampGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500BAD7E2C519F2D00B4CD7F /* MessageTimestampGenerator.swift */; };
500BAD822C519F3600B4CD7F /* MessageTimestampGeneratorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500BAD7F2C519F2D00B4CD7F /* MessageTimestampGeneratorTest.swift */; };
500CC1E52F15661A001A86F7 /* SendableMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500CC1E42F15661A001A86F7 /* SendableMessage.swift */; };
500CC1E72F15C20B001A86F7 /* ReceiptMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500CC1E62F15C20B001A86F7 /* ReceiptMessage.swift */; };
500CC1E92F15C8A7001A86F7 /* OutgoingCallMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500CC1E82F15C8A7001A86F7 /* OutgoingCallMessage.swift */; };
500CC1EB2F15D1E5001A86F7 /* StickerPackSyncMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500CC1EA2F15D1E5001A86F7 /* StickerPackSyncMessage.swift */; };
@ -4753,6 +4754,7 @@
500B25822DD6A08300E9ECAA /* BackgroundMessageFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundMessageFetcher.swift; sourceTree = "<group>"; };
500BAD7E2C519F2D00B4CD7F /* MessageTimestampGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTimestampGenerator.swift; sourceTree = "<group>"; };
500BAD7F2C519F2D00B4CD7F /* MessageTimestampGeneratorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTimestampGeneratorTest.swift; sourceTree = "<group>"; };
500CC1E42F15661A001A86F7 /* SendableMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendableMessage.swift; sourceTree = "<group>"; };
500CC1E62F15C20B001A86F7 /* ReceiptMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptMessage.swift; sourceTree = "<group>"; };
500CC1E82F15C8A7001A86F7 /* OutgoingCallMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingCallMessage.swift; sourceTree = "<group>"; };
500CC1EA2F15D1E5001A86F7 /* StickerPackSyncMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPackSyncMessage.swift; sourceTree = "<group>"; };
@ -14393,6 +14395,7 @@
66D13F092A731E590092D47B /* RecipientHidingManager+SignalServiceAddress.swift */,
E1A090372A4B909B00F2BE8B /* RecipientHidingManager.swift */,
50B0E9472AC73C3B005D46AB /* RecipientStateMerger.swift */,
500CC1E42F15661A001A86F7 /* SendableMessage.swift */,
504F98B22EB0270A00DF465B /* SendMessageFailure.swift */,
50A5AA9A2A7449D000CF2ECC /* ServerReceiptEnvelope.swift */,
506395802F2403BE00B2F772 /* TransientOutgoingMessage.swift */,
@ -19322,6 +19325,7 @@
66138FB6298326C7002E0CFE /* SecureValueRecovery.swift in Sources */,
662C440B2A156DF7001F83E2 /* SecureValueRecovery2Impl.swift in Sources */,
6691E7F72996EAD70032A68A /* SecureValueRecoveryMock.swift in Sources */,
500CC1E52F15661A001A86F7 /* SendableMessage.swift in Sources */,
725465392BA01FAA00EABFD2 /* SendGiftBadgeJobQueue.swift in Sources */,
D9AE0AD5291877600063488B /* SendGiftBadgeJobRecord.swift in Sources */,
504F98B32EB0270A00DF465B /* SendMessageFailure.swift in Sources */,

View File

@ -770,8 +770,8 @@ public class GroupManager: NSObject {
thread.groupModel.groupMembership.invitedMembers.compactMap(\.serviceId)
}
public static func shouldMessageHaveAdditionalRecipients(
_ message: TSOutgoingMessage,
static func shouldMessageHaveAdditionalRecipients(
_ message: any SendableMessage,
groupThread: TSGroupThread,
) -> Bool {
guard groupThread.groupModel.groupsVersion == .V2 else {

View File

@ -84,7 +84,7 @@ public class MessageSendLog {
let uniqueId: String
}
func recordPayload(_ plaintext: Data, for message: TSOutgoingMessage, tx: DBWriteTransaction) -> Int64? {
func recordPayload(_ plaintext: Data, for message: any SendableMessage, tx: DBWriteTransaction) -> Int64? {
guard !RemoteConfig.current.messageResendKillSwitch else {
return nil
}
@ -134,7 +134,7 @@ public class MessageSendLog {
plaintextContent: plaintext,
contentHint: message.contentHint,
sentTimestamp: message.timestamp,
uniqueThreadId: message.uniqueThreadId,
uniqueThreadId: message.threadUniqueId,
sendComplete: false,
)
try payload.insert(tx.database)
@ -197,7 +197,7 @@ public class MessageSendLog {
return existingValue.payload
}
public func sendComplete(message: TSOutgoingMessage, tx: DBWriteTransaction) {
func sendComplete(message: any SendableMessage, tx: DBWriteTransaction) {
guard !RemoteConfig.current.messageResendKillSwitch else {
return
}
@ -221,10 +221,10 @@ public class MessageSendLog {
}
private func fetchUniquePayload(
for message: TSOutgoingMessage,
for message: any SendableMessage,
tx: DBReadTransaction,
) throws -> (Int64, Payload)? {
let query = fetchRequest(threadUniqueId: message.uniqueThreadId).filter(Column("sentTimestamp") == message.timestamp)
let query = fetchRequest(threadUniqueId: message.threadUniqueId).filter(Column("sentTimestamp") == message.timestamp)
return try fetchUniquePayload(query: query, tx: tx)
}
@ -283,7 +283,7 @@ public class MessageSendLog {
payloadId: Int64,
recipientAci: Aci,
recipientDeviceId: DeviceId,
message: TSOutgoingMessage,
message: any SendableMessage,
tx: DBWriteTransaction,
) {
guard !RemoteConfig.current.messageResendKillSwitch else {
@ -315,7 +315,7 @@ public class MessageSendLog {
}
func recordSuccessfulDelivery(
message: TSOutgoingMessage,
message: any SendableMessage,
recipientAci: Aci,
recipientDeviceId: DeviceId,
tx: DBWriteTransaction,

View File

@ -55,7 +55,7 @@ extension MessageSender {
func prepareSenderKeyMessageSend(
for recipients: [ServiceId],
in thread: TSThread,
message: TSOutgoingMessage,
message: any SendableMessage,
serializedMessage: SerializedMessage,
endorsements: GroupSendEndorsements?,
udAccessMap: [Aci: OWSUDAccess],
@ -219,7 +219,7 @@ extension MessageSender {
private func sendSenderKeyMessage(
to eligibleRecipients: Set<ServiceId>,
in thread: TSThread,
message: TSOutgoingMessage,
message: any SendableMessage,
serializedMessage: SerializedMessage,
authBuilder: (_ readyRecipients: [ServiceId]) -> TSRequest.SealedSenderAuth,
senderCertificate: SenderCertificate,
@ -275,7 +275,7 @@ extension MessageSender {
private func sendSenderKeyCiphertext(
_ ciphertextResult: Result<Data, any Error>,
to recipients: [Recipient],
message: TSOutgoingMessage,
message: any SendableMessage,
payloadId: Int64?,
authBuilder: () -> TSRequest.SealedSenderAuth,
localIdentifiers: LocalIdentifiers,
@ -343,7 +343,7 @@ extension MessageSender {
private func prepareSenderKeyDistributionMessages(
for recipients: some Sequence<ServiceId>,
in thread: TSThread,
originalMessage: TSOutgoingMessage,
originalMessage: any SendableMessage,
endorsements: GroupSendEndorsements?,
udAccessMap: [Aci: OWSUDAccess],
senderCertificate: SenderCertificate,
@ -475,7 +475,7 @@ extension MessageSender {
/// *except* those returned as unregistered in the result.
private func sendSenderKeyRequest(
to recipients: [Recipient],
message: TSOutgoingMessage,
message: any SendableMessage,
ciphertextResult: Result<Data, any Error>,
authBuilder: () -> TSRequest.SealedSenderAuth,
) async throws -> SenderKeySendResult {
@ -585,7 +585,7 @@ extension MessageSender {
private func senderKeyMessageBody(
plaintext: Data,
message: TSOutgoingMessage,
message: any SendableMessage,
thread: TSThread,
recipients: [Recipient],
senderCertificate: SenderCertificate,

View File

@ -8,7 +8,7 @@ import LibSignalClient
// MARK: - Message "isXYZ" properties
private extension TSOutgoingMessage {
extension TSOutgoingMessage {
var isTransientSKDM: Bool {
(self as? OutgoingSenderKeyDistributionMessage)?.isSentOnBehalfOfOnlineMessage ?? false
}
@ -456,7 +456,7 @@ public class MessageSender {
// * A recipient is unregistered.
// * A recipient does not have the required capability.
private func markSkippedRecipients(
of message: TSOutgoingMessage,
of message: any SendableMessage,
sendingRecipients: [ServiceId],
tx: DBWriteTransaction,
) {
@ -466,7 +466,7 @@ public class MessageSender {
}
private func unsentRecipients(
of message: TSOutgoingMessage,
of message: any SendableMessage,
in thread: TSThread,
localIdentifiers: LocalIdentifiers,
tx: DBReadTransaction,
@ -578,7 +578,7 @@ public class MessageSender {
)
}
private func areAttachmentsUploadedWithSneakyTransaction(for message: TSOutgoingMessage) -> Bool {
private func areAttachmentsUploadedWithSneakyTransaction(for message: any SendableMessage) -> Bool {
if message.shouldBeSaved == false {
// Unsaved attachments come in two types:
// * no attachments
@ -597,7 +597,7 @@ public class MessageSender {
}
}
private func sendPreparedMessage(_ message: TSOutgoingMessage) async throws -> SendMessageFailure? {
private func sendPreparedMessage(_ message: any SendableMessage) async throws -> SendMessageFailure? {
if !areAttachmentsUploadedWithSneakyTransaction(for: message) {
throw OWSGenericError("attachments aren't uploaded")
}
@ -691,12 +691,13 @@ public class MessageSender {
}
private func sendPreparedMessage(
_ message: TSOutgoingMessage,
_ message: any SendableMessage,
recoveryState: OuterRecoveryState,
senderCertificates: SenderCertificates,
localIdentifiers: LocalIdentifiers,
) async throws -> SendMessageFailure? {
let nextAction = try await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx -> SendMessageNextAction? in
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
let nextAction = try await databaseStorage.awaitableWrite { tx -> SendMessageNextAction? in
guard let thread = message.thread(tx: tx) else {
throw MessageSenderError.threadMissing
}
@ -906,7 +907,7 @@ public class MessageSender {
}
private func sendPreparedMessage(
message: TSOutgoingMessage,
message: any SendableMessage,
serializedMessage: SerializedMessage,
in thread: TSThread,
viaFanoutTo fanoutRecipients: Set<ServiceId>,
@ -1005,7 +1006,7 @@ public class MessageSender {
}
private func handleSendFailure(
message: TSOutgoingMessage,
message: any SendableMessage,
thread: TSThread,
perRecipientErrors allErrors: [(serviceId: ServiceId, error: any Error)],
) async throws -> SendMessageFailure? {
@ -1057,7 +1058,7 @@ public class MessageSender {
}
private func normalizeRecipientStatesIfNeeded(
message: TSOutgoingMessage,
message: any SendableMessage,
recipientErrors: some Sequence<(serviceId: ServiceId, error: Error)>,
tx: DBWriteTransaction,
) {
@ -1090,7 +1091,7 @@ public class MessageSender {
/// It is important to be conservative about which messages unhide a
/// recipient. It is far better to not unhide when should than to
/// unhide when we should not.
private func shouldMessageSendUnhideRecipient(_ message: TSOutgoingMessage, tx: DBReadTransaction) -> Bool {
private func shouldMessageSendUnhideRecipient(_ message: any SendableMessage, tx: DBReadTransaction) -> Bool {
if
message.shouldBeSaved,
let rowId = message.sqliteRowId,
@ -1117,7 +1118,7 @@ public class MessageSender {
}
private func handleMessageSentLocally(
_ message: TSOutgoingMessage,
_ message: any SendableMessage,
localIdentifiers: LocalIdentifiers,
) async throws {
await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in
@ -1181,7 +1182,7 @@ public class MessageSender {
}
private func sendSyncTranscriptIfNeeded(
forMessage message: TSOutgoingMessage,
forMessage message: any SendableMessage,
localIdentifiers: LocalIdentifiers,
) async throws {
guard message.shouldSyncTranscript() else {
@ -1195,7 +1196,7 @@ public class MessageSender {
}
private func sendSyncTranscript(
forMessage message: TSOutgoingMessage,
forMessage message: any SendableMessage,
localIdentifiers: LocalIdentifiers,
) async throws {
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
@ -1228,7 +1229,7 @@ public class MessageSender {
}
func buildAndRecordMessage(
_ message: TSOutgoingMessage,
_ message: any SendableMessage,
in thread: TSThread,
tx: DBWriteTransaction,
) throws -> SerializedMessage {
@ -1615,7 +1616,7 @@ public class MessageSender {
messageSend: OWSMessageSend,
sealedSenderParameters: SealedSenderParameters?,
) async throws -> [SentDeviceMessage] {
let message: TSOutgoingMessage = messageSend.message
let message = messageSend.message
let requestMaker = RequestMaker(
label: "Message Send",
@ -1659,7 +1660,7 @@ public class MessageSender {
deviceMessages: [DeviceMessage],
wasSentByUD: Bool,
) async -> [SentDeviceMessage] {
let message: TSOutgoingMessage = messageSend.message
let message = messageSend.message
Logger.info("Successfully sent message: \(type(of: message)), serviceId: \(messageSend.serviceId), timestamp: \(message.timestamp), wasSentByUD: \(wasSentByUD)")
@ -1722,7 +1723,7 @@ public class MessageSender {
responseError: Error,
sealedSenderParameters: SealedSenderParameters?,
) async throws -> [SentDeviceMessage] {
let message: TSOutgoingMessage = messageSend.message
let message = messageSend.message
Logger.warn("\(type(of: message)) to \(messageSend.serviceId), timestamp: \(message.timestamp), error: \(responseError)")

View File

@ -8,13 +8,13 @@ import LibSignalClient
/// Provides parameters required for assembling a Sealed Sender message.
final class SealedSenderParameters {
let message: TSOutgoingMessage
let message: any SendableMessage
let senderCertificate: SenderCertificate
let accessKey: OWSUDAccess?
let endorsement: GroupSendFullTokenBuilder?
init?(
message: TSOutgoingMessage,
message: any SendableMessage,
senderCertificate: SenderCertificate,
accessKey: OWSUDAccess?,
endorsement: GroupSendFullTokenBuilder?,
@ -45,7 +45,7 @@ final class SealedSenderParameters {
// to multiple recipients and therefore require multiple instances of
// OWSMessageSend.
final class OWSMessageSend {
let message: TSOutgoingMessage
let message: any SendableMessage
let plaintextContent: Data
let plaintextPayloadId: Int64?
let thread: TSThread
@ -53,7 +53,7 @@ final class OWSMessageSend {
let localIdentifiers: LocalIdentifiers
init(
message: TSOutgoingMessage,
message: any SendableMessage,
plaintextContent: Data,
plaintextPayloadId: Int64?,
thread: TSThread,

View File

@ -25,7 +25,7 @@ final class OutgoingSenderKeyDistributionMessage: TransientOutgoingMessage {
init(
recipientThread: TSContactThread,
senderKeyDistributionMessage: SenderKeyDistributionMessage,
onBehalfOfMessage originalMessage: TSOutgoingMessage,
onBehalfOfMessage originalMessage: any SendableMessage,
inThread originalThread: TSThread,
tx: DBReadTransaction,
) {

View File

@ -0,0 +1,88 @@
//
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import LibSignalClient
protocol SendableMessage {
var threadUniqueId: String { get }
var sqliteRowId: Int64? { get }
var uniqueId: String { get }
var timestamp: UInt64 { get }
var isSyncMessage: Bool { get }
var isStorySend: Bool { get }
var isOnline: Bool { get }
var isTransientSKDM: Bool { get }
var isUrgent: Bool { get }
var isResendRequest: Bool { get }
var canSendToLocalAddress: Bool { get }
var contentHint: SealedSenderContentHint { get }
var encryptionStyle: EncryptionStyle { get }
func buildPlainTextData(_ thread: TSThread, transaction: DBWriteTransaction) -> Data?
var wasSentToAnyRecipient: Bool { get }
func recipientAddresses() -> [SignalServiceAddress]
func sendingRecipientAddresses() -> [SignalServiceAddress]
func sentRecipientAddresses() -> [SignalServiceAddress]
func insertedMessageHasRenderableContent(rowId: Int64, tx: DBReadTransaction) -> Bool
func anyUpdateOutgoingMessage(transaction: DBWriteTransaction, block: (TSOutgoingMessage) -> Void)
func updateWithSkippedRecipients(_ skippedRecipients: some Sequence<SignalServiceAddress>, tx: DBWriteTransaction)
func updateWithFailedRecipients(_ recipientErrors: some Collection<(serviceId: ServiceId, error: Error)>, tx: DBWriteTransaction)
func updateWithSentRecipients(_ serviceIds: [ServiceId], wasSentByUD: Bool, transaction: DBWriteTransaction)
func update(withReadRecipient recipientAddress: SignalServiceAddress, deviceId: DeviceId, readTimestamp timestamp: UInt64, tx: DBWriteTransaction)
func update(withViewedRecipient recipientAddress: SignalServiceAddress, deviceId: DeviceId, viewedTimestamp timestamp: UInt64, tx: DBWriteTransaction)
func shouldSyncTranscript() -> Bool
func thread(tx: DBReadTransaction) -> TSThread?
var shouldBeSaved: Bool { get }
var shouldRecordSendLog: Bool { get }
var relatedUniqueIds: Set<String> { get }
func envelopeGroupIdWithTransaction(_ transaction: DBReadTransaction) -> Data?
var isVoiceMessage: Bool { get }
var isViewOnceMessage: Bool { get }
func buildTranscriptSyncMessage(
localThread: TSContactThread,
transaction: DBWriteTransaction,
) -> OutgoingSyncMessage?
func update(withHasSyncedTranscript: Bool, transaction: DBWriteTransaction)
func allAttachments(transaction: DBReadTransaction) -> [ReferencedAttachment]
}
extension TSOutgoingMessage: SendableMessage {
var threadUniqueId: String { self.uniqueThreadId }
}