]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java
95c171026f67bea7e89745f1d240aa67f31d6d1e
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / recipients / MergeRecipientHelper.java
1 package org.asamk.signal.manager.storage.recipients;
2
3 import org.asamk.signal.manager.api.Pair;
4 import org.slf4j.Logger;
5 import org.slf4j.LoggerFactory;
6
7 import java.sql.SQLException;
8 import java.util.HashSet;
9 import java.util.List;
10 import java.util.Set;
11 import java.util.stream.Collectors;
12
13 public class MergeRecipientHelper {
14
15 private static final Logger logger = LoggerFactory.getLogger(MergeRecipientHelper.class);
16
17 static Pair<RecipientId, List<RecipientId>> resolveRecipientTrustedLocked(
18 Store store,
19 RecipientAddress address
20 ) throws SQLException {
21 // address has at least one of serviceId/pni and optionally number/username
22
23 final var recipients = store.findAllByAddress(address);
24
25 if (recipients.isEmpty()) {
26 logger.debug("Got new recipient, serviceId, PNI, number, username are unknown");
27 return new Pair<>(store.addNewRecipient(address), List.of());
28 }
29
30 if (recipients.size() == 1) {
31 final var recipient = recipients.stream().findFirst().get();
32 if (recipient.address().hasIdentifiersOf(address)) {
33 return new Pair<>(recipient.id(), List.of());
34 }
35
36 if (recipient.address().aci().isEmpty() || (
37 address.aci().isEmpty() && (
38 address.pni().isEmpty() || recipient.address().pni().equals(address.pni())
39 )
40 ) || recipient.address().aci().equals(address.aci())) {
41 logger.debug("Got existing recipient {}, updating with high trust address", recipient.id());
42 store.updateRecipientAddress(recipient.id(), recipient.address().withIdentifiersFrom(address));
43 return new Pair<>(recipient.id(), List.of());
44 }
45
46 logger.debug(
47 "Got recipient {} existing with number/pni/username, but different aci, so stripping its number and adding new recipient",
48 recipient.id());
49 store.updateRecipientAddress(recipient.id(), recipient.address().removeIdentifiersFrom(address));
50
51 return new Pair<>(store.addNewRecipient(address), List.of());
52 }
53
54 var resultingRecipient = recipients.stream()
55 .filter(r -> r.address().aci().isPresent() && r.address().aci().equals(address.aci()))
56 .findFirst();
57 if (resultingRecipient.isEmpty() && address.pni().isPresent()) {
58 resultingRecipient = recipients.stream().filter(r -> r.address().pni().equals(address.pni())).findFirst();
59 }
60
61 final Set<RecipientWithAddress> remainingRecipients;
62 if (resultingRecipient.isEmpty()) {
63 remainingRecipients = recipients;
64 } else {
65 remainingRecipients = new HashSet<>(recipients);
66 remainingRecipients.remove(resultingRecipient.get());
67 }
68
69 final var recipientsToBeMerged = new HashSet<RecipientWithAddress>();
70 final var recipientsToBeStripped = new HashSet<RecipientWithAddress>();
71 for (final var recipient : remainingRecipients) {
72 if (!recipient.address().hasAdditionalIdentifiersThan(address)) {
73 recipientsToBeMerged.add(recipient);
74 continue;
75 }
76
77 if (recipient.address().hasOnlyPniAndNumber()) {
78 // PNI and phone number are linked by the server
79 recipientsToBeMerged.add(recipient);
80 continue;
81 }
82
83 recipientsToBeStripped.add(recipient);
84 }
85
86 logger.debug("Got separate recipients for high trust identifiers {}, need to merge ({}) and strip ({})",
87 address,
88 recipientsToBeMerged.stream().map(r -> r.id().toString()).collect(Collectors.joining(", ")),
89 recipientsToBeStripped.stream().map(r -> r.id().toString()).collect(Collectors.joining(", ")));
90
91 RecipientAddress finalAddress = resultingRecipient.map(RecipientWithAddress::address).orElse(null);
92 for (final var recipient : recipientsToBeMerged) {
93 if (finalAddress == null) {
94 finalAddress = recipient.address();
95 } else {
96 finalAddress = finalAddress.withIdentifiersFrom(recipient.address());
97 }
98 store.removeRecipientAddress(recipient.id());
99 }
100 if (finalAddress == null) {
101 finalAddress = address;
102 } else {
103 finalAddress = finalAddress.withIdentifiersFrom(address);
104 }
105
106 for (final var recipient : recipientsToBeStripped) {
107 store.updateRecipientAddress(recipient.id(), recipient.address().removeIdentifiersFrom(finalAddress));
108 }
109
110 // Create fixed RecipientIds that won't update its id after merged
111 final var toBeMergedRecipientIds = recipientsToBeMerged.stream()
112 .map(r -> new RecipientId(r.id().id(), null))
113 .toList();
114
115 if (resultingRecipient.isPresent()) {
116 store.updateRecipientAddress(resultingRecipient.get().id(), finalAddress);
117 return new Pair<>(resultingRecipient.get().id(), toBeMergedRecipientIds);
118 }
119
120 return new Pair<>(store.addNewRecipient(finalAddress), toBeMergedRecipientIds);
121 }
122
123 public interface Store {
124
125 Set<RecipientWithAddress> findAllByAddress(final RecipientAddress address) throws SQLException;
126
127 RecipientId addNewRecipient(final RecipientAddress address) throws SQLException;
128
129 void updateRecipientAddress(RecipientId recipientId, final RecipientAddress address) throws SQLException;
130
131 void removeRecipientAddress(RecipientId recipientId) throws SQLException;
132 }
133 }