1 package org
.asamk
.signal
.manager
.helper
;
3 import org
.asamk
.signal
.manager
.SignalDependencies
;
4 import org
.asamk
.signal
.manager
.api
.RecipientIdentifier
;
5 import org
.asamk
.signal
.manager
.api
.UnregisteredRecipientException
;
6 import org
.asamk
.signal
.manager
.config
.ServiceEnvironmentConfig
;
7 import org
.asamk
.signal
.manager
.storage
.SignalAccount
;
8 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
9 import org
.slf4j
.Logger
;
10 import org
.slf4j
.LoggerFactory
;
11 import org
.whispersystems
.signalservice
.api
.push
.ACI
;
12 import org
.whispersystems
.signalservice
.api
.push
.PNI
;
13 import org
.whispersystems
.signalservice
.api
.push
.ServiceId
;
14 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
15 import org
.whispersystems
.signalservice
.api
.services
.CdsiV2Service
;
17 import java
.io
.IOException
;
18 import java
.util
.Collection
;
19 import java
.util
.HashMap
;
20 import java
.util
.HashSet
;
22 import java
.util
.Optional
;
25 public class RecipientHelper
{
27 private final static Logger logger
= LoggerFactory
.getLogger(RecipientHelper
.class);
29 private final SignalAccount account
;
30 private final SignalDependencies dependencies
;
31 private final ServiceEnvironmentConfig serviceEnvironmentConfig
;
33 public RecipientHelper(final Context context
) {
34 this.account
= context
.getAccount();
35 this.dependencies
= context
.getDependencies();
36 this.serviceEnvironmentConfig
= dependencies
.getServiceEnvironmentConfig();
39 public SignalServiceAddress
resolveSignalServiceAddress(RecipientId recipientId
) {
40 final var address
= account
.getRecipientAddressResolver().resolveRecipientAddress(recipientId
);
41 if (address
.number().isEmpty() || address
.serviceId().isPresent()) {
42 return address
.toSignalServiceAddress();
45 // Address in recipient store doesn't have a uuid, this shouldn't happen
46 // Try to retrieve the uuid from the server
47 final var number
= address
.number().get();
48 final ServiceId serviceId
;
50 serviceId
= getRegisteredUser(number
);
51 } catch (UnregisteredRecipientException e
) {
52 logger
.warn("Failed to get uuid for e164 number: {}", number
);
53 // Return SignalServiceAddress with unknown UUID
54 return address
.toSignalServiceAddress();
55 } catch (IOException e
) {
56 logger
.warn("Failed to get uuid for e164 number: {}", number
, e
);
57 // Return SignalServiceAddress with unknown UUID
58 return address
.toSignalServiceAddress();
60 return account
.getRecipientAddressResolver()
61 .resolveRecipientAddress(account
.getRecipientResolver().resolveRecipient(serviceId
))
62 .toSignalServiceAddress();
65 public RecipientId
resolveRecipient(final SignalServiceAddress address
) {
66 return account
.getRecipientResolver().resolveRecipient(address
);
69 public Set
<RecipientId
> resolveRecipients(Collection
<RecipientIdentifier
.Single
> recipients
) throws UnregisteredRecipientException
{
70 final var recipientIds
= new HashSet
<RecipientId
>(recipients
.size());
71 for (var number
: recipients
) {
72 final var recipientId
= resolveRecipient(number
);
73 recipientIds
.add(recipientId
);
78 public RecipientId
resolveRecipient(final RecipientIdentifier
.Single recipient
) throws UnregisteredRecipientException
{
79 if (recipient
instanceof RecipientIdentifier
.Uuid uuidRecipient
) {
80 return account
.getRecipientResolver().resolveRecipient(ServiceId
.from(uuidRecipient
.uuid()));
82 final var number
= ((RecipientIdentifier
.Number
) recipient
).number();
83 return account
.getRecipientStore().resolveRecipient(number
, () -> {
85 return getRegisteredUser(number
);
86 } catch (Exception e
) {
93 public RecipientId
refreshRegisteredUser(RecipientId recipientId
) throws IOException
, UnregisteredRecipientException
{
94 final var address
= resolveSignalServiceAddress(recipientId
);
95 if (address
.getNumber().isEmpty()) {
98 final var number
= address
.getNumber().get();
99 final var serviceId
= getRegisteredUser(number
);
100 return account
.getRecipientTrustedResolver()
101 .resolveRecipientTrusted(new SignalServiceAddress(serviceId
, number
));
104 public Map
<String
, RegisteredUser
> getRegisteredUsers(final Set
<String
> numbers
) throws IOException
{
105 Map
<String
, RegisteredUser
> registeredUsers
= getRegisteredUsersV2(numbers
, true);
107 // Store numbers as recipients, so we have the number/uuid association
108 registeredUsers
.forEach((number
, u
) -> account
.getRecipientTrustedResolver()
109 .resolveRecipientTrusted(u
.aci
, u
.pni
, Optional
.of(number
)));
111 return registeredUsers
;
114 private ServiceId
getRegisteredUser(final String number
) throws IOException
, UnregisteredRecipientException
{
115 final Map
<String
, RegisteredUser
> aciMap
;
117 aciMap
= getRegisteredUsers(Set
.of(number
));
118 } catch (NumberFormatException e
) {
119 throw new UnregisteredRecipientException(new org
.asamk
.signal
.manager
.api
.RecipientAddress(null, number
));
121 final var user
= aciMap
.get(number
);
123 throw new UnregisteredRecipientException(new org
.asamk
.signal
.manager
.api
.RecipientAddress(null, number
));
125 return user
.getServiceId();
128 private Map
<String
, RegisteredUser
> getRegisteredUsersV2(
129 final Set
<String
> numbers
, boolean useCompat
130 ) throws IOException
{
131 // Only partial refresh is implemented here
132 final CdsiV2Service
.Response response
;
134 response
= dependencies
.getAccountManager()
135 .getRegisteredUsersWithCdsi(Set
.of(),
137 account
.getRecipientStore().getServiceIdToProfileKeyMap(),
140 serviceEnvironmentConfig
.getCdsiMrenclave(),
142 // Not storing for partial refresh
144 } catch (NumberFormatException e
) {
145 throw new IOException(e
);
147 logger
.debug("CDSI request successful, quota used by this request: {}", response
.getQuotaUsedDebugOnly());
149 final var registeredUsers
= new HashMap
<String
, RegisteredUser
>();
150 response
.getResults()
151 .forEach((key
, value
) -> registeredUsers
.put(key
,
152 new RegisteredUser(value
.getAci(), Optional
.of(value
.getPni()))));
153 return registeredUsers
;
156 private ACI
getRegisteredUserByUsername(String username
) throws IOException
{
157 return dependencies
.getAccountManager().getAciByUsernameHash(username
);
160 public record RegisteredUser(Optional
<ACI
> aci
, Optional
<PNI
> pni
) {
162 public RegisteredUser
{
163 aci
= aci
.isPresent() && aci
.get().equals(ServiceId
.UNKNOWN
) ? Optional
.empty() : aci
;
164 pni
= pni
.isPresent() && pni
.get().equals(ServiceId
.UNKNOWN
) ? Optional
.empty() : pni
;
165 if (aci
.isEmpty() && pni
.isEmpty()) {
166 throw new AssertionError("Must have either a ACI or PNI!");
170 public ServiceId
getServiceId() {
171 return aci
.map(a
-> (ServiceId
) a
).or(this::pni
).orElse(null);