X-Git-Url: https://git.nmode.ca/signal-cli/blobdiff_plain/a3c5cfd2f49df3663a221ca3544adaf1ad3d705f..22add1cbee357e988ca9c85d9d161fc783fd5fa3:/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 a080b185..fa041c55 100644 --- a/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/ManagerImpl.java @@ -16,6 +16,7 @@ */ package org.asamk.signal.manager; +import org.asamk.signal.manager.api.AttachmentInvalidException; import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Device; import org.asamk.signal.manager.api.Group; @@ -24,6 +25,7 @@ import org.asamk.signal.manager.api.InactiveGroupLinkException; import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.InvalidStickerException; import org.asamk.signal.manager.api.Message; +import org.asamk.signal.manager.api.NotMasterDeviceException; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.SendGroupMessageResults; @@ -31,6 +33,7 @@ import org.asamk.signal.manager.api.SendMessageResult; import org.asamk.signal.manager.api.SendMessageResults; import org.asamk.signal.manager.api.StickerPack; import org.asamk.signal.manager.api.StickerPackId; +import org.asamk.signal.manager.api.StickerPackInvalidException; import org.asamk.signal.manager.api.StickerPackUrl; import org.asamk.signal.manager.api.TypingAction; import org.asamk.signal.manager.api.UnregisteredRecipientException; @@ -42,6 +45,7 @@ import org.asamk.signal.manager.groups.GroupNotFoundException; import org.asamk.signal.manager.groups.GroupSendingNotAllowedException; import org.asamk.signal.manager.groups.LastGroupAdminException; import org.asamk.signal.manager.groups.NotAGroupMemberException; +import org.asamk.signal.manager.helper.AccountFileUpdater; import org.asamk.signal.manager.helper.Context; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.groups.GroupInfo; @@ -50,21 +54,22 @@ import org.asamk.signal.manager.storage.recipients.Contact; import org.asamk.signal.manager.storage.recipients.Profile; import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack; +import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore; import org.asamk.signal.manager.storage.stickers.Sticker; import org.asamk.signal.manager.util.AttachmentUtils; import org.asamk.signal.manager.util.KeyUtils; import org.asamk.signal.manager.util.StickerUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; 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.util.DeviceNameUtil; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; -import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; import org.whispersystems.signalservice.internal.util.Hex; import org.whispersystems.signalservice.internal.util.Util; @@ -77,10 +82,12 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; import java.util.stream.Collectors; @@ -88,11 +95,12 @@ import java.util.stream.Stream; import io.reactivex.rxjava3.disposables.CompositeDisposable; -public class ManagerImpl implements Manager { +class ManagerImpl implements Manager { private final static Logger logger = LoggerFactory.getLogger(ManagerImpl.class); private SignalAccount account; + private AccountFileUpdater accountFileUpdater; private final SignalDependencies dependencies; private final Context context; @@ -103,20 +111,19 @@ public class ManagerImpl implements Manager { private final Set weakHandlers = new HashSet<>(); private final Set messageHandlers = new HashSet<>(); private final List closedListeners = new ArrayList<>(); + private final List addressChangedListeners = new ArrayList<>(); private final CompositeDisposable disposable = new CompositeDisposable(); ManagerImpl( SignalAccount account, PathConfig pathConfig, + AccountFileUpdater accountFileUpdater, ServiceEnvironmentConfig serviceEnvironmentConfig, String userAgent ) { this.account = account; + this.accountFileUpdater = accountFileUpdater; - final var credentialsProvider = new DynamicCredentialsProvider(account.getAci(), - account.getAccount(), - account.getPassword(), - account.getDeviceId()); final var sessionLock = new SignalSessionLock() { private final ReentrantLock LEGACY_LOCK = new ReentrantLock(); @@ -128,15 +135,28 @@ public class ManagerImpl implements Manager { }; this.dependencies = new SignalDependencies(serviceEnvironmentConfig, userAgent, - credentialsProvider, - account.getSignalProtocolStore(), + account.getCredentialsProvider(), + account.getSignalServiceDataStore(), executor, sessionLock); final var avatarStore = new AvatarStore(pathConfig.avatarsPath()); final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath()); final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath()); - this.context = new Context(account, dependencies, avatarStore, attachmentStore, stickerPackStore); + this.context = new Context(account, new AccountFileUpdater() { + @Override + public void updateAccountIdentifiers(final String number, final ACI aci) { + accountFileUpdater.updateAccountIdentifiers(number, aci); + synchronized (addressChangedListeners) { + addressChangedListeners.forEach(Runnable::run); + } + } + + @Override + public void removeAccount() { + accountFileUpdater.removeAccount(); + } + }, dependencies, avatarStore, attachmentStore, stickerPackStore); this.context.getAccountHelper().setUnregisteredListener(this::close); this.context.getReceiveHelper().setAuthenticationFailureListener(this::close); this.context.getReceiveHelper().setCaughtUpWithOldMessagesListener(() -> { @@ -145,7 +165,7 @@ public class ManagerImpl implements Manager { } }); disposable.add(account.getIdentityKeyStore().getIdentityChanges().subscribe(recipientId -> { - logger.trace("Archiving old sessions"); + logger.trace("Archiving old sessions for {}", recipientId); account.getSessionStore().archiveSessions(recipientId); account.getSenderKeyStore().deleteSharedWith(recipientId); final var profile = account.getRecipientStore().getProfile(recipientId); @@ -162,11 +182,10 @@ public class ManagerImpl implements Manager { @Override public String getSelfNumber() { - return account.getAccount(); + return account.getNumber(); } - @Override - public void checkAccountState() throws IOException { + void checkAccountState() throws IOException { context.getAccountHelper().checkAccountState(); } @@ -174,7 +193,7 @@ public class ManagerImpl implements Manager { public Map> areUsersRegistered(Set numbers) throws IOException { final var canonicalizedNumbers = numbers.stream().collect(Collectors.toMap(n -> n, n -> { try { - final var canonicalizedNumber = PhoneNumberFormatter.formatNumber(n, account.getAccount()); + final var canonicalizedNumber = PhoneNumberFormatter.formatNumber(n, account.getNumber()); if (!canonicalizedNumber.equals(n)) { logger.debug("Normalized number {} to {}.", n, canonicalizedNumber); } @@ -238,14 +257,9 @@ public class ManagerImpl implements Manager { @Override public void setProfile( - String givenName, final String familyName, String about, String aboutEmoji, java.util.Optional avatar + String givenName, final String familyName, String about, String aboutEmoji, Optional avatar ) throws IOException { - context.getProfileHelper() - .setProfile(givenName, - familyName, - about, - aboutEmoji, - avatar == null ? null : Optional.fromNullable(avatar.orElse(null))); + context.getProfileHelper().setProfile(givenName, familyName, about, aboutEmoji, avatar); context.getSyncHelper().sendSyncFetchProfileMessage(); } @@ -270,7 +284,7 @@ public class ManagerImpl implements Manager { public List getLinkedDevices() throws IOException { var devices = dependencies.getAccountManager().getDevices(); account.setMultiDevice(devices.size() > 1); - var identityKey = account.getIdentityKeyPair().getPrivateKey(); + var identityKey = account.getAciIdentityKeyPair().getPrivateKey(); return devices.stream().map(d -> { String deviceName = d.getName(); if (deviceName != null) { @@ -289,7 +303,7 @@ public class ManagerImpl implements Manager { } @Override - public void removeLinkedDevices(long deviceId) throws IOException { + public void removeLinkedDevices(int deviceId) throws IOException { context.getAccountHelper().removeLinkedDevices(deviceId); } @@ -300,7 +314,7 @@ public class ManagerImpl implements Manager { } @Override - public void setRegistrationLockPin(java.util.Optional pin) throws IOException, NotMasterDeviceException { + public void setRegistrationLockPin(Optional pin) throws IOException, NotMasterDeviceException { if (!account.isMasterDevice()) { throw new NotMasterDeviceException(); } @@ -345,6 +359,11 @@ public class ManagerImpl implements Manager { @Override public void deleteGroup(GroupId groupId) throws IOException { + final var group = context.getGroupHelper().getGroup(groupId); + if (group.isMember(account.getSelfRecipientId())) { + throw new IOException( + "The local group information cannot be removed, as the user is still a member of the group"); + } context.getGroupHelper().deleteGroup(groupId); } @@ -378,6 +397,12 @@ public class ManagerImpl implements Manager { updateGroup.getRemoveAdmins() == null ? null : context.getRecipientHelper().resolveRecipients(updateGroup.getRemoveAdmins()), + updateGroup.getBanMembers() == null + ? null + : context.getRecipientHelper().resolveRecipients(updateGroup.getBanMembers()), + updateGroup.getUnbanMembers() == null + ? null + : context.getRecipientHelper().resolveRecipients(updateGroup.getUnbanMembers()), updateGroup.isResetGroupLink(), updateGroup.getGroupLinkState(), updateGroup.getAddMemberPermission(), @@ -434,7 +459,7 @@ public class ManagerImpl implements Manager { final var timestamp = System.currentTimeMillis(); for (var recipient : recipients) { if (recipient instanceof RecipientIdentifier.Single single) { - final var message = new SignalServiceTypingMessage(action, timestamp, Optional.absent()); + final var message = new SignalServiceTypingMessage(action, timestamp, Optional.empty()); try { final var recipientId = context.getRecipientHelper().resolveRecipient(single); final var result = context.getSendHelper().sendTypingMessage(message, recipientId); @@ -503,6 +528,11 @@ public class ManagerImpl implements Manager { public SendMessageResults sendMessage( Message message, Set recipients ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException { + final var selfProfile = context.getProfileHelper().getSelfProfile(); + if (selfProfile == null || selfProfile.getDisplayName().isEmpty()) { + logger.warn( + "No profile name set. When sending a message it's recommended to set a profile name wit the updateProfile command. This may become mandatory in the future."); + } final var messageBuilder = SignalServiceDataMessage.newBuilder(); applyMessage(messageBuilder, message); return sendMessage(messageBuilder, recipients); @@ -526,7 +556,8 @@ public class ManagerImpl implements Manager { .resolveSignalServiceAddress(context.getRecipientHelper().resolveRecipient(quote.author())), quote.message(), List.of(), - resolveMentions(quote.mentions()))); + resolveMentions(quote.mentions()), + SignalServiceDataMessage.Quote.Type.NORMAL)); } if (message.sticker().isPresent()) { final var sticker = message.sticker().get(); @@ -550,7 +581,7 @@ public class ManagerImpl implements Manager { stickerPack.getPackKey(), stickerId, manifestSticker.emoji(), - AttachmentUtils.createAttachment(streamDetails, Optional.absent()))); + AttachmentUtils.createAttachment(streamDetails, Optional.empty()))); } } @@ -560,7 +591,7 @@ public class ManagerImpl implements Manager { final var recipientId = context.getRecipientHelper().resolveRecipient(m.recipient()); mentions.add(new SignalServiceDataMessage.Mention(context.getRecipientHelper() .resolveSignalServiceAddress(recipientId) - .getAci(), m.start(), m.length())); + .getServiceId(), m.start(), m.length())); } return mentions; } @@ -571,6 +602,17 @@ public class ManagerImpl implements Manager { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); + for (final var recipient : recipients) { + if (recipient instanceof RecipientIdentifier.Single r) { + try { + final var recipientId = context.getRecipientHelper().resolveRecipient(r); + account.getMessageSendLogStore().deleteEntryForRecipientNonGroup(targetSentTimestamp, recipientId); + } catch (UnregisteredRecipientException ignored) { + } + } else if (recipient instanceof RecipientIdentifier.Group r) { + account.getMessageSendLogStore().deleteEntryForGroup(targetSentTimestamp, r.groupId()); + } + } return sendMessage(messageBuilder, recipients); } @@ -700,7 +742,7 @@ public class ManagerImpl implements Manager { pack.isInstalled(), manifest.title(), manifest.author(), - java.util.Optional.ofNullable(manifest.cover() == null ? null : manifest.cover().toApi()), + Optional.ofNullable(manifest.cover() == null ? null : manifest.cover().toApi()), manifest.stickers().stream().map(JsonStickerPack.JsonSticker::toApi).toList()); } catch (Exception e) { logger.warn("Failed to read local sticker pack manifest: {}", e.getMessage(), e); @@ -738,30 +780,25 @@ public class ManagerImpl implements Manager { } } + private static final AtomicInteger threadNumber = new AtomicInteger(0); + private void startReceiveThreadIfRequired() { if (receiveThread != null) { return; } receiveThread = new Thread(() -> { logger.debug("Starting receiving messages"); - while (!Thread.interrupted()) { - try { - context.getReceiveHelper().receiveMessages(Duration.ofMinutes(1), 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); - } - }); + context.getReceiveHelper().receiveMessagesContinuously((envelope, e) -> { + synchronized (messageHandlers) { + Stream.concat(messageHandlers.stream(), weakHandlers.stream()).forEach(h -> { + try { + h.handleMessage(envelope, e); + } catch (Throwable ex) { + logger.warn("Message handler failed, ignoring", ex); } }); - break; - } catch (IOException e) { - logger.warn("Receiving messages failed, retrying", e); } - } + }); logger.debug("Finished receiving messages"); synchronized (messageHandlers) { receiveThread = null; @@ -773,6 +810,7 @@ public class ManagerImpl implements Manager { } } }); + receiveThread.setName("receive-" + threadNumber.getAndIncrement()); receiveThread.start(); } @@ -794,7 +832,10 @@ public class ManagerImpl implements Manager { } private void stopReceiveThread(final Thread thread) { - thread.interrupt(); + if (context.getReceiveHelper().requestStopReceiveMessages()) { + logger.debug("Receive stop requested, interrupting read from server."); + thread.interrupt(); + } try { thread.join(); } catch (InterruptedException ignored) { @@ -977,6 +1018,13 @@ public class ManagerImpl implements Manager { return updated; } + @Override + public void addAddressChangedListener(final Runnable listener) { + synchronized (addressChangedListeners) { + addressChangedListeners.add(listener); + } + } + @Override public void addClosedListener(final Runnable listener) { synchronized (closedListeners) { @@ -1001,14 +1049,15 @@ public class ManagerImpl implements Manager { dependencies.getSignalWebSocket().disconnect(); disposable.dispose(); + if (account != null) { + account.close(); + } + synchronized (closedListeners) { closedListeners.forEach(Runnable::run); closedListeners.clear(); } - if (account != null) { - account.close(); - } account = null; } }