Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Messages/DeviceSyncing/OutgoingSyncMessage.swift
1 views
//
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation

/**
 * Abstract base class used for the family of sync messages which take care
 * of keeping your multiple registered devices consistent. E.g. sharing contacts, sharing groups,
 * notifying your devices of sent messages, and "read" receipts.
 */
@objc(OWSOutgoingSyncMessage)
public class OutgoingSyncMessage: TransientOutgoingMessage {

    override public class var supportsSecureCoding: Bool { true }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    init(localThread: TSContactThread, tx: DBReadTransaction) {
        let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: localThread)
        super.init(
            outgoingMessageWith: messageBuilder,
            additionalRecipients: [],
            explicitRecipients: [],
            skippedRecipients: [],
            transaction: tx,
        )
    }

    init(timestamp: UInt64, localThread: TSContactThread, tx: DBReadTransaction) {
        let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: localThread)
        messageBuilder.timestamp = timestamp
        super.init(
            outgoingMessageWith: messageBuilder,
            additionalRecipients: [],
            explicitRecipients: [],
            skippedRecipients: [],
            transaction: tx,
        )
    }

    override public func shouldSyncTranscript() -> Bool { false }

    // This method should not be overridden because we want to add random padding to *every* sync message
    private func buildSyncMessage(tx: DBReadTransaction) -> SSKProtoSyncMessage? {
        guard let builder = self.syncMessageBuilder(tx: tx) else {
            return nil
        }
        do {
            return try Self.buildSyncMessageProto(forMessageBuilder: builder)
        } catch {
            owsFailDebug("could not build protobuf: \(error)")
            return nil
        }
    }

    func syncMessageBuilder(tx: DBReadTransaction) -> SSKProtoSyncMessageBuilder? {
        owsFail("Method must be implemented by subclasses.")
    }

    override public func contentBuilder(thread: TSThread, transaction: DBReadTransaction) -> SSKProtoContentBuilder? {
        guard let syncMessage = self.buildSyncMessage(tx: transaction) else {
            return nil
        }

        let contentBuilder = SSKProtoContent.builder()
        contentBuilder.setSyncMessage(syncMessage)
        return contentBuilder
    }

    static func buildSyncMessageProto(forMessageBuilder messageBuilder: SSKProtoSyncMessageBuilder) throws -> SSKProtoSyncMessage {
        // Add a random 1-512 bytes to obscure sync message type
        let paddingBytesLength = UInt.random(in: 1...512)
        messageBuilder.setPadding(Randomness.generateRandomBytes(paddingBytesLength))
        return try messageBuilder.build()
    }
}