mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Improve behavior of changed recipient id
This commit is contained in:
parent
bbe74ef020
commit
10df4338b1
8 changed files with 95 additions and 28 deletions
|
@ -36,6 +36,7 @@ import java.util.Base64;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class GroupStore {
|
public class GroupStore {
|
||||||
|
@ -82,8 +83,8 @@ public class GroupStore {
|
||||||
m.number));
|
m.number));
|
||||||
}
|
}
|
||||||
|
|
||||||
return RecipientId.of(m.recipientId);
|
return recipientResolver.resolveRecipient(m.recipientId);
|
||||||
}).collect(Collectors.toSet());
|
}).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||||
|
|
||||||
return new GroupInfoV1(GroupIdV1.fromBase64(g1.groupId),
|
return new GroupInfoV1(GroupIdV1.fromBase64(g1.groupId),
|
||||||
g1.expectedV2Id == null ? null : GroupIdV2.fromBase64(g1.expectedV2Id),
|
g1.expectedV2Id == null ? null : GroupIdV2.fromBase64(g1.expectedV2Id),
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -161,7 +162,8 @@ public class IdentityKeyStore implements org.whispersystems.libsignal.state.Iden
|
||||||
}
|
}
|
||||||
return Arrays.stream(files)
|
return Arrays.stream(files)
|
||||||
.filter(f -> identityFileNamePattern.matcher(f.getName()).matches())
|
.filter(f -> identityFileNamePattern.matcher(f.getName()).matches())
|
||||||
.map(f -> RecipientId.of(Integer.parseInt(f.getName())))
|
.map(f -> resolver.resolveRecipient(Long.parseLong(f.getName())))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.map(this::loadIdentityLocked)
|
.map(this::loadIdentityLocked)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,42 @@
|
||||||
package org.asamk.signal.manager.storage.recipients;
|
package org.asamk.signal.manager.storage.recipients;
|
||||||
|
|
||||||
public record RecipientId(long id) {
|
import java.util.Objects;
|
||||||
|
|
||||||
public static RecipientId of(long id) {
|
public final class RecipientId {
|
||||||
return new RecipientId(id);
|
|
||||||
|
private long id;
|
||||||
|
private final RecipientStore recipientStore;
|
||||||
|
|
||||||
|
RecipientId(long id, final RecipientStore recipientStore) {
|
||||||
|
this.id = id;
|
||||||
|
this.recipientStore = recipientStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long id() {
|
||||||
|
if (recipientStore != null) {
|
||||||
|
final var actualRecipientId = recipientStore.getActualRecipientId(this.id);
|
||||||
|
if (actualRecipientId != this.id) {
|
||||||
|
this.id = actualRecipientId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == this) return true;
|
||||||
|
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||||
|
var that = (RecipientId) obj;
|
||||||
|
return this.id() == that.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "RecipientId[" + "id=" + id() + ']';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,4 +12,6 @@ public interface RecipientResolver {
|
||||||
RecipientId resolveRecipient(SignalServiceAddress address);
|
RecipientId resolveRecipient(SignalServiceAddress address);
|
||||||
|
|
||||||
RecipientId resolveRecipient(ACI aci);
|
RecipientId resolveRecipient(ACI aci);
|
||||||
|
|
||||||
|
RecipientId resolveRecipient(long recipientId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
private final RecipientMergeHandler recipientMergeHandler;
|
private final RecipientMergeHandler recipientMergeHandler;
|
||||||
|
|
||||||
private final Map<RecipientId, Recipient> recipients;
|
private final Map<RecipientId, Recipient> recipients;
|
||||||
private final Map<RecipientId, RecipientId> recipientsMerged = new HashMap<>();
|
private final Map<Long, Long> recipientsMerged = new HashMap<>();
|
||||||
|
|
||||||
private long lastId;
|
private long lastId;
|
||||||
|
|
||||||
|
@ -52,8 +52,14 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
final var objectMapper = Utils.createStorageObjectMapper();
|
final var objectMapper = Utils.createStorageObjectMapper();
|
||||||
try (var inputStream = new FileInputStream(file)) {
|
try (var inputStream = new FileInputStream(file)) {
|
||||||
final var storage = objectMapper.readValue(inputStream, Storage.class);
|
final var storage = objectMapper.readValue(inputStream, Storage.class);
|
||||||
|
|
||||||
|
final var recipientStore = new RecipientStore(objectMapper,
|
||||||
|
file,
|
||||||
|
recipientMergeHandler,
|
||||||
|
new HashMap<>(),
|
||||||
|
storage.lastId);
|
||||||
final var recipients = storage.recipients.stream().map(r -> {
|
final var recipients = storage.recipients.stream().map(r -> {
|
||||||
final var recipientId = new RecipientId(r.id);
|
final var recipientId = new RecipientId(r.id, recipientStore);
|
||||||
final var address = new RecipientAddress(Optional.ofNullable(r.uuid).map(UuidUtil::parseOrThrow),
|
final var address = new RecipientAddress(Optional.ofNullable(r.uuid).map(UuidUtil::parseOrThrow),
|
||||||
Optional.ofNullable(r.number));
|
Optional.ofNullable(r.number));
|
||||||
|
|
||||||
|
@ -101,7 +107,9 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
return new Recipient(recipientId, address, contact, profileKey, profileKeyCredential, profile);
|
return new Recipient(recipientId, address, contact, profileKey, profileKeyCredential, profile);
|
||||||
}).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
|
}).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
|
||||||
|
|
||||||
return new RecipientStore(objectMapper, file, recipientMergeHandler, recipients, storage.lastId);
|
recipientStore.addRecipients(recipients);
|
||||||
|
|
||||||
|
return recipientStore;
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
logger.debug("Creating new recipient store.");
|
logger.debug("Creating new recipient store.");
|
||||||
return new RecipientStore(objectMapper, file, recipientMergeHandler, new HashMap<>(), 0);
|
return new RecipientStore(objectMapper, file, recipientMergeHandler, new HashMap<>(), 0);
|
||||||
|
@ -130,7 +138,6 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
|
|
||||||
public Recipient getRecipient(RecipientId recipientId) {
|
public Recipient getRecipient(RecipientId recipientId) {
|
||||||
synchronized (recipients) {
|
synchronized (recipients) {
|
||||||
recipientId = getActualRecipientId(recipientId);
|
|
||||||
return recipients.get(recipientId);
|
return recipients.get(recipientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,6 +147,12 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
return resolveRecipient(new RecipientAddress(aci == null ? null : aci.uuid()), false);
|
return resolveRecipient(new RecipientAddress(aci == null ? null : aci.uuid()), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecipientId resolveRecipient(final long recipientId) {
|
||||||
|
final var recipient = getRecipient(new RecipientId(recipientId, this));
|
||||||
|
return recipient == null ? null : recipient.getRecipientId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RecipientId resolveRecipient(final String identifier) {
|
public RecipientId resolveRecipient(final String identifier) {
|
||||||
return resolveRecipient(Utils.getRecipientAddressFromIdentifier(identifier), false);
|
return resolveRecipient(Utils.getRecipientAddressFromIdentifier(identifier), false);
|
||||||
|
@ -201,7 +214,6 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
@Override
|
@Override
|
||||||
public void storeContact(RecipientId recipientId, final Contact contact) {
|
public void storeContact(RecipientId recipientId, final Contact contact) {
|
||||||
synchronized (recipients) {
|
synchronized (recipients) {
|
||||||
recipientId = getActualRecipientId(recipientId);
|
|
||||||
final var recipient = recipients.get(recipientId);
|
final var recipient = recipients.get(recipientId);
|
||||||
storeRecipientLocked(recipientId, Recipient.newBuilder(recipient).withContact(contact).build());
|
storeRecipientLocked(recipientId, Recipient.newBuilder(recipient).withContact(contact).build());
|
||||||
}
|
}
|
||||||
|
@ -225,7 +237,6 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
@Override
|
@Override
|
||||||
public void deleteContact(RecipientId recipientId) {
|
public void deleteContact(RecipientId recipientId) {
|
||||||
synchronized (recipients) {
|
synchronized (recipients) {
|
||||||
recipientId = getActualRecipientId(recipientId);
|
|
||||||
final var recipient = recipients.get(recipientId);
|
final var recipient = recipients.get(recipientId);
|
||||||
storeRecipientLocked(recipientId, Recipient.newBuilder(recipient).withContact(null).build());
|
storeRecipientLocked(recipientId, Recipient.newBuilder(recipient).withContact(null).build());
|
||||||
}
|
}
|
||||||
|
@ -233,7 +244,6 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
|
|
||||||
public void deleteRecipientData(RecipientId recipientId) {
|
public void deleteRecipientData(RecipientId recipientId) {
|
||||||
synchronized (recipients) {
|
synchronized (recipients) {
|
||||||
recipientId = getActualRecipientId(recipientId);
|
|
||||||
logger.debug("Deleting recipient data for {}", recipientId);
|
logger.debug("Deleting recipient data for {}", recipientId);
|
||||||
final var recipient = recipients.get(recipientId);
|
final var recipient = recipients.get(recipientId);
|
||||||
storeRecipientLocked(recipientId,
|
storeRecipientLocked(recipientId,
|
||||||
|
@ -265,7 +275,6 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
@Override
|
@Override
|
||||||
public void storeProfile(RecipientId recipientId, final Profile profile) {
|
public void storeProfile(RecipientId recipientId, final Profile profile) {
|
||||||
synchronized (recipients) {
|
synchronized (recipients) {
|
||||||
recipientId = getActualRecipientId(recipientId);
|
|
||||||
final var recipient = recipients.get(recipientId);
|
final var recipient = recipients.get(recipientId);
|
||||||
storeRecipientLocked(recipientId, Recipient.newBuilder(recipient).withProfile(profile).build());
|
storeRecipientLocked(recipientId, Recipient.newBuilder(recipient).withProfile(profile).build());
|
||||||
}
|
}
|
||||||
|
@ -274,7 +283,6 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
@Override
|
@Override
|
||||||
public void storeProfileKey(RecipientId recipientId, final ProfileKey profileKey) {
|
public void storeProfileKey(RecipientId recipientId, final ProfileKey profileKey) {
|
||||||
synchronized (recipients) {
|
synchronized (recipients) {
|
||||||
recipientId = getActualRecipientId(recipientId);
|
|
||||||
final var recipient = recipients.get(recipientId);
|
final var recipient = recipients.get(recipientId);
|
||||||
if (profileKey != null && profileKey.equals(recipient.getProfileKey())) {
|
if (profileKey != null && profileKey.equals(recipient.getProfileKey())) {
|
||||||
return;
|
return;
|
||||||
|
@ -294,7 +302,6 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
@Override
|
@Override
|
||||||
public void storeProfileKeyCredential(RecipientId recipientId, final ProfileKeyCredential profileKeyCredential) {
|
public void storeProfileKeyCredential(RecipientId recipientId, final ProfileKeyCredential profileKeyCredential) {
|
||||||
synchronized (recipients) {
|
synchronized (recipients) {
|
||||||
recipientId = getActualRecipientId(recipientId);
|
|
||||||
final var recipient = recipients.get(recipientId);
|
final var recipient = recipients.get(recipientId);
|
||||||
storeRecipientLocked(recipientId,
|
storeRecipientLocked(recipientId,
|
||||||
Recipient.newBuilder(recipient).withProfileKeyCredential(profileKeyCredential).build());
|
Recipient.newBuilder(recipient).withProfileKeyCredential(profileKeyCredential).build());
|
||||||
|
@ -307,6 +314,10 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addRecipients(final Map<RecipientId, Recipient> recipients) {
|
||||||
|
this.recipients.putAll(recipients);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param isHighTrust true, if the number/uuid connection was obtained from a trusted source.
|
* @param isHighTrust true, if the number/uuid connection was obtained from a trusted source.
|
||||||
* Has no effect, if the address contains only a number or a uuid.
|
* Has no effect, if the address contains only a number or a uuid.
|
||||||
|
@ -391,8 +402,10 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
byNumberRecipient.getRecipientId(),
|
byNumberRecipient.getRecipientId(),
|
||||||
byUuidRecipient.getRecipientId());
|
byUuidRecipient.getRecipientId());
|
||||||
updateRecipientAddressLocked(byUuidRecipient.getRecipientId(), address);
|
updateRecipientAddressLocked(byUuidRecipient.getRecipientId(), address);
|
||||||
mergeRecipientsLocked(byUuidRecipient.getRecipientId(), byNumberRecipient.getRecipientId());
|
// Create a fixed RecipientId that won't update its id after merge
|
||||||
return new Pair<>(byUuidRecipient.getRecipientId(), byNumber.map(Recipient::getRecipientId));
|
final var toBeMergedRecipientId = new RecipientId(byNumberRecipient.getRecipientId().id(), null);
|
||||||
|
mergeRecipientsLocked(byUuidRecipient.getRecipientId(), toBeMergedRecipientId);
|
||||||
|
return new Pair<>(byUuidRecipient.getRecipientId(), Optional.of(toBeMergedRecipientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private RecipientId addNewRecipientLocked(final RecipientAddress address) {
|
private RecipientId addNewRecipientLocked(final RecipientAddress address) {
|
||||||
|
@ -403,12 +416,11 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRecipientAddressLocked(RecipientId recipientId, final RecipientAddress address) {
|
private void updateRecipientAddressLocked(RecipientId recipientId, final RecipientAddress address) {
|
||||||
recipientId = getActualRecipientId(recipientId);
|
|
||||||
final var recipient = recipients.get(recipientId);
|
final var recipient = recipients.get(recipientId);
|
||||||
storeRecipientLocked(recipientId, Recipient.newBuilder(recipient).withAddress(address).build());
|
storeRecipientLocked(recipientId, Recipient.newBuilder(recipient).withAddress(address).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private RecipientId getActualRecipientId(RecipientId recipientId) {
|
long getActualRecipientId(long recipientId) {
|
||||||
while (recipientsMerged.containsKey(recipientId)) {
|
while (recipientsMerged.containsKey(recipientId)) {
|
||||||
final var newRecipientId = recipientsMerged.get(recipientId);
|
final var newRecipientId = recipientsMerged.get(recipientId);
|
||||||
logger.debug("Using {} instead of {}, because recipients have been merged", newRecipientId, recipientId);
|
logger.debug("Using {} instead of {}, because recipients have been merged", newRecipientId, recipientId);
|
||||||
|
@ -440,7 +452,7 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
: toBeMergedRecipient.getProfileKeyCredential(),
|
: toBeMergedRecipient.getProfileKeyCredential(),
|
||||||
recipient.getProfile() != null ? recipient.getProfile() : toBeMergedRecipient.getProfile()));
|
recipient.getProfile() != null ? recipient.getProfile() : toBeMergedRecipient.getProfile()));
|
||||||
recipients.remove(toBeMergedRecipientId);
|
recipients.remove(toBeMergedRecipientId);
|
||||||
recipientsMerged.put(toBeMergedRecipientId, recipientId);
|
recipientsMerged.put(toBeMergedRecipientId.id(), recipientId.id());
|
||||||
saveLocked();
|
saveLocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,7 +479,7 @@ public class RecipientStore implements RecipientResolver, ContactsStore, Profile
|
||||||
}
|
}
|
||||||
|
|
||||||
private RecipientId nextIdLocked() {
|
private RecipientId nextIdLocked() {
|
||||||
return new RecipientId(++this.lastId);
|
return new RecipientId(++this.lastId, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveLocked() {
|
private void saveLocked() {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -139,9 +140,14 @@ public class SenderKeyRecordStore implements org.whispersystems.libsignal.groups
|
||||||
return Arrays.stream(files)
|
return Arrays.stream(files)
|
||||||
.map(f -> senderKeyFileNamePattern.matcher(f.getName()))
|
.map(f -> senderKeyFileNamePattern.matcher(f.getName()))
|
||||||
.filter(Matcher::matches)
|
.filter(Matcher::matches)
|
||||||
.map(matcher -> new Key(RecipientId.of(Long.parseLong(matcher.group(1))),
|
.map(matcher -> {
|
||||||
Integer.parseInt(matcher.group(2)),
|
final var recipientId = resolver.resolveRecipient(Long.parseLong(matcher.group(1)));
|
||||||
UUID.fromString(matcher.group(3))))
|
if (recipientId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Key(recipientId, Integer.parseInt(matcher.group(2)), UUID.fromString(matcher.group(3)));
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,11 @@ public class SenderKeySharedStore {
|
||||||
final var storage = objectMapper.readValue(inputStream, Storage.class);
|
final var storage = objectMapper.readValue(inputStream, Storage.class);
|
||||||
final var sharedSenderKeys = new HashMap<DistributionId, Set<SenderKeySharedEntry>>();
|
final var sharedSenderKeys = new HashMap<DistributionId, Set<SenderKeySharedEntry>>();
|
||||||
for (final var senderKey : storage.sharedSenderKeys) {
|
for (final var senderKey : storage.sharedSenderKeys) {
|
||||||
final var entry = new SenderKeySharedEntry(RecipientId.of(senderKey.recipientId), senderKey.deviceId);
|
final var recipientId = resolver.resolveRecipient(senderKey.recipientId);
|
||||||
|
if (recipientId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var entry = new SenderKeySharedEntry(recipientId, senderKey.deviceId);
|
||||||
final var uuid = UuidUtil.parseOrNull(senderKey.distributionId);
|
final var uuid = UuidUtil.parseOrNull(senderKey.distributionId);
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
logger.warn("Read invalid distribution id from storage {}, ignoring", senderKey.distributionId);
|
logger.warn("Read invalid distribution id from storage {}, ignoring", senderKey.distributionId);
|
||||||
|
|
|
@ -238,8 +238,14 @@ public class SessionStore implements SignalServiceSessionStore {
|
||||||
return Arrays.stream(files)
|
return Arrays.stream(files)
|
||||||
.map(f -> sessionFileNamePattern.matcher(f.getName()))
|
.map(f -> sessionFileNamePattern.matcher(f.getName()))
|
||||||
.filter(Matcher::matches)
|
.filter(Matcher::matches)
|
||||||
.map(matcher -> new Key(RecipientId.of(Long.parseLong(matcher.group(1))),
|
.map(matcher -> {
|
||||||
Integer.parseInt(matcher.group(2))))
|
final var recipientId = resolver.resolveRecipient(Long.parseLong(matcher.group(1)));
|
||||||
|
if (recipientId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Key(recipientId, Integer.parseInt(matcher.group(2)));
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue