From 24069c8277cca7f8e94aa3f083980e85dca25269 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 15 Oct 2023 22:37:46 +0200 Subject: [PATCH 01/16] Update documentation --- README.md | 4 +-- man/signal-cli.1.adoc | 36 +++++++++++++++++++ .../asamk/signal/commands/SendCommand.java | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b546a62c..a254e267 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ See [latest version](https://github.com/AsamK/signal-cli/releases). ```sh export VERSION= -wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}"-Linux.tar.gz -sudo tar xf signal-cli-"${VERSION}"-Linux.tar.gz -C /opt +wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz +sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/ ``` diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index b3431341..2b545fa2 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -96,6 +96,7 @@ If the account was deleted (with --delete-account) it cannot be reactivated. *-v*, *--voice*:: The verification should be done over voice, not SMS. +Voice verification only works if an SMS verification has been attempted before. *--captcha*:: The captcha token, required if registration failed with a captcha required error. @@ -146,6 +147,38 @@ Can fix problems with receiving messages. *-n* NAME, *--device-name* NAME:: Set a new device name for the primary or linked device +=== startChangeNumber + +Change an account to a new phone number with SMS or voice verification. +Use the finishChangeNumber command to complete the verification. + +NUMBER:: +The new phone number. + +*-v*, *--voice*:: +The verification should be done over voice, not SMS. +Voice verification only works if an SMS verification has been attempted before. + +*--captcha*:: +The captcha token, required if registration failed with a captcha required error. +To get the token, go to https://signalcaptchas.org/registration/generate.html +For the staging environment, use: https://signalcaptchas.org/staging/registration/generate.html +Check the developer tools for a redirect starting with signalcaptcha:// Everything after signalcaptcha:// is the captcha token. + +=== finishChangeNumber + +Verify the number using the code received via SMS or voice. + +NUMBER:: +The new phone number. + +*-v*, *--verification-code*:: +The verification code. + +*-p* PIN, *--pin* PIN:: +The registration lock PIN, that was set by the user. +Only required if a PIN was set. + === updateConfiguration Update signal configs and sync them to linked devices. @@ -272,6 +305,9 @@ Specify the mentions of the original message (same format as `--mention`). *--quote-text-style*:: Style parts of the original message text (same format as `--text-style`). +*--quote-attachment*:: +Specify the attachments of the original message (syntax: contentType[:filename[:previewFile]]), e.g. 'audio/aac' or 'image/png:test.png:/tmp/preview.jpg'. + *--preview-url*:: Specify the url for the link preview. The same url must also appear in the message body, otherwise the preview won't be displayed by the apps. diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 1c3d91ba..51f1fa1e 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -80,7 +80,7 @@ public class SendCommand implements JsonRpcLocalCommand { .help("Quote with mention of another group member (syntax: start:length:recipientNumber)"); subparser.addArgument("--quote-attachment") .nargs("*") - .help("Specify the attachments of the original message (syntax: contentType[:filename[:previewFile]], e.g. 'audio/aac' or 'image/png:test.png:/tmp/preview.jpg'."); + .help("Specify the attachments of the original message (syntax: contentType[:filename[:previewFile]]), e.g. 'audio/aac' or 'image/png:test.png:/tmp/preview.jpg'."); subparser.addArgument("--quote-text-style") .nargs("*") .help("Quote with style parts of the message text (syntax: start:length:STYLE)"); -- 2.51.0 From 505de39d2acabed2bffd396ab44219e3957c051d Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 16 Oct 2023 17:34:28 +0200 Subject: [PATCH 02/16] Use partial cdsi request only for a maximum of 3 numbers Fixes #1239 --- .../signal/manager/config/ServiceConfig.java | 1 + .../signal/manager/helper/RecipientHelper.java | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java index 7b2ba140..bf45f8cd 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceConfig.java @@ -29,6 +29,7 @@ public class ServiceConfig { public final static long AVATAR_DOWNLOAD_FAILSAFE_MAX_SIZE = 10 * 1024 * 1024; public final static boolean AUTOMATIC_NETWORK_RETRY = true; public final static int GROUP_MAX_SIZE = 1001; + public final static int MAXIMUM_ONE_OFF_REQUEST_SIZE = 3; private final static KeyStore iasKeyStore; diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java index 322bd032..133aab5b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java @@ -26,6 +26,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import static org.asamk.signal.manager.config.ServiceConfig.MAXIMUM_ONE_OFF_REQUEST_SIZE; + public class RecipientHelper { private final static Logger logger = LoggerFactory.getLogger(RecipientHelper.class); @@ -134,7 +136,14 @@ public class RecipientHelper { public Map getRegisteredUsers( final Set numbers ) throws IOException { - return getRegisteredUsers(numbers, true); + if (numbers.size() > MAXIMUM_ONE_OFF_REQUEST_SIZE) { + final var allNumbers = new HashSet<>(account.getRecipientStore().getAllNumbers()) {{ + addAll(numbers); + }}; + return getRegisteredUsers(allNumbers, false); + } else { + return getRegisteredUsers(numbers, true); + } } private Map getRegisteredUsers( @@ -174,7 +183,10 @@ public class RecipientHelper { logger.debug("No new numbers to query."); return Map.of(); } - logger.trace("Querying CDSI for {} new numbers ({} previous)", newNumbers.size(), previousNumbers.size()); + logger.trace("Querying CDSI for {} new numbers ({} previous), isPartialRefresh={}", + newNumbers.size(), + previousNumbers.size(), + isPartialRefresh); final var token = previousNumbers.isEmpty() ? Optional.empty() : Optional.ofNullable(account.getCdsiToken()); -- 2.51.0 From dd3326f03897839fe10103bf429c0ac77105bd0c Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 16 Oct 2023 17:43:03 +0200 Subject: [PATCH 03/16] Do a full recipients refresh every day --- .../signal/manager/helper/RecipientHelper.java | 1 + .../asamk/signal/manager/internal/ManagerImpl.java | 13 +++++++++++++ .../asamk/signal/manager/storage/SignalAccount.java | 12 ++++++++++++ 3 files changed, 26 insertions(+) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java index 133aab5b..a27aca64 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java @@ -214,6 +214,7 @@ public class RecipientHelper { }}; account.getCdsiStore().updateAfterFullCdsQuery(fullNumbers, seenNumbers); account.setCdsiToken(newToken); + account.setLastRecipientsRefresh(System.currentTimeMillis()); } }); } catch (CdsiInvalidTokenException e) { diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index 189e4fe9..841fd791 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -115,6 +115,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; @@ -215,6 +216,18 @@ public class ManagerImpl implements Manager { public void checkAccountState() throws IOException { context.getAccountHelper().checkAccountState(); + final var lastRecipientsRefresh = account.getLastRecipientsRefresh(); + if (lastRecipientsRefresh == null + || lastRecipientsRefresh < System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)) { + try { + context.getRecipientHelper().refreshUsers(); + } catch (Exception e) { + logger.warn("Full CDSI recipients refresh failed, ignoring: {} ({})", + e.getMessage(), + e.getClass().getSimpleName()); + logger.debug("Full CDSI refresh failed", e); + } + } } @Override diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index fbfd88f6..a51136a0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -147,6 +147,8 @@ public class SignalAccount implements Closeable { long.class, 0L); private final KeyValueEntry cdsiToken = new KeyValueEntry<>("cdsi-token", byte[].class); + private final KeyValueEntry lastRecipientsRefresh = new KeyValueEntry<>("last-recipients-refresh", + long.class); private final KeyValueEntry storageManifestVersion = new KeyValueEntry<>("storage-manifest-version", long.class, -1L); @@ -374,6 +376,7 @@ public class SignalAccount implements Closeable { this.storageKey = null; trustSelfIdentity(ServiceIdType.ACI); trustSelfIdentity(ServiceIdType.PNI); + getKeyValueStore().storeEntry(lastRecipientsRefresh, null); } public void finishRegistration( @@ -406,6 +409,7 @@ public class SignalAccount implements Closeable { getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress()); trustSelfIdentity(ServiceIdType.ACI); trustSelfIdentity(ServiceIdType.PNI); + getKeyValueStore().storeEntry(lastRecipientsRefresh, null); } public void initDatabase() { @@ -1586,6 +1590,14 @@ public class SignalAccount implements Closeable { getKeyValueStore().storeEntry(cdsiToken, value); } + public Long getLastRecipientsRefresh() { + return getKeyValueStore().getEntry(lastRecipientsRefresh); + } + + public void setLastRecipientsRefresh(final Long value) { + getKeyValueStore().storeEntry(lastRecipientsRefresh, value); + } + public ProfileKey getProfileKey() { return profileKey; } -- 2.51.0 From 400dcf28990e75f395b44b80c70279ae132e877b Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 16 Oct 2023 18:28:08 +0200 Subject: [PATCH 04/16] Refactor creating linked account files --- .../signal/manager/SignalAccountFiles.java | 5 - .../internal/ProvisioningManagerImpl.java | 54 ++++---- .../signal/manager/storage/SignalAccount.java | 127 ++++-------------- 3 files changed, 55 insertions(+), 131 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java index da8a3f3a..ef160621 100644 --- a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java +++ b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java @@ -14,7 +14,6 @@ import org.asamk.signal.manager.internal.RegistrationManagerImpl; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.accounts.AccountsStore; import org.asamk.signal.manager.util.KeyUtils; -import org.signal.libsignal.protocol.util.KeyHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException; @@ -160,8 +159,6 @@ public class SignalAccountFiles { final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath; var aciIdentityKey = KeyUtils.generateIdentityKeyPair(); var pniIdentityKey = KeyUtils.generateIdentityKeyPair(); - var registrationId = KeyHelper.generateRegistrationId(false); - var pniRegistrationId = KeyHelper.generateRegistrationId(false); var profileKey = KeyUtils.createProfileKey(); var account = SignalAccount.create(pathConfig.dataPath(), @@ -170,8 +167,6 @@ public class SignalAccountFiles { serviceEnvironment, aciIdentityKey, pniIdentityKey, - registrationId, - pniRegistrationId, profileKey, settings); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java index 405657b0..155756c9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ProvisioningManagerImpl.java @@ -27,12 +27,12 @@ import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.accounts.AccountsStore; import org.asamk.signal.manager.util.KeyUtils; import org.signal.libsignal.protocol.IdentityKeyPair; -import org.signal.libsignal.protocol.util.KeyHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; +import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.util.DeviceNameUtil; @@ -59,8 +59,6 @@ public class ProvisioningManagerImpl implements ProvisioningManager { private final SignalServiceAccountManager accountManager; private final IdentityKeyPair tempIdentityKey; - private final int registrationId; - private final int pniRegistrationId; private final String password; public ProvisioningManagerImpl( @@ -77,8 +75,6 @@ public class ProvisioningManagerImpl implements ProvisioningManager { this.accountsStore = accountsStore; tempIdentityKey = KeyUtils.generateIdentityKeyPair(); - registrationId = KeyHelper.generateRegistrationId(false); - pniRegistrationId = KeyHelper.generateRegistrationId(false); password = KeyUtils.createPassword(); GroupsV2Operations groupsV2Operations; try { @@ -114,9 +110,9 @@ public class ProvisioningManagerImpl implements ProvisioningManager { if (accountPath == null) { accountPath = accountsStore.getPathByNumber(number); } - if (accountPath != null - && SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath) - && !canRelinkExistingAccount(accountPath)) { + final var accountExists = accountPath != null && SignalAccount.accountFileExists(pathConfig.dataPath(), + accountPath); + if (accountExists && !canRelinkExistingAccount(accountPath)) { throw new UserAlreadyExistsException(number, SignalAccount.getFileName(pathConfig.dataPath(), accountPath)); } if (accountPath == null) { @@ -128,38 +124,42 @@ public class ProvisioningManagerImpl implements ProvisioningManager { var encryptedDeviceName = deviceName == null ? null : DeviceNameUtil.encryptDeviceName(deviceName, ret.getAciIdentity().getPrivateKey()); - - logger.debug("Finishing new device registration"); - var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(), - new ConfirmCodeMessage(false, - true, - registrationId, - pniRegistrationId, - encryptedDeviceName, - getCapabilities(false))); - // Create new account with the synced identity var profileKey = ret.getProfileKey() == null ? KeyUtils.createProfileKey() : ret.getProfileKey(); SignalAccount account = null; try { - account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(), - accountPath, - number, - serviceEnvironmentConfig.type(), + if (!accountExists) { + account = SignalAccount.createLinkedAccount(pathConfig.dataPath(), + accountPath, + serviceEnvironmentConfig.type(), + Settings.DEFAULT); + } else { + account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, Settings.DEFAULT); + } + + account.setProvisioningData(number, aci, pni, password, encryptedDeviceName, - deviceId, ret.getAciIdentity(), ret.getPniIdentity(), - registrationId, - pniRegistrationId, - profileKey, - Settings.DEFAULT); + profileKey); + account.getConfigurationStore().setReadReceipts(ret.isReadReceipts()); + logger.debug("Finishing new device registration"); + var deviceId = accountManager.finishNewDeviceRegistration(ret.getProvisioningCode(), + new ConfirmCodeMessage(false, + true, + account.getAccountData(ServiceIdType.ACI).getLocalRegistrationId(), + account.getAccountData(ServiceIdType.PNI).getLocalRegistrationId(), + encryptedDeviceName, + getCapabilities(false))); + + account.finishLinking(deviceId); + ManagerImpl m = null; try { m = new ManagerImpl(account, diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java index a51136a0..57300b11 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java @@ -130,7 +130,7 @@ public class SignalAccount implements Closeable { private String number; private String username; private String encryptedDeviceName; - private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; + private int deviceId = 0; private String password; private String registrationLockPin; private MasterKey pinMasterKey; @@ -204,8 +204,6 @@ public class SignalAccount implements Closeable { ServiceEnvironment serviceEnvironment, IdentityKeyPair aciIdentityKey, IdentityKeyPair pniIdentityKey, - int registrationId, - int pniRegistrationId, ProfileKey profileKey, final Settings settings ) throws IOException { @@ -223,160 +221,91 @@ public class SignalAccount implements Closeable { signalAccount.serviceEnvironment = serviceEnvironment; signalAccount.profileKey = profileKey; signalAccount.password = KeyUtils.createPassword(); + signalAccount.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; signalAccount.dataPath = dataPath; signalAccount.aciAccountData.setIdentityKeyPair(aciIdentityKey); signalAccount.pniAccountData.setIdentityKeyPair(pniIdentityKey); - signalAccount.aciAccountData.setLocalRegistrationId(registrationId); - signalAccount.pniAccountData.setLocalRegistrationId(pniRegistrationId); + signalAccount.aciAccountData.setLocalRegistrationId(KeyHelper.generateRegistrationId(false)); + signalAccount.pniAccountData.setLocalRegistrationId(KeyHelper.generateRegistrationId(false)); signalAccount.settings = settings; signalAccount.registered = false; signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION; signalAccount.migrateLegacyConfigs(); - signalAccount.clearAllPreKeys(); signalAccount.save(); return signalAccount; } - private static SignalAccount createLinkedAccount( - File dataPath, - String accountPath, - String number, - ServiceEnvironment serviceEnvironment, - ACI aci, - PNI pni, - String password, - String encryptedDeviceName, - int deviceId, - IdentityKeyPair aciIdentityKey, - IdentityKeyPair pniIdentityKey, - int registrationId, - int pniRegistrationId, - ProfileKey profileKey, + public static SignalAccount createLinkedAccount( + final File dataPath, + final String accountPath, + final ServiceEnvironment serviceEnvironment, final Settings settings ) throws IOException { + IOUtils.createPrivateDirectories(dataPath); var fileName = getFileName(dataPath, accountPath); IOUtils.createPrivateFile(fileName); final var pair = openFileChannel(fileName, true); - var signalAccount = new SignalAccount(pair.first(), pair.second()); + final var signalAccount = new SignalAccount(pair.first(), pair.second()); signalAccount.dataPath = dataPath; signalAccount.accountPath = accountPath; signalAccount.serviceEnvironment = serviceEnvironment; - signalAccount.aciAccountData.setLocalRegistrationId(registrationId); - signalAccount.pniAccountData.setLocalRegistrationId(pniRegistrationId); + signalAccount.aciAccountData.setLocalRegistrationId(KeyHelper.generateRegistrationId(false)); + signalAccount.pniAccountData.setLocalRegistrationId(KeyHelper.generateRegistrationId(false)); signalAccount.settings = settings; - signalAccount.setProvisioningData(number, - aci, - pni, - password, - encryptedDeviceName, - deviceId, - aciIdentityKey, - pniIdentityKey, - profileKey); - signalAccount.getRecipientTrustedResolver() - .resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress()); signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION; - signalAccount.migrateLegacyConfigs(); - signalAccount.clearAllPreKeys(); - signalAccount.save(); - - return signalAccount; - } - public static SignalAccount createOrUpdateLinkedAccount( - File dataPath, - String accountPath, - String number, - ServiceEnvironment serviceEnvironment, - ACI aci, - PNI pni, - String password, - String encryptedDeviceName, - int deviceId, - IdentityKeyPair aciIdentityKey, - IdentityKeyPair pniIdentityKey, - int registrationId, - int pniRegistrationId, - ProfileKey profileKey, - final Settings settings - ) throws IOException { - IOUtils.createPrivateDirectories(dataPath); - var fileName = getFileName(dataPath, accountPath); - if (!fileName.exists()) { - return createLinkedAccount(dataPath, - accountPath, - number, - serviceEnvironment, - aci, - pni, - password, - encryptedDeviceName, - deviceId, - aciIdentityKey, - pniIdentityKey, - registrationId, - pniRegistrationId, - profileKey, - settings); - } - - final var signalAccount = load(dataPath, accountPath, true, settings); - signalAccount.setProvisioningData(number, - aci, - pni, - password, - encryptedDeviceName, - deviceId, - aciIdentityKey, - pniIdentityKey, - profileKey); - signalAccount.getRecipientTrustedResolver() - .resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress()); - signalAccount.aciAccountData.getSessionStore().archiveAllSessions(); - signalAccount.pniAccountData.getSessionStore().archiveAllSessions(); - signalAccount.getSenderKeyStore().deleteAll(); - signalAccount.clearAllPreKeys(); return signalAccount; } - private void setProvisioningData( + public void setProvisioningData( final String number, final ACI aci, final PNI pni, final String password, final String encryptedDeviceName, - final int deviceId, final IdentityKeyPair aciIdentity, final IdentityKeyPair pniIdentity, final ProfileKey profileKey ) { + this.deviceId = 0; this.number = number; this.aciAccountData.setServiceId(aci); this.pniAccountData.setServiceId(pni); + getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress()); this.password = password; this.profileKey = profileKey; getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey()); this.encryptedDeviceName = encryptedDeviceName; - this.deviceId = deviceId; this.aciAccountData.setIdentityKeyPair(aciIdentity); this.pniAccountData.setIdentityKeyPair(pniIdentity); - this.registered = true; + this.registered = false; this.isMultiDevice = true; getKeyValueStore().storeEntry(lastReceiveTimestamp, 0L); this.pinMasterKey = null; getKeyValueStore().storeEntry(storageManifestVersion, -1L); this.setStorageManifest(null); this.storageKey = null; + getSenderKeyStore().deleteAll(); trustSelfIdentity(ServiceIdType.ACI); trustSelfIdentity(ServiceIdType.PNI); + aciAccountData.getSessionStore().archiveAllSessions(); + pniAccountData.getSessionStore().archiveAllSessions(); + clearAllPreKeys(); getKeyValueStore().storeEntry(lastRecipientsRefresh, null); + save(); + } + + public void finishLinking(final int deviceId) { + this.registered = true; + this.deviceId = deviceId; + save(); } public void finishRegistration( @@ -476,7 +405,7 @@ public class SignalAccount implements Closeable { return false; } var f = getFileName(dataPath, account); - return !(!f.exists() || f.isDirectory()); + return f.exists() && !f.isDirectory() && f.length() > 0L; } private void load( -- 2.51.0 From 5cc20ace1f65c3c08be1b0812ba54b034d499c61 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 13:34:09 +0200 Subject: [PATCH 05/16] Ignore failures from SVR v1 pin --- .../signal/manager/helper/AccountHelper.java | 3 ++ .../asamk/signal/manager/helper/Context.java | 7 ++-- .../signal/manager/helper/PinHelper.java | 32 ++++--------------- .../internal/RegistrationManagerImpl.java | 5 ++- 4 files changed, 17 insertions(+), 30 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java index e021cc37..f2002347 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java @@ -416,12 +416,14 @@ public class AccountHelper { var masterKey = account.getOrCreatePinMasterKey(); context.getPinHelper().migrateRegistrationLockPin(account.getRegistrationLockPin(), masterKey); + dependencies.getAccountManager().enableRegistrationLock(masterKey); } public void setRegistrationPin(String pin) throws IOException { var masterKey = account.getOrCreatePinMasterKey(); context.getPinHelper().setRegistrationLockPin(pin, masterKey); + dependencies.getAccountManager().enableRegistrationLock(masterKey); account.setRegistrationLockPin(pin); } @@ -429,6 +431,7 @@ public class AccountHelper { public void removeRegistrationPin() throws IOException { // Remove KBS Pin context.getPinHelper().removeRegistrationLockPin(); + dependencies.getAccountManager().disableRegistrationLock(); account.setRegistrationLockPin(null); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/Context.java b/lib/src/main/java/org/asamk/signal/manager/helper/Context.java index a11ea470..ed0cf391 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/Context.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/Context.java @@ -6,6 +6,7 @@ import org.asamk.signal.manager.storage.AttachmentStore; import org.asamk.signal.manager.storage.AvatarStore; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.stickerPacks.StickerPackStore; +import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1; import java.util.function.Supplier; @@ -115,9 +116,9 @@ public class Context { PinHelper getPinHelper() { return getOrCreate(() -> pinHelper, - () -> pinHelper = new PinHelper(dependencies.getKeyBackupService(), - dependencies.getFallbackKeyBackupServices(), - dependencies.getSecureValueRecoveryV2())); + () -> pinHelper = new PinHelper(new SecureValueRecoveryV1(dependencies.getKeyBackupService()), + dependencies.getSecureValueRecoveryV2(), + dependencies.getFallbackKeyBackupServices())); } public PreKeyHelper getPreKeyHelper() { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java index dcd89f20..d8c801be 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/PinHelper.java @@ -5,11 +5,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.KeyBackupService; import org.whispersystems.signalservice.api.kbs.MasterKey; -import org.whispersystems.signalservice.api.kbs.PinHashUtil; import org.whispersystems.signalservice.api.svr.SecureValueRecovery; import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1; import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV2; -import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException; import org.whispersystems.signalservice.internal.push.AuthCredentials; import org.whispersystems.signalservice.internal.push.LockedException; @@ -20,35 +18,24 @@ public class PinHelper { private final static Logger logger = LoggerFactory.getLogger(PinHelper.class); - private final KeyBackupService keyBackupService; private final SecureValueRecoveryV1 secureValueRecoveryV1; private final SecureValueRecoveryV2 secureValueRecoveryV2; private final Collection fallbackKeyBackupServices; public PinHelper( - final KeyBackupService keyBackupService, - final Collection fallbackKeyBackupServices, - SecureValueRecoveryV2 secureValueRecoveryV2 + final SecureValueRecoveryV1 secureValueRecoveryV1, + final SecureValueRecoveryV2 secureValueRecoveryV2, + final Collection fallbackKeyBackupServices ) { - this.keyBackupService = keyBackupService; this.fallbackKeyBackupServices = fallbackKeyBackupServices; - this.secureValueRecoveryV1 = new SecureValueRecoveryV1(keyBackupService); + this.secureValueRecoveryV1 = secureValueRecoveryV1; this.secureValueRecoveryV2 = secureValueRecoveryV2; } public void setRegistrationLockPin( String pin, MasterKey masterKey ) throws IOException { - final var pinChangeSession = keyBackupService.newPinChangeSession(); - final var hashedPin = PinHashUtil.hashPin(pin, pinChangeSession.hashSalt()); - - try { - pinChangeSession.setPin(hashedPin, masterKey); - } catch (UnauthenticatedResponseException e) { - throw new IOException(e); - } - pinChangeSession.enableRegistrationLock(masterKey); - + secureValueRecoveryV1.setPin(pin, masterKey).execute(); final var backupResponse = secureValueRecoveryV2.setPin(pin, masterKey).execute(); if (backupResponse instanceof SecureValueRecovery.BackupResponse.Success) { } else if (backupResponse instanceof SecureValueRecovery.BackupResponse.ServerRejected) { @@ -80,14 +67,7 @@ public class PinHelper { } public void removeRegistrationLockPin() throws IOException { - final var pinChangeSession = keyBackupService.newPinChangeSession(); - pinChangeSession.disableRegistrationLock(); - try { - pinChangeSession.removePin(); - } catch (UnauthenticatedResponseException e) { - throw new IOException(e); - } - + secureValueRecoveryV1.deleteData(); final var deleteResponse = secureValueRecoveryV2.deleteData(); if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.Success) { } else if (deleteResponse instanceof SecureValueRecovery.DeleteResponse.ServerRejected) { diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java index 793f2f49..815cc8f1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java @@ -45,6 +45,7 @@ import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException; import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException; +import org.whispersystems.signalservice.api.svr.SecureValueRecoveryV1; import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider; @@ -108,7 +109,9 @@ public class RegistrationManagerImpl implements RegistrationManager { 10)) .toList(); final var secureValueRecoveryV2 = accountManager.getSecureValueRecoveryV2(serviceEnvironmentConfig.svr2Mrenclave()); - this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices, secureValueRecoveryV2); + this.pinHelper = new PinHelper(new SecureValueRecoveryV1(keyBackupService), + secureValueRecoveryV2, + fallbackKeyBackupServices); } @Override -- 2.51.0 From ca2e6adedb1ce2d4e80ecce14f1bc44ef567bc03 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 14:18:35 +0200 Subject: [PATCH 06/16] Update dependencies --- settings.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 9238ea43..01104694 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,9 +7,9 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { library("bouncycastle", "org.bouncycastle", "bcprov-jdk15on").version("1.70") - library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.15.2") + library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.15.3") library("argparse4j", "net.sourceforge.argparse4j", "argparse4j").version("0.9.0") - library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("4.3.0") + library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("4.3.1") version("slf4j", "2.0.9") library("slf4j.api", "org.slf4j", "slf4j-api").versionRef("slf4j") library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") @@ -17,7 +17,7 @@ dependencyResolutionManagement { library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_85") - library("sqlite", "org.xerial", "sqlite-jdbc").version("3.43.0.0") + library("sqlite", "org.xerial", "sqlite-jdbc").version("3.43.2.0") library("hikari", "com.zaxxer", "HikariCP").version("5.0.1") library("junit", "org.junit.jupiter", "junit-jupiter").version("5.10.0") } -- 2.51.0 From 733c14bbc8d7b89b58c972a2ac7d332f04985179 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 14:19:32 +0200 Subject: [PATCH 07/16] Ignore invalid recipient numbers --- .../signal/manager/storage/recipients/RecipientStore.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index 62ab525a..9731496e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -395,6 +395,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re try (final var statement = connection.prepareStatement(sql)) { return Utils.executeQueryForStream(statement, resultSet -> resultSet.getString("number")) .filter(Objects::nonNull) + .filter(n -> { + try { + Long.parseLong(n); + return true; + } catch (NumberFormatException e) { + return false; + } + }) .collect(Collectors.toSet()); } } catch (SQLException e) { -- 2.51.0 From 1addffe6226416bdca4fc15e98c07e24b394b3dc Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 14:58:15 +0200 Subject: [PATCH 08/16] Store username aci link in recipient store --- .../storage/recipients/RecipientStore.java | 21 ++++++------------- .../recipients/RecipientTrustedResolver.java | 7 +++---- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index 9731496e..816ac791 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -229,7 +229,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public RecipientId resolveRecipientByUsername( - final String username, Supplier serviceIdSupplier + final String username, Supplier aciSupplier ) throws UnregisteredRecipientException { final Optional byUsername; try (final var connection = database.getConnection()) { @@ -238,14 +238,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re throw new RuntimeException("Failed read from recipient store", e); } if (byUsername.isEmpty() || byUsername.get().address().serviceId().isEmpty()) { - final var serviceId = serviceIdSupplier.get(); - if (serviceId == null) { + final var aci = aciSupplier.get(); + if (aci == null) { throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, null, username)); } - return resolveRecipient(serviceId); + return resolveRecipientTrusted(aci, username); } return byUsername.get().id(); } @@ -287,17 +287,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } @Override - public RecipientId resolveRecipientTrusted(final ServiceId serviceId, final String username) { - return resolveRecipientTrusted(new RecipientAddress(serviceId, null, null, username), false); - } - - public RecipientId resolveRecipientTrusted( - final ACI aci, final String username - ) { - return resolveRecipientTrusted(new RecipientAddress(Optional.of(aci), - Optional.empty(), - Optional.empty(), - Optional.of(username)), false); + public RecipientId resolveRecipientTrusted(final ACI aci, final String username) { + return resolveRecipientTrusted(new RecipientAddress(aci, null, null, username), false); } @Override diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientTrustedResolver.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientTrustedResolver.java index 54e5d5d1..21708551 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientTrustedResolver.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientTrustedResolver.java @@ -1,6 +1,5 @@ package org.asamk.signal.manager.storage.recipients; -import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -16,7 +15,7 @@ public interface RecipientTrustedResolver { RecipientId resolveRecipientTrusted(Optional aci, Optional pni, Optional number); - RecipientId resolveRecipientTrusted(ServiceId serviceId, String username); + RecipientId resolveRecipientTrusted(ACI aci, String username); class RecipientTrustedResolverWrapper implements RecipientTrustedResolver { @@ -44,8 +43,8 @@ public interface RecipientTrustedResolver { } @Override - public RecipientId resolveRecipientTrusted(final ServiceId serviceId, final String username) { - return recipientTrustedResolverSupplier.get().resolveRecipientTrusted(serviceId, username); + public RecipientId resolveRecipientTrusted(final ACI aci, final String username) { + return recipientTrustedResolverSupplier.get().resolveRecipientTrusted(aci, username); } } } -- 2.51.0 From 2c5edbc9815088357ebd94d0a5a15a5a2f239154 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 15:20:14 +0200 Subject: [PATCH 09/16] Add cache for serviceId to recipient id/address mapping --- .../storage/recipients/RecipientStore.java | 117 ++++++++++++------ 1 file changed, 76 insertions(+), 41 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java index 816ac791..44c0bafa 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java @@ -47,6 +47,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re private final Object recipientsLock = new Object(); private final Map recipientsMerged = new HashMap<>(); + private final Map recipientAddressCache = new HashMap<>(); + public static void createSql(Connection connection) throws SQLException { // When modifying the CREATE statement here, also add a migration in AccountDatabase.java try (final var statement = connection.createStatement()) { @@ -176,15 +178,18 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re @Override public RecipientId resolveRecipient(final ServiceId serviceId) { synchronized (recipientsLock) { - final RecipientId recipientId; + final var recipientWithAddress = recipientAddressCache.get(serviceId); + if (recipientWithAddress != null) { + return recipientWithAddress.id(); + } try (final var connection = database.getConnection()) { connection.setAutoCommit(false); - recipientId = resolveRecipientLocked(connection, serviceId); + final var recipientId = resolveRecipientLocked(connection, serviceId); connection.commit(); + return recipientId; } catch (SQLException e) { throw new RuntimeException("Failed read recipient store", e); } - return recipientId; } } @@ -357,7 +362,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re FROM %s r WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL) AND %s """ - ).formatted(TABLE_RECIPIENT, sqlWhere.size() == 0 ? "TRUE" : String.join(" AND ", sqlWhere)); + ).formatted(TABLE_RECIPIENT, sqlWhere.isEmpty() ? "TRUE" : String.join(" AND ", sqlWhere)); try (final var connection = database.getConnection()) { try (final var statement = connection.prepareStatement(sql)) { if (blocked.isPresent()) { @@ -429,16 +434,22 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public void deleteRecipientData(RecipientId recipientId) { logger.debug("Deleting recipient data for {}", recipientId); - try (final var connection = database.getConnection()) { - connection.setAutoCommit(false); - storeContact(connection, recipientId, null); - storeProfile(connection, recipientId, null); - storeProfileKey(connection, recipientId, null, false); - storeExpiringProfileKeyCredential(connection, recipientId, null); - deleteRecipient(connection, recipientId); - connection.commit(); - } catch (SQLException e) { - throw new RuntimeException("Failed update recipient store", e); + synchronized (recipientsLock) { + recipientAddressCache.entrySet() + .stream() + .filter(e -> e.getValue().id().equals(recipientId)) + .forEach(e -> recipientAddressCache.remove(e.getKey())); + try (final var connection = database.getConnection()) { + connection.setAutoCommit(false); + storeContact(connection, recipientId, null); + storeProfile(connection, recipientId, null); + storeProfileKey(connection, recipientId, null, false); + storeExpiringProfileKeyCredential(connection, recipientId, null); + deleteRecipient(connection, recipientId); + connection.commit(); + } catch (SQLException e) { + throw new RuntimeException("Failed update recipient store", e); + } } } @@ -691,11 +702,17 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } } - if (pair.second().size() > 0) { + if (!pair.second().isEmpty()) { try (final var connection = database.getConnection()) { for (final var toBeMergedRecipientId : pair.second()) { recipientMergeHandler.mergeRecipients(connection, pair.first(), toBeMergedRecipientId); deleteRecipient(connection, toBeMergedRecipientId); + synchronized (recipientsLock) { + recipientAddressCache.entrySet() + .stream() + .filter(e -> e.getValue().id().equals(toBeMergedRecipientId)) + .forEach(e -> recipientAddressCache.remove(e.getKey())); + } } } catch (SQLException e) { throw new RuntimeException("Failed update recipient store", e); @@ -789,37 +806,49 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private void removeRecipientAddress(Connection connection, RecipientId recipientId) throws SQLException { - final var sql = ( - """ - UPDATE %s - SET number = NULL, uuid = NULL, pni = NULL - WHERE _id = ? - """ - ).formatted(TABLE_RECIPIENT); - try (final var statement = connection.prepareStatement(sql)) { - statement.setLong(1, recipientId.id()); - statement.executeUpdate(); + synchronized (recipientsLock) { + recipientAddressCache.entrySet() + .stream() + .filter(e -> e.getValue().id().equals(recipientId)) + .forEach(e -> recipientAddressCache.remove(e.getKey())); + final var sql = ( + """ + UPDATE %s + SET number = NULL, uuid = NULL, pni = NULL + WHERE _id = ? + """ + ).formatted(TABLE_RECIPIENT); + try (final var statement = connection.prepareStatement(sql)) { + statement.setLong(1, recipientId.id()); + statement.executeUpdate(); + } } } private void updateRecipientAddress( Connection connection, RecipientId recipientId, final RecipientAddress address ) throws SQLException { - final var sql = ( - """ - UPDATE %s - SET number = ?, uuid = ?, pni = ?, username = ? - WHERE _id = ? - """ - ).formatted(TABLE_RECIPIENT); - try (final var statement = connection.prepareStatement(sql)) { - statement.setString(1, address.number().orElse(null)); - statement.setBytes(2, - address.serviceId().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); - statement.setBytes(3, address.pni().map(PNI::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); - statement.setString(4, address.username().orElse(null)); - statement.setLong(5, recipientId.id()); - statement.executeUpdate(); + synchronized (recipientsLock) { + recipientAddressCache.entrySet() + .stream() + .filter(e -> e.getValue().id().equals(recipientId)) + .forEach(e -> recipientAddressCache.remove(e.getKey())); + final var sql = ( + """ + UPDATE %s + SET number = ?, uuid = ?, pni = ?, username = ? + WHERE _id = ? + """ + ).formatted(TABLE_RECIPIENT); + try (final var statement = connection.prepareStatement(sql)) { + statement.setString(1, address.number().orElse(null)); + statement.setBytes(2, + address.serviceId().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); + statement.setBytes(3, address.pni().map(PNI::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); + statement.setString(4, address.username().orElse(null)); + statement.setLong(5, recipientId.id()); + statement.executeUpdate(); + } } } @@ -900,6 +929,10 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re private Optional findByServiceId( final Connection connection, final ServiceId serviceId ) throws SQLException { + var recipientWithAddress = Optional.ofNullable(recipientAddressCache.get(serviceId)); + if (recipientWithAddress.isPresent()) { + return recipientWithAddress; + } final var sql = """ SELECT r._id, r.number, r.uuid, r.pni, r.username FROM %s r @@ -908,7 +941,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re """.formatted(TABLE_RECIPIENT); try (final var statement = connection.prepareStatement(sql)) { statement.setBytes(1, UuidUtil.toByteArray(serviceId.getRawUuid())); - return Utils.executeQueryForOptional(statement, this::getRecipientWithAddressFromResultSet); + recipientWithAddress = Utils.executeQueryForOptional(statement, this::getRecipientWithAddressFromResultSet); + recipientWithAddress.ifPresent(r -> recipientAddressCache.put(serviceId, r)); + return recipientWithAddress; } } -- 2.51.0 From fb7c63c5079aed53c43d9f1a0f7913218a6865e1 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 15:21:18 +0200 Subject: [PATCH 10/16] Reduce sqlite logging --- src/main/java/org/asamk/signal/logging/LogConfigurator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/asamk/signal/logging/LogConfigurator.java b/src/main/java/org/asamk/signal/logging/LogConfigurator.java index 943f9929..7401bf2c 100644 --- a/src/main/java/org/asamk/signal/logging/LogConfigurator.java +++ b/src/main/java/org/asamk/signal/logging/LogConfigurator.java @@ -50,6 +50,8 @@ public class LogConfigurator extends ContextAwareBase implements Configurator { lc.getLogger("org.asamk").setLevel(verboseLevel > 1 ? Level.ALL : verboseLevel > 0 ? Level.DEBUG : Level.INFO); lc.getLogger("com.zaxxer.hikari.pool.PoolBase") .setLevel(verboseLevel > 2 ? Level.ALL : verboseLevel > 1 ? Level.INFO : Level.WARN); + lc.getLogger("org.sqlite.core.NativeDB") + .setLevel(verboseLevel > 3 ? Level.ALL : verboseLevel > 1 ? Level.INFO : Level.WARN); if (logFile != null) { consoleAppender.addFilter(new Filter<>() { -- 2.51.0 From 1ed5148624a1054c944072f6e2a50297b2828667 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 15:21:28 +0200 Subject: [PATCH 11/16] Bump version to 0.12.3 --- CHANGELOG.md | 21 +++++++++++++++++++++ build.gradle.kts | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 235f693d..e5158f41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,28 @@ ## [Unreleased] +## [0.12.3] - 2023-10-17 + +### Added + +- Added `startChangeNumber` and `finishChangeNumber` commands to switch to another phone number +- Added `--quote-attachment` paramter to `send` command +- Added support for scannable safety numbers based on serviceId +- Added `EditMessageReceived` signal for D-Bus interface +- Added new exit code `5` for rate limit failures +- Added full CDSI refresh to get current ACI/PNIs for known numbers regularly + +### Fixed + +- Correctly respond with delivery receipts for edit messages + +### Changed + +- JSON-RPC requests are now executed in parallel. + Clients should make sure to use the `id` field to get the correct response for a request. + ## [0.12.2] - 2023-09-30 + **Attention**: Now requires native libsignal-client version 0.32.1 ### Added diff --git a/build.gradle.kts b/build.gradle.kts index 82e17f7f..2879633f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.9.27" } -version = "0.12.2" +version = "0.12.3" java { sourceCompatibility = JavaVersion.VERSION_17 -- 2.51.0 From 5b5644574114a898b66277e144dbe279ef58effe Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 19:21:41 +0200 Subject: [PATCH 12/16] Update github checkout action --- .github/workflows/ci.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/release.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3e7f35b..06ffe066 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: java: [ '17', '21' ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK uses: actions/setup-java@v3 with: @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: graalvm/setup-graalvm@v1 with: version: 'latest' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index cd83c179..9bb10c38 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ jobs: java-version: 17 - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea5bf41d..35c40a9d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -164,7 +164,7 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download signal-cli build from CI workflow uses: actions/download-artifact@v3 @@ -214,7 +214,7 @@ jobs: packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download signal-cli build from CI workflow uses: actions/download-artifact@v3 -- 2.51.0 From b9e644269b073937202ca3ba6c9327e1ba46e2c3 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 19:22:09 +0200 Subject: [PATCH 13/16] Use gradle-build-action for caching and dependency submission --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06ffe066..fd5a938d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,10 @@ jobs: with: distribution: 'zulu' java-version: ${{ matrix.java }} - cache: 'gradle' + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + with: + dependency-graph: generate-and-submit - name: Build with Gradle run: ./gradlew --no-daemon build - name: Compress archive -- 2.51.0 From 9ec942ea1da34cfb2d5f986b33e27b2be5699ce7 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 19:34:42 +0200 Subject: [PATCH 14/16] Update workflow permission --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd5a938d..08390721 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: workflow_call: permissions: - contents: read # to fetch code (actions/checkout) + contents: write # to fetch code (actions/checkout) and submit dependency graph (gradle/gradle-build-action) jobs: build: -- 2.51.0 From 9ba70c18088ebb9b44c91fd18de5b6ff1adbe9f4 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 19:47:14 +0200 Subject: [PATCH 15/16] Update bouncycastle dependency --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 01104694..1d87bc3d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,7 +6,7 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { - library("bouncycastle", "org.bouncycastle", "bcprov-jdk15on").version("1.70") + library("bouncycastle", "org.bouncycastle", "bcprov-jdk18on").version("1.76") library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.15.3") library("argparse4j", "net.sourceforge.argparse4j", "argparse4j").version("0.9.0") library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("4.3.1") -- 2.51.0 From d51dd7ae575222b0baea7265c18ebc79f4a7b001 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 17 Oct 2023 20:01:59 +0200 Subject: [PATCH 16/16] Use .isEmpty() for checking lists and strings --- .../manager/api/GroupInviteLinkUrl.java | 4 +-- .../signal/manager/helper/GroupHelper.java | 20 ++++++------- .../signal/manager/helper/SendHelper.java | 10 +++---- .../signal/manager/helper/StorageHelper.java | 2 +- .../signal/manager/internal/ManagerImpl.java | 16 +++++----- .../asamk/signal/ReceiveMessageHandler.java | 30 +++++++++---------- .../signal/commands/ListGroupsCommand.java | 2 +- .../asamk/signal/dbus/DbusCommandHandler.java | 2 +- .../asamk/signal/dbus/DbusManagerImpl.java | 14 ++++----- .../dbus/DbusReceiveMessageHandler.java | 6 ++-- .../signal/http/ServerSentEventSender.java | 2 +- .../asamk/signal/json/JsonDataMessage.java | 10 +++---- .../java/org/asamk/signal/json/JsonQuote.java | 6 ++-- .../asamk/signal/json/JsonSharedContact.java | 6 ++-- .../asamk/signal/json/JsonSyncMessage.java | 2 +- .../asamk/signal/jsonrpc/JsonRpcReader.java | 4 +-- src/main/java/org/asamk/signal/util/Util.java | 4 +-- 17 files changed, 70 insertions(+), 70 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/GroupInviteLinkUrl.java b/lib/src/main/java/org/asamk/signal/manager/api/GroupInviteLinkUrl.java index d44fc982..cc3690f4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/GroupInviteLinkUrl.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/GroupInviteLinkUrl.java @@ -39,13 +39,13 @@ public final class GroupInviteLinkUrl { } try { - if (!"/".equals(uri.getPath()) && uri.getPath().length() > 0) { + if (!"/".equals(uri.getPath()) && !uri.getPath().isEmpty()) { throw new InvalidGroupLinkException("No path was expected in uri"); } var encoding = uri.getFragment(); - if (encoding == null || encoding.length() == 0) { + if (encoding == null || encoding.isEmpty()) { throw new InvalidGroupLinkException("No reference was in the uri"); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java index ef104fa2..697786fc 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupHelper.java @@ -594,14 +594,14 @@ public class GroupHelper { if (members != null) { final var requestingMembers = new HashSet<>(members); requestingMembers.retainAll(group.getRequestingMembers()); - if (requestingMembers.size() > 0) { + if (!requestingMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.approveJoinRequestMembers(group, requestingMembers); result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); } final var newMembers = new HashSet<>(members); newMembers.removeAll(group.getMembers()); newMembers.removeAll(group.getRequestingMembers()); - if (newMembers.size() > 0) { + if (!newMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.addMembers(group, newMembers); result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); } @@ -617,20 +617,20 @@ public class GroupHelper { existingRemoveMembers.removeAll(members); } existingRemoveMembers.remove(account.getSelfRecipientId());// self can be removed with sendQuitGroupMessage - if (existingRemoveMembers.size() > 0) { + if (!existingRemoveMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.removeMembers(group, existingRemoveMembers); result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); } var pendingRemoveMembers = new HashSet<>(removeMembers); pendingRemoveMembers.retainAll(group.getPendingMembers()); - if (pendingRemoveMembers.size() > 0) { + if (!pendingRemoveMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.revokeInvitedMembers(group, pendingRemoveMembers); result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); } var requestingRemoveMembers = new HashSet<>(removeMembers); requestingRemoveMembers.retainAll(group.getRequestingMembers()); - if (requestingRemoveMembers.size() > 0) { + if (!requestingRemoveMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.refuseJoinRequestMembers(group, requestingRemoveMembers); result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); } @@ -640,7 +640,7 @@ public class GroupHelper { final var newAdmins = new HashSet<>(admins); newAdmins.retainAll(group.getMembers()); newAdmins.removeAll(group.getAdminMembers()); - if (newAdmins.size() > 0) { + if (!newAdmins.isEmpty()) { for (var admin : newAdmins) { var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true); result = sendUpdateGroupV2Message(group, @@ -653,7 +653,7 @@ public class GroupHelper { if (removeAdmins != null) { final var existingRemoveAdmins = new HashSet<>(removeAdmins); existingRemoveAdmins.retainAll(group.getAdminMembers()); - if (existingRemoveAdmins.size() > 0) { + if (!existingRemoveAdmins.isEmpty()) { for (var admin : existingRemoveAdmins) { var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false); result = sendUpdateGroupV2Message(group, @@ -666,7 +666,7 @@ public class GroupHelper { if (banMembers != null) { final var newlyBannedMembers = new HashSet<>(banMembers); newlyBannedMembers.removeAll(group.getBannedMembers()); - if (newlyBannedMembers.size() > 0) { + if (!newlyBannedMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.banMembers(group, newlyBannedMembers); result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); } @@ -675,7 +675,7 @@ public class GroupHelper { if (unbanMembers != null) { var existingUnbanMembers = new HashSet<>(unbanMembers); existingUnbanMembers.retainAll(group.getBannedMembers()); - if (existingUnbanMembers.size() > 0) { + if (!existingUnbanMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.unbanMembers(group, existingUnbanMembers); result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); } @@ -745,7 +745,7 @@ public class GroupHelper { if (currentAdmins.contains(account.getSelfRecipientId()) && currentAdmins.size() == 1 && groupInfoV2.getMembers().size() > 1 - && newAdmins.size() == 0) { + && newAdmins.isEmpty()) { // Last admin can't leave the group, unless she's also the last member throw new LastGroupAdminException(groupInfoV2.getGroupId(), groupInfoV2.getTitle()); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java index 51c1e9ca..49758486 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SendHelper.java @@ -465,7 +465,7 @@ public class SendHelper { : getSenderKeyCapableRecipientIds(recipientIds); final var allResults = new ArrayList(recipientIds.size()); - if (senderKeyTargets.size() > 0) { + if (!senderKeyTargets.isEmpty()) { final var results = sendGroupMessageInternalWithSenderKey(senderKeySender, senderKeyTargets, distributionId, @@ -479,7 +479,7 @@ public class SendHelper { .filter(r -> !r.isSuccess()) .map(r -> context.getRecipientHelper().resolveRecipient(r.getAddress())) .toList(); - if (failedTargets.size() > 0) { + if (!failedTargets.isEmpty()) { senderKeyTargets = new HashSet<>(senderKeyTargets); failedTargets.forEach(senderKeyTargets::remove); } @@ -490,8 +490,8 @@ public class SendHelper { legacyTargets.removeAll(senderKeyTargets); final boolean onlyTargetIsSelfWithLinkedDevice = recipientIds.isEmpty() && account.isMultiDevice(); - if (legacyTargets.size() > 0 || onlyTargetIsSelfWithLinkedDevice) { - if (legacyTargets.size() > 0) { + if (!legacyTargets.isEmpty() || onlyTargetIsSelfWithLinkedDevice) { + if (!legacyTargets.isEmpty()) { logger.debug("Need to do {} legacy sends.", legacyTargets.size()); } else { logger.debug("Need to do a legacy send to send a sync message for a group of only ourselves."); @@ -499,7 +499,7 @@ public class SendHelper { final List results = sendGroupMessageInternalWithLegacy(legacySender, legacyTargets, - isRecipientUpdate || allResults.size() > 0); + isRecipientUpdate || !allResults.isEmpty()); allResults.addAll(results); } final var duration = Duration.ofMillis(System.currentTimeMillis() - startTime); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java index e8f23cae..113a6920 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/StorageHelper.java @@ -298,7 +298,7 @@ public class StorageHelper { logger.warn("Failed to read storage records, ignoring."); return null; } - return records.size() > 0 ? records.get(0) : null; + return !records.isEmpty() ? records.get(0) : null; } private List getSignalStorageRecords(final Collection storageIds) throws IOException { diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java index 841fd791..a2c90aa4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java @@ -663,13 +663,13 @@ public class ManagerImpl implements Manager { } else { messageBuilder.withBody(message.messageText()); } - if (message.attachments().size() > 0) { + if (!message.attachments().isEmpty()) { messageBuilder.withAttachments(context.getAttachmentHelper().uploadAttachments(message.attachments())); } - if (message.mentions().size() > 0) { + if (!message.mentions().isEmpty()) { messageBuilder.withMentions(resolveMentions(message.mentions())); } - if (message.textStyles().size() > 0) { + if (!message.textStyles().isEmpty()) { messageBuilder.withBodyRanges(message.textStyles().stream().map(TextStyle::toBodyRange).toList()); } if (message.quote().isPresent()) { @@ -715,7 +715,7 @@ public class ManagerImpl implements Manager { manifestSticker.emoji(), AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty()))); } - if (message.previews().size() > 0) { + if (!message.previews().isEmpty()) { final var previews = new ArrayList(message.previews().size()); for (final var p : message.previews()) { final var image = p.image().isPresent() ? context.getAttachmentHelper() @@ -874,7 +874,7 @@ public class ManagerImpl implements Manager { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); } - if (recipients.size() == 0) { + if (recipients.isEmpty()) { return; } final var recipientIds = context.getRecipientHelper().resolveRecipients(recipients); @@ -906,7 +906,7 @@ public class ManagerImpl implements Manager { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); } - if (groupIds.size() == 0) { + if (groupIds.isEmpty()) { return; } boolean shouldRotateProfileKey = false; @@ -1083,7 +1083,7 @@ public class ManagerImpl implements Manager { return true; } synchronized (messageHandlers) { - return messageHandlers.size() > 0; + return !messageHandlers.isEmpty(); } } @@ -1113,7 +1113,7 @@ public class ManagerImpl implements Manager { synchronized (messageHandlers) { receiveThread = null; isReceivingSynchronous = false; - if (messageHandlers.size() > 0) { + if (!messageHandlers.isEmpty()) { startReceiveThreadIfRequired(); } } diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index faea75dc..ef7a8359 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -127,7 +127,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { final var groupCallUpdate = message.groupCallUpdate().get(); writer.indentedWriter().println("Era id: {}", groupCallUpdate.eraId()); } - if (message.previews().size() > 0) { + if (!message.previews().isEmpty()) { writer.println("Previews:"); final var previews = message.previews(); for (var preview : previews) { @@ -135,7 +135,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { printPreview(writer.indentedWriter(), preview); } } - if (message.sharedContacts().size() > 0) { + if (!message.sharedContacts().isEmpty()) { writer.println("Contacts:"); for (var contact : message.sharedContacts()) { writer.println("- Contact:"); @@ -176,19 +176,19 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { final var remoteDelete = message.remoteDeleteId().get(); writer.println("Remote delete message: timestamp = {}", remoteDelete); } - if (message.mentions().size() > 0) { + if (!message.mentions().isEmpty()) { writer.println("Mentions:"); for (var mention : message.mentions()) { printMention(writer, mention); } } - if (message.textStyles().size() > 0) { + if (!message.textStyles().isEmpty()) { writer.println("Text styles:"); for (var textStyle : message.textStyles()) { printTextStyle(writer, textStyle); } } - if (message.attachments().size() > 0) { + if (!message.attachments().isEmpty()) { writer.println("Attachments:"); for (var attachment : message.attachments()) { writer.println("- Attachment:"); @@ -283,7 +283,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { var hangupMessage = callMessage.hangup().get(); writer.println("Hangup message: {}", hangupMessage.id()); } - if (callMessage.iceUpdate().size() > 0) { + if (!callMessage.iceUpdate().isEmpty()) { writer.println("Ice update messages:"); var iceUpdateMessages = callMessage.iceUpdate(); for (var iceUpdateMessage : iceUpdateMessages) { @@ -313,7 +313,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (syncMessage.groups().isPresent()) { writer.println("Received sync groups."); } - if (syncMessage.read().size() > 0) { + if (!syncMessage.read().isEmpty()) { writer.println("Received sync read messages list"); for (var rm : syncMessage.read()) { writer.println("- From: {} Message timestamp: {}", @@ -321,7 +321,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { DateUtils.formatTimestamp(rm.timestamp())); } } - if (syncMessage.viewed().size() > 0) { + if (!syncMessage.viewed().isEmpty()) { writer.println("Received sync viewed messages list"); for (var vm : syncMessage.viewed()) { writer.println("- From: {} Message timestamp: {}", @@ -335,7 +335,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { String to; if (sentTranscriptMessage.destination().isPresent()) { to = formatContact(sentTranscriptMessage.destination().get()); - } else if (sentTranscriptMessage.recipients().size() > 0) { + } else if (!sentTranscriptMessage.recipients().isEmpty()) { to = sentTranscriptMessage.recipients() .stream() .map(this::formatContact) @@ -429,13 +429,13 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (quote.text().isPresent()) { writer.println("Text: {}", quote.text().get()); } - if (quote.mentions() != null && quote.mentions().size() > 0) { + if (quote.mentions() != null && !quote.mentions().isEmpty()) { writer.println("Mentions:"); for (var mention : quote.mentions()) { printMention(writer, mention); } } - if (quote.attachments().size() > 0) { + if (!quote.attachments().isEmpty()) { writer.println("Attachments:"); for (var attachment : quote.attachments()) { writer.println("- Attachment:"); @@ -478,7 +478,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { writer.println("Organisation: {}", contact.organization().get()); } - if (contact.phone().size() > 0) { + if (!contact.phone().isEmpty()) { writer.println("Phone details:"); for (var phone : contact.phone()) { writer.println("- Phone:"); @@ -492,7 +492,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - if (contact.email().size() > 0) { + if (!contact.email().isEmpty()) { writer.println("Email details:"); for (var email : contact.email()) { writer.println("- Email:"); @@ -506,7 +506,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - if (contact.address().size() > 0) { + if (!contact.address().isEmpty()) { writer.println("Address details:"); for (var address : contact.address()) { writer.println("- Address:"); @@ -615,7 +615,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { if (attachment.isGif()) { flags.add("video gif"); } - if (flags.size() > 0) { + if (!flags.isEmpty()) { writer.println("Flags: {}", String.join(", ", flags)); } if (attachment.width().isPresent() || attachment.height().isPresent()) { diff --git a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java index 91272da5..b22ace6c 100644 --- a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java @@ -85,7 +85,7 @@ public class ListGroupsCommand implements JsonRpcLocalCommand { final var groupIdStrings = ns.getList("group-id"); final var groupIds = CommandUtil.getGroupIds(groupIdStrings); - if (groupIds.size() > 0) { + if (!groupIds.isEmpty()) { groups = groups.stream().filter(g -> groupIds.contains(g.groupId())).toList(); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusCommandHandler.java b/src/main/java/org/asamk/signal/dbus/DbusCommandHandler.java index 1ab7cb22..6e7a6ee1 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusCommandHandler.java +++ b/src/main/java/org/asamk/signal/dbus/DbusCommandHandler.java @@ -65,7 +65,7 @@ public class DbusCommandHandler { SignalControl.class); try { final var accounts = control.listAccounts(); - if (accounts.size() == 0) { + if (accounts.isEmpty()) { throw new UserErrorException("No local users found, you first need to register or link an account"); } else if (accounts.size() > 1) { throw new UserErrorException( diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 2e30417c..290ab647 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -244,7 +244,7 @@ public class DbusManagerImpl implements Manager { public SendGroupMessageResults quitGroup( final GroupId groupId, final Set groupAdmins ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException { - if (groupAdmins.size() > 0) { + if (!groupAdmins.isEmpty()) { throw new UnsupportedOperationException(); } final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class); @@ -522,7 +522,7 @@ public class DbusManagerImpl implements Manager { if (isWeakListener) { weakHandlers.add(handler); } else { - if (messageHandlers.size() == 0) { + if (messageHandlers.isEmpty()) { installMessageHandlers(); } messageHandlers.add(handler); @@ -535,7 +535,7 @@ public class DbusManagerImpl implements Manager { synchronized (messageHandlers) { weakHandlers.remove(handler); messageHandlers.remove(handler); - if (messageHandlers.size() == 0) { + if (messageHandlers.isEmpty()) { uninstallMessageHandlers(); } } @@ -544,7 +544,7 @@ public class DbusManagerImpl implements Manager { @Override public boolean isReceiving() { synchronized (messageHandlers) { - return messageHandlers.size() > 0; + return !messageHandlers.isEmpty(); } } @@ -622,7 +622,7 @@ public class DbusManagerImpl implements Manager { return null; } final var contactName = signal.getContactName(n); - if (onlyContacts && contactName.length() == 0) { + if (onlyContacts && contactName.isEmpty()) { return null; } if (name.isPresent() && !name.get().equals(contactName)) { @@ -721,7 +721,7 @@ public class DbusManagerImpl implements Manager { this.notify(); } synchronized (messageHandlers) { - if (messageHandlers.size() > 0) { + if (!messageHandlers.isEmpty()) { uninstallMessageHandlers(); } weakHandlers.clear(); @@ -745,7 +745,7 @@ public class DbusManagerImpl implements Manager { .map(RecipientIdentifier.Single.class::cast) .map(RecipientIdentifier.Single::getIdentifier) .toList(); - if (singleRecipients.size() > 0) { + if (!singleRecipients.isEmpty()) { timestamp = recipientsHandler.apply(singleRecipients); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusReceiveMessageHandler.java b/src/main/java/org/asamk/signal/dbus/DbusReceiveMessageHandler.java index 6aac76a8..0ce114cd 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/dbus/DbusReceiveMessageHandler.java @@ -131,7 +131,7 @@ public class DbusReceiveMessageHandler implements Manager.ReceiveMessageHandler private List getAttachments(MessageEnvelope.Data message) { var attachments = new ArrayList(); - if (message.attachments().size() > 0) { + if (!message.attachments().isEmpty()) { for (var attachment : message.attachments()) { if (attachment.file().isPresent()) { attachments.add(attachment.file().get().getAbsolutePath()); @@ -143,7 +143,7 @@ public class DbusReceiveMessageHandler implements Manager.ReceiveMessageHandler private HashMap> getMessageExtras(MessageEnvelope.Data message) { var extras = new HashMap>(); - if (message.attachments().size() > 0) { + if (!message.attachments().isEmpty()) { var attachments = message.attachments() .stream() .filter(a -> a.id().isPresent()) @@ -151,7 +151,7 @@ public class DbusReceiveMessageHandler implements Manager.ReceiveMessageHandler .toList(); extras.put("attachments", new Variant<>(attachments, "aa{sv}")); } - if (message.mentions().size() > 0) { + if (!message.mentions().isEmpty()) { var mentions = message.mentions().stream().map(this::getMentionMap).toList(); extras.put("mentions", new Variant<>(mentions, "aa{sv}")); } diff --git a/src/main/java/org/asamk/signal/http/ServerSentEventSender.java b/src/main/java/org/asamk/signal/http/ServerSentEventSender.java index b33d3310..a1452a63 100644 --- a/src/main/java/org/asamk/signal/http/ServerSentEventSender.java +++ b/src/main/java/org/asamk/signal/http/ServerSentEventSender.java @@ -35,7 +35,7 @@ public class ServerSentEventSender { writer.write(event); writer.write("\n"); } - if (data.size() == 0) { + if (data.isEmpty()) { writer.write("data\n"); } else { for (final var d : data) { diff --git a/src/main/java/org/asamk/signal/json/JsonDataMessage.java b/src/main/java/org/asamk/signal/json/JsonDataMessage.java index 6da7145a..ef742585 100644 --- a/src/main/java/org/asamk/signal/json/JsonDataMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonDataMessage.java @@ -38,27 +38,27 @@ record JsonDataMessage( final var reaction = dataMessage.reaction().map(JsonReaction::from).orElse(null); final var quote = dataMessage.quote().isPresent() ? JsonQuote.from(dataMessage.quote().get()) : null; final var payment = dataMessage.payment().isPresent() ? JsonPayment.from(dataMessage.payment().get()) : null; - final var mentions = dataMessage.mentions().size() > 0 ? dataMessage.mentions() + final var mentions = !dataMessage.mentions().isEmpty() ? dataMessage.mentions() .stream() .map(JsonMention::from) .toList() : null; - final var previews = dataMessage.previews().size() > 0 ? dataMessage.previews() + final var previews = !dataMessage.previews().isEmpty() ? dataMessage.previews() .stream() .map(JsonPreview::from) .toList() : null; final var remoteDelete = dataMessage.remoteDeleteId().isPresent() ? new JsonRemoteDelete(dataMessage.remoteDeleteId().get()) : null; - final var attachments = dataMessage.attachments().size() > 0 ? dataMessage.attachments() + final var attachments = !dataMessage.attachments().isEmpty() ? dataMessage.attachments() .stream() .map(JsonAttachment::from) .toList() : null; final var sticker = dataMessage.sticker().isPresent() ? JsonSticker.from(dataMessage.sticker().get()) : null; - final var contacts = dataMessage.sharedContacts().size() > 0 ? dataMessage.sharedContacts() + final var contacts = !dataMessage.sharedContacts().isEmpty() ? dataMessage.sharedContacts() .stream() .map(JsonSharedContact::from) .toList() : null; - final var textStyles = dataMessage.textStyles().size() > 0 ? dataMessage.textStyles() + final var textStyles = !dataMessage.textStyles().isEmpty() ? dataMessage.textStyles() .stream() .map(JsonTextStyle::from) .toList() : null; diff --git a/src/main/java/org/asamk/signal/json/JsonQuote.java b/src/main/java/org/asamk/signal/json/JsonQuote.java index 94f3f52c..514f3db9 100644 --- a/src/main/java/org/asamk/signal/json/JsonQuote.java +++ b/src/main/java/org/asamk/signal/json/JsonQuote.java @@ -26,16 +26,16 @@ public record JsonQuote( final var authorUuid = address.uuid().map(UUID::toString).orElse(null); final var text = quote.text().orElse(null); - final var mentions = quote.mentions().size() > 0 + final var mentions = !quote.mentions().isEmpty() ? quote.mentions().stream().map(JsonMention::from).toList() : null; - final var attachments = quote.attachments().size() > 0 ? quote.attachments() + final var attachments = !quote.attachments().isEmpty() ? quote.attachments() .stream() .map(JsonQuotedAttachment::from) .toList() : List.of(); - final var textStyles = quote.textStyles().size() > 0 ? quote.textStyles() + final var textStyles = !quote.textStyles().isEmpty() ? quote.textStyles() .stream() .map(JsonTextStyle::from) .toList() : null; diff --git a/src/main/java/org/asamk/signal/json/JsonSharedContact.java b/src/main/java/org/asamk/signal/json/JsonSharedContact.java index e71feda6..d898c63f 100644 --- a/src/main/java/org/asamk/signal/json/JsonSharedContact.java +++ b/src/main/java/org/asamk/signal/json/JsonSharedContact.java @@ -19,15 +19,15 @@ public record JsonSharedContact( final var name = JsonContactName.from(contact.name()); final var avatar = contact.avatar().isPresent() ? JsonContactAvatar.from(contact.avatar().get()) : null; - final var phone = contact.phone().size() > 0 + final var phone = !contact.phone().isEmpty() ? contact.phone().stream().map(JsonContactPhone::from).toList() : null; - final var email = contact.email().size() > 0 + final var email = !contact.email().isEmpty() ? contact.email().stream().map(JsonContactEmail::from).toList() : null; - final var address = contact.address().size() > 0 ? contact.address() + final var address = !contact.address().isEmpty() ? contact.address() .stream() .map(JsonContactAddress::from) .toList() : null; diff --git a/src/main/java/org/asamk/signal/json/JsonSyncMessage.java b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java index 98c3571e..fbe11daf 100644 --- a/src/main/java/org/asamk/signal/json/JsonSyncMessage.java +++ b/src/main/java/org/asamk/signal/json/JsonSyncMessage.java @@ -45,7 +45,7 @@ record JsonSyncMessage( blockedGroupIds = null; } - final var readMessages = syncMessage.read().size() > 0 ? syncMessage.read() + final var readMessages = !syncMessage.read().isEmpty() ? syncMessage.read() .stream() .map(JsonSyncReadMessage::from) .toList() : null; diff --git a/src/main/java/org/asamk/signal/jsonrpc/JsonRpcReader.java b/src/main/java/org/asamk/signal/jsonrpc/JsonRpcReader.java index 16f5fbd0..25e5c66c 100644 --- a/src/main/java/org/asamk/signal/jsonrpc/JsonRpcReader.java +++ b/src/main/java/org/asamk/signal/jsonrpc/JsonRpcReader.java @@ -127,7 +127,7 @@ public class JsonRpcReader { Util.closeExecutorService(executor); } - if (responseList.size() > 0) { + if (!responseList.isEmpty()) { jsonRpcSender.sendBatchResponses(responseList); } } @@ -193,7 +193,7 @@ public class JsonRpcReader { null), null)); return null; } else if (jsonNode.isArray()) { - if (jsonNode.size() == 0) { + if (jsonNode.isEmpty()) { jsonRpcSender.sendResponse(JsonRpcResponse.forError(new JsonRpcResponse.Error(JsonRpcResponse.Error.INVALID_REQUEST, "invalid request", null), null)); diff --git a/src/main/java/org/asamk/signal/util/Util.java b/src/main/java/org/asamk/signal/util/Util.java index 948560af..e193d253 100644 --- a/src/main/java/org/asamk/signal/util/Util.java +++ b/src/main/java/org/asamk/signal/util/Util.java @@ -41,12 +41,12 @@ public class Util { } private static String toCamelCaseString(List strings) { - if (strings.size() == 0) { + if (strings.isEmpty()) { return ""; } return strings.get(0) + strings.stream() .skip(1) - .filter(s -> s.length() > 0) + .filter(s -> !s.isEmpty()) .map(s -> Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase(Locale.ROOT)) .collect(Collectors.joining()); } -- 2.51.0