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

import Foundation

@objc(OWSVerificationStateSyncMessage)
final class OutgoingVerificationStateSyncMessage: OutgoingSyncMessage {

    // This is a clunky name, but we want to differentiate it from
    // `recipientIdentifier` inherited from `TSOutgoingMessage`
    let verificationForRecipientAddress: SignalServiceAddress

    let paddingBytesLength: UInt

    let verificationState: OWSVerificationState
    let identityKey: Data

    init(
        localThread: TSContactThread,
        verificationState: OWSVerificationState,
        identityKey: Data,
        verificationForRecipientAddress: SignalServiceAddress,
        tx: DBReadTransaction,
    ) {
        owsAssertDebug(identityKey.count == OWSIdentityManagerImpl.Constants.identityKeyLength)
        owsAssertDebug(verificationForRecipientAddress.isValid)

        // we only sync users marking as un/verified. Never sync the conflicted
        // state, the sibling device will figure that out on its own.
        owsAssertDebug(verificationState != .noLongerVerified)

        self.verificationState = verificationState
        self.identityKey = identityKey
        self.verificationForRecipientAddress = verificationForRecipientAddress

        // This sync message should be 1-512 bytes longer than the corresponding
        // NullMessage. We store this values so the corresponding NullMessage can
        // subtract it from the total length.
        self.paddingBytesLength = UInt.random(in: 1...512)

        super.init(localThread: localThread, tx: tx)
    }

    override class var supportsSecureCoding: Bool { true }

    override func encode(with coder: NSCoder) {
        owsFail("Doesn't support serialization.")
    }

    required init?(coder: NSCoder) {
        // Doesn't support serialization.
        return nil
    }

    override var hash: Int {
        var hasher = Hasher()
        hasher.combine(super.hash)
        hasher.combine(self.identityKey)
        hasher.combine(self.paddingBytesLength)
        hasher.combine(self.verificationForRecipientAddress)
        hasher.combine(self.verificationState)
        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.identityKey == object.identityKey else { return false }
        guard self.paddingBytesLength == object.paddingBytesLength else { return false }
        guard self.verificationForRecipientAddress == object.verificationForRecipientAddress else { return false }
        guard self.verificationState == object.verificationState else { return false }
        return true
    }

    override func syncMessageBuilder(tx: DBReadTransaction) -> SSKProtoSyncMessageBuilder? {
        // We add the same amount of padding in the VerificationStateSync message and it's corresponding NullMessage so that
        // the sync message is indistinguishable from an outgoing Sent transcript corresponding to the NullMessage. We pad
        // the NullMessage so as to obscure it's content. The sync message (like all sync messages) will be *additionally*
        // padded by the superclass while being sent. The end result is we send a NullMessage of a non-distinct size, and a
        // verification sync which is ~1-512 bytes larger then that.
        owsAssertDebug(self.paddingBytesLength != 0)

        guard let verificationForRecipientAci = self.verificationForRecipientAddress.aci else {
            return nil
        }

        let verifiedProto = OWSRecipientIdentity.buildVerifiedProto(
            destinationAci: verificationForRecipientAci,
            identityKey: self.identityKey,
            verificationState: self.verificationState,
            paddingBytesLength: self.paddingBytesLength,
        )

        let syncMessageBuilder = SSKProtoSyncMessage.builder()
        syncMessageBuilder.setVerified(verifiedProto)
        return syncMessageBuilder
    }

    override var isUrgent: Bool { false }
}