X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/32818a8608f5bddc46ad5c7dc442f509c939791c..fa5c09d23b830f2999a52421c189a3e4661da99f:/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 dd7d6bee..81c18683 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -17,6 +17,7 @@ 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; @@ -74,6 +75,7 @@ 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.util.DeviceNameUtil; import org.whispersystems.signalservice.api.util.InvalidNumberException; @@ -93,6 +95,7 @@ import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.security.SignatureException; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -106,6 +109,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.asamk.signal.manager.config.ServiceConfig.capabilities; @@ -137,7 +141,9 @@ public class ManagerImpl implements Manager { private boolean ignoreAttachments = false; private Thread receiveThread; + private final Set weakHandlers = new HashSet<>(); private final Set messageHandlers = new HashSet<>(); + private final List closedListeners = new ArrayList<>(); private boolean isReceivingSynchronous; ManagerImpl( @@ -149,8 +155,8 @@ public class ManagerImpl implements Manager { this.account = account; this.serviceEnvironmentConfig = serviceEnvironmentConfig; - final var credentialsProvider = new DynamicCredentialsProvider(account.getUuid(), - account.getUsername(), + final var credentialsProvider = new DynamicCredentialsProvider(account.getAci(), + account.getAccount(), account.getPassword(), account.getDeviceId()); final var sessionLock = new SignalSessionLock() { @@ -245,7 +251,7 @@ public class ManagerImpl implements Manager { @Override public String getSelfNumber() { - return account.getUsername(); + return account.getAccount(); } @Override @@ -262,8 +268,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); } @@ -279,7 +285,7 @@ public class ManagerImpl implements Manager { public Map> areUsersRegistered(Set numbers) throws IOException { Map canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> { try { - return PhoneNumberFormatter.formatNumber(n, account.getUsername()); + return PhoneNumberFormatter.formatNumber(n, account.getAccount()); } catch (InvalidNumberException e) { return ""; } @@ -293,8 +299,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()); })); } @@ -321,29 +327,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(); } @@ -375,6 +387,7 @@ public class ManagerImpl implements Manager { dependencies.getAccountManager().setGcmId(Optional.absent()); account.setRegistered(false); + close(); } @Override @@ -389,10 +402,13 @@ public class ManagerImpl implements Manager { dependencies.getAccountManager().deleteAccount(); account.setRegistered(false); + close(); } @Override public void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException { + captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); + dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha); } @@ -667,6 +683,16 @@ public class ManagerImpl implements Manager { if (attachments != null) { messageBuilder.withAttachments(attachmentHelper.uploadAttachments(attachments)); } + if (message.mentions().size() > 0) { + final var mentions = new ArrayList(); + for (final var m : message.mentions()) { + final var recipientId = resolveRecipient(m.recipient()); + mentions.add(new SignalServiceDataMessage.Mention(resolveSignalServiceAddress(recipientId).getAci(), + m.start(), + m.length())); + } + messageBuilder.withMentions(mentions); + } } @Override @@ -817,22 +843,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 IOException(number, e); } - final var uuid = uuidMap.get(number); + final var uuid = aciMap.get(number); if (uuid == 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(), @@ -842,8 +868,8 @@ 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; } @@ -896,14 +922,17 @@ public class ManagerImpl implements Manager { } @Override - public void addReceiveHandler(final ReceiveMessageHandler handler) { + public void addReceiveHandler(final ReceiveMessageHandler handler, final boolean isWeakListener) { if (isReceivingSynchronous) { throw new IllegalStateException("Already receiving message synchronously."); } synchronized (messageHandlers) { - messageHandlers.add(handler); - - startReceiveThreadIfRequired(); + if (isWeakListener) { + weakHandlers.add(handler); + } else { + messageHandlers.add(handler); + startReceiveThreadIfRequired(); + } } } @@ -912,17 +941,18 @@ public class ManagerImpl implements Manager { return; } receiveThread = new Thread(() -> { + logger.debug("Starting receiving messages"); while (!Thread.interrupted()) { try { receiveMessagesInternal(1L, TimeUnit.HOURS, false, (envelope, e) -> { synchronized (messageHandlers) { - for (ReceiveMessageHandler h : 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; @@ -930,12 +960,14 @@ public class ManagerImpl implements Manager { 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(); } } @@ -948,12 +980,13 @@ public class ManagerImpl implements Manager { public void removeReceiveHandler(final ReceiveMessageHandler handler) { final Thread thread; synchronized (messageHandlers) { - thread = receiveThread; - receiveThread = null; + weakHandlers.remove(handler); messageHandlers.remove(handler); - if (!messageHandlers.isEmpty() || isReceivingSynchronous) { + if (!messageHandlers.isEmpty() || receiveThread == null || isReceivingSynchronous) { return; } + thread = receiveThread; + receiveThread = null; } stopReceiveThread(thread); @@ -1009,7 +1042,8 @@ public class ManagerImpl implements Manager { ) throws IOException { retryFailedReceivedMessages(handler); - Set queuedActions = new HashSet<>(); + // Use a Map here because java Set doesn't have a get method ... + Map queuedActions = new HashMap<>(); final var signalWebSocket = dependencies.getSignalWebSocket(); signalWebSocket.connect(); @@ -1021,7 +1055,10 @@ public class ManagerImpl implements Manager { while (!Thread.interrupted()) { SignalServiceEnvelope envelope; final CachedMessage[] cachedMessage = {null}; - account.setLastReceiveTimestamp(System.currentTimeMillis()); + final var nowMillis = System.currentTimeMillis(); + if (nowMillis - account.getLastReceiveTimestamp() > 60000) { + account.setLastReceiveTimestamp(nowMillis); + } logger.debug("Checking for new message from server"); try { var result = signalWebSocket.readOrEmpty(unit.toMillis(timeout), envelope1 -> { @@ -1038,7 +1075,7 @@ public class ManagerImpl implements Manager { logger.debug("New message received from server"); } else { logger.debug("Received indicator that server queue is empty"); - handleQueuedActions(queuedActions); + handleQueuedActions(queuedActions.keySet()); queuedActions.clear(); hasCaughtUpWithOldMessages = true; @@ -1079,11 +1116,18 @@ public class ManagerImpl implements Manager { } final var result = incomingMessageHandler.handleEnvelope(envelope, ignoreAttachments, handler); - queuedActions.addAll(result.first()); + for (final var h : result.first()) { + final var existingAction = queuedActions.get(h); + if (existingAction == null) { + queuedActions.put(h, h); + } else { + existingAction.mergeOther(h); + } + } final var exception = result.second(); if (hasCaughtUpWithOldMessages) { - handleQueuedActions(queuedActions); + handleQueuedActions(queuedActions.keySet()); queuedActions.clear(); } if (cachedMessage[0] != null) { @@ -1104,8 +1148,9 @@ public class ManagerImpl implements Manager { } } } - handleQueuedActions(queuedActions); + handleQueuedActions(queuedActions.keySet()); queuedActions.clear(); + dependencies.getSignalWebSocket().disconnect(); } @Override @@ -1233,7 +1278,7 @@ public class ManagerImpl implements Manager { /** * Trust this the identity with this fingerprint * - * @param recipient username of the identity + * @param recipient account of the identity * @param fingerprint Fingerprint */ @Override @@ -1250,7 +1295,7 @@ public class ManagerImpl implements Manager { /** * Trust this the identity with this safety number * - * @param recipient username of the identity + * @param recipient account of the identity * @param safetyNumber Safety number */ @Override @@ -1267,7 +1312,7 @@ public class ManagerImpl implements Manager { /** * Trust this the identity with this scannable safety number * - * @param recipient username of the identity + * @param recipient account of the identity * @param safetyNumber Scannable safety number */ @Override @@ -1284,7 +1329,7 @@ public class ManagerImpl implements Manager { /** * Trust all keys of this identity without verification * - * @param recipient username of the identity + * @param recipient account of the identity */ @Override public boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) { @@ -1297,6 +1342,13 @@ public class ManagerImpl implements Manager { return identityHelper.trustIdentityAllKeys(recipientId); } + @Override + public void addClosedListener(final Runnable listener) { + synchronized (closedListeners) { + closedListeners.add(listener); + } + } + private void handleIdentityFailure( final RecipientId recipientId, final org.whispersystems.signalservice.api.messages.SendMessageResult.IdentityFailure identityFailure @@ -1313,15 +1365,15 @@ 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 IOException { @@ -1334,8 +1386,8 @@ public class ManagerImpl implements Manager { } private RecipientId resolveRecipient(final RecipientIdentifier.Single recipient) throws IOException { - if (recipient instanceof RecipientIdentifier.Uuid) { - return account.getRecipientStore().resolveRecipient(((RecipientIdentifier.Uuid) recipient).uuid()); + if (recipient instanceof RecipientIdentifier.Uuid uuidRecipient) { + return account.getRecipientStore().resolveRecipient(ACI.from(uuidRecipient.uuid())); } else { final var number = ((RecipientIdentifier.Number) recipient).number(); return account.getRecipientStore().resolveRecipient(number, () -> { @@ -1362,12 +1414,9 @@ public class ManagerImpl implements Manager { @Override public void close() throws IOException { - close(true); - } - - private void close(boolean closeAccount) throws IOException { Thread thread; synchronized (messageHandlers) { + weakHandlers.clear(); messageHandlers.clear(); thread = receiveThread; receiveThread = null; @@ -1379,7 +1428,12 @@ public class ManagerImpl implements Manager { dependencies.getSignalWebSocket().disconnect(); - if (closeAccount && account != null) { + synchronized (closedListeners) { + closedListeners.forEach(Runnable::run); + closedListeners.clear(); + } + + if (account != null) { account.close(); } account = null;