import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.PendingAdminApprovalException;
import org.asamk.signal.manager.api.ReceiveConfig;
+import org.asamk.signal.manager.api.Recipient;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.recipients.Profile;
-import org.asamk.signal.manager.storage.recipients.Recipient;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.io.Closeable;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.PendingAdminApprovalException;
import org.asamk.signal.manager.api.ReceiveConfig;
+import org.asamk.signal.manager.api.Recipient;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResult;
import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.manager.storage.recipients.Profile;
-import org.asamk.signal.manager.storage.recipients.Recipient;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack;
import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore;
@Override
public void deleteRecipient(final RecipientIdentifier.Single recipient) {
- account.removeRecipient(account.getRecipientResolver().resolveRecipient(recipient.toPartialRecipientAddress()));
+ account.removeRecipient(account.getRecipientResolver().resolveRecipient(recipient.getIdentifier()));
}
@Override
public void deleteContact(final RecipientIdentifier.Single recipient) {
account.getContactStore()
- .deleteContact(account.getRecipientResolver().resolveRecipient(recipient.toPartialRecipientAddress()));
+ .deleteContact(account.getRecipientResolver().resolveRecipient(recipient.getIdentifier()));
}
@Override
}
// refresh profiles of explicitly given recipients
context.getProfileHelper().refreshRecipientProfiles(recipientIds);
- return account.getRecipientStore().getRecipients(onlyContacts, blocked, recipientIds, name);
+ return account.getRecipientStore()
+ .getRecipients(onlyContacts, blocked, recipientIds, name)
+ .stream()
+ .map(s -> new Recipient(s.getRecipientId(),
+ s.getAddress().toApiRecipientAddress(),
+ s.getContact(),
+ s.getProfileKey(),
+ s.getExpiringProfileKeyCredential(),
+ s.getProfile()))
+ .toList();
}
@Override
.resolveRecipientAddress(account.getRecipientResolver().resolveRecipient(identityInfo.getServiceId()));
final var scannableFingerprint = context.getIdentityHelper()
.computeSafetyNumberForScanning(identityInfo.getServiceId(), identityInfo.getIdentityKey());
- return new Identity(address,
+ return new Identity(address.toApiRecipientAddress(),
identityInfo.getIdentityKey(),
context.getIdentityHelper()
.computeSafetyNumber(identityInfo.getServiceId(), identityInfo.getIdentityKey()),
import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.groups.GroupInfo;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import java.util.Set;
groupInfo.getMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
+ .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getPendingMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
+ .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getRequestingMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
+ .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getAdminMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
+ .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getBannedMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
+ .map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.isBlocked(),
groupInfo.getMessageExpirationTimer(),
package org.asamk.signal.manager.api;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.signal.libsignal.protocol.IdentityKey;
public record Identity(
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.helper.RecipientAddressResolver;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.metadata.ProtocolException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
RecipientAddressResolver addressResolver
) {
return new StoryContext(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
- storyContext.getAuthorServiceId())), storyContext.getSentTimestamp());
+ storyContext.getAuthorServiceId())).toApiRecipientAddress(), storyContext.getSentTimestamp());
}
}
RecipientAddressResolver addressResolver
) {
return new Reaction(reaction.getTargetSentTimestamp(),
- addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(reaction.getTargetAuthor())),
+ addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(reaction.getTargetAuthor()))
+ .toApiRecipientAddress(),
reaction.getEmoji(),
reaction.isRemove());
}
final AttachmentFileProvider fileProvider
) {
return new Quote(quote.getId(),
- addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor())),
+ addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor()))
+ .toApiRecipientAddress(),
Optional.ofNullable(quote.getText()),
quote.getMentions() == null
? List.of()
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver
) {
- return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getServiceId())),
- mention.getStart(),
- mention.getLength());
+ return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getServiceId()))
+ .toApiRecipientAddress(), mention.getStart(), mention.getLength());
}
}
return new Sent(sentMessage.getTimestamp(),
sentMessage.getExpirationStartTimestamp(),
sentMessage.getDestination()
- .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))),
+ .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))
+ .toApiRecipientAddress()),
sentMessage.getRecipients()
.stream()
- .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
+ .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))
+ .toApiRecipientAddress())
.collect(Collectors.toSet()),
sentMessage.getDataMessage()
.map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)),
) {
return new Blocked(blockedListMessage.getAddresses()
.stream()
- .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
+ .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))
+ .toApiRecipientAddress())
.toList(), blockedListMessage.getGroupIds().stream().map(GroupId::unknownVersion).toList());
}
}
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver
) {
- return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
- readMessage.getTimestamp());
+ return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender()))
+ .toApiRecipientAddress(), readMessage.getTimestamp());
}
}
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver
) {
- return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
- readMessage.getTimestamp());
+ return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender()))
+ .toApiRecipientAddress(), readMessage.getTimestamp());
}
}
RecipientAddressResolver addressResolver
) {
return new ViewOnceOpen(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
- readMessage.getSender())), readMessage.getTimestamp());
+ readMessage.getSender())).toApiRecipientAddress(), readMessage.getTimestamp());
}
}
return new MessageRequestResponse(Type.from(messageRequestResponse.getType()),
messageRequestResponse.getGroupId().map(GroupId::unknownVersion),
messageRequestResponse.getPerson()
- .map(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(p))));
+ .map(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(p))
+ .toApiRecipientAddress()));
}
public enum Type {
return new MessageEnvelope(source == null
? Optional.empty()
- : Optional.of(addressResolver.resolveRecipientAddress(source)),
+ : Optional.of(addressResolver.resolveRecipientAddress(source).toApiRecipientAddress()),
sourceDevice,
envelope.getTimestamp(),
envelope.getServerReceivedTimestamp(),
--- /dev/null
+package org.asamk.signal.manager.api;
+
+import org.asamk.signal.manager.storage.recipients.Contact;
+import org.asamk.signal.manager.storage.recipients.Profile;
+import org.asamk.signal.manager.storage.recipients.RecipientId;
+import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
+import org.signal.libsignal.zkgroup.profiles.ProfileKey;
+
+import java.util.Objects;
+
+public class Recipient {
+
+ private final RecipientId recipientId;
+
+ private final RecipientAddress address;
+
+ private final Contact contact;
+
+ private final ProfileKey profileKey;
+
+ private final ExpiringProfileKeyCredential expiringProfileKeyCredential;
+
+ private final Profile profile;
+
+ public Recipient(
+ final RecipientId recipientId,
+ final RecipientAddress address,
+ final Contact contact,
+ final ProfileKey profileKey,
+ final ExpiringProfileKeyCredential expiringProfileKeyCredential,
+ final Profile profile
+ ) {
+ this.recipientId = recipientId;
+ this.address = address;
+ this.contact = contact;
+ this.profileKey = profileKey;
+ this.expiringProfileKeyCredential = expiringProfileKeyCredential;
+ this.profile = profile;
+ }
+
+ private Recipient(final Builder builder) {
+ recipientId = builder.recipientId;
+ address = builder.address;
+ contact = builder.contact;
+ profileKey = builder.profileKey;
+ expiringProfileKeyCredential = builder.expiringProfileKeyCredential1;
+ profile = builder.profile;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static Builder newBuilder(final Recipient copy) {
+ Builder builder = new Builder();
+ builder.recipientId = copy.getRecipientId();
+ builder.address = copy.getAddress();
+ builder.contact = copy.getContact();
+ builder.profileKey = copy.getProfileKey();
+ builder.expiringProfileKeyCredential1 = copy.getExpiringProfileKeyCredential();
+ builder.profile = copy.getProfile();
+ return builder;
+ }
+
+ public RecipientId getRecipientId() {
+ return recipientId;
+ }
+
+ public RecipientAddress getAddress() {
+ return address;
+ }
+
+ public Contact getContact() {
+ return contact;
+ }
+
+ public ProfileKey getProfileKey() {
+ return profileKey;
+ }
+
+ public ExpiringProfileKeyCredential getExpiringProfileKeyCredential() {
+ return expiringProfileKeyCredential;
+ }
+
+ public Profile getProfile() {
+ return profile;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final Recipient recipient = (Recipient) o;
+ return Objects.equals(recipientId, recipient.recipientId)
+ && Objects.equals(address, recipient.address)
+ && Objects.equals(contact, recipient.contact)
+ && Objects.equals(profileKey, recipient.profileKey)
+ && Objects.equals(expiringProfileKeyCredential, recipient.expiringProfileKeyCredential)
+ && Objects.equals(profile, recipient.profile);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(recipientId, address, contact, profileKey, expiringProfileKeyCredential, profile);
+ }
+
+ public static final class Builder {
+
+ private RecipientId recipientId;
+ private RecipientAddress address;
+ private Contact contact;
+ private ProfileKey profileKey;
+ private ExpiringProfileKeyCredential expiringProfileKeyCredential1;
+ private Profile profile;
+
+ private Builder() {
+ }
+
+ public Builder withRecipientId(final RecipientId val) {
+ recipientId = val;
+ return this;
+ }
+
+ public Builder withAddress(final RecipientAddress val) {
+ address = val;
+ return this;
+ }
+
+ public Builder withContact(final Contact val) {
+ contact = val;
+ return this;
+ }
+
+ public Builder withProfileKey(final ProfileKey val) {
+ profileKey = val;
+ return this;
+ }
+
+ public Builder withExpiringProfileKeyCredential(final ExpiringProfileKeyCredential val) {
+ expiringProfileKeyCredential1 = val;
+ return this;
+ }
+
+ public Builder withProfile(final Profile val) {
+ profile = val;
+ return this;
+ }
+
+ public Recipient build() {
+ return new Recipient(this);
+ }
+ }
+}
--- /dev/null
+package org.asamk.signal.manager.api;
+
+import org.whispersystems.signalservice.api.push.ServiceId;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public record RecipientAddress(Optional<UUID> uuid, Optional<String> number) {
+
+ public static final UUID UNKNOWN_UUID = ServiceId.UNKNOWN.uuid();
+
+ /**
+ * Construct a RecipientAddress.
+ *
+ * @param uuid The UUID of the user, if available.
+ * @param number The phone number of the user, if available.
+ */
+ public RecipientAddress {
+ uuid = uuid.isPresent() && uuid.get().equals(UNKNOWN_UUID) ? Optional.empty() : uuid;
+ if (uuid.isEmpty() && number.isEmpty()) {
+ throw new AssertionError("Must have either a UUID or E164 number!");
+ }
+ }
+
+ public RecipientAddress(UUID uuid, String e164) {
+ this(Optional.ofNullable(uuid), Optional.ofNullable(e164));
+ }
+
+ public RecipientAddress(SignalServiceAddress address) {
+ this(Optional.of(address.getServiceId().uuid()), address.getNumber());
+ }
+
+ public RecipientAddress(UUID uuid) {
+ this(Optional.of(uuid), Optional.empty());
+ }
+
+ public ServiceId getServiceId() {
+ return ServiceId.from(uuid.orElse(UNKNOWN_UUID));
+ }
+
+ public String getIdentifier() {
+ if (uuid.isPresent()) {
+ return uuid.get().toString();
+ } else if (number.isPresent()) {
+ return number.get();
+ } else {
+ throw new AssertionError("Given the checks in the constructor, this should not be possible.");
+ }
+ }
+
+ public String getLegacyIdentifier() {
+ if (number.isPresent()) {
+ return number.get();
+ } else if (uuid.isPresent()) {
+ return uuid.get().toString();
+ } else {
+ throw new AssertionError("Given the checks in the constructor, this should not be possible.");
+ }
+ }
+
+ public boolean matches(RecipientAddress other) {
+ return (uuid.isPresent() && other.uuid.isPresent() && uuid.get().equals(other.uuid.get())) || (
+ number.isPresent() && other.number.isPresent() && number.get().equals(other.number.get())
+ );
+ }
+
+ public SignalServiceAddress toSignalServiceAddress() {
+ return new SignalServiceAddress(getServiceId(), number);
+ }
+}
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.helper.RecipientAddressResolver;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.IdentityKey;
RecipientAddressResolver addressResolver
) {
return new SendMessageResult(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
- sendMessageResult.getAddress())),
+ sendMessageResult.getAddress())).toApiRecipientAddress(),
sendMessageResult.isSuccess(),
sendMessageResult.isNetworkFailure(),
sendMessageResult.isUnregisteredFailure(),
package org.asamk.signal.manager.api;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
-
public class UnregisteredRecipientException extends Exception {
private final RecipientAddress sender;
package org.asamk.signal.manager.api;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
-
public class UntrustedIdentityException extends Exception {
private final RecipientAddress sender;
account.getAciIdentityKeyPair().getPublicKey(),
address.getServiceId().equals(serviceId)
? address
- : new RecipientAddress(serviceId.uuid(), address.number().orElse(null)),
+ : new RecipientAddress(serviceId, address.number().orElse(null)),
theirIdentityKey);
}
} catch (ProtocolUntrustedIdentityException e) {
final var recipientId = account.getRecipientResolver().resolveRecipient(e.getSender());
final var exception = new UntrustedIdentityException(account.getRecipientAddressResolver()
- .resolveRecipientAddress(recipientId), e.getSenderDevice());
+ .resolveRecipientAddress(recipientId)
+ .toApiRecipientAddress(), e.getSenderDevice());
return new Pair<>(List.of(), exception);
} catch (Exception e) {
return new Pair<>(List.of(), e);
final var recipientId = account.getRecipientResolver().resolveRecipient(e.getSender());
actions.add(new RetrieveProfileAction(recipientId));
exception = new UntrustedIdentityException(account.getRecipientAddressResolver()
- .resolveRecipientAddress(recipientId), e.getSenderDevice());
+ .resolveRecipientAddress(recipientId)
+ .toApiRecipientAddress(), e.getSenderDevice());
} catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException |
ProtocolInvalidMessageException e) {
logger.debug("Failed to decrypt incoming message", e);
if (exception instanceof UntrustedIdentityException) {
logger.debug("Keeping message with untrusted identity in message cache");
final var address = ((UntrustedIdentityException) exception).getSender();
- final var recipientId = account.getRecipientResolver().resolveRecipient(address);
+ final var recipientId = account.getRecipientResolver().resolveRecipient(address.getServiceId());
if (!envelope.hasSourceUuid()) {
try {
cachedMessage[0] = account.getMessageCache().replaceSender(cachedMessage[0], recipientId);
}
if (!envelope.hasSourceUuid()) {
final var identifier = ((UntrustedIdentityException) exception).getSender();
- final var recipientId = account.getRecipientResolver().resolveRecipient(identifier);
+ final var recipientId = account.getRecipientResolver().resolveRecipient(identifier.getServiceId());
try {
account.getMessageCache().replaceSender(cachedMessage, recipientId);
} catch (IOException ioException) {
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.storage.SignalAccount;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.slf4j.Logger;
public SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId);
- if (address.number().isEmpty() || address.uuid().isPresent()) {
+ if (address.number().isEmpty() || address.serviceId().isPresent()) {
return address.toSignalServiceAddress();
}
try {
aciMap = getRegisteredUsers(Set.of(number));
} catch (NumberFormatException e) {
- throw new UnregisteredRecipientException(new RecipientAddress(null, number));
+ throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, number));
}
final var uuid = aciMap.get(number);
if (uuid == null) {
- throw new UnregisteredRecipientException(new RecipientAddress(null, number));
+ throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, number));
}
return uuid;
}
}
final var contactRecord = record.getContact().get();
- final var address = new RecipientAddress(contactRecord.getServiceId().uuid(),
- contactRecord.getNumber().orElse(null));
+ final var address = new RecipientAddress(contactRecord.getServiceId(), contactRecord.getNumber().orElse(null));
final var recipientId = account.getRecipientResolver().resolveRecipient(address);
final var contact = account.getContactStore().getContact(recipientId);
getRecipientStore().deleteRecipientData(recipientId);
getMessageCache().deleteMessages(recipientId);
final var recipientAddress = getRecipientStore().resolveRecipientAddress(recipientId);
- if (recipientAddress.uuid().isPresent()) {
- final var serviceId = ServiceId.from(recipientAddress.uuid().get());
+ if (recipientAddress.serviceId().isPresent()) {
+ final var serviceId = recipientAddress.serviceId().get();
getAciSessionStore().deleteAllSessions(serviceId);
getPniSessionStore().deleteAllSessions(serviceId);
getIdentityKeyStore().deleteIdentity(serviceId);
if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
logger.debug("Migrating legacy identity session store.");
for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
- if (identity.getAddress().uuid().isEmpty()) {
+ if (identity.getAddress().serviceId().isEmpty()) {
continue;
}
final var serviceId = identity.getAddress().getServiceId();
}
public RecipientAddress getSelfRecipientAddress() {
- return new RecipientAddress(aci == null ? null : aci.uuid(), number);
+ return new RecipientAddress(aci, pni, number);
}
public RecipientId getSelfRecipientId() {
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.util.UuidUtil;
public static RecipientAddress getRecipientAddressFromIdentifier(final String identifier) {
if (UuidUtil.isUuid(identifier)) {
- return new RecipientAddress(UuidUtil.parseOrThrow(identifier));
+ return new RecipientAddress(ServiceId.parseOrThrow(identifier));
} else {
return new RecipientAddress(Optional.empty(), Optional.of(identifier));
}
import com.fasterxml.jackson.annotation.JsonProperty;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.UUID;
@JsonIgnore
public RecipientAddress getAddress() {
- return new RecipientAddress(uuid, number);
+ return new RecipientAddress(uuid == null ? null : ServiceId.from(uuid), number);
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.DistributionId;
-import org.whispersystems.signalservice.api.util.UuidUtil;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.internal.util.Hex;
import java.io.File;
if (g instanceof Storage.GroupV1 g1) {
final var members = g1.members.stream().map(m -> {
if (m.recipientId == null) {
- return recipientResolver.resolveRecipient(new RecipientAddress(UuidUtil.parseOrNull(m.uuid),
+ return recipientResolver.resolveRecipient(new RecipientAddress(ServiceId.parseOrNull(m.uuid),
m.number));
}
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
-import org.whispersystems.signalservice.api.util.UuidUtil;
+import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.IOException;
import java.util.ArrayList;
if (node.isArray()) {
for (var entry : node) {
var name = entry.hasNonNull("name") ? entry.get("name").asText() : null;
- var uuid = entry.hasNonNull("uuid") ? UuidUtil.parseOrNull(entry.get("uuid").asText()) : null;
- final var address = new RecipientAddress(uuid, name);
+ var serviceId = entry.hasNonNull("uuid") ? ServiceId.parseOrNull(entry.get("uuid").asText()) : null;
+ final var address = new RecipientAddress(serviceId, name);
ProfileKey profileKey = null;
try {
profileKey = new ProfileKey(Base64.getDecoder().decode(entry.get("profileKey").asText()));
import org.signal.libsignal.protocol.InvalidKeyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.IOException;
: null;
final var address = uuid == null
? Utils.getRecipientAddressFromIdentifier(trustedKeyName)
- : new RecipientAddress(uuid, trustedKeyName);
+ : new RecipientAddress(ServiceId.from(uuid), trustedKeyName);
try {
var id = new IdentityKey(Base64.getDecoder().decode(trustedKey.get("identityKey").asText()), 0);
var trustLevel = trustedKey.hasNonNull("trustLevel") ? TrustLevel.fromInt(trustedKey.get(
import org.asamk.signal.manager.storage.Utils;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.IOException;
var uuid = session.hasNonNull("uuid") ? UuidUtil.parseOrNull(session.get("uuid").asText()) : null;
final var address = uuid == null
? Utils.getRecipientAddressFromIdentifier(sessionName)
- : new RecipientAddress(uuid, sessionName);
+ : new RecipientAddress(ServiceId.from(uuid), sessionName);
final var deviceId = session.get("deviceId").asInt();
final var record = Base64.getDecoder().decode(session.get("record").asText());
var sessionInfo = new LegacySessionInfo(address, deviceId, record);
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import org.whispersystems.signalservice.api.util.UuidUtil;
+import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.IOException;
import java.util.ArrayList;
if (node.isArray()) {
for (var recipient : node) {
var recipientName = recipient.get("name").asText();
- var uuid = UuidUtil.parseOrThrow(recipient.get("uuid").asText());
- addresses.add(new RecipientAddress(uuid, recipientName));
+ var serviceId = ServiceId.parseOrThrow(recipient.get("uuid").asText());
+ addresses.add(new RecipientAddress(serviceId, recipientName));
}
}
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.util.UuidUtil;
+import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.File;
import java.io.FileInputStream;
final var recipients = storage.recipients.stream().map(r -> {
final var recipientId = new RecipientId(r.id, recipientStore);
- final var address = new RecipientAddress(Optional.ofNullable(r.uuid).map(UuidUtil::parseOrThrow),
+ final var address = new RecipientAddress(Optional.ofNullable(r.uuid).map(ServiceId::parseOrThrow),
Optional.ofNullable(r.number));
Contact contact = null;
package org.asamk.signal.manager.storage.recipients;
+import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Optional;
-import java.util.UUID;
-public record RecipientAddress(Optional<UUID> uuid, Optional<String> number) {
-
- public static final UUID UNKNOWN_UUID = ServiceId.UNKNOWN.uuid();
+public record RecipientAddress(Optional<ServiceId> serviceId, Optional<PNI> pni, Optional<String> number) {
/**
* Construct a RecipientAddress.
*
- * @param uuid The UUID of the user, if available.
- * @param number The phone number of the user, if available.
+ * @param serviceId The ACI or PNI of the user, if available.
+ * @param number The phone number of the user, if available.
*/
public RecipientAddress {
- uuid = uuid.isPresent() && uuid.get().equals(UNKNOWN_UUID) ? Optional.empty() : uuid;
- if (uuid.isEmpty() && number.isEmpty()) {
- throw new AssertionError("Must have either a UUID or E164 number!");
+ if (serviceId.isPresent() && serviceId.get().equals(ServiceId.UNKNOWN)) {
+ serviceId = Optional.empty();
+ }
+ if (pni.isPresent() && pni.get().equals(ServiceId.UNKNOWN)) {
+ pni = Optional.empty();
+ }
+ if (serviceId.isEmpty() && pni.isPresent()) {
+ serviceId = Optional.of(pni.get());
}
+ if (serviceId.isEmpty() && number.isEmpty()) {
+ throw new AssertionError("Must have either a ServiceId or E164 number!");
+ }
+ }
+
+ public RecipientAddress(Optional<ServiceId> serviceId, Optional<String> number) {
+ this(serviceId, Optional.empty(), number);
+ }
+
+ public RecipientAddress(ServiceId serviceId, String e164) {
+ this(Optional.ofNullable(serviceId), Optional.empty(), Optional.ofNullable(e164));
}
- public RecipientAddress(UUID uuid, String e164) {
- this(Optional.ofNullable(uuid), Optional.ofNullable(e164));
+ public RecipientAddress(ServiceId serviceId, PNI pni, String e164) {
+ this(Optional.ofNullable(serviceId), Optional.ofNullable(pni), Optional.ofNullable(e164));
}
public RecipientAddress(SignalServiceAddress address) {
- this(Optional.of(address.getServiceId().uuid()), address.getNumber());
+ this(Optional.of(address.getServiceId()), Optional.empty(), address.getNumber());
}
- public RecipientAddress(UUID uuid) {
- this(Optional.of(uuid), Optional.empty());
+ public RecipientAddress(ServiceId serviceId) {
+ this(Optional.of(serviceId), Optional.empty());
}
public ServiceId getServiceId() {
- return ServiceId.from(uuid.orElse(UNKNOWN_UUID));
+ return serviceId.orElse(ServiceId.UNKNOWN);
}
public String getIdentifier() {
- if (uuid.isPresent()) {
- return uuid.get().toString();
+ if (serviceId.isPresent()) {
+ return serviceId.get().toString();
} else if (number.isPresent()) {
return number.get();
} else {
public String getLegacyIdentifier() {
if (number.isPresent()) {
return number.get();
- } else if (uuid.isPresent()) {
- return uuid.get().toString();
+ } else if (serviceId.isPresent()) {
+ return serviceId.get().toString();
} else {
throw new AssertionError("Given the checks in the constructor, this should not be possible.");
}
}
public boolean matches(RecipientAddress other) {
- return (uuid.isPresent() && other.uuid.isPresent() && uuid.get().equals(other.uuid.get())) || (
+ return (serviceId.isPresent() && other.serviceId.isPresent() && serviceId.get().equals(other.serviceId.get()))
+ || (
+ pni.isPresent() && other.serviceId.isPresent() && pni.get().equals(other.serviceId.get())
+ )
+ || (
+ serviceId.isPresent() && other.pni.isPresent() && serviceId.get().equals(other.pni.get())
+ )
+ || (
+ pni.isPresent() && other.pni.isPresent() && pni.get().equals(other.pni.get())
+ )
+ || (
number.isPresent() && other.number.isPresent() && number.get().equals(other.number.get())
);
}
public SignalServiceAddress toSignalServiceAddress() {
return new SignalServiceAddress(getServiceId(), number);
}
+
+ public org.asamk.signal.manager.api.RecipientAddress toApiRecipientAddress() {
+ return new org.asamk.signal.manager.api.RecipientAddress(serviceId().map(ServiceId::uuid), number());
+ }
}
}
default RecipientId resolveRecipient(ServiceId serviceId) {
- return resolveRecipient(new RecipientAddress(serviceId.uuid()));
+ return resolveRecipient(new RecipientAddress(serviceId));
}
}
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
} catch (SQLException e) {
throw new RuntimeException("Failed read from recipient store", e);
}
- if (byNumber.isEmpty() || byNumber.get().address().uuid().isEmpty()) {
+ if (byNumber.isEmpty() || byNumber.get().address().serviceId().isEmpty()) {
final var aci = aciSupplier.get();
if (aci == null) {
- throw new UnregisteredRecipientException(new RecipientAddress(null, number));
+ throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, number));
}
- return resolveRecipient(new RecipientAddress(aci.uuid(), number), false, false);
+ return resolveRecipient(new RecipientAddress(aci, number), false, false);
}
return byNumber.get().id();
}
for (final var recipient : recipients.values()) {
statement.setLong(1, recipient.getRecipientId().id());
statement.setString(2, recipient.getAddress().number().orElse(null));
- statement.setBytes(3, recipient.getAddress().uuid().map(UuidUtil::toByteArray).orElse(null));
+ statement.setBytes(3,
+ recipient.getAddress()
+ .serviceId()
+ .map(ServiceId::uuid)
+ .map(UuidUtil::toByteArray)
+ .orElse(null));
statement.executeUpdate();
}
}
final var byNumber = address.number().isEmpty()
? Optional.<RecipientWithAddress>empty()
: findByNumber(connection, address.number().get());
- final var byUuid = address.uuid().isEmpty()
+ final var byUuid = address.serviceId().isEmpty()
? Optional.<RecipientWithAddress>empty()
- : findByUuid(connection, address.uuid().get());
+ : findByServiceId(connection, address.serviceId().get());
if (byNumber.isEmpty() && byUuid.isEmpty()) {
logger.debug("Got new recipient, both uuid and number are unknown");
- if (isHighTrust || address.uuid().isEmpty() || address.number().isEmpty()) {
+ if (isHighTrust || address.serviceId().isEmpty() || address.number().isEmpty()) {
return new Pair<>(addNewRecipient(connection, address), Optional.empty());
}
- return new Pair<>(addNewRecipient(connection, new RecipientAddress(address.uuid().get())),
+ return new Pair<>(addNewRecipient(connection, new RecipientAddress(address.serviceId().get())),
Optional.empty());
}
- if (!isHighTrust || address.uuid().isEmpty() || address.number().isEmpty() || byNumber.equals(byUuid)) {
+ if (!isHighTrust || address.serviceId().isEmpty() || address.number().isEmpty() || byNumber.equals(byUuid)) {
return new Pair<>(byUuid.or(() -> byNumber).map(RecipientWithAddress::id).get(), Optional.empty());
}
final var byNumberRecipient = byNumber.get();
if (byUuid.isEmpty()) {
- if (byNumberRecipient.address().uuid().isPresent()) {
+ if (byNumberRecipient.address().serviceId().isPresent()) {
logger.debug(
"Got recipient {} existing with number, but different uuid, so stripping its number and adding new recipient",
byNumberRecipient.id());
updateRecipientAddress(connection,
byNumberRecipient.id(),
- new RecipientAddress(byNumberRecipient.address().uuid().get()));
+ new RecipientAddress(byNumberRecipient.address().serviceId().get()));
return new Pair<>(addNewRecipient(connection, address), Optional.empty());
}
final var byUuidRecipient = byUuid.get();
- if (byNumberRecipient.address().uuid().isPresent()) {
+ if (byNumberRecipient.address().serviceId().isPresent()) {
logger.debug(
"Got separate recipients for high trust number {} and uuid {}, recipient for number has different uuid, so stripping its number",
byNumberRecipient.id(),
updateRecipientAddress(connection,
byNumberRecipient.id(),
- new RecipientAddress(byNumberRecipient.address().uuid().get()));
+ new RecipientAddress(byNumberRecipient.address().serviceId().get()));
updateRecipientAddress(connection, byUuidRecipient.id(), address);
return new Pair<>(byUuidRecipient.id(), Optional.empty());
}
).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
statement.setString(1, address.number().orElse(null));
- statement.setBytes(2, address.uuid().map(UuidUtil::toByteArray).orElse(null));
+ statement.setBytes(2, address.serviceId().map(ServiceId::uuid).map(UuidUtil::toByteArray).orElse(null));
statement.executeUpdate();
final var generatedKeys = statement.getGeneratedKeys();
if (generatedKeys.next()) {
).formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
statement.setString(1, address.number().orElse(null));
- statement.setBytes(2, address.uuid().map(UuidUtil::toByteArray).orElse(null));
+ statement.setBytes(2, address.serviceId().map(ServiceId::uuid).map(UuidUtil::toByteArray).orElse(null));
statement.setLong(3, recipientId.id());
statement.executeUpdate();
}
}
}
- private Optional<RecipientWithAddress> findByUuid(
- final Connection connection, final UUID uuid
+ private Optional<RecipientWithAddress> findByServiceId(
+ final Connection connection, final ServiceId serviceId
) throws SQLException {
final var sql = """
SELECT r._id, r.number, r.uuid
WHERE r.uuid = ?
""".formatted(TABLE_RECIPIENT);
try (final var statement = connection.prepareStatement(sql)) {
- statement.setBytes(1, UuidUtil.toByteArray(uuid));
+ statement.setBytes(1, UuidUtil.toByteArray(serviceId.uuid()));
return Utils.executeQueryForOptional(statement, this::getRecipientWithAddressFromResultSet);
}
}
}
private RecipientAddress getRecipientAddressFromResultSet(ResultSet resultSet) throws SQLException {
- final var uuid = Optional.ofNullable(resultSet.getBytes("uuid")).map(UuidUtil::parseOrNull);
+ final var serviceId = Optional.ofNullable(resultSet.getBytes("uuid")).map(ServiceId::parseOrNull);
final var number = Optional.ofNullable(resultSet.getString("number"));
- return new RecipientAddress(uuid, number);
+ return new RecipientAddress(serviceId, Optional.empty(), number);
}
private RecipientId getRecipientIdFromResultSet(ResultSet resultSet) throws SQLException {
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.File;
import java.io.FileInputStream;
final var senderKeys = parseFileNames(files, resolver).stream().map(key -> {
final var record = loadSenderKeyLocked(key, senderKeysPath);
- final var uuid = addressResolver.resolveRecipientAddress(key.recipientId).uuid();
- if (record == null || uuid.isEmpty()) {
+ final var serviceId = addressResolver.resolveRecipientAddress(key.recipientId).serviceId();
+ if (record == null || serviceId.isEmpty()) {
return null;
}
- return new Pair<>(new SenderKeyRecordStore.Key(ServiceId.from(uuid.get()),
- key.deviceId,
- key.distributionId), record);
+ return new Pair<>(new SenderKeyRecordStore.Key(serviceId.get(), key.deviceId, key.distributionId), record);
}).filter(Objects::nonNull).toList();
senderKeyStore.addLegacySenderKeys(senderKeys);
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.DistributionId;
-import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.File;
import java.io.FileInputStream;
if (recipientId == null) {
continue;
}
- final var uuid = addressResolver.resolveRecipientAddress(recipientId).uuid();
- if (uuid.isEmpty()) {
+ final var serviceId = addressResolver.resolveRecipientAddress(recipientId).serviceId();
+ if (serviceId.isEmpty()) {
continue;
}
- final var entry = new SenderKeySharedEntry(ServiceId.from(uuid.get()), senderKey.deviceId);
+ final var entry = new SenderKeySharedEntry(serviceId.get(), senderKey.deviceId);
final var distributionId = DistributionId.from(senderKey.distributionId);
var entries = sharedSenderKeys.get(distributionId);
if (entries == null) {
import org.signal.libsignal.protocol.state.SessionRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.File;
import java.io.FileInputStream;
final var keys = getKeysLocked(sessionsPath, resolver);
final var sessions = keys.stream().map(key -> {
final var record = loadSessionLocked(key, sessionsPath);
- final var uuid = addressResolver.resolveRecipientAddress(key.recipientId).uuid();
- if (record == null || uuid.isEmpty()) {
+ final var serviceId = addressResolver.resolveRecipientAddress(key.recipientId).serviceId();
+ if (record == null || serviceId.isEmpty()) {
return null;
}
- return new Pair<>(new SessionStore.Key(ServiceId.from(uuid.get()), key.deviceId()), record);
+ return new Pair<>(new SessionStore.Key(serviceId.get(), key.deviceId()), record);
}).filter(Objects::nonNull).toList();
sessionStore.addLegacySessions(sessions);
deleteAllSessions(sessionsPath);
version = 1;
ownId = ownAddress.number().get().getBytes();
theirId = theirAddress.number().get().getBytes();
- } else if (isUuidCapable && theirAddress.uuid().isPresent()) {
+ } else if (isUuidCapable && theirAddress.serviceId().isPresent()) {
// Version 2: UUID user
version = 2;
ownId = ownAddress.getServiceId().toByteArray();
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.RecipientAddress;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.UntrustedIdentityException;
import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.output.PlainTextWriter;
import org.asamk.signal.util.DateUtils;
import org.asamk.signal.util.Hex;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.Group;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+import org.asamk.signal.manager.api.RecipientAddress;
import org.asamk.signal.output.JsonWriter;
import org.asamk.signal.output.OutputWriter;
import org.asamk.signal.output.PlainTextWriter;
import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.ReceiveConfig;
+import org.asamk.signal.manager.api.Recipient;
+import org.asamk.signal.manager.api.RecipientAddress;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile;
-import org.asamk.signal.manager.storage.recipients.Recipient;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.freedesktop.dbus.DBusMap;
import org.freedesktop.dbus.DBusPath;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.asamk.Signal;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.RecipientAddress;
import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.types.Variant;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.PendingAdminApprovalException;
+import org.asamk.signal.manager.api.RecipientAddress;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendMessageResult;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.util.SendMessageResultUtils;
import org.freedesktop.dbus.DBusPath;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.RecipientAddress;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.UntrustedIdentityException;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import java.util.UUID;
package org.asamk.signal.json;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
+import org.asamk.signal.manager.api.RecipientAddress;
import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.asamk.signal.manager.api.MessageEnvelope;
+import org.asamk.signal.manager.api.RecipientAddress;
import org.asamk.signal.manager.groups.GroupId;
-import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import java.util.List;