]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java
Refactor contact and profile store
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / ProfileHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.storage.recipients.RecipientId;
4 import org.signal.zkgroup.profiles.ProfileKey;
5 import org.whispersystems.libsignal.util.guava.Optional;
6 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
7 import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
8 import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
9 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
10 import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
11 import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
12 import org.whispersystems.signalservice.internal.util.concurrent.CascadingFuture;
13 import org.whispersystems.signalservice.internal.util.concurrent.ListenableFuture;
14
15 import java.io.IOException;
16 import java.util.Arrays;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20
21 public final class ProfileHelper {
22
23 private final ProfileKeyProvider profileKeyProvider;
24
25 private final UnidentifiedAccessProvider unidentifiedAccessProvider;
26
27 private final MessagePipeProvider messagePipeProvider;
28
29 private final MessageReceiverProvider messageReceiverProvider;
30
31 private final SignalServiceAddressResolver addressResolver;
32
33 public ProfileHelper(
34 final ProfileKeyProvider profileKeyProvider,
35 final UnidentifiedAccessProvider unidentifiedAccessProvider,
36 final MessagePipeProvider messagePipeProvider,
37 final MessageReceiverProvider messageReceiverProvider,
38 final SignalServiceAddressResolver addressResolver
39 ) {
40 this.profileKeyProvider = profileKeyProvider;
41 this.unidentifiedAccessProvider = unidentifiedAccessProvider;
42 this.messagePipeProvider = messagePipeProvider;
43 this.messageReceiverProvider = messageReceiverProvider;
44 this.addressResolver = addressResolver;
45 }
46
47 public ProfileAndCredential retrieveProfileSync(
48 RecipientId recipientId, SignalServiceProfile.RequestType requestType
49 ) throws IOException {
50 try {
51 return retrieveProfile(recipientId, requestType).get(10, TimeUnit.SECONDS);
52 } catch (ExecutionException e) {
53 if (e.getCause() instanceof PushNetworkException) {
54 throw (PushNetworkException) e.getCause();
55 } else if (e.getCause() instanceof NotFoundException) {
56 throw (NotFoundException) e.getCause();
57 } else {
58 throw new IOException(e);
59 }
60 } catch (InterruptedException | TimeoutException e) {
61 throw new PushNetworkException(e);
62 }
63 }
64
65 public ListenableFuture<ProfileAndCredential> retrieveProfile(
66 RecipientId recipientId, SignalServiceProfile.RequestType requestType
67 ) {
68 var unidentifiedAccess = getUnidentifiedAccess(recipientId);
69 var profileKey = Optional.fromNullable(profileKeyProvider.getProfileKey(recipientId));
70
71 final var address = addressResolver.resolveSignalServiceAddress(recipientId);
72 if (unidentifiedAccess.isPresent()) {
73 return new CascadingFuture<>(Arrays.asList(() -> getPipeRetrievalFuture(address,
74 profileKey,
75 unidentifiedAccess,
76 requestType),
77 () -> getSocketRetrievalFuture(address, profileKey, unidentifiedAccess, requestType),
78 () -> getPipeRetrievalFuture(address, profileKey, Optional.absent(), requestType),
79 () -> getSocketRetrievalFuture(address, profileKey, Optional.absent(), requestType)),
80 e -> !(e instanceof NotFoundException));
81 } else {
82 return new CascadingFuture<>(Arrays.asList(() -> getPipeRetrievalFuture(address,
83 profileKey,
84 Optional.absent(),
85 requestType), () -> getSocketRetrievalFuture(address, profileKey, Optional.absent(), requestType)),
86 e -> !(e instanceof NotFoundException));
87 }
88 }
89
90 private ListenableFuture<ProfileAndCredential> getPipeRetrievalFuture(
91 SignalServiceAddress address,
92 Optional<ProfileKey> profileKey,
93 Optional<UnidentifiedAccess> unidentifiedAccess,
94 SignalServiceProfile.RequestType requestType
95 ) throws IOException {
96 var unidentifiedPipe = messagePipeProvider.getMessagePipe(true);
97 var pipe = unidentifiedPipe != null && unidentifiedAccess.isPresent()
98 ? unidentifiedPipe
99 : messagePipeProvider.getMessagePipe(false);
100 if (pipe != null) {
101 try {
102 return pipe.getProfile(address, profileKey, unidentifiedAccess, requestType);
103 } catch (NoClassDefFoundError e) {
104 // Native zkgroup lib not available for ProfileKey
105 if (!address.getNumber().isPresent()) {
106 throw new NotFoundException("Can't request profile without number");
107 }
108 var addressWithoutUuid = new SignalServiceAddress(Optional.absent(), address.getNumber());
109 return pipe.getProfile(addressWithoutUuid, profileKey, unidentifiedAccess, requestType);
110 }
111 }
112
113 throw new IOException("No pipe available!");
114 }
115
116 private ListenableFuture<ProfileAndCredential> getSocketRetrievalFuture(
117 SignalServiceAddress address,
118 Optional<ProfileKey> profileKey,
119 Optional<UnidentifiedAccess> unidentifiedAccess,
120 SignalServiceProfile.RequestType requestType
121 ) throws NotFoundException {
122 var receiver = messageReceiverProvider.getMessageReceiver();
123 try {
124 return receiver.retrieveProfile(address, profileKey, unidentifiedAccess, requestType);
125 } catch (NoClassDefFoundError e) {
126 // Native zkgroup lib not available for ProfileKey
127 if (!address.getNumber().isPresent()) {
128 throw new NotFoundException("Can't request profile without number");
129 }
130 var addressWithoutUuid = new SignalServiceAddress(Optional.absent(), address.getNumber());
131 return receiver.retrieveProfile(addressWithoutUuid, profileKey, unidentifiedAccess, requestType);
132 }
133 }
134
135 private Optional<UnidentifiedAccess> getUnidentifiedAccess(RecipientId recipientId) {
136 var unidentifiedAccess = unidentifiedAccessProvider.getAccessFor(recipientId);
137
138 if (unidentifiedAccess.isPresent()) {
139 return unidentifiedAccess.get().getTargetUnidentifiedAccess();
140 }
141
142 return Optional.absent();
143 }
144 }