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()
}
}