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

import LibSignalClient

/// Represents a message that can be "addressed" stably across clients.
struct AddressableMessage {
    enum Author {
        case aci(Aci)
        case e164(E164)
    }

    let author: Author
    let sentTimestamp: UInt64

    init(author: Author, sentTimestamp: UInt64) {
        self.author = author
        self.sentTimestamp = sentTimestamp
    }

    init?(message: TSMessage, localIdentifiers: LocalIdentifiers) {
        if let incomingMessage = message as? TSIncomingMessage {
            self.init(incomingMessage: incomingMessage)
        } else if let outgoingMessage = message as? TSOutgoingMessage {
            self.init(outgoingMessage: outgoingMessage, localIdentifiers: localIdentifiers)
        } else {
            return nil
        }
    }

    init?(incomingMessage: TSIncomingMessage) {
        if let authorAci = incomingMessage.authorAddress.aci {
            author = .aci(authorAci)
        } else if let authorE164 = incomingMessage.authorAddress.e164 {
            author = .e164(authorE164)
        } else {
            return nil
        }

        sentTimestamp = incomingMessage.timestamp
    }

    init(outgoingMessage: TSOutgoingMessage, localIdentifiers: LocalIdentifiers) {
        author = .aci(localIdentifiers.aci)
        sentTimestamp = outgoingMessage.timestamp
    }

    init?(proto: SSKProtoAddressableMessage) {
        guard proto.hasSentTimestamp, SDS.fitsInInt64(proto.sentTimestamp) else {
            return nil
        }

        if
            let authorAci = ServiceId.parseFrom(
                serviceIdBinary: proto.authorServiceIDBinary,
                serviceIdString: proto.authorServiceID,
            ) as? Aci
        {
            author = .aci(authorAci)
        } else if let authorE164 = E164(proto.authorE164) {
            author = .e164(authorE164)
        } else {
            return nil
        }

        sentTimestamp = proto.sentTimestamp
    }

    var asProto: SSKProtoAddressableMessage {
        let protoBuilder = SSKProtoAddressableMessage.builder()
        protoBuilder.setSentTimestamp(sentTimestamp)
        switch author {
        case .aci(let aci): protoBuilder.setAuthorServiceIDBinary(aci.serviceIdBinary)
        case .e164(let e164): protoBuilder.setAuthorE164(e164.stringValue)
        }
        return protoBuilder.buildInfallibly()
    }
}

// MARK: -

enum ConversationIdentifier {
    case serviceId(ServiceId)
    case e164(E164)
    case groupIdentifier(GroupIdentifier)

    init?(proto: SSKProtoConversationIdentifier) {
        if
            let serviceId = ServiceId.parseFrom(
                serviceIdBinary: proto.threadServiceIDBinary,
                serviceIdString: proto.threadServiceID,
            )
        {
            self = .serviceId(serviceId)
        } else if let e164 = E164(proto.threadE164) {
            self = .e164(e164)
        } else if
            let groupIdData = proto.threadGroupID,
            let groupIdentifier = try? GroupIdentifier(contents: groupIdData)
        {
            self = .groupIdentifier(groupIdentifier)
        } else {
            return nil
        }
    }

    var asProto: SSKProtoConversationIdentifier {
        let protoBuilder = SSKProtoConversationIdentifier.builder()
        switch self {
        case .serviceId(let serviceId): protoBuilder.setThreadServiceIDBinary(serviceId.serviceIdBinary)
        case .e164(let e164): protoBuilder.setThreadE164(e164.stringValue)
        case .groupIdentifier(let groupIdentifier): protoBuilder.setThreadGroupID(groupIdentifier.serialize())
        }
        return protoBuilder.buildInfallibly()
    }
}