import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.push.ACI;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
this.notifyAll();
}
});
- disposable.add(account.getIdentityKeyStore().getIdentityChanges().subscribe(recipientId -> {
- logger.trace("Archiving old sessions for {}", recipientId);
- account.getAciSessionStore().archiveSessions(recipientId);
- account.getPniSessionStore().archiveSessions(recipientId);
- account.getSenderKeyStore().deleteSharedWith(recipientId);
+ disposable.add(account.getIdentityKeyStore().getIdentityChanges().subscribe(serviceId -> {
+ logger.trace("Archiving old sessions for {}", serviceId);
+ account.getAciSessionStore().archiveSessions(serviceId);
+ account.getPniSessionStore().archiveSessions(serviceId);
+ account.getSenderKeyStore().deleteSharedWith(serviceId);
+ final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId);
final var profile = account.getProfileStore().getProfile(recipientId);
if (profile != null) {
account.getProfileStore()
if (recipient instanceof RecipientIdentifier.Single r) {
try {
final var recipientId = context.getRecipientHelper().resolveRecipient(r);
- account.getMessageSendLogStore().deleteEntryForRecipientNonGroup(targetSentTimestamp, recipientId);
+ account.getMessageSendLogStore()
+ .deleteEntryForRecipientNonGroup(targetSentTimestamp,
+ account.getRecipientAddressResolver()
+ .resolveRecipientAddress(recipientId)
+ .getServiceId());
} catch (UnregisteredRecipientException ignored) {
}
} else if (recipient instanceof RecipientIdentifier.Group r) {
} catch (UnregisteredRecipientException e) {
continue;
}
- account.getAciSessionStore().deleteAllSessions(recipientId);
+ final var serviceId = context.getAccount()
+ .getRecipientAddressResolver()
+ .resolveRecipientAddress(recipientId)
+ .getServiceId();
+ account.getAciSessionStore().deleteAllSessions(serviceId);
}
}
}
}
final var address = account.getRecipientAddressResolver()
- .resolveRecipientAddress(identityInfo.getRecipientId());
+ .resolveRecipientAddress(account.getRecipientResolver().resolveRecipient(identityInfo.getServiceId()));
final var scannableFingerprint = context.getIdentityHelper()
- .computeSafetyNumberForScanning(identityInfo.getRecipientId(), identityInfo.getIdentityKey());
+ .computeSafetyNumberForScanning(identityInfo.getServiceId(), identityInfo.getIdentityKey());
return new Identity(address,
identityInfo.getIdentityKey(),
context.getIdentityHelper()
- .computeSafetyNumber(identityInfo.getRecipientId(), identityInfo.getIdentityKey()),
+ .computeSafetyNumber(identityInfo.getServiceId(), identityInfo.getIdentityKey()),
scannableFingerprint == null ? null : scannableFingerprint.getSerialized(),
identityInfo.getTrustLevel(),
identityInfo.getDateAddedTimestamp());
@Override
public List<Identity> getIdentities(RecipientIdentifier.Single recipient) {
- IdentityInfo identity;
+ ServiceId serviceId;
try {
- identity = account.getIdentityKeyStore()
- .getIdentityInfo(context.getRecipientHelper().resolveRecipient(recipient));
+ serviceId = account.getRecipientAddressResolver()
+ .resolveRecipientAddress(context.getRecipientHelper().resolveRecipient(recipient))
+ .getServiceId();
} catch (UnregisteredRecipientException e) {
- identity = null;
+ return List.of();
}
+ final var identity = account.getIdentityKeyStore().getIdentityInfo(serviceId);
return identity == null ? List.of() : List.of(toIdentity(identity));
}
import org.asamk.signal.manager.helper.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;
+import org.whispersystems.signalservice.api.push.ServiceId;
public class RenewSessionAction implements HandleAction {
private final RecipientId recipientId;
+ private final ServiceId serviceId;
- public RenewSessionAction(final RecipientId recipientId) {
+ public RenewSessionAction(final RecipientId recipientId, final ServiceId serviceId) {
this.recipientId = recipientId;
+ this.serviceId = serviceId;
}
@Override
public void execute(Context context) throws Throwable {
- context.getAccount().getAciSessionStore().archiveSessions(recipientId);
+ context.getAccount().getAciSessionStore().archiveSessions(serviceId);
if (!recipientId.equals(context.getAccount().getSelfRecipientId())) {
context.getSendHelper().sendNullMessage(recipientId);
}
import org.signal.libsignal.protocol.message.CiphertextMessage;
import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import java.util.Optional;
public class SendRetryMessageRequestAction implements HandleAction {
private final RecipientId recipientId;
+ private final ServiceId serviceId;
private final ProtocolException protocolException;
private final SignalServiceEnvelope envelope;
public SendRetryMessageRequestAction(
final RecipientId recipientId,
+ final ServiceId serviceId,
final ProtocolException protocolException,
final SignalServiceEnvelope envelope
) {
this.recipientId = recipientId;
+ this.serviceId = serviceId;
this.protocolException = protocolException;
this.envelope = envelope;
}
@Override
public void execute(Context context) throws Throwable {
- context.getAccount().getAciSessionStore().archiveSessions(recipientId);
+ context.getAccount().getAciSessionStore().archiveSessions(serviceId);
int senderDevice = protocolException.getSenderDevice();
Optional<GroupId> groupId = protocolException.getGroupId().isPresent() ? Optional.of(GroupId.unknownVersion(
import org.asamk.signal.manager.api.TrustLevel;
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.asamk.signal.manager.util.Utils;
import org.signal.libsignal.protocol.IdentityKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
-import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.IOException;
import java.util.Arrays;
}
public boolean trustIdentityVerified(RecipientId recipientId, byte[] fingerprint) {
- return trustIdentity(recipientId,
+ final var serviceId = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId).getServiceId();
+ return trustIdentity(serviceId,
identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
TrustLevel.TRUSTED_VERIFIED);
}
public boolean trustIdentityVerifiedSafetyNumber(RecipientId recipientId, String safetyNumber) {
- return trustIdentity(recipientId,
- identityKey -> safetyNumber.equals(computeSafetyNumber(recipientId, identityKey)),
+ final var serviceId = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId).getServiceId();
+ return trustIdentity(serviceId,
+ identityKey -> safetyNumber.equals(computeSafetyNumber(serviceId, identityKey)),
TrustLevel.TRUSTED_VERIFIED);
}
public boolean trustIdentityVerifiedSafetyNumber(RecipientId recipientId, byte[] safetyNumber) {
- return trustIdentity(recipientId, identityKey -> {
- final var fingerprint = computeSafetyNumberForScanning(recipientId, identityKey);
+ final var serviceId = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId).getServiceId();
+ return trustIdentity(serviceId, identityKey -> {
+ final var fingerprint = computeSafetyNumberForScanning(serviceId, identityKey);
try {
return fingerprint != null && fingerprint.compareTo(safetyNumber);
} catch (FingerprintVersionMismatchException | FingerprintParsingException e) {
}
public boolean trustIdentityAllKeys(RecipientId recipientId) {
- return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
+ final var serviceId = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId).getServiceId();
+ return trustIdentity(serviceId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
}
- public String computeSafetyNumber(RecipientId recipientId, IdentityKey theirIdentityKey) {
- var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
- final Fingerprint fingerprint = computeSafetyNumberFingerprint(address, theirIdentityKey);
+ public String computeSafetyNumber(ServiceId serviceId, IdentityKey theirIdentityKey) {
+ final Fingerprint fingerprint = computeSafetyNumberFingerprint(serviceId, theirIdentityKey);
return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText();
}
- public ScannableFingerprint computeSafetyNumberForScanning(RecipientId recipientId, IdentityKey theirIdentityKey) {
- var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
- final Fingerprint fingerprint = computeSafetyNumberFingerprint(address, theirIdentityKey);
+ public ScannableFingerprint computeSafetyNumberForScanning(ServiceId serviceId, IdentityKey theirIdentityKey) {
+ final Fingerprint fingerprint = computeSafetyNumberFingerprint(serviceId, theirIdentityKey);
return fingerprint == null ? null : fingerprint.getScannableFingerprint();
}
private Fingerprint computeSafetyNumberFingerprint(
- final SignalServiceAddress theirAddress, final IdentityKey theirIdentityKey
+ final ServiceId serviceId, final IdentityKey theirIdentityKey
) {
+ final var address = account.getRecipientAddressResolver()
+ .resolveRecipientAddress(account.getRecipientResolver().resolveRecipient(serviceId));
+
return Utils.computeSafetyNumber(capabilities.isUuid(),
- account.getSelfAddress(),
+ account.getSelfRecipientAddress(),
account.getAciIdentityKeyPair().getPublicKey(),
- theirAddress,
+ address.getServiceId().equals(serviceId)
+ ? address
+ : new RecipientAddress(serviceId.uuid(), address.number().orElse(null)),
theirIdentityKey);
}
private boolean trustIdentity(
- RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
+ ServiceId serviceId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
) {
- var identity = account.getIdentityKeyStore().getIdentityInfo(recipientId);
+ var identity = account.getIdentityKeyStore().getIdentityInfo(serviceId);
if (identity == null) {
return false;
}
return false;
}
- account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
+ account.getIdentityKeyStore().setIdentityTrustLevel(serviceId, identity.getIdentityKey(), trustLevel);
try {
- var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
+ final var address = account.getRecipientAddressResolver()
+ .resolveRecipientAddress(account.getRecipientResolver().resolveRecipient(serviceId))
+ .toSignalServiceAddress();
context.getSyncHelper().sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
} catch (IOException e) {
logger.warn("Failed to send verification sync message: {}", e.getMessage());
}
public void handleIdentityFailure(
- final RecipientId recipientId, final SendMessageResult.IdentityFailure identityFailure
+ final RecipientId recipientId,
+ final ServiceId serviceId,
+ final SendMessageResult.IdentityFailure identityFailure
) {
final var identityKey = identityFailure.getIdentityKey();
if (identityKey != null) {
- account.getIdentityKeyStore().saveIdentity(recipientId, identityKey);
+ account.getIdentityKeyStore().saveIdentity(serviceId, identityKey);
} else {
// Retrieve profile to get the current identity key from the server
context.getProfileHelper().refreshRecipientProfile(recipientId);
import org.signal.libsignal.metadata.ProtocolNoSessionException;
import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException;
import org.signal.libsignal.metadata.SelfSendException;
-import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.ArrayList;
} else {
final var senderProfile = context.getProfileHelper().getRecipientProfile(sender);
final var selfProfile = context.getProfileHelper().getSelfProfile();
+ final var serviceId = ServiceId.parseOrThrow(e.getSender());
if ((!sender.equals(account.getSelfRecipientId()) || e.getSenderDevice() != account.getDeviceId())
&& senderProfile != null
&& senderProfile.getCapabilities().contains(Profile.Capability.senderKey)
&& selfProfile != null
&& selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) {
logger.debug("Received invalid message, requesting message resend.");
- actions.add(new SendRetryMessageRequestAction(sender, e, envelope));
+ actions.add(new SendRetryMessageRequestAction(sender, serviceId, e, envelope));
} else {
logger.debug("Received invalid message, queuing renew session action.");
- actions.add(new RenewSessionAction(sender));
+ actions.add(new RenewSessionAction(sender, serviceId));
}
}
exception = e;
account.getRecipientTrustedResolver().resolveRecipientTrusted(content.getSender());
}
if (envelope.isReceipt()) {
- final var senderPair = getSender(envelope, content);
- final var sender = senderPair.first();
- final var senderDeviceId = senderPair.second();
+ final var senderDeviceAddress = getSender(envelope, content);
+ final var sender = senderDeviceAddress.serviceId();
+ final var senderDeviceId = senderDeviceAddress.deviceId();
account.getMessageSendLogStore().deleteEntryForRecipient(envelope.getTimestamp(), sender, senderDeviceId);
}
SignalServiceEnvelope envelope, SignalServiceContent content, ReceiveConfig receiveConfig
) {
var actions = new ArrayList<HandleAction>();
- final var senderPair = getSender(envelope, content);
- final var sender = senderPair.first();
- final var senderDeviceId = senderPair.second();
+ final var senderDeviceAddress = getSender(envelope, content);
+ final var sender = senderDeviceAddress.recipientId();
+ final var senderServiceId = senderDeviceAddress.serviceId();
+ final var senderDeviceId = senderDeviceAddress.deviceId();
final var destination = getDestination(envelope);
if (content.getReceiptMessage().isPresent()) {
final var message = content.getReceiptMessage().get();
if (message.isDeliveryReceipt()) {
account.getMessageSendLogStore()
- .deleteEntriesForRecipient(message.getTimestamps(), sender, senderDeviceId);
+ .deleteEntriesForRecipient(message.getTimestamps(), senderServiceId, senderDeviceId);
}
}
if (content.getSenderKeyDistributionMessage().isPresent()) {
final var message = content.getSenderKeyDistributionMessage().get();
- final var protocolAddress = new SignalProtocolAddress(context.getRecipientHelper()
- .resolveSignalServiceAddress(sender)
- .getIdentifier(), senderDeviceId);
+ final var protocolAddress = senderServiceId.toProtocolAddress(senderDeviceId);
logger.debug("Received a sender key distribution message for distributionId {} from {}",
message.getDistributionId(),
protocolAddress);
senderDeviceId,
message.getTimestamp());
if (message.getDeviceId() == account.getDeviceId()) {
- handleDecryptionErrorMessage(actions, sender, senderDeviceId, message);
+ handleDecryptionErrorMessage(actions, sender, senderServiceId, senderDeviceId, message);
} else {
logger.debug("Request is for another one of our devices");
}
actions.addAll(handleSignalServiceDataMessage(message,
false,
- sender,
+ senderDeviceAddress,
destination,
receiveConfig.ignoreAttachments()));
}
if (content.getSyncMessage().isPresent()) {
var syncMessage = content.getSyncMessage().get();
- actions.addAll(handleSyncMessage(syncMessage, sender, receiveConfig.ignoreAttachments()));
+ actions.addAll(handleSyncMessage(syncMessage, senderDeviceAddress, receiveConfig.ignoreAttachments()));
}
return actions;
private void handleDecryptionErrorMessage(
final List<HandleAction> actions,
final RecipientId sender,
+ final ServiceId senderServiceId,
final int senderDeviceId,
final DecryptionErrorMessage message
) {
final var logEntries = account.getMessageSendLogStore()
- .findMessages(sender, senderDeviceId, message.getTimestamp(), message.getRatchetKey().isEmpty());
+ .findMessages(senderServiceId,
+ senderDeviceId,
+ message.getTimestamp(),
+ message.getRatchetKey().isEmpty());
for (final var logEntry : logEntries) {
actions.add(new ResendMessageAction(sender, message.getTimestamp(), logEntry));
if (message.getRatchetKey().isPresent()) {
if (account.getAciSessionStore()
- .isCurrentRatchetKey(sender, senderDeviceId, message.getRatchetKey().get())) {
+ .isCurrentRatchetKey(senderServiceId, senderDeviceId, message.getRatchetKey().get())) {
if (logEntries.isEmpty()) {
logger.debug("Renewing the session with sender");
- actions.add(new RenewSessionAction(sender));
+ actions.add(new RenewSessionAction(sender, senderServiceId));
} else {
logger.trace("Archiving the session with sender, a resend message has already been queued");
- context.getAccount().getAciSessionStore().archiveSessions(sender);
+ context.getAccount().getAciSessionStore().archiveSessions(senderServiceId);
}
}
return;
sender,
senderDeviceId,
group.getDistributionId());
- account.getSenderKeyStore().deleteSharedWith(sender, senderDeviceId, group.getDistributionId());
+ account.getSenderKeyStore().deleteSharedWith(senderServiceId, senderDeviceId, group.getDistributionId());
}
if (!found) {
logger.debug("Reset all shared sender keys with this recipient, no related message found in send log");
- account.getSenderKeyStore().deleteSharedWith(sender);
+ account.getSenderKeyStore().deleteSharedWith(senderServiceId);
}
}
private List<HandleAction> handleSyncMessage(
- final SignalServiceSyncMessage syncMessage, final RecipientId sender, final boolean ignoreAttachments
+ final SignalServiceSyncMessage syncMessage, final DeviceAddress sender, final boolean ignoreAttachments
) {
var actions = new ArrayList<HandleAction>();
account.setMultiDevice(true);
actions.addAll(handleSignalServiceDataMessage(message.getDataMessage().get(),
true,
sender,
- destination == null ? null : context.getRecipientHelper().resolveRecipient(destination),
+ destination == null
+ ? null
+ : new DeviceAddress(context.getRecipientHelper().resolveRecipient(destination),
+ destination.getServiceId(),
+ 0),
ignoreAttachments));
}
if (message.getStoryMessage().isPresent()) {
actions.addAll(handleSignalServiceStoryMessage(message.getStoryMessage().get(),
- sender,
+ sender.recipientId(),
ignoreAttachments));
}
}
if (syncMessage.getVerified().isPresent()) {
final var verifiedMessage = syncMessage.getVerified().get();
account.getIdentityKeyStore()
- .setIdentityTrustLevel(account.getRecipientTrustedResolver()
- .resolveRecipientTrusted(verifiedMessage.getDestination()),
+ .setIdentityTrustLevel(verifiedMessage.getDestination().getServiceId(),
verifiedMessage.getIdentityKey(),
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
private List<HandleAction> handleSignalServiceDataMessage(
SignalServiceDataMessage message,
boolean isSync,
- RecipientId source,
- RecipientId destination,
+ DeviceAddress source,
+ DeviceAddress destination,
boolean ignoreAttachments
) {
var actions = new ArrayList<HandleAction>();
}
case DELIVER:
if (groupV1 == null && !isSync) {
- actions.add(new SendGroupInfoRequestAction(source, groupId));
+ actions.add(new SendGroupInfoRequestAction(source.recipientId(), groupId));
}
break;
case QUIT: {
if (groupV1 != null) {
- groupV1.removeMember(source);
+ groupV1.removeMember(source.recipientId());
account.getGroupStore().updateGroup(groupV1);
}
break;
}
case REQUEST_INFO:
if (groupV1 != null && !isSync) {
- actions.add(new SendGroupInfoAction(source, groupV1.getGroupId()));
+ actions.add(new SendGroupInfoAction(source.recipientId(), groupV1.getGroupId()));
}
break;
}
final var conversationPartnerAddress = isSync ? destination : source;
if (conversationPartnerAddress != null && message.isEndSession()) {
- account.getAciSessionStore().deleteAllSessions(conversationPartnerAddress);
+ account.getAciSessionStore().deleteAllSessions(conversationPartnerAddress.serviceId());
}
if (message.isExpirationUpdate() || message.getBody().isPresent()) {
if (message.getGroupContext().isPresent()) {
}
} else if (conversationPartnerAddress != null) {
context.getContactHelper()
- .setExpirationTimer(conversationPartnerAddress, message.getExpiresInSeconds());
+ .setExpirationTimer(conversationPartnerAddress.recipientId(), message.getExpiresInSeconds());
}
}
if (!ignoreAttachments) {
}
}
if (message.getProfileKey().isPresent()) {
- handleIncomingProfileKey(message.getProfileKey().get(), source);
+ handleIncomingProfileKey(message.getProfileKey().get(), source.recipientId());
}
if (message.getSticker().isPresent()) {
final var messageSticker = message.getSticker().get();
this.account.getProfileStore().storeProfileKey(source, profileKey);
}
- private Pair<RecipientId, Integer> getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
+ private DeviceAddress getSender(SignalServiceEnvelope envelope, SignalServiceContent content) {
if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
- return new Pair<>(context.getRecipientHelper().resolveRecipient(envelope.getSourceAddress()),
+ return new DeviceAddress(context.getRecipientHelper().resolveRecipient(envelope.getSourceAddress()),
+ envelope.getSourceAddress().getServiceId(),
envelope.getSourceDevice());
} else {
- return new Pair<>(context.getRecipientHelper().resolveRecipient(content.getSender()),
+ return new DeviceAddress(context.getRecipientHelper().resolveRecipient(content.getSender()),
+ content.getSender().getServiceId(),
content.getSenderDevice());
}
}
- private RecipientId getDestination(SignalServiceEnvelope envelope) {
+ private DeviceAddress getDestination(SignalServiceEnvelope envelope) {
if (!envelope.hasDestinationUuid()) {
- return account.getSelfRecipientId();
+ return new DeviceAddress(account.getSelfRecipientId(), account.getAci(), account.getDeviceId());
}
final var addressOptional = SignalServiceAddress.fromRaw(envelope.getDestinationUuid(), null);
if (addressOptional.isEmpty()) {
- return account.getSelfRecipientId();
+ return new DeviceAddress(account.getSelfRecipientId(), account.getAci(), account.getDeviceId());
}
- return context.getRecipientHelper().resolveRecipient(addressOptional.get());
+ final var address = addressOptional.get();
+ return new DeviceAddress(context.getRecipientHelper().resolveRecipient(address), address.getServiceId(), 0);
}
+
+ private record DeviceAddress(RecipientId recipientId, ServiceId serviceId, int deviceId) {}
}
try {
logger.trace("Storing identity");
final var identityKey = new IdentityKey(Base64.getDecoder().decode(encryptedProfile.getIdentityKey()));
- account.getIdentityKeyStore().saveIdentity(recipientId, identityKey);
+ account.getIdentityKeyStore().saveIdentity(p.getProfile().getServiceId(), identityKey);
} catch (InvalidKeyException ignored) {
logger.warn("Got invalid identity key in profile for {}",
context.getRecipientHelper().resolveSignalServiceAddress(recipientId).getIdentifier());
continue;
}
- final var identity = account.getIdentityKeyStore().getIdentityInfo(recipientId);
+ final var serviceId = account.getRecipientAddressResolver()
+ .resolveRecipientAddress(recipientId)
+ .getServiceId();
+ final var identity = account.getIdentityKeyStore().getIdentityInfo(serviceId);
if (identity == null || !identity.getTrustLevel().isTrusted()) {
continue;
}
final var recipientIdList = new ArrayList<>(recipientIds);
long keyCreateTime = account.getSenderKeyStore()
- .getCreateTimeForOurKey(account.getSelfRecipientId(), account.getDeviceId(), distributionId);
+ .getCreateTimeForOurKey(account.getAci(), account.getDeviceId(), distributionId);
long keyAge = System.currentTimeMillis() - keyCreateTime;
if (keyCreateTime != -1 && keyAge > TimeUnit.DAYS.toMillis(14)) {
keyCreateTime,
keyAge,
TimeUnit.MILLISECONDS.toDays(keyAge));
- account.getSenderKeyStore().deleteOurKey(account.getSelfRecipientId(), distributionId);
+ account.getSenderKeyStore().deleteOurKey(account.getAci(), distributionId);
}
List<SignalServiceAddress> addresses = recipientIdList.stream()
return null;
} catch (NoSessionException e) {
logger.warn("No session. Falling back to legacy sends.", e);
- account.getSenderKeyStore().deleteOurKey(account.getSelfRecipientId(), distributionId);
+ account.getSenderKeyStore().deleteOurKey(account.getAci(), distributionId);
return null;
} catch (InvalidKeyException e) {
logger.warn("Invalid key. Falling back to legacy sends.", e);
- account.getSenderKeyStore().deleteOurKey(account.getSelfRecipientId(), distributionId);
+ account.getSenderKeyStore().deleteOurKey(account.getAci(), distributionId);
return null;
} catch (InvalidRegistrationIdException e) {
logger.warn("Invalid registrationId. Falling back to legacy sends.", e);
}
if (r.getIdentityFailure() != null) {
final var recipientId = context.getRecipientHelper().resolveRecipient(r.getAddress());
- context.getIdentityHelper().handleIdentityFailure(recipientId, r.getIdentityFailure());
+ context.getIdentityHelper()
+ .handleIdentityFailure(recipientId, r.getAddress().getServiceId(), r.getIdentityFailure());
}
}
try {
logger.trace("Storing identity key {}", recipientId);
final var identityKey = new IdentityKey(contactRecord.getIdentityKey().get());
- account.getIdentityKeyStore().saveIdentity(recipientId, identityKey);
+ account.getIdentityKeyStore().saveIdentity(address.getServiceId(), identityKey);
final var trustLevel = TrustLevel.fromIdentityState(contactRecord.getIdentityState());
if (trustLevel != null) {
- account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identityKey, trustLevel);
+ account.getIdentityKeyStore()
+ .setIdentityTrustLevel(address.getServiceId(), identityKey, trustLevel);
}
} catch (InvalidKeyException e) {
logger.warn("Received invalid contact identity key from storage");
final var contact = contactPair.second();
final var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId);
- var currentIdentity = account.getIdentityKeyStore().getIdentityInfo(recipientId);
+ var currentIdentity = account.getIdentityKeyStore().getIdentityInfo(address.getServiceId());
VerifiedMessage verifiedMessage = null;
if (currentIdentity != null) {
verifiedMessage = new VerifiedMessage(address,
if (c.getVerified().isPresent()) {
final var verifiedMessage = c.getVerified().get();
account.getIdentityKeyStore()
- .setIdentityTrustLevel(account.getRecipientTrustedResolver()
- .resolveRecipientTrusted(verifiedMessage.getDestination()),
+ .setIdentityTrustLevel(verifiedMessage.getDestination().getServiceId(),
verifiedMessage.getIdentityKey(),
TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
}
public class AccountDatabase extends Database {
private final static Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
- private static final long DATABASE_VERSION = 9;
+ private static final long DATABASE_VERSION = 10;
private AccountDatabase(final HikariDataSource dataSource) {
super(logger, DATABASE_VERSION, dataSource);
""");
}
}
+ if (oldVersion < 10) {
+ logger.debug("Updating database: Key tables on serviceId instead of recipientId");
+ try (final var statement = connection.createStatement()) {
+ statement.executeUpdate("""
+ CREATE TABLE identity2 (
+ _id INTEGER PRIMARY KEY,
+ uuid BLOB UNIQUE NOT NULL,
+ identity_key BLOB NOT NULL,
+ added_timestamp INTEGER NOT NULL,
+ trust_level INTEGER NOT NULL
+ ) STRICT;
+ INSERT INTO identity2 (_id, uuid, identity_key, added_timestamp, trust_level)
+ SELECT i._id, r.uuid, i.identity_key, i.added_timestamp, i.trust_level
+ FROM identity i LEFT JOIN recipient r ON i.recipient_id = r._id
+ WHERE uuid IS NOT NULL;
+ DROP TABLE identity;
+ ALTER TABLE identity2 RENAME TO identity;
+
+ DROP INDEX msl_recipient_index;
+ ALTER TABLE message_send_log ADD COLUMN uuid BLOB;
+ UPDATE message_send_log
+ SET uuid = r.uuid
+ FROM message_send_log i, (SELECT _id, uuid FROM recipient) AS r
+ WHERE i.recipient_id = r._id;
+ DELETE FROM message_send_log WHERE uuid IS NULL;
+ ALTER TABLE message_send_log DROP COLUMN recipient_id;
+ CREATE INDEX msl_recipient_index ON message_send_log (uuid, device_id, content_id);
+
+ CREATE TABLE sender_key2 (
+ _id INTEGER PRIMARY KEY,
+ uuid BLOB NOT NULL,
+ device_id INTEGER NOT NULL,
+ distribution_id BLOB NOT NULL,
+ record BLOB NOT NULL,
+ created_timestamp INTEGER NOT NULL,
+ UNIQUE(uuid, device_id, distribution_id)
+ ) STRICT;
+ INSERT INTO sender_key2 (_id, uuid, device_id, distribution_id, record, created_timestamp)
+ SELECT s._id, r.uuid, s.device_id, s.distribution_id, s.record, s.created_timestamp
+ FROM sender_key s LEFT JOIN recipient r ON s.recipient_id = r._id
+ WHERE uuid IS NOT NULL;
+ DROP TABLE sender_key;
+ ALTER TABLE sender_key2 RENAME TO sender_key;
+
+ CREATE TABLE sender_key_shared2 (
+ _id INTEGER PRIMARY KEY,
+ uuid BLOB NOT NULL,
+ device_id INTEGER NOT NULL,
+ distribution_id BLOB NOT NULL,
+ timestamp INTEGER NOT NULL,
+ UNIQUE(uuid, device_id, distribution_id)
+ ) STRICT;
+ INSERT INTO sender_key_shared2 (_id, uuid, device_id, distribution_id, timestamp)
+ SELECT s._id, r.uuid, s.device_id, s.distribution_id, s.timestamp
+ FROM sender_key_shared s LEFT JOIN recipient r ON s.recipient_id = r._id
+ WHERE uuid IS NOT NULL;
+ DROP TABLE sender_key_shared;
+ ALTER TABLE sender_key_shared2 RENAME TO sender_key_shared;
+
+ CREATE TABLE session2 (
+ _id INTEGER PRIMARY KEY,
+ account_id_type INTEGER NOT NULL,
+ uuid BLOB NOT NULL,
+ device_id INTEGER NOT NULL,
+ record BLOB NOT NULL,
+ UNIQUE(account_id_type, uuid, device_id)
+ ) STRICT;
+ INSERT INTO session2 (_id, account_id_type, uuid, device_id, record)
+ SELECT s._id, s.account_id_type, r.uuid, s.device_id, s.record
+ FROM session s LEFT JOIN recipient r ON s.recipient_id = r._id
+ WHERE uuid IS NOT NULL;
+ DROP TABLE session;
+ ALTER TABLE session2 RENAME TO session;
+ """);
+ }
+ }
}
}
this.storageManifestVersion = -1;
this.setStorageManifest(null);
this.storageKey = null;
+ final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
+ getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
+ getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
+ if (getPniIdentityKeyPair() != null) {
+ final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
+ getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
+ getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
+ }
}
private void migrateLegacyConfigs() {
}
private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
- getAciSessionStore().mergeRecipients(recipientId, toBeMergedRecipientId);
- getPniSessionStore().mergeRecipients(recipientId, toBeMergedRecipientId);
- getIdentityKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId);
getGroupStore().mergeRecipients(recipientId, toBeMergedRecipientId);
- getSenderKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
}
public void removeRecipient(final RecipientId recipientId) {
- getAciSessionStore().deleteAllSessions(recipientId);
- getPniSessionStore().deleteAllSessions(recipientId);
- getIdentityKeyStore().deleteIdentity(recipientId);
- getMessageCache().deleteMessages(recipientId);
- getSenderKeyStore().deleteAll(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());
+ getAciSessionStore().deleteAllSessions(serviceId);
+ getPniSessionStore().deleteAllSessions(serviceId);
+ getIdentityKeyStore().deleteIdentity(serviceId);
+ getSenderKeyStore().deleteAll(serviceId);
+ }
}
public static File getFileName(File dataPath, String account) {
}
final var legacySessionsPath = getSessionsPath(dataPath, accountPath);
if (legacySessionsPath.exists()) {
- LegacySessionStore.migrate(legacySessionsPath, getRecipientResolver(), getAciSessionStore());
+ LegacySessionStore.migrate(legacySessionsPath,
+ getRecipientResolver(),
+ getRecipientAddressResolver(),
+ getAciSessionStore());
migratedLegacyConfig = true;
}
final var legacyIdentitiesPath = getIdentitiesPath(dataPath, accountPath);
if (legacyIdentitiesPath.exists()) {
- LegacyIdentityKeyStore.migrate(legacyIdentitiesPath, getRecipientResolver(), getIdentityKeyStore());
+ LegacyIdentityKeyStore.migrate(legacyIdentitiesPath,
+ getRecipientResolver(),
+ getRecipientAddressResolver(),
+ getIdentityKeyStore());
migratedLegacyConfig = true;
}
final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
final var legacySenderKeysPath = getSenderKeysPath(dataPath, accountPath);
if (legacySenderKeysPath.exists()) {
- LegacySenderKeyRecordStore.migrate(legacySenderKeysPath, getRecipientResolver(), getSenderKeyStore());
+ LegacySenderKeyRecordStore.migrate(legacySenderKeysPath,
+ getRecipientResolver(),
+ getRecipientAddressResolver(),
+ getSenderKeyStore());
migratedLegacyConfig = true;
}
final var legacySenderKeysSharedPath = getSharedSenderKeysFile(dataPath, accountPath);
if (legacySenderKeysSharedPath.exists()) {
- LegacySenderKeySharedStore.migrate(legacySenderKeysSharedPath, getRecipientResolver(), getSenderKeyStore());
+ LegacySenderKeySharedStore.migrate(legacySenderKeysSharedPath,
+ getRecipientResolver(),
+ getRecipientAddressResolver(),
+ getSenderKeyStore());
migratedLegacyConfig = true;
}
if (rootNode.hasNonNull("groupStore")) {
if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
logger.debug("Migrating legacy identity session store.");
for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
- RecipientId recipientId = getRecipientStore().resolveRecipientTrusted(identity.getAddress());
- getIdentityKeyStore().saveIdentity(recipientId, identity.getIdentityKey());
- getIdentityKeyStore().setIdentityTrustLevel(recipientId,
+ if (identity.getAddress().uuid().isEmpty()) {
+ continue;
+ }
+ final var serviceId = identity.getAddress().getServiceId();
+ getIdentityKeyStore().saveIdentity(serviceId, identity.getIdentityKey());
+ getIdentityKeyStore().setIdentityTrustLevel(serviceId,
identity.getIdentityKey(),
identity.getTrustLevel());
}
public SessionStore getAciSessionStore() {
return getOrCreate(() -> aciSessionStore,
- () -> aciSessionStore = new SessionStore(getAccountDatabase(),
- ServiceIdType.ACI,
- getRecipientResolver(),
- getRecipientIdCreator()));
+ () -> aciSessionStore = new SessionStore(getAccountDatabase(), ServiceIdType.ACI));
}
public SessionStore getPniSessionStore() {
return getOrCreate(() -> pniSessionStore,
- () -> pniSessionStore = new SessionStore(getAccountDatabase(),
- ServiceIdType.PNI,
- getRecipientResolver(),
- getRecipientIdCreator()));
+ () -> pniSessionStore = new SessionStore(getAccountDatabase(), ServiceIdType.PNI));
}
public IdentityKeyStore getIdentityKeyStore() {
return getOrCreate(() -> identityKeyStore,
- () -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(),
- getRecipientIdCreator(),
- trustNewIdentity));
+ () -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(), trustNewIdentity));
}
public SignalIdentityKeyStore getAciIdentityKeyStore() {
}
public SenderKeyStore getSenderKeyStore() {
- return getOrCreate(() -> senderKeyStore,
- () -> senderKeyStore = new SenderKeyStore(getAccountDatabase(),
- getRecipientAddressResolver(),
- getRecipientResolver(),
- getRecipientIdCreator()));
+ return getOrCreate(() -> senderKeyStore, () -> senderKeyStore = new SenderKeyStore(getAccountDatabase()));
}
public ConfigurationStore getConfigurationStore() {
public MessageSendLogStore getMessageSendLogStore() {
return getOrCreate(() -> messageSendLogStore,
- () -> messageSendLogStore = new MessageSendLogStore(getRecipientResolver(), getAccountDatabase()));
+ () -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase()));
}
public CredentialsProvider getCredentialsProvider() {
public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
pniIdentityKeyPair = identityKeyPair;
+ final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
+ getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
+ getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
save();
}
getAciSessionStore().archiveAllSessions();
getPniSessionStore().archiveAllSessions();
getSenderKeyStore().deleteAll();
- final var recipientId = getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
- final var publicKey = getAciIdentityKeyPair().getPublicKey();
- getIdentityKeyStore().saveIdentity(recipientId, publicKey);
- getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
+ getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
+ final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
+ getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
+ getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
+ final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
+ getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
+ getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
}
public void deleteAccountData() throws IOException {
package org.asamk.signal.manager.storage.identities;
import org.asamk.signal.manager.api.TrustLevel;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.signal.libsignal.protocol.IdentityKey;
+import org.whispersystems.signalservice.api.push.ServiceId;
public class IdentityInfo {
- private final RecipientId recipientId;
+ private final ServiceId serviceId;
private final IdentityKey identityKey;
private final TrustLevel trustLevel;
private final long addedTimestamp;
IdentityInfo(
- final RecipientId recipientId, IdentityKey identityKey, TrustLevel trustLevel, long addedTimestamp
+ final ServiceId serviceId, IdentityKey identityKey, TrustLevel trustLevel, long addedTimestamp
) {
- this.recipientId = recipientId;
+ this.serviceId = serviceId;
this.identityKey = identityKey;
this.trustLevel = trustLevel;
this.addedTimestamp = addedTimestamp;
}
- public RecipientId getRecipientId() {
- return recipientId;
+ public ServiceId getServiceId() {
+ return serviceId;
}
public IdentityKey getIdentityKey() {
import org.asamk.signal.manager.api.TrustLevel;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
-import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.state.IdentityKeyStore.Direction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.push.ServiceId;
import java.sql.Connection;
import java.sql.ResultSet;
private final static Logger logger = LoggerFactory.getLogger(IdentityKeyStore.class);
private static final String TABLE_IDENTITY = "identity";
private final Database database;
- private final RecipientIdCreator recipientIdCreator;
private final TrustNewIdentity trustNewIdentity;
- private final PublishSubject<RecipientId> identityChanges = PublishSubject.create();
+ private final PublishSubject<ServiceId> identityChanges = PublishSubject.create();
private boolean isRetryingDecryption = false;
statement.executeUpdate("""
CREATE TABLE identity (
_id INTEGER PRIMARY KEY,
- recipient_id INTEGER UNIQUE NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
+ uuid BLOB UNIQUE NOT NULL,
identity_key BLOB NOT NULL,
added_timestamp INTEGER NOT NULL,
trust_level INTEGER NOT NULL
- );
+ ) STRICT;
""");
}
}
- public IdentityKeyStore(
- final Database database,
- final RecipientIdCreator recipientIdCreator,
- final TrustNewIdentity trustNewIdentity
- ) {
+ public IdentityKeyStore(final Database database, final TrustNewIdentity trustNewIdentity) {
this.database = database;
- this.recipientIdCreator = recipientIdCreator;
this.trustNewIdentity = trustNewIdentity;
}
- public Observable<RecipientId> getIdentityChanges() {
+ public Observable<ServiceId> getIdentityChanges() {
return identityChanges;
}
- public boolean saveIdentity(final RecipientId recipientId, final IdentityKey identityKey) {
+ public boolean saveIdentity(final ServiceId serviceId, final IdentityKey identityKey) {
if (isRetryingDecryption) {
return false;
}
try (final var connection = database.getConnection()) {
- final var identityInfo = loadIdentity(connection, recipientId);
+ final var identityInfo = loadIdentity(connection, serviceId);
if (identityInfo != null && identityInfo.getIdentityKey().equals(identityKey)) {
// Identity already exists, not updating the trust level
- logger.trace("Not storing new identity for recipient {}, identity already stored", recipientId);
+ logger.trace("Not storing new identity for recipient {}, identity already stored", serviceId);
return false;
}
- saveNewIdentity(connection, recipientId, identityKey, identityInfo == null);
+ saveNewIdentity(connection, serviceId, identityKey, identityInfo == null);
return true;
} catch (SQLException e) {
throw new RuntimeException("Failed update identity store", e);
isRetryingDecryption = retryingDecryption;
}
- public boolean setIdentityTrustLevel(RecipientId recipientId, IdentityKey identityKey, TrustLevel trustLevel) {
+ public boolean setIdentityTrustLevel(ServiceId serviceId, IdentityKey identityKey, TrustLevel trustLevel) {
try (final var connection = database.getConnection()) {
- final var identityInfo = loadIdentity(connection, recipientId);
+ final var identityInfo = loadIdentity(connection, serviceId);
if (identityInfo == null) {
- logger.debug("Not updating trust level for recipient {}, identity not found", recipientId);
+ logger.debug("Not updating trust level for recipient {}, identity not found", serviceId);
return false;
}
if (!identityInfo.getIdentityKey().equals(identityKey)) {
- logger.debug("Not updating trust level for recipient {}, different identity found", recipientId);
+ logger.debug("Not updating trust level for recipient {}, different identity found", serviceId);
return false;
}
if (identityInfo.getTrustLevel() == trustLevel) {
- logger.trace("Not updating trust level for recipient {}, trust level already matches", recipientId);
+ logger.trace("Not updating trust level for recipient {}, trust level already matches", serviceId);
return false;
}
- logger.debug("Updating trust level for recipient {} with trust {}", recipientId, trustLevel);
- final var newIdentityInfo = new IdentityInfo(recipientId,
+ logger.debug("Updating trust level for recipient {} with trust {}", serviceId, trustLevel);
+ final var newIdentityInfo = new IdentityInfo(serviceId,
identityKey,
trustLevel,
identityInfo.getDateAddedTimestamp());
}
}
- public boolean isTrustedIdentity(RecipientId recipientId, IdentityKey identityKey, Direction direction) {
+ public boolean isTrustedIdentity(ServiceId serviceId, IdentityKey identityKey, Direction direction) {
if (trustNewIdentity == TrustNewIdentity.ALWAYS) {
return true;
}
try (final var connection = database.getConnection()) {
// TODO implement possibility for different handling of incoming/outgoing trust decisions
- var identityInfo = loadIdentity(connection, recipientId);
+ var identityInfo = loadIdentity(connection, serviceId);
if (identityInfo == null) {
- logger.debug("Initial identity found for {}, saving.", recipientId);
- saveNewIdentity(connection, recipientId, identityKey, true);
- identityInfo = loadIdentity(connection, recipientId);
+ logger.debug("Initial identity found for {}, saving.", serviceId);
+ saveNewIdentity(connection, serviceId, identityKey, true);
+ identityInfo = loadIdentity(connection, serviceId);
} else if (!identityInfo.getIdentityKey().equals(identityKey)) {
// Identity found, but different
if (direction == Direction.SENDING) {
- logger.debug("Changed identity found for {}, saving.", recipientId);
- saveNewIdentity(connection, recipientId, identityKey, false);
- identityInfo = loadIdentity(connection, recipientId);
+ logger.debug("Changed identity found for {}, saving.", serviceId);
+ saveNewIdentity(connection, serviceId, identityKey, false);
+ identityInfo = loadIdentity(connection, serviceId);
} else {
- logger.trace("Trusting identity for {} for {}: {}", recipientId, direction, false);
+ logger.trace("Trusting identity for {} for {}: {}", serviceId, direction, false);
return false;
}
}
final var isTrusted = identityInfo != null && identityInfo.isTrusted();
- logger.trace("Trusting identity for {} for {}: {}", recipientId, direction, isTrusted);
+ logger.trace("Trusting identity for {} for {}: {}", serviceId, direction, isTrusted);
return isTrusted;
} catch (SQLException e) {
throw new RuntimeException("Failed read from identity store", e);
}
}
- public IdentityInfo getIdentityInfo(RecipientId recipientId) {
+ public IdentityInfo getIdentityInfo(ServiceId serviceId) {
try (final var connection = database.getConnection()) {
- return loadIdentity(connection, recipientId);
+ return loadIdentity(connection, serviceId);
} catch (SQLException e) {
throw new RuntimeException("Failed read from identity store", e);
}
try (final var connection = database.getConnection()) {
final var sql = (
"""
- SELECT i.recipient_id, i.identity_key, i.added_timestamp, i.trust_level
+ SELECT i.uuid, i.identity_key, i.added_timestamp, i.trust_level
FROM %s AS i
"""
).formatted(TABLE_IDENTITY);
}
}
- public void mergeRecipients(final RecipientId recipientId, final RecipientId toBeMergedRecipientId) {
+ public void deleteIdentity(final ServiceId serviceId) {
try (final var connection = database.getConnection()) {
- connection.setAutoCommit(false);
- final var sql = (
- """
- UPDATE OR IGNORE %s
- SET recipient_id = ?
- WHERE recipient_id = ?
- """
- ).formatted(TABLE_IDENTITY);
- try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
- statement.setLong(2, toBeMergedRecipientId.id());
- statement.executeUpdate();
- }
-
- deleteIdentity(connection, toBeMergedRecipientId);
- connection.commit();
- } catch (SQLException e) {
- throw new RuntimeException("Failed update identity store", e);
- }
- }
-
- public void deleteIdentity(final RecipientId recipientId) {
- try (final var connection = database.getConnection()) {
- deleteIdentity(connection, recipientId);
+ deleteIdentity(connection, serviceId);
} catch (SQLException e) {
throw new RuntimeException("Failed update identity store", e);
}
}
private IdentityInfo loadIdentity(
- final Connection connection, final RecipientId recipientId
+ final Connection connection, final ServiceId serviceId
) throws SQLException {
final var sql = (
"""
- SELECT i.recipient_id, i.identity_key, i.added_timestamp, i.trust_level
+ SELECT i.uuid, i.identity_key, i.added_timestamp, i.trust_level
FROM %s AS i
- WHERE i.recipient_id = ?
+ WHERE i.uuid = ?
"""
).formatted(TABLE_IDENTITY);
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
+ statement.setBytes(1, serviceId.toByteArray());
return Utils.executeQueryForOptional(statement, this::getIdentityInfoFromResultSet).orElse(null);
}
}
private void saveNewIdentity(
final Connection connection,
- final RecipientId recipientId,
+ final ServiceId serviceId,
final IdentityKey identityKey,
final boolean firstIdentity
) throws SQLException {
final var trustLevel = trustNewIdentity == TrustNewIdentity.ALWAYS || (
trustNewIdentity == TrustNewIdentity.ON_FIRST_USE && firstIdentity
) ? TrustLevel.TRUSTED_UNVERIFIED : TrustLevel.UNTRUSTED;
- logger.debug("Storing new identity for recipient {} with trust {}", recipientId, trustLevel);
- final var newIdentityInfo = new IdentityInfo(recipientId, identityKey, trustLevel, System.currentTimeMillis());
+ logger.debug("Storing new identity for recipient {} with trust {}", serviceId, trustLevel);
+ final var newIdentityInfo = new IdentityInfo(serviceId, identityKey, trustLevel, System.currentTimeMillis());
storeIdentity(connection, newIdentityInfo);
- identityChanges.onNext(recipientId);
+ identityChanges.onNext(serviceId);
}
private void storeIdentity(final Connection connection, final IdentityInfo identityInfo) throws SQLException {
logger.trace("Storing identity info for {}, trust: {}, added: {}",
- identityInfo.getRecipientId(),
+ identityInfo.getServiceId(),
identityInfo.getTrustLevel(),
identityInfo.getDateAddedTimestamp());
final var sql = (
"""
- INSERT OR REPLACE INTO %s (recipient_id, identity_key, added_timestamp, trust_level)
+ INSERT OR REPLACE INTO %s (uuid, identity_key, added_timestamp, trust_level)
VALUES (?, ?, ?, ?)
"""
).formatted(TABLE_IDENTITY);
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, identityInfo.getRecipientId().id());
+ statement.setBytes(1, identityInfo.getServiceId().toByteArray());
statement.setBytes(2, identityInfo.getIdentityKey().serialize());
statement.setLong(3, identityInfo.getDateAddedTimestamp());
statement.setInt(4, identityInfo.getTrustLevel().ordinal());
}
}
- private void deleteIdentity(final Connection connection, final RecipientId recipientId) throws SQLException {
+ private void deleteIdentity(final Connection connection, final ServiceId serviceId) throws SQLException {
final var sql = (
"""
DELETE FROM %s AS i
- WHERE i.recipient_id = ?
+ WHERE i.uuid = ?
"""
).formatted(TABLE_IDENTITY);
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
+ statement.setBytes(1, serviceId.toByteArray());
statement.executeUpdate();
}
}
private IdentityInfo getIdentityInfoFromResultSet(ResultSet resultSet) throws SQLException {
try {
- final var recipientId = recipientIdCreator.create(resultSet.getLong("recipient_id"));
+ final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
final var id = new IdentityKey(resultSet.getBytes("identity_key"));
final var trustLevel = TrustLevel.fromInt(resultSet.getInt("trust_level"));
final var added = resultSet.getLong("added_timestamp");
- return new IdentityInfo(recipientId, id, trustLevel, added);
+ return new IdentityInfo(serviceId, id, trustLevel, added);
} catch (InvalidKeyException e) {
logger.warn("Failed to load identity key, resetting: {}", e.getMessage());
return null;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.asamk.signal.manager.api.TrustLevel;
+import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.asamk.signal.manager.util.IOUtils;
private static final ObjectMapper objectMapper = org.asamk.signal.manager.storage.Utils.createStorageObjectMapper();
public static void migrate(
- final File identitiesPath, final RecipientResolver resolver, final IdentityKeyStore identityKeyStore
+ final File identitiesPath,
+ final RecipientResolver resolver,
+ final RecipientAddressResolver addressResolver,
+ final IdentityKeyStore identityKeyStore
) {
- final var identities = getIdentities(identitiesPath, resolver);
+ final var identities = getIdentities(identitiesPath, resolver, addressResolver);
identityKeyStore.addLegacyIdentities(identities);
removeIdentityFiles(identitiesPath);
}
static final Pattern identityFileNamePattern = Pattern.compile("(\\d+)");
- private static List<IdentityInfo> getIdentities(final File identitiesPath, final RecipientResolver resolver) {
+ private static List<IdentityInfo> getIdentities(
+ final File identitiesPath, final RecipientResolver resolver, final RecipientAddressResolver addressResolver
+ ) {
final var files = identitiesPath.listFiles();
if (files == null) {
return List.of();
.filter(f -> identityFileNamePattern.matcher(f.getName()).matches())
.map(f -> resolver.resolveRecipient(Long.parseLong(f.getName())))
.filter(Objects::nonNull)
- .map(recipientId -> loadIdentityLocked(recipientId, identitiesPath))
+ .map(recipientId -> loadIdentityLocked(recipientId, addressResolver, identitiesPath))
.filter(Objects::nonNull)
.toList();
}
return new File(identitiesPath, String.valueOf(recipientId.id()));
}
- private static IdentityInfo loadIdentityLocked(final RecipientId recipientId, final File identitiesPath) {
+ private static IdentityInfo loadIdentityLocked(
+ final RecipientId recipientId, RecipientAddressResolver addressResolver, final File identitiesPath
+ ) {
final var file = getIdentityFile(recipientId, identitiesPath);
if (!file.exists()) {
return null;
var trustLevel = TrustLevel.fromInt(storage.trustLevel());
var added = storage.addedTimestamp();
- return new IdentityInfo(recipientId, id, trustLevel, added);
+ final var serviceId = addressResolver.resolveRecipientAddress(recipientId).getServiceId();
+ return new IdentityInfo(serviceId, id, trustLevel, added);
} catch (IOException | InvalidKeyException e) {
logger.warn("Failed to load identity key: {}", e.getMessage());
return null;
package org.asamk.signal.manager.storage.identities;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.SignalProtocolAddress;
+import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.function.Supplier;
public class SignalIdentityKeyStore implements org.signal.libsignal.protocol.state.IdentityKeyStore {
- private final RecipientResolver resolver;
private final Supplier<IdentityKeyPair> identityKeyPairSupplier;
private final int localRegistrationId;
private final IdentityKeyStore identityKeyStore;
final int localRegistrationId,
final IdentityKeyStore identityKeyStore
) {
- this.resolver = resolver;
this.identityKeyPairSupplier = identityKeyPairSupplier;
this.localRegistrationId = localRegistrationId;
this.identityKeyStore = identityKeyStore;
@Override
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
- final var recipientId = resolveRecipient(address.getName());
+ final var serviceId = ServiceId.parseOrThrow(address.getName());
- return identityKeyStore.saveIdentity(recipientId, identityKey);
+ return identityKeyStore.saveIdentity(serviceId, identityKey);
}
@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
- var recipientId = resolveRecipient(address.getName());
+ final var serviceId = ServiceId.parseOrThrow(address.getName());
- return identityKeyStore.isTrustedIdentity(recipientId, identityKey, direction);
+ return identityKeyStore.isTrustedIdentity(serviceId, identityKey, direction);
}
@Override
public IdentityKey getIdentity(SignalProtocolAddress address) {
- var recipientId = resolveRecipient(address.getName());
- final var identityInfo = identityKeyStore.getIdentityInfo(recipientId);
+ final var serviceId = ServiceId.parseOrThrow(address.getName());
+ final var identityInfo = identityKeyStore.getIdentityInfo(serviceId);
return identityInfo == null ? null : identityInfo.getIdentityKey();
}
-
- /**
- * @param identifier can be either a serialized uuid or an e164 phone number
- */
- private RecipientId resolveRecipient(String identifier) {
- return resolver.resolveRecipient(identifier);
- }
}
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();
}
public SignalServiceAddress toSignalServiceAddress() {
- return new SignalServiceAddress(ServiceId.from(uuid.orElse(UNKNOWN_UUID)), number);
+ return new SignalServiceAddress(getServiceId(), number);
}
}
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
-import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import java.io.IOException;
private static final Duration LOG_DURATION = Duration.ofDays(1);
- private final RecipientResolver recipientResolver;
private final Database database;
private final Thread cleanupThread;
- public MessageSendLogStore(
- final RecipientResolver recipientResolver, final Database database
- ) {
- this.recipientResolver = recipientResolver;
+ public MessageSendLogStore(final Database database) {
this.database = database;
this.cleanupThread = new Thread(() -> {
try {
CREATE TABLE message_send_log (
_id INTEGER PRIMARY KEY,
content_id INTEGER NOT NULL REFERENCES message_send_log_content (_id) ON DELETE CASCADE,
- recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
+ uuid BLOB NOT NULL,
device_id INTEGER NOT NULL
- );
+ ) STRICT;
CREATE TABLE message_send_log_content (
_id INTEGER PRIMARY KEY,
group_id BLOB,
urgent BOOLEAN NOT NULL
);
CREATE INDEX mslc_timestamp_index ON message_send_log_content (timestamp);
- CREATE INDEX msl_recipient_index ON message_send_log (recipient_id, device_id, content_id);
+ CREATE INDEX msl_recipient_index ON message_send_log (uuid, device_id, content_id);
CREATE INDEX msl_content_index ON message_send_log (content_id);
""");
}
}
public List<MessageSendLogEntry> findMessages(
- final RecipientId recipientId, final int deviceId, final long timestamp, final boolean isSenderKey
+ final ServiceId serviceId, final int deviceId, final long timestamp, final boolean isSenderKey
) {
final var sql = """
SELECT group_id, content, content_hint
FROM %s l
INNER JOIN %s lc ON l.content_id = lc._id
- WHERE l.recipient_id = ? AND l.device_id = ? AND lc.timestamp = ?
+ WHERE l.uuid = ? AND l.device_id = ? AND lc.timestamp = ?
""".formatted(TABLE_MESSAGE_SEND_LOG, TABLE_MESSAGE_SEND_LOG_CONTENT);
try (final var connection = database.getConnection()) {
deleteOutdatedEntries(connection);
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
+ statement.setBytes(1, serviceId.toByteArray());
statement.setInt(2, deviceId);
statement.setLong(3, timestamp);
try (var result = Utils.executeQueryForStream(statement, this::getMessageSendLogEntryFromResultSet)) {
}
}
- public void deleteEntryForRecipientNonGroup(long sentTimestamp, RecipientId recipientId) {
+ public void deleteEntryForRecipientNonGroup(long sentTimestamp, ServiceId serviceId) {
final var sql = """
DELETE FROM %s AS lc
- WHERE lc.timestamp = ? AND lc.group_id IS NULL AND lc._id IN (SELECT content_id FROM %s l WHERE l.recipient_id = ?)
+ WHERE lc.timestamp = ? AND lc.group_id IS NULL AND lc._id IN (SELECT content_id FROM %s l WHERE l.uuid = ?)
""".formatted(TABLE_MESSAGE_SEND_LOG_CONTENT, TABLE_MESSAGE_SEND_LOG);
try (final var connection = database.getConnection()) {
connection.setAutoCommit(false);
try (final var statement = connection.prepareStatement(sql)) {
statement.setLong(1, sentTimestamp);
- statement.setLong(2, recipientId.id());
+ statement.setBytes(2, serviceId.toByteArray());
statement.executeUpdate();
}
}
}
- public void deleteEntryForRecipient(long sentTimestamp, RecipientId recipientId, int deviceId) {
- deleteEntriesForRecipient(List.of(sentTimestamp), recipientId, deviceId);
+ public void deleteEntryForRecipient(long sentTimestamp, ServiceId serviceId, int deviceId) {
+ deleteEntriesForRecipient(List.of(sentTimestamp), serviceId, deviceId);
}
- public void deleteEntriesForRecipient(List<Long> sentTimestamps, RecipientId recipientId, int deviceId) {
+ public void deleteEntriesForRecipient(List<Long> sentTimestamps, ServiceId serviceId, int deviceId) {
final var sql = """
DELETE FROM %s AS l
- WHERE l.content_id IN (SELECT _id FROM %s lc WHERE lc.timestamp = ?) AND l.recipient_id = ? AND l.device_id = ?
+ WHERE l.content_id IN (SELECT _id FROM %s lc WHERE lc.timestamp = ?) AND l.uuid = ? AND l.device_id = ?
""".formatted(TABLE_MESSAGE_SEND_LOG, TABLE_MESSAGE_SEND_LOG_CONTENT);
try (final var connection = database.getConnection()) {
connection.setAutoCommit(false);
try (final var statement = connection.prepareStatement(sql)) {
for (final var sentTimestamp : sentTimestamps) {
statement.setLong(1, sentTimestamp);
- statement.setLong(2, recipientId.id());
+ statement.setBytes(2, serviceId.toByteArray());
statement.setInt(3, deviceId);
statement.executeUpdate();
}
private RecipientDevices getRecipientDevices(final SendMessageResult sendMessageResult) {
if (sendMessageResult.isSuccess() && sendMessageResult.getSuccess().getContent().isPresent()) {
- final var recipientId = recipientResolver.resolveRecipient(sendMessageResult.getAddress());
- return new RecipientDevices(recipientId, sendMessageResult.getSuccess().getDevices());
+ final var serviceId = sendMessageResult.getAddress().getServiceId();
+ return new RecipientDevices(serviceId, sendMessageResult.getSuccess().getDevices());
} else {
return null;
}
final long contentId, final List<RecipientDevices> recipientDevices, final Connection connection
) throws SQLException {
final var sql = """
- INSERT INTO %s (recipient_id, device_id, content_id)
+ INSERT INTO %s (uuid, device_id, content_id)
VALUES (?,?,?)
""".formatted(TABLE_MESSAGE_SEND_LOG);
try (final var statement = connection.prepareStatement(sql)) {
for (final var recipientDevice : recipientDevices) {
for (final var deviceId : recipientDevice.deviceIds()) {
- statement.setLong(1, recipientDevice.recipientId().id());
+ statement.setBytes(1, recipientDevice.serviceId().toByteArray());
statement.setInt(2, deviceId);
statement.setLong(3, contentId);
statement.executeUpdate();
return new MessageSendLogEntry(groupId, content, contentHint, urgent);
}
- private record RecipientDevices(RecipientId recipientId, List<Integer> deviceIds) {}
+ private record RecipientDevices(ServiceId serviceId, List<Integer> deviceIds) {}
}
package org.asamk.signal.manager.storage.senderKeys;
import org.asamk.signal.manager.api.Pair;
+import org.asamk.signal.manager.helper.RecipientAddressResolver;
+import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.InvalidMessageException;
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;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static org.asamk.signal.manager.storage.senderKeys.SenderKeyRecordStore.Key;
-
public class LegacySenderKeyRecordStore {
private final static Logger logger = LoggerFactory.getLogger(LegacySenderKeyRecordStore.class);
public static void migrate(
- final File senderKeysPath, final RecipientResolver resolver, SenderKeyStore senderKeyStore
+ final File senderKeysPath,
+ final RecipientResolver resolver,
+ final RecipientAddressResolver addressResolver,
+ final SenderKeyStore senderKeyStore
) {
final var files = senderKeysPath.listFiles();
if (files == null) {
final var senderKeys = parseFileNames(files, resolver).stream().map(key -> {
final var record = loadSenderKeyLocked(key, senderKeysPath);
- if (record == null) {
+ final var uuid = addressResolver.resolveRecipientAddress(key.recipientId).uuid();
+ if (record == null || uuid.isEmpty()) {
return null;
}
- return new Pair<>(key, record);
+ return new Pair<>(new SenderKeyRecordStore.Key(ServiceId.from(uuid.get()),
+ key.deviceId,
+ key.distributionId), record);
}).filter(Objects::nonNull).toList();
senderKeyStore.addLegacySenderKeys(senderKeys);
return null;
}
}
+
+ record Key(RecipientId recipientId, int deviceId, UUID distributionId) {}
}
package org.asamk.signal.manager.storage.senderKeys;
+import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.Utils;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.asamk.signal.manager.storage.senderKeys.SenderKeySharedStore.SenderKeySharedEntry;
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;
private final static Logger logger = LoggerFactory.getLogger(LegacySenderKeySharedStore.class);
public static void migrate(
- final File file, final RecipientResolver resolver, SenderKeyStore senderKeyStore
+ final File file,
+ final RecipientResolver resolver,
+ final RecipientAddressResolver addressResolver,
+ final SenderKeyStore senderKeyStore
) {
final var objectMapper = Utils.createStorageObjectMapper();
try (var inputStream = new FileInputStream(file)) {
if (recipientId == null) {
continue;
}
- final var entry = new SenderKeySharedEntry(recipientId, senderKey.deviceId);
+ final var uuid = addressResolver.resolveRecipientAddress(recipientId).uuid();
+ if (uuid.isEmpty()) {
+ continue;
+ }
+ final var entry = new SenderKeySharedEntry(ServiceId.from(uuid.get()), senderKey.deviceId);
final var distributionId = DistributionId.from(senderKey.distributionId);
var entries = sharedSenderKeys.get(distributionId);
if (entries == null) {
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
-import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord;
import org.signal.libsignal.protocol.groups.state.SenderKeyStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.sql.Connection;
private final static String TABLE_SENDER_KEY = "sender_key";
private final Database database;
- private final RecipientResolver resolver;
public static void createSql(Connection connection) throws SQLException {
// When modifying the CREATE statement here, also add a migration in AccountDatabase.java
statement.executeUpdate("""
CREATE TABLE sender_key (
_id INTEGER PRIMARY KEY,
- recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
+ uuid BLOB NOT NULL,
device_id INTEGER NOT NULL,
distribution_id BLOB NOT NULL,
record BLOB NOT NULL,
created_timestamp INTEGER NOT NULL,
- UNIQUE(recipient_id, device_id, distribution_id)
- );
+ UNIQUE(uuid, device_id, distribution_id)
+ ) STRICT;
""");
}
}
- SenderKeyRecordStore(
- final Database database, final RecipientResolver resolver
- ) {
+ SenderKeyRecordStore(final Database database) {
this.database = database;
- this.resolver = resolver;
}
@Override
}
}
- long getCreateTimeForKey(final RecipientId selfRecipientId, final int selfDeviceId, final UUID distributionId) {
+ long getCreateTimeForKey(final ServiceId selfServiceId, final int selfDeviceId, final UUID distributionId) {
final var sql = (
"""
SELECT s.created_timestamp
FROM %s AS s
- WHERE s.recipient_id = ? AND s.device_id = ? AND s.distribution_id = ?
+ WHERE s.uuid = ? AND s.device_id = ? AND s.distribution_id = ?
"""
).formatted(TABLE_SENDER_KEY);
try (final var connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, selfRecipientId.id());
+ statement.setBytes(1, selfServiceId.toByteArray());
statement.setInt(2, selfDeviceId);
statement.setBytes(3, UuidUtil.toByteArray(distributionId));
return Utils.executeQueryForOptional(statement, res -> res.getLong("created_timestamp")).orElse(-1L);
}
}
- void deleteSenderKey(final RecipientId recipientId, final UUID distributionId) {
+ void deleteSenderKey(final ServiceId serviceId, final UUID distributionId) {
final var sql = (
"""
DELETE FROM %s AS s
- WHERE s.recipient_id = ? AND s.distribution_id = ?
+ WHERE s.uuid = ? AND s.distribution_id = ?
"""
).formatted(TABLE_SENDER_KEY);
try (final var connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
+ statement.setBytes(1, serviceId.toByteArray());
statement.setBytes(2, UuidUtil.toByteArray(distributionId));
statement.executeUpdate();
}
}
}
- void deleteAllFor(final RecipientId recipientId) {
+ void deleteAllFor(final ServiceId serviceId) {
try (final var connection = database.getConnection()) {
- deleteAllFor(connection, recipientId);
- } catch (SQLException e) {
- throw new RuntimeException("Failed update sender key store", e);
- }
- }
-
- void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
- try (final var connection = database.getConnection()) {
- connection.setAutoCommit(false);
- final var sql = """
- UPDATE OR IGNORE %s
- SET recipient_id = ?
- WHERE recipient_id = ?
- """.formatted(TABLE_SENDER_KEY);
- try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
- statement.setLong(2, toBeMergedRecipientId.id());
- final var rows = statement.executeUpdate();
- if (rows > 0) {
- logger.debug("Reassigned {} sender keys of to be merged recipient.", rows);
- }
- }
- // Delete all conflicting sender keys now
- deleteAllFor(connection, toBeMergedRecipientId);
- connection.commit();
+ deleteAllFor(connection, serviceId);
} catch (SQLException e) {
throw new RuntimeException("Failed update sender key store", e);
}
logger.debug("Complete sender keys migration took {}ms", (System.nanoTime() - start) / 1000000);
}
- /**
- * @param identifier can be either a serialized uuid or an e164 phone number
- */
- private RecipientId resolveRecipient(String identifier) {
- return resolver.resolveRecipient(identifier);
- }
-
private Key getKey(final SignalProtocolAddress address, final UUID distributionId) {
- final var recipientId = resolveRecipient(address.getName());
- return new Key(recipientId, address.getDeviceId(), distributionId);
+ final var serviceId = ServiceId.parseOrThrow(address.getName());
+ return new Key(serviceId, address.getDeviceId(), distributionId);
}
private SenderKeyRecord loadSenderKey(final Connection connection, final Key key) throws SQLException {
"""
SELECT s.record
FROM %s AS s
- WHERE s.recipient_id = ? AND s.device_id = ? AND s.distribution_id = ?
+ WHERE s.uuid = ? AND s.device_id = ? AND s.distribution_id = ?
"""
).formatted(TABLE_SENDER_KEY);
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, key.recipientId().id());
+ statement.setBytes(1, key.serviceId().toByteArray());
statement.setInt(2, key.deviceId());
statement.setBytes(3, UuidUtil.toByteArray(key.distributionId()));
return Utils.executeQueryForOptional(statement, this::getSenderKeyRecordFromResultSet).orElse(null);
final var sqlUpdate = """
UPDATE %s
SET record = ?
- WHERE recipient_id = ? AND device_id = ? and distribution_id = ?
+ WHERE uuid = ? AND device_id = ? and distribution_id = ?
""".formatted(TABLE_SENDER_KEY);
try (final var statement = connection.prepareStatement(sqlUpdate)) {
statement.setBytes(1, senderKeyRecord.serialize());
- statement.setLong(2, key.recipientId().id());
+ statement.setBytes(2, key.serviceId().toByteArray());
statement.setLong(3, key.deviceId());
statement.setBytes(4, UuidUtil.toByteArray(key.distributionId()));
final var rows = statement.executeUpdate();
// Record doesn't exist yet, creating a new one
final var sqlInsert = (
"""
- INSERT OR REPLACE INTO %s (recipient_id, device_id, distribution_id, record, created_timestamp)
+ INSERT OR REPLACE INTO %s (uuid, device_id, distribution_id, record, created_timestamp)
VALUES (?, ?, ?, ?, ?)
"""
).formatted(TABLE_SENDER_KEY);
try (final var statement = connection.prepareStatement(sqlInsert)) {
- statement.setLong(1, key.recipientId().id());
+ statement.setBytes(1, key.serviceId().toByteArray());
statement.setInt(2, key.deviceId());
statement.setBytes(3, UuidUtil.toByteArray(key.distributionId()));
statement.setBytes(4, senderKeyRecord.serialize());
}
}
- private void deleteAllFor(final Connection connection, final RecipientId recipientId) throws SQLException {
+ private void deleteAllFor(final Connection connection, final ServiceId serviceId) throws SQLException {
final var sql = (
"""
DELETE FROM %s AS s
- WHERE s.recipient_id = ?
+ WHERE s.uuid = ?
"""
).formatted(TABLE_SENDER_KEY);
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
+ statement.setBytes(1, serviceId.toByteArray());
statement.executeUpdate();
}
}
}
}
- record Key(RecipientId recipientId, int deviceId, UUID distributionId) {}
+ record Key(ServiceId serviceId, int deviceId, UUID distributionId) {}
}
package org.asamk.signal.manager.storage.senderKeys;
-import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
-import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
-import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.DistributionId;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.sql.Connection;
private final static String TABLE_SENDER_KEY_SHARED = "sender_key_shared";
private final Database database;
- private final RecipientIdCreator recipientIdCreator;
- private final RecipientResolver resolver;
- private final RecipientAddressResolver addressResolver;
public static void createSql(Connection connection) throws SQLException {
// When modifying the CREATE statement here, also add a migration in AccountDatabase.java
statement.executeUpdate("""
CREATE TABLE sender_key_shared (
_id INTEGER PRIMARY KEY,
- recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
+ uuid BLOB NOT NULL,
device_id INTEGER NOT NULL,
distribution_id BLOB NOT NULL,
timestamp INTEGER NOT NULL,
- UNIQUE(recipient_id, device_id, distribution_id)
- );
+ UNIQUE(uuid, device_id, distribution_id)
+ ) STRICT;
""");
}
}
- SenderKeySharedStore(
- final Database database,
- final RecipientIdCreator recipientIdCreator,
- final RecipientAddressResolver addressResolver,
- final RecipientResolver resolver
- ) {
+ SenderKeySharedStore(final Database database) {
this.database = database;
- this.recipientIdCreator = recipientIdCreator;
- this.addressResolver = addressResolver;
- this.resolver = resolver;
}
public Set<SignalProtocolAddress> getSenderKeySharedWith(final DistributionId distributionId) {
try (final var connection = database.getConnection()) {
final var sql = (
"""
- SELECT s.recipient_id, s.device_id
+ SELECT s.uuid, s.device_id
FROM %s AS s
WHERE s.distribution_id = ?
"""
try (final var statement = connection.prepareStatement(sql)) {
statement.setBytes(1, UuidUtil.toByteArray(distributionId.asUuid()));
return Utils.executeQueryForStream(statement, this::getSenderKeySharedEntryFromResultSet)
- .map(k -> new SignalProtocolAddress(addressResolver.resolveRecipientAddress(k.recipientId())
- .getIdentifier(), k.deviceId()))
+ .map(k -> k.serviceId.toProtocolAddress(k.deviceId()))
.collect(Collectors.toSet());
}
} catch (SQLException e) {
final DistributionId distributionId, final Collection<SignalProtocolAddress> addresses
) {
final var newEntries = addresses.stream()
- .map(a -> new SenderKeySharedEntry(resolver.resolveRecipient(a.getName()), a.getDeviceId()))
+ .map(a -> new SenderKeySharedEntry(ServiceId.parseOrThrow(a.getName()), a.getDeviceId()))
.collect(Collectors.toSet());
try (final var connection = database.getConnection()) {
public void clearSenderKeySharedWith(final Collection<SignalProtocolAddress> addresses) {
final var entriesToDelete = addresses.stream()
- .map(a -> new SenderKeySharedEntry(resolver.resolveRecipient(a.getName()), a.getDeviceId()))
+ .filter(a -> UuidUtil.isUuid(a.getName()))
+ .map(a -> new SenderKeySharedEntry(ServiceId.parseOrThrow(a.getName()), a.getDeviceId()))
.collect(Collectors.toSet());
try (final var connection = database.getConnection()) {
final var sql = (
"""
DELETE FROM %s AS s
- WHERE recipient_id = ? AND device_id = ?
+ WHERE uuid = ? AND device_id = ?
"""
).formatted(TABLE_SENDER_KEY_SHARED);
try (final var statement = connection.prepareStatement(sql)) {
for (final var entry : entriesToDelete) {
- statement.setLong(1, entry.recipientId().id());
+ statement.setBytes(1, entry.serviceId().toByteArray());
statement.setInt(2, entry.deviceId());
statement.executeUpdate();
}
}
}
- public void deleteAllFor(final RecipientId recipientId) {
+ public void deleteAllFor(final ServiceId serviceId) {
try (final var connection = database.getConnection()) {
final var sql = (
"""
DELETE FROM %s AS s
- WHERE recipient_id = ?
+ WHERE uuid = ?
"""
).formatted(TABLE_SENDER_KEY_SHARED);
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
+ statement.setBytes(1, serviceId.toByteArray());
statement.executeUpdate();
}
} catch (SQLException e) {
}
public void deleteSharedWith(
- final RecipientId recipientId, final int deviceId, final DistributionId distributionId
+ final ServiceId serviceId, final int deviceId, final DistributionId distributionId
) {
try (final var connection = database.getConnection()) {
final var sql = (
"""
DELETE FROM %s AS s
- WHERE recipient_id = ? AND device_id = ? AND distribution_id = ?
+ WHERE uuid = ? AND device_id = ? AND distribution_id = ?
"""
).formatted(TABLE_SENDER_KEY_SHARED);
try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
+ statement.setBytes(1, serviceId.toByteArray());
statement.setInt(2, deviceId);
statement.setBytes(3, UuidUtil.toByteArray(distributionId.asUuid()));
statement.executeUpdate();
}
}
- public void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
- try (final var connection = database.getConnection()) {
- final var sql = (
- """
- UPDATE OR REPLACE %s
- SET recipient_id = ?
- WHERE recipient_id = ?
- """
- ).formatted(TABLE_SENDER_KEY_SHARED);
- try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
- statement.setLong(2, toBeMergedRecipientId.id());
- statement.executeUpdate();
- }
- } catch (SQLException e) {
- throw new RuntimeException("Failed update shared sender key store", e);
- }
- }
-
void addLegacySenderKeysShared(final Map<DistributionId, Set<SenderKeySharedEntry>> sharedSenderKeys) {
logger.debug("Migrating legacy sender keys shared to database");
long start = System.nanoTime();
) throws SQLException {
final var sql = (
"""
- INSERT OR REPLACE INTO %s (recipient_id, device_id, distribution_id, timestamp)
+ INSERT OR REPLACE INTO %s (uuid, device_id, distribution_id, timestamp)
VALUES (?, ?, ?, ?)
"""
).formatted(TABLE_SENDER_KEY_SHARED);
try (final var statement = connection.prepareStatement(sql)) {
for (final var entry : newEntries) {
- statement.setLong(1, entry.recipientId().id());
+ statement.setBytes(1, entry.serviceId().toByteArray());
statement.setInt(2, entry.deviceId());
statement.setBytes(3, UuidUtil.toByteArray(distributionId.asUuid()));
statement.setLong(4, System.currentTimeMillis());
}
private SenderKeySharedEntry getSenderKeySharedEntryFromResultSet(ResultSet resultSet) throws SQLException {
- final var recipientId = resultSet.getLong("recipient_id");
+ final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
final var deviceId = resultSet.getInt("device_id");
- return new SenderKeySharedEntry(recipientIdCreator.create(recipientId), deviceId);
+ return new SenderKeySharedEntry(serviceId, deviceId);
}
- record SenderKeySharedEntry(RecipientId recipientId, int deviceId) {}
+ record SenderKeySharedEntry(ServiceId serviceId, int deviceId) {}
}
package org.asamk.signal.manager.storage.senderKeys;
import org.asamk.signal.manager.api.Pair;
-import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.Database;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
-import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
-import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord;
import org.whispersystems.signalservice.api.SignalServiceSenderKeyStore;
import org.whispersystems.signalservice.api.push.DistributionId;
+import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.Collection;
import java.util.Map;
private final SenderKeyRecordStore senderKeyRecordStore;
private final SenderKeySharedStore senderKeySharedStore;
- public SenderKeyStore(
- final Database database,
- final RecipientAddressResolver addressResolver,
- final RecipientResolver resolver,
- final RecipientIdCreator recipientIdCreator
- ) {
- this.senderKeyRecordStore = new SenderKeyRecordStore(database, resolver);
- this.senderKeySharedStore = new SenderKeySharedStore(database, recipientIdCreator, addressResolver, resolver);
+ public SenderKeyStore(final Database database) {
+ this.senderKeyRecordStore = new SenderKeyRecordStore(database);
+ this.senderKeySharedStore = new SenderKeySharedStore(database);
}
@Override
senderKeyRecordStore.deleteAll();
}
- public void deleteAll(RecipientId recipientId) {
- senderKeySharedStore.deleteAllFor(recipientId);
- senderKeyRecordStore.deleteAllFor(recipientId);
+ public void deleteAll(ServiceId serviceId) {
+ senderKeySharedStore.deleteAllFor(serviceId);
+ senderKeyRecordStore.deleteAllFor(serviceId);
}
- public void deleteSharedWith(RecipientId recipientId) {
- senderKeySharedStore.deleteAllFor(recipientId);
+ public void deleteSharedWith(ServiceId serviceId) {
+ senderKeySharedStore.deleteAllFor(serviceId);
}
- public void deleteSharedWith(RecipientId recipientId, int deviceId, DistributionId distributionId) {
- senderKeySharedStore.deleteSharedWith(recipientId, deviceId, distributionId);
+ public void deleteSharedWith(ServiceId serviceId, int deviceId, DistributionId distributionId) {
+ senderKeySharedStore.deleteSharedWith(serviceId, deviceId, distributionId);
}
- public void deleteOurKey(RecipientId selfRecipientId, DistributionId distributionId) {
+ public void deleteOurKey(ServiceId selfServiceId, DistributionId distributionId) {
senderKeySharedStore.deleteAllFor(distributionId);
- senderKeyRecordStore.deleteSenderKey(selfRecipientId, distributionId.asUuid());
- }
-
- public long getCreateTimeForOurKey(RecipientId selfRecipientId, int deviceId, DistributionId distributionId) {
- return senderKeyRecordStore.getCreateTimeForKey(selfRecipientId, deviceId, distributionId.asUuid());
+ senderKeyRecordStore.deleteSenderKey(selfServiceId, distributionId.asUuid());
}
- public void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
- senderKeySharedStore.mergeRecipients(recipientId, toBeMergedRecipientId);
- senderKeyRecordStore.mergeRecipients(recipientId, toBeMergedRecipientId);
+ public long getCreateTimeForOurKey(ServiceId selfServiceId, int deviceId, DistributionId distributionId) {
+ return senderKeyRecordStore.getCreateTimeForKey(selfServiceId, deviceId, distributionId.asUuid());
}
void addLegacySenderKeys(final Collection<Pair<SenderKeyRecordStore.Key, SenderKeyRecord>> senderKeys) {
package org.asamk.signal.manager.storage.sessions;
import org.asamk.signal.manager.api.Pair;
+import org.asamk.signal.manager.helper.RecipientAddressResolver;
+import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
-import org.asamk.signal.manager.storage.sessions.SessionStore.Key;
import org.asamk.signal.manager.util.IOUtils;
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;
private final static Logger logger = LoggerFactory.getLogger(LegacySessionStore.class);
public static void migrate(
- final File sessionsPath, final RecipientResolver resolver, final SessionStore sessionStore
+ final File sessionsPath,
+ final RecipientResolver resolver,
+ final RecipientAddressResolver addressResolver,
+ final SessionStore sessionStore
) {
final var keys = getKeysLocked(sessionsPath, resolver);
final var sessions = keys.stream().map(key -> {
final var record = loadSessionLocked(key, sessionsPath);
- if (record == null) {
+ final var uuid = addressResolver.resolveRecipientAddress(key.recipientId).uuid();
+ if (record == null || uuid.isEmpty()) {
return null;
}
- return new Pair<>(key, record);
+ return new Pair<>(new SessionStore.Key(ServiceId.from(uuid.get()), key.deviceId()), record);
}).filter(Objects::nonNull).toList();
sessionStore.addLegacySessions(sessions);
deleteAllSessions(sessionsPath);
return null;
}
}
+
+ record Key(RecipientId recipientId, int deviceId) {}
}
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
-import org.asamk.signal.manager.storage.recipients.RecipientId;
-import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
-import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.NoSessionException;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalServiceSessionStore;
+import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIdType;
+import org.whispersystems.signalservice.api.util.UuidUtil;
import java.sql.Connection;
import java.sql.ResultSet;
private final Database database;
private final int accountIdType;
- private final RecipientResolver resolver;
- private final RecipientIdCreator recipientIdCreator;
public static void createSql(Connection connection) throws SQLException {
// When modifying the CREATE statement here, also add a migration in AccountDatabase.java
CREATE TABLE session (
_id INTEGER PRIMARY KEY,
account_id_type INTEGER NOT NULL,
- recipient_id INTEGER NOT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
+ uuid BLOB NOT NULL,
device_id INTEGER NOT NULL,
record BLOB NOT NULL,
- UNIQUE(account_id_type, recipient_id, device_id)
- );
+ UNIQUE(account_id_type, uuid, device_id)
+ ) STRICT;
""");
}
}
- public SessionStore(
- final Database database,
- final ServiceIdType serviceIdType,
- final RecipientResolver resolver,
- final RecipientIdCreator recipientIdCreator
- ) {
+ public SessionStore(final Database database, final ServiceIdType serviceIdType) {
this.database = database;
this.accountIdType = Utils.getAccountIdType(serviceIdType);
- this.resolver = resolver;
- this.recipientIdCreator = recipientIdCreator;
}
@Override
@Override
public List<Integer> getSubDeviceSessions(String name) {
- final var recipientId = resolver.resolveRecipient(name);
+ final var serviceId = ServiceId.parseOrThrow(name);
// get all sessions for recipient except primary device session
final var sql = (
"""
SELECT s.device_id
FROM %s AS s
- WHERE s.account_id_type = ? AND s.recipient_id = ? AND s.device_id != 1
+ WHERE s.account_id_type = ? AND s.uuid = ? AND s.device_id != 1
"""
).formatted(TABLE_SESSION);
try (final var connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
- statement.setLong(2, recipientId.id());
+ statement.setBytes(2, serviceId.toByteArray());
return Utils.executeQueryForStream(statement, res -> res.getInt("device_id")).toList();
}
} catch (SQLException e) {
}
}
- public boolean isCurrentRatchetKey(RecipientId recipientId, int deviceId, ECPublicKey ratchetKey) {
- final var key = new Key(recipientId, deviceId);
+ public boolean isCurrentRatchetKey(ServiceId serviceId, int deviceId, ECPublicKey ratchetKey) {
+ final var key = new Key(serviceId, deviceId);
try (final var connection = database.getConnection()) {
final var session = loadSession(connection, key);
@Override
public void deleteAllSessions(String name) {
- final var recipientId = resolver.resolveRecipient(name);
- deleteAllSessions(recipientId);
+ final var serviceId = ServiceId.parseOrThrow(name);
+ deleteAllSessions(serviceId);
}
- public void deleteAllSessions(RecipientId recipientId) {
+ public void deleteAllSessions(ServiceId serviceId) {
try (final var connection = database.getConnection()) {
- deleteAllSessions(connection, recipientId);
+ deleteAllSessions(connection, serviceId);
} catch (SQLException e) {
throw new RuntimeException("Failed update session store", e);
}
@Override
public void archiveSession(final SignalProtocolAddress address) {
+ if (!UuidUtil.isUuid(address.getName())) {
+ return;
+ }
+
final var key = getKey(address);
try (final var connection = database.getConnection()) {
@Override
public Set<SignalProtocolAddress> getAllAddressesWithActiveSessions(final List<String> addressNames) {
- final var recipientIdToNameMap = addressNames.stream()
- .collect(Collectors.toMap(resolver::resolveRecipient, name -> name));
- final var recipientIdsCommaSeparated = recipientIdToNameMap.keySet()
- .stream()
- .map(recipientId -> String.valueOf(recipientId.id()))
+ final var serviceIdsCommaSeparated = addressNames.stream()
+ .map(ServiceId::parseOrThrow)
+ .map(ServiceId::toString)
.collect(Collectors.joining(","));
final var sql = (
"""
- SELECT s.recipient_id, s.device_id, s.record
+ SELECT s.uuid, s.device_id, s.record
FROM %s AS s
- WHERE s.account_id_type = ? AND s.recipient_id IN (%s)
+ WHERE s.account_id_type = ? AND s.uuid IN (%s)
"""
- ).formatted(TABLE_SESSION, recipientIdsCommaSeparated);
+ ).formatted(TABLE_SESSION, serviceIdsCommaSeparated);
try (final var connection = database.getConnection()) {
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
res -> new Pair<>(getKeyFromResultSet(res), getSessionRecordFromResultSet(res)))
.filter(pair -> isActive(pair.second()))
.map(Pair::first)
- .map(key -> new SignalProtocolAddress(recipientIdToNameMap.get(key.recipientId),
- key.deviceId()))
+ .map(key -> key.serviceId().toProtocolAddress(key.deviceId()))
.collect(Collectors.toSet());
}
} catch (SQLException e) {
public void archiveAllSessions() {
final var sql = (
"""
- SELECT s.recipient_id, s.device_id, s.record
+ SELECT s.uuid, s.device_id, s.record
FROM %s AS s
WHERE s.account_id_type = ?
"""
}
}
- public void archiveSessions(final RecipientId recipientId) {
+ public void archiveSessions(final ServiceId serviceId) {
final var sql = (
"""
- SELECT s.recipient_id, s.device_id, s.record
+ SELECT s.uuid, s.device_id, s.record
FROM %s AS s
- WHERE s.account_id_type = ? AND s.recipient_id = ?
+ WHERE s.account_id_type = ? AND s.uuid = ?
"""
).formatted(TABLE_SESSION);
try (final var connection = database.getConnection()) {
final List<Pair<Key, SessionRecord>> records;
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
- statement.setLong(2, recipientId.id());
+ statement.setBytes(2, serviceId.toByteArray());
records = Utils.executeQueryForStream(statement,
res -> new Pair<>(getKeyFromResultSet(res), getSessionRecordFromResultSet(res))).toList();
}
}
}
- public void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
- try (final var connection = database.getConnection()) {
- connection.setAutoCommit(false);
- synchronized (cachedSessions) {
- cachedSessions.clear();
- }
-
- final var sql = """
- UPDATE OR IGNORE %s
- SET recipient_id = ?
- WHERE account_id_type = ? AND recipient_id = ?
- """.formatted(TABLE_SESSION);
- try (final var statement = connection.prepareStatement(sql)) {
- statement.setLong(1, recipientId.id());
- statement.setInt(2, accountIdType);
- statement.setLong(3, toBeMergedRecipientId.id());
- final var rows = statement.executeUpdate();
- if (rows > 0) {
- logger.debug("Reassigned {} sessions of to be merged recipient.", rows);
- }
- }
- // Delete all conflicting sessions now
- deleteAllSessions(connection, toBeMergedRecipientId);
- connection.commit();
- } catch (SQLException e) {
- throw new RuntimeException("Failed update session store", e);
- }
- }
-
void addLegacySessions(final Collection<Pair<Key, SessionRecord>> sessions) {
logger.debug("Migrating legacy sessions to database");
long start = System.nanoTime();
}
private Key getKey(final SignalProtocolAddress address) {
- final var recipientId = resolver.resolveRecipient(address.getName());
- return new Key(recipientId, address.getDeviceId());
+ final var serviceId = ServiceId.parseOrThrow(address.getName());
+ return new Key(serviceId, address.getDeviceId());
}
private SessionRecord loadSession(Connection connection, final Key key) throws SQLException {
"""
SELECT s.record
FROM %s AS s
- WHERE s.account_id_type = ? AND s.recipient_id = ? AND s.device_id = ?
+ WHERE s.account_id_type = ? AND s.uuid = ? AND s.device_id = ?
"""
).formatted(TABLE_SESSION);
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
- statement.setLong(2, key.recipientId().id());
+ statement.setBytes(2, key.serviceId().toByteArray());
statement.setInt(3, key.deviceId());
return Utils.executeQueryForOptional(statement, this::getSessionRecordFromResultSet).orElse(null);
}
}
private Key getKeyFromResultSet(ResultSet resultSet) throws SQLException {
- final var recipientId = resultSet.getLong("recipient_id");
+ final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid"));
final var deviceId = resultSet.getInt("device_id");
- return new Key(recipientIdCreator.create(recipientId), deviceId);
+ return new Key(serviceId, deviceId);
}
private SessionRecord getSessionRecordFromResultSet(ResultSet resultSet) throws SQLException {
}
final var sql = """
- INSERT OR REPLACE INTO %s (account_id_type, recipient_id, device_id, record)
+ INSERT OR REPLACE INTO %s (account_id_type, uuid, device_id, record)
VALUES (?, ?, ?, ?)
""".formatted(TABLE_SESSION);
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
- statement.setLong(2, key.recipientId().id());
+ statement.setBytes(2, key.serviceId().toByteArray());
statement.setInt(3, key.deviceId());
statement.setBytes(4, session.serialize());
statement.executeUpdate();
}
}
- private void deleteAllSessions(final Connection connection, final RecipientId recipientId) throws SQLException {
+ private void deleteAllSessions(final Connection connection, final ServiceId serviceId) throws SQLException {
synchronized (cachedSessions) {
cachedSessions.clear();
}
final var sql = (
"""
DELETE FROM %s AS s
- WHERE s.account_id_type = ? AND s.recipient_id = ?
+ WHERE s.account_id_type = ? AND s.uuid = ?
"""
).formatted(TABLE_SESSION);
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
- statement.setLong(2, recipientId.id());
+ statement.setBytes(2, serviceId.toByteArray());
statement.executeUpdate();
}
}
final var sql = (
"""
DELETE FROM %s AS s
- WHERE s.account_id_type = ? AND s.recipient_id = ? AND s.device_id = ?
+ WHERE s.account_id_type = ? AND s.uuid = ? AND s.device_id = ?
"""
).formatted(TABLE_SESSION);
try (final var statement = connection.prepareStatement(sql)) {
statement.setInt(1, accountIdType);
- statement.setLong(2, key.recipientId().id());
+ statement.setBytes(2, key.serviceId().toByteArray());
statement.setInt(3, key.deviceId());
statement.executeUpdate();
}
&& record.getSessionVersion() == CiphertextMessage.CURRENT_VERSION;
}
- record Key(RecipientId recipientId, int deviceId) {}
+ record Key(ServiceId serviceId, int deviceId) {}
}
package org.asamk.signal.manager.util;
import org.asamk.signal.manager.api.Pair;
+import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.fingerprint.Fingerprint;
import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.BufferedInputStream;
public static Fingerprint computeSafetyNumber(
boolean isUuidCapable,
- SignalServiceAddress ownAddress,
+ RecipientAddress ownAddress,
IdentityKey ownIdentityKey,
- SignalServiceAddress theirAddress,
+ RecipientAddress theirAddress,
IdentityKey theirIdentityKey
) {
int version;
byte[] ownId;
byte[] theirId;
- if (isUuidCapable) {
+ if (!isUuidCapable && ownAddress.number().isPresent() && theirAddress.number().isPresent()) {
+ // Version 1: E164 user
+ version = 1;
+ ownId = ownAddress.number().get().getBytes();
+ theirId = theirAddress.number().get().getBytes();
+ } else if (isUuidCapable && theirAddress.uuid().isPresent()) {
// Version 2: UUID user
version = 2;
ownId = ownAddress.getServiceId().toByteArray();
theirId = theirAddress.getServiceId().toByteArray();
} else {
- // Version 1: E164 user
- version = 1;
- if (ownAddress.getNumber().isEmpty() || theirAddress.getNumber().isEmpty()) {
- return null;
- }
- ownId = ownAddress.getNumber().get().getBytes();
- theirId = theirAddress.getNumber().get().getBytes();
+ return null;
}
return new NumericFingerprintGenerator(5200).createFor(version,