X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/997b3c6a2a1f2e9a50fe579ac726677ea0d57d0c..4a1af0786c938f887a109a17dcc879da21704a8b:/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java diff --git a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java index a14f2d1f..69ae9269 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -17,12 +17,17 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.actions.HandleAction; +import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Device; import org.asamk.signal.manager.api.Group; import org.asamk.signal.manager.api.Identity; +import org.asamk.signal.manager.api.InactiveGroupLinkException; +import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.Message; +import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.SendGroupMessageResults; +import org.asamk.signal.manager.api.SendMessageResult; import org.asamk.signal.manager.api.SendMessageResults; import org.asamk.signal.manager.api.TypingAction; import org.asamk.signal.manager.api.UpdateGroup; @@ -38,6 +43,7 @@ import org.asamk.signal.manager.helper.AttachmentHelper; import org.asamk.signal.manager.helper.ContactHelper; import org.asamk.signal.manager.helper.GroupHelper; import org.asamk.signal.manager.helper.GroupV2Helper; +import org.asamk.signal.manager.helper.IdentityHelper; import org.asamk.signal.manager.helper.IncomingMessageHandler; import org.asamk.signal.manager.helper.PinHelper; import org.asamk.signal.manager.helper.PreKeyHelper; @@ -59,27 +65,18 @@ import org.asamk.signal.manager.storage.stickers.Sticker; import org.asamk.signal.manager.storage.stickers.StickerPackId; import org.asamk.signal.manager.util.KeyUtils; import org.asamk.signal.manager.util.StickerUtils; -import org.asamk.signal.manager.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.ecc.ECPublicKey; -import org.whispersystems.libsignal.fingerprint.Fingerprint; -import org.whispersystems.libsignal.fingerprint.FingerprintParsingException; -import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException; -import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalSessionLock; -import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; -import org.whispersystems.signalservice.api.messages.SendMessageResult; -import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; 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.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import org.whispersystems.signalservice.api.util.DeviceNameUtil; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; @@ -98,9 +95,7 @@ import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.SignatureException; -import java.util.Arrays; import java.util.Collection; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -112,8 +107,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.asamk.signal.manager.config.ServiceConfig.capabilities; @@ -138,9 +133,16 @@ public class ManagerImpl implements Manager { private final ContactHelper contactHelper; private final IncomingMessageHandler incomingMessageHandler; private final PreKeyHelper preKeyHelper; + private final IdentityHelper identityHelper; private final Context context; private boolean hasCaughtUpWithOldMessages = false; + private boolean ignoreAttachments = false; + + private Thread receiveThread; + private final Set weakHandlers = new HashSet<>(); + private final Set messageHandlers = new HashSet<>(); + private boolean isReceivingSynchronous; ManagerImpl( SignalAccount account, @@ -151,7 +153,7 @@ public class ManagerImpl implements Manager { this.account = account; this.serviceEnvironmentConfig = serviceEnvironmentConfig; - final var credentialsProvider = new DynamicCredentialsProvider(account.getUuid(), + final var credentialsProvider = new DynamicCredentialsProvider(account.getAci(), account.getUsername(), account.getPassword(), account.getDeviceId()); @@ -170,9 +172,9 @@ public class ManagerImpl implements Manager { account.getSignalProtocolStore(), executor, sessionLock); - final var avatarStore = new AvatarStore(pathConfig.getAvatarsPath()); - final var attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath()); - final var stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath()); + final var avatarStore = new AvatarStore(pathConfig.avatarsPath()); + final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath()); + final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath()); this.attachmentHelper = new AttachmentHelper(dependencies, attachmentStore); this.pinHelper = new PinHelper(dependencies.getKeyBackupService()); @@ -238,6 +240,11 @@ public class ManagerImpl implements Manager { syncHelper, this::getRecipientProfile, jobExecutor); + this.identityHelper = new IdentityHelper(account, + dependencies, + this::resolveSignalServiceAddress, + syncHelper, + profileHelper); } @Override @@ -259,8 +266,8 @@ public class ManagerImpl implements Manager { } } preKeyHelper.refreshPreKeysIfNecessary(); - if (account.getUuid() == null) { - account.setUuid(dependencies.getAccountManager().getOwnUuid()); + if (account.getAci() == null) { + account.setAci(dependencies.getAccountManager().getOwnAci()); } updateAccountAttributes(null); } @@ -290,8 +297,8 @@ public class ManagerImpl implements Manager { return numbers.stream().collect(Collectors.toMap(n -> n, n -> { final var number = canonicalizedNumbers.get(n); - final var uuid = registeredUsers.get(number); - return new Pair<>(number.isEmpty() ? null : number, uuid); + final var aci = registeredUsers.get(number); + return new Pair<>(number.isEmpty() ? null : number, aci == null ? null : aci.uuid()); })); } @@ -318,29 +325,35 @@ public class ManagerImpl implements Manager { account.isDiscoverableByPhoneNumber()); } + @Override + public Configuration getConfiguration() { + final var configurationStore = account.getConfigurationStore(); + return new Configuration(java.util.Optional.ofNullable(configurationStore.getReadReceipts()), + java.util.Optional.ofNullable(configurationStore.getUnidentifiedDeliveryIndicators()), + java.util.Optional.ofNullable(configurationStore.getTypingIndicators()), + java.util.Optional.ofNullable(configurationStore.getLinkPreviews())); + } + @Override public void updateConfiguration( - final Boolean readReceipts, - final Boolean unidentifiedDeliveryIndicators, - final Boolean typingIndicators, - final Boolean linkPreviews + Configuration configuration ) throws IOException, NotMasterDeviceException { if (!account.isMasterDevice()) { throw new NotMasterDeviceException(); } final var configurationStore = account.getConfigurationStore(); - if (readReceipts != null) { - configurationStore.setReadReceipts(readReceipts); + if (configuration.readReceipts().isPresent()) { + configurationStore.setReadReceipts(configuration.readReceipts().get()); } - if (unidentifiedDeliveryIndicators != null) { - configurationStore.setUnidentifiedDeliveryIndicators(unidentifiedDeliveryIndicators); + if (configuration.unidentifiedDeliveryIndicators().isPresent()) { + configurationStore.setUnidentifiedDeliveryIndicators(configuration.unidentifiedDeliveryIndicators().get()); } - if (typingIndicators != null) { - configurationStore.setTypingIndicators(typingIndicators); + if (configuration.typingIndicators().isPresent()) { + configurationStore.setTypingIndicators(configuration.typingIndicators().get()); } - if (linkPreviews != null) { - configurationStore.setLinkPreviews(linkPreviews); + if (configuration.linkPreviews().isPresent()) { + configurationStore.setLinkPreviews(configuration.linkPreviews().get()); } syncHelper.sendConfigurationMessage(); } @@ -354,9 +367,13 @@ public class ManagerImpl implements Manager { */ @Override public void setProfile( - String givenName, final String familyName, String about, String aboutEmoji, Optional avatar + String givenName, final String familyName, String about, String aboutEmoji, java.util.Optional avatar ) throws IOException { - profileHelper.setProfile(givenName, familyName, about, aboutEmoji, avatar); + profileHelper.setProfile(givenName, + familyName, + about, + aboutEmoji, + avatar == null ? null : Optional.fromNullable(avatar.orElse(null))); syncHelper.sendSyncFetchProfileMessage(); } @@ -374,7 +391,7 @@ public class ManagerImpl implements Manager { public void deleteAccount() throws IOException { try { pinHelper.removeRegistrationLockPin(); - } catch (UnauthenticatedResponseException e) { + } catch (IOException e) { logger.warn("Failed to remove registration lock pin"); } account.setRegistrationLockPin(null, null); @@ -386,6 +403,8 @@ public class ManagerImpl implements Manager { @Override public void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException { + captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); + dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha); } @@ -419,27 +438,33 @@ public class ManagerImpl implements Manager { } @Override - public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException { + public void addDeviceLink(URI linkUri) throws IOException, InvalidDeviceLinkException { var info = DeviceLinkInfo.parseDeviceLinkUri(linkUri); - addDevice(info.deviceIdentifier, info.deviceKey); + addDevice(info.deviceIdentifier(), info.deviceKey()); } - private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException { + private void addDevice( + String deviceIdentifier, ECPublicKey deviceKey + ) throws IOException, InvalidDeviceLinkException { var identityKeyPair = account.getIdentityKeyPair(); var verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode(); - dependencies.getAccountManager() - .addDevice(deviceIdentifier, - deviceKey, - identityKeyPair, - Optional.of(account.getProfileKey().serialize()), - verificationCode); + try { + dependencies.getAccountManager() + .addDevice(deviceIdentifier, + deviceKey, + identityKeyPair, + Optional.of(account.getProfileKey().serialize()), + verificationCode); + } catch (InvalidKeyException e) { + throw new InvalidDeviceLinkException("Invalid device link", e); + } account.setMultiDevice(true); } @Override - public void setRegistrationLockPin(Optional pin) throws IOException, UnauthenticatedResponseException { + public void setRegistrationLockPin(java.util.Optional pin) throws IOException { if (!account.isMasterDevice()) { throw new RuntimeException("Only master device can set a PIN"); } @@ -464,7 +489,7 @@ public class ManagerImpl implements Manager { } @Override - public Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws UnregisteredUserException { + public Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws IOException { return profileHelper.getRecipientProfile(resolveRecipient(recipient)); } @@ -554,7 +579,7 @@ public class ManagerImpl implements Manager { @Override public Pair joinGroup( GroupInviteLinkUrl inviteLinkUrl - ) throws IOException, GroupLinkNotActiveException { + ) throws IOException, InactiveGroupLinkException { return groupHelper.joinGroup(inviteLinkUrl); } @@ -565,17 +590,27 @@ public class ManagerImpl implements Manager { long timestamp = System.currentTimeMillis(); messageBuilder.withTimestamp(timestamp); for (final var recipient : recipients) { - if (recipient instanceof RecipientIdentifier.Single) { - final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient); + if (recipient instanceof RecipientIdentifier.Single single) { + final var recipientId = resolveRecipient(single); final var result = sendHelper.sendMessage(messageBuilder, recipientId); - results.put(recipient, List.of(result)); + results.put(recipient, + List.of(SendMessageResult.from(result, + account.getRecipientStore(), + account.getRecipientStore()::resolveRecipientAddress))); } else if (recipient instanceof RecipientIdentifier.NoteToSelf) { final var result = sendHelper.sendSelfMessage(messageBuilder); - results.put(recipient, List.of(result)); - } else if (recipient instanceof RecipientIdentifier.Group) { - final var groupId = ((RecipientIdentifier.Group) recipient).groupId; - final var result = sendHelper.sendAsGroupMessage(messageBuilder, groupId); - results.put(recipient, result); + results.put(recipient, + List.of(SendMessageResult.from(result, + account.getRecipientStore(), + account.getRecipientStore()::resolveRecipientAddress))); + } else if (recipient instanceof RecipientIdentifier.Group group) { + final var result = sendHelper.sendAsGroupMessage(messageBuilder, group.groupId()); + results.put(recipient, + result.stream() + .map(sendMessageResult -> SendMessageResult.from(sendMessageResult, + account.getRecipientStore(), + account.getRecipientStore()::resolveRecipientAddress)) + .collect(Collectors.toList())); } } return new SendMessageResults(timestamp, results); @@ -591,7 +626,7 @@ public class ManagerImpl implements Manager { final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient); sendHelper.sendTypingMessage(message, recipientId); } else if (recipient instanceof RecipientIdentifier.Group) { - final var groupId = ((RecipientIdentifier.Group) recipient).groupId; + final var groupId = ((RecipientIdentifier.Group) recipient).groupId(); final var message = new SignalServiceTypingMessage(action, timestamp, Optional.of(groupId.serialize())); sendHelper.sendGroupTypingMessage(message, groupId); } @@ -639,8 +674,8 @@ public class ManagerImpl implements Manager { private void applyMessage( final SignalServiceDataMessage.Builder messageBuilder, final Message message ) throws AttachmentInvalidException, IOException { - messageBuilder.withBody(message.getMessageText()); - final var attachments = message.getAttachments(); + messageBuilder.withBody(message.messageText()); + final var attachments = message.attachments(); if (attachments != null) { messageBuilder.withAttachments(attachmentHelper.uploadAttachments(attachments)); } @@ -692,7 +727,7 @@ public class ManagerImpl implements Manager { @Override public void setContactName( RecipientIdentifier.Single recipient, String name - ) throws NotMasterDeviceException, UnregisteredUserException { + ) throws NotMasterDeviceException, IOException { if (!account.isMasterDevice()) { throw new NotMasterDeviceException(); } @@ -794,22 +829,22 @@ public class ManagerImpl implements Manager { return resolveRecipientTrusted(new SignalServiceAddress(uuid, number)); } - private UUID getRegisteredUser(final String number) throws IOException { - final Map uuidMap; + private ACI getRegisteredUser(final String number) throws IOException { + final Map aciMap; try { - uuidMap = getRegisteredUsers(Set.of(number)); + aciMap = getRegisteredUsers(Set.of(number)); } catch (NumberFormatException e) { - throw new UnregisteredUserException(number, e); + throw new IOException(number, e); } - final var uuid = uuidMap.get(number); + final var uuid = aciMap.get(number); if (uuid == null) { - throw new UnregisteredUserException(number, null); + throw new IOException(number, null); } return uuid; } - private Map getRegisteredUsers(final Set numbers) throws IOException { - final Map registeredUsers; + private Map getRegisteredUsers(final Set numbers) throws IOException { + final Map registeredUsers; try { registeredUsers = dependencies.getAccountManager() .getRegisteredUsers(ServiceConfig.getIasKeyStore(), @@ -819,16 +854,16 @@ public class ManagerImpl implements Manager { throw new IOException(e); } - // Store numbers as recipients so we have the number/uuid association - registeredUsers.forEach((number, uuid) -> resolveRecipientTrusted(new SignalServiceAddress(uuid, number))); + // Store numbers as recipients, so we have the number/uuid association + registeredUsers.forEach((number, aci) -> resolveRecipientTrusted(new SignalServiceAddress(aci, number))); return registeredUsers; } - private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) { + private void retryFailedReceivedMessages(ReceiveMessageHandler handler) { Set queuedActions = new HashSet<>(); for (var cachedMessage : account.getMessageCache().getCachedMessages()) { - var actions = retryFailedReceivedMessage(handler, ignoreAttachments, cachedMessage); + var actions = retryFailedReceivedMessage(handler, cachedMessage); if (actions != null) { queuedActions.addAll(actions); } @@ -837,7 +872,7 @@ public class ManagerImpl implements Manager { } private List retryFailedReceivedMessage( - final ReceiveMessageHandler handler, final boolean ignoreAttachments, final CachedMessage cachedMessage + final ReceiveMessageHandler handler, final CachedMessage cachedMessage ) { var envelope = cachedMessage.loadEnvelope(); if (envelope == null) { @@ -873,14 +908,125 @@ public class ManagerImpl implements Manager { } @Override - public void receiveMessages( - long timeout, - TimeUnit unit, - boolean returnOnTimeout, - boolean ignoreAttachments, - ReceiveMessageHandler handler + public void addReceiveHandler(final ReceiveMessageHandler handler, final boolean isWeakListener) { + if (isReceivingSynchronous) { + throw new IllegalStateException("Already receiving message synchronously."); + } + synchronized (messageHandlers) { + if (isWeakListener) { + weakHandlers.add(handler); + } else { + messageHandlers.add(handler); + startReceiveThreadIfRequired(); + } + } + } + + private void startReceiveThreadIfRequired() { + if (receiveThread != null) { + return; + } + receiveThread = new Thread(() -> { + logger.debug("Starting receiving messages"); + while (!Thread.interrupted()) { + try { + receiveMessagesInternal(1L, TimeUnit.HOURS, false, (envelope, e) -> { + synchronized (messageHandlers) { + Stream.concat(messageHandlers.stream(), weakHandlers.stream()).forEach(h -> { + try { + h.handleMessage(envelope, e); + } catch (Exception ex) { + logger.warn("Message handler failed, ignoring", ex); + } + }); + } + }); + break; + } catch (IOException e) { + logger.warn("Receiving messages failed, retrying", e); + } + } + logger.debug("Finished receiving messages"); + hasCaughtUpWithOldMessages = false; + synchronized (messageHandlers) { + receiveThread = null; + + // Check if in the meantime another handler has been registered + if (!messageHandlers.isEmpty()) { + logger.debug("Another handler has been registered, starting receive thread again"); + startReceiveThreadIfRequired(); + } + } + }); + + receiveThread.start(); + } + + @Override + public void removeReceiveHandler(final ReceiveMessageHandler handler) { + final Thread thread; + synchronized (messageHandlers) { + weakHandlers.remove(handler); + messageHandlers.remove(handler); + if (!messageHandlers.isEmpty() || receiveThread == null || isReceivingSynchronous) { + return; + } + thread = receiveThread; + receiveThread = null; + } + + stopReceiveThread(thread); + } + + private void stopReceiveThread(final Thread thread) { + thread.interrupt(); + try { + thread.join(); + } catch (InterruptedException ignored) { + } + } + + @Override + public boolean isReceiving() { + if (isReceivingSynchronous) { + return true; + } + synchronized (messageHandlers) { + return messageHandlers.size() > 0; + } + } + + @Override + public void receiveMessages(long timeout, TimeUnit unit, ReceiveMessageHandler handler) throws IOException { + receiveMessages(timeout, unit, true, handler); + } + + @Override + public void receiveMessages(ReceiveMessageHandler handler) throws IOException { + receiveMessages(1L, TimeUnit.HOURS, false, handler); + } + + private void receiveMessages( + long timeout, TimeUnit unit, boolean returnOnTimeout, ReceiveMessageHandler handler + ) throws IOException { + if (isReceiving()) { + throw new IllegalStateException("Already receiving message."); + } + isReceivingSynchronous = true; + receiveThread = Thread.currentThread(); + try { + receiveMessagesInternal(timeout, unit, returnOnTimeout, handler); + } finally { + receiveThread = null; + hasCaughtUpWithOldMessages = false; + isReceivingSynchronous = false; + } + } + + private void receiveMessagesInternal( + long timeout, TimeUnit unit, boolean returnOnTimeout, ReceiveMessageHandler handler ) throws IOException { - retryFailedReceivedMessages(handler, ignoreAttachments); + retryFailedReceivedMessages(handler); Set queuedActions = new HashSet<>(); @@ -888,6 +1034,8 @@ public class ManagerImpl implements Manager { signalWebSocket.connect(); hasCaughtUpWithOldMessages = false; + var backOffCounter = 0; + final var MAX_BACKOFF_COUNTER = 9; while (!Thread.interrupted()) { SignalServiceEnvelope envelope; @@ -902,6 +1050,8 @@ public class ManagerImpl implements Manager { // store message on disk, before acknowledging receipt to the server cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId); }); + backOffCounter = 0; + if (result.isPresent()) { envelope = result.get(); logger.debug("New message received from server"); @@ -925,11 +1075,24 @@ public class ManagerImpl implements Manager { } else { throw e; } - } catch (WebSocketUnavailableException e) { - logger.debug("Pipe unexpectedly unavailable, connecting"); - signalWebSocket.connect(); - continue; + } catch (IOException e) { + logger.debug("Pipe unexpectedly unavailable: {}", e.getMessage()); + if (e instanceof WebSocketUnavailableException || "Connection closed!".equals(e.getMessage())) { + final var sleepMilliseconds = 100 * (long) Math.pow(2, backOffCounter); + backOffCounter = Math.min(backOffCounter + 1, MAX_BACKOFF_COUNTER); + logger.warn("Connection closed unexpectedly, reconnecting in {} ms", sleepMilliseconds); + try { + Thread.sleep(sleepMilliseconds); + } catch (InterruptedException interruptedException) { + return; + } + hasCaughtUpWithOldMessages = false; + signalWebSocket.connect(); + continue; + } + throw e; } catch (TimeoutException e) { + backOffCounter = 0; if (returnOnTimeout) return; continue; } @@ -940,9 +1103,11 @@ public class ManagerImpl implements Manager { if (hasCaughtUpWithOldMessages) { handleQueuedActions(queuedActions); + queuedActions.clear(); } if (cachedMessage[0] != null) { if (exception instanceof UntrustedIdentityException) { + logger.debug("Keeping message with untrusted identity in message cache"); final var address = ((UntrustedIdentityException) exception).getSender(); final var recipientId = resolveRecipient(address); if (!envelope.hasSourceUuid()) { @@ -959,6 +1124,13 @@ public class ManagerImpl implements Manager { } } handleQueuedActions(queuedActions); + queuedActions.clear(); + dependencies.getSignalWebSocket().disconnect(); + } + + @Override + public void setIgnoreAttachments(final boolean ignoreAttachments) { + this.ignoreAttachments = ignoreAttachments; } @Override @@ -967,6 +1139,7 @@ public class ManagerImpl implements Manager { } private void handleQueuedActions(final Collection queuedActions) { + logger.debug("Handling message actions"); var interrupted = false; for (var action : queuedActions) { try { @@ -990,17 +1163,12 @@ public class ManagerImpl implements Manager { final RecipientId recipientId; try { recipientId = resolveRecipient(recipient); - } catch (UnregisteredUserException e) { + } catch (IOException e) { return false; } return contactHelper.isContactBlocked(recipientId); } - @Override - public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) { - return attachmentHelper.getAttachmentFile(attachmentId); - } - @Override public void sendContacts() throws IOException { syncHelper.sendContacts(); @@ -1020,7 +1188,7 @@ public class ManagerImpl implements Manager { final RecipientId recipientId; try { recipientId = resolveRecipient(recipient); - } catch (UnregisteredUserException e) { + } catch (IOException e) { return null; } @@ -1042,7 +1210,7 @@ public class ManagerImpl implements Manager { return toGroup(groupHelper.getGroup(groupId)); } - public GroupInfo getGroupInfo(GroupId groupId) { + private GroupInfo getGroupInfo(GroupId groupId) { return groupHelper.getGroup(groupId); } @@ -1061,10 +1229,12 @@ public class ManagerImpl implements Manager { } final var address = account.getRecipientStore().resolveRecipientAddress(identityInfo.getRecipientId()); + final var scannableFingerprint = identityHelper.computeSafetyNumberForScanning(identityInfo.getRecipientId(), + identityInfo.getIdentityKey()); return new Identity(address, identityInfo.getIdentityKey(), - computeSafetyNumber(address.toSignalServiceAddress(), identityInfo.getIdentityKey()), - computeSafetyNumberForScanning(address.toSignalServiceAddress(), identityInfo.getIdentityKey()), + identityHelper.computeSafetyNumber(identityInfo.getRecipientId(), identityInfo.getIdentityKey()), + scannableFingerprint == null ? null : scannableFingerprint.getSerialized(), identityInfo.getTrustLevel(), identityInfo.getDateAdded()); } @@ -1074,7 +1244,7 @@ public class ManagerImpl implements Manager { IdentityInfo identity; try { identity = account.getIdentityKeyStore().getIdentity(resolveRecipient(recipient)); - } catch (UnregisteredUserException e) { + } catch (IOException e) { identity = null; } return identity == null ? List.of() : List.of(toIdentity(identity)); @@ -1091,12 +1261,10 @@ public class ManagerImpl implements Manager { RecipientId recipientId; try { recipientId = resolveRecipient(recipient); - } catch (UnregisteredUserException e) { + } catch (IOException e) { return false; } - return trustIdentity(recipientId, - identityKey -> Arrays.equals(identityKey.serialize(), fingerprint), - TrustLevel.TRUSTED_VERIFIED); + return identityHelper.trustIdentityVerified(recipientId, fingerprint); } /** @@ -1110,13 +1278,10 @@ public class ManagerImpl implements Manager { RecipientId recipientId; try { recipientId = resolveRecipient(recipient); - } catch (UnregisteredUserException e) { + } catch (IOException e) { return false; } - var address = resolveSignalServiceAddress(recipientId); - return trustIdentity(recipientId, - identityKey -> safetyNumber.equals(computeSafetyNumber(address, identityKey)), - TrustLevel.TRUSTED_VERIFIED); + return identityHelper.trustIdentityVerifiedSafetyNumber(recipientId, safetyNumber); } /** @@ -1130,18 +1295,10 @@ public class ManagerImpl implements Manager { RecipientId recipientId; try { recipientId = resolveRecipient(recipient); - } catch (UnregisteredUserException e) { + } catch (IOException e) { return false; } - var address = resolveSignalServiceAddress(recipientId); - return trustIdentity(recipientId, identityKey -> { - final var fingerprint = computeSafetyNumberFingerprint(address, identityKey); - try { - return fingerprint != null && fingerprint.getScannableFingerprint().compareTo(safetyNumber); - } catch (FingerprintVersionMismatchException | FingerprintParsingException e) { - return false; - } - }, TrustLevel.TRUSTED_VERIFIED); + return identityHelper.trustIdentityVerifiedSafetyNumber(recipientId, safetyNumber); } /** @@ -1154,74 +1311,17 @@ public class ManagerImpl implements Manager { RecipientId recipientId; try { recipientId = resolveRecipient(recipient); - } catch (UnregisteredUserException e) { - return false; - } - return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED); - } - - private boolean trustIdentity( - RecipientId recipientId, Function verifier, TrustLevel trustLevel - ) { - var identity = account.getIdentityKeyStore().getIdentity(recipientId); - if (identity == null) { - return false; - } - - if (!verifier.apply(identity.getIdentityKey())) { - return false; - } - - account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel); - try { - var address = resolveSignalServiceAddress(recipientId); - syncHelper.sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel); } catch (IOException e) { - logger.warn("Failed to send verification sync message: {}", e.getMessage()); + return false; } - - return true; + return identityHelper.trustIdentityAllKeys(recipientId); } private void handleIdentityFailure( - final RecipientId recipientId, final SendMessageResult.IdentityFailure identityFailure + final RecipientId recipientId, + final org.whispersystems.signalservice.api.messages.SendMessageResult.IdentityFailure identityFailure ) { - final var identityKey = identityFailure.getIdentityKey(); - if (identityKey != null) { - final var newIdentity = account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date()); - if (newIdentity) { - account.getSessionStore().archiveSessions(recipientId); - } - } else { - // Retrieve profile to get the current identity key from the server - profileHelper.refreshRecipientProfile(recipientId); - } - } - - @Override - public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) { - final Fingerprint fingerprint = computeSafetyNumberFingerprint(theirAddress, theirIdentityKey); - return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText(); - } - - private byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) { - final Fingerprint fingerprint = computeSafetyNumberFingerprint(theirAddress, theirIdentityKey); - return fingerprint == null ? null : fingerprint.getScannableFingerprint().getSerialized(); - } - - private Fingerprint computeSafetyNumberFingerprint( - final SignalServiceAddress theirAddress, final IdentityKey theirIdentityKey - ) { - return Utils.computeSafetyNumber(capabilities.isUuid(), - account.getSelfAddress(), - account.getIdentityKeyPair().getPublicKey(), - theirAddress, - theirIdentityKey); - } - - @Override - public SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address) { - return resolveSignalServiceAddress(resolveRecipient(address)); + this.identityHelper.handleIdentityFailure(recipientId, identityFailure); } private SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) { @@ -1233,18 +1333,18 @@ public class ManagerImpl implements Manager { // Address in recipient store doesn't have a uuid, this shouldn't happen // Try to retrieve the uuid from the server final var number = address.getNumber().get(); - final UUID uuid; + final ACI aci; try { - uuid = getRegisteredUser(number); + aci = getRegisteredUser(number); } catch (IOException e) { logger.warn("Failed to get uuid for e164 number: {}", number, e); // Return SignalServiceAddress with unknown UUID return address.toSignalServiceAddress(); } - return resolveSignalServiceAddress(account.getRecipientStore().resolveRecipient(uuid)); + return resolveSignalServiceAddress(account.getRecipientStore().resolveRecipient(aci)); } - private Set resolveRecipients(Collection recipients) throws UnregisteredUserException { + private Set resolveRecipients(Collection recipients) throws IOException { final var recipientIds = new HashSet(recipients.size()); for (var number : recipients) { final var recipientId = resolveRecipient(number); @@ -1253,11 +1353,11 @@ public class ManagerImpl implements Manager { return recipientIds; } - private RecipientId resolveRecipient(final RecipientIdentifier.Single recipient) throws UnregisteredUserException { - if (recipient instanceof RecipientIdentifier.Uuid) { - return account.getRecipientStore().resolveRecipient(((RecipientIdentifier.Uuid) recipient).uuid); + private RecipientId resolveRecipient(final RecipientIdentifier.Single recipient) throws IOException { + if (recipient instanceof RecipientIdentifier.Uuid uuidRecipient) { + return account.getRecipientStore().resolveRecipient(ACI.from(uuidRecipient.uuid())); } else { - final var number = ((RecipientIdentifier.Number) recipient).number; + final var number = ((RecipientIdentifier.Number) recipient).number(); return account.getRecipientStore().resolveRecipient(number, () -> { try { return getRegisteredUser(number); @@ -1268,6 +1368,10 @@ public class ManagerImpl implements Manager { } } + private RecipientId resolveRecipient(RecipientAddress address) { + return account.getRecipientStore().resolveRecipient(address); + } + private RecipientId resolveRecipient(SignalServiceAddress address) { return account.getRecipientStore().resolveRecipient(address); } @@ -1282,6 +1386,16 @@ public class ManagerImpl implements Manager { } private void close(boolean closeAccount) throws IOException { + Thread thread; + synchronized (messageHandlers) { + weakHandlers.clear(); + messageHandlers.clear(); + thread = receiveThread; + receiveThread = null; + } + if (thread != null) { + stopReceiveThread(thread); + } executor.shutdown(); dependencies.getSignalWebSocket().disconnect(); @@ -1291,5 +1405,4 @@ public class ManagerImpl implements Manager { } account = null; } - }