From db9acaf4ff31540b954f67504b893e4113a4529a Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 18 Feb 2024 22:14:09 +0100 Subject: [PATCH 001/279] Set snapshot version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7acc5ced..cc3f0258 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.0" } -version = "0.13.0" +version = "0.13.1-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_21 From 6f407ab5094a1d492d7fd54d2988606f12b7f7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Mon, 19 Feb 2024 08:20:24 +0100 Subject: [PATCH 002/279] Fix typos (#1459) --- CHANGELOG.md | 2 +- README.md | 2 +- .../signal/manager/internal/SignalWebSocketHealthMonitor.java | 2 +- .../org/asamk/signal/manager/storage/groups/GroupStore.java | 4 ++-- src/main/java/org/asamk/signal/commands/TrustCommand.java | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e7db200..ecaead9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -471,7 +471,7 @@ - Improve exit code for message sending. Exit with 0 status code if the message was sent successfully to at least one recipient, otherwise exit with status code 2 or 4 (for untrusted). -- Download profiles in parallel for improved perfomance +- Download profiles in parallel for improved performance - `--verbose` flag can be specified multiple times for additional log output - Enable more security options for systemd service file - Rename sandbox to staging environment, to match the upstream name. diff --git a/README.md b/README.md index 6b87a745..a4d6cc53 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ version installed, you can replace `./gradlew` with `gradle` in the following st It is possible to build a native binary with [GraalVM](https://www.graalvm.org). This is still experimental and will not work in all situations. -1. [Install GraalVM and setup the enviroment](https://www.graalvm.org/docs/getting-started/#install-graalvm) +1. [Install GraalVM and setup the environment](https://www.graalvm.org/docs/getting-started/#install-graalvm) 2. Execute Gradle: ./gradlew nativeCompile diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java index 02bdd49e..3ebbe4f3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java @@ -147,7 +147,7 @@ final class SignalWebSocketHealthMonitor implements HealthMonitor { } } } catch (Throwable e) { - logger.warn("Error occured in KeepAliveSender, ignoring ...", e); + logger.warn("Error occurred in KeepAliveSender, ignoring ...", e); } } } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java index 5ebac9c5..e24816bc 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java @@ -733,7 +733,7 @@ public class GroupStore { final var expirationTime = resultSet.getInt("expiration_time"); final var blocked = resultSet.getBoolean("blocked"); final var archived = resultSet.getBoolean("archived"); - final var storagRecord = resultSet.getBytes("storage_record"); + final var storageRecord = resultSet.getBytes("storage_record"); return new GroupInfoV1(GroupId.v1(groupId), groupIdV2 == null ? null : GroupId.v2(groupIdV2), name, @@ -742,7 +742,7 @@ public class GroupStore { expirationTime, blocked, archived, - storagRecord); + storageRecord); } private GroupInfoV2 getGroupV2ByV1Id(final Connection connection, final GroupIdV1 groupId) throws SQLException { diff --git a/src/main/java/org/asamk/signal/commands/TrustCommand.java b/src/main/java/org/asamk/signal/commands/TrustCommand.java index 9b9735d3..a130c03c 100644 --- a/src/main/java/org/asamk/signal/commands/TrustCommand.java +++ b/src/main/java/org/asamk/signal/commands/TrustCommand.java @@ -35,8 +35,8 @@ public class TrustCommand implements JsonRpcLocalCommand { public void handleCommand( final Namespace ns, final Manager m, final OutputWriter outputWriter ) throws CommandException { - var recipentString = ns.getString("recipient"); - var recipient = CommandUtil.getSingleRecipientIdentifier(recipentString, m.getSelfNumber()); + var recipientString = ns.getString("recipient"); + var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber()); if (Boolean.TRUE.equals(ns.getBoolean("trust-all-known-keys"))) { try { final var res = m.trustIdentityAllKeys(recipient); From 7bc7242f08a8ff558d9fa5bb0df06321df386819 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 20 Feb 2024 11:03:15 +0100 Subject: [PATCH 003/279] Add uniqueness check to db migration --- .../java/org/asamk/signal/manager/storage/AccountDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java index 014009c4..b1672f01 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java @@ -556,7 +556,7 @@ public class AccountDatabase extends Database { profile_capabilities TEXT ) STRICT; INSERT INTO recipient2 (_id, aci, pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities) - SELECT r._id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.uuid AND t.address not like 'PNI:%') aci, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.pni AND t.address like 'PNI:%') pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities + SELECT r._id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.uuid AND t.address not like 'PNI:%') aci, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.pni AND t.address like 'PNI:%' AND t.address NOT IN (SELECT pni FROM recipient2)) pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities FROM recipient r; DROP TABLE recipient; ALTER TABLE recipient2 RENAME TO recipient; From f1e3b5c9ccef84c08f20138a160c7778fe52c4da Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 20 Feb 2024 17:05:56 +0100 Subject: [PATCH 004/279] Add uniqueness check to db migration --- .../java/org/asamk/signal/manager/storage/AccountDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java index b1672f01..af498359 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java @@ -556,7 +556,7 @@ public class AccountDatabase extends Database { profile_capabilities TEXT ) STRICT; INSERT INTO recipient2 (_id, aci, pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities) - SELECT r._id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.uuid AND t.address not like 'PNI:%') aci, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.pni AND t.address like 'PNI:%' AND t.address NOT IN (SELECT pni FROM recipient2)) pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities + SELECT r._id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.uuid AND t.address not like 'PNI:%') aci, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.pni AND t.address like 'PNI:%' AND (SELECT COUNT(pni) FROM recipient WHERE pni = r.pni) = 1) pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities FROM recipient r; DROP TABLE recipient; ALTER TABLE recipient2 RENAME TO recipient; From 59c1f4eed23d695cb701b26339e4006f99b3d2c2 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 20 Feb 2024 17:37:31 +0100 Subject: [PATCH 005/279] Show information when requesting voice verification without SMS verification Fixes #1373 --- lib/src/main/java/org/asamk/signal/manager/Manager.java | 3 ++- .../org/asamk/signal/manager/RegistrationManager.java | 3 ++- .../api/VerificationMethoNotAvailableException.java | 8 ++++++++ .../org/asamk/signal/manager/helper/AccountHelper.java | 3 ++- .../org/asamk/signal/manager/internal/ManagerImpl.java | 3 ++- .../signal/manager/internal/RegistrationManagerImpl.java | 3 ++- .../signal/manager/util/NumberVerificationUtils.java | 7 +++++-- .../java/org/asamk/signal/commands/RegisterCommand.java | 7 +++++++ .../asamk/signal/commands/StartChangeNumberCommand.java | 7 +++++++ .../java/org/asamk/signal/dbus/DbusSignalControlImpl.java | 3 ++- 10 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 lib/src/main/java/org/asamk/signal/manager/api/VerificationMethoNotAvailableException.java diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index f4d330a4..76b6a85c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -43,6 +43,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; +import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; @@ -125,7 +126,7 @@ public interface Manager extends Closeable { void startChangeNumber( String newNumber, boolean voiceVerification, String captcha - ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException; + ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethoNotAvailableException; void finishChangeNumber( String newNumber, String verificationCode, String pin diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index 8ba2bf70..365dd399 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -5,6 +5,7 @@ import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; +import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; import java.io.Closeable; import java.io.IOException; @@ -13,7 +14,7 @@ public interface RegistrationManager extends Closeable { void register( boolean voiceVerification, String captcha - ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException; + ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethoNotAvailableException; void verifyAccount( String verificationCode, String pin diff --git a/lib/src/main/java/org/asamk/signal/manager/api/VerificationMethoNotAvailableException.java b/lib/src/main/java/org/asamk/signal/manager/api/VerificationMethoNotAvailableException.java new file mode 100644 index 00000000..172e44e4 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/VerificationMethoNotAvailableException.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager.api; + +public class VerificationMethoNotAvailableException extends Exception { + + public VerificationMethoNotAvailableException() { + super("Invalid verification method"); + } +} 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 1f44e2a3..06b1fe80 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 @@ -7,6 +7,7 @@ import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; +import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; import org.asamk.signal.manager.internal.SignalDependencies; import org.asamk.signal.manager.jobs.SyncStorageJob; import org.asamk.signal.manager.storage.SignalAccount; @@ -164,7 +165,7 @@ public class AccountHelper { public void startChangeNumber( String newNumber, boolean voiceVerification, String captcha - ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException { + ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethoNotAvailableException { final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword()); String sessionId = NumberVerificationUtils.handleVerificationSession(accountManager, account.getSessionId(newNumber), 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 a25c92c0..2caffc38 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 @@ -64,6 +64,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; +import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.helper.AccountFileUpdater; import org.asamk.signal.manager.helper.Context; @@ -374,7 +375,7 @@ public class ManagerImpl implements Manager { @Override public void startChangeNumber( String newNumber, boolean voiceVerification, String captcha - ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException { + ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethoNotAvailableException { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); } 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 4518fb01..2df7ff4b 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 @@ -24,6 +24,7 @@ import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.UpdateProfile; +import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.helper.AccountFileUpdater; @@ -104,7 +105,7 @@ public class RegistrationManagerImpl implements RegistrationManager { @Override public void register( boolean voiceVerification, String captcha - ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException { + ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethoNotAvailableException { if (account.isRegistered() && account.getServiceEnvironment() != null && account.getServiceEnvironment() != serviceEnvironmentConfig.type()) { diff --git a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java index a5c91f5b..9eb01cdb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java @@ -6,6 +6,7 @@ import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; +import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; import org.asamk.signal.manager.helper.PinHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,7 +35,7 @@ public class NumberVerificationUtils { Consumer sessionIdSaver, boolean voiceVerification, String captcha - ) throws CaptchaRequiredException, IOException, RateLimitException { + ) throws CaptchaRequiredException, IOException, RateLimitException, VerificationMethoNotAvailableException { RegistrationSessionMetadataResponse sessionResponse; try { sessionResponse = getValidSession(accountManager, sessionId); @@ -61,7 +62,9 @@ public class NumberVerificationUtils { final var nextAttempt = voiceVerification ? sessionResponse.getBody().getNextCall() : sessionResponse.getBody().getNextSms(); - if (nextAttempt != null && nextAttempt > 0) { + if (nextAttempt == null) { + throw new VerificationMethoNotAvailableException(); + } else if (nextAttempt > 0) { final var timestamp = sessionResponse.getHeaders().getTimestamp() + nextAttempt * 1000; throw new RateLimitException(timestamp); } diff --git a/src/main/java/org/asamk/signal/commands/RegisterCommand.java b/src/main/java/org/asamk/signal/commands/RegisterCommand.java index 876d1a8a..4776ac6e 100644 --- a/src/main/java/org/asamk/signal/commands/RegisterCommand.java +++ b/src/main/java/org/asamk/signal/commands/RegisterCommand.java @@ -15,6 +15,7 @@ import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.RateLimitException; +import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; import org.asamk.signal.output.JsonWriter; import org.asamk.signal.util.CommandUtil; @@ -79,6 +80,12 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration } catch (IOException e) { throw new IOErrorException("Failed to register: %s (%s)".formatted(e.getMessage(), e.getClass().getSimpleName()), e); + } catch (VerificationMethoNotAvailableException e) { + throw new UserErrorException("Failed to register: " + e.getMessage() + ( + voiceVerification + ? ": Before requesting voice verification you need to request SMS verification and wait a minute." + : "" + ), e); } } diff --git a/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java b/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java index 7b18dbf0..823a0c92 100644 --- a/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java +++ b/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java @@ -13,6 +13,7 @@ import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.NotPrimaryDeviceException; import org.asamk.signal.manager.api.RateLimitException; +import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; import org.asamk.signal.output.OutputWriter; import org.asamk.signal.util.CommandUtil; @@ -59,6 +60,12 @@ public class StartChangeNumberCommand implements JsonRpcLocalCommand { } catch (IOException e) { throw new IOErrorException("Failed to change number: %s (%s)".formatted(e.getMessage(), e.getClass().getSimpleName()), e); + } catch (VerificationMethoNotAvailableException e) { + throw new UserErrorException("Failed to register: " + e.getMessage() + ( + voiceVerification + ? ": Before requesting voice verification you need to request SMS verification and wait a minute." + : "" + ), e); } } } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index f21dd40d..d8465d26 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -13,6 +13,7 @@ import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.UserAlreadyExistsException; +import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; import org.freedesktop.dbus.DBusPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,7 +74,7 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { throw new Error.InvalidNumber(e.getMessage()); } catch (OverlappingFileLockException e) { throw new SignalControl.Error.Failure("Account is already in use"); - } catch (IOException e) { + } catch (IOException | VerificationMethoNotAvailableException e) { throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage()); } } From 08ba774b7135f83bf50d513214f305a4b0db2181 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 20 Feb 2024 17:37:59 +0100 Subject: [PATCH 006/279] Update graalvm buildtools --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index cc3f0258..f07d250b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { application eclipse `check-lib-versions` - id("org.graalvm.buildtools.native") version "0.10.0" + id("org.graalvm.buildtools.native") version "0.10.1" } version = "0.13.1-SNAPSHOT" From 0bb2a64781ccb26f7ddc85e85f434066a97bad23 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 22 Feb 2024 19:59:54 +0100 Subject: [PATCH 007/279] Add missing field handling in account record processor --- .../signal/manager/syncStorage/AccountRecordProcessor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java index acce82e6..897f4291 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java @@ -102,6 +102,8 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor Date: Thu, 22 Feb 2024 19:59:19 +0100 Subject: [PATCH 008/279] Allow setting a username with explicit descriminator Fixes #1469 --- graalvm-config-dir/jni-config.json | 4 +++ .../signal/manager/helper/AccountHelper.java | 25 +++++++++++++++++-- .../signal/manager/internal/ManagerImpl.java | 6 ++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index 5b47a7b6..b79debed 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -165,6 +165,10 @@ { "name":"org.signal.libsignal.protocol.state.SignedPreKeyStore" }, +{ + "name":"org.signal.libsignal.usernames.BadDiscriminatorCharacterException", + "methods":[{"name":"","parameterTypes":["java.lang.String"] }] +}, { "name":"org.signal.libsignal.usernames.BadNicknameCharacterException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] 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 06b1fe80..5abac1e7 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 @@ -321,21 +321,42 @@ public class AccountHelper { public static final int USERNAME_MIN_LENGTH = 3; public static final int USERNAME_MAX_LENGTH = 32; - public void reserveUsername(String nickname) throws IOException, BaseUsernameException { + public void reserveUsernameFromNickname(String nickname) throws IOException, BaseUsernameException { final var currentUsername = account.getUsername(); if (currentUsername != null) { final var currentNickname = currentUsername.substring(0, currentUsername.indexOf('.')); if (currentNickname.equals(nickname)) { try { refreshCurrentUsername(); + return; } catch (IOException | BaseUsernameException e) { logger.warn("[reserveUsername] Failed to refresh current username, trying to claim new username"); } - return; } } final var candidates = Username.candidatesFrom(nickname, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH); + reserveUsername(candidates); + } + + public void reserveExactUsername(String username) throws IOException, BaseUsernameException { + final var currentUsername = account.getUsername(); + if (currentUsername != null) { + if (currentUsername.equals(username)) { + try { + refreshCurrentUsername(); + return; + } catch (IOException | BaseUsernameException e) { + logger.warn("[reserveUsername] Failed to refresh current username, trying to claim new username"); + } + } + } + + final var candidates = List.of(new Username(username)); + reserveUsername(candidates); + } + + private void reserveUsername(final List candidates) throws IOException { final var candidateHashes = new ArrayList(); for (final var candidate : candidates) { candidateHashes.add(Base64.encodeUrlSafeWithoutPadding(candidate.getHash())); 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 2caffc38..51a0e5ac 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 @@ -361,7 +361,11 @@ public class ManagerImpl implements Manager { @Override public void setUsername(final String username) throws IOException, InvalidUsernameException { try { - context.getAccountHelper().reserveUsername(username); + if (username.contains(".")) { + context.getAccountHelper().reserveExactUsername(username); + } else { + context.getAccountHelper().reserveUsernameFromNickname(username); + } } catch (BaseUsernameException e) { throw new InvalidUsernameException(e.getMessage() + " (" + e.getClass().getSimpleName() + ")", e); } From c9002d94811c487dcba4ab06849d010a87fd767c Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 25 Feb 2024 17:46:09 +0100 Subject: [PATCH 009/279] Ignore failure when uploading PNI prekeys Can happen if PNI identity key hasn't been sent to the server yet. --- .../java/org/asamk/signal/manager/helper/PreKeyHelper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java index a2b4ddde..59ecf160 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java @@ -14,6 +14,7 @@ import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.account.PreKeyUpload; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; +import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.internal.push.OneTimePreKeyCounts; import java.io.IOException; @@ -171,6 +172,11 @@ public class PreKeyHelper { } catch (AuthorizationFailedException e) { // This can happen when the primary device has changed phone number logger.warn("Failed to updated pre keys: {}", e.getMessage()); + } catch (NonSuccessfulResponseCodeException e) { + if (serviceIdType != ServiceIdType.PNI || e.getCode() != 422) { + throw e; + } + logger.warn("Failed to update PNI pre keys, ignoring."); } return needsReset; } From 2f3c064462a0e8f0ddec931d02203003a5e5ebdb Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 25 Feb 2024 17:46:27 +0100 Subject: [PATCH 010/279] Update documentation --- man/signal-cli.1.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index a626ab61..4b59c754 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -151,8 +151,9 @@ Can fix problems with receiving messages. *-n* NAME, *--device-name* NAME:: Set a new device name for the primary or linked device -*--username* NAME:: +*-u* NAME *--username* NAME:: Specify a username that can then be used to contact this account. +This can either be just the nickname (e.g. test) or the complete username with discriminator (e.g. test.000). Returns the new username with discriminator and the username link. *--delete-username*:: From b76964f219a388ab46ecd0f6cb1f5482ba2e9eda Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 25 Feb 2024 17:54:18 +0100 Subject: [PATCH 011/279] Improve warning message --- .../main/java/org/asamk/signal/manager/helper/PreKeyHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java index 59ecf160..864ef656 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java @@ -176,7 +176,7 @@ public class PreKeyHelper { if (serviceIdType != ServiceIdType.PNI || e.getCode() != 422) { throw e; } - logger.warn("Failed to update PNI pre keys, ignoring."); + logger.warn("Failed to set PNI pre keys, ignoring for now. Account needs to be reregistered to fix this."); } return needsReset; } From 22ac3cb50f9367f3a4728a4e276c3962e3e72596 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 25 Feb 2024 18:12:36 +0100 Subject: [PATCH 012/279] Removing linked devices only works on the primary device --- lib/src/main/java/org/asamk/signal/manager/Manager.java | 2 +- .../java/org/asamk/signal/manager/internal/ManagerImpl.java | 5 ++++- .../java/org/asamk/signal/commands/RemoveDeviceCommand.java | 4 ++++ src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 76b6a85c..7e0c1120 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -140,7 +140,7 @@ public interface Manager extends Closeable { List getLinkedDevices() throws IOException; - void removeLinkedDevices(int deviceId) throws IOException; + void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException; void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException; 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 51a0e5ac..5ef30db0 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 @@ -436,7 +436,10 @@ public class ManagerImpl implements Manager { } @Override - public void removeLinkedDevices(int deviceId) throws IOException { + public void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException { + if (!account.isPrimaryDevice()) { + throw new NotPrimaryDeviceException(); + } context.getAccountHelper().removeLinkedDevices(deviceId); } diff --git a/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java b/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java index 24b3f81e..68a53616 100644 --- a/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java +++ b/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java @@ -5,7 +5,9 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.IOErrorException; +import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.api.NotPrimaryDeviceException; import org.asamk.signal.output.OutputWriter; import java.io.IOException; @@ -33,6 +35,8 @@ public class RemoveDeviceCommand implements JsonRpcLocalCommand { try { final var deviceId = ns.getInt("device-id"); m.removeLinkedDevices(deviceId); + } catch (NotPrimaryDeviceException e) { + throw new UserErrorException("This command doesn't work on linked devices."); } catch (IOException e) { throw new IOErrorException("Error while removing device: " + e.getMessage(), e); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 6db2ef63..257283fc 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -1177,6 +1177,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { try { m.removeLinkedDevices(device.id()); updateDevices(); + } catch (NotPrimaryDeviceException e) { + throw new Error.Failure("This command doesn't work on linked devices."); } catch (IOException e) { throw new Error.Failure(e.getMessage()); } From 6c44662496376b97c8008722a8ae2a1210b87823 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 25 Feb 2024 18:27:20 +0100 Subject: [PATCH 013/279] Allow overriding user agent string Not recommended, as it could lead to issues with newer Signal protocol changes. Fixes #1476 --- src/main/java/org/asamk/signal/BaseConfig.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 406c3fb1..9f8eead1 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -1,11 +1,14 @@ package org.asamk.signal; +import java.util.Optional; + public class BaseConfig { public static final String PROJECT_NAME = BaseConfig.class.getPackage().getImplementationTitle(); public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); - static final String USER_AGENT_SIGNAL_ANDROID = "Signal-Android/6.46.0"; + static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) + .orElse("Signal-Android/6.46.0"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From 57164ad7fb4db377699f056efd5e9305c614b759 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 25 Feb 2024 19:41:10 +0100 Subject: [PATCH 014/279] Prevent crash when receiving already migrated group v1 from storage Fixes #1471 --- .../manager/storage/groups/GroupStore.java | 26 +++++++++++-------- .../syncStorage/GroupV1RecordProcessor.java | 18 +++++++------ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java index e24816bc..3dac4876 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java @@ -229,22 +229,26 @@ public class GroupStore { public GroupInfoV1 getOrCreateGroupV1(GroupIdV1 groupId) { try (final var connection = database.getConnection()) { - var group = getGroup(connection, groupId); - - if (group != null) { - return group; - } - - if (getGroupV2ByV1Id(connection, groupId) == null) { - return new GroupInfoV1(groupId); - } - - return null; + return getOrCreateGroupV1(connection, groupId); } catch (SQLException e) { throw new RuntimeException("Failed read from group store", e); } } + public GroupInfoV1 getOrCreateGroupV1(final Connection connection, final GroupIdV1 groupId) throws SQLException { + var group = getGroup(connection, groupId); + + if (group != null) { + return group; + } + + if (getGroupV2ByV1Id(connection, groupId) == null) { + return new GroupInfoV1(groupId); + } + + return null; + } + public GroupInfoV2 getGroupOrPartialMigrate( Connection connection, final GroupMasterKey groupMasterKey ) throws SQLException { diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV1RecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV1RecordProcessor.java index be95591b..14d3c882 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV1RecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/GroupV1RecordProcessor.java @@ -112,14 +112,16 @@ public final class GroupV1RecordProcessor extends DefaultStorageRecordProcessor< final var groupV1Record = update.newRecord(); final var groupIdV1 = GroupId.v1(groupV1Record.getGroupId()); - final var group = account.getGroupStore().getGroup(connection, groupIdV1); - group.setBlocked(groupV1Record.isBlocked()); - account.getGroupStore().updateGroup(connection, group); - account.getGroupStore() - .storeStorageRecord(connection, - group.getGroupId(), - groupV1Record.getId(), - groupV1Record.toProto().encode()); + final var group = account.getGroupStore().getOrCreateGroupV1(connection, groupIdV1); + if (group != null) { + group.setBlocked(groupV1Record.isBlocked()); + account.getGroupStore().updateGroup(connection, group); + account.getGroupStore() + .storeStorageRecord(connection, + group.getGroupId(), + groupV1Record.getId(), + groupV1Record.toProto().encode()); + } } @Override From fc2ae856d2ce80676a7bce190a02099a4019de81 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 25 Feb 2024 19:41:34 +0100 Subject: [PATCH 015/279] Adapt account record processor for linked devices --- .../signal/manager/syncStorage/AccountRecordProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java index 897f4291..31e7a90b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java @@ -90,7 +90,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor Date: Mon, 26 Feb 2024 18:23:37 +0100 Subject: [PATCH 016/279] Update account attributes after setting a pin Ensures that the recovery password gets set immediately. Related to #1447 --- .../main/java/org/asamk/signal/manager/helper/AccountHelper.java | 1 + 1 file changed, 1 insertion(+) 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 5abac1e7..0e21d8b5 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 @@ -500,6 +500,7 @@ public class AccountHelper { dependencies.getAccountManager().enableRegistrationLock(masterKey); account.setRegistrationLockPin(pin); + updateAccountAttributes(); } public void removeRegistrationPin() throws IOException { From df76aa9919fd5f5292da2dc63bf18f8c69a7f19f Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 26 Feb 2024 18:27:09 +0100 Subject: [PATCH 017/279] Default number sharing to NOBODY Matches the official apps behavior. Closes #1472 --- graalvm-config-dir/reflect-config.json | 4 ++++ .../manager/helper/UnidentifiedAccessHelper.java | 2 +- .../signal/manager/syncStorage/StorageSyncModels.java | 11 ++++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 899c6dfc..96961e4c 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -436,6 +436,10 @@ "name":"java.util.concurrent.atomic.Striped64", "fields":[{"name":"base"}, {"name":"cellsBusy"}] }, +{ + "name":"java.util.concurrent.atomic.Striped64$Cell", + "fields":[{"name":"value"}] +}, { "name":"javax.security.auth.x500.X500Principal", "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java index 7c73c80f..dacc97ba 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java @@ -98,7 +98,7 @@ public class UnidentifiedAccessHelper { private byte[] getSenderCertificateFor(final RecipientId recipientId) { final var sharingMode = account.getConfigurationStore().getPhoneNumberSharingMode(); - if (sharingMode == null || sharingMode == PhoneNumberSharingMode.EVERYBODY || ( + if (sharingMode == PhoneNumberSharingMode.EVERYBODY || ( sharingMode == PhoneNumberSharingMode.CONTACTS && account.getContactStore().getContact(recipientId) != null )) { diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncModels.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncModels.java index 8cf06048..92ea4222 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncModels.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncModels.java @@ -16,7 +16,6 @@ import org.whispersystems.signalservice.api.storage.SignalStorageRecord; import org.whispersystems.signalservice.api.util.UuidUtil; import org.whispersystems.signalservice.internal.storage.protos.AccountRecord; import org.whispersystems.signalservice.internal.storage.protos.AccountRecord.UsernameLink; -import org.whispersystems.signalservice.internal.storage.protos.ContactRecord; import org.whispersystems.signalservice.internal.storage.protos.ContactRecord.IdentityState; import java.util.Optional; @@ -38,7 +37,8 @@ public final class StorageSyncModels { public static PhoneNumberSharingMode remoteToLocal(AccountRecord.PhoneNumberSharingMode phoneNumberPhoneNumberSharingMode) { return switch (phoneNumberPhoneNumberSharingMode) { case EVERYBODY -> PhoneNumberSharingMode.EVERYBODY; - case UNKNOWN, NOBODY -> PhoneNumberSharingMode.NOBODY; + case NOBODY -> PhoneNumberSharingMode.NOBODY; + case UNKNOWN -> null; }; } @@ -63,8 +63,9 @@ public final class StorageSyncModels { .orElse(true)) .setLinkPreviewsEnabled(Optional.ofNullable(configStore.getLinkPreviews()).orElse(true)) .setUnlistedPhoneNumber(Optional.ofNullable(configStore.getPhoneNumberUnlisted()).orElse(false)) - .setPhoneNumberSharingMode(localToRemote(Optional.ofNullable(configStore.getPhoneNumberSharingMode()) - .orElse(PhoneNumberSharingMode.EVERYBODY))) + .setPhoneNumberSharingMode(Optional.ofNullable(configStore.getPhoneNumberSharingMode()) + .map(StorageSyncModels::localToRemote) + .orElse(AccountRecord.PhoneNumberSharingMode.UNKNOWN)) .setE164(self.getAddress().number().orElse("")) .setUsername(self.getAddress().username().orElse(null)); if (usernameLinkComponents != null) { @@ -136,7 +137,7 @@ public final class StorageSyncModels { return SignalStorageRecord.forGroupV2(builder.build()); } - public static TrustLevel remoteToLocal(ContactRecord.IdentityState identityState) { + public static TrustLevel remoteToLocal(IdentityState identityState) { return switch (identityState) { case DEFAULT -> TrustLevel.TRUSTED_UNVERIFIED; case UNVERIFIED -> TrustLevel.UNTRUSTED; From e77d9e3d600cd783511eaaefca8c06c400258527 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 26 Feb 2024 22:07:36 +0100 Subject: [PATCH 018/279] Update libsignal-service --- settings.gradle.kts | 2 +- src/main/java/org/asamk/signal/BaseConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 1fcc646a..a98ac404 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") library("logback", "ch.qos.logback", "logback-classic").version("1.5.0") - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_96") + library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_97") library("sqlite", "org.xerial", "sqlite-jdbc").version("3.45.1.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 9f8eead1..8132e719 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/6.46.0"); + .orElse("Signal-Android/7.0.2"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From 189b21dbde0b9981365ee6e39e3645b94d634ef6 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 26 Feb 2024 22:13:57 +0100 Subject: [PATCH 019/279] Improve error message if captcha is rejected by server Fixes #1328 --- .../java/org/asamk/signal/manager/Manager.java | 6 +++++- .../manager/api/CaptchaRejectedException.java | 16 ++++++++++++++++ .../signal/manager/internal/ManagerImpl.java | 12 ++++++++++-- .../SubmitRateLimitChallengeCommand.java | 5 +++++ .../org/asamk/signal/dbus/DbusSignalImpl.java | 5 ++++- 5 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 lib/src/main/java/org/asamk/signal/manager/api/CaptchaRejectedException.java diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 7e0c1120..edc16285 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -2,6 +2,7 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.api.AlreadyReceivingException; import org.asamk.signal.manager.api.AttachmentInvalidException; +import org.asamk.signal.manager.api.CaptchaRejectedException; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Device; @@ -136,7 +137,10 @@ public interface Manager extends Closeable { void deleteAccount() throws IOException; - void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException; + void submitRateLimitRecaptchaChallenge( + String challenge, + String captcha + ) throws IOException, CaptchaRejectedException; List getLinkedDevices() throws IOException; diff --git a/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRejectedException.java b/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRejectedException.java new file mode 100644 index 00000000..a41a168e --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/CaptchaRejectedException.java @@ -0,0 +1,16 @@ +package org.asamk.signal.manager.api; + +public class CaptchaRejectedException extends Exception { + + public CaptchaRejectedException() { + super("Captcha rejected"); + } + + public CaptchaRejectedException(final String message) { + super(message); + } + + public CaptchaRejectedException(final String message, final Throwable cause) { + super(message, cause); + } +} 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 5ef30db0..b7808fc6 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 @@ -19,6 +19,7 @@ package org.asamk.signal.manager.internal; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.AlreadyReceivingException; import org.asamk.signal.manager.api.AttachmentInvalidException; +import org.asamk.signal.manager.api.CaptchaRejectedException; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Device; @@ -407,10 +408,17 @@ public class ManagerImpl implements Manager { } @Override - public void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException { + public void submitRateLimitRecaptchaChallenge( + String challenge, + String captcha + ) throws IOException, CaptchaRejectedException { captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); - dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha); + try { + dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha); + } catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRejectedException ignored) { + throw new CaptchaRejectedException(); + } } @Override diff --git a/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java b/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java index a5e07627..91c6fc67 100644 --- a/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java +++ b/src/main/java/org/asamk/signal/commands/SubmitRateLimitChallengeCommand.java @@ -5,7 +5,9 @@ import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.IOErrorException; +import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.api.CaptchaRejectedException; import org.asamk.signal.output.OutputWriter; import java.io.IOException; @@ -38,6 +40,9 @@ public class SubmitRateLimitChallengeCommand implements JsonRpcLocalCommand { m.submitRateLimitRecaptchaChallenge(challenge, captcha); } catch (IOException e) { throw new IOErrorException("Submit challenge error: " + e.getMessage(), e); + } catch (CaptchaRejectedException e) { + throw new UserErrorException( + "Captcha rejected, it may be outdated, already used or solved from a different IP address."); } } } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 257283fc..65c210fc 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -4,6 +4,7 @@ import org.asamk.Signal; import org.asamk.signal.BaseConfig; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.AttachmentInvalidException; +import org.asamk.signal.manager.api.CaptchaRejectedException; import org.asamk.signal.manager.api.DeviceLinkUrl; import org.asamk.signal.manager.api.GroupId; import org.asamk.signal.manager.api.GroupInviteLinkUrl; @@ -159,8 +160,10 @@ public class DbusSignalImpl implements Signal, AutoCloseable { m.submitRateLimitRecaptchaChallenge(challenge, captcha); } catch (IOException e) { throw new Error.Failure("Submit challenge error: " + e.getMessage()); + } catch (CaptchaRejectedException e) { + throw new Error.Failure( + "Captcha rejected, it may be outdated, already used or solved from a different IP address."); } - } @Override From 2e4cd0eddc6e66bb47d2751006073c6ee828b183 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 27 Feb 2024 17:52:21 +0100 Subject: [PATCH 020/279] Only retry messages after identity was trusted Fixes #1477 --- .../signal/manager/helper/ReceiveHelper.java | 15 +++++---------- .../signal/manager/internal/ManagerImpl.java | 5 ++--- .../signal/manager/storage/SignalAccount.java | 17 ++++++++++++++--- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java index a66d6ff6..a40c134d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java @@ -41,7 +41,6 @@ public class ReceiveHelper { private final Context context; private ReceiveConfig receiveConfig = new ReceiveConfig(false, false, false); - private boolean needsToRetryFailedMessages = false; private boolean hasCaughtUpWithOldMessages = false; private boolean isWaitingForMessage = false; private boolean shouldStop = false; @@ -59,10 +58,6 @@ public class ReceiveHelper { dependencies.setAllowStories(!receiveConfig.ignoreStories()); } - public void setNeedsToRetryFailedMessages(final boolean needsToRetryFailedMessages) { - this.needsToRetryFailedMessages = needsToRetryFailedMessages; - } - public void setAuthenticationFailureListener(final Callable authenticationFailureListener) { this.authenticationFailureListener = authenticationFailureListener; } @@ -90,7 +85,7 @@ public class ReceiveHelper { public void receiveMessages( Duration timeout, boolean returnOnTimeout, Integer maxMessages, Manager.ReceiveMessageHandler handler ) throws IOException { - needsToRetryFailedMessages = true; + account.setNeedsToRetryFailedMessages(true); hasCaughtUpWithOldMessages = false; // Use a Map here because java Set doesn't have a get method ... @@ -130,9 +125,8 @@ public class ReceiveHelper { isWaitingForMessage = false; while (!shouldStop && remainingMessages != 0) { - if (needsToRetryFailedMessages) { + if (account.getNeedsToRetryFailedMessages()) { retryFailedReceivedMessages(handler); - needsToRetryFailedMessages = false; } SignalServiceEnvelope envelope; final CachedMessage[] cachedMessage = {null}; @@ -266,6 +260,7 @@ public class ReceiveHelper { } } handleQueuedActions(queuedActions); + account.setNeedsToRetryFailedMessages(false); } private List retryFailedReceivedMessage( @@ -282,8 +277,8 @@ public class ReceiveHelper { final var exception = result.second(); if (exception instanceof UntrustedIdentityException) { - if (System.currentTimeMillis() - envelope.getServerDeliveredTimestamp() > 1000L * 60 * 60 * 24 * 30) { - // Envelope is more than a month old, cleaning up. + if (System.currentTimeMillis() - envelope.getServerDeliveredTimestamp() > 1000L * 60 * 60 * 24 * 14) { + // Envelope is more than two weeks old, cleaning up. cachedMessage.delete(); return null; } 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 b7808fc6..35ad4d3c 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 @@ -409,8 +409,7 @@ public class ManagerImpl implements Manager { @Override public void submitRateLimitRecaptchaChallenge( - String challenge, - String captcha + String challenge, String captcha ) throws IOException, CaptchaRejectedException { captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); @@ -1389,7 +1388,7 @@ public class ManagerImpl implements Manager { final var recipientId = context.getRecipientHelper().resolveRecipient(recipient); final var updated = trustMethod.apply(recipientId); if (updated && this.isReceiving()) { - context.getReceiveHelper().setNeedsToRetryFailedMessages(true); + account.setNeedsToRetryFailedMessages(true); } return updated; } 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 72725f5c..e8f2db45 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 @@ -149,6 +149,9 @@ public class SignalAccount implements Closeable { private final KeyValueEntry lastReceiveTimestamp = new KeyValueEntry<>("last-receive-timestamp", long.class, 0L); + private final KeyValueEntry needsToRetryFailedMessages = new KeyValueEntry<>("retry-failed-messages", + Boolean.class, + true); private final KeyValueEntry cdsiToken = new KeyValueEntry<>("cdsi-token", byte[].class); private final KeyValueEntry lastRecipientsRefresh = new KeyValueEntry<>("last-recipients-refresh", long.class); @@ -297,7 +300,7 @@ public class SignalAccount implements Closeable { this.pniAccountData.setIdentityKeyPair(pniIdentity); this.registered = false; this.isMultiDevice = true; - getKeyValueStore().storeEntry(lastReceiveTimestamp, 0L); + setLastReceiveTimestamp(0L); this.pinMasterKey = masterKey; getKeyValueStore().storeEntry(storageManifestVersion, -1L); this.setStorageManifest(null); @@ -342,7 +345,7 @@ public class SignalAccount implements Closeable { this.pniAccountData.setServiceId(pni); init(); this.registrationLockPin = pin; - getKeyValueStore().storeEntry(lastReceiveTimestamp, 0L); + setLastReceiveTimestamp(0L); save(); setPreKeys(ServiceIdType.ACI, aciPreKeys); @@ -590,7 +593,7 @@ public class SignalAccount implements Closeable { isMultiDevice = rootNode.get("isMultiDevice").asBoolean(); } if (rootNode.hasNonNull("lastReceiveTimestamp")) { - getKeyValueStore().storeEntry(lastReceiveTimestamp, rootNode.get("lastReceiveTimestamp").asLong()); + setLastReceiveTimestamp(rootNode.get("lastReceiveTimestamp").asLong()); } int registrationId = 0; if (rootNode.hasNonNull("registrationId")) { @@ -1650,6 +1653,14 @@ public class SignalAccount implements Closeable { getKeyValueStore().storeEntry(lastReceiveTimestamp, value); } + public void setNeedsToRetryFailedMessages(final boolean value) { + getKeyValueStore().storeEntry(needsToRetryFailedMessages, value); + } + + public boolean getNeedsToRetryFailedMessages() { + return getKeyValueStore().getEntry(needsToRetryFailedMessages); + } + public boolean isUnrestrictedUnidentifiedAccess() { return Boolean.TRUE.equals(getKeyValueStore().getEntry(unrestrictedUnidentifiedAccess)); } From 2424fc1f531e968da95d18b3954215c897cc2452 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 27 Feb 2024 18:12:43 +0100 Subject: [PATCH 021/279] Add register parameter to force reregistration --- .../signal/manager/RegistrationManager.java | 2 +- .../internal/RegistrationManagerImpl.java | 17 ++++++++++++----- man/signal-cli.1.adoc | 5 ++++- .../asamk/signal/commands/RegisterCommand.java | 14 +++++++++----- .../dbus/DbusRegistrationManagerImpl.java | 5 ++++- .../signal/dbus/DbusSignalControlImpl.java | 2 +- 6 files changed, 31 insertions(+), 14 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index 365dd399..75a91dc9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -13,7 +13,7 @@ import java.io.IOException; public interface RegistrationManager extends Closeable { void register( - boolean voiceVerification, String captcha + boolean voiceVerification, String captcha, final boolean forceRegister ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethoNotAvailableException; void verifyAccount( 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 2df7ff4b..b0725cbd 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 @@ -104,7 +104,7 @@ public class RegistrationManagerImpl implements RegistrationManager { @Override public void register( - boolean voiceVerification, String captcha + boolean voiceVerification, String captcha, final boolean forceRegister ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethoNotAvailableException { if (account.isRegistered() && account.getServiceEnvironment() != null @@ -113,12 +113,18 @@ public class RegistrationManagerImpl implements RegistrationManager { } try { - final var recoveryPassword = account.getRecoveryPassword(); - if (recoveryPassword != null && account.isPrimaryDevice() && attemptReregisterAccount(recoveryPassword)) { - return; + if (!forceRegister) { + if (account.isRegistered()) { + throw new IOException("Account is already registered"); + } + + if (account.getAci() != null && attemptReactivateAccount()) { + return; + } } - if (account.getAci() != null && attemptReactivateAccount()) { + final var recoveryPassword = account.getRecoveryPassword(); + if (recoveryPassword != null && account.isPrimaryDevice() && attemptReregisterAccount(recoveryPassword)) { return; } @@ -128,6 +134,7 @@ public class RegistrationManagerImpl implements RegistrationManager { voiceVerification, captcha); NumberVerificationUtils.requestVerificationCode(accountManager, sessionId, voiceVerification); + account.setRegistered(false); } catch (DeprecatedVersionException e) { logger.debug("Signal-Server returned deprecated version exception", e); throw e; diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 4b59c754..80d4c37e 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -102,12 +102,15 @@ If the account was deleted (with --delete-account) it cannot be reactivated. The verification should be done over voice, not SMS. Voice verification only works if an SMS verification has been attempted before. -*--captcha*:: +*--captcha* 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 After solving the captcha, right-click on the "Open Signal" link and copy the link. +*--reregister*:: +Register even if account is already registered. + === verify Verify the number using the code received via SMS or voice. diff --git a/src/main/java/org/asamk/signal/commands/RegisterCommand.java b/src/main/java/org/asamk/signal/commands/RegisterCommand.java index 4776ac6e..2f03cbb3 100644 --- a/src/main/java/org/asamk/signal/commands/RegisterCommand.java +++ b/src/main/java/org/asamk/signal/commands/RegisterCommand.java @@ -37,14 +37,18 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration .action(Arguments.storeTrue()); subparser.addArgument("--captcha") .help("The captcha token, required if registration failed with a captcha required error."); + subparser.addArgument("--reregister") + .action(Arguments.storeTrue()) + .help("Register even if account is already registered"); } @Override public void handleCommand(final Namespace ns, final RegistrationManager m) throws CommandException { final boolean voiceVerification = Boolean.TRUE.equals(ns.getBoolean("voice")); final var captcha = ns.getString("captcha"); + final var reregister = Boolean.TRUE.equals(ns.getBoolean("reregister")); - register(m, voiceVerification, captcha); + register(m, voiceVerification, captcha, reregister); } @Override @@ -61,14 +65,14 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration public void handleCommand( final RegistrationParams request, final RegistrationManager m, final JsonWriter jsonWriter ) throws CommandException { - register(m, Boolean.TRUE.equals(request.voice()), request.captcha()); + register(m, Boolean.TRUE.equals(request.voice()), request.captcha(), Boolean.TRUE.equals(request.reregister())); } private void register( - final RegistrationManager m, final boolean voiceVerification, final String captcha + final RegistrationManager m, final boolean voiceVerification, final String captcha, final boolean reregister ) throws CommandException { try { - m.register(voiceVerification, captcha); + m.register(voiceVerification, captcha, reregister); } catch (RateLimitException e) { final var message = CommandUtil.getRateLimitMessage(e); throw new RateLimitErrorException(message, e); @@ -89,5 +93,5 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration } } - public record RegistrationParams(Boolean voice, String captcha) {} + public record RegistrationParams(Boolean voice, String captcha, Boolean reregister) {} } diff --git a/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java index 4d43b98c..788e0c91 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java @@ -27,8 +27,11 @@ public class DbusRegistrationManagerImpl implements RegistrationManager { @Override public void register( - final boolean voiceVerification, final String captcha + final boolean voiceVerification, final String captcha, final boolean forceRegister ) throws IOException, CaptchaRequiredException { + if (forceRegister) { + throw new UnsupportedOperationException(); + } if (captcha == null) { signalControl.register(number, voiceVerification); } else { diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index d8465d26..ddf25cd1 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -63,7 +63,7 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { "Invalid account (phone number), make sure you include the country code."); } try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) { - registrationManager.register(voiceVerification, captcha); + registrationManager.register(voiceVerification, captcha, false); } catch (RateLimitException e) { String message = "Rate limit reached"; throw new SignalControl.Error.Failure(message); From 0cc2da690aafa0b821c09dcf9e7166361f15b01a Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 27 Feb 2024 18:18:30 +0100 Subject: [PATCH 022/279] Bump version to 0.13.1 --- CHANGELOG.md | 22 +++++++++++++++++++++- build.gradle.kts | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecaead9a..2f15faf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ # Changelog -## [Unreleased] +## [0.13.1] - 2024-02-27 + +### Added + +- Add `--reregister` parameter to force registration of an already registered account + +### Fixed + +- Fixed rare issue with duplicate PNIs during migration + +### Improved + +- Show information when requesting voice verification without prior SMS verification +- Username can now be set with an explicit discriminator (e.g. testname.000) +- Improve behavior when PNI prekeys upload fails +- Improve `submitRateLimitChallenge` error message if captcha is rejected by server +- Only retry messages after an identity was trusted + +### Changed + +- Default number sharing to NOBODY, to match the official apps behavior. ## [0.13.0] - 2024-02-18 diff --git a/build.gradle.kts b/build.gradle.kts index f07d250b..e9f54923 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.1" } -version = "0.13.1-SNAPSHOT" +version = "0.13.1" java { sourceCompatibility = JavaVersion.VERSION_21 From aebe64571da275068effd505c070b2393a609cad Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 27 Feb 2024 18:20:21 +0100 Subject: [PATCH 023/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f15faf4..50f45f28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.1] - 2024-02-27 ### Added diff --git a/build.gradle.kts b/build.gradle.kts index e9f54923..0d4ebe84 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.1" } -version = "0.13.1" +version = "0.13.2-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_21 From 5e17fe8414b632246bdbd6a9051e6ee98296b2a0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 27 Feb 2024 19:35:38 +0100 Subject: [PATCH 024/279] Fix typo --- lib/src/main/java/org/asamk/signal/manager/Manager.java | 7 +++---- .../org/asamk/signal/manager/RegistrationManager.java | 4 ++-- .../api/VerificationMethoNotAvailableException.java | 8 -------- .../api/VerificationMethodNotAvailableException.java | 8 ++++++++ .../org/asamk/signal/manager/helper/AccountHelper.java | 4 ++-- .../org/asamk/signal/manager/internal/ManagerImpl.java | 4 ++-- .../signal/manager/internal/RegistrationManagerImpl.java | 4 ++-- .../signal/manager/util/NumberVerificationUtils.java | 6 +++--- .../java/org/asamk/signal/commands/RegisterCommand.java | 4 ++-- .../asamk/signal/commands/StartChangeNumberCommand.java | 4 ++-- .../java/org/asamk/signal/dbus/DbusSignalControlImpl.java | 4 ++-- 11 files changed, 28 insertions(+), 29 deletions(-) delete mode 100644 lib/src/main/java/org/asamk/signal/manager/api/VerificationMethoNotAvailableException.java create mode 100644 lib/src/main/java/org/asamk/signal/manager/api/VerificationMethodNotAvailableException.java diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index edc16285..d6cf01d8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -44,7 +44,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; -import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; @@ -127,7 +127,7 @@ public interface Manager extends Closeable { void startChangeNumber( String newNumber, boolean voiceVerification, String captcha - ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethoNotAvailableException; + ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethodNotAvailableException; void finishChangeNumber( String newNumber, String verificationCode, String pin @@ -138,8 +138,7 @@ public interface Manager extends Closeable { void deleteAccount() throws IOException; void submitRateLimitRecaptchaChallenge( - String challenge, - String captcha + String challenge, String captcha ) throws IOException, CaptchaRejectedException; List getLinkedDevices() throws IOException; diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index 75a91dc9..5660bb13 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -5,7 +5,7 @@ import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; -import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import java.io.Closeable; import java.io.IOException; @@ -14,7 +14,7 @@ public interface RegistrationManager extends Closeable { void register( boolean voiceVerification, String captcha, final boolean forceRegister - ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethoNotAvailableException; + ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException; void verifyAccount( String verificationCode, String pin diff --git a/lib/src/main/java/org/asamk/signal/manager/api/VerificationMethoNotAvailableException.java b/lib/src/main/java/org/asamk/signal/manager/api/VerificationMethoNotAvailableException.java deleted file mode 100644 index 172e44e4..00000000 --- a/lib/src/main/java/org/asamk/signal/manager/api/VerificationMethoNotAvailableException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.asamk.signal.manager.api; - -public class VerificationMethoNotAvailableException extends Exception { - - public VerificationMethoNotAvailableException() { - super("Invalid verification method"); - } -} diff --git a/lib/src/main/java/org/asamk/signal/manager/api/VerificationMethodNotAvailableException.java b/lib/src/main/java/org/asamk/signal/manager/api/VerificationMethodNotAvailableException.java new file mode 100644 index 00000000..9ffdb68f --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/VerificationMethodNotAvailableException.java @@ -0,0 +1,8 @@ +package org.asamk.signal.manager.api; + +public class VerificationMethodNotAvailableException extends Exception { + + public VerificationMethodNotAvailableException() { + super("Invalid verification method"); + } +} 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 0e21d8b5..299ce4d3 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 @@ -7,7 +7,7 @@ import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; -import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.asamk.signal.manager.internal.SignalDependencies; import org.asamk.signal.manager.jobs.SyncStorageJob; import org.asamk.signal.manager.storage.SignalAccount; @@ -165,7 +165,7 @@ public class AccountHelper { public void startChangeNumber( String newNumber, boolean voiceVerification, String captcha - ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethoNotAvailableException { + ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException { final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword()); String sessionId = NumberVerificationUtils.handleVerificationSession(accountManager, account.getSessionId(newNumber), 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 35ad4d3c..47a5bc03 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 @@ -65,7 +65,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; -import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.helper.AccountFileUpdater; import org.asamk.signal.manager.helper.Context; @@ -380,7 +380,7 @@ public class ManagerImpl implements Manager { @Override public void startChangeNumber( String newNumber, boolean voiceVerification, String captcha - ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethoNotAvailableException { + ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethodNotAvailableException { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); } 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 b0725cbd..597b15c0 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 @@ -24,7 +24,7 @@ import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.UpdateProfile; -import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.helper.AccountFileUpdater; @@ -105,7 +105,7 @@ public class RegistrationManagerImpl implements RegistrationManager { @Override public void register( boolean voiceVerification, String captcha, final boolean forceRegister - ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethoNotAvailableException { + ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException { if (account.isRegistered() && account.getServiceEnvironment() != null && account.getServiceEnvironment() != serviceEnvironmentConfig.type()) { diff --git a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java index 9eb01cdb..f96e79d3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java @@ -6,7 +6,7 @@ import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; -import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.asamk.signal.manager.helper.PinHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +35,7 @@ public class NumberVerificationUtils { Consumer sessionIdSaver, boolean voiceVerification, String captcha - ) throws CaptchaRequiredException, IOException, RateLimitException, VerificationMethoNotAvailableException { + ) throws CaptchaRequiredException, IOException, RateLimitException, VerificationMethodNotAvailableException { RegistrationSessionMetadataResponse sessionResponse; try { sessionResponse = getValidSession(accountManager, sessionId); @@ -63,7 +63,7 @@ public class NumberVerificationUtils { ? sessionResponse.getBody().getNextCall() : sessionResponse.getBody().getNextSms(); if (nextAttempt == null) { - throw new VerificationMethoNotAvailableException(); + throw new VerificationMethodNotAvailableException(); } else if (nextAttempt > 0) { final var timestamp = sessionResponse.getHeaders().getTimestamp() + nextAttempt * 1000; throw new RateLimitException(timestamp); diff --git a/src/main/java/org/asamk/signal/commands/RegisterCommand.java b/src/main/java/org/asamk/signal/commands/RegisterCommand.java index 2f03cbb3..cd361218 100644 --- a/src/main/java/org/asamk/signal/commands/RegisterCommand.java +++ b/src/main/java/org/asamk/signal/commands/RegisterCommand.java @@ -15,7 +15,7 @@ import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.RateLimitException; -import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.asamk.signal.output.JsonWriter; import org.asamk.signal.util.CommandUtil; @@ -84,7 +84,7 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration } catch (IOException e) { throw new IOErrorException("Failed to register: %s (%s)".formatted(e.getMessage(), e.getClass().getSimpleName()), e); - } catch (VerificationMethoNotAvailableException e) { + } catch (VerificationMethodNotAvailableException e) { throw new UserErrorException("Failed to register: " + e.getMessage() + ( voiceVerification ? ": Before requesting voice verification you need to request SMS verification and wait a minute." diff --git a/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java b/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java index 823a0c92..84fcf527 100644 --- a/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java +++ b/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java @@ -13,7 +13,7 @@ import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.NotPrimaryDeviceException; import org.asamk.signal.manager.api.RateLimitException; -import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.asamk.signal.output.OutputWriter; import org.asamk.signal.util.CommandUtil; @@ -60,7 +60,7 @@ public class StartChangeNumberCommand implements JsonRpcLocalCommand { } catch (IOException e) { throw new IOErrorException("Failed to change number: %s (%s)".formatted(e.getMessage(), e.getClass().getSimpleName()), e); - } catch (VerificationMethoNotAvailableException e) { + } catch (VerificationMethodNotAvailableException e) { throw new UserErrorException("Failed to register: " + e.getMessage() + ( voiceVerification ? ": Before requesting voice verification you need to request SMS verification and wait a minute." diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index ddf25cd1..def343a9 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -13,7 +13,7 @@ import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.UserAlreadyExistsException; -import org.asamk.signal.manager.api.VerificationMethoNotAvailableException; +import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.freedesktop.dbus.DBusPath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,7 +74,7 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { throw new Error.InvalidNumber(e.getMessage()); } catch (OverlappingFileLockException e) { throw new SignalControl.Error.Failure("Account is already in use"); - } catch (IOException | VerificationMethoNotAvailableException e) { + } catch (IOException | VerificationMethodNotAvailableException e) { throw new SignalControl.Error.Failure(e.getClass().getSimpleName() + " " + e.getMessage()); } } From ee39733978159646a087846cd064bc31506d6dd0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 7 Mar 2024 17:44:19 +0100 Subject: [PATCH 025/279] Update libsignal-service --- .../org/asamk/signal/manager/api/Profile.java | 5 +---- .../asamk/signal/manager/config/LiveConfig.java | 7 +++++-- .../manager/config/ServiceEnvironmentConfig.java | 2 ++ .../signal/manager/config/StagingConfig.java | 7 +++++-- .../manager/helper/IncomingMessageHandler.java | 16 +--------------- .../signal/manager/helper/RecipientHelper.java | 1 + .../asamk/signal/manager/helper/SendHelper.java | 12 ------------ .../manager/internal/SignalDependencies.java | 3 ++- .../signal/manager/storage/SignalAccount.java | 3 --- .../storage/profiles/LegacySignalProfile.java | 2 +- .../asamk/signal/manager/util/ProfileUtils.java | 9 --------- settings.gradle.kts | 2 +- 12 files changed, 19 insertions(+), 50 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Profile.java b/lib/src/main/java/org/asamk/signal/manager/api/Profile.java index 3eb58a55..33117188 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Profile.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Profile.java @@ -152,10 +152,7 @@ public class Profile { } public enum Capability { - storage, - gv1Migration, - senderKey, - announcementGroup; + storage; public static Capability valueOfOrNull(String value) { try { diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index 3c477299..2bbdfe19 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -1,5 +1,6 @@ package org.asamk.signal.manager.config; +import org.signal.libsignal.net.Network.Environment; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; @@ -28,7 +29,6 @@ class LiveConfig { .decode("BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF"); private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"; private static final String SVR2_MRENCLAVE = "a6622ad4656e1abcd0bc0ff17c229477747d2ded0495c4ebee7ed35c1789fa97"; - private static final String SVR2_MRENCLAVE_DEPRECATED = "6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094"; private static final String URL = "https://chat.signal.org"; private static final String CDN_URL = "https://cdn.signal.org"; @@ -49,6 +49,8 @@ class LiveConfig { private static final byte[] backupServerPublicParams = Base64.getDecoder() .decode("AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O"); + private static Environment LIBSIGNAL_NET_ENV = Environment.PRODUCTION; + static SignalServiceConfiguration createDefaultServiceConfiguration( final List interceptors ) { @@ -78,10 +80,11 @@ class LiveConfig { static ServiceEnvironmentConfig getServiceEnvironmentConfig(List interceptors) { return new ServiceEnvironmentConfig(LIVE, + LIBSIGNAL_NET_ENV, createDefaultServiceConfiguration(interceptors), getUnidentifiedSenderTrustRoot(), CDSI_MRENCLAVE, - List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_DEPRECATED)); + List.of(SVR2_MRENCLAVE)); } private LiveConfig() { diff --git a/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java index f4622064..8f9f8ce3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/ServiceEnvironmentConfig.java @@ -1,6 +1,7 @@ package org.asamk.signal.manager.config; import org.asamk.signal.manager.api.ServiceEnvironment; +import org.signal.libsignal.net.Network; import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration; @@ -8,6 +9,7 @@ import java.util.List; public record ServiceEnvironmentConfig( ServiceEnvironment type, + Network.Environment netEnvironment, SignalServiceConfiguration signalServiceConfiguration, ECPublicKey unidentifiedSenderTrustRoot, String cdsiMrenclave, diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index 83bb7be6..e99b049c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -1,5 +1,6 @@ package org.asamk.signal.manager.config; +import org.signal.libsignal.net.Network; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; @@ -28,7 +29,6 @@ class StagingConfig { .decode("BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"); private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"; private static final String SVR2_MRENCLAVE = "acb1973aa0bbbd14b3b4e06f145497d948fd4a98efc500fcce363b3b743ec482"; - private static final String SVR2_MRENCLAVE_DEPRECATED = "a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95"; private static final String URL = "https://chat.staging.signal.org"; private static final String CDN_URL = "https://cdn-staging.signal.org"; @@ -49,6 +49,8 @@ class StagingConfig { private static final byte[] backupServerPublicParams = Base64.getDecoder() .decode("AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8"); + private static Network.Environment LIBSIGNAL_NET_ENV = Network.Environment.STAGING; + static SignalServiceConfiguration createDefaultServiceConfiguration( final List interceptors ) { @@ -78,10 +80,11 @@ class StagingConfig { static ServiceEnvironmentConfig getServiceEnvironmentConfig(List interceptors) { return new ServiceEnvironmentConfig(STAGING, + LIBSIGNAL_NET_ENV, createDefaultServiceConfiguration(interceptors), getUnidentifiedSenderTrustRoot(), CDSI_MRENCLAVE, - List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_DEPRECATED)); + List.of(SVR2_MRENCLAVE)); } private StagingConfig() { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index f7b978ab..fbd719b9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -22,7 +22,6 @@ import org.asamk.signal.manager.api.GroupId; import org.asamk.signal.manager.api.GroupNotFoundException; import org.asamk.signal.manager.api.MessageEnvelope; import org.asamk.signal.manager.api.Pair; -import org.asamk.signal.manager.api.Profile; import org.asamk.signal.manager.api.ReceiveConfig; import org.asamk.signal.manager.api.StickerPackId; import org.asamk.signal.manager.api.TrustLevel; @@ -161,24 +160,11 @@ public final class IncomingMessageHandler { if (context.getContactHelper().isContactBlocked(sender)) { logger.debug("Received invalid message from blocked contact, ignoring."); } else { - final var senderProfile = context.getProfileHelper().getRecipientProfile(sender); - final var selfProfile = context.getProfileHelper().getSelfProfile(); var serviceId = ServiceId.parseOrNull(e.getSender()); - if (serviceId == null) { - // Workaround for libsignal-client issue #492 - serviceId = account.getRecipientAddressResolver() - .resolveRecipientAddress(sender) - .serviceId() - .orElse(null); - } if (serviceId != null) { final var isSelf = sender.equals(account.getSelfRecipientId()) && e.getSenderDevice() == account.getDeviceId(); - final var isSenderSenderKeyCapable = senderProfile != null && senderProfile.getCapabilities() - .contains(Profile.Capability.senderKey); - final var isSelfSenderKeyCapable = selfProfile != null && selfProfile.getCapabilities() - .contains(Profile.Capability.senderKey); - if (!isSelf && isSenderSenderKeyCapable && isSelfSenderKeyCapable) { + if (!isSelf) { logger.debug("Received invalid message, requesting message resend."); actions.add(new SendRetryMessageRequestAction(sender, serviceId, e, envelope, destination)); } else { 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 947c251b..905bbd5a 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 @@ -213,6 +213,7 @@ public class RecipientHelper { token, serviceEnvironmentConfig.cdsiMrenclave(), null, + dependencies.getServiceEnvironmentConfig().netEnvironment(), newToken -> { if (isPartialRefresh) { account.getCdsiStore().updateAfterPartialCdsQuery(newNumbers); 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 d3068fc4..a066c851 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 @@ -523,21 +523,9 @@ public class SendHelper { } private Set getSenderKeyCapableRecipientIds(final Set recipientIds) { - final var selfProfile = context.getProfileHelper().getSelfProfile(); - if (selfProfile == null || !selfProfile.getCapabilities().contains(Profile.Capability.senderKey)) { - logger.debug("Not all of our devices support sender key. Using legacy."); - return Set.of(); - } - final var senderKeyTargets = new HashSet(); final var recipientList = new ArrayList<>(recipientIds); - final var profiles = context.getProfileHelper().getRecipientProfiles(recipientList).iterator(); for (final var recipientId : recipientList) { - final var profile = profiles.next(); - if (profile == null || !profile.getCapabilities().contains(Profile.Capability.senderKey)) { - continue; - } - final var access = context.getUnidentifiedAccessHelper().getAccessFor(recipientId); if (access.isEmpty() || access.get().getTargetUnidentifiedAccess().isEmpty()) { continue; diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index 9488c75e..561534c5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -189,7 +189,8 @@ public class SignalDependencies { Optional.empty(), executor, ServiceConfig.MAX_ENVELOPE_SIZE, - pushServiceSocket)); + pushServiceSocket, + false)); } public List getSecureValueRecoveryV2() { 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 e8f2db45..0076f314 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 @@ -895,9 +895,6 @@ public class SignalAccount implements Closeable { if (profile != null) { final var capabilities = new HashSet(); if (profile.getCapabilities() != null) { - if (profile.getCapabilities().gv1Migration) { - capabilities.add(Profile.Capability.gv1Migration); - } if (profile.getCapabilities().storage) { capabilities.add(Profile.Capability.storage); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacySignalProfile.java b/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacySignalProfile.java index e3e18b1e..4114df60 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacySignalProfile.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacySignalProfile.java @@ -94,7 +94,7 @@ public class LegacySignalProfile { @JsonProperty public boolean storage; - @JsonProperty + @JsonIgnore public boolean gv1Migration; } } diff --git a/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java index 85c9fdd6..82ffc861 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java @@ -76,18 +76,9 @@ public class ProfileUtils { public static HashSet getCapabilities(final SignalServiceProfile encryptedProfile) { final var capabilities = new HashSet(); - if (encryptedProfile.getCapabilities().isGv1Migration()) { - capabilities.add(Profile.Capability.gv1Migration); - } if (encryptedProfile.getCapabilities().isStorage()) { capabilities.add(Profile.Capability.storage); } - if (encryptedProfile.getCapabilities().isSenderKey()) { - capabilities.add(Profile.Capability.senderKey); - } - if (encryptedProfile.getCapabilities().isAnnouncementGroup()) { - capabilities.add(Profile.Capability.announcementGroup); - } return capabilities; } diff --git a/settings.gradle.kts b/settings.gradle.kts index a98ac404..b219a1ea 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") library("logback", "ch.qos.logback", "logback-classic").version("1.5.0") - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_97") + library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_98") library("sqlite", "org.xerial", "sqlite-jdbc").version("3.45.1.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") From 3372992dc20291f77bbaa704f267cdef51472071 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 9 Mar 2024 11:33:32 +0100 Subject: [PATCH 026/279] Add appstream metainfo file --- data/org.asamk.SignalCli.metainfo.xml | 55 ++++++++++++++++++++++ data/{icon.svg => org.asamk.SignalCli.svg} | 0 2 files changed, 55 insertions(+) create mode 100644 data/org.asamk.SignalCli.metainfo.xml rename data/{icon.svg => org.asamk.SignalCli.svg} (100%) diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml new file mode 100644 index 00000000..f61b0224 --- /dev/null +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -0,0 +1,55 @@ + + + org.asamk.SignalCli + + signal-cli + Use Signal messenger in terminal + + AsamK + + org.asamk.SignalCli + + https://github.com/AsamK/signal-cli/issues + https://github.com/AsamK/signal-cli + https://github.com/sponsors/AsamK + https://github.com/AsamK/signal-cli/discussions + https://github.com/AsamK/signal-cli + + CC0-1.0 + GPL-3.0-only + + +

+ signal-cli is an unofficial commandline interface for the Signal Messenger. + It supports many Signal functions, including registering, verifying, sending and receiving messages. + For registering you need a phone number where you can receive SMS or incoming calls. + Alternatively signal-cli can be linked to an existing App account. +

+
+ + + Utility + Java + + + + signal-cli + + + intense + + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.1 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.0 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.12.8 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.12.7 + + +
diff --git a/data/icon.svg b/data/org.asamk.SignalCli.svg similarity index 100% rename from data/icon.svg rename to data/org.asamk.SignalCli.svg From 323a8016000f436ff05b7431b126216cc8c00a73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 Mar 2024 13:08:46 +0100 Subject: [PATCH 027/279] Bump mio from 0.8.10 to 0.8.11 in /client (#1488) Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.10 to 0.8.11. - [Release notes](https://github.com/tokio-rs/mio/releases) - [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/mio/compare/v0.8.10...v0.8.11) --- updated-dependencies: - dependency-name: mio dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- client/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/Cargo.lock b/client/Cargo.lock index 642559a1..7b642387 100644 --- a/client/Cargo.lock +++ b/client/Cargo.lock @@ -623,9 +623,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", From 8b4f377cf18b6f529ee825c53a257819d9410723 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 22 Mar 2024 10:04:57 +0100 Subject: [PATCH 028/279] Update dependencies --- graalvm-config-dir/jni-config.json | 30 ++++++++++++++++++++++++++++++ settings.gradle.kts | 6 +++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index b79debed..d7667b09 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -18,6 +18,9 @@ "name":"java.lang.ClassLoader", "methods":[{"name":"getPlatformClassLoader","parameterTypes":[] }, {"name":"loadClass","parameterTypes":["java.lang.String"] }] }, +{ + "name":"java.lang.ClassNotFoundException" +}, { "name":"java.lang.IllegalArgumentException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] @@ -26,6 +29,13 @@ "name":"java.lang.IllegalStateException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, +{ + "name":"java.lang.Long", + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"java.lang.NoClassDefFoundError" +}, { "name":"java.lang.NoSuchMethodError" }, @@ -40,6 +50,14 @@ "name":"java.lang.UnsatisfiedLinkError", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, +{ + "name":"java.util.HashMap", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"java.util.Map", + "methods":[{"name":"get","parameterTypes":["java.lang.Object"] }, {"name":"put","parameterTypes":["java.lang.Object","java.lang.Object"] }, {"name":"remove","parameterTypes":["java.lang.Object"] }] +}, { "name":"java.util.UUID", "methods":[{"name":"","parameterTypes":["long","long"] }, {"name":"getLeastSignificantBits","parameterTypes":[] }, {"name":"getMostSignificantBits","parameterTypes":[] }] @@ -59,6 +77,18 @@ "name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints", "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }] }, +{ + "name":"org.signal.libsignal.internal.CompletableFuture", + "methods":[{"name":"","parameterTypes":[] }, {"name":"complete","parameterTypes":["java.lang.Object"] }] +}, +{ + "name":"org.signal.libsignal.net.CdsiLookupResponse", + "methods":[{"name":"","parameterTypes":["java.util.Map","int"] }] +}, +{ + "name":"org.signal.libsignal.net.CdsiLookupResponse$Entry", + "methods":[{"name":"","parameterTypes":["byte[]","byte[]"] }] +}, { "name":"org.signal.libsignal.protocol.DuplicateMessageException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] diff --git a/settings.gradle.kts b/settings.gradle.kts index b219a1ea..125c2642 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,16 +7,16 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { library("bouncycastle", "org.bouncycastle", "bcprov-jdk18on").version("1.77") - library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.16.1") + library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.17.0") library("argparse4j", "net.sourceforge.argparse4j", "argparse4j").version("0.9.0") library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("5.0.0") version("slf4j", "2.0.12") library("slf4j.api", "org.slf4j", "slf4j-api").versionRef("slf4j") library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") - library("logback", "ch.qos.logback", "logback-classic").version("1.5.0") + library("logback", "ch.qos.logback", "logback-classic").version("1.5.3") library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_98") - library("sqlite", "org.xerial", "sqlite-jdbc").version("3.45.1.0") + library("sqlite", "org.xerial", "sqlite-jdbc").version("3.45.2.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") library("junit.launcher", "org.junit.platform", "junit-platform-launcher").version("1.10.2") From d356d92b5eb24f7340055f766455b943c274bc50 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 22 Mar 2024 10:54:42 +0100 Subject: [PATCH 029/279] Extend getUserStatus command for usernames --- graalvm-config-dir/reflect-config.json | 2 +- .../org/asamk/signal/manager/Manager.java | 3 + .../signal/manager/api/UsernameStatus.java | 5 ++ .../manager/helper/RecipientHelper.java | 67 ++++++++++++------- .../signal/manager/internal/ManagerImpl.java | 28 ++++++++ man/signal-cli.1.adoc | 5 +- .../signal/commands/GetUserStatusCommand.java | 40 +++++++++-- .../asamk/signal/dbus/DbusManagerImpl.java | 6 ++ 8 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 lib/src/main/java/org/asamk/signal/manager/api/UsernameStatus.java diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 96961e4c..e3b47e86 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -693,7 +693,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "methods":[{"name":"isRegistered","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"recipient","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] + "methods":[{"name":"isRegistered","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"recipient","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] }, { "name":"org.asamk.signal.commands.ListAccountsCommand$JsonAccount", diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index d6cf01d8..736f4e08 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -44,6 +44,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; +import org.asamk.signal.manager.api.UsernameStatus; import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,6 +93,8 @@ public interface Manager extends Closeable { */ Map getUserStatus(Set numbers) throws IOException, RateLimitException; + Map getUsernameStatus(Set usernames); + void updateAccountAttributes( String deviceName, Boolean unrestrictedUnidentifiedSender, diff --git a/lib/src/main/java/org/asamk/signal/manager/api/UsernameStatus.java b/lib/src/main/java/org/asamk/signal/manager/api/UsernameStatus.java new file mode 100644 index 00000000..570d5511 --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/UsernameStatus.java @@ -0,0 +1,5 @@ +package org.asamk.signal.manager.api; + +import java.util.UUID; + +public record UsernameStatus(String username, UUID uuid, boolean unrestrictedUnidentifiedAccess) {} 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 905bbd5a..1581c1a5 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 @@ -91,30 +91,53 @@ public class RecipientHelper { }); } else if (recipient instanceof RecipientIdentifier.Username usernameRecipient) { var username = usernameRecipient.username(); - try { - UsernameLinkUrl usernameLinkUrl = UsernameLinkUrl.fromUri(username); - final var components = usernameLinkUrl.getComponents(); - final var encryptedUsername = dependencies.getAccountManager() - .getEncryptedUsernameFromLinkServerId(components.getServerId()); - final var link = new Username.UsernameLink(components.getEntropy(), encryptedUsername); - - username = Username.fromLink(link).getUsername(); - } catch (UsernameLinkUrl.InvalidUsernameLinkException e) { - } catch (IOException | BaseUsernameException e) { - throw new RuntimeException(e); - } - final String finalUsername = username; - return account.getRecipientStore().resolveRecipientByUsername(finalUsername, () -> { - try { - return getRegisteredUserByUsername(finalUsername); - } catch (Exception e) { - return null; - } - }); + return resolveRecipientByUsernameOrLink(username, false); } throw new AssertionError("Unexpected RecipientIdentifier: " + recipient); } + public RecipientId resolveRecipientByUsernameOrLink( + String username, boolean forceRefresh + ) throws UnregisteredRecipientException { + final Username finalUsername; + try { + finalUsername = getUsernameFromUsernameOrLink(username); + } catch (IOException | BaseUsernameException e) { + throw new RuntimeException(e); + } + if (forceRefresh) { + try { + final var aci = dependencies.getAccountManager().getAciByUsername(finalUsername); + return account.getRecipientStore().resolveRecipientTrusted(aci, finalUsername.getUsername()); + } catch (IOException e) { + throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, + null, + username)); + } + } + return account.getRecipientStore().resolveRecipientByUsername(finalUsername.getUsername(), () -> { + try { + return dependencies.getAccountManager().getAciByUsername(finalUsername); + } catch (Exception e) { + return null; + } + }); + } + + private Username getUsernameFromUsernameOrLink(String username) throws BaseUsernameException, IOException { + try { + final var usernameLinkUrl = UsernameLinkUrl.fromUri(username); + final var components = usernameLinkUrl.getComponents(); + final var encryptedUsername = dependencies.getAccountManager() + .getEncryptedUsernameFromLinkServerId(components.getServerId()); + final var link = new Username.UsernameLink(components.getEntropy(), encryptedUsername); + + return Username.fromLink(link); + } catch (UsernameLinkUrl.InvalidUsernameLinkException e) { + return new Username(username); + } + } + public Optional resolveRecipientOptional(final RecipientIdentifier.Single recipient) { try { return Optional.of(resolveRecipient(recipient)); @@ -246,10 +269,6 @@ public class RecipientHelper { return registeredUsers; } - private ACI getRegisteredUserByUsername(String username) throws IOException, BaseUsernameException { - return dependencies.getAccountManager().getAciByUsername(new Username(username)); - } - public record RegisteredUser(Optional aci, Optional pni) { public RegisteredUser { 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 47a5bc03..3f99d8e6 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 @@ -65,6 +65,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; +import org.asamk.signal.manager.api.UsernameStatus; import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.helper.AccountFileUpdater; @@ -280,6 +281,33 @@ public class ManagerImpl implements Manager { })); } + @Override + public Map getUsernameStatus(Set usernames) { + final var registeredUsers = new HashMap(); + for (final var username : usernames) { + try { + final var recipientId = context.getRecipientHelper().resolveRecipientByUsernameOrLink(username, true); + final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId); + registeredUsers.put(username, address); + } catch (UnregisteredRecipientException e) { + // ignore + } + } + + return usernames.stream().collect(Collectors.toMap(n -> n, username -> { + final var user = registeredUsers.get(username); + final var serviceId = user == null ? null : user.serviceId().orElse(null); + final var profile = serviceId == null + ? null + : context.getProfileHelper() + .getRecipientProfile(account.getRecipientResolver().resolveRecipient(serviceId)); + return new UsernameStatus(username, + serviceId == null ? null : serviceId.getRawUuid(), + profile != null + && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED); + })); + } + @Override public void updateAccountAttributes( String deviceName, diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 80d4c37e..8739e493 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -266,13 +266,16 @@ Use listDevices to see the deviceIds. === getUserStatus -Uses a list of phone numbers to determine the statuses of those users. +Uses a list of phone numbers or usernames to determine the statuses of those users. Shows if they are registered on the Signal Servers or not. In json mode this is outputted as a list of objects. [NUMBER [NUMBER ...]]:: One or more numbers to check. +[--username [USERNAME ...]]:: +One or more usernames to check. + === send Send a message to another user or group. diff --git a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java index 7cf78ec8..1691fac8 100644 --- a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java +++ b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java @@ -1,5 +1,7 @@ package org.asamk.signal.commands; +import com.fasterxml.jackson.annotation.JsonInclude; + import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; @@ -9,6 +11,7 @@ import org.asamk.signal.commands.exceptions.RateLimitErrorException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.UserStatus; +import org.asamk.signal.manager.api.UsernameStatus; import org.asamk.signal.output.JsonWriter; import org.asamk.signal.output.OutputWriter; import org.asamk.signal.output.PlainTextWriter; @@ -19,6 +22,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashSet; import java.util.Map; +import java.util.stream.Stream; public class GetUserStatusCommand implements JsonRpcLocalCommand { @@ -32,7 +36,8 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { @Override public void attachToSubparser(final Subparser subparser) { subparser.help("Check if the specified phone number/s have been registered"); - subparser.addArgument("recipient").help("Phone number").nargs("+"); + subparser.addArgument("recipient").help("Phone number").nargs("*"); + subparser.addArgument("--username").help("Specify the recipient username or username link.").nargs("*"); } @Override @@ -54,17 +59,31 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { + ")", e); } + final var usernames = ns.getList("username"); + final var registeredUsernames = usernames == null + ? Map.of() + : m.getUsernameStatus(new HashSet<>(usernames)); + // Output switch (outputWriter) { case JsonWriter writer -> { - var jsonUserStatuses = registered.entrySet().stream().map(entry -> { + var jsonUserStatuses = Stream.concat(registered.entrySet().stream().map(entry -> { final var number = entry.getValue().number(); final var uuid = entry.getValue().uuid(); return new JsonUserStatus(entry.getKey(), number, + null, uuid == null ? null : uuid.toString(), uuid != null); - }).toList(); + }), registeredUsernames.entrySet().stream().map(entry -> { + final var username = entry.getValue().username(); + final var uuid = entry.getValue().uuid(); + return new JsonUserStatus(entry.getKey(), + null, + username, + uuid == null ? null : uuid.toString(), + uuid != null); + })).toList(); writer.write(jsonUserStatuses); } case PlainTextWriter writer -> { @@ -75,9 +94,22 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { userStatus.uuid() != null, userStatus.unrestrictedUnidentifiedAccess() ? " (unrestricted sealed sender)" : ""); } + for (var entry : registeredUsernames.entrySet()) { + final var userStatus = entry.getValue(); + writer.println("{}: {}{}", + entry.getKey(), + userStatus.uuid() != null, + userStatus.unrestrictedUnidentifiedAccess() ? " (unrestricted sealed sender)" : ""); + } } } } - private record JsonUserStatus(String recipient, String number, String uuid, boolean isRegistered) {} + private record JsonUserStatus( + String recipient, + @JsonInclude(JsonInclude.Include.NON_NULL) String number, + @JsonInclude(JsonInclude.Include.NON_NULL) String username, + String uuid, + boolean isRegistered + ) {} } diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 522bb3f6..cd65be51 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -47,6 +47,7 @@ import org.asamk.signal.manager.api.UpdateGroup; import org.asamk.signal.manager.api.UpdateProfile; import org.asamk.signal.manager.api.UserStatus; import org.asamk.signal.manager.api.UsernameLinkUrl; +import org.asamk.signal.manager.api.UsernameStatus; import org.freedesktop.dbus.DBusMap; import org.freedesktop.dbus.DBusPath; import org.freedesktop.dbus.connections.impl.DBusConnection; @@ -122,6 +123,11 @@ public class DbusManagerImpl implements Manager { return result; } + @Override + public Map getUsernameStatus(final Set usernames) { + throw new UnsupportedOperationException(); + } + @Override public void updateAccountAttributes( final String deviceName, From abddf24752218ff88cd0eca0a8c367ab5c0f2dfc Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 23 Mar 2024 09:50:54 +0100 Subject: [PATCH 030/279] Update user agent --- src/main/java/org/asamk/signal/BaseConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 8132e719..84d7f104 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.0.2"); + .orElse("Signal-Android/7.1.3"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From 95e70b9d153f1f3ec668c8e91a17f89b25ecf3ce Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 23 Mar 2024 09:33:37 +0100 Subject: [PATCH 031/279] Add Java 22 to CI --- .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 1c6bb3c0..df7a6c79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '21' ] + java: [ '21', '22' ] steps: - uses: actions/checkout@v4 From dda23e76ac4ed8d88f23c9f39b7110460a8a1ed5 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 23 Mar 2024 09:57:15 +0100 Subject: [PATCH 032/279] Bump version to 0.13.2 --- CHANGELOG.md | 10 +++++++++- build.gradle.kts | 2 +- data/org.asamk.SignalCli.metainfo.xml | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50f45f28..c845502d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog -## [Unreleased] +## [0.13.2] - 2024-03-23 + +### Added + +- Add `--username` parameter to `getUserStatus` command + +### Fixed + +- Fixed setting and retrieving PIN after server changes ## [0.13.1] - 2024-02-27 diff --git a/build.gradle.kts b/build.gradle.kts index 0d4ebe84..48606a8d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.1" } -version = "0.13.2-SNAPSHOT" +version = "0.13.2" java { sourceCompatibility = JavaVersion.VERSION_21 diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index f61b0224..0fe85fa9 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -39,6 +39,9 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.2 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.1 From c85c995fef7580ef135a03c121617eaeec49b3e4 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 27 Feb 2024 18:20:21 +0100 Subject: [PATCH 033/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c845502d..5b7e2b78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.2] - 2024-03-23 ### Added diff --git a/build.gradle.kts b/build.gradle.kts index 48606a8d..f7a3737b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.1" } -version = "0.13.2" +version = "0.13.3-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_21 From 49cc9cd9f8db71db9dc5076be25b1977400cedfa Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 23 Mar 2024 15:00:02 +0100 Subject: [PATCH 034/279] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b7e2b78..d9e4501f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ## [0.13.2] - 2024-03-23 +**Attention**: Now requires libsignal-client version 0.40.1 + ### Added - Add `--username` parameter to `getUserStatus` command From be0e8ddd8aa5fd8d1835ba6a16675c66e1b77f82 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 27 Mar 2024 22:58:00 +0100 Subject: [PATCH 035/279] Add reregister to tests --- run_tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/run_tests.sh b/run_tests.sh index 6a7c8358..0e5ff4be 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -160,6 +160,7 @@ wait fi run_main -a "$NUMBER_1" setPin "$TEST_PIN_1" +run_main -a "$NUMBER_1" register --reregister run_main -a "$NUMBER_2" removePin ## Contacts From 17c24b3ff22ca00d9fa26e976b4f1de64eb4a689 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 27 Mar 2024 22:54:15 +0100 Subject: [PATCH 036/279] Update libsignal-service-java --- CHANGELOG.md | 2 ++ .../manager/api/ProofRequiredException.java | 5 ++-- .../signal/manager/config/LiveConfig.java | 2 +- .../signal/manager/config/StagingConfig.java | 2 +- .../signal/manager/helper/SyncHelper.java | 2 -- .../signal/manager/internal/ManagerImpl.java | 2 +- .../syncStorage/ContactRecordProcessor.java | 23 ++++++++++++++++++- settings.gradle.kts | 2 +- .../java/org/asamk/signal/BaseConfig.java | 2 +- .../signal/util/SendMessageResultUtils.java | 2 +- 10 files changed, 33 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9e4501f..7a96d412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +**Attention**: Now requires libsignal-client version 0.42.0 + ## [0.13.2] - 2024-03-23 **Attention**: Now requires libsignal-client version 0.40.1 diff --git a/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java b/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java index 949dc4eb..277406cb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java @@ -31,12 +31,13 @@ public class ProofRequiredException extends Exception { } public enum Option { - RECAPTCHA, + CAPTCHA, PUSH_CHALLENGE; static Option from(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException.Option option) { return switch (option) { - case RECAPTCHA -> RECAPTCHA; + case RECAPTCHA -> CAPTCHA; + case CAPTCHA -> CAPTCHA; case PUSH_CHALLENGE -> PUSH_CHALLENGE; }; } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index 2bbdfe19..c86e650b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -42,7 +42,7 @@ class LiveConfig { private static final Optional proxy = Optional.empty(); private static final byte[] zkGroupServerPublicParams = Base64.getDecoder() - .decode("AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0I="); + .decode("AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0LUlT9vALgh/f2DPVOOmR0RW6bgRvc7DSF20V/omg+YBw=="); private static final byte[] genericServerPublicParams = Base64.getDecoder() .decode("AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN"); diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index e99b049c..c4d87cc3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -42,7 +42,7 @@ class StagingConfig { private static final Optional proxy = Optional.empty(); private static final byte[] zkGroupServerPublicParams = Base64.getDecoder() - .decode("ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCM="); + .decode("ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ=="); private static final byte[] genericServerPublicParams = Base64.getDecoder() .decode("AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N"); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index 4789790e..0d516eb6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -201,7 +201,6 @@ public class SyncHelper { Optional.ofNullable(contact == null ? null : contact.color()), Optional.ofNullable(verifiedMessage), Optional.ofNullable(profileKey), - contact != null && contact.isBlocked(), Optional.ofNullable(contact == null ? null : contact.messageExpirationTime()), Optional.empty(), contact != null && contact.isArchived()); @@ -357,7 +356,6 @@ public class SyncHelper { if (c.getExpirationTimer().isPresent()) { builder.withMessageExpirationTime(c.getExpirationTimer().get()); } - builder.withIsBlocked(c.isBlocked()); builder.withIsArchived(c.isArchived()); account.getContactStore().storeContact(recipientId, builder.build()); 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 3f99d8e6..b43dfba4 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 @@ -443,7 +443,7 @@ public class ManagerImpl implements Manager { try { dependencies.getAccountManager().submitRateLimitRecaptchaChallenge(challenge, captcha); - } catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRejectedException ignored) { + } catch (org.whispersystems.signalservice.internal.push.exceptions.CaptchaRejectedException ignored) { throw new CaptchaRejectedException(); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java index 5bae64c8..28e7b663 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java @@ -104,6 +104,23 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor Date: Sat, 6 Apr 2024 12:34:55 +0200 Subject: [PATCH 037/279] Update gradle wrapper --- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch delta 34118 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 delta 34176 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 SGW6ptSs!X7{rG*o_g?|vpSEZK diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce..b82aa23a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 95f9e18de2228e8bececd7856e8cee836edaafcd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:04:48 +0200 Subject: [PATCH 038/279] Bump h2 from 0.3.24 to 0.3.26 in /client (#1501) Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26. - [Release notes](https://github.com/hyperium/h2/releases) - [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md) - [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26) --- updated-dependencies: - dependency-name: h2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- client/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/Cargo.lock b/client/Cargo.lock index 7b642387..11c9659c 100644 --- a/client/Cargo.lock +++ b/client/Cargo.lock @@ -370,9 +370,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", From edce33ae1535cb345bb8c90e110314a0a320df0c Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 6 Apr 2024 13:54:47 +0200 Subject: [PATCH 039/279] Disable java 22 until gradle supports it --- .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 df7a6c79..1c6bb3c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '21', '22' ] + java: [ '21' ] steps: - uses: actions/checkout@v4 From d4e1f9b7f182dce311c07b0c0ab647e639c04d34 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 6 Apr 2024 13:55:40 +0200 Subject: [PATCH 040/279] Remove unnecessary config field --- .../org/asamk/signal/manager/helper/RecipientHelper.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 1581c1a5..ab87996d 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 @@ -3,7 +3,6 @@ package org.asamk.signal.manager.helper; import org.asamk.signal.manager.api.RecipientIdentifier; import org.asamk.signal.manager.api.UnregisteredRecipientException; import org.asamk.signal.manager.api.UsernameLinkUrl; -import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.internal.SignalDependencies; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.recipients.RecipientId; @@ -34,12 +33,10 @@ public class RecipientHelper { private final SignalAccount account; private final SignalDependencies dependencies; - private final ServiceEnvironmentConfig serviceEnvironmentConfig; public RecipientHelper(final Context context) { this.account = context.getAccount(); this.dependencies = context.getDependencies(); - this.serviceEnvironmentConfig = dependencies.getServiceEnvironmentConfig(); } public SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) { @@ -234,7 +231,7 @@ public class RecipientHelper { newNumbers, account.getRecipientStore().getServiceIdToProfileKeyMap(), token, - serviceEnvironmentConfig.cdsiMrenclave(), + dependencies.getServiceEnvironmentConfig().cdsiMrenclave(), null, dependencies.getServiceEnvironmentConfig().netEnvironment(), newToken -> { From 419beee29a495fef141f87c95185407d42cf495f Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 6 Apr 2024 13:56:25 +0200 Subject: [PATCH 041/279] Update libsignal-service-java --- graalvm-config-dir/reflect-config.json | 8 ++++++++ .../manager/helper/RecipientHelper.java | 2 +- .../manager/internal/SignalDependencies.java | 7 +++++++ .../syncStorage/ContactRecordProcessor.java | 19 ++----------------- settings.gradle.kts | 4 ++-- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index e3b47e86..252839b2 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -1384,6 +1384,10 @@ "name":"org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE$Mappings", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.CompositeSignatures$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.DH$Mappings", "methods":[{"name":"","parameterTypes":[] }] @@ -2748,6 +2752,10 @@ "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord", "allDeclaredFields":true }, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Name", + "allDeclaredFields":true +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record", "allDeclaredFields":true 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 ab87996d..deb4e01b 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 @@ -233,7 +233,7 @@ public class RecipientHelper { token, dependencies.getServiceEnvironmentConfig().cdsiMrenclave(), null, - dependencies.getServiceEnvironmentConfig().netEnvironment(), + dependencies.getLibSignalNetwork(), newToken -> { if (isPartialRefresh) { account.getCdsiStore().updateAfterPartialCdsQuery(newNumbers); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index 561534c5..b715611e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -3,6 +3,7 @@ package org.asamk.signal.manager.internal; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.signal.libsignal.metadata.certificate.CertificateValidator; +import org.signal.libsignal.net.Network; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceDataStore; @@ -48,6 +49,7 @@ public class SignalDependencies { private ClientZkOperations clientZkOperations; private PushServiceSocket pushServiceSocket; + private Network libSignalNetwork; private SignalWebSocket signalWebSocket; private SignalServiceMessageReceiver messageReceiver; private SignalServiceMessageSender messageSender; @@ -104,6 +106,11 @@ public class SignalDependencies { ServiceConfig.AUTOMATIC_NETWORK_RETRY)); } + public Network getLibSignalNetwork() { + return getOrCreate(() -> libSignalNetwork, + () -> libSignalNetwork = new Network(serviceEnvironmentConfig.netEnvironment())); + } + public SignalServiceAccountManager getAccountManager() { return getOrCreate(() -> accountManager, () -> accountManager = new SignalServiceAccountManager(getPushServiceSocket(), diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java index 28e7b663..cd1e107c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java @@ -104,23 +104,6 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor Date: Sat, 13 Apr 2024 11:26:15 -0700 Subject: [PATCH 042/279] Document the unit of "start" and "length" for mentions and text styles (#1505) The unit of UTF-16 code units is not necessarily obvious for users of languages that index strings by Unicode code points. Provide a pointer to an FAQ entry as well: https://github.com/AsamK/signal-cli/wiki/FAQ#string-indexing-units Closes #1504 Signed-off-by: Stephen Brennan --- man/signal-cli.1.adoc | 3 ++- src/main/java/org/asamk/signal/commands/SendCommand.java | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 8739e493..31bc5499 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -312,10 +312,11 @@ e.g.: `--sticker 00abac3bc18d7f599bff2325dc306d43:2` *--mention*:: Mention another group member (syntax: start:length:recipientNumber) In the apps the mention replaces part of the message text, which is specified by the start and length values. +The units of start and length should be UTF-16 code units, NOT Unicode code points. For more information, see https://github.com/AsamK/signal-cli/wiki/FAQ#string-indexing-units e.g.: `-m "Hi X!" --mention "3:1:+123456789"` *--text-style*:: -Style parts of the message text (syntax: start:length:STYLE). +Style parts of the message text (syntax: start:length:STYLE). Like `--mention`, the units are UTF-16 code units. Where STYLE is one of: BOLD, ITALIC, SPOILER, STRIKETHROUGH, MONOSPACE e.g.: `-m "Something BIG!" --text-style "10:3:BOLD"` or for a mixed text style `-m "Something BIG!" --text-style "0:9:ITALIC" "10:3:BOLD"` diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 0c38cf7d..004ba506 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -71,10 +71,12 @@ public class SendCommand implements JsonRpcLocalCommand { .action(Arguments.storeTrue()); subparser.addArgument("--mention") .nargs("*") - .help("Mention another group member (syntax: start:length:recipientNumber)"); + .help("Mention another group member (syntax: start:length:recipientNumber). " + + "Unit of start and length is UTF-16 code units, NOT Unicode code points."); subparser.addArgument("--text-style") .nargs("*") - .help("Style parts of the message text (syntax: start:length:STYLE)"); + .help("Style parts of the message text (syntax: start:length:STYLE). " + + "Unit of start and length is UTF-16 code units, NOT Unicode code points."); subparser.addArgument("--quote-timestamp") .type(long.class) .help("Specify the timestamp of a previous message with the recipient or group to add a quote to the new message."); From e0cd5b987e151222c7486c14d5cebeebc2ac7740 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 12 Apr 2024 16:57:20 +0200 Subject: [PATCH 043/279] Add handling for new nickname and note fields --- .../org/asamk/signal/manager/api/Contact.java | 24 +++++++++++++++++++ .../manager/storage/AccountDatabase.java | 12 +++++++++- .../signal/manager/storage/SignalAccount.java | 3 +++ .../recipients/LegacyRecipientStore2.java | 3 +++ .../storage/recipients/RecipientStore.java | 6 +++++ .../syncStorage/ContactRecordProcessor.java | 3 +++ .../syncStorage/StorageSyncModels.java | 3 +++ .../asamk/signal/dbus/DbusManagerImpl.java | 3 +++ 8 files changed, 56 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java index cfe3f894..3605b7c5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Contact.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Contact.java @@ -6,6 +6,9 @@ public record Contact( String givenName, String familyName, String nickName, + String nickNameGivenName, + String nickNameFamilyName, + String note, String color, int messageExpirationTime, long muteUntil, @@ -21,6 +24,9 @@ public record Contact( this(builder.givenName, builder.familyName, builder.nickName, + builder.nickNameGivenName, + builder.nickNameFamilyName, + builder.note, builder.color, builder.messageExpirationTime, builder.muteUntil, @@ -73,6 +79,9 @@ public record Contact( private String givenName; private String familyName; private String nickName; + private String nickNameGivenName; + private String nickNameFamilyName; + private String note; private String color; private int messageExpirationTime; private long muteUntil; @@ -105,6 +114,21 @@ public record Contact( return this; } + public Builder withNickNameGivenName(final String val) { + nickNameGivenName = val; + return this; + } + + public Builder withNickNameFamilyName(final String val) { + nickNameFamilyName = val; + return this; + } + + public Builder withNote(final String val) { + note = val; + return this; + } + public Builder withColor(final String val) { color = val; return this; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java index af498359..bf751fbc 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java @@ -33,7 +33,7 @@ import java.util.UUID; public class AccountDatabase extends Database { private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class); - private static final long DATABASE_VERSION = 24; + private static final long DATABASE_VERSION = 25; private AccountDatabase(final HikariDataSource dataSource) { super(logger, DATABASE_VERSION, dataSource); @@ -581,6 +581,16 @@ public class AccountDatabase extends Database { """); } } + if (oldVersion < 25) { + logger.debug("Updating database: Create nick_name and note columns"); + try (final var statement = connection.createStatement()) { + statement.executeUpdate(""" + ALTER TABLE recipient ADD nick_name_given_name TEXT; + ALTER TABLE recipient ADD nick_name_family_name TEXT; + ALTER TABLE recipient ADD note TEXT; + """); + } + } } private static void createUuidMappingTable( 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 0076f314..a3a98c6c 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 @@ -857,6 +857,9 @@ public class SignalAccount implements Closeable { final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress()); getContactStore().storeContact(recipientId, new Contact(contact.name, + null, + null, + null, null, null, contact.color, diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java index 17f136ec..186ac517 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java @@ -39,6 +39,9 @@ public class LegacyRecipientStore2 { Contact contact = null; if (r.contact != null) { contact = new Contact(r.contact.name, + null, + null, + null, null, null, r.contact.color, 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 7d47839b..c291342e 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 @@ -71,6 +71,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re given_name TEXT, family_name TEXT, nick_name TEXT, + nick_name_given_name TEXT, + nick_name_family_name TEXT, + note TEXT, color TEXT, expiration_time INTEGER NOT NULL DEFAULT 0, @@ -1436,6 +1439,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re return new Contact(resultSet.getString("given_name"), resultSet.getString("family_name"), resultSet.getString("nick_name"), + resultSet.getString("nick_name_given_name"), + resultSet.getString("nick_name_family_name"), + resultSet.getString("note"), resultSet.getString("color"), resultSet.getInt("expiration_time"), resultSet.getLong("mute_until"), diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java index cd1e107c..a087553b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java @@ -271,6 +271,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor Date: Sat, 13 Apr 2024 21:29:45 +0200 Subject: [PATCH 044/279] Add aci,pni to API RecipientAddress --- .../signal/manager/api/RecipientAddress.java | 55 ++++++++++--------- .../manager/api/RecipientIdentifier.java | 8 +-- .../signal/manager/helper/ReceiveHelper.java | 4 +- .../manager/helper/RecipientHelper.java | 5 +- .../storage/recipients/RecipientAddress.java | 8 ++- .../storage/recipients/RecipientStore.java | 4 +- .../asamk/signal/dbus/DbusManagerImpl.java | 28 ++++------ 7 files changed, 59 insertions(+), 53 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/RecipientAddress.java b/lib/src/main/java/org/asamk/signal/manager/api/RecipientAddress.java index c94a21ac..dd8a6f3c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/RecipientAddress.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/RecipientAddress.java @@ -1,49 +1,55 @@ package org.asamk.signal.manager.api; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.util.UuidUtil; import java.util.Optional; import java.util.UUID; -public record RecipientAddress(Optional uuid, Optional number, Optional username) { +public record RecipientAddress( + Optional aci, Optional pni, Optional number, Optional username +) { public static final UUID UNKNOWN_UUID = UuidUtil.UNKNOWN_UUID; /** * Construct a RecipientAddress. * - * @param uuid The UUID of the user, if available. + * @param aci The ACI of the user, if available. + * @param pni The PNI of the user, if available. * @param number The phone number of the user, if available. */ public RecipientAddress { - uuid = uuid.isPresent() && uuid.get().equals(UNKNOWN_UUID) ? Optional.empty() : uuid; - if (uuid.isEmpty() && number.isEmpty() && username.isEmpty()) { - throw new AssertionError("Must have either a UUID, username or E164 number!"); + if (aci.isEmpty() && pni.isEmpty() && number.isEmpty() && username.isEmpty()) { + throw new AssertionError("Must have either a ACI, PNI, username or E164 number!"); } } - public RecipientAddress(UUID uuid, String e164) { - this(Optional.ofNullable(uuid), Optional.ofNullable(e164), Optional.empty()); - } - - public RecipientAddress(UUID uuid, String e164, String username) { - this(Optional.ofNullable(uuid), Optional.ofNullable(e164), Optional.ofNullable(username)); - } - - public RecipientAddress(SignalServiceAddress address) { - this(Optional.of(address.getServiceId().getRawUuid()), address.getNumber(), Optional.empty()); + public RecipientAddress(String e164) { + this(null, null, e164, null); } public RecipientAddress(UUID uuid) { - this(Optional.of(uuid), Optional.empty(), Optional.empty()); + this(uuid.toString(), null, null, null); + } + + public RecipientAddress(String aci, String pni, String e164, String username) { + this(Optional.ofNullable(aci), + Optional.ofNullable(pni), + Optional.ofNullable(e164), + Optional.ofNullable(username)); + } + + public Optional uuid() { + return aci.map(UUID::fromString); } public String getIdentifier() { - if (uuid.isPresent()) { - return uuid.get().toString(); + if (aci.isPresent()) { + return aci.get(); } else if (number.isPresent()) { return number.get(); + } else if (pni.isPresent()) { + return pni.get(); } else if (username.isPresent()) { return username.get(); } else { @@ -54,17 +60,16 @@ public record RecipientAddress(Optional uuid, Optional number, Opt public String getLegacyIdentifier() { if (number.isPresent()) { return number.get(); - } else if (uuid.isPresent()) { - return uuid.get().toString(); - } else if (username.isPresent()) { - return username.get(); } else { - throw new AssertionError("Given the checks in the constructor, this should not be possible."); + return getIdentifier(); } } public boolean matches(RecipientAddress other) { - return (uuid.isPresent() && other.uuid.isPresent() && uuid.get().equals(other.uuid.get())) + return (aci.isPresent() && other.aci.isPresent() && aci.get().equals(other.aci.get())) + || ( + pni.isPresent() && other.pni.isPresent() && pni.get().equals(other.pni.get()) + ) || (number.isPresent() && other.number.isPresent() && number.get().equals(other.number.get())) || (username.isPresent() && other.username.isPresent() && username.get().equals(other.username.get())); } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java b/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java index fc822058..823881c7 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java @@ -47,8 +47,8 @@ public sealed interface RecipientIdentifier { static Single fromAddress(RecipientAddress address) { if (address.number().isPresent()) { return new Number(address.number().get()); - } else if (address.uuid().isPresent()) { - return new Uuid(address.uuid().get()); + } else if (address.aci().isPresent()) { + return new Uuid(UUID.fromString(address.aci().get())); } else if (address.username().isPresent()) { return new Username(address.username().get()); } @@ -80,7 +80,7 @@ public sealed interface RecipientIdentifier { @Override public RecipientAddress toPartialRecipientAddress() { - return new RecipientAddress(null, number); + return new RecipientAddress(number); } } @@ -93,7 +93,7 @@ public sealed interface RecipientIdentifier { @Override public RecipientAddress toPartialRecipientAddress() { - return new RecipientAddress(null, null, username); + return new RecipientAddress(null, null, null, username); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java index a40c134d..baa0d583 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java @@ -229,9 +229,9 @@ public class ReceiveHelper { if (exception instanceof UntrustedIdentityException) { logger.debug("Keeping message with untrusted identity in message cache"); final var address = ((UntrustedIdentityException) exception).getSender(); - if (envelope.getSourceServiceId().isEmpty() && address.uuid().isPresent()) { + if (envelope.getSourceServiceId().isEmpty() && address.aci().isPresent()) { final var recipientId = account.getRecipientResolver() - .resolveRecipient(ACI.from(address.uuid().get())); + .resolveRecipient(ACI.parseOrThrow(address.aci().get())); try { cachedMessage[0] = account.getMessageCache() .replaceSender(cachedMessage[0], recipientId); 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 deb4e01b..3bdd8227 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 @@ -108,6 +108,7 @@ public class RecipientHelper { return account.getRecipientStore().resolveRecipientTrusted(aci, finalUsername.getUsername()); } catch (IOException e) { throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, + null, null, username)); } @@ -196,11 +197,11 @@ public class RecipientHelper { try { aciMap = getRegisteredUsers(Set.of(number), true); } catch (NumberFormatException e) { - throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, number)); + throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(number)); } final var user = aciMap.get(number); if (user == null) { - throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, number)); + throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(number)); } return user.getServiceId(); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientAddress.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientAddress.java index 7eacb6a4..56adb0a6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientAddress.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientAddress.java @@ -69,7 +69,10 @@ public record RecipientAddress( } public RecipientAddress(org.asamk.signal.manager.api.RecipientAddress address) { - this(address.uuid().map(ACI::from), Optional.empty(), address.number(), address.username()); + this(address.aci().map(ACI::parseOrNull), + address.pni().map(PNI::parseOrNull), + address.number(), + address.username()); } public RecipientAddress(ServiceId serviceId) { @@ -169,7 +172,8 @@ public record RecipientAddress( } public org.asamk.signal.manager.api.RecipientAddress toApiRecipientAddress() { - return new org.asamk.signal.manager.api.RecipientAddress(serviceId().map(ServiceId::getRawUuid), + return new org.asamk.signal.manager.api.RecipientAddress(aci().map(ServiceId::toString), + pni().map(ServiceId::toString), number(), username()); } 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 c291342e..5c12a1ec 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 @@ -215,8 +215,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re if (byNumber.isEmpty() || byNumber.get().address().serviceId().isEmpty()) { final var serviceId = serviceIdSupplier.get(); if (serviceId == null) { - throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, - number)); + throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(number)); } return resolveRecipient(serviceId); @@ -247,6 +246,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var aci = aciSupplier.get(); if (aci == null) { throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, + null, null, username)); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index b428fb32..4573b861 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -690,7 +690,7 @@ public class DbusManagerImpl implements Manager { return null; } return Recipient.newBuilder() - .withAddress(new RecipientAddress(null, n)) + .withAddress(new RecipientAddress(n)) .withContact(new Contact(contactName, null, null, @@ -731,19 +731,19 @@ public class DbusManagerImpl implements Manager { (String) group.get("Description").getValue(), GroupInviteLinkUrl.fromUri((String) group.get("GroupInviteLink").getValue()), ((List) group.get("Members").getValue()).stream() - .map(m -> new RecipientAddress(null, m)) + .map(m -> new RecipientAddress(m)) .collect(Collectors.toSet()), ((List) group.get("PendingMembers").getValue()).stream() - .map(m -> new RecipientAddress(null, m)) + .map(m -> new RecipientAddress(m)) .collect(Collectors.toSet()), ((List) group.get("RequestingMembers").getValue()).stream() - .map(m -> new RecipientAddress(null, m)) + .map(m -> new RecipientAddress(m)) .collect(Collectors.toSet()), ((List) group.get("Admins").getValue()).stream() - .map(m -> new RecipientAddress(null, m)) + .map(m -> new RecipientAddress(m)) .collect(Collectors.toSet()), ((List) group.get("Banned").getValue()).stream() - .map(m -> new RecipientAddress(null, m)) + .map(m -> new RecipientAddress(m)) .collect(Collectors.toSet()), (boolean) group.get("IsBlocked").getValue(), (int) group.get("MessageExpirationTimer").getValue(), @@ -854,8 +854,7 @@ public class DbusManagerImpl implements Manager { try { this.dbusMsgHandler = messageReceived -> { final var extras = messageReceived.getExtras(); - final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null, - messageReceived.getSender())), + final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(messageReceived.getSender())), 0, messageReceived.getTimestamp(), 0, @@ -896,8 +895,7 @@ public class DbusManagerImpl implements Manager { connection.addSigHandler(Signal.MessageReceivedV2.class, signal, this.dbusMsgHandler); this.dbusEditMsgHandler = messageReceived -> { final var extras = messageReceived.getExtras(); - final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null, - messageReceived.getSender())), + final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(messageReceived.getSender())), 0, messageReceived.getTimestamp(), 0, @@ -945,8 +943,7 @@ public class DbusManagerImpl implements Manager { case "delivery" -> MessageEnvelope.Receipt.Type.DELIVERY; default -> MessageEnvelope.Receipt.Type.UNKNOWN; }; - final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null, - receiptReceived.getSender())), + final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(receiptReceived.getSender())), 0, receiptReceived.getTimestamp(), 0, @@ -967,8 +964,7 @@ public class DbusManagerImpl implements Manager { this.dbusSyncHandler = syncReceived -> { final var extras = syncReceived.getExtras(); - final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(null, - syncReceived.getSource())), + final var envelope = new MessageEnvelope(Optional.of(new RecipientAddress(syncReceived.getSource())), 0, syncReceived.getTimestamp(), 0, @@ -982,7 +978,7 @@ public class DbusManagerImpl implements Manager { syncReceived.getTimestamp(), syncReceived.getDestination().isEmpty() ? Optional.empty() - : Optional.of(new RecipientAddress(null, syncReceived.getDestination())), + : Optional.of(new RecipientAddress(syncReceived.getDestination())), Set.of(), Optional.of(new MessageEnvelope.Data(syncReceived.getTimestamp(), syncReceived.getGroupId().length > 0 @@ -1081,7 +1077,7 @@ public class DbusManagerImpl implements Manager { final List>> mentions = getValue(extras, "mentions"); return mentions.stream() - .map(a -> new MessageEnvelope.Data.Mention(new RecipientAddress(null, getValue(a, "recipient")), + .map(a -> new MessageEnvelope.Data.Mention(new RecipientAddress(this.getValue(a, "recipient")), getValue(a, "start"), getValue(a, "length"))) .toList(); From 71de8e63cce8633b020ee49ed62baf0cf838ca54 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 15 Apr 2024 19:23:41 +0200 Subject: [PATCH 045/279] Cache newly created session record Fixes #1481 --- .../signal/manager/storage/sessions/SessionStore.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java index 3f3ba4d6..067dc5d3 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java @@ -59,8 +59,11 @@ public class SessionStore implements SignalServiceSessionStore { public SessionRecord loadSession(SignalProtocolAddress address) { final var key = getKey(address); try (final var connection = database.getConnection()) { - final var session = loadSession(connection, key); - return Objects.requireNonNullElseGet(session, SessionRecord::new); + final var sessionRecord = Objects.requireNonNullElseGet(loadSession(connection, key), SessionRecord::new); + synchronized (cachedSessions) { + cachedSessions.put(key, sessionRecord); + } + return sessionRecord; } catch (SQLException e) { throw new RuntimeException("Failed read from session store", e); } @@ -148,7 +151,9 @@ public class SessionStore implements SignalServiceSessionStore { try (final var connection = database.getConnection()) { final var session = loadSession(connection, key); - return isActive(session); + final var active = isActive(session); + logger.trace("Contains session {}: {} (active: {})", address, session != null, active); + return active; } catch (SQLException e) { throw new RuntimeException("Failed read from session store", e); } From 7e0d4c9b8944b43f140e3b5dbf0ec5c33f19b92d Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 16 Apr 2024 21:55:50 +0200 Subject: [PATCH 046/279] Store profile phone number sharing mode and discoverable state --- .../manager/api/PhoneNumberSharingMode.java | 13 ++- .../org/asamk/signal/manager/api/Profile.java | 17 ++- .../asamk/signal/manager/api/Recipient.java | 25 +++- .../signal/manager/helper/ProfileHelper.java | 5 + .../manager/helper/RecipientHelper.java | 3 +- .../signal/manager/internal/ManagerImpl.java | 3 +- .../manager/storage/AccountDatabase.java | 11 +- .../signal/manager/storage/SignalAccount.java | 3 +- .../recipients/LegacyRecipientStore2.java | 4 +- .../manager/storage/recipients/Recipient.java | 15 +++ .../storage/recipients/RecipientStore.java | 107 +++++++++++++++--- .../signal/manager/util/ProfileUtils.java | 43 ++++--- 12 files changed, 202 insertions(+), 47 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/PhoneNumberSharingMode.java b/lib/src/main/java/org/asamk/signal/manager/api/PhoneNumberSharingMode.java index 9efa01be..fce56305 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/PhoneNumberSharingMode.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/PhoneNumberSharingMode.java @@ -3,5 +3,16 @@ package org.asamk.signal.manager.api; public enum PhoneNumberSharingMode { EVERYBODY, CONTACTS, - NOBODY, + NOBODY; + + public static PhoneNumberSharingMode valueOfOrNull(String value) { + if (value == null) { + return null; + } + try { + return valueOf(value); + } catch (IllegalArgumentException ignored) { + return null; + } + } } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Profile.java b/lib/src/main/java/org/asamk/signal/manager/api/Profile.java index 33117188..5b1ecf3a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Profile.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Profile.java @@ -26,6 +26,8 @@ public class Profile { private final Set capabilities; + private final PhoneNumberSharingMode phoneNumberSharingMode; + public Profile( final long lastUpdateTimestamp, final String givenName, @@ -35,7 +37,8 @@ public class Profile { final String avatarUrlPath, final byte[] mobileCoinAddress, final UnidentifiedAccessMode unidentifiedAccessMode, - final Set capabilities + final Set capabilities, + final PhoneNumberSharingMode phoneNumberSharingMode ) { this.lastUpdateTimestamp = lastUpdateTimestamp; this.givenName = givenName; @@ -46,6 +49,7 @@ public class Profile { this.mobileCoinAddress = mobileCoinAddress; this.unidentifiedAccessMode = unidentifiedAccessMode; this.capabilities = capabilities; + this.phoneNumberSharingMode = phoneNumberSharingMode; } private Profile(final Builder builder) { @@ -58,6 +62,7 @@ public class Profile { mobileCoinAddress = builder.mobileCoinAddress; unidentifiedAccessMode = builder.unidentifiedAccessMode; capabilities = builder.capabilities; + phoneNumberSharingMode = builder.phoneNumberSharingMode; } public static Builder newBuilder() { @@ -136,6 +141,10 @@ public class Profile { return capabilities; } + public PhoneNumberSharingMode getPhoneNumberSharingMode() { + return phoneNumberSharingMode; + } + public enum UnidentifiedAccessMode { UNKNOWN, DISABLED, @@ -200,6 +209,7 @@ public class Profile { private byte[] mobileCoinAddress; private UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN; private Set capabilities = Collections.emptySet(); + private PhoneNumberSharingMode phoneNumberSharingMode; private long lastUpdateTimestamp = 0; private Builder() { @@ -240,6 +250,11 @@ public class Profile { return this; } + public Builder withPhoneNumberSharingMode(final PhoneNumberSharingMode val) { + phoneNumberSharingMode = val; + return this; + } + public Profile build() { return new Profile(this); } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Recipient.java b/lib/src/main/java/org/asamk/signal/manager/api/Recipient.java index 52b68e58..9685bcab 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Recipient.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Recipient.java @@ -20,13 +20,16 @@ public class Recipient { private final Profile profile; + private final Boolean discoverable; + public Recipient( final RecipientId recipientId, final RecipientAddress address, final Contact contact, final ProfileKey profileKey, final ExpiringProfileKeyCredential expiringProfileKeyCredential, - final Profile profile + final Profile profile, + final Boolean discoverable ) { this.recipientId = recipientId; this.address = address; @@ -34,6 +37,7 @@ public class Recipient { this.profileKey = profileKey; this.expiringProfileKeyCredential = expiringProfileKeyCredential; this.profile = profile; + this.discoverable = discoverable; } private Recipient(final Builder builder) { @@ -41,8 +45,9 @@ public class Recipient { address = builder.address; contact = builder.contact; profileKey = builder.profileKey; - expiringProfileKeyCredential = builder.expiringProfileKeyCredential1; + expiringProfileKeyCredential = builder.expiringProfileKeyCredential; profile = builder.profile; + discoverable = builder.discoverable; } public static Builder newBuilder() { @@ -55,7 +60,7 @@ public class Recipient { builder.address = copy.getAddress(); builder.contact = copy.getContact(); builder.profileKey = copy.getProfileKey(); - builder.expiringProfileKeyCredential1 = copy.getExpiringProfileKeyCredential(); + builder.expiringProfileKeyCredential = copy.getExpiringProfileKeyCredential(); builder.profile = copy.getProfile(); return builder; } @@ -84,6 +89,10 @@ public class Recipient { return profile; } + public Boolean getDiscoverable() { + return discoverable; + } + @Override public boolean equals(final Object o) { if (this == o) return true; @@ -108,8 +117,9 @@ public class Recipient { private RecipientAddress address; private Contact contact; private ProfileKey profileKey; - private ExpiringProfileKeyCredential expiringProfileKeyCredential1; + private ExpiringProfileKeyCredential expiringProfileKeyCredential; private Profile profile; + private Boolean discoverable; private Builder() { } @@ -135,7 +145,7 @@ public class Recipient { } public Builder withExpiringProfileKeyCredential(final ExpiringProfileKeyCredential val) { - expiringProfileKeyCredential1 = val; + expiringProfileKeyCredential = val; return this; } @@ -144,6 +154,11 @@ public class Recipient { return this; } + public Builder withDiscoverable(final Boolean val) { + discoverable = val; + return this; + } + public Recipient build() { return new Recipient(this); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java index 5d6b90d4..4a2b79bd 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java @@ -363,6 +363,7 @@ public final class ProfileHelper { logger.trace("Storing profile"); account.getProfileStore().storeProfile(recipientId, newProfile); + account.getRecipientStore().markRegistered(recipientId, true); logger.trace("Done handling retrieved profile"); }).doOnError(e -> { @@ -374,6 +375,10 @@ public final class ProfileHelper { .withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN) .withCapabilities(Set.of()) .build(); + if (e instanceof NotFoundException) { + logger.debug("Marking recipient {} as unregistered after 404 profile fetch.", recipientId); + account.getRecipientStore().markRegistered(recipientId, false); + } account.getProfileStore().storeProfile(recipientId, newProfile); }); 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 3bdd8227..092cff6f 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 @@ -187,7 +187,8 @@ public class RecipientHelper { final var unregisteredUsers = new HashSet<>(numbers); unregisteredUsers.removeAll(registeredUsers.keySet()); - account.getRecipientStore().markUnregistered(unregisteredUsers); + account.getRecipientStore().markUndiscoverablePossiblyUnregistered(unregisteredUsers); + account.getRecipientStore().markDiscoverable(registeredUsers.keySet()); return registeredUsers; } 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 b43dfba4..8b32b19b 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 @@ -1310,7 +1310,8 @@ public class ManagerImpl implements Manager { s.getContact(), s.getProfileKey(), s.getExpiringProfileKeyCredential(), - s.getProfile())) + s.getProfile(), + s.getDiscoverable())) .toList(); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java index bf751fbc..3d9fad9c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java @@ -33,7 +33,7 @@ import java.util.UUID; public class AccountDatabase extends Database { private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class); - private static final long DATABASE_VERSION = 25; + private static final long DATABASE_VERSION = 26; private AccountDatabase(final HikariDataSource dataSource) { super(logger, DATABASE_VERSION, dataSource); @@ -591,6 +591,15 @@ public class AccountDatabase extends Database { """); } } + if (oldVersion < 26) { + logger.debug("Updating database: Create discoverabel and profile_phone_number_sharing columns"); + try (final var statement = connection.createStatement()) { + statement.executeUpdate(""" + ALTER TABLE recipient ADD discoverable INTEGER; + ALTER TABLE recipient ADD profile_phone_number_sharing TEXT; + """); + } + } } private static void createUuidMappingTable( 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 a3a98c6c..a65d157d 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 @@ -914,7 +914,8 @@ public class SignalAccount implements Closeable { : profile.getUnidentifiedAccess() != null ? Profile.UnidentifiedAccessMode.ENABLED : Profile.UnidentifiedAccessMode.DISABLED, - capabilities); + capabilities, + null); getProfileStore().storeProfile(recipientId, newProfile); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java index 186ac517..e7aff640 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java @@ -87,7 +87,8 @@ public class LegacyRecipientStore2 { r.profile.capabilities.stream() .map(Profile.Capability::valueOfOrNull) .filter(Objects::nonNull) - .collect(Collectors.toSet())); + .collect(Collectors.toSet()), + null); } return new Recipient(recipientId, @@ -96,6 +97,7 @@ public class LegacyRecipientStore2 { profileKey, expiringProfileKeyCredential, profile, + null, null); }).collect(Collectors.toMap(Recipient::getRecipientId, r -> r)); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/Recipient.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/Recipient.java index 1d5fb9c8..c5e1181b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/Recipient.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/Recipient.java @@ -21,6 +21,8 @@ public class Recipient { private final Profile profile; + private final Boolean discoverable; + private final byte[] storageRecord; public Recipient( @@ -30,6 +32,7 @@ public class Recipient { final ProfileKey profileKey, final ExpiringProfileKeyCredential expiringProfileKeyCredential, final Profile profile, + final Boolean discoverable, final byte[] storageRecord ) { this.recipientId = recipientId; @@ -38,6 +41,7 @@ public class Recipient { this.profileKey = profileKey; this.expiringProfileKeyCredential = expiringProfileKeyCredential; this.profile = profile; + this.discoverable = discoverable; this.storageRecord = storageRecord; } @@ -48,6 +52,7 @@ public class Recipient { profileKey = builder.profileKey; expiringProfileKeyCredential = builder.expiringProfileKeyCredential; profile = builder.profile; + discoverable = builder.discoverable; storageRecord = builder.storageRecord; } @@ -91,6 +96,10 @@ public class Recipient { return profile; } + public Boolean getDiscoverable() { + return discoverable; + } + public byte[] getStorageRecord() { return storageRecord; } @@ -121,6 +130,7 @@ public class Recipient { private ProfileKey profileKey; private ExpiringProfileKeyCredential expiringProfileKeyCredential; private Profile profile; + private Boolean discoverable; private byte[] storageRecord; private Builder() { @@ -156,6 +166,11 @@ public class Recipient { return this; } + public Builder withDiscoverable(final Boolean val) { + discoverable = val; + return this; + } + public Builder withStorageRecord(final byte[] val) { storageRecord = val; return this; 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 5c12a1ec..6428d202 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 @@ -2,6 +2,7 @@ package org.asamk.signal.manager.storage.recipients; import org.asamk.signal.manager.api.Contact; import org.asamk.signal.manager.api.Pair; +import org.asamk.signal.manager.api.PhoneNumberSharingMode; import org.asamk.signal.manager.api.Profile; import org.asamk.signal.manager.api.UnregisteredRecipientException; import org.asamk.signal.manager.storage.Database; @@ -64,6 +65,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re aci TEXT UNIQUE, pni TEXT UNIQUE, unregistered_timestamp INTEGER, + discoverable INTEGER, profile_key BLOB, profile_key_credential BLOB, needs_pni_signature INTEGER NOT NULL DEFAULT FALSE, @@ -92,7 +94,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re profile_avatar_url_path TEXT, profile_mobile_coin_address BLOB, profile_unidentified_access_mode TEXT, - profile_capabilities TEXT + profile_capabilities TEXT, + profile_phone_number_sharing TEXT ) STRICT; """); } @@ -354,7 +357,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, - r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, + r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, + r.discoverable, r.storage_record FROM %s r WHERE r._id = ? @@ -373,7 +377,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, - r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, + r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, + r.discoverable, r.storage_record FROM %s r WHERE r.storage_id = ? @@ -409,7 +414,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, - r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, + r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, + r.discoverable, r.storage_record FROM %s r WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s @@ -898,15 +904,19 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } } - public void markUnregistered(final Set unregisteredUsers) { - logger.debug("Marking {} numbers as unregistered", unregisteredUsers.size()); + public void markUndiscoverablePossiblyUnregistered(final Set numbers) { + logger.debug("Marking {} numbers as unregistered", numbers.size()); try (final var connection = database.getConnection()) { connection.setAutoCommit(false); - for (final var number : unregisteredUsers) { - final var recipient = findByNumber(connection, number); - if (recipient.isPresent()) { - final var recipientId = recipient.get().id(); - markUnregisteredAndSplitIfNecessary(connection, recipientId); + for (final var number : numbers) { + final var recipientAddress = findByNumber(connection, number); + if (recipientAddress.isPresent()) { + final var recipientId = recipientAddress.get().id(); + markDiscoverable(connection, recipientId, false); + final var contact = getContact(connection, recipientId); + if (recipientAddress.get().address().aci().isEmpty() || contact.unregisteredTimestamp() != null) { + markUnregisteredAndSplitIfNecessary(connection, recipientId); + } } } connection.commit(); @@ -915,6 +925,38 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } } + public void markDiscoverable(final Set numbers) { + logger.debug("Marking {} numbers as discoverable", numbers.size()); + try (final var connection = database.getConnection()) { + connection.setAutoCommit(false); + for (final var number : numbers) { + final var recipientAddress = findByNumber(connection, number); + if (recipientAddress.isPresent()) { + final var recipientId = recipientAddress.get().id(); + markDiscoverable(connection, recipientId, true); + } + } + connection.commit(); + } catch (SQLException e) { + throw new RuntimeException("Failed update recipient store", e); + } + } + + public void markRegistered(final RecipientId recipientId, final boolean registered) { + logger.debug("Marking {} as registered={}", recipientId, registered); + try (final var connection = database.getConnection()) { + connection.setAutoCommit(false); + if (registered) { + markRegistered(connection, recipientId); + } else { + markUnregistered(connection, recipientId); + } + connection.commit(); + } catch (SQLException e) { + throw new RuntimeException("Failed update recipient store", e); + } + } + private void markUnregisteredAndSplitIfNecessary( final Connection connection, final RecipientId recipientId ) throws SQLException { @@ -927,6 +969,23 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } } + private void markDiscoverable( + final Connection connection, final RecipientId recipientId, final boolean discoverable + ) throws SQLException { + final var sql = ( + """ + UPDATE %s + SET discoverable = ? + WHERE _id = ? + """ + ).formatted(TABLE_RECIPIENT); + try (final var statement = connection.prepareStatement(sql)) { + statement.setBoolean(1, discoverable); + statement.setLong(2, recipientId.id()); + statement.executeUpdate(); + } + } + private void markRegistered( final Connection connection, final RecipientId recipientId ) throws SQLException { @@ -949,8 +1008,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ UPDATE %s - SET unregistered_timestamp = ? - WHERE _id = ? AND unregistered_timestamp IS NULL + SET unregistered_timestamp = ?, discoverable = FALSE + WHERE _id = ? """ ).formatted(TABLE_RECIPIENT); try (final var statement = connection.prepareStatement(sql)) { @@ -985,7 +1044,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ UPDATE %s - SET profile_last_update_timestamp = ?, profile_given_name = ?, profile_family_name = ?, profile_about = ?, profile_about_emoji = ?, profile_avatar_url_path = ?, profile_mobile_coin_address = ?, profile_unidentified_access_mode = ?, profile_capabilities = ? + SET profile_last_update_timestamp = ?, profile_given_name = ?, profile_family_name = ?, profile_about = ?, profile_about_emoji = ?, profile_avatar_url_path = ?, profile_mobile_coin_address = ?, profile_unidentified_access_mode = ?, profile_capabilities = ?, profile_phone_number_sharing = ? WHERE _id = ? """ ).formatted(TABLE_RECIPIENT); @@ -1002,7 +1061,11 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re profile == null ? null : profile.getCapabilities().stream().map(Enum::name).collect(Collectors.joining(","))); - statement.setLong(10, recipientId.id()); + statement.setString(10, + profile == null || profile.getPhoneNumberSharingMode() == null + ? null + : profile.getPhoneNumberSharingMode().name()); + statement.setLong(11, recipientId.id()); statement.executeUpdate(); } rotateStorageId(connection, recipientId); @@ -1396,7 +1459,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public Profile getProfile(final Connection connection, final RecipientId recipientId) throws SQLException { final var sql = ( """ - SELECT r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities + SELECT r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing FROM %s r WHERE r._id = ? AND r.profile_capabilities IS NOT NULL """ @@ -1431,6 +1494,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re getProfileKeyFromResultSet(resultSet), getExpiringProfileKeyCredentialFromResultSet(resultSet), getProfileFromResultSet(resultSet), + getDiscoverableFromResultSet(resultSet), getStorageRecordFromResultSet(resultSet)); } @@ -1453,6 +1517,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re unregisteredTimestamp == 0 ? null : unregisteredTimestamp); } + private static Boolean getDiscoverableFromResultSet(final ResultSet resultSet) throws SQLException { + final var discoverable = resultSet.getBoolean("discoverable"); + if (resultSet.wasNull()) { + return null; + } + return discoverable; + } + private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException { final var profileCapabilities = resultSet.getString("profile_capabilities"); final var profileUnidentifiedAccessMode = resultSet.getString("profile_unidentified_access_mode"); @@ -1471,7 +1543,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re : Arrays.stream(profileCapabilities.split(",")) .map(Profile.Capability::valueOfOrNull) .filter(Objects::nonNull) - .collect(Collectors.toSet())); + .collect(Collectors.toSet()), + PhoneNumberSharingMode.valueOfOrNull(resultSet.getString("profile_phone_number_sharing"))); } private ProfileKey getProfileKeyFromResultSet(ResultSet resultSet) throws SQLException { diff --git a/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java index 82ffc861..17e26ee8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java @@ -1,6 +1,7 @@ package org.asamk.signal.manager.util; import org.asamk.signal.manager.api.Pair; +import org.asamk.signal.manager.api.PhoneNumberSharingMode; import org.asamk.signal.manager.api.Profile; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.InvalidKeyException; @@ -16,6 +17,7 @@ import org.whispersystems.signalservice.internal.push.PaymentAddress; import java.io.IOException; import java.util.Base64; import java.util.HashSet; +import java.util.Optional; public class ProfileUtils { @@ -33,11 +35,14 @@ public class ProfileUtils { } try { - var name = decrypt(encryptedProfile.getName(), profileCipher); - var about = trimZeros(decrypt(encryptedProfile.getAbout(), profileCipher)); - var aboutEmoji = trimZeros(decrypt(encryptedProfile.getAboutEmoji(), profileCipher)); + var name = decryptString(encryptedProfile.getName(), profileCipher); + var about = decryptString(encryptedProfile.getAbout(), profileCipher); + var aboutEmoji = decryptString(encryptedProfile.getAboutEmoji(), profileCipher); final var nameParts = splitName(name); + final var remotePhoneNumberSharing = decryptBoolean(encryptedProfile.getPhoneNumberSharing(), + profileCipher).map(v -> v ? PhoneNumberSharingMode.EVERYBODY : PhoneNumberSharingMode.NOBODY) + .orElse(null); return new Profile(System.currentTimeMillis(), nameParts.first(), nameParts.second(), @@ -50,7 +55,8 @@ public class ProfileUtils { profileCipher, identityKey.getPublicKey()), getUnidentifiedAccessMode(encryptedProfile, profileCipher), - getCapabilities(encryptedProfile)); + getCapabilities(encryptedProfile), + remotePhoneNumberSharing); } catch (InvalidCiphertextException e) { logger.debug("Failed to decrypt profile for {}", encryptedProfile.getServiceId(), e); return null; @@ -83,18 +89,28 @@ public class ProfileUtils { return capabilities; } - private static String decrypt( - final String encryptedName, final ProfileCipher profileCipher + private static String decryptString( + final String encrypted, final ProfileCipher profileCipher ) throws InvalidCiphertextException { try { - return encryptedName == null - ? null - : new String(profileCipher.decrypt(Base64.getDecoder().decode(encryptedName))); + return encrypted == null ? null : profileCipher.decryptString(Base64.getDecoder().decode(encrypted)); } catch (IllegalArgumentException e) { return null; } } + private static Optional decryptBoolean( + final String encrypted, final ProfileCipher profileCipher + ) throws InvalidCiphertextException { + try { + return encrypted == null + ? Optional.empty() + : profileCipher.decryptBoolean(Base64.getDecoder().decode(encrypted)); + } catch (IllegalArgumentException e) { + return Optional.empty(); + } + } + private static byte[] decryptAndVerifyMobileCoinAddress( final byte[] encryptedPaymentAddress, final ProfileCipher profileCipher, final ECPublicKey publicKey ) throws InvalidCiphertextException { @@ -129,13 +145,4 @@ public class ProfileUtils { default -> new Pair<>(parts[0], parts[1]); }; } - - static String trimZeros(String str) { - if (str == null) { - return null; - } - - int pos = str.indexOf(0); - return pos == -1 ? str : str.substring(0, pos); - } } From 8aeaf927e61b3eb2d0e2ac5cddf3a1ffa4ea98ff Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 17 Apr 2024 20:50:03 +0200 Subject: [PATCH 047/279] Add missing parts for new nick name and note columns --- .../storage/recipients/RecipientStore.java | 19 +++++++++++-------- .../syncStorage/ContactRecordProcessor.java | 8 +++++++- .../syncStorage/StorageSyncModels.java | 8 ++++++-- 3 files changed, 24 insertions(+), 11 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 6428d202..31a5a963 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 @@ -41,7 +41,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re private static final Logger logger = LoggerFactory.getLogger(RecipientStore.class); private static final String TABLE_RECIPIENT = "recipient"; - private static final String SQL_IS_CONTACT = "r.given_name IS NOT NULL OR r.family_name IS NOT NULL OR r.nick_name IS NOT NULL OR r.expiration_time > 0 OR r.profile_sharing = TRUE OR r.color IS NOT NULL OR r.blocked = TRUE OR r.archived = TRUE"; + private static final String SQL_IS_CONTACT = "r.given_name IS NOT NULL OR r.family_name IS NOT NULL OR r.nick_name IS NOT NULL OR r.nick_name_given_name IS NOT NULL OR r.nick_name_family_name IS NOT NULL OR r.note IS NOT NULL OR r.expiration_time > 0 OR r.profile_sharing = TRUE OR r.color IS NOT NULL OR r.blocked = TRUE OR r.archived = TRUE"; private final RecipientMergeHandler recipientMergeHandler; private final SelfAddressProvider selfAddressProvider; @@ -332,7 +332,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public List> getContacts() { final var sql = ( """ - SELECT r._id, r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp + SELECT r._id, r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp FROM %s r WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s AND r.hidden = FALSE """ @@ -356,7 +356,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re SELECT r._id, r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, + r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, r.discoverable, r.storage_record @@ -376,7 +376,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re SELECT r._id, r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, + r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, r.discoverable, r.storage_record @@ -413,7 +413,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re SELECT r._id, r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, + r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp, r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing, r.discoverable, r.storage_record @@ -817,7 +817,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ UPDATE %s - SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?, unregistered_timestamp = ? + SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?, unregistered_timestamp = ?, nick_name_given_name = ?, nick_name_family_name = ?, note = ? WHERE _id = ? """ ).formatted(TABLE_RECIPIENT); @@ -837,7 +837,10 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } else { statement.setLong(11, contact.unregisteredTimestamp()); } - statement.setLong(12, recipientId.id()); + statement.setString(12, contact == null ? null : contact.nickNameGivenName()); + statement.setString(13, contact == null ? null : contact.nickNameFamilyName()); + statement.setString(14, contact == null ? null : contact.note()); + statement.setLong(15, recipientId.id()); statement.executeUpdate(); } if (contact != null && contact.unregisteredTimestamp() != null) { @@ -1410,7 +1413,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re private Contact getContact(final Connection connection, final RecipientId recipientId) throws SQLException { final var sql = ( """ - SELECT r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp + SELECT r.given_name, r.family_name, r.nick_name, r.nick_name_given_name, r.nick_name_family_name, r.note, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp FROM %s r WHERE r._id = ? AND (%s) """ diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java index a087553b..72ee4163 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java @@ -250,6 +250,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor Date: Wed, 17 Apr 2024 21:26:16 +0200 Subject: [PATCH 048/279] Add more details to listContacts command Fixes #1502 --- graalvm-config-dir/reflect-config.json | 18 ++++ man/signal-cli.1.adoc | 6 ++ .../signal/commands/ListContactsCommand.java | 100 +++++++++++++----- .../org/asamk/signal/json/JsonContact.java | 44 ++++++++ 4 files changed, 143 insertions(+), 25 deletions(-) create mode 100644 src/main/java/org/asamk/signal/json/JsonContact.java diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 252839b2..c6e85760 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -849,6 +849,24 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"id","parameterTypes":[] }, {"name":"opaque","parameterTypes":[] }, {"name":"sdp","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] }, +{ + "name":"org.asamk.signal.json.JsonContact", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"color","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"internal","parameterTypes":[] }, {"name":"isBlocked","parameterTypes":[] }, {"name":"isHidden","parameterTypes":[] }, {"name":"messageExpirationTime","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"nickFamilyName","parameterTypes":[] }, {"name":"nickGivenName","parameterTypes":[] }, {"name":"nickName","parameterTypes":[] }, {"name":"note","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"profile","parameterTypes":[] }, {"name":"profileSharing","parameterTypes":[] }, {"name":"unregistered","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContact$JsonInternal", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"capabilities","parameterTypes":[] }, {"name":"discoverableByPhonenumber","parameterTypes":[] }, {"name":"sharesPhoneNumber","parameterTypes":[] }, {"name":"unidentifiedAccessMode","parameterTypes":[] }] +}, +{ + "name":"org.asamk.signal.json.JsonContact$JsonProfile", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "methods":[{"name":"about","parameterTypes":[] }, {"name":"aboutEmoji","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"hasAvatar","parameterTypes":[] }, {"name":"lastUpdateTimestamp","parameterTypes":[] }, {"name":"mobileCoinAddress","parameterTypes":[] }] +}, { "name":"org.asamk.signal.json.JsonContactAddress", "allDeclaredFields":true, diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 31bc5499..7335eba6 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -590,6 +590,12 @@ Specify if only blocked or unblocked contacts should be shown (default: all cont *--name*:: Find contacts with the given contact or profile name. +*--detailed*:: +List the contacts with more details. If output=json, then this is always set + +*--internal*:: +Include internal information that's normally not user visible + === listIdentities List all known identity keys and their trust status, fingerprint and safety number. diff --git a/src/main/java/org/asamk/signal/commands/ListContactsCommand.java b/src/main/java/org/asamk/signal/commands/ListContactsCommand.java index 698e6f2b..3865d829 100644 --- a/src/main/java/org/asamk/signal/commands/ListContactsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListContactsCommand.java @@ -5,8 +5,10 @@ import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.asamk.signal.commands.exceptions.CommandException; +import org.asamk.signal.json.JsonContact; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.Contact; +import org.asamk.signal.manager.api.PhoneNumberSharingMode; import org.asamk.signal.manager.api.Profile; import org.asamk.signal.output.JsonWriter; import org.asamk.signal.output.OutputWriter; @@ -35,6 +37,12 @@ public class ListContactsCommand implements JsonRpcLocalCommand { .type(Boolean.class) .help("Specify if only blocked or unblocked contacts should be shown (default: all contacts)"); subparser.addArgument("--name").help("Find contacts with the given contact or profile name."); + subparser.addArgument("--detailed") + .action(Arguments.storeTrue()) + .help("List the contacts with more details. If output=json, then this is always set"); + subparser.addArgument("--internal") + .action(Arguments.storeTrue()) + .help("Include internal information that's normally not user visible"); } @Override @@ -51,33 +59,94 @@ public class ListContactsCommand implements JsonRpcLocalCommand { recipientIdentifiers, Optional.ofNullable(name)); + final var detailed = Boolean.TRUE.equals(ns.getBoolean("detailed")); + final var internal = Boolean.TRUE.equals(ns.getBoolean("internal")); + switch (outputWriter) { case PlainTextWriter writer -> { for (var r : recipients) { final var contact = r.getContact() == null ? Contact.newBuilder().build() : r.getContact(); final var profile = r.getProfile() == null ? Profile.newBuilder().build() : r.getProfile(); writer.println( - "Number: {} Name: {} Profile name: {} Username: {} Color: {} Blocked: {} Message expiration: {}", - r.getAddress().getLegacyIdentifier(), + "Number: {} ACI: {} Name: {} Profile name: {} Username: {} Color: {} Blocked: {} Message expiration: {}", + r.getAddress().number().orElse(""), + r.getAddress().aci().orElse(""), contact.getName(), profile.getDisplayName(), r.getAddress().username().orElse(""), - contact.color(), + Optional.ofNullable(contact.color()).orElse(""), contact.isBlocked(), contact.messageExpirationTime() == 0 ? "disabled" : contact.messageExpirationTime() + "s"); + if (detailed) { + writer.indentedWriter() + .println( + "PNI: {} Given name: {} Family name: {}, Nick name: {} Nick given name: {} Nick family name {} Note: {} Archived: {} Hidden: {} Profile sharing: {} About: {} About Emoji: {} Unregistered: {}", + r.getAddress().pni().orElse(""), + Optional.ofNullable(r.getContact().givenName()).orElse(""), + Optional.ofNullable(r.getContact().familyName()).orElse(""), + Optional.ofNullable(r.getContact().nickName()).orElse(""), + Optional.ofNullable(r.getContact().nickNameGivenName()).orElse(""), + Optional.ofNullable(r.getContact().nickNameFamilyName()).orElse(""), + Optional.ofNullable(r.getContact().note()).orElse(""), + r.getContact().isArchived(), + r.getContact().isHidden(), + r.getContact().isProfileSharingEnabled(), + Optional.ofNullable(r.getProfile().getAbout()).orElse(""), + Optional.ofNullable(r.getProfile().getAboutEmoji()).orElse(""), + r.getContact().unregisteredTimestamp() != null); + } + if (internal) { + writer.indentedWriter() + .println( + "Capabilities: {} Unidentified access mode: {} Shares number: {} Discoverable by number: {}", + r.getProfile().getCapabilities().stream().map(Enum::name).toList(), + Optional.ofNullable(r.getProfile().getUnidentifiedAccessMode() + == Profile.UnidentifiedAccessMode.UNKNOWN + ? null + : r.getProfile().getUnidentifiedAccessMode().name()).orElse(""), + r.getProfile().getPhoneNumberSharingMode() == null + ? "" + : String.valueOf(r.getProfile().getPhoneNumberSharingMode() + == PhoneNumberSharingMode.EVERYBODY), + r.getDiscoverable() == null ? "" : String.valueOf(r.getDiscoverable())); + } } } case JsonWriter writer -> { final var jsonContacts = recipients.stream().map(r -> { final var address = r.getAddress(); final var contact = r.getContact() == null ? Contact.newBuilder().build() : r.getContact(); + final var jsonInternal = !internal + ? null + : new JsonContact.JsonInternal(r.getProfile() + .getCapabilities() + .stream() + .map(Enum::name) + .toList(), + r.getProfile().getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNKNOWN + ? null + : r.getProfile().getUnidentifiedAccessMode().name(), + r.getProfile().getPhoneNumberSharingMode() == null + ? null + : r.getProfile().getPhoneNumberSharingMode() + == PhoneNumberSharingMode.EVERYBODY, + r.getDiscoverable()); return new JsonContact(address.number().orElse(null), address.uuid().map(UUID::toString).orElse(null), address.username().orElse(null), contact.getName(), + contact.givenName(), + contact.familyName(), + contact.nickName(), + contact.nickNameGivenName(), + contact.nickNameFamilyName(), + contact.note(), contact.color(), contact.isBlocked(), + contact.isHidden(), contact.messageExpirationTime(), + r.getContact().isProfileSharingEnabled(), + r.getContact().unregisteredTimestamp() != null, r.getProfile() == null ? null : new JsonContact.JsonProfile(r.getProfile().getLastUpdateTimestamp(), @@ -85,34 +154,15 @@ public class ListContactsCommand implements JsonRpcLocalCommand { r.getProfile().getFamilyName(), r.getProfile().getAbout(), r.getProfile().getAboutEmoji(), + r.getProfile().getAvatarUrlPath() != null, r.getProfile().getMobileCoinAddress() == null ? null : Base64.getEncoder() - .encodeToString(r.getProfile().getMobileCoinAddress()))); + .encodeToString(r.getProfile().getMobileCoinAddress())), + jsonInternal); }).toList(); writer.write(jsonContacts); } } } - - private record JsonContact( - String number, - String uuid, - String username, - String name, - String color, - boolean isBlocked, - int messageExpirationTime, - JsonProfile profile - ) { - - private record JsonProfile( - long lastUpdateTimestamp, - String givenName, - String familyName, - String about, - String aboutEmoji, - String mobileCoinAddress - ) {} - } } diff --git a/src/main/java/org/asamk/signal/json/JsonContact.java b/src/main/java/org/asamk/signal/json/JsonContact.java new file mode 100644 index 00000000..71f05c8d --- /dev/null +++ b/src/main/java/org/asamk/signal/json/JsonContact.java @@ -0,0 +1,44 @@ +package org.asamk.signal.json; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; + +public record JsonContact( + String number, + String uuid, + String username, + String name, + String givenName, + String familyName, + String nickName, + String nickGivenName, + String nickFamilyName, + String note, + String color, + boolean isBlocked, + boolean isHidden, + int messageExpirationTime, + boolean profileSharing, + boolean unregistered, + JsonProfile profile, + @JsonInclude(JsonInclude.Include.NON_NULL) JsonInternal internal +) { + + public record JsonProfile( + long lastUpdateTimestamp, + String givenName, + String familyName, + String about, + String aboutEmoji, + boolean hasAvatar, + String mobileCoinAddress + ) {} + + public record JsonInternal( + List capabilities, + String unidentifiedAccessMode, + Boolean sharesPhoneNumber, + Boolean discoverableByPhonenumber + ) {} +} From c1775913b9c54c84b45154aa841a0ba475c7c9fd Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 17:07:18 +0200 Subject: [PATCH 049/279] Implement dbus support for listIdentities Fixes #195 --- graalvm-config-dir/proxy-config.json | 3 +++ graalvm-config-dir/reflect-config.json | 6 ++++-- .../asamk/signal/manager/api/Identity.java | 11 ++--------- .../signal/manager/internal/ManagerImpl.java | 2 +- src/main/java/org/asamk/Signal.java | 2 +- .../commands/ListIdentitiesCommand.java | 4 ++-- .../asamk/signal/dbus/DbusManagerImpl.java | 19 +++++++++++++++++-- .../org/asamk/signal/dbus/DbusSignalImpl.java | 2 +- 8 files changed, 31 insertions(+), 18 deletions(-) diff --git a/graalvm-config-dir/proxy-config.json b/graalvm-config-dir/proxy-config.json index d9a734a8..1a97b30a 100644 --- a/graalvm-config-dir/proxy-config.json +++ b/graalvm-config-dir/proxy-config.json @@ -14,6 +14,9 @@ { "interfaces":["org.asamk.Signal$Group"] }, + { + "interfaces":["org.asamk.Signal$Identity"] + }, { "interfaces":["org.asamk.SignalControl"] }, diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index c6e85760..67252c9d 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -572,7 +572,7 @@ "name":"org.asamk.Signal", "allDeclaredMethods":true, "allDeclaredClasses":true, - "methods":[{"name":"getContactName","parameterTypes":["java.lang.String"] }, {"name":"getDevice","parameterTypes":["long"] }, {"name":"getGroup","parameterTypes":["byte[]"] }, {"name":"getSelfNumber","parameterTypes":[] }, {"name":"getThisDevice","parameterTypes":[] }, {"name":"listDevices","parameterTypes":[] }, {"name":"sendGroupMessageReaction","parameterTypes":["java.lang.String","boolean","java.lang.String","long","byte[]"] }, {"name":"sendMessage","parameterTypes":["java.lang.String","java.util.List","java.lang.String"] }, {"name":"sendMessage","parameterTypes":["java.lang.String","java.util.List","java.util.List"] }, {"name":"sendMessageReaction","parameterTypes":["java.lang.String","boolean","java.lang.String","long","java.util.List"] }, {"name":"subscribeReceive","parameterTypes":[] }, {"name":"unsubscribeReceive","parameterTypes":[] }, {"name":"version","parameterTypes":[] }] + "methods":[{"name":"getContactName","parameterTypes":["java.lang.String"] }, {"name":"getDevice","parameterTypes":["long"] }, {"name":"getGroup","parameterTypes":["byte[]"] }, {"name":"getSelfNumber","parameterTypes":[] }, {"name":"getThisDevice","parameterTypes":[] }, {"name":"listDevices","parameterTypes":[] }, {"name":"listIdentities","parameterTypes":[] }, {"name":"sendGroupMessageReaction","parameterTypes":["java.lang.String","boolean","java.lang.String","long","byte[]"] }, {"name":"sendMessage","parameterTypes":["java.lang.String","java.util.List","java.lang.String"] }, {"name":"sendMessage","parameterTypes":["java.lang.String","java.util.List","java.util.List"] }, {"name":"sendMessageReaction","parameterTypes":["java.lang.String","boolean","java.lang.String","long","java.util.List"] }, {"name":"subscribeReceive","parameterTypes":[] }, {"name":"unsubscribeReceive","parameterTypes":[] }, {"name":"version","parameterTypes":[] }] }, { "name":"org.asamk.Signal$Configuration", @@ -651,7 +651,9 @@ }, { "name":"org.asamk.Signal$StructIdentity", - "allDeclaredFields":true + "allDeclaredFields":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["org.freedesktop.dbus.DBusPath","java.lang.String","java.lang.String"] }] }, { "name":"org.asamk.Signal$SyncMessageReceived", diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Identity.java b/lib/src/main/java/org/asamk/signal/manager/api/Identity.java index 5d43aacc..91f46750 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Identity.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Identity.java @@ -1,17 +1,10 @@ package org.asamk.signal.manager.api; -import org.signal.libsignal.protocol.IdentityKey; - public record Identity( RecipientAddress recipient, - IdentityKey identityKey, + byte[] fingerprint, String safetyNumber, byte[] scannableSafetyNumber, TrustLevel trustLevel, long dateAddedTimestamp -) { - - public byte[] getFingerprint() { - return identityKey.getPublicKey().serialize(); - } -} +) {} 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 8b32b19b..ef2e52e3 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 @@ -1366,7 +1366,7 @@ public class ManagerImpl implements Manager { final var scannableFingerprint = context.getIdentityHelper() .computeSafetyNumberForScanning(identityInfo.getServiceId(), identityInfo.getIdentityKey()); return new Identity(address.toApiRecipientAddress(), - identityInfo.getIdentityKey(), + identityInfo.getIdentityKey().getPublicKey().serialize(), context.getIdentityHelper() .computeSafetyNumber(identityInfo.getServiceId(), identityInfo.getIdentityKey()), scannableFingerprint == null ? null : scannableFingerprint.getSerialized(), diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index 1fab8102..1d715659 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -642,7 +642,7 @@ public interface Signal extends DBusInterface { @DBusProperty(name = "Fingerprint", type = Byte[].class, access = DBusProperty.Access.READ) @DBusProperty(name = "SafetyNumber", type = String.class, access = DBusProperty.Access.READ) @DBusProperty(name = "TrustLevel", type = String.class, access = DBusProperty.Access.READ) - @DBusProperty(name = "AddedDate", type = Integer.class, access = DBusProperty.Access.READ) + @DBusProperty(name = "AddedDate", type = Long.class, access = DBusProperty.Access.READ) @DBusProperty(name = "ScannableSafetyNumber", type = Byte[].class, access = DBusProperty.Access.READ) interface Identity extends DBusInterface, Properties { diff --git a/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java b/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java index 3c4e7a19..6b49a812 100644 --- a/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java @@ -34,7 +34,7 @@ public class ListIdentitiesCommand implements JsonRpcLocalCommand { theirId.recipient().getLegacyIdentifier(), theirId.trustLevel(), DateUtils.formatTimestamp(theirId.dateAddedTimestamp()), - Hex.toString(theirId.getFingerprint()), + Hex.toString(theirId.fingerprint()), Util.formatSafetyNumber(theirId.safetyNumber())); } @@ -70,7 +70,7 @@ public class ListIdentitiesCommand implements JsonRpcLocalCommand { var scannableSafetyNumber = id.scannableSafetyNumber(); return new JsonIdentity(address.number().orElse(null), address.uuid().map(UUID::toString).orElse(null), - Hex.toString(id.getFingerprint()), + Hex.toString(id.fingerprint()), safetyNumber, scannableSafetyNumber == null ? null diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index 4573b861..574f68c6 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -41,6 +41,7 @@ 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.TrustLevel; import org.asamk.signal.manager.api.TypingAction; import org.asamk.signal.manager.api.UnregisteredRecipientException; import org.asamk.signal.manager.api.UpdateGroup; @@ -759,12 +760,26 @@ public class DbusManagerImpl implements Manager { @Override public List getIdentities() { - throw new UnsupportedOperationException(); + final var identities = signal.listIdentities(); + return identities.stream().map(Signal.StructIdentity::getObjectPath).map(this::getIdentity).toList(); } @Override public List getIdentities(final RecipientIdentifier.Single recipient) { - throw new UnsupportedOperationException(); + final var path = signal.getIdentity(recipient.getIdentifier()); + return List.of(getIdentity(path)); + } + + private Identity getIdentity(final DBusPath identityPath) { + final var group = getRemoteObject(identityPath, Signal.Identity.class).GetAll("org.asamk.Signal.Identity"); + final var aci = (String) group.get("Uuid").getValue(); + final var number = (String) group.get("Number").getValue(); + return new Identity(new RecipientAddress(aci, null, number, null), + (byte[]) group.get("Fingerprint").getValue(), + (String) group.get("SafetyNumber").getValue(), + (byte[]) group.get("ScannableSafetyNumber").getValue(), + TrustLevel.valueOf((String) group.get("TrustLevel").getValue()), + (Long) group.get("AddedDate").getValue()); } @Override diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 65c210fc..fae0c0a5 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -1105,7 +1105,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { List.of(new DbusProperty<>("Number", () -> identity.recipient().number().orElse("")), new DbusProperty<>("Uuid", () -> identity.recipient().uuid().map(UUID::toString).orElse("")), - new DbusProperty<>("Fingerprint", identity::getFingerprint), + new DbusProperty<>("Fingerprint", identity::fingerprint), new DbusProperty<>("SafetyNumber", identity::safetyNumber), new DbusProperty<>("ScannableSafetyNumber", identity::scannableSafetyNumber), new DbusProperty<>("TrustLevel", identity::trustLevel), From cef83d962cf5415e01fd65d4e602aaa42850d517 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 17:07:29 +0200 Subject: [PATCH 050/279] Fix missing null check --- .../signal/manager/storage/recipients/RecipientStore.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 31a5a963..6a672008 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 @@ -917,7 +917,10 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var recipientId = recipientAddress.get().id(); markDiscoverable(connection, recipientId, false); final var contact = getContact(connection, recipientId); - if (recipientAddress.get().address().aci().isEmpty() || contact.unregisteredTimestamp() != null) { + if (recipientAddress.get().address().aci().isEmpty() || ( + contact != null + && contact.unregisteredTimestamp() != null + )) { markUnregisteredAndSplitIfNecessary(connection, recipientId); } } From 0a82c51b7937df9195501e61266d28756472420b Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 17:13:25 +0200 Subject: [PATCH 051/279] Update dependencies --- graalvm-config-dir/jni-config.json | 12 ++++++++++++ settings.gradle.kts | 10 +++++----- src/main/java/org/asamk/signal/BaseConfig.java | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index d7667b09..5fed7404 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -89,6 +89,18 @@ "name":"org.signal.libsignal.net.CdsiLookupResponse$Entry", "methods":[{"name":"","parameterTypes":["byte[]","byte[]"] }] }, +{ + "name":"org.signal.libsignal.net.ChatService" +}, +{ + "name":"org.signal.libsignal.net.ChatService$DebugInfo" +}, +{ + "name":"org.signal.libsignal.net.ChatService$Response" +}, +{ + "name":"org.signal.libsignal.net.ChatService$ResponseAndDebugInfo" +}, { "name":"org.signal.libsignal.protocol.DuplicateMessageException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] diff --git a/settings.gradle.kts b/settings.gradle.kts index 8ef7a6e9..cf324502 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,17 +6,17 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { - library("bouncycastle", "org.bouncycastle", "bcprov-jdk18on").version("1.78") + library("bouncycastle", "org.bouncycastle", "bcprov-jdk18on").version("1.78.1") library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.17.0") library("argparse4j", "net.sourceforge.argparse4j", "argparse4j").version("0.9.0") library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("5.0.0") - version("slf4j", "2.0.12") + version("slf4j", "2.0.13") library("slf4j.api", "org.slf4j", "slf4j-api").versionRef("slf4j") library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") - library("logback", "ch.qos.logback", "logback-classic").version("1.5.3") + library("logback", "ch.qos.logback", "logback-classic").version("1.5.6") - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_100") - library("sqlite", "org.xerial", "sqlite-jdbc").version("3.45.2.0") + library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_101") + library("sqlite", "org.xerial", "sqlite-jdbc").version("3.45.3.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") library("junit.launcher", "org.junit.platform", "junit-platform-launcher").version("1.10.2") diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 8a0ddc06..060a2664 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.2.0"); + .orElse("Signal-Android/7.5.0"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From f0054372b89c2a8372c1cc7b78bad559db092375 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 17:25:43 +0200 Subject: [PATCH 052/279] Add timestamp to account file Closes #1498 --- graalvm-config-dir/reflect-config.json | 2 +- .../java/org/asamk/signal/manager/storage/SignalAccount.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 67252c9d..dec3312e 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -1152,7 +1152,7 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"aciAccountData","parameterTypes":[] }, {"name":"deviceId","parameterTypes":[] }, {"name":"encryptedDeviceName","parameterTypes":[] }, {"name":"isMultiDevice","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"password","parameterTypes":[] }, {"name":"pinMasterKey","parameterTypes":[] }, {"name":"pniAccountData","parameterTypes":[] }, {"name":"profileKey","parameterTypes":[] }, {"name":"registered","parameterTypes":[] }, {"name":"registrationLockPin","parameterTypes":[] }, {"name":"serviceEnvironment","parameterTypes":[] }, {"name":"storageKey","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"usernameLinkEntropy","parameterTypes":[] }, {"name":"usernameLinkServerId","parameterTypes":[] }, {"name":"version","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":["int","long","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"aciAccountData","parameterTypes":[] }, {"name":"deviceId","parameterTypes":[] }, {"name":"encryptedDeviceName","parameterTypes":[] }, {"name":"isMultiDevice","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"password","parameterTypes":[] }, {"name":"pinMasterKey","parameterTypes":[] }, {"name":"pniAccountData","parameterTypes":[] }, {"name":"profileKey","parameterTypes":[] }, {"name":"registered","parameterTypes":[] }, {"name":"registrationLockPin","parameterTypes":[] }, {"name":"serviceEnvironment","parameterTypes":[] }, {"name":"storageKey","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"usernameLinkEntropy","parameterTypes":[] }, {"name":"usernameLinkServerId","parameterTypes":[] }, {"name":"version","parameterTypes":[] }] }, { "name":"org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData", 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 a65d157d..ee6f4167 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 @@ -959,6 +959,7 @@ public class SignalAccount implements Closeable { synchronized (fileChannel) { final var base64 = Base64.getEncoder(); final var storage = new Storage(CURRENT_STORAGE_VERSION, + System.currentTimeMillis(), serviceEnvironment.name(), registered, number, @@ -1858,6 +1859,7 @@ public class SignalAccount implements Closeable { public record Storage( int version, + long timestamp, String serviceEnvironment, boolean registered, String number, From c9f2cca0241f93327ebc77aee895bccc29d3d52e Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:21:07 +0200 Subject: [PATCH 053/279] Bump version to 0.13.3 --- CHANGELOG.md | 11 +++++++++-- build.gradle.kts | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a96d412..3a443171 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,15 @@ # Changelog -## [Unreleased] +## [0.13.3] - 2024-04-19 -**Attention**: Now requires libsignal-client version 0.42.0 +**Attention**: Now requires libsignal-client version 0.44.0 + +### Added +- Support for reading contact nickname and notes +- Add `--internal` and `--detailed` parameters to `listContacts` command + +### Fixed +- Fix issue with sending messages when a new session is created ## [0.13.2] - 2024-03-23 diff --git a/build.gradle.kts b/build.gradle.kts index f7a3737b..794a0972 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.1" } -version = "0.13.3-SNAPSHOT" +version = "0.13.3" java { sourceCompatibility = JavaVersion.VERSION_21 From 7060faf5d39f79883bfe0e740ad5b58d6681dbbc Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 054/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a443171..6f9fad47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.3] - 2024-04-19 **Attention**: Now requires libsignal-client version 0.44.0 diff --git a/build.gradle.kts b/build.gradle.kts index 794a0972..527f8146 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.1" } -version = "0.13.3" +version = "0.13.4-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_21 From 3cd8e323c954393ebe539cddb158c6da92c6cfb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Apr 2024 13:53:36 +0200 Subject: [PATCH 055/279] Bump rustls from 0.21.10 to 0.21.11 in /client (#1511) Bumps [rustls](https://github.com/rustls/rustls) from 0.21.10 to 0.21.11. - [Release notes](https://github.com/rustls/rustls/releases) - [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md) - [Commits](https://github.com/rustls/rustls/compare/v/0.21.10...v/0.21.11) --- updated-dependencies: - dependency-name: rustls dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- client/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/Cargo.lock b/client/Cargo.lock index 11c9659c..1794713a 100644 --- a/client/Cargo.lock +++ b/client/Cargo.lock @@ -776,9 +776,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", "ring", From 3f31f1a8a6bd7db967d448ae7831a89f85daec65 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 21 Apr 2024 09:34:06 +0200 Subject: [PATCH 056/279] Update metainfo --- data/org.asamk.SignalCli.metainfo.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index 0fe85fa9..d5b9a976 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -8,6 +8,12 @@ AsamK org.asamk.SignalCli + + signal + signal-cli + messenger + messaging + https://github.com/AsamK/signal-cli/issues https://github.com/AsamK/signal-cli @@ -39,6 +45,9 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.3 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.2 From 90b1e4bc02ddbb26332d9882542c976ba471fbad Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 1 May 2024 09:04:03 +0200 Subject: [PATCH 057/279] Delete username link when deleting username --- .../java/org/asamk/signal/manager/helper/AccountHelper.java | 2 ++ 1 file changed, 2 insertions(+) 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 299ce4d3..85c9b220 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 @@ -446,6 +446,8 @@ public class AccountHelper { } public void deleteUsername() throws IOException { + dependencies.getAccountManager().deleteUsernameLink(); + account.setUsernameLink(null); dependencies.getAccountManager().deleteUsername(); account.setUsername(null); logger.debug("[deleteUsername] Successfully deleted the username."); From 09e3e7f3358313e68487b1b5ab240837326844dc Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 1 May 2024 09:07:39 +0200 Subject: [PATCH 058/279] Rotate storageId after setting username --- .../java/org/asamk/signal/manager/helper/AccountHelper.java | 3 +++ 1 file changed, 3 insertions(+) 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 85c9b220..5c74a37f 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 @@ -376,6 +376,7 @@ public class AccountHelper { account.setUsername(username.getUsername()); account.setUsernameLink(linkComponents); account.getRecipientStore().resolveSelfRecipientTrusted(account.getSelfRecipientAddress()); + account.getRecipientStore().rotateSelfStorageId(); logger.debug("[confirmUsername] Successfully confirmed username."); } @@ -409,6 +410,7 @@ public class AccountHelper { e.getClass().getSimpleName()); account.setUsername(null); account.setUsernameLink(null); + account.getRecipientStore().rotateSelfStorageId(); throw e; } } else { @@ -431,6 +433,7 @@ public class AccountHelper { account.setUsernameLink(linkComponents); logger.debug("[confirmUsername] Successfully reclaimed existing username and link."); } + account.getRecipientStore().rotateSelfStorageId(); } private void tryToSetUsernameLink(Username username) { From 04726f005cb9a4c02df387c5bbfc464bf9f6b3aa Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 1 May 2024 09:08:00 +0200 Subject: [PATCH 059/279] Save account file after setting username link Fixes #1515 --- .../java/org/asamk/signal/manager/storage/SignalAccount.java | 1 + 1 file changed, 1 insertion(+) 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 ee6f4167..0ca76e9d 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 @@ -1338,6 +1338,7 @@ public class SignalAccount implements Closeable { public void setUsernameLink(final UsernameLinkComponents usernameLink) { this.usernameLink = usernameLink; + save(); } public ServiceEnvironment getServiceEnvironment() { From fb81bf1d05f46c521ca0bd136728ca6024d7ba4a Mon Sep 17 00:00:00 2001 From: Sebastian Scheibner Date: Thu, 9 May 2024 21:20:38 +0200 Subject: [PATCH 060/279] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a4d6cc53..03455d12 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ signal-cli is a commandline interface for the [Signal messenger](https://signal.org/). It supports registering, verifying, sending and receiving messages. signal-cli uses a [patched libsignal-service-java](https://github.com/Turasa/libsignal-service-java), -extracted from the [Signal-Android source code](https://github.com/signalapp/Signal-Android/tree/main/libsignal/service). +extracted from the [Signal-Android source code](https://github.com/signalapp/Signal-Android/tree/main/libsignal-service). For registering you need a phone number where you can receive SMS or incoming calls. signal-cli is primarily intended to be used on servers to notify admins of important events. From 8f756cd90c4579d89a8aa827a613cc78f9d3aee0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 9 May 2024 21:07:17 +0200 Subject: [PATCH 061/279] Update libsignal-service --- graalvm-config-dir/jni-config.json | 5 ++++- .../signal/manager/internal/SignalDependencies.java | 13 ++++++++----- .../internal/SignalWebSocketHealthMonitor.java | 4 ++-- settings.gradle.kts | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index 5fed7404..4050838c 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -12,7 +12,7 @@ }, { "name":"java.lang.Class", - "methods":[{"name":"getCanonicalName","parameterTypes":[] }] + "methods":[{"name":"getCanonicalName","parameterTypes":[] }, {"name":"getClassLoader","parameterTypes":[] }] }, { "name":"java.lang.ClassLoader", @@ -62,6 +62,9 @@ "name":"java.util.UUID", "methods":[{"name":"","parameterTypes":["long","long"] }, {"name":"getLeastSignificantBits","parameterTypes":[] }, {"name":"getMostSignificantBits","parameterTypes":[] }] }, +{ + "name":"jdk.internal.loader.ClassLoaders$AppClassLoader" +}, { "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader" }, diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index b715611e..b495466b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -23,6 +23,8 @@ import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.api.websocket.WebSocketFactory; import org.whispersystems.signalservice.internal.push.PushServiceSocket; +import org.whispersystems.signalservice.internal.websocket.LibSignalNetwork; +import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection; import org.whispersystems.signalservice.internal.websocket.WebSocketConnection; import java.util.List; @@ -49,7 +51,7 @@ public class SignalDependencies { private ClientZkOperations clientZkOperations; private PushServiceSocket pushServiceSocket; - private Network libSignalNetwork; + private LibSignalNetwork libSignalNetwork; private SignalWebSocket signalWebSocket; private SignalServiceMessageReceiver messageReceiver; private SignalServiceMessageSender messageSender; @@ -106,9 +108,10 @@ public class SignalDependencies { ServiceConfig.AUTOMATIC_NETWORK_RETRY)); } - public Network getLibSignalNetwork() { + public LibSignalNetwork getLibSignalNetwork() { return getOrCreate(() -> libSignalNetwork, - () -> libSignalNetwork = new Network(serviceEnvironmentConfig.netEnvironment())); + () -> libSignalNetwork = new LibSignalNetwork(new Network(serviceEnvironmentConfig.netEnvironment(), + userAgent), serviceEnvironmentConfig.signalServiceConfiguration())); } public SignalServiceAccountManager getAccountManager() { @@ -159,7 +162,7 @@ public class SignalDependencies { final var webSocketFactory = new WebSocketFactory() { @Override public WebSocketConnection createWebSocket() { - return new WebSocketConnection("normal", + return new OkHttpWebSocketConnection("normal", serviceEnvironmentConfig.signalServiceConfiguration(), Optional.of(credentialsProvider), userAgent, @@ -169,7 +172,7 @@ public class SignalDependencies { @Override public WebSocketConnection createUnidentifiedWebSocket() { - return new WebSocketConnection("unidentified", + return new OkHttpWebSocketConnection("unidentified", serviceEnvironmentConfig.signalServiceConfiguration(), Optional.empty(), userAgent, diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java index 3ebbe4f3..0fb1585a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java @@ -7,7 +7,7 @@ import org.whispersystems.signalservice.api.util.Preconditions; import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.websocket.HealthMonitor; import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState; -import org.whispersystems.signalservice.internal.websocket.WebSocketConnection; +import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection; import java.util.Arrays; import java.util.concurrent.TimeUnit; @@ -25,7 +25,7 @@ final class SignalWebSocketHealthMonitor implements HealthMonitor { private static final Logger logger = LoggerFactory.getLogger(SignalWebSocketHealthMonitor.class); - private static final long KEEP_ALIVE_SEND_CADENCE = TimeUnit.SECONDS.toMillis(WebSocketConnection.KEEPALIVE_FREQUENCY_SECONDS); + private static final long KEEP_ALIVE_SEND_CADENCE = TimeUnit.SECONDS.toMillis(OkHttpWebSocketConnection.KEEPALIVE_FREQUENCY_SECONDS); private static final long MAX_TIME_SINCE_SUCCESSFUL_KEEP_ALIVE = KEEP_ALIVE_SEND_CADENCE * 3; private SignalWebSocket signalWebSocket; diff --git a/settings.gradle.kts b/settings.gradle.kts index cf324502..7078f528 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") library("logback", "ch.qos.logback", "logback-classic").version("1.5.6") - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_101") + library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_102") library("sqlite", "org.xerial", "sqlite-jdbc").version("3.45.3.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") From 53d7e0f08b3322eeedba328236e3df8ccac54d48 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 17 May 2024 18:02:05 +0200 Subject: [PATCH 062/279] Handle all possible identifiers of a RecipientAddress Fixes #1516 --- .../signal/manager/api/RecipientIdentifier.java | 15 +++++++++++++++ .../signal/manager/helper/RecipientHelper.java | 2 ++ .../signal/manager/internal/ManagerImpl.java | 4 ++++ 3 files changed, 21 insertions(+) diff --git a/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java b/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java index 823881c7..794672a4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/RecipientIdentifier.java @@ -49,6 +49,8 @@ public sealed interface RecipientIdentifier { return new Number(address.number().get()); } else if (address.aci().isPresent()) { return new Uuid(UUID.fromString(address.aci().get())); + } else if (address.pni().isPresent()) { + return new Pni(address.pni().get()); } else if (address.username().isPresent()) { return new Username(address.username().get()); } @@ -71,6 +73,19 @@ public sealed interface RecipientIdentifier { } } + record Pni(String pni) implements Single { + + @Override + public String getIdentifier() { + return pni; + } + + @Override + public RecipientAddress toPartialRecipientAddress() { + return new RecipientAddress(null, pni, null, null); + } + } + record Number(String number) implements Single { @Override 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 092cff6f..bdd192a6 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 @@ -77,6 +77,8 @@ public class RecipientHelper { public RecipientId resolveRecipient(final RecipientIdentifier.Single recipient) throws UnregisteredRecipientException { if (recipient instanceof RecipientIdentifier.Uuid uuidRecipient) { return account.getRecipientResolver().resolveRecipient(ACI.from(uuidRecipient.uuid())); + } else if (recipient instanceof RecipientIdentifier.Pni pniRecipient) { + return account.getRecipientResolver().resolveRecipient(PNI.parseOrThrow(pniRecipient.pni())); } else if (recipient instanceof RecipientIdentifier.Number numberRecipient) { final var number = numberRecipient.number(); return account.getRecipientStore().resolveRecipientByNumber(number, () -> { 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 ef2e52e3..42bce3c6 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 @@ -98,6 +98,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage; 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.ServiceIdType; import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException; import org.whispersystems.signalservice.api.util.DeviceNameUtil; @@ -836,6 +837,9 @@ public class ManagerImpl implements Manager { if (recipient instanceof RecipientIdentifier.Uuid u) { account.getMessageSendLogStore() .deleteEntryForRecipientNonGroup(targetSentTimestamp, ACI.from(u.uuid())); + } else if (recipient instanceof RecipientIdentifier.Pni pni) { + account.getMessageSendLogStore() + .deleteEntryForRecipientNonGroup(targetSentTimestamp, PNI.parseOrThrow(pni.pni())); } else if (recipient instanceof RecipientIdentifier.Single r) { try { final var recipientId = context.getRecipientHelper().resolveRecipient(r); From fb21a42cce033e4e2f99d3f63410c5ef63a74fef Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 18 May 2024 22:13:29 +0200 Subject: [PATCH 063/279] Update graalvm build tools --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 527f8146..eae31b12 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { application eclipse `check-lib-versions` - id("org.graalvm.buildtools.native") version "0.10.1" + id("org.graalvm.buildtools.native") version "0.10.2" } version = "0.13.4-SNAPSHOT" From 6baf0eac13645fa67fe1707fef9a0e2b295332a3 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 21 May 2024 20:28:16 +0200 Subject: [PATCH 064/279] Fix type parsing in JSON RPC mode Fixes #1533 --- .../signal/commands/SendMessageRequestResponseCommand.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java b/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java index 146ebaf2..53cd3ea7 100644 --- a/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java @@ -36,7 +36,10 @@ public class SendMessageRequestResponseCommand implements JsonRpcLocalCommand { final var recipientStrings = ns.getList("recipient"); final var groupIdStrings = ns.getList("group-id"); final var usernameStrings = ns.getList("username"); - final var type = ns.get("type"); + final var typeObj = ns.get("type"); + final var type = typeObj instanceof MessageRequestResponseType t + ? t + : MessageRequestResponseType.valueOf(((String) typeObj).toUpperCase()); final var recipientIdentifiers = CommandUtil.getRecipientIdentifiers(m, false, From 04cf54263eaf9a70ec4070f3ea1cbb40aa486994 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 23 May 2024 12:46:15 +0200 Subject: [PATCH 065/279] Fix getUserStatus command with only username parameter Related #1535 --- .../java/org/asamk/signal/commands/GetUserStatusCommand.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java index 1691fac8..09adabd4 100644 --- a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java +++ b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java @@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; public class GetUserStatusCommand implements JsonRpcLocalCommand { @@ -47,7 +48,8 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { // Get a map of registration statuses Map registered; try { - registered = m.getUserStatus(new HashSet<>(ns.getList("recipient"))); + final var recipient = ns.getList("recipient"); + registered = m.getUserStatus(recipient == null ? Set.of() : new HashSet<>(recipient)); } catch (RateLimitException e) { final var message = CommandUtil.getRateLimitMessage(e); throw new RateLimitErrorException(message, e); From 9781c56571b063e90a50d66e2fcb5c7b3e11f08d Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 24 May 2024 16:09:07 +0200 Subject: [PATCH 066/279] Improve username update error message Fixes #1535 --- .../java/org/asamk/signal/manager/internal/ManagerImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 42bce3c6..c21a150e 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 @@ -101,6 +101,8 @@ import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException; +import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException; +import org.whispersystems.signalservice.api.push.exceptions.UsernameTakenException; import org.whispersystems.signalservice.api.util.DeviceNameUtil; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; @@ -396,6 +398,10 @@ public class ManagerImpl implements Manager { } else { context.getAccountHelper().reserveUsernameFromNickname(username); } + } catch (UsernameMalformedException e) { + throw new InvalidUsernameException("Username is malformed", e); + } catch (UsernameTakenException e) { + throw new InvalidUsernameException("Username is already registered", e); } catch (BaseUsernameException e) { throw new InvalidUsernameException(e.getMessage() + " (" + e.getClass().getSimpleName() + ")", e); } From 777cfbb69f7279add5aabe174fda4695b5f37bd1 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 2 Jun 2024 13:25:09 +0200 Subject: [PATCH 067/279] Update gradle wrapper --- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23a..a4413138 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..b740cf13 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. From 6d764db11418d6e3ea090a7c0dafb5ab3cfd6962 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 2 Jun 2024 13:26:17 +0200 Subject: [PATCH 068/279] Update dependencies --- settings.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 7078f528..3210a55d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,7 +7,7 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { library("bouncycastle", "org.bouncycastle", "bcprov-jdk18on").version("1.78.1") - library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.17.0") + library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.17.1") library("argparse4j", "net.sourceforge.argparse4j", "argparse4j").version("0.9.0") library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("5.0.0") version("slf4j", "2.0.13") @@ -16,7 +16,7 @@ dependencyResolutionManagement { library("logback", "ch.qos.logback", "logback-classic").version("1.5.6") library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_102") - library("sqlite", "org.xerial", "sqlite-jdbc").version("3.45.3.0") + library("sqlite", "org.xerial", "sqlite-jdbc").version("3.46.0.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") library("junit.launcher", "org.junit.platform", "junit-platform-launcher").version("1.10.2") From a2b002ac5ea60fff33d8445c1a83ab1ee1b62747 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 6 Jun 2024 09:12:40 +0200 Subject: [PATCH 069/279] Add java 22 to CI --- .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 1c6bb3c0..df7a6c79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '21' ] + java: [ '21', '22' ] steps: - uses: actions/checkout@v4 From 10b9c264fde52bee3d7b61578683e6e547d7cfc7 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 6 Jun 2024 09:37:01 +0200 Subject: [PATCH 070/279] Update libsignal-service Fixes #1530 --- graalvm-config-dir/reflect-config.json | 3 +++ .../org/asamk/signal/manager/helper/GroupHelper.java | 11 +++++------ .../signal/manager/internal/SignalDependencies.java | 8 +++----- settings.gradle.kts | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index dec3312e..e17f4f64 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -203,6 +203,9 @@ "name":"java.lang.Class", "methods":[{"name":"getPermittedSubclasses","parameterTypes":[] }, {"name":"getRecordComponents","parameterTypes":[] }, {"name":"isRecord","parameterTypes":[] }, {"name":"isSealed","parameterTypes":[] }] }, +{ + "name":"java.lang.ClassValue" +}, { "name":"java.lang.Comparable", "allDeclaredMethods":true 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 8f7b2869..c0ca3871 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 @@ -38,7 +38,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupHistoryEntry; +import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog; import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; @@ -482,11 +482,10 @@ public class GroupHelper { final var newProfileKeys = new HashMap(); while (true) { final var page = context.getGroupV2Helper().getDecryptedGroupHistoryPage(groupSecretParams, fromRevision); - page.getResults() + page.getChangeLogs() .stream() - .map(DecryptedGroupHistoryEntry::getChange) - .filter(Optional::isPresent) - .map(Optional::get) + .map(DecryptedGroupChangeLog::getChange) + .filter(Objects::nonNull) .map(context.getGroupV2Helper()::getAuthoritativeProfileKeyFromChange) .filter(Objects::nonNull) .forEach(p -> { @@ -495,7 +494,7 @@ public class GroupHelper { final var recipientId = account.getRecipientResolver().resolveRecipient(serviceId); newProfileKeys.put(recipientId, profileKey); }); - if (!page.getPagingData().hasMorePages()) { + if (!page.getPagingData().getHasMorePages()) { break; } fromRevision = page.getPagingData().getNextPageRevision(); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index b495466b..a143fceb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -23,7 +23,6 @@ import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; import org.whispersystems.signalservice.api.websocket.WebSocketFactory; import org.whispersystems.signalservice.internal.push.PushServiceSocket; -import org.whispersystems.signalservice.internal.websocket.LibSignalNetwork; import org.whispersystems.signalservice.internal.websocket.OkHttpWebSocketConnection; import org.whispersystems.signalservice.internal.websocket.WebSocketConnection; @@ -51,7 +50,7 @@ public class SignalDependencies { private ClientZkOperations clientZkOperations; private PushServiceSocket pushServiceSocket; - private LibSignalNetwork libSignalNetwork; + private Network libSignalNetwork; private SignalWebSocket signalWebSocket; private SignalServiceMessageReceiver messageReceiver; private SignalServiceMessageSender messageSender; @@ -108,10 +107,9 @@ public class SignalDependencies { ServiceConfig.AUTOMATIC_NETWORK_RETRY)); } - public LibSignalNetwork getLibSignalNetwork() { + public Network getLibSignalNetwork() { return getOrCreate(() -> libSignalNetwork, - () -> libSignalNetwork = new LibSignalNetwork(new Network(serviceEnvironmentConfig.netEnvironment(), - userAgent), serviceEnvironmentConfig.signalServiceConfiguration())); + () -> libSignalNetwork = new Network(serviceEnvironmentConfig.netEnvironment(), userAgent)); } public SignalServiceAccountManager getAccountManager() { diff --git a/settings.gradle.kts b/settings.gradle.kts index 3210a55d..dd0e6244 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") library("logback", "ch.qos.logback", "logback-classic").version("1.5.6") - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_102") + library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_103") library("sqlite", "org.xerial", "sqlite-jdbc").version("3.46.0.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") From 17596795c2e4cd9c1da790ca52bdd73810729c1c Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 6 Jun 2024 09:37:53 +0200 Subject: [PATCH 071/279] Only store profile keys for group history if none is known yet --- .../java/org/asamk/signal/manager/helper/GroupHelper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 c0ca3871..0c15ceff 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 @@ -500,7 +500,10 @@ public class GroupHelper { fromRevision = page.getPagingData().getNextPageRevision(); } - newProfileKeys.forEach(account.getProfileStore()::storeProfileKey); + newProfileKeys.entrySet() + .stream() + .filter(entry -> account.getProfileStore().getProfileKey(entry.getKey()) == null) + .forEach(entry -> account.getProfileStore().storeProfileKey(entry.getKey(), entry.getValue())); } private GroupInfo getGroupForUpdating(GroupId groupId) throws GroupNotFoundException, NotAGroupMemberException { From 5a97b9e134dfa4acfc9c5d27a1b3c80fbbe6c4a3 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 6 Jun 2024 10:03:27 +0200 Subject: [PATCH 072/279] Update groups when using listGroups command Fixes #1517 --- .../signal/manager/helper/GroupHelper.java | 68 ++++++++++++------- .../signal/manager/helper/SendHelper.java | 2 +- .../signal/manager/internal/ManagerImpl.java | 2 +- 3 files changed, 45 insertions(+), 27 deletions(-) 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 0c15ceff..7cc9fef8 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 @@ -79,6 +79,12 @@ public class GroupHelper { return getGroup(groupId, false); } + public List getGroups() { + final var groups = account.getGroupStore().getGroups(); + groups.forEach(group -> fillOrUpdateGroup(group, false)); + return groups; + } + public boolean isGroupBlocked(final GroupId groupId) { var group = getGroup(groupId); return group != null && group.isBlocked(); @@ -382,34 +388,46 @@ public class GroupHelper { private GroupInfo getGroup(GroupId groupId, boolean forceUpdate) { final var group = account.getGroupStore().getGroup(groupId); - if (group instanceof GroupInfoV2 groupInfoV2) { - if (forceUpdate || (!groupInfoV2.isPermissionDenied() && groupInfoV2.getGroup() == null)) { - final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); - DecryptedGroup decryptedGroup; - try { - decryptedGroup = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams); - } catch (NotAGroupMemberException e) { - groupInfoV2.setPermissionDenied(true); - decryptedGroup = null; - } - if (decryptedGroup != null) { - try { - storeProfileKeysFromHistory(groupSecretParams, groupInfoV2, decryptedGroup); - } catch (NotAGroupMemberException ignored) { - } - storeProfileKeysFromMembers(decryptedGroup); - final var avatar = decryptedGroup.avatar; - if (!avatar.isEmpty()) { - downloadGroupAvatar(groupInfoV2.getGroupId(), groupSecretParams, avatar); - } - } - groupInfoV2.setGroup(decryptedGroup); - account.getGroupStore().updateGroup(group); - } - } + fillOrUpdateGroup(group, forceUpdate); return group; } + private void fillOrUpdateGroup(final GroupInfo group, final boolean forceUpdate) { + if (!(group instanceof GroupInfoV2 groupInfoV2)) { + return; + } + + if (!forceUpdate && (groupInfoV2.isPermissionDenied() || groupInfoV2.getGroup() != null)) { + return; + } + + final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); + DecryptedGroup decryptedGroup; + try { + decryptedGroup = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams); + } catch (NotAGroupMemberException e) { + groupInfoV2.setPermissionDenied(true); + account.getGroupStore().updateGroup(group); + return; + } + + if (decryptedGroup == null) { + return; + } + + try { + storeProfileKeysFromHistory(groupSecretParams, groupInfoV2, decryptedGroup); + } catch (NotAGroupMemberException ignored) { + } + storeProfileKeysFromMembers(decryptedGroup); + final var avatar = decryptedGroup.avatar; + if (!avatar.isEmpty()) { + downloadGroupAvatar(groupInfoV2.getGroupId(), groupSecretParams, avatar); + } + groupInfoV2.setGroup(decryptedGroup); + account.getGroupStore().updateGroup(group); + } + private void downloadGroupAvatar(GroupIdV2 groupId, GroupSecretParams groupSecretParams, String cdnKey) { try { context.getAvatarStore() 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 a066c851..0ff7fd89 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 @@ -261,7 +261,7 @@ public class SendHelper { } final var groupId = messageSendLogEntry.groupId().get(); - final var group = account.getGroupStore().getGroup(groupId); + final var group = context.getGroupHelper().getGroup(groupId); if (group == null) { logger.debug("Could not find a matching group for the groupId {}! Skipping message send.", 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 c21a150e..13785a94 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 @@ -511,7 +511,7 @@ public class ManagerImpl implements Manager { @Override public List getGroups() { - return account.getGroupStore().getGroups().stream().map(this::toGroup).toList(); + return context.getGroupHelper().getGroups().stream().map(this::toGroup).toList(); } private Group toGroup(final GroupInfo groupInfo) { From fd402b52c8f3f2fd40006911191267d05971da3e Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 6 Jun 2024 10:16:40 +0200 Subject: [PATCH 073/279] Update doc --- man/signal-cli.1.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 7335eba6..2b0f3dac 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -270,7 +270,7 @@ Uses a list of phone numbers or usernames to determine the statuses of those use Shows if they are registered on the Signal Servers or not. In json mode this is outputted as a list of objects. -[NUMBER [NUMBER ...]]:: +[RECIPIENT [RECIPIENT ...]]:: One or more numbers to check. [--username [USERNAME ...]]:: From 67012b40b1e17bbd2cb990b9dfed7e781902b770 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 6 Jun 2024 10:16:53 +0200 Subject: [PATCH 074/279] Update user agent --- src/main/java/org/asamk/signal/BaseConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 060a2664..e234ed2f 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.5.0"); + .orElse("Signal-Android/7.9.0"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From 8b355918e83c016bd11868974bcc9bdc17ae88b9 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 6 Jun 2024 10:39:26 +0200 Subject: [PATCH 075/279] Use jvm running gradle if it's compatible with targetCompatibility --- build.gradle.kts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index eae31b12..0cc6d6de 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,8 +12,10 @@ java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) + if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) { + toolchain { + languageVersion.set(JavaLanguageVersion.of(targetCompatibility.majorVersion)) + } } } From c716f946495e2236d03c3a7621e882c4f8eddacb Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 6 Jun 2024 10:47:42 +0200 Subject: [PATCH 076/279] Bump version to 0.13.4 --- CHANGELOG.md | 18 +++++++++++++++++- build.gradle.kts | 2 +- data/org.asamk.SignalCli.metainfo.xml | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f9fad47..ce7a38c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,32 @@ # Changelog -## [Unreleased] +## [0.13.4] - 2024-06-06 + +**Attention**: Now requires libsignal-client version 0.47.0 + +### Improved + +- Improve username update error message +- Update groups when using listGroups command + +### Fixed + +- Update libsignal to fix graalvm native startup +- Fix issue with saving username link +- Fix sendMessageRequestResponse type parameter parsing in JSON RPC mode +- Fix getUserStatus command with only username parameter ## [0.13.3] - 2024-04-19 **Attention**: Now requires libsignal-client version 0.44.0 ### Added + - Support for reading contact nickname and notes - Add `--internal` and `--detailed` parameters to `listContacts` command ### Fixed + - Fix issue with sending messages when a new session is created ## [0.13.2] - 2024-03-23 diff --git a/build.gradle.kts b/build.gradle.kts index 0cc6d6de..ec5da78e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.2" } -version = "0.13.4-SNAPSHOT" +version = "0.13.4" java { sourceCompatibility = JavaVersion.VERSION_21 diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index d5b9a976..de1a00bd 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -45,6 +45,9 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.3 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.3 From baf7b74a610250c280793252c8e41a6259bda593 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 077/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce7a38c3..987af127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.4] - 2024-06-06 **Attention**: Now requires libsignal-client version 0.47.0 diff --git a/build.gradle.kts b/build.gradle.kts index ec5da78e..fc1dd1b0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.2" } -version = "0.13.4" +version = "0.13.5-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_21 From 5ff66728e32237a23fdffda4515031d24dad7cf0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 26 Jun 2024 15:35:54 +0200 Subject: [PATCH 078/279] Update libsignal-service --- graalvm-config-dir/reflect-config.json | 2 +- .../asamk/signal/manager/config/ServiceConfig.java | 11 ++++++++++- .../org/asamk/signal/manager/helper/PinHelper.java | 2 +- .../asamk/signal/manager/internal/ManagerImpl.java | 11 +++++++++-- .../signal/manager/internal/SignalDependencies.java | 3 +-- .../asamk/signal/manager/util/AttachmentUtils.java | 4 +++- settings.gradle.kts | 2 +- 7 files changed, 26 insertions(+), 9 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index e17f4f64..64efa057 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -2142,7 +2142,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "methods":[{"name":"getAnnouncementGroup","parameterTypes":[] }, {"name":"getChangeNumber","parameterTypes":[] }, {"name":"getGiftBadges","parameterTypes":[] }, {"name":"getPaymentActivation","parameterTypes":[] }, {"name":"getPni","parameterTypes":[] }, {"name":"getSenderKey","parameterTypes":[] }, {"name":"getStorage","parameterTypes":[] }, {"name":"getStories","parameterTypes":[] }] + "methods":[{"name":"getAnnouncementGroup","parameterTypes":[] }, {"name":"getChangeNumber","parameterTypes":[] }, {"name":"getDeleteSync","parameterTypes":[] }, {"name":"getGiftBadges","parameterTypes":[] }, {"name":"getPaymentActivation","parameterTypes":[] }, {"name":"getPni","parameterTypes":[] }, {"name":"getSenderKey","parameterTypes":[] }, {"name":"getStorage","parameterTypes":[] }, {"name":"getStories","parameterTypes":[] }] }, { "name":"org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest", 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 ea47b8a6..e827e3f5 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 @@ -30,7 +30,16 @@ public class ServiceConfig { final var giftBadges = !isPrimaryDevice; final var pni = !isPrimaryDevice; final var paymentActivation = !isPrimaryDevice; - return new AccountAttributes.Capabilities(true, true, true, true, true, giftBadges, pni, paymentActivation); + final var deleteSync = !isPrimaryDevice; + return new AccountAttributes.Capabilities(true, + true, + true, + true, + true, + giftBadges, + pni, + paymentActivation, + deleteSync); } public static ServiceEnvironmentConfig getServiceEnvironmentConfig( 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 0bcfe09e..cb9b9057 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 @@ -105,7 +105,7 @@ public class PinHelper { public SecureValueRecovery.RestoreResponse.Success getRegistrationLockData( SecureValueRecovery secureValueRecovery, AuthCredentials authCredentials, String pin ) throws IOException, IncorrectPinException { - final var restoreResponse = secureValueRecovery.restoreDataPreRegistration(authCredentials, pin); + final var restoreResponse = secureValueRecovery.restoreDataPreRegistration(authCredentials, null, pin); switch (restoreResponse) { case SecureValueRecovery.RestoreResponse.Success s -> { 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 13785a94..426429da 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 @@ -92,6 +92,7 @@ import org.signal.libsignal.usernames.BaseUsernameException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.SignalSessionLock; +import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServicePreview; import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; @@ -738,17 +739,23 @@ public class ManagerImpl implements Manager { private void applyMessage( final SignalServiceDataMessage.Builder messageBuilder, final Message message ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException { + final var additionalAttachments = new ArrayList(); if (message.messageText().length() > 2000) { final var messageBytes = message.messageText().getBytes(StandardCharsets.UTF_8); final var textAttachment = AttachmentUtils.createAttachmentStream(new StreamDetails(new ByteArrayInputStream( messageBytes), MimeUtils.LONG_TEXT, messageBytes.length), Optional.empty()); messageBuilder.withBody(message.messageText().substring(0, 2000)); - messageBuilder.withAttachment(context.getAttachmentHelper().uploadAttachment(textAttachment)); + additionalAttachments.add(context.getAttachmentHelper().uploadAttachment(textAttachment)); } else { messageBuilder.withBody(message.messageText()); } if (!message.attachments().isEmpty()) { - messageBuilder.withAttachments(context.getAttachmentHelper().uploadAttachments(message.attachments())); + if (!additionalAttachments.isEmpty()) { + additionalAttachments.addAll(context.getAttachmentHelper().uploadAttachments(message.attachments())); + messageBuilder.withAttachments(additionalAttachments); + } else { + messageBuilder.withAttachments(context.getAttachmentHelper().uploadAttachments(message.attachments())); + } } if (!message.mentions().isEmpty()) { messageBuilder.withMentions(resolveMentions(message.mentions())); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index a143fceb..feb61d1c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -197,8 +197,7 @@ public class SignalDependencies { Optional.empty(), executor, ServiceConfig.MAX_ENVELOPE_SIZE, - pushServiceSocket, - false)); + pushServiceSocket)); } public List getSecureValueRecoveryV2() { diff --git a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java index 5126b691..d06d8745 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.UUID; public class AttachmentUtils { @@ -58,6 +59,7 @@ public class AttachmentUtils { blurHash, null, null, - resumableUploadSpec); + resumableUploadSpec, + UUID.randomUUID()); } } diff --git a/settings.gradle.kts b/settings.gradle.kts index dd0e6244..d185c417 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") library("logback", "ch.qos.logback", "logback-classic").version("1.5.6") - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_103") + library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_104") library("sqlite", "org.xerial", "sqlite-jdbc").version("3.46.0.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") From e51b1ee23a725ce8cfc25f0a277ab6065a532081 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 25 Jul 2024 16:24:41 +0200 Subject: [PATCH 079/279] Update libsignal-service --- graalvm-config-dir/jni-config.json | 6 + .../manager/api/ProofRequiredException.java | 1 - .../signal/manager/config/ServiceConfig.java | 13 +- .../signal/manager/helper/GroupHelper.java | 131 +++++++++++++----- .../signal/manager/helper/GroupV2Helper.java | 80 ++++++----- .../signal/manager/helper/ProfileHelper.java | 15 +- .../signal/manager/helper/SendHelper.java | 34 +++-- .../helper/UnidentifiedAccessHelper.java | 54 +++----- settings.gradle.kts | 2 +- .../java/org/asamk/signal/BaseConfig.java | 2 +- 10 files changed, 201 insertions(+), 137 deletions(-) diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index 4050838c..381dca9e 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -1,7 +1,13 @@ [ +{ + "name":"[B" +}, { "name":"[Z" }, +{ + "name":"[[B" +}, { "name":"com.sun.security.auth.module.UnixSystem", "fields":[{"name":"gid"}, {"name":"groups"}, {"name":"uid"}, {"name":"username"}] diff --git a/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java b/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java index 277406cb..21b4034d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/ProofRequiredException.java @@ -36,7 +36,6 @@ public class ProofRequiredException extends Exception { static Option from(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException.Option option) { return switch (option) { - case RECAPTCHA -> CAPTCHA; case CAPTCHA -> CAPTCHA; case PUSH_CHALLENGE -> PUSH_CHALLENGE; }; 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 e827e3f5..62aedaf6 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 @@ -27,19 +27,8 @@ public class ServiceConfig { public static final long UNREGISTERED_LIFESPAN = TimeUnit.DAYS.toMillis(30); public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) { - final var giftBadges = !isPrimaryDevice; - final var pni = !isPrimaryDevice; - final var paymentActivation = !isPrimaryDevice; final var deleteSync = !isPrimaryDevice; - return new AccountAttributes.Capabilities(true, - true, - true, - true, - true, - giftBadges, - pni, - paymentActivation, - deleteSync); + return new AccountAttributes.Capabilities(true, deleteSync); } public static ServiceEnvironmentConfig getServiceEnvironmentConfig( 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 7cc9fef8..62291a3a 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 @@ -33,13 +33,16 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey; import org.signal.libsignal.zkgroup.groups.GroupSecretParams; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.storageservice.protos.groups.GroupChange; +import org.signal.storageservice.protos.groups.GroupChangeResponse; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog; +import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse; import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException; +import org.whispersystems.signalservice.api.groupsv2.ReceivedGroupSendEndorsements; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; @@ -133,9 +136,10 @@ public class GroupHelper { } if (group == null) { try { - group = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams); + final var response = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams); - if (group != null) { + if (response != null) { + group = handleDecryptedGroupResponse(groupInfoV2, response); storeProfileKeysFromHistory(groupSecretParams, groupInfoV2, group); } } catch (NotAGroupMemberException ignored) { @@ -156,6 +160,35 @@ public class GroupHelper { return groupInfoV2; } + private DecryptedGroup handleDecryptedGroupResponse( + GroupInfoV2 groupInfoV2, final DecryptedGroupResponse decryptedGroupResponse + ) { + final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); + ReceivedGroupSendEndorsements groupSendEndorsements = dependencies.getGroupsV2Operations() + .forGroup(groupSecretParams) + .receiveGroupSendEndorsements(account.getAci(), + decryptedGroupResponse.getGroup(), + decryptedGroupResponse.getGroupSendEndorsementsResponse()); + + // TODO save group endorsements + + return decryptedGroupResponse.getGroup(); + } + + private GroupChange handleGroupChangeResponse( + final GroupInfoV2 groupInfoV2, final GroupChangeResponse groupChangeResponse + ) { + ReceivedGroupSendEndorsements groupSendEndorsements = dependencies.getGroupsV2Operations() + .forGroup(GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey())) + .receiveGroupSendEndorsements(account.getAci(), + groupInfoV2.getGroup(), + groupChangeResponse.groupSendEndorsementsResponse); + + // TODO save group endorsements + + return groupChangeResponse.groupChange; + } + public Pair createGroup( String name, Set members, String avatarFile ) throws IOException, AttachmentInvalidException { @@ -181,7 +214,7 @@ public class GroupHelper { final var gv2 = gv2Pair.first(); final var decryptedGroup = gv2Pair.second(); - gv2.setGroup(decryptedGroup); + gv2.setGroup(handleDecryptedGroupResponse(gv2, decryptedGroup)); gv2.setProfileSharingEnabled(true); if (avatarBytes != null) { context.getAvatarStore() @@ -277,7 +310,7 @@ public class GroupHelper { var group = getGroupForUpdating(groupId); if (group instanceof GroupInfoV2 groupInfoV2) { - Pair groupChangePair; + Pair groupChangePair; try { groupChangePair = context.getGroupV2Helper().updateSelfProfileKey(groupInfoV2); } catch (ConflictException e) { @@ -286,7 +319,9 @@ public class GroupHelper { groupChangePair = context.getGroupV2Helper().updateSelfProfileKey(groupInfoV2); } if (groupChangePair != null) { - sendUpdateGroupV2Message(groupInfoV2, groupChangePair.first(), groupChangePair.second()); + sendUpdateGroupV2Message(groupInfoV2, + groupChangePair.first(), + handleGroupChangeResponse(groupInfoV2, groupChangePair.second())); } } } @@ -304,11 +339,12 @@ public class GroupHelper { if (groupJoinInfo.pendingAdminApproval) { throw new PendingAdminApprovalException("You have already requested to join the group."); } - final var groupChange = context.getGroupV2Helper() + final var changeResponse = context.getGroupV2Helper() .joinGroup(inviteLinkUrl.getGroupMasterKey(), inviteLinkUrl.getPassword(), groupJoinInfo); final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(), groupJoinInfo.revision + 1, - groupChange.encode()); + changeResponse.groupChange == null ? null : changeResponse.groupChange.encode()); + final var groupChange = handleGroupChangeResponse(group, changeResponse); if (group.getGroup() == null) { // Only requested member, can't send update to group members @@ -404,17 +440,17 @@ public class GroupHelper { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); DecryptedGroup decryptedGroup; try { - decryptedGroup = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams); + final var response = context.getGroupV2Helper().getDecryptedGroup(groupSecretParams); + if (response == null) { + return; + } + decryptedGroup = handleDecryptedGroupResponse(groupInfoV2, response); } catch (NotAGroupMemberException e) { groupInfoV2.setPermissionDenied(true); account.getGroupStore().updateGroup(group); return; } - if (decryptedGroup == null) { - return; - } - try { storeProfileKeysFromHistory(groupSecretParams, groupInfoV2, decryptedGroup); } catch (NotAGroupMemberException ignored) { @@ -496,10 +532,12 @@ public class GroupHelper { ) throws NotAGroupMemberException { final var revisionWeWereAdded = context.getGroupV2Helper().findRevisionWeWereAdded(newDecryptedGroup); final var localRevision = localGroup.getGroup() == null ? 0 : localGroup.getGroup().revision; + final var sendEndorsementsExpirationMs = 0L;// TODO store expiration localGroup.getGroup() == null ? 0 : localGroup.getGroup().revision; var fromRevision = Math.max(revisionWeWereAdded, localRevision); final var newProfileKeys = new HashMap(); while (true) { - final var page = context.getGroupV2Helper().getDecryptedGroupHistoryPage(groupSecretParams, fromRevision); + final var page = context.getGroupV2Helper() + .getDecryptedGroupHistoryPage(groupSecretParams, fromRevision, sendEndorsementsExpirationMs); page.getChangeLogs() .stream() .map(DecryptedGroupChangeLog::getChange) @@ -606,7 +644,9 @@ public class GroupHelper { final var groupV2Helper = context.getGroupV2Helper(); if (group.isPendingMember(account.getSelfRecipientId())) { var groupGroupChangePair = groupV2Helper.acceptInvite(group); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } if (members != null) { @@ -614,14 +654,18 @@ public class GroupHelper { requestingMembers.retainAll(group.getRequestingMembers()); if (!requestingMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.approveJoinRequestMembers(group, requestingMembers); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } final var newMembers = new HashSet<>(members); newMembers.removeAll(group.getMembers()); newMembers.removeAll(group.getRequestingMembers()); if (!newMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.addMembers(group, newMembers); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } } @@ -637,20 +681,26 @@ public class GroupHelper { existingRemoveMembers.remove(account.getSelfRecipientId());// self can be removed with sendQuitGroupMessage if (!existingRemoveMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.removeMembers(group, existingRemoveMembers); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } var pendingRemoveMembers = new HashSet<>(removeMembers); pendingRemoveMembers.retainAll(group.getPendingMembers()); if (!pendingRemoveMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.revokeInvitedMembers(group, pendingRemoveMembers); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } var requestingRemoveMembers = new HashSet<>(removeMembers); requestingRemoveMembers.retainAll(group.getRequestingMembers()); if (!requestingRemoveMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.refuseJoinRequestMembers(group, requestingRemoveMembers); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } } @@ -663,7 +713,7 @@ public class GroupHelper { var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true); result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), - groupGroupChangePair.second()); + handleGroupChangeResponse(group, groupGroupChangePair.second())); } } } @@ -676,7 +726,7 @@ public class GroupHelper { var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false); result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), - groupGroupChangePair.second()); + handleGroupChangeResponse(group, groupGroupChangePair.second())); } } } @@ -686,7 +736,9 @@ public class GroupHelper { newlyBannedMembers.removeAll(group.getBannedMembers()); if (!newlyBannedMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.banMembers(group, newlyBannedMembers); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } } @@ -695,38 +747,52 @@ public class GroupHelper { existingUnbanMembers.retainAll(group.getBannedMembers()); if (!existingUnbanMembers.isEmpty()) { var groupGroupChangePair = groupV2Helper.unbanMembers(group, existingUnbanMembers); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } } if (resetGroupLink) { var groupGroupChangePair = groupV2Helper.resetGroupLinkPassword(group); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } if (groupLinkState != null) { var groupGroupChangePair = groupV2Helper.setGroupLinkState(group, groupLinkState); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } if (addMemberPermission != null) { var groupGroupChangePair = groupV2Helper.setAddMemberPermission(group, addMemberPermission); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } if (editDetailsPermission != null) { var groupGroupChangePair = groupV2Helper.setEditDetailsPermission(group, editDetailsPermission); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } if (expirationTimer != null) { var groupGroupChangePair = groupV2Helper.setMessageExpirationTimer(group, expirationTimer); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } if (isAnnouncementGroup != null) { var groupGroupChangePair = groupV2Helper.setIsAnnouncementGroup(group, isAnnouncementGroup); - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } if (name != null || description != null || avatarFile != null) { @@ -735,7 +801,9 @@ public class GroupHelper { context.getAvatarStore() .storeGroupAvatar(group.getGroupId(), outputStream -> outputStream.write(avatarFile)); } - result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second()); + result = sendUpdateGroupV2Message(group, + groupGroupChangePair.first(), + handleGroupChangeResponse(group, groupGroupChangePair.second())); } return result; @@ -771,7 +839,8 @@ public class GroupHelper { groupInfoV2.setGroup(groupGroupChangePair.first()); account.getGroupStore().updateGroup(groupInfoV2); - var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupGroupChangePair.second().encode()); + var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, + handleGroupChangeResponse(groupInfoV2, groupGroupChangePair.second()).encode()); return sendGroupMessage(messageBuilder, groupInfoV2.getMembersIncludingPendingWithout(account.getSelfRecipientId()), groupInfoV2.getDistributionId()); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java index 83f49926..6c6acd65 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java @@ -19,6 +19,7 @@ import org.signal.libsignal.zkgroup.groups.UuidCiphertext; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.signal.storageservice.protos.groups.AccessControl; import org.signal.storageservice.protos.groups.GroupChange; +import org.signal.storageservice.protos.groups.GroupChangeResponse; import org.signal.storageservice.protos.groups.Member; import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroupChange; @@ -27,6 +28,7 @@ import org.signal.storageservice.protos.groups.local.DecryptedMember; import org.signal.storageservice.protos.groups.local.DecryptedPendingMember; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupResponse; import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil; import org.whispersystems.signalservice.api.groupsv2.GroupCandidate; import org.whispersystems.signalservice.api.groupsv2.GroupHistoryPage; @@ -75,7 +77,7 @@ class GroupV2Helper { groupApiCredentials = null; } - DecryptedGroup getDecryptedGroup(final GroupSecretParams groupSecretParams) throws NotAGroupMemberException { + DecryptedGroupResponse getDecryptedGroup(final GroupSecretParams groupSecretParams) throws NotAGroupMemberException { try { final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams); return dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupsV2AuthorizationString); @@ -85,7 +87,7 @@ class GroupV2Helper { } logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage()); return null; - } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { + } catch (IOException | VerificationFailedException | InvalidGroupStateException | InvalidInputException e) { logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage()); return null; } @@ -103,19 +105,23 @@ class GroupV2Helper { } GroupHistoryPage getDecryptedGroupHistoryPage( - final GroupSecretParams groupSecretParams, int fromRevision + final GroupSecretParams groupSecretParams, int fromRevision, long sendEndorsementsExpirationMs ) throws NotAGroupMemberException { try { final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams); return dependencies.getGroupsV2Api() - .getGroupHistoryPage(groupSecretParams, fromRevision, groupsV2AuthorizationString, false); + .getGroupHistoryPage(groupSecretParams, + fromRevision, + groupsV2AuthorizationString, + false, + sendEndorsementsExpirationMs); } catch (NonSuccessfulResponseCodeException e) { if (e.getCode() == 403) { throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null); } logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage()); return null; - } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { + } catch (IOException | VerificationFailedException | InvalidGroupStateException | InvalidInputException e) { logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage()); return null; } @@ -132,7 +138,7 @@ class GroupV2Helper { return partialDecryptedGroup.revision; } - Pair createGroup( + Pair createGroup( String name, Set members, byte[] avatarFile ) { final var newGroup = buildNewGroup(name, members, avatarFile); @@ -143,16 +149,16 @@ class GroupV2Helper { final var groupSecretParams = newGroup.getGroupSecretParams(); final GroupsV2AuthorizationString groupAuthForToday; - final DecryptedGroup decryptedGroup; + final DecryptedGroupResponse response; try { groupAuthForToday = getGroupAuthForToday(groupSecretParams); dependencies.getGroupsV2Api().putNewGroup(newGroup, groupAuthForToday); - decryptedGroup = dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupAuthForToday); - } catch (IOException | VerificationFailedException | InvalidGroupStateException e) { + response = dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupAuthForToday); + } catch (IOException | VerificationFailedException | InvalidGroupStateException | InvalidInputException e) { logger.warn("Failed to create V2 group: {}", e.getMessage()); return null; } - if (decryptedGroup == null) { + if (response == null) { logger.warn("Failed to create V2 group, unknown error!"); return null; } @@ -161,7 +167,7 @@ class GroupV2Helper { final var masterKey = groupSecretParams.getMasterKey(); var g = new GroupInfoV2(groupId, masterKey, context.getAccount().getRecipientResolver()); - return new Pair<>(g, decryptedGroup); + return new Pair<>(g, response); } private GroupsV2Operations.NewGroup buildNewGroup( @@ -195,7 +201,7 @@ class GroupV2Helper { 0); } - Pair updateGroup( + Pair updateGroup( GroupInfoV2 groupInfoV2, String name, String description, byte[] avatarFile ) throws IOException { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); @@ -218,7 +224,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - Pair addMembers( + Pair addMembers( GroupInfoV2 groupInfoV2, Set newMembers ) throws IOException { GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -244,7 +250,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - Pair leaveGroup( + Pair leaveGroup( GroupInfoV2 groupInfoV2, Set membersToMakeAdmin ) throws IOException { var pendingMembersList = groupInfoV2.getGroup().pendingMembers; @@ -264,7 +270,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, groupOperations.createLeaveAndPromoteMembersToAdmin(selfAci, adminUuids)); } - Pair removeMembers( + Pair removeMembers( GroupInfoV2 groupInfoV2, Set members ) throws IOException { final var memberUuids = members.stream() @@ -276,7 +282,7 @@ class GroupV2Helper { return ejectMembers(groupInfoV2, memberUuids); } - Pair approveJoinRequestMembers( + Pair approveJoinRequestMembers( GroupInfoV2 groupInfoV2, Set members ) throws IOException { final var memberUuids = members.stream() @@ -287,7 +293,7 @@ class GroupV2Helper { return approveJoinRequest(groupInfoV2, memberUuids); } - Pair refuseJoinRequestMembers( + Pair refuseJoinRequestMembers( GroupInfoV2 groupInfoV2, Set members ) throws IOException { final var memberUuids = members.stream() @@ -297,7 +303,7 @@ class GroupV2Helper { return refuseJoinRequest(groupInfoV2, memberUuids); } - Pair revokeInvitedMembers( + Pair revokeInvitedMembers( GroupInfoV2 groupInfoV2, Set members ) throws IOException { var pendingMembersList = groupInfoV2.getGroup().pendingMembers; @@ -311,7 +317,7 @@ class GroupV2Helper { return revokeInvites(groupInfoV2, memberUuids); } - Pair banMembers( + Pair banMembers( GroupInfoV2 groupInfoV2, Set block ) throws IOException { GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -329,7 +335,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - Pair unbanMembers( + Pair unbanMembers( GroupInfoV2 groupInfoV2, Set block ) throws IOException { GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -345,14 +351,14 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - Pair resetGroupLinkPassword(GroupInfoV2 groupInfoV2) throws IOException { + Pair resetGroupLinkPassword(GroupInfoV2 groupInfoV2) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final var newGroupLinkPassword = GroupLinkPassword.createNew().serialize(); final var change = groupOperations.createModifyGroupLinkPasswordChange(newGroupLinkPassword); return commitChange(groupInfoV2, change); } - Pair setGroupLinkState( + Pair setGroupLinkState( GroupInfoV2 groupInfoV2, GroupLinkState state ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -367,7 +373,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - Pair setEditDetailsPermission( + Pair setEditDetailsPermission( GroupInfoV2 groupInfoV2, GroupPermission permission ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -377,7 +383,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - Pair setAddMemberPermission( + Pair setAddMemberPermission( GroupInfoV2 groupInfoV2, GroupPermission permission ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -387,7 +393,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - Pair updateSelfProfileKey(GroupInfoV2 groupInfoV2) throws IOException { + Pair updateSelfProfileKey(GroupInfoV2 groupInfoV2) throws IOException { Optional selfInGroup = groupInfoV2.getGroup() == null ? Optional.empty() : DecryptedGroupUtil.findMemberByAci(groupInfoV2.getGroup().members, getSelfAci()); @@ -417,7 +423,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - GroupChange joinGroup( + GroupChangeResponse joinGroup( GroupMasterKey groupMasterKey, GroupLinkPassword groupLinkPassword, DecryptedGroupJoinInfo decryptedGroupJoinInfo @@ -444,7 +450,7 @@ class GroupV2Helper { return commitChange(groupSecretParams, decryptedGroupJoinInfo.revision, change, groupLinkPassword); } - Pair acceptInvite(GroupInfoV2 groupInfoV2) throws IOException { + Pair acceptInvite(GroupInfoV2 groupInfoV2) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final var selfRecipientId = context.getAccount().getSelfRecipientId(); @@ -461,7 +467,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - Pair setMemberAdmin( + Pair setMemberAdmin( GroupInfoV2 groupInfoV2, RecipientId recipientId, boolean admin ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -475,7 +481,7 @@ class GroupV2Helper { } } - Pair setMessageExpirationTimer( + Pair setMessageExpirationTimer( GroupInfoV2 groupInfoV2, int messageExpirationTimer ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -483,7 +489,7 @@ class GroupV2Helper { return commitChange(groupInfoV2, change); } - Pair setIsAnnouncementGroup( + Pair setIsAnnouncementGroup( GroupInfoV2 groupInfoV2, boolean isAnnouncementGroup ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -511,7 +517,7 @@ class GroupV2Helper { return dependencies.getGroupsV2Operations().forGroup(groupSecretParams); } - private Pair revokeInvites( + private Pair revokeInvites( GroupInfoV2 groupInfoV2, Set pendingMembers ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -525,28 +531,28 @@ class GroupV2Helper { return commitChange(groupInfoV2, groupOperations.createRemoveInvitationChange(uuidCipherTexts)); } - private Pair approveJoinRequest( + private Pair approveJoinRequest( GroupInfoV2 groupInfoV2, Set uuids ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); return commitChange(groupInfoV2, groupOperations.createApproveGroupJoinRequest(uuids)); } - private Pair refuseJoinRequest( + private Pair refuseJoinRequest( GroupInfoV2 groupInfoV2, Set serviceIds ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); return commitChange(groupInfoV2, groupOperations.createRefuseGroupJoinRequest(serviceIds, false, List.of())); } - private Pair ejectMembers( + private Pair ejectMembers( GroupInfoV2 groupInfoV2, Set members ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); return commitChange(groupInfoV2, groupOperations.createRemoveMembersChange(members, false, List.of())); } - private Pair commitChange( + private Pair commitChange( GroupInfoV2 groupInfoV2, GroupChange.Actions.Builder change ) throws IOException { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); @@ -567,10 +573,12 @@ class GroupV2Helper { var signedGroupChange = dependencies.getGroupsV2Api() .patchGroup(changeActions, getGroupAuthForToday(groupSecretParams), Optional.empty()); + groupInfoV2.setGroup(decryptedGroupState); + return new Pair<>(decryptedGroupState, signedGroupChange); } - private GroupChange commitChange( + private GroupChangeResponse commitChange( GroupSecretParams groupSecretParams, int currentRevision, GroupChange.Actions.Builder change, diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java index 4a2b79bd..03d277f9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java @@ -16,13 +16,14 @@ import org.asamk.signal.manager.util.KeyUtils; import org.asamk.signal.manager.util.PaymentUtils; import org.asamk.signal.manager.util.ProfileUtils; import org.asamk.signal.manager.util.Utils; +import org.jetbrains.annotations.Nullable; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; +import org.whispersystems.signalservice.api.crypto.SealedSenderAccess; import org.whispersystems.signalservice.api.profiles.AvatarUploadParams; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; import org.whispersystems.signalservice.api.profiles.SignalServiceProfile; @@ -387,7 +388,7 @@ public final class ProfileHelper { private Single retrieveProfile( SignalServiceAddress address, Optional profileKey, - Optional unidentifiedAccess, + @Nullable SealedSenderAccess unidentifiedAccess, SignalServiceProfile.RequestType requestType ) { final var profileService = dependencies.getProfileService(); @@ -450,13 +451,7 @@ public final class ProfileHelper { } } - private Optional getUnidentifiedAccess(RecipientId recipientId) { - var unidentifiedAccess = context.getUnidentifiedAccessHelper().getAccessFor(recipientId, true); - - if (unidentifiedAccess.isPresent()) { - return unidentifiedAccess.get().getTargetUnidentifiedAccess(); - } - - return Optional.empty(); + private @Nullable SealedSenderAccess getUnidentifiedAccess(RecipientId recipientId) { + return context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId, true); } } 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 0ff7fd89..2ac0fc9c 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 @@ -13,6 +13,7 @@ import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.groups.GroupInfo; import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry; +import org.jetbrains.annotations.Nullable; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidRegistrationIdException; import org.signal.libsignal.protocol.NoSessionException; @@ -22,9 +23,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.ContentHint; +import org.whispersystems.signalservice.api.crypto.SealedSenderAccess; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; +import org.whispersystems.signalservice.api.groupsv2.GroupSendEndorsements; import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage; @@ -199,7 +201,7 @@ public class SendHelper { return SendMessageResult.success(account.getSelfAddress(), List.of(), false, false, 0, Optional.empty()); } try { - return messageSender.sendSyncMessage(message, context.getUnidentifiedAccessHelper().getAccessForSync()); + return messageSender.sendSyncMessage(message); } catch (UnregisteredUserException e) { var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId()); return SendMessageResult.unregisteredFailure(address); @@ -380,10 +382,11 @@ public class SendHelper { () -> false, urgent, editTargetTimestamp.get()); - final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendGroupDataMessage( + final SenderKeySenderHandler senderKeySender = (distId, recipients, unidentifiedAccess, groupSendEndorsements, isRecipientUpdate) -> messageSender.sendGroupDataMessage( distId, recipients, unidentifiedAccess, + groupSendEndorsements, isRecipientUpdate, contentHint, message, @@ -436,9 +439,11 @@ public class SendHelper { unidentifiedAccess, message, () -> false), - (distId, recipients, unidentifiedAccess, isRecipientUpdate) -> messageSender.sendGroupTyping(distId, + (distId, recipients, unidentifiedAccess, groupSendEndorsements, isRecipientUpdate) -> messageSender.sendGroupTyping( + distId, recipients, unidentifiedAccess, + groupSendEndorsements, message), recipientIds, distributionId); @@ -526,8 +531,8 @@ public class SendHelper { final var senderKeyTargets = new HashSet(); final var recipientList = new ArrayList<>(recipientIds); for (final var recipientId : recipientList) { - final var access = context.getUnidentifiedAccessHelper().getAccessFor(recipientId); - if (access.isEmpty() || access.get().getTargetUnidentifiedAccess().isEmpty()) { + final var access = context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId); + if (access != null) { continue; } @@ -562,7 +567,8 @@ public class SendHelper { final var addresses = recipientIdList.stream() .map(context.getRecipientHelper()::resolveSignalServiceAddress) .toList(); - final var unidentifiedAccesses = context.getUnidentifiedAccessHelper().getAccessFor(recipientIdList); + final var unidentifiedAccesses = context.getUnidentifiedAccessHelper() + .getSealedSenderAccessFor(recipientIdList); try { final var results = sender.send(addresses, unidentifiedAccesses, isRecipientUpdate); @@ -601,15 +607,14 @@ public class SendHelper { List unidentifiedAccesses = context.getUnidentifiedAccessHelper() .getAccessFor(recipientIdList) .stream() - .map(Optional::get) - .map(UnidentifiedAccessPair::getTargetUnidentifiedAccess) - .map(Optional::get) .toList(); + final GroupSendEndorsements groupSendEndorsements = null;//TODO try { List results = sender.send(distributionId, addresses, unidentifiedAccesses, + groupSendEndorsements, isRecipientUpdate); final var successCount = results.stream().filter(SendMessageResult::isSuccess).count(); @@ -684,7 +689,7 @@ public class SendHelper { try { return s.send(messageSender, address, - context.getUnidentifiedAccessHelper().getAccessFor(recipientId), + context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId), includePniSignature); } catch (UnregisteredUserException e) { final RecipientId newRecipientId; @@ -696,7 +701,7 @@ public class SendHelper { address = context.getRecipientHelper().resolveSignalServiceAddress(newRecipientId); return s.send(messageSender, address, - context.getUnidentifiedAccessHelper().getAccessFor(newRecipientId), + context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(newRecipientId), includePniSignature); } } catch (UnregisteredUserException e) { @@ -772,7 +777,7 @@ public class SendHelper { SendMessageResult send( SignalServiceMessageSender messageSender, SignalServiceAddress address, - Optional unidentifiedAccess, + @Nullable SealedSenderAccess unidentifiedAccess, boolean includePniSignature ) throws IOException, UnregisteredUserException, ProofRequiredException, RateLimitException, org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; } @@ -783,6 +788,7 @@ public class SendHelper { DistributionId distributionId, List recipients, List unidentifiedAccess, + GroupSendEndorsements groupSendEndorsements, boolean isRecipientUpdate ) throws IOException, UntrustedIdentityException, NoSessionException, InvalidKeyException, InvalidRegistrationIdException; } @@ -791,7 +797,7 @@ public class SendHelper { List send( List recipients, - List> unidentifiedAccess, + List unidentifiedAccess, boolean isRecipientUpdate ) throws IOException, UntrustedIdentityException; } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java index dacc97ba..3899179c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java @@ -5,17 +5,17 @@ import org.asamk.signal.manager.api.Profile; import org.asamk.signal.manager.internal.SignalDependencies; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.recipients.RecipientId; +import org.jetbrains.annotations.Nullable; import org.signal.libsignal.metadata.certificate.InvalidCertificateException; import org.signal.libsignal.metadata.certificate.SenderCertificate; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.crypto.SealedSenderAccess; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; -import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import java.io.IOException; import java.util.List; -import java.util.Optional; import java.util.concurrent.TimeUnit; public class UnidentifiedAccessHelper { @@ -42,57 +42,49 @@ public class UnidentifiedAccessHelper { senderCertificate = null; } - public List> getAccessFor(List recipients) { + public List getSealedSenderAccessFor(List recipients) { + return recipients.stream().map(this::getAccessFor).map(SealedSenderAccess::forIndividual).toList(); + } + + public @Nullable SealedSenderAccess getSealedSenderAccessFor(RecipientId recipient) { + return getSealedSenderAccessFor(recipient, false); + } + + public @Nullable SealedSenderAccess getSealedSenderAccessFor(RecipientId recipient, boolean noRefresh) { + return SealedSenderAccess.forIndividual(getAccessFor(recipient, noRefresh)); + } + + public List getAccessFor(List recipients) { return recipients.stream().map(this::getAccessFor).toList(); } - public Optional getAccessFor(RecipientId recipient) { + private @Nullable UnidentifiedAccess getAccessFor(RecipientId recipient) { return getAccessFor(recipient, false); } - public Optional getAccessFor(RecipientId recipientId, boolean noRefresh) { + private @Nullable UnidentifiedAccess getAccessFor(RecipientId recipientId, boolean noRefresh) { var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipientId, noRefresh); if (recipientUnidentifiedAccessKey == null) { logger.trace("Unidentified access not available for {}", recipientId); - return Optional.empty(); + return null; } var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(noRefresh); if (selfUnidentifiedAccessKey == null) { logger.trace("Unidentified access not available for self"); - return Optional.empty(); + return null; } var senderCertificate = getSenderCertificateFor(recipientId); if (senderCertificate == null) { logger.trace("Unidentified access not available due to missing sender certificate"); - return Optional.empty(); + return null; } try { - return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(recipientUnidentifiedAccessKey, - senderCertificate, - false), new UnidentifiedAccess(selfUnidentifiedAccessKey, senderCertificate, false))); + return new UnidentifiedAccess(recipientUnidentifiedAccessKey, senderCertificate, false); } catch (InvalidCertificateException e) { - return Optional.empty(); - } - } - - public Optional getAccessForSync() { - var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey(false); - var selfUnidentifiedAccessCertificate = getSenderCertificate(); - - if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) { - return Optional.empty(); - } - - try { - return Optional.of(new UnidentifiedAccessPair(new UnidentifiedAccess(selfUnidentifiedAccessKey, - selfUnidentifiedAccessCertificate, - false), - new UnidentifiedAccess(selfUnidentifiedAccessKey, selfUnidentifiedAccessCertificate, false))); - } catch (InvalidCertificateException e) { - return Optional.empty(); + return null; } } @@ -121,7 +113,7 @@ public class UnidentifiedAccessHelper { privacySenderCertificate = new SenderCertificate(certificate); return certificate; } catch (IOException | InvalidCertificateException e) { - logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage()); + logger.warn("Failed to get sender certificate (pnp), ignoring: {}", e.getMessage()); return null; } } diff --git a/settings.gradle.kts b/settings.gradle.kts index d185c417..99018552 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") library("logback", "ch.qos.logback", "logback-classic").version("1.5.6") - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_104") + library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_105") library("sqlite", "org.xerial", "sqlite-jdbc").version("3.46.0.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index e234ed2f..43db0531 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.9.0"); + .orElse("Signal-Android/7.12.1"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From 2db3d3259e1274a6f5ee769186cf53cfdcfe0111 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 25 Jul 2024 22:17:46 +0200 Subject: [PATCH 080/279] Use UploadSpec for attachment uploads --- graalvm-config-dir/reflect-config.json | 7 +++ .../manager/helper/AttachmentHelper.java | 17 +++++- .../signal/manager/helper/GroupHelper.java | 3 +- .../signal/manager/helper/SyncHelper.java | 14 ++++- .../signal/manager/internal/ManagerImpl.java | 15 ++++- .../signal/manager/util/AttachmentUtils.java | 57 ++++++------------- 6 files changed, 67 insertions(+), 46 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 64efa057..50ed1e48 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -2381,6 +2381,13 @@ "name":"org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse", "fields":[{"name":"bitField0_"}, {"name":"data_"}, {"name":"status_"}, {"name":"token_"}, {"name":"tries_"}] }, +{ + "name":"org.whispersystems.signalservice.internal.push.AttachmentUploadForm", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["int","java.lang.String","java.util.Map","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","java.util.Map","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, { "name":"org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes", "allDeclaredFields":true, diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java index 75f0fd4a..951b7c5c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java @@ -44,7 +44,7 @@ public class AttachmentHelper { } public List uploadAttachments(final List attachments) throws AttachmentInvalidException, IOException { - var attachmentStreams = AttachmentUtils.createAttachmentStreams(attachments); + var attachmentStreams = createAttachmentStreams(attachments); // Upload attachments here, so we only upload once even for multiple recipients var attachmentPointers = new ArrayList(attachmentStreams.size()); @@ -54,8 +54,21 @@ public class AttachmentHelper { return attachmentPointers; } + private List createAttachmentStreams(List attachments) throws AttachmentInvalidException, IOException { + if (attachments == null) { + return null; + } + final var signalServiceAttachments = new ArrayList(attachments.size()); + for (var attachment : attachments) { + final var uploadSpec = dependencies.getMessageSender().getResumableUploadSpec().toProto(); + signalServiceAttachments.add(AttachmentUtils.createAttachmentStream(attachment, uploadSpec)); + } + return signalServiceAttachments; + } + public SignalServiceAttachmentPointer uploadAttachment(String attachment) throws IOException, AttachmentInvalidException { - var attachmentStream = AttachmentUtils.createAttachmentStream(attachment); + final var uploadSpec = dependencies.getMessageSender().getResumableUploadSpec().toProto(); + var attachmentStream = AttachmentUtils.createAttachmentStream(attachment, uploadSpec); return uploadAttachment(attachmentStream); } 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 62291a3a..a1f04775 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 @@ -109,7 +109,8 @@ public class GroupHelper { return Optional.empty(); } - return Optional.of(AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty())); + final var uploadSpec = dependencies.getMessageSender().getResumableUploadSpec().toProto(); + return Optional.of(AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty(), uploadSpec)); } public GroupInfoV2 getOrMigrateGroup( diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index 0d516eb6..db8ee935 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -36,6 +36,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOper import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.internal.push.SyncMessage; +import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -115,10 +116,15 @@ public class SyncHelper { if (groupsFile.exists() && groupsFile.length() > 0) { try (var groupsFileStream = new FileInputStream(groupsFile)) { + final var uploadSpec = context.getDependencies() + .getMessageSender() + .getResumableUploadSpec() + .toProto(); var attachmentStream = SignalServiceAttachment.newStreamBuilder() .withStream(groupsFileStream) .withContentType(MimeUtils.OCTET_STREAM) .withLength(groupsFile.length()) + .withResumableUploadSpec(ResumableUploadSpec.from(uploadSpec)) .build(); context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forGroups(attachmentStream)); @@ -158,10 +164,15 @@ public class SyncHelper { if (contactsFile.exists() && contactsFile.length() > 0) { try (var contactsFileStream = new FileInputStream(contactsFile)) { + final var uploadSpec = context.getDependencies() + .getMessageSender() + .getResumableUploadSpec() + .toProto(); var attachmentStream = SignalServiceAttachment.newStreamBuilder() .withStream(contactsFileStream) .withContentType(MimeUtils.OCTET_STREAM) .withLength(contactsFile.length()) + .withResumableUploadSpec(ResumableUploadSpec.from(uploadSpec)) .build(); context.getSendHelper() @@ -400,7 +411,8 @@ public class SyncHelper { return Optional.empty(); } - return Optional.of(AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty())); + final var uploadSpec = context.getDependencies().getMessageSender().getResumableUploadSpec().toProto(); + return Optional.of(AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty(), uploadSpec)); } private void downloadContactAvatar(SignalServiceAttachment avatar, RecipientAddress address) { 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 426429da..480fa751 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 @@ -742,8 +742,13 @@ public class ManagerImpl implements Manager { final var additionalAttachments = new ArrayList(); if (message.messageText().length() > 2000) { final var messageBytes = message.messageText().getBytes(StandardCharsets.UTF_8); - final var textAttachment = AttachmentUtils.createAttachmentStream(new StreamDetails(new ByteArrayInputStream( - messageBytes), MimeUtils.LONG_TEXT, messageBytes.length), Optional.empty()); + final var uploadSpec = dependencies.getMessageSender().getResumableUploadSpec().toProto(); + final var streamDetails = new StreamDetails(new ByteArrayInputStream(messageBytes), + MimeUtils.LONG_TEXT, + messageBytes.length); + final var textAttachment = AttachmentUtils.createAttachmentStream(streamDetails, + Optional.empty(), + uploadSpec); messageBuilder.withBody(message.messageText().substring(0, 2000)); additionalAttachments.add(context.getAttachmentHelper().uploadAttachment(textAttachment)); } else { @@ -800,11 +805,15 @@ public class ManagerImpl implements Manager { if (streamDetails == null) { throw new InvalidStickerException("Missing local sticker file"); } + final var uploadSpec = dependencies.getMessageSender().getResumableUploadSpec().toProto(); + final var stickerAttachment = AttachmentUtils.createAttachmentStream(streamDetails, + Optional.empty(), + uploadSpec); messageBuilder.withSticker(new SignalServiceDataMessage.Sticker(packId.serialize(), stickerPack.packKey(), stickerId, manifestSticker.emoji(), - AttachmentUtils.createAttachmentStream(streamDetails, Optional.empty()))); + stickerAttachment)); } if (!message.previews().isEmpty()) { final var previews = new ArrayList(message.previews().size()); diff --git a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java index d06d8745..5504640f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java @@ -1,65 +1,44 @@ package org.asamk.signal.manager.util; import org.asamk.signal.manager.api.AttachmentInvalidException; +import org.signal.protos.resumableuploads.ResumableUpload; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; +import org.whispersystems.signalservice.api.push.exceptions.ResumeLocationInvalidException; import org.whispersystems.signalservice.api.util.StreamDetails; import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import java.util.UUID; public class AttachmentUtils { - public static List createAttachmentStreams(List attachments) throws AttachmentInvalidException { - if (attachments == null) { - return null; - } - final var signalServiceAttachments = new ArrayList(attachments.size()); - for (var attachment : attachments) { - signalServiceAttachments.add(createAttachmentStream(attachment)); - } - return signalServiceAttachments; - } - - public static SignalServiceAttachmentStream createAttachmentStream(String attachment) throws AttachmentInvalidException { + public static SignalServiceAttachmentStream createAttachmentStream( + String attachment, ResumableUpload resumableUpload + ) throws AttachmentInvalidException { try { final var streamDetails = Utils.createStreamDetails(attachment); - return createAttachmentStream(streamDetails.first(), streamDetails.second()); + return createAttachmentStream(streamDetails.first(), streamDetails.second(), resumableUpload); } catch (IOException e) { throw new AttachmentInvalidException(attachment, e); } } public static SignalServiceAttachmentStream createAttachmentStream( - StreamDetails streamDetails, Optional name - ) { + StreamDetails streamDetails, Optional name, ResumableUpload resumableUpload + ) throws ResumeLocationInvalidException { // TODO maybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option final var uploadTimestamp = System.currentTimeMillis(); - Optional preview = Optional.empty(); - Optional caption = Optional.empty(); - Optional blurHash = Optional.empty(); - final Optional resumableUploadSpec = Optional.empty(); - return new SignalServiceAttachmentStream(streamDetails.getStream(), - streamDetails.getContentType(), - streamDetails.getLength(), - name, - false, - false, - false, - false, - preview, - 0, - 0, - uploadTimestamp, - caption, - blurHash, - null, - null, - resumableUploadSpec, - UUID.randomUUID()); + final var resumableUploadSpec = ResumableUploadSpec.from(resumableUpload); + return SignalServiceAttachmentStream.newStreamBuilder() + .withStream(streamDetails.getStream()) + .withContentType(streamDetails.getContentType()) + .withLength(streamDetails.getLength()) + .withFileName(name.orElse(null)) + .withUploadTimestamp(uploadTimestamp) + .withResumableUploadSpec(resumableUploadSpec) + .withUuid(UUID.randomUUID()) + .build(); } } From e961488b1af4e5eb861d7938394c1cca1441671f Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 25 Jul 2024 22:40:46 +0200 Subject: [PATCH 081/279] Update man page --- man/signal-cli.1.adoc | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 2b0f3dac..a019b815 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -23,6 +23,11 @@ For registering you need a phone number where you can receive SMS or incoming ca signal-cli was primarily developed to be used on servers to notify admins of important events. For this use-case, it has a dbus and a JSON-RPC interface, that can be used to send messages from other programs. +Originally the Signal protocol only allowed sending messages to other phone numbers. +Now with the introduction of phone number privacy, the phone number of a recipient is not always known. +Only the recipient's ServiceId, ACI/PNI (a special UUID) is known. +This special identifier can be used instead of the phone number in signal-cli's interface. + For some functionality the Signal protocol requires that all messages have been received from the server. The `receive` command should be regularly executed. In daemon mode messages are by default continuously received. @@ -312,11 +317,13 @@ e.g.: `--sticker 00abac3bc18d7f599bff2325dc306d43:2` *--mention*:: Mention another group member (syntax: start:length:recipientNumber) In the apps the mention replaces part of the message text, which is specified by the start and length values. -The units of start and length should be UTF-16 code units, NOT Unicode code points. For more information, see https://github.com/AsamK/signal-cli/wiki/FAQ#string-indexing-units +The units of start and length should be UTF-16 code units, NOT Unicode code points. +For more information, see https://github.com/AsamK/signal-cli/wiki/FAQ#string-indexing-units e.g.: `-m "Hi X!" --mention "3:1:+123456789"` *--text-style*:: -Style parts of the message text (syntax: start:length:STYLE). Like `--mention`, the units are UTF-16 code units. +Style parts of the message text (syntax: start:length:STYLE). +Like `--mention`, the units are UTF-16 code units. Where STYLE is one of: BOLD, ITALIC, SPOILER, STRIKETHROUGH, MONOSPACE e.g.: `-m "Something BIG!" --text-style "10:3:BOLD"` or for a mixed text style `-m "Something BIG!" --text-style "0:9:ITALIC" "10:3:BOLD"` @@ -591,7 +598,8 @@ Specify if only blocked or unblocked contacts should be shown (default: all cont Find contacts with the given contact or profile name. *--detailed*:: -List the contacts with more details. If output=json, then this is always set +List the contacts with more details. +If output=json, then this is always set *--internal*:: Include internal information that's normally not user visible From 3d11221732478d271ee3a0cfd4b5498d1a30b8e9 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 25 Jul 2024 22:43:55 +0200 Subject: [PATCH 082/279] Update jackson --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 99018552..8c41b37d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,7 +7,7 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { library("bouncycastle", "org.bouncycastle", "bcprov-jdk18on").version("1.78.1") - library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.17.1") + library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.17.2") library("argparse4j", "net.sourceforge.argparse4j", "argparse4j").version("0.9.0") library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("5.0.0") version("slf4j", "2.0.13") From fe3934171d6dc659f7b0a6dadba06b642b3e65d9 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 25 Jul 2024 22:47:16 +0200 Subject: [PATCH 083/279] Bump version to 0.13.5 --- CHANGELOG.md | 8 +++++++- build.gradle.kts | 2 +- data/org.asamk.SignalCli.metainfo.xml | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 987af127..81153ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog -## [Unreleased] +## [0.13.5] - 2024-07-25 + +Requires libsignal-client version 0.52.2 + +### Fixed + +- Fixed device linking, due to new feature flag ## [0.13.4] - 2024-06-06 diff --git a/build.gradle.kts b/build.gradle.kts index fc1dd1b0..d8ac80b7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.2" } -version = "0.13.5-SNAPSHOT" +version = "0.13.5" java { sourceCompatibility = JavaVersion.VERSION_21 diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index de1a00bd..42ae0509 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -45,8 +45,11 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.5 + - https://github.com/AsamK/signal-cli/releases/tag/v0.13.3 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.4 https://github.com/AsamK/signal-cli/releases/tag/v0.13.3 From 62da5148509f91e18b3d81e4a2944e1ab5c58e10 Mon Sep 17 00:00:00 2001 From: Ingmar Lippert Date: Tue, 20 Aug 2024 16:59:52 +0200 Subject: [PATCH 084/279] Add more specificity to addDevice (#1561) --- man/signal-cli.1.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index a019b815..a892a296 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -254,7 +254,7 @@ Only works, if this is the primary device. *--uri* URI:: Specify the uri contained in the QR code shown by the new device. -You will need the full URI such as "sgnl://linkdevice?uuid=..." (formerly "tsdevice:/?uuid=...") Make sure to enclose it in quotation marks for shells. +You will need the full URI such as "sgnl://linkdevice?uuid=...&pub_key=..." (formerly "tsdevice:/?uuid=...") Make sure to enclose it in quotation marks for shells. === listDevices From 6ea373fbd5b739f392b2601187523805ad406251 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 085/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81153ceb..6b07eb2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.5] - 2024-07-25 Requires libsignal-client version 0.52.2 diff --git a/build.gradle.kts b/build.gradle.kts index d8ac80b7..983e128b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.2" } -version = "0.13.5" +version = "0.13.6-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_21 From a69a9b7b0e3cf31446a977bedabda3ebfebefdf7 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 27 Jul 2024 11:35:24 +0200 Subject: [PATCH 086/279] Add method to update group endorsements --- .../java/org/asamk/signal/manager/helper/GroupHelper.java | 4 ++++ 1 file changed, 4 insertions(+) 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 a1f04775..268e6186 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 @@ -82,6 +82,10 @@ public class GroupHelper { return getGroup(groupId, false); } + public void updateGroupSendEndorsements(GroupId groupId) { + getGroup(groupId, true); + } + public List getGroups() { final var groups = account.getGroupStore().getGroups(); groups.forEach(group -> fillOrUpdateGroup(group, false)); From 6bdc9a4b16be0f0dd3a458b3380ed7171b432b2c Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 20 Aug 2024 17:09:36 +0200 Subject: [PATCH 087/279] Update gradle --- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 ++++- gradlew.bat | 2 ++ 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 12612 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 HJ4oML2Lo<@i8BwbL^1~GkG`E7C$SEa_ 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138..9355b415 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf13..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e4..9b42019c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## From 130cee9906c3f00120dee04959d5e4bc11dcf988 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 20 Aug 2024 17:13:57 +0200 Subject: [PATCH 088/279] Fix sending to groups with non sender key capable members Regression in 0.13.5 Fixes #1568 --- .../main/java/org/asamk/signal/manager/helper/SendHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2ac0fc9c..d80de2f8 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 @@ -532,7 +532,7 @@ public class SendHelper { final var recipientList = new ArrayList<>(recipientIds); for (final var recipientId : recipientList) { final var access = context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(recipientId); - if (access != null) { + if (access == null) { continue; } From cb129db95e20e2af9e5d7a6f4eca251074add951 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 20 Aug 2024 17:39:39 +0200 Subject: [PATCH 089/279] Update libsignal-service --- CHANGELOG.md | 6 +++ graalvm-config-dir/reflect-config.json | 7 +-- .../signal/manager/helper/SendHelper.java | 46 +++++++------------ settings.gradle.kts | 2 +- .../java/org/asamk/signal/BaseConfig.java | 2 +- 5 files changed, 28 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b07eb2e..7b534de6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +Requires libsignal-client version 0.52.5 + +### Fixed + +- Fix issue with sending to some groups + ## [0.13.5] - 2024-07-25 Requires libsignal-client version 0.52.2 diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 50ed1e48..0a65c68b 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -248,7 +248,8 @@ "allDeclaredMethods":true }, { - "name":"java.lang.Object" + "name":"java.lang.Object", + "methods":[{"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"hashCode","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] }, { "name":"java.lang.Record", @@ -2236,7 +2237,7 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":["org.signal.libsignal.protocol.ServiceId"] }] + "methods":[{"name":"","parameterTypes":["org.signal.libsignal.protocol.ServiceId"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"hashCode","parameterTypes":[] }, {"name":"logString","parameterTypes":[] }, {"name":"toByteArray","parameterTypes":[] }, {"name":"toByteString","parameterTypes":[] }, {"name":"toProtocolAddress","parameterTypes":["int"] }, {"name":"toString","parameterTypes":[] }] }, { "name":"org.whispersystems.signalservice.api.push.ServiceId$ACI" @@ -2246,7 +2247,7 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"fromLibSignal","parameterTypes":["org.signal.libsignal.protocol.ServiceId"] }, {"name":"parseOrNull","parameterTypes":["java.lang.String"] }, {"name":"parseOrNull","parameterTypes":["okio.ByteString"] }, {"name":"parseOrNull","parameterTypes":["byte[]"] }, {"name":"parseOrThrow","parameterTypes":["java.lang.String"] }, {"name":"parseOrThrow","parameterTypes":["okio.ByteString"] }, {"name":"parseOrThrow","parameterTypes":["byte[]"] }] + "methods":[{"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"fromLibSignal","parameterTypes":["org.signal.libsignal.protocol.ServiceId"] }, {"name":"hashCode","parameterTypes":[] }, {"name":"parseOrNull","parameterTypes":["java.lang.String"] }, {"name":"parseOrNull","parameterTypes":["java.lang.String","boolean"] }, {"name":"parseOrNull","parameterTypes":["okio.ByteString"] }, {"name":"parseOrNull","parameterTypes":["byte[]"] }, {"name":"parseOrThrow","parameterTypes":["java.lang.String"] }, {"name":"parseOrThrow","parameterTypes":["okio.ByteString"] }, {"name":"parseOrThrow","parameterTypes":["byte[]"] }, {"name":"toString","parameterTypes":[] }] }, { "name":"org.whispersystems.signalservice.api.push.ServiceId$PNI" 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 d80de2f8..84b9f0e6 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 @@ -202,24 +202,15 @@ public class SendHelper { } try { return messageSender.sendSyncMessage(message); - } catch (UnregisteredUserException e) { + } catch (Throwable e) { var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId()); - return SendMessageResult.unregisteredFailure(address); - } catch (ProofRequiredException e) { - var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId()); - return SendMessageResult.proofRequiredFailure(address, e); - } catch (RateLimitException e) { - var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId()); - logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage()); - return SendMessageResult.rateLimitFailure(address, e); - } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) { - var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId()); - return SendMessageResult.identityFailure(address, e.getIdentityKey()); - } catch (IOException e) { - var address = context.getRecipientHelper().resolveSignalServiceAddress(account.getSelfRecipientId()); - logger.warn("Failed to send message due to IO exception: {}", e.getMessage()); - logger.debug("Exception", e); - return SendMessageResult.networkFailure(address); + try { + return SignalServiceMessageSender.mapSendErrorToSendResult(e, System.currentTimeMillis(), address); + } catch (IOException ex) { + logger.warn("Failed to send message due to IO exception: {}", e.getMessage()); + logger.debug("Exception", e); + return SendMessageResult.networkFailure(address); + } } } @@ -704,19 +695,14 @@ public class SendHelper { context.getUnidentifiedAccessHelper().getSealedSenderAccessFor(newRecipientId), includePniSignature); } - } catch (UnregisteredUserException e) { - return SendMessageResult.unregisteredFailure(address); - } catch (ProofRequiredException e) { - return SendMessageResult.proofRequiredFailure(address, e); - } catch (RateLimitException e) { - logger.warn("Sending failed due to rate limiting from the signal server: {}", e.getMessage()); - return SendMessageResult.rateLimitFailure(address, e); - } catch (org.whispersystems.signalservice.api.crypto.UntrustedIdentityException e) { - return SendMessageResult.identityFailure(address, e.getIdentityKey()); - } catch (IOException e) { - logger.warn("Failed to send message due to IO exception: {}", e.getMessage()); - logger.debug("Exception", e); - return SendMessageResult.networkFailure(address); + } catch (Throwable e) { + try { + return SignalServiceMessageSender.mapSendErrorToSendResult(e, System.currentTimeMillis(), address); + } catch (IOException ex) { + logger.warn("Failed to send message due to IO exception: {}", e.getMessage()); + logger.debug("Exception", e); + return SendMessageResult.networkFailure(address); + } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 8c41b37d..e03e3059 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ dependencyResolutionManagement { library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") library("logback", "ch.qos.logback", "logback-classic").version("1.5.6") - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_105") + library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_106") library("sqlite", "org.xerial", "sqlite-jdbc").version("3.46.0.0") library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.10.2") diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 43db0531..6fd1b21e 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.12.1"); + .orElse("Signal-Android/7.14.1"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From bda395191b016b75119d14950aeb26a1b820568c Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 8 Sep 2024 08:29:54 +0200 Subject: [PATCH 090/279] Don't set previousE164s param if cdsi token is empty Fixes #1576 --- .../java/org/asamk/signal/manager/helper/RecipientHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bdd192a6..e003450b 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 @@ -231,7 +231,7 @@ public class RecipientHelper { final CdsiV2Service.Response response; try { response = dependencies.getAccountManager() - .getRegisteredUsersWithCdsi(previousNumbers, + .getRegisteredUsersWithCdsi(token.isEmpty() ? Set.of() : previousNumbers, newNumbers, account.getRecipientStore().getServiceIdToProfileKeyMap(), token, From 485c4fd46764e6be45e2da974b85e8a719d198a3 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 8 Sep 2024 08:30:08 +0200 Subject: [PATCH 091/279] Improve daemon deprecation message --- src/main/java/org/asamk/signal/commands/DaemonCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index e4a14f4a..59c2245e 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -198,7 +198,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { && httpAddress == null && inheritedChannel == null) { logger.warn( - "Running daemon command without explicit mode is deprecated. Use --dbus to use the dbus interface."); + "Running daemon command without explicit mode is deprecated. Use 'daemon --dbus' to use the dbus interface."); daemonHandler.runDbus(false, DbusConfig.getBusname()); } } From 7cc0ef1c70f02629659da970528bca4c04986bb8 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 8 Sep 2024 08:30:31 +0200 Subject: [PATCH 092/279] Improve error message and log output for failed jsonrpc commands --- .../signal/jsonrpc/SignalJsonRpcCommandHandler.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java b/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java index 3609163c..96456a4e 100644 --- a/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java +++ b/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java @@ -229,15 +229,17 @@ public class SignalJsonRpcCommandHandler { case RateLimitErrorException e -> throw new JsonRpcException(new JsonRpcResponse.Error(RATELIMIT_ERROR, e.getMessage(), getErrorDataNode(objectMapper, result))); - case UnexpectedErrorException e -> - throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INTERNAL_ERROR, - e.getMessage(), - getErrorDataNode(objectMapper, result))); + case UnexpectedErrorException e -> { + logger.error("Command execution failed with unexpected error", e); + throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INTERNAL_ERROR, + e.getMessage() + " (" + e.getClass().getSimpleName() + ")", + getErrorDataNode(objectMapper, result))); + } } } catch (Throwable e) { logger.error("Command execution failed", e); throw new JsonRpcException(new JsonRpcResponse.Error(JsonRpcResponse.Error.INTERNAL_ERROR, - e.getMessage(), + e.getMessage() + " (" + e.getClass().getSimpleName() + ")", getErrorDataNode(objectMapper, result))); } From b51f849fe63054d6575408855f63af2e1278c4c1 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 8 Sep 2024 08:51:33 +0200 Subject: [PATCH 093/279] Send sync message for read/viewed receipt messages Fixes #1570 --- .../signal/manager/helper/SyncHelper.java | 20 +++++++++++++++++++ .../signal/manager/internal/ManagerImpl.java | 11 ++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index db8ee935..9af1f01f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; +import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage; import org.whispersystems.signalservice.api.messages.multidevice.BlockedListMessage; import org.whispersystems.signalservice.api.messages.multidevice.ConfigurationMessage; import org.whispersystems.signalservice.api.messages.multidevice.ContactsMessage; @@ -30,10 +31,13 @@ import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsInp import org.whispersystems.signalservice.api.messages.multidevice.DeviceGroupsOutputStream; import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage; import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage; import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; +import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage; +import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.internal.push.SyncMessage; import org.whispersystems.signalservice.internal.push.http.ResumableUploadSpec; @@ -89,6 +93,22 @@ public class SyncHelper { .sendSyncMessage(SignalServiceSyncMessage.forFetchLatest(SignalServiceSyncMessage.FetchType.STORAGE_MANIFEST)); } + public void sendSyncReceiptMessage(ServiceId sender, SignalServiceReceiptMessage receiptMessage) { + if (receiptMessage.isReadReceipt()) { + final var readMessages = receiptMessage.getTimestamps() + .stream() + .map(t -> new ReadMessage(sender, t)) + .toList(); + context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forRead(readMessages)); + } else if (receiptMessage.isViewedReceipt()) { + final var viewedMessages = receiptMessage.getTimestamps() + .stream() + .map(t -> new ViewedMessage(sender, t)) + .toList(); + context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forViewed(viewedMessages)); + } + } + public void sendGroups() throws IOException { var groupsFile = IOUtils.createTempFile(); 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 480fa751..6549b088 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 @@ -704,8 +704,15 @@ public class ManagerImpl implements Manager { final SignalServiceReceiptMessage receiptMessage ) { try { - final var result = context.getSendHelper() - .sendReceiptMessage(receiptMessage, context.getRecipientHelper().resolveRecipient(sender)); + final var recipientId = context.getRecipientHelper().resolveRecipient(sender); + final var result = context.getSendHelper().sendReceiptMessage(receiptMessage, recipientId); + + final var serviceId = account.getRecipientAddressResolver() + .resolveRecipientAddress(recipientId) + .serviceId(); + if (serviceId.isPresent()) { + context.getSyncHelper().sendSyncReceiptMessage(serviceId.get(), receiptMessage); + } return new SendMessageResults(timestamp, Map.of(sender, List.of(toSendMessageResult(result)))); } catch (UnregisteredRecipientException e) { return new SendMessageResults(timestamp, From 19b15e68e4ec652e23f9625662b7c0f96b963029 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 8 Sep 2024 09:00:34 +0200 Subject: [PATCH 094/279] Improve addDevice error message Fixes #1573 --- .../main/java/org/asamk/signal/manager/Manager.java | 3 ++- .../manager/api/DeviceLimitExceededException.java | 12 ++++++++++++ .../asamk/signal/manager/helper/AccountHelper.java | 10 ++++++++-- .../asamk/signal/manager/internal/ManagerImpl.java | 3 ++- .../org/asamk/signal/commands/AddDeviceCommand.java | 5 ++++- .../java/org/asamk/signal/dbus/DbusSignalImpl.java | 3 ++- 6 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 lib/src/main/java/org/asamk/signal/manager/api/DeviceLimitExceededException.java diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 736f4e08..4b086994 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -6,6 +6,7 @@ import org.asamk.signal.manager.api.CaptchaRejectedException; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Device; +import org.asamk.signal.manager.api.DeviceLimitExceededException; import org.asamk.signal.manager.api.DeviceLinkUrl; import org.asamk.signal.manager.api.Group; import org.asamk.signal.manager.api.GroupId; @@ -148,7 +149,7 @@ public interface Manager extends Closeable { void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException; - void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException; + void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException, DeviceLimitExceededException; void setRegistrationLockPin(Optional pin) throws IOException, NotPrimaryDeviceException; diff --git a/lib/src/main/java/org/asamk/signal/manager/api/DeviceLimitExceededException.java b/lib/src/main/java/org/asamk/signal/manager/api/DeviceLimitExceededException.java new file mode 100644 index 00000000..e24cc2ac --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/DeviceLimitExceededException.java @@ -0,0 +1,12 @@ +package org.asamk.signal.manager.api; + +public class DeviceLimitExceededException extends Exception { + + public DeviceLimitExceededException(final String message) { + super(message); + } + + public DeviceLimitExceededException(final String message, final Throwable cause) { + super(message, cause); + } +} 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 5c74a37f..0ec45cb9 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 @@ -39,6 +39,7 @@ import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotReserve import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException; import org.whispersystems.signalservice.api.push.exceptions.UsernameTakenException; import org.whispersystems.signalservice.api.util.DeviceNameUtil; +import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException; import org.whispersystems.signalservice.internal.push.KyberPreKeyEntity; import org.whispersystems.signalservice.internal.push.OutgoingPushMessage; import org.whispersystems.signalservice.internal.push.SyncMessage; @@ -466,8 +467,13 @@ public class AccountHelper { dependencies.getAccountManager().setAccountAttributes(account.getAccountAttributes(null)); } - public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, InvalidDeviceLinkException { - var verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode(); + public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, InvalidDeviceLinkException, org.asamk.signal.manager.api.DeviceLimitExceededException { + String verificationCode; + try { + verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode(); + } catch (DeviceLimitExceededException e) { + throw new org.asamk.signal.manager.api.DeviceLimitExceededException("Too many linked devices", e); + } try { dependencies.getAccountManager() 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 6549b088..39f59ccd 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 @@ -23,6 +23,7 @@ import org.asamk.signal.manager.api.CaptchaRejectedException; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.Configuration; import org.asamk.signal.manager.api.Device; +import org.asamk.signal.manager.api.DeviceLimitExceededException; import org.asamk.signal.manager.api.DeviceLinkUrl; import org.asamk.signal.manager.api.Group; import org.asamk.signal.manager.api.GroupId; @@ -487,7 +488,7 @@ public class ManagerImpl implements Manager { } @Override - public void addDeviceLink(DeviceLinkUrl linkUrl) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException { + public void addDeviceLink(DeviceLinkUrl linkUrl) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException, DeviceLimitExceededException { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); } diff --git a/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java b/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java index 2813f4af..c40e4901 100644 --- a/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java +++ b/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java @@ -7,6 +7,7 @@ import org.asamk.signal.commands.exceptions.CommandException; import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.Manager; +import org.asamk.signal.manager.api.DeviceLimitExceededException; import org.asamk.signal.manager.api.DeviceLinkUrl; import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.NotPrimaryDeviceException; @@ -53,8 +54,10 @@ public class AddDeviceCommand implements JsonRpcLocalCommand { logger.error("Add device link failed: {}", e.getMessage()); throw new IOErrorException("Add device link failed", e); } catch (InvalidDeviceLinkException e) { - logger.error("Invalid device link"); + logger.info("Invalid device link"); throw new UserErrorException("Invalid device link", e); + } catch (DeviceLimitExceededException e) { + throw new UserErrorException("Account has too many linked devices already", e); } catch (NotPrimaryDeviceException e) { throw new UserErrorException("This command doesn't work on linked devices."); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index fae0c0a5..1b8b7825 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -5,6 +5,7 @@ import org.asamk.signal.BaseConfig; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.AttachmentInvalidException; import org.asamk.signal.manager.api.CaptchaRejectedException; +import org.asamk.signal.manager.api.DeviceLimitExceededException; import org.asamk.signal.manager.api.DeviceLinkUrl; import org.asamk.signal.manager.api.GroupId; import org.asamk.signal.manager.api.GroupInviteLinkUrl; @@ -189,7 +190,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { try { var deviceLinkUrl = DeviceLinkUrl.parseDeviceLinkUri(new URI(uri)); m.addDeviceLink(deviceLinkUrl); - } catch (IOException | InvalidDeviceLinkException e) { + } catch (IOException | InvalidDeviceLinkException | DeviceLimitExceededException e) { throw new Error.Failure(e.getClass().getSimpleName() + " Add device link failed. " + e.getMessage()); } catch (NotPrimaryDeviceException e) { throw new Error.Failure("This command doesn't work on linked devices."); From 82fbde4f1958983f7ca65d832cdcd0bebeed154f Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 8 Sep 2024 09:00:39 +0200 Subject: [PATCH 095/279] Adapt code style --- .idea/codeStyles/Project.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index adf3548b..aa627792 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -5,8 +5,8 @@

    Use Signal messenger in terminal - - AsamK - - org.asamk.SignalCli - - signal - signal-cli - messenger - messaging - + signal-cli + Use Signal messenger in terminal + + AsamK + + org.asamk.SignalCli + + signal + signal-cli + messenger + messaging + - https://github.com/AsamK/signal-cli/issues - https://github.com/AsamK/signal-cli - https://github.com/sponsors/AsamK - https://github.com/AsamK/signal-cli/discussions - https://github.com/AsamK/signal-cli + https://github.com/AsamK/signal-cli/issues + https://github.com/AsamK/signal-cli + https://github.com/sponsors/AsamK + https://github.com/AsamK/signal-cli/discussions + https://github.com/AsamK/signal-cli - CC0-1.0 - GPL-3.0-only + CC0-1.0 + GPL-3.0-only - -

    - signal-cli is an unofficial commandline interface for the Signal Messenger. - It supports many Signal functions, including registering, verifying, sending and receiving messages. - For registering you need a phone number where you can receive SMS or incoming calls. - Alternatively signal-cli can be linked to an existing App account. -

    -
    + +

    + signal-cli is an unofficial commandline interface for the Signal Messenger. + It supports many Signal functions, including registering, verifying, sending and receiving messages. + For registering you need a phone number where you can receive SMS or incoming calls. + Alternatively signal-cli can be linked to an existing App account. +

    +
    - - Utility - Java - + + Utility + Java + - - signal-cli - - - intense - - - - https://github.com/AsamK/signal-cli/releases/tag/v0.13.7 - - - https://github.com/AsamK/signal-cli/releases/tag/v0.13.6 - - - https://github.com/AsamK/signal-cli/releases/tag/v0.13.5 - - - https://github.com/AsamK/signal-cli/releases/tag/v0.13.4 - - - https://github.com/AsamK/signal-cli/releases/tag/v0.13.3 - - - https://github.com/AsamK/signal-cli/releases/tag/v0.13.2 - - - https://github.com/AsamK/signal-cli/releases/tag/v0.13.1 - - - https://github.com/AsamK/signal-cli/releases/tag/v0.13.0 - - - https://github.com/AsamK/signal-cli/releases/tag/v0.12.8 - - - https://github.com/AsamK/signal-cli/releases/tag/v0.12.7 - - + + signal-cli + + + intense + + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.8 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.7 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.6 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.5 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.4 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.3 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.2 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.1 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.0 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.12.8 + + + https://github.com/AsamK/signal-cli/releases/tag/v0.12.7 + + diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 62b89b78..06daf4b4 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -1036,7 +1036,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "methods":[{"name":"callMessage","parameterTypes":[] }, {"name":"dataMessage","parameterTypes":[] }, {"name":"editMessage","parameterTypes":[] }, {"name":"receiptMessage","parameterTypes":[] }, {"name":"source","parameterTypes":[] }, {"name":"sourceDevice","parameterTypes":[] }, {"name":"sourceName","parameterTypes":[] }, {"name":"sourceNumber","parameterTypes":[] }, {"name":"sourceUuid","parameterTypes":[] }, {"name":"storyMessage","parameterTypes":[] }, {"name":"syncMessage","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"typingMessage","parameterTypes":[] }] + "methods":[{"name":"callMessage","parameterTypes":[] }, {"name":"dataMessage","parameterTypes":[] }, {"name":"editMessage","parameterTypes":[] }, {"name":"receiptMessage","parameterTypes":[] }, {"name":"serverDeliveredTimestamp","parameterTypes":[] }, {"name":"serverReceivedTimestamp","parameterTypes":[] }, {"name":"source","parameterTypes":[] }, {"name":"sourceDevice","parameterTypes":[] }, {"name":"sourceName","parameterTypes":[] }, {"name":"sourceNumber","parameterTypes":[] }, {"name":"sourceUuid","parameterTypes":[] }, {"name":"storyMessage","parameterTypes":[] }, {"name":"syncMessage","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"typingMessage","parameterTypes":[] }] }, { "name":"org.asamk.signal.json.JsonPayment", From acddef6e35fcb1f7c5782bc4628e2adb4fdff20a Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 131/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ccb96cd..b40c4ed7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.8] - 2024-10-26 Requires libsignal-client version 0.58.2 diff --git a/build.gradle.kts b/build.gradle.kts index 37495dde..523e2980 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.3" } -version = "0.13.8" +version = "0.13.9-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_21 From 1b959608c36a795668c8e4a6a44474a9ab7c0bcd Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 28 Oct 2024 10:35:18 +0100 Subject: [PATCH 132/279] Simplify verification code request --- graalvm-config-dir/reflect-config.json | 2 +- .../manager/util/NumberVerificationUtils.java | 17 ++++------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 06daf4b4..09df8cfb 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -179,7 +179,7 @@ "name":"com.zaxxer.hikari.HikariConfig", "allDeclaredFields":true, "queryAllPublicMethods":true, - "methods":[{"name":"getAllowPoolSuspension","parameterTypes":[] }, {"name":"getAutoCommit","parameterTypes":[] }, {"name":"getCatalog","parameterTypes":[] }, {"name":"getConnectionInitSql","parameterTypes":[] }, {"name":"getConnectionTestQuery","parameterTypes":[] }, {"name":"getConnectionTimeout","parameterTypes":[] }, {"name":"getDataSource","parameterTypes":[] }, {"name":"getDataSourceClassName","parameterTypes":[] }, {"name":"getDataSourceJNDI","parameterTypes":[] }, {"name":"getDataSourceProperties","parameterTypes":[] }, {"name":"getDriverClassName","parameterTypes":[] }, {"name":"getExceptionOverrideClassName","parameterTypes":[] }, {"name":"getHealthCheckProperties","parameterTypes":[] }, {"name":"getHealthCheckRegistry","parameterTypes":[] }, {"name":"getIdleTimeout","parameterTypes":[] }, {"name":"getInitializationFailTimeout","parameterTypes":[] }, {"name":"getIsolateInternalQueries","parameterTypes":[] }, {"name":"getJdbcUrl","parameterTypes":[] }, {"name":"getKeepaliveTime","parameterTypes":[] }, {"name":"getLeakDetectionThreshold","parameterTypes":[] }, {"name":"getMaxLifetime","parameterTypes":[] }, {"name":"getMaximumPoolSize","parameterTypes":[] }, {"name":"getMetricRegistry","parameterTypes":[] }, {"name":"getMetricsTrackerFactory","parameterTypes":[] }, {"name":"getMinimumIdle","parameterTypes":[] }, {"name":"getPassword","parameterTypes":[] }, {"name":"getPoolName","parameterTypes":[] }, {"name":"getReadOnly","parameterTypes":[] }, {"name":"getRegisterMbeans","parameterTypes":[] }, {"name":"getScheduledExecutor","parameterTypes":[] }, {"name":"getSchema","parameterTypes":[] }, {"name":"getThreadFactory","parameterTypes":[] }, {"name":"getTransactionIsolation","parameterTypes":[] }, {"name":"getUsername","parameterTypes":[] }, {"name":"getValidationTimeout","parameterTypes":[] }, {"name":"isAllowPoolSuspension","parameterTypes":[] }, {"name":"isAutoCommit","parameterTypes":[] }, {"name":"isIsolateInternalQueries","parameterTypes":[] }, {"name":"isReadOnly","parameterTypes":[] }, {"name":"isRegisterMbeans","parameterTypes":[] }, {"name":"setAllowPoolSuspension","parameterTypes":["boolean"] }, {"name":"setAutoCommit","parameterTypes":["boolean"] }, {"name":"setCatalog","parameterTypes":["java.lang.String"] }, {"name":"setClass","parameterTypes":["java.lang.Class"] }, {"name":"setConnectionInitSql","parameterTypes":["java.lang.String"] }, {"name":"setConnectionTestQuery","parameterTypes":["java.lang.String"] }, {"name":"setConnectionTimeout","parameterTypes":["long"] }, {"name":"setDataSource","parameterTypes":["javax.sql.DataSource"] }, {"name":"setDataSourceClassName","parameterTypes":["java.lang.String"] }, {"name":"setDataSourceJNDI","parameterTypes":["java.lang.String"] }, {"name":"setDataSourceProperties","parameterTypes":["java.util.Properties"] }, {"name":"setDriverClassName","parameterTypes":["java.lang.String"] }, {"name":"setExceptionOverrideClassName","parameterTypes":["java.lang.String"] }, {"name":"setHealthCheckProperties","parameterTypes":["java.util.Properties"] }, {"name":"setHealthCheckRegistry","parameterTypes":["java.lang.Object"] }, {"name":"setIdleTimeout","parameterTypes":["long"] }, {"name":"setInitializationFailTimeout","parameterTypes":["long"] }, {"name":"setIsolateInternalQueries","parameterTypes":["boolean"] }, {"name":"setJdbcUrl","parameterTypes":["java.lang.String"] }, {"name":"setKeepaliveTime","parameterTypes":["long"] }, {"name":"setLeakDetectionThreshold","parameterTypes":["long"] }, {"name":"setMaxLifetime","parameterTypes":["long"] }, {"name":"setMaximumPoolSize","parameterTypes":["int"] }, {"name":"setMetricRegistry","parameterTypes":["java.lang.Object"] }, {"name":"setMetricsTrackerFactory","parameterTypes":["com.zaxxer.hikari.metrics.MetricsTrackerFactory"] }, {"name":"setMinimumIdle","parameterTypes":["int"] }, {"name":"setPassword","parameterTypes":["java.lang.String"] }, {"name":"setPoolName","parameterTypes":["java.lang.String"] }, {"name":"setReadOnly","parameterTypes":["boolean"] }, {"name":"setRegisterMbeans","parameterTypes":["boolean"] }, {"name":"setScheduledExecutor","parameterTypes":["java.util.concurrent.ScheduledExecutorService"] }, {"name":"setSchema","parameterTypes":["java.lang.String"] }, {"name":"setThreadFactory","parameterTypes":["java.util.concurrent.ThreadFactory"] }, {"name":"setTransactionIsolation","parameterTypes":["java.lang.String"] }, {"name":"setUsername","parameterTypes":["java.lang.String"] }, {"name":"setValidationTimeout","parameterTypes":["long"] }] + "methods":[{"name":"getAllowPoolSuspension","parameterTypes":[] }, {"name":"getAutoCommit","parameterTypes":[] }, {"name":"getCatalog","parameterTypes":[] }, {"name":"getConnectionInitSql","parameterTypes":[] }, {"name":"getConnectionTestQuery","parameterTypes":[] }, {"name":"getConnectionTimeout","parameterTypes":[] }, {"name":"getCredentials","parameterTypes":[] }, {"name":"getDataSource","parameterTypes":[] }, {"name":"getDataSourceClassName","parameterTypes":[] }, {"name":"getDataSourceJNDI","parameterTypes":[] }, {"name":"getDataSourceProperties","parameterTypes":[] }, {"name":"getDriverClassName","parameterTypes":[] }, {"name":"getExceptionOverride","parameterTypes":[] }, {"name":"getExceptionOverrideClassName","parameterTypes":[] }, {"name":"getHealthCheckProperties","parameterTypes":[] }, {"name":"getHealthCheckRegistry","parameterTypes":[] }, {"name":"getIdleTimeout","parameterTypes":[] }, {"name":"getInitializationFailTimeout","parameterTypes":[] }, {"name":"getIsolateInternalQueries","parameterTypes":[] }, {"name":"getJdbcUrl","parameterTypes":[] }, {"name":"getKeepaliveTime","parameterTypes":[] }, {"name":"getLeakDetectionThreshold","parameterTypes":[] }, {"name":"getMaxLifetime","parameterTypes":[] }, {"name":"getMaximumPoolSize","parameterTypes":[] }, {"name":"getMetricRegistry","parameterTypes":[] }, {"name":"getMetricsTrackerFactory","parameterTypes":[] }, {"name":"getMinimumIdle","parameterTypes":[] }, {"name":"getPassword","parameterTypes":[] }, {"name":"getPoolName","parameterTypes":[] }, {"name":"getReadOnly","parameterTypes":[] }, {"name":"getRegisterMbeans","parameterTypes":[] }, {"name":"getScheduledExecutor","parameterTypes":[] }, {"name":"getSchema","parameterTypes":[] }, {"name":"getThreadFactory","parameterTypes":[] }, {"name":"getTransactionIsolation","parameterTypes":[] }, {"name":"getUsername","parameterTypes":[] }, {"name":"getValidationTimeout","parameterTypes":[] }, {"name":"isAllowPoolSuspension","parameterTypes":[] }, {"name":"isAutoCommit","parameterTypes":[] }, {"name":"isIsolateInternalQueries","parameterTypes":[] }, {"name":"isReadOnly","parameterTypes":[] }, {"name":"isRegisterMbeans","parameterTypes":[] }, {"name":"setAllowPoolSuspension","parameterTypes":["boolean"] }, {"name":"setAutoCommit","parameterTypes":["boolean"] }, {"name":"setCatalog","parameterTypes":["java.lang.String"] }, {"name":"setClass","parameterTypes":["java.lang.Class"] }, {"name":"setConnectionInitSql","parameterTypes":["java.lang.String"] }, {"name":"setConnectionTestQuery","parameterTypes":["java.lang.String"] }, {"name":"setConnectionTimeout","parameterTypes":["long"] }, {"name":"setCredentials","parameterTypes":["com.zaxxer.hikari.util.Credentials"] }, {"name":"setDataSource","parameterTypes":["javax.sql.DataSource"] }, {"name":"setDataSourceClassName","parameterTypes":["java.lang.String"] }, {"name":"setDataSourceJNDI","parameterTypes":["java.lang.String"] }, {"name":"setDataSourceProperties","parameterTypes":["java.util.Properties"] }, {"name":"setDriverClassName","parameterTypes":["java.lang.String"] }, {"name":"setExceptionOverride","parameterTypes":["com.zaxxer.hikari.SQLExceptionOverride"] }, {"name":"setExceptionOverrideClassName","parameterTypes":["java.lang.String"] }, {"name":"setHealthCheckProperties","parameterTypes":["java.util.Properties"] }, {"name":"setHealthCheckRegistry","parameterTypes":["java.lang.Object"] }, {"name":"setIdleTimeout","parameterTypes":["long"] }, {"name":"setInitializationFailTimeout","parameterTypes":["long"] }, {"name":"setIsolateInternalQueries","parameterTypes":["boolean"] }, {"name":"setJdbcUrl","parameterTypes":["java.lang.String"] }, {"name":"setKeepaliveTime","parameterTypes":["long"] }, {"name":"setLeakDetectionThreshold","parameterTypes":["long"] }, {"name":"setMaxLifetime","parameterTypes":["long"] }, {"name":"setMaximumPoolSize","parameterTypes":["int"] }, {"name":"setMetricRegistry","parameterTypes":["java.lang.Object"] }, {"name":"setMetricsTrackerFactory","parameterTypes":["com.zaxxer.hikari.metrics.MetricsTrackerFactory"] }, {"name":"setMinimumIdle","parameterTypes":["int"] }, {"name":"setPassword","parameterTypes":["java.lang.String"] }, {"name":"setPoolName","parameterTypes":["java.lang.String"] }, {"name":"setReadOnly","parameterTypes":["boolean"] }, {"name":"setRegisterMbeans","parameterTypes":["boolean"] }, {"name":"setScheduledExecutor","parameterTypes":["java.util.concurrent.ScheduledExecutorService"] }, {"name":"setSchema","parameterTypes":["java.lang.String"] }, {"name":"setThreadFactory","parameterTypes":["java.util.concurrent.ThreadFactory"] }, {"name":"setTransactionIsolation","parameterTypes":["java.lang.String"] }, {"name":"setUsername","parameterTypes":["java.lang.String"] }, {"name":"setValidationTimeout","parameterTypes":["long"] }] }, { "name":"com.zaxxer.hikari.pool.PoolBase", diff --git a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java index 1c5aa411..03b1c03a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java @@ -10,7 +10,6 @@ import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; import org.asamk.signal.manager.helper.PinHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.NetworkResult; import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; @@ -91,19 +90,11 @@ public class NumberVerificationUtils { public static void requestVerificationCode( RegistrationApi registrationApi, String sessionId, boolean voiceVerification ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException { - final NetworkResult response; final var locale = Utils.getDefaultLocale(Locale.US); - if (voiceVerification) { - response = registrationApi.requestSmsVerificationCode(sessionId, - locale, - false, - VerificationCodeTransport.VOICE); - } else { - response = registrationApi.requestSmsVerificationCode(sessionId, - locale, - false, - VerificationCodeTransport.SMS); - } + final var response = registrationApi.requestSmsVerificationCode(sessionId, + locale, + false, + voiceVerification ? VerificationCodeTransport.VOICE : VerificationCodeTransport.SMS); try { Utils.handleResponseException(response); } catch (org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) { From a1d552698a8bbadc70af9793b5bc5ea5a49b1a42 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 28 Oct 2024 10:42:26 +0100 Subject: [PATCH 133/279] Fix verifyAccount Fixes #1614 --- .../java/org/asamk/signal/manager/helper/AccountHelper.java | 2 +- .../asamk/signal/manager/internal/RegistrationManagerImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 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 ba18c63c..474a2822 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 @@ -283,7 +283,7 @@ public class AccountHelper { (sessionId1, verificationCode1, registrationLock) -> { final var registrationApi = dependencies.getRegistrationApi(); try { - Utils.handleResponseException(registrationApi.verifyAccount(verificationCode1, sessionId1)); + Utils.handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1)); } catch (AlreadyVerifiedException e) { // Already verified so can continue changing number } 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 eaaba787..543d9a99 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 @@ -258,7 +258,7 @@ public class RegistrationManagerImpl implements RegistrationManager { ) throws IOException { final var registrationApi = unauthenticatedAccountManager.getRegistrationApi(); try { - Utils.handleResponseException(registrationApi.verifyAccount(verificationCode, sessionId)); + Utils.handleResponseException(registrationApi.verifyAccount(sessionId, verificationCode)); } catch (AlreadyVerifiedException e) { // Already verified so can continue registering } From 181086aefecfe7bcc0827477a442324fa4570e8b Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 28 Oct 2024 10:58:14 +0100 Subject: [PATCH 134/279] Update graalvm config Fixes #1612 --- graalvm-config-dir/reflect-config.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 09df8cfb..8a80f9c7 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -51,6 +51,9 @@ { "name":"[Lorg.asamk.signal.json.JsonQuotedAttachment;" }, +{ + "name":"[Lorg.asamk.signal.json.JsonSyncReadMessage;" +}, { "name":"[Lorg.asamk.signal.json.JsonTextStyle;" }, From 277652e3f2d6369da3c66c2ea941e493f344eb84 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 28 Oct 2024 23:37:26 +0100 Subject: [PATCH 135/279] Bump version to 0.13.9 --- CHANGELOG.md | 6 +++++- build.gradle.kts | 2 +- data/org.asamk.SignalCli.metainfo.xml | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b40c4ed7..f49b8b81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -## [Unreleased] +## [0.13.9] - 2024-10-28 + +### Fixed + +- Fix verify command ## [0.13.8] - 2024-10-26 diff --git a/build.gradle.kts b/build.gradle.kts index 523e2980..8e74a5bf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.3" } -version = "0.13.9-SNAPSHOT" +version = "0.13.9" java { sourceCompatibility = JavaVersion.VERSION_21 diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index 4722f8f7..46921033 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -45,6 +45,9 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.9 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.8 From a5e272be3f8d5280b702069604b7b1c6869e69bd Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 136/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f49b8b81..dd44885b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.9] - 2024-10-28 ### Fixed diff --git a/build.gradle.kts b/build.gradle.kts index 8e74a5bf..c205612e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.graalvm.buildtools.native") version "0.10.3" } -version = "0.13.9" +version = "0.13.10-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_21 From e5251ae1589bc82dade0dc294dc7e66a7b25cc9b Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 21 Nov 2024 21:17:33 +0100 Subject: [PATCH 137/279] Switch to using toml version catalogs --- gradle/libs.versions.toml | 17 +++++++++++++++++ settings.gradle.kts | 19 ------------------- 2 files changed, 17 insertions(+), 19 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..17a8dd46 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,17 @@ +[versions] +slf4j = "2.0.16" + +[libraries] +bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.78.1" +jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.18.0" +argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0" +dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0" +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } +logback = "ch.qos.logback:logback-classic:1.5.11" + +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_110" +sqlite = "org.xerial:sqlite-jdbc:3.47.0.0" +hikari = "com.zaxxer:HikariCP:6.0.0" +junit-jupiter = "org.junit.jupiter:junit-jupiter:5.11.3" +junit-launcher = "org.junit.platform:junit-platform-launcher:1.11.3" diff --git a/settings.gradle.kts b/settings.gradle.kts index ef9ec486..fe7c45a9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,25 +3,6 @@ dependencyResolutionManagement { mavenLocal() mavenCentral() } - - versionCatalogs { - create("libs") { - library("bouncycastle", "org.bouncycastle", "bcprov-jdk18on").version("1.78.1") - library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.18.0") - library("argparse4j", "net.sourceforge.argparse4j", "argparse4j").version("0.9.0") - library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("5.0.0") - version("slf4j", "2.0.16") - library("slf4j.api", "org.slf4j", "slf4j-api").versionRef("slf4j") - library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") - library("logback", "ch.qos.logback", "logback-classic").version("1.5.11") - - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_110") - library("sqlite", "org.xerial", "sqlite-jdbc").version("3.47.0.0") - library("hikari", "com.zaxxer", "HikariCP").version("6.0.0") - library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.11.3") - library("junit.launcher", "org.junit.platform", "junit-platform-launcher").version("1.11.3") - } - } } rootProject.name = "signal-cli" From 3533500b736ce382b9cc1d53d29ff267324bf4be Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 21 Nov 2024 21:17:44 +0100 Subject: [PATCH 138/279] Update dependencies --- graalvm-config-dir/reflect-config.json | 16 ++++++++++++++++ gradle/libs.versions.toml | 8 ++++---- .../asamk/signal/logging/LogConfigurator.java | 11 ++++++----- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 8a80f9c7..abe02ac4 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -1499,6 +1499,10 @@ "name":"org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE$Mappings", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.CONTEXT$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.CompositeSignatures$Mappings", "methods":[{"name":"","parameterTypes":[] }] @@ -1559,6 +1563,14 @@ "name":"org.bouncycastle.jcajce.provider.asymmetric.LMS$Mappings", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.MLDSA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.MLKEM$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.NTRU$Mappings", "methods":[{"name":"","parameterTypes":[] }] @@ -1567,6 +1579,10 @@ "name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.SLHDSA$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.SPHINCSPlus$Mappings", "methods":[{"name":"","parameterTypes":[] }] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 17a8dd46..d7d56d71 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,16 +2,16 @@ slf4j = "2.0.16" [libraries] -bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.78.1" -jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.18.0" +bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.79" +jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.18.1" argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0" dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0" slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } -logback = "ch.qos.logback:logback-classic:1.5.11" +logback = "ch.qos.logback:logback-classic:1.5.12" signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_110" sqlite = "org.xerial:sqlite-jdbc:3.47.0.0" -hikari = "com.zaxxer:HikariCP:6.0.0" +hikari = "com.zaxxer:HikariCP:6.2.1" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.11.3" junit-launcher = "org.junit.platform:junit-platform-launcher:1.11.3" diff --git a/src/main/java/org/asamk/signal/logging/LogConfigurator.java b/src/main/java/org/asamk/signal/logging/LogConfigurator.java index 65e8c4f5..f6e23b70 100644 --- a/src/main/java/org/asamk/signal/logging/LogConfigurator.java +++ b/src/main/java/org/asamk/signal/logging/LogConfigurator.java @@ -42,7 +42,7 @@ public class LogConfigurator extends ContextAwareBase implements Configurator { }}); final var rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME); - final var defaultLevel = verboseLevel > 2 ? Level.ALL : verboseLevel > 0 ? Level.INFO : Level.WARN; + final var defaultLevel = verboseLevel > 2 ? Level.TRACE : verboseLevel > 0 ? Level.INFO : Level.WARN; rootLogger.setLevel(defaultLevel); final var consoleLayout = verboseLevel == 0 || logFile != null @@ -51,13 +51,14 @@ public class LogConfigurator extends ContextAwareBase implements Configurator { final var consoleAppender = createLoggingConsoleAppender(lc, createLayoutWrappingEncoder(consoleLayout)); rootLogger.addAppender(consoleAppender); - lc.getLogger("org.asamk").setLevel(verboseLevel > 1 ? Level.ALL : verboseLevel > 0 ? Level.DEBUG : Level.INFO); + lc.getLogger("org.asamk") + .setLevel(verboseLevel > 1 ? Level.TRACE : verboseLevel > 0 ? Level.DEBUG : Level.INFO); lc.getLogger("org.asamk.Signal") - .setLevel(verboseLevel > 2 ? Level.ALL : verboseLevel > 1 ? Level.INFO : Level.WARN); + .setLevel(verboseLevel > 2 ? Level.TRACE : verboseLevel > 1 ? Level.INFO : Level.WARN); lc.getLogger("com.zaxxer.hikari.pool.PoolBase") - .setLevel(verboseLevel > 2 ? Level.ALL : verboseLevel > 1 ? Level.INFO : Level.WARN); + .setLevel(verboseLevel > 2 ? Level.TRACE : verboseLevel > 1 ? Level.INFO : Level.WARN); lc.getLogger("org.sqlite.core.NativeDB") - .setLevel(verboseLevel > 3 ? Level.ALL : verboseLevel > 1 ? Level.INFO : Level.WARN); + .setLevel(verboseLevel > 3 ? Level.TRACE : verboseLevel > 1 ? Level.INFO : Level.WARN); if (logFile != null) { consoleAppender.addFilter(new Filter<>() { From f2005593ecefd37c7e1666c2dc0c71b259271af0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 23 Nov 2024 21:28:00 +0100 Subject: [PATCH 139/279] Reformat files --- .../org/asamk/signal/manager/Manager.java | 70 ++++++++---- .../signal/manager/RegistrationManager.java | 7 +- .../signal/manager/SignalAccountFiles.java | 6 +- .../manager/actions/ResendMessageAction.java | 4 +- .../manager/actions/SendReceiptAction.java | 4 +- .../org/asamk/signal/manager/api/Group.java | 4 +- .../signal/manager/api/MessageEnvelope.java | 14 +-- .../signal/manager/config/ServiceConfig.java | 3 +- .../signal/manager/groups/GroupUtils.java | 3 +- .../signal/manager/helper/AccountHelper.java | 16 ++- .../manager/helper/AttachmentHelper.java | 7 +- .../signal/manager/helper/ContactHelper.java | 4 +- .../signal/manager/helper/GroupHelper.java | 48 +++++--- .../signal/manager/helper/GroupV2Helper.java | 81 +++++++++----- .../signal/manager/helper/IdentityHelper.java | 8 +- .../helper/IncomingMessageHandler.java | 15 ++- .../signal/manager/helper/PinHelper.java | 11 +- .../signal/manager/helper/PreKeyHelper.java | 14 ++- .../signal/manager/helper/ProfileHelper.java | 19 ++-- .../signal/manager/helper/ReceiveHelper.java | 8 +- .../manager/helper/RecipientHelper.java | 9 +- .../signal/manager/helper/SendHelper.java | 29 +++-- .../signal/manager/helper/StickerHelper.java | 7 +- .../signal/manager/helper/StorageHelper.java | 19 +++- .../signal/manager/helper/SyncHelper.java | 21 ++-- .../helper/UnidentifiedAccessHelper.java | 3 +- .../signal/manager/internal/ManagerImpl.java | 88 ++++++++++----- .../internal/RegistrationManagerImpl.java | 7 +- .../manager/storage/AccountDatabase.java | 3 +- .../manager/storage/AttachmentStore.java | 18 +-- .../signal/manager/storage/Database.java | 3 +- .../signal/manager/storage/SignalAccount.java | 20 ++-- .../storage/UnknownStorageIdStore.java | 4 +- .../asamk/signal/manager/storage/Utils.java | 9 +- .../storage/accounts/AccountsStore.java | 8 +- .../configuration/ConfigurationStore.java | 6 +- .../manager/storage/groups/GroupInfoV2.java | 4 +- .../manager/storage/groups/GroupStore.java | 32 ++++-- .../storage/groups/LegacyGroupStore.java | 6 +- .../storage/identities/IdentityInfo.java | 4 +- .../storage/identities/IdentityKeyStore.java | 16 ++- .../identities/LegacyIdentityKeyStore.java | 8 +- .../storage/keyValue/KeyValueStore.java | 12 +- .../storage/profiles/LegacyProfileStore.java | 3 +- .../storage/profiles/ProfileStore.java | 3 +- .../protocol/LegacyJsonIdentityKeyStore.java | 7 +- .../protocol/LegacyJsonPreKeyStore.java | 3 +- .../protocol/LegacyJsonSessionStore.java | 3 +- .../protocol/LegacyJsonSignedPreKeyStore.java | 3 +- .../storage/protocol/SignalProtocolStore.java | 7 +- .../manager/storage/recipients/CdsiStore.java | 12 +- .../recipients/LegacyRecipientStore.java | 3 +- .../recipients/MergeRecipientHelper.java | 3 +- .../storage/recipients/RecipientStore.java | 105 ++++++++++++------ .../recipients/RecipientTrustedResolver.java | 4 +- .../storage/sendLog/MessageSendLogStore.java | 25 ++++- .../senderKeys/SenderKeyRecordStore.java | 8 +- .../senderKeys/SenderKeySharedStore.java | 11 +- .../storage/senderKeys/SenderKeyStore.java | 7 +- .../storage/sessions/SessionStore.java | 4 +- .../threads/LegacyJsonThreadStore.java | 7 +- .../syncStorage/AccountRecordProcessor.java | 4 +- .../syncStorage/ContactRecordProcessor.java | 4 +- .../syncStorage/StorageSyncModels.java | 12 +- .../syncStorage/StorageSyncValidations.java | 8 +- .../signal/manager/util/AttachmentUtils.java | 7 +- .../asamk/signal/manager/util/KeyUtils.java | 7 +- .../manager/util/NumberVerificationUtils.java | 24 +++- .../signal/manager/util/PaymentUtils.java | 8 +- .../signal/manager/util/ProfileUtils.java | 17 +-- .../signal/manager/util/StickerUtils.java | 8 +- .../org/asamk/signal/manager/util/Utils.java | 10 +- .../recipients/MergeRecipientHelperTest.java | 4 +- src/main/java/org/asamk/Signal.java | 76 ++++++++----- src/main/java/org/asamk/SignalControl.java | 7 +- src/main/java/org/asamk/signal/App.java | 6 +- .../asamk/signal/ReceiveMessageHandler.java | 60 +++------- .../signal/commands/AddDeviceCommand.java | 4 +- .../commands/AddStickerPackCommand.java | 4 +- .../asamk/signal/commands/BlockCommand.java | 4 +- .../asamk/signal/commands/CommandHandler.java | 9 +- .../asamk/signal/commands/DaemonCommand.java | 8 +- .../DeleteLocalAccountDataCommand.java | 4 +- .../commands/FinishChangeNumberCommand.java | 4 +- .../signal/commands/FinishLinkCommand.java | 4 +- .../signal/commands/GetAttachmentCommand.java | 4 +- .../signal/commands/GetAvatarCommand.java | 4 +- .../signal/commands/GetStickerCommand.java | 4 +- .../signal/commands/GetUserStatusCommand.java | 4 +- .../signal/commands/JoinGroupCommand.java | 4 +- .../commands/JsonRpcDispatcherCommand.java | 8 +- .../signal/commands/JsonRpcLocalCommand.java | 4 +- .../commands/JsonRpcMultiLocalCommand.java | 4 +- .../asamk/signal/commands/LinkCommand.java | 4 +- .../signal/commands/ListAccountsCommand.java | 4 +- .../signal/commands/ListContactsCommand.java | 4 +- .../signal/commands/ListDevicesCommand.java | 4 +- .../signal/commands/ListGroupsCommand.java | 8 +- .../commands/ListIdentitiesCommand.java | 4 +- .../commands/ListStickerPacksCommand.java | 4 +- .../signal/commands/QuitGroupCommand.java | 4 +- .../asamk/signal/commands/ReceiveCommand.java | 8 +- .../signal/commands/RegisterCommand.java | 9 +- .../signal/commands/RemoteDeleteCommand.java | 4 +- .../signal/commands/RemoveContactCommand.java | 4 +- .../signal/commands/RemoveDeviceCommand.java | 4 +- .../signal/commands/RemovePinCommand.java | 4 +- .../asamk/signal/commands/SendCommand.java | 7 +- .../signal/commands/SendContactsCommand.java | 4 +- .../SendMessageRequestResponseCommand.java | 4 +- .../SendPaymentNotificationCommand.java | 4 +- .../signal/commands/SendReactionCommand.java | 4 +- .../signal/commands/SendReceiptCommand.java | 4 +- .../commands/SendSyncRequestCommand.java | 4 +- .../signal/commands/SendTypingCommand.java | 4 +- .../asamk/signal/commands/SetPinCommand.java | 4 +- .../commands/StartChangeNumberCommand.java | 4 +- .../signal/commands/StartLinkCommand.java | 4 +- .../asamk/signal/commands/TrustCommand.java | 4 +- .../asamk/signal/commands/UnblockCommand.java | 4 +- .../signal/commands/UnregisterCommand.java | 4 +- .../signal/commands/UpdateAccountCommand.java | 4 +- .../commands/UpdateConfigurationCommand.java | 4 +- .../signal/commands/UpdateContactCommand.java | 4 +- .../signal/commands/UpdateGroupCommand.java | 8 +- .../signal/commands/UpdateProfileCommand.java | 4 +- .../commands/UploadStickerPackCommand.java | 4 +- .../asamk/signal/commands/VerifyCommand.java | 8 +- .../asamk/signal/commands/VersionCommand.java | 8 +- .../asamk/signal/dbus/DbusCommandHandler.java | 3 +- .../org/asamk/signal/dbus/DbusHandler.java | 5 +- .../dbus/DbusInterfacePropertiesHandler.java | 4 +- .../asamk/signal/dbus/DbusManagerImpl.java | 68 +++++++----- .../dbus/DbusMultiAccountManagerImpl.java | 4 +- .../dbus/DbusProvisioningManagerImpl.java | 4 +- .../dbus/DbusRegistrationManagerImpl.java | 7 +- .../signal/dbus/DbusSignalControlImpl.java | 11 +- .../org/asamk/signal/dbus/DbusSignalImpl.java | 49 ++++---- .../asamk/signal/http/HttpServerHandler.java | 4 +- .../signal/json/JsonMessageEnvelope.java | 4 +- .../asamk/signal/jsonrpc/JsonRpcRequest.java | 9 +- .../jsonrpc/SignalJsonRpcCommandHandler.java | 8 +- .../SignalJsonRpcDispatcherHandler.java | 20 +++- .../asamk/signal/jsonrpc/SocketHandler.java | 4 +- .../asamk/signal/logging/LogConfigurator.java | 6 +- .../org/asamk/signal/logging/Scrubber.java | 4 +- .../org/asamk/signal/util/CommandUtil.java | 6 +- .../signal/util/SendMessageResultUtils.java | 3 +- 148 files changed, 1056 insertions(+), 578 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 4b086994..85644f61 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -130,11 +130,15 @@ public interface Manager extends Closeable { void deleteUsername() throws IOException; void startChangeNumber( - String newNumber, boolean voiceVerification, String captcha + String newNumber, + boolean voiceVerification, + String captcha ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethodNotAvailableException; void finishChangeNumber( - String newNumber, String verificationCode, String pin + String newNumber, + String verificationCode, + String pin ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException; void unregister() throws IOException; @@ -142,7 +146,8 @@ public interface Manager extends Closeable { void deleteAccount() throws IOException; void submitRateLimitRecaptchaChallenge( - String challenge, String captcha + String challenge, + String captcha ) throws IOException, CaptchaRejectedException; List getLinkedDevices() throws IOException; @@ -156,17 +161,21 @@ public interface Manager extends Closeable { List getGroups(); SendGroupMessageResults quitGroup( - GroupId groupId, Set groupAdmins + GroupId groupId, + Set groupAdmins ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException, UnregisteredRecipientException; void deleteGroup(GroupId groupId) throws IOException; Pair createGroup( - String name, Set members, String avatarFile + String name, + Set members, + String avatarFile ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException; SendGroupMessageResults updateGroup( - final GroupId groupId, final UpdateGroup updateGroup + final GroupId groupId, + final UpdateGroup updateGroup ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException, UnregisteredRecipientException; Pair joinGroup( @@ -174,27 +183,29 @@ public interface Manager extends Closeable { ) throws IOException, InactiveGroupLinkException, PendingAdminApprovalException; SendMessageResults sendTypingMessage( - TypingAction action, Set recipients + TypingAction action, + Set recipients ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; - SendMessageResults sendReadReceipt( - RecipientIdentifier.Single sender, List messageIds - ); + SendMessageResults sendReadReceipt(RecipientIdentifier.Single sender, List messageIds); - SendMessageResults sendViewedReceipt( - RecipientIdentifier.Single sender, List messageIds - ); + SendMessageResults sendViewedReceipt(RecipientIdentifier.Single sender, List messageIds); SendMessageResults sendMessage( - Message message, Set recipients, boolean notifySelf + Message message, + Set recipients, + boolean notifySelf ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; SendMessageResults sendEditMessage( - Message message, Set recipients, long editTargetTimestamp + Message message, + Set recipients, + long editTargetTimestamp ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException; SendMessageResults sendRemoteDeleteMessage( - long targetSentTimestamp, Set recipients + long targetSentTimestamp, + Set recipients ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException; SendMessageResults sendMessageReaction( @@ -207,13 +218,16 @@ public interface Manager extends Closeable { ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException; SendMessageResults sendPaymentNotificationMessage( - byte[] receipt, String note, RecipientIdentifier.Single recipient + byte[] receipt, + String note, + RecipientIdentifier.Single recipient ) throws IOException; SendMessageResults sendEndSessionMessage(Set recipients) throws IOException; SendMessageResults sendMessageRequestResponse( - MessageEnvelope.Sync.MessageRequestResponse.Type type, Set recipientIdentifiers + MessageEnvelope.Sync.MessageRequestResponse.Type type, + Set recipientIdentifiers ); void hideRecipient(RecipientIdentifier.Single recipient); @@ -223,22 +237,27 @@ public interface Manager extends Closeable { void deleteContact(RecipientIdentifier.Single recipient); void setContactName( - RecipientIdentifier.Single recipient, String givenName, final String familyName + RecipientIdentifier.Single recipient, + String givenName, + final String familyName ) throws NotPrimaryDeviceException, UnregisteredRecipientException; void setContactsBlocked( - Collection recipient, boolean blocked + Collection recipient, + boolean blocked ) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException; void setGroupsBlocked( - Collection groupId, boolean blocked + Collection groupId, + boolean blocked ) throws GroupNotFoundException, IOException, NotPrimaryDeviceException; /** * Change the expiration timer for a contact */ void setExpirationTimer( - RecipientIdentifier.Single recipient, int messageExpirationTimer + RecipientIdentifier.Single recipient, + int messageExpirationTimer ) throws IOException, UnregisteredRecipientException; /** @@ -277,7 +296,9 @@ public interface Manager extends Closeable { * Receive new messages from server, returns if no new message arrive in a timespan of timeout. */ void receiveMessages( - Optional timeout, Optional maxMessages, ReceiveMessageHandler handler + Optional timeout, + Optional maxMessages, + ReceiveMessageHandler handler ) throws IOException, AlreadyReceivingException; void stopReceiveMessages(); @@ -309,7 +330,8 @@ public interface Manager extends Closeable { * @param recipient account of the identity */ boolean trustIdentityVerified( - RecipientIdentifier.Single recipient, IdentityVerificationCode verificationCode + RecipientIdentifier.Single recipient, + IdentityVerificationCode verificationCode ) throws UnregisteredRecipientException; /** diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index 5660bb13..7d358df6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -13,11 +13,14 @@ import java.io.IOException; public interface RegistrationManager extends Closeable { void register( - boolean voiceVerification, String captcha, final boolean forceRegister + boolean voiceVerification, + String captcha, + final boolean forceRegister ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException; void verifyAccount( - String verificationCode, String pin + String verificationCode, + String pin ) throws IOException, PinLockedException, IncorrectPinException; void deleteLocalAccountData() throws IOException; 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 03c44013..4072a9df 100644 --- a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java +++ b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java @@ -85,7 +85,8 @@ public class SignalAccountFiles { } private Manager initManager( - String number, String accountPath + String number, + String accountPath ) throws IOException, NotRegisteredException, AccountCheckException { if (accountPath == null) { throw new NotRegisteredException(); @@ -152,7 +153,8 @@ public class SignalAccountFiles { } public RegistrationManager initRegistrationManager( - String number, Consumer newManagerListener + String number, + Consumer newManagerListener ) throws IOException { final var accountPath = accountsStore.getPathByNumber(number); if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) { diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java index 9f399dd8..a8f67fd6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java +++ b/lib/src/main/java/org/asamk/signal/manager/actions/ResendMessageAction.java @@ -13,7 +13,9 @@ public class ResendMessageAction implements HandleAction { private final MessageSendLogEntry messageSendLogEntry; public ResendMessageAction( - final RecipientId recipientId, final long timestamp, final MessageSendLogEntry messageSendLogEntry + final RecipientId recipientId, + final long timestamp, + final MessageSendLogEntry messageSendLogEntry ) { this.recipientId = recipientId; this.timestamp = timestamp; diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendReceiptAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendReceiptAction.java index c08438b5..6a5cfd59 100644 --- a/lib/src/main/java/org/asamk/signal/manager/actions/SendReceiptAction.java +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendReceiptAction.java @@ -15,7 +15,9 @@ public class SendReceiptAction implements HandleAction { private final List timestamps = new ArrayList<>(); public SendReceiptAction( - final RecipientId recipientId, final SignalServiceReceiptMessage.Type type, final long timestamp + final RecipientId recipientId, + final SignalServiceReceiptMessage.Type type, + final long timestamp ) { this.recipientId = recipientId; this.type = type; diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Group.java b/lib/src/main/java/org/asamk/signal/manager/api/Group.java index 5e7e36f8..4d1fdaa1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Group.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Group.java @@ -27,7 +27,9 @@ public record Group( ) { public static Group from( - final GroupInfo groupInfo, final RecipientAddressResolver recipientStore, final RecipientId selfRecipientId + final GroupInfo groupInfo, + final RecipientAddressResolver recipientStore, + final RecipientId selfRecipientId ) { return new Group(groupInfo.getGroupId(), groupInfo.getTitle(), diff --git a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java index aefaa800..5598d438 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java @@ -338,7 +338,8 @@ public record MessageEnvelope( } static Attachment from( - SignalServiceDataMessage.Quote.QuotedAttachment a, final AttachmentFileProvider fileProvider + SignalServiceDataMessage.Quote.QuotedAttachment a, + final AttachmentFileProvider fileProvider ) { return new Attachment(Optional.empty(), Optional.empty(), @@ -510,9 +511,7 @@ public record MessageEnvelope( public record Preview(String title, String description, long date, String url, Optional image) { - static Preview from( - SignalServicePreview preview, final AttachmentFileProvider fileProvider - ) { + static Preview from(SignalServicePreview preview, final AttachmentFileProvider fileProvider) { return new Preview(preview.getTitle(), preview.getDescription(), preview.getDate(), @@ -832,9 +831,7 @@ public record MessageEnvelope( Optional textAttachment ) { - public static Story from( - SignalServiceStoryMessage storyMessage, final AttachmentFileProvider fileProvider - ) { + public static Story from(SignalServiceStoryMessage storyMessage, final AttachmentFileProvider fileProvider) { return new Story(storyMessage.getAllowsReplies().orElse(false), storyMessage.getGroupContext().map(c -> GroupUtils.getGroupIdV2(c.getMasterKey())), storyMessage.getFileAttachment().map(f -> Data.Attachment.from(f, fileProvider)), @@ -852,7 +849,8 @@ public record MessageEnvelope( ) { static TextAttachment from( - SignalServiceTextAttachment textAttachment, final AttachmentFileProvider fileProvider + SignalServiceTextAttachment textAttachment, + final AttachmentFileProvider fileProvider ) { return new TextAttachment(textAttachment.getText(), textAttachment.getStyle().map(Style::from), 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 7ee80815..482e0f60 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 @@ -33,7 +33,8 @@ public class ServiceConfig { } public static ServiceEnvironmentConfig getServiceEnvironmentConfig( - ServiceEnvironment serviceEnvironment, String userAgent + ServiceEnvironment serviceEnvironment, + String userAgent ) { final Interceptor userAgentInterceptor = chain -> chain.proceed(chain.request() .newBuilder() diff --git a/lib/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java b/lib/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java index 76352d1a..ae686c36 100644 --- a/lib/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/groups/GroupUtils.java @@ -18,7 +18,8 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2; public class GroupUtils { public static void setGroupContext( - final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo groupInfo + final SignalServiceDataMessage.Builder messageBuilder, + final GroupInfo groupInfo ) { if (groupInfo instanceof GroupInfoV1) { var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.DELIVER) 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 474a2822..d5e8581e 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 @@ -165,7 +165,9 @@ public class AccountHelper { } public void startChangeNumber( - String newNumber, boolean voiceVerification, String captcha + String newNumber, + boolean voiceVerification, + String captcha ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException { final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword()); final var registrationApi = accountManager.getRegistrationApi(); @@ -178,7 +180,9 @@ public class AccountHelper { } public void finishChangeNumber( - String newNumber, String verificationCode, String pin + String newNumber, + String verificationCode, + String pin ) throws IncorrectPinException, PinLockedException, IOException { for (var attempts = 0; attempts < 5; attempts++) { try { @@ -196,7 +200,9 @@ public class AccountHelper { } private void finishChangeNumberInternal( - String newNumber, String verificationCode, String pin + String newNumber, + String verificationCode, + String pin ) throws IncorrectPinException, PinLockedException, IOException { final var pniIdentity = KeyUtils.generateIdentityKeyPair(); final var encryptedDeviceMessages = new ArrayList(); @@ -308,9 +314,7 @@ public class AccountHelper { handlePniChangeNumberMessage(selfChangeNumber, updatePni); } - public void handlePniChangeNumberMessage( - final SyncMessage.PniChangeNumber pniChangeNumber, final PNI updatedPni - ) { + public void handlePniChangeNumberMessage(final SyncMessage.PniChangeNumber pniChangeNumber, final PNI updatedPni) { if (pniChangeNumber.identityKeyPair != null && pniChangeNumber.registrationId != null && pniChangeNumber.signedPreKey != null) { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java index 3005325a..0ffa62ed 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java @@ -104,9 +104,7 @@ public class AttachmentHelper { retrieveAttachment(attachment, input -> IOUtils.copyStream(input, outputStream)); } - public void retrieveAttachment( - SignalServiceAttachment attachment, AttachmentHandler consumer - ) throws IOException { + public void retrieveAttachment(SignalServiceAttachment attachment, AttachmentHandler consumer) throws IOException { if (attachment.isStream()) { var input = attachment.asStream().getInputStream(); // don't close input stream here, it might be reused later (e.g. with contact sync messages ...) @@ -131,7 +129,8 @@ public class AttachmentHelper { } private InputStream retrieveAttachmentAsStream( - SignalServiceAttachmentPointer pointer, File tmpFile + SignalServiceAttachmentPointer pointer, + File tmpFile ) throws IOException { try { return dependencies.getMessageReceiver() diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java index 56ef89ed..fafb9ffe 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ContactHelper.java @@ -49,7 +49,9 @@ public class ContactHelper { } public void setExpirationTimer( - RecipientId recipientId, int messageExpirationTimer, int messageExpirationTimerVersion + RecipientId recipientId, + int messageExpirationTimer, + int messageExpirationTimerVersion ) { var contact = account.getContactStore().getContact(recipientId); if (contact != null && ( 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 ad31f35b..0d838204 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 @@ -118,7 +118,9 @@ public class GroupHelper { } public GroupInfoV2 getOrMigrateGroup( - final GroupMasterKey groupMasterKey, final int revision, final byte[] signedGroupChange + final GroupMasterKey groupMasterKey, + final int revision, + final byte[] signedGroupChange ) { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); @@ -166,7 +168,8 @@ public class GroupHelper { } private DecryptedGroup handleDecryptedGroupResponse( - GroupInfoV2 groupInfoV2, final DecryptedGroupResponse decryptedGroupResponse + GroupInfoV2 groupInfoV2, + final DecryptedGroupResponse decryptedGroupResponse ) { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); ReceivedGroupSendEndorsements groupSendEndorsements = dependencies.getGroupsV2Operations() @@ -181,7 +184,8 @@ public class GroupHelper { } private GroupChange handleGroupChangeResponse( - final GroupInfoV2 groupInfoV2, final GroupChangeResponse groupChangeResponse + final GroupInfoV2 groupInfoV2, + final GroupChangeResponse groupChangeResponse ) { ReceivedGroupSendEndorsements groupSendEndorsements = dependencies.getGroupsV2Operations() .forGroup(GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey())) @@ -195,7 +199,9 @@ public class GroupHelper { } public Pair createGroup( - String name, Set members, String avatarFile + String name, + Set members, + String avatarFile ) throws IOException, AttachmentInvalidException { final var selfRecipientId = account.getSelfRecipientId(); if (members != null && members.contains(selfRecipientId)) { @@ -363,7 +369,8 @@ public class GroupHelper { } public SendGroupMessageResults quitGroup( - final GroupId groupId, final Set newAdmins + final GroupId groupId, + final Set newAdmins ) throws IOException, LastGroupAdminException, NotAGroupMemberException, GroupNotFoundException { var group = getGroupForUpdating(groupId); if (group instanceof GroupInfoV1) { @@ -396,9 +403,7 @@ public class GroupHelper { context.getJobExecutor().enqueueJob(new SyncStorageJob()); } - public SendGroupMessageResults sendGroupInfoRequest( - GroupIdV1 groupId, RecipientId recipientId - ) throws IOException { + public SendGroupMessageResults sendGroupInfoRequest(GroupIdV1 groupId, RecipientId recipientId) throws IOException { var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.REQUEST_INFO).withId(groupId.serialize()); var messageBuilder = SignalServiceDataMessage.newBuilder().asGroupMessage(group.build()); @@ -408,7 +413,8 @@ public class GroupHelper { } public SendGroupMessageResults sendGroupInfoMessage( - GroupIdV1 groupId, RecipientId recipientId + GroupIdV1 groupId, + RecipientId recipientId ) throws IOException, NotAGroupMemberException, GroupNotFoundException, AttachmentInvalidException { GroupInfoV1 g; var group = getGroupForUpdating(groupId); @@ -480,7 +486,9 @@ public class GroupHelper { } private void retrieveGroupV2Avatar( - GroupSecretParams groupSecretParams, String cdnKey, OutputStream outputStream + GroupSecretParams groupSecretParams, + String cdnKey, + OutputStream outputStream ) throws IOException { var groupOperations = dependencies.getGroupsV2Operations().forGroup(groupSecretParams); @@ -583,7 +591,10 @@ public class GroupHelper { } private SendGroupMessageResults updateGroupV1( - final GroupInfoV1 gv1, final String name, final Set members, final byte[] avatarFile + final GroupInfoV1 gv1, + final String name, + final Set members, + final byte[] avatarFile ) throws IOException, AttachmentInvalidException { updateGroupV1Details(gv1, name, members, avatarFile); @@ -596,7 +607,10 @@ public class GroupHelper { } private void updateGroupV1Details( - final GroupInfoV1 g, final String name, final Collection members, final byte[] avatarFile + final GroupInfoV1 g, + final String name, + final Collection members, + final byte[] avatarFile ) throws IOException { if (name != null) { g.name = name; @@ -615,7 +629,8 @@ public class GroupHelper { * Change the expiration timer for a group */ private void setExpirationTimer( - GroupInfoV1 groupInfoV1, int messageExpirationTimer + GroupInfoV1 groupInfoV1, + int messageExpirationTimer ) throws NotAGroupMemberException, GroupNotFoundException, IOException, GroupSendingNotAllowedException { groupInfoV1.messageExpirationTime = messageExpirationTimer; account.getGroupStore().updateGroup(groupInfoV1); @@ -828,7 +843,8 @@ public class GroupHelper { } private SendGroupMessageResults quitGroupV2( - final GroupInfoV2 groupInfoV2, final Set newAdmins + final GroupInfoV2 groupInfoV2, + final Set newAdmins ) throws LastGroupAdminException, IOException { final var currentAdmins = groupInfoV2.getAdminMembers(); newAdmins.removeAll(currentAdmins); @@ -882,7 +898,9 @@ public class GroupHelper { } private SendGroupMessageResults sendUpdateGroupV2Message( - GroupInfoV2 group, DecryptedGroup newDecryptedGroup, GroupChange groupChange + GroupInfoV2 group, + DecryptedGroup newDecryptedGroup, + GroupChange groupChange ) throws IOException { final var selfRecipientId = account.getSelfRecipientId(); final var members = group.getMembersIncludingPendingWithout(selfRecipientId); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java index 6c6acd65..05bc876a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java @@ -94,7 +94,8 @@ class GroupV2Helper { } DecryptedGroupJoinInfo getDecryptedGroupJoinInfo( - GroupMasterKey groupMasterKey, GroupLinkPassword password + GroupMasterKey groupMasterKey, + GroupLinkPassword password ) throws IOException, GroupLinkNotActiveException { var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); @@ -105,7 +106,9 @@ class GroupV2Helper { } GroupHistoryPage getDecryptedGroupHistoryPage( - final GroupSecretParams groupSecretParams, int fromRevision, long sendEndorsementsExpirationMs + final GroupSecretParams groupSecretParams, + int fromRevision, + long sendEndorsementsExpirationMs ) throws NotAGroupMemberException { try { final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams); @@ -138,9 +141,7 @@ class GroupV2Helper { return partialDecryptedGroup.revision; } - Pair createGroup( - String name, Set members, byte[] avatarFile - ) { + Pair createGroup(String name, Set members, byte[] avatarFile) { final var newGroup = buildNewGroup(name, members, avatarFile); if (newGroup == null) { return null; @@ -170,9 +171,7 @@ class GroupV2Helper { return new Pair<>(g, response); } - private GroupsV2Operations.NewGroup buildNewGroup( - String name, Set members, byte[] avatar - ) { + private GroupsV2Operations.NewGroup buildNewGroup(String name, Set members, byte[] avatar) { final var profileKeyCredential = context.getProfileHelper() .getExpiringProfileKeyCredential(context.getAccount().getSelfRecipientId()); if (profileKeyCredential == null) { @@ -202,7 +201,10 @@ class GroupV2Helper { } Pair updateGroup( - GroupInfoV2 groupInfoV2, String name, String description, byte[] avatarFile + GroupInfoV2 groupInfoV2, + String name, + String description, + byte[] avatarFile ) throws IOException { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); var groupOperations = dependencies.getGroupsV2Operations().forGroup(groupSecretParams); @@ -225,7 +227,8 @@ class GroupV2Helper { } Pair addMembers( - GroupInfoV2 groupInfoV2, Set newMembers + GroupInfoV2 groupInfoV2, + Set newMembers ) throws IOException { GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -251,7 +254,8 @@ class GroupV2Helper { } Pair leaveGroup( - GroupInfoV2 groupInfoV2, Set membersToMakeAdmin + GroupInfoV2 groupInfoV2, + Set membersToMakeAdmin ) throws IOException { var pendingMembersList = groupInfoV2.getGroup().pendingMembers; final var selfAci = getSelfAci(); @@ -271,7 +275,8 @@ class GroupV2Helper { } Pair removeMembers( - GroupInfoV2 groupInfoV2, Set members + GroupInfoV2 groupInfoV2, + Set members ) throws IOException { final var memberUuids = members.stream() .map(context.getRecipientHelper()::resolveSignalServiceAddress) @@ -283,7 +288,8 @@ class GroupV2Helper { } Pair approveJoinRequestMembers( - GroupInfoV2 groupInfoV2, Set members + GroupInfoV2 groupInfoV2, + Set members ) throws IOException { final var memberUuids = members.stream() .map(context.getRecipientHelper()::resolveSignalServiceAddress) @@ -294,7 +300,8 @@ class GroupV2Helper { } Pair refuseJoinRequestMembers( - GroupInfoV2 groupInfoV2, Set members + GroupInfoV2 groupInfoV2, + Set members ) throws IOException { final var memberUuids = members.stream() .map(context.getRecipientHelper()::resolveSignalServiceAddress) @@ -304,7 +311,8 @@ class GroupV2Helper { } Pair revokeInvitedMembers( - GroupInfoV2 groupInfoV2, Set members + GroupInfoV2 groupInfoV2, + Set members ) throws IOException { var pendingMembersList = groupInfoV2.getGroup().pendingMembers; final var memberUuids = members.stream() @@ -318,7 +326,8 @@ class GroupV2Helper { } Pair banMembers( - GroupInfoV2 groupInfoV2, Set block + GroupInfoV2 groupInfoV2, + Set block ) throws IOException { GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -336,7 +345,8 @@ class GroupV2Helper { } Pair unbanMembers( - GroupInfoV2 groupInfoV2, Set block + GroupInfoV2 groupInfoV2, + Set block ) throws IOException { GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -359,7 +369,8 @@ class GroupV2Helper { } Pair setGroupLinkState( - GroupInfoV2 groupInfoV2, GroupLinkState state + GroupInfoV2 groupInfoV2, + GroupLinkState state ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -374,7 +385,8 @@ class GroupV2Helper { } Pair setEditDetailsPermission( - GroupInfoV2 groupInfoV2, GroupPermission permission + GroupInfoV2 groupInfoV2, + GroupPermission permission ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -384,7 +396,8 @@ class GroupV2Helper { } Pair setAddMemberPermission( - GroupInfoV2 groupInfoV2, GroupPermission permission + GroupInfoV2 groupInfoV2, + GroupPermission permission ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); @@ -468,7 +481,9 @@ class GroupV2Helper { } Pair setMemberAdmin( - GroupInfoV2 groupInfoV2, RecipientId recipientId, boolean admin + GroupInfoV2 groupInfoV2, + RecipientId recipientId, + boolean admin ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final var address = context.getRecipientHelper().resolveSignalServiceAddress(recipientId); @@ -482,7 +497,8 @@ class GroupV2Helper { } Pair setMessageExpirationTimer( - GroupInfoV2 groupInfoV2, int messageExpirationTimer + GroupInfoV2 groupInfoV2, + int messageExpirationTimer ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final var change = groupOperations.createModifyGroupTimerChange(messageExpirationTimer); @@ -490,7 +506,8 @@ class GroupV2Helper { } Pair setIsAnnouncementGroup( - GroupInfoV2 groupInfoV2, boolean isAnnouncementGroup + GroupInfoV2 groupInfoV2, + boolean isAnnouncementGroup ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final var change = groupOperations.createAnnouncementGroupChange(isAnnouncementGroup); @@ -518,7 +535,8 @@ class GroupV2Helper { } private Pair revokeInvites( - GroupInfoV2 groupInfoV2, Set pendingMembers + GroupInfoV2 groupInfoV2, + Set pendingMembers ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); final var uuidCipherTexts = pendingMembers.stream().map(member -> { @@ -532,28 +550,32 @@ class GroupV2Helper { } private Pair approveJoinRequest( - GroupInfoV2 groupInfoV2, Set uuids + GroupInfoV2 groupInfoV2, + Set uuids ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); return commitChange(groupInfoV2, groupOperations.createApproveGroupJoinRequest(uuids)); } private Pair refuseJoinRequest( - GroupInfoV2 groupInfoV2, Set serviceIds + GroupInfoV2 groupInfoV2, + Set serviceIds ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); return commitChange(groupInfoV2, groupOperations.createRefuseGroupJoinRequest(serviceIds, false, List.of())); } private Pair ejectMembers( - GroupInfoV2 groupInfoV2, Set members + GroupInfoV2 groupInfoV2, + Set members ) throws IOException { final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2); return commitChange(groupInfoV2, groupOperations.createRemoveMembersChange(members, false, List.of())); } private Pair commitChange( - GroupInfoV2 groupInfoV2, GroupChange.Actions.Builder change + GroupInfoV2 groupInfoV2, + GroupChange.Actions.Builder change ) throws IOException { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey()); final var groupOperations = dependencies.getGroupsV2Operations().forGroup(groupSecretParams); @@ -676,7 +698,8 @@ class GroupV2Helper { } private GroupsV2AuthorizationString getAuthorizationString( - final GroupSecretParams groupSecretParams, final long todaySeconds + final GroupSecretParams groupSecretParams, + final long todaySeconds ) throws VerificationFailedException { var authCredentialResponse = groupApiCredentials.get(todaySeconds); final var aci = getSelfAci(); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IdentityHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/IdentityHelper.java index cc71e15c..82d44fde 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IdentityHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IdentityHelper.java @@ -66,9 +66,7 @@ public class IdentityHelper { return fingerprint == null ? null : fingerprint.getScannableFingerprint(); } - private Fingerprint computeSafetyNumberFingerprint( - final ServiceId serviceId, final IdentityKey theirIdentityKey - ) { + private Fingerprint computeSafetyNumberFingerprint(final ServiceId serviceId, final IdentityKey theirIdentityKey) { if (!serviceId.isUnknown()) { return Utils.computeSafetyNumberForUuid(account.getAci(), account.getAciIdentityKeyPair().getPublicKey(), @@ -89,7 +87,9 @@ public class IdentityHelper { } private boolean trustIdentity( - RecipientId recipientId, BiFunction verifier, TrustLevel trustLevel + RecipientId recipientId, + BiFunction verifier, + TrustLevel trustLevel ) { final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId); final var serviceId = address.serviceId().orElse(null); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 294b17f6..376a671b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -190,7 +190,9 @@ public final class IncomingMessageHandler { } private SignalServiceContent validate( - Envelope envelope, SignalServiceCipherResult cipherResult, long serverDeliveredTimestamp + Envelope envelope, + SignalServiceCipherResult cipherResult, + long serverDeliveredTimestamp ) throws ProtocolInvalidKeyException, ProtocolInvalidMessageException, UnsupportedDataMessageException, InvalidMessageStructureException { final var content = cipherResult.getContent(); final var envelopeMetadata = cipherResult.getMetadata(); @@ -280,7 +282,9 @@ public final class IncomingMessageHandler { } public List handleMessage( - SignalServiceEnvelope envelope, SignalServiceContent content, ReceiveConfig receiveConfig + SignalServiceEnvelope envelope, + SignalServiceContent content, + ReceiveConfig receiveConfig ) { var actions = new ArrayList(); final var senderDeviceAddress = getSender(envelope, content); @@ -381,7 +385,8 @@ public final class IncomingMessageHandler { } private boolean handlePniSignatureMessage( - final SignalServicePniSignatureMessage message, final SignalServiceAddress senderAddress + final SignalServicePniSignatureMessage message, + final SignalServiceAddress senderAddress ) { final var aci = senderAddress.getServiceId(); final var aciIdentity = account.getIdentityKeyStore().getIdentityInfo(aci); @@ -865,7 +870,9 @@ public final class IncomingMessageHandler { } private List handleSignalServiceStoryMessage( - SignalServiceStoryMessage message, RecipientId source, boolean ignoreAttachments + SignalServiceStoryMessage message, + RecipientId source, + boolean ignoreAttachments ) { var actions = new ArrayList(); if (message.getGroupContext().isPresent()) { 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 7669b383..a95635cf 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 @@ -21,9 +21,7 @@ public class PinHelper { this.secureValueRecoveries = secureValueRecoveries; } - public void setRegistrationLockPin( - String pin, MasterKey masterKey - ) throws IOException { + public void setRegistrationLockPin(String pin, MasterKey masterKey) throws IOException { IOException exception = null; for (final var secureValueRecovery : secureValueRecoveries) { try { @@ -82,7 +80,8 @@ public class PinHelper { } public SecureValueRecovery.RestoreResponse.Success getRegistrationLockData( - String pin, LockedException lockedException + String pin, + LockedException lockedException ) throws IOException, IncorrectPinException { var svr2Credentials = lockedException.getSvr2Credentials(); if (svr2Credentials != null) { @@ -103,7 +102,9 @@ public class PinHelper { } public SecureValueRecovery.RestoreResponse.Success getRegistrationLockData( - SecureValueRecovery secureValueRecovery, AuthCredentials authCredentials, String pin + SecureValueRecovery secureValueRecovery, + AuthCredentials authCredentials, + String pin ) throws IOException, IncorrectPinException { final var restoreResponse = secureValueRecovery.restoreDataPreRegistration(authCredentials, null, pin); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java index 864ef656..339425c1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java @@ -30,9 +30,7 @@ public class PreKeyHelper { private final SignalAccount account; private final SignalDependencies dependencies; - public PreKeyHelper( - final SignalAccount account, final SignalDependencies dependencies - ) { + public PreKeyHelper(final SignalAccount account, final SignalDependencies dependencies) { this.account = account; this.dependencies = dependencies; } @@ -79,7 +77,8 @@ public class PreKeyHelper { } private boolean refreshPreKeysIfNecessary( - final ServiceIdType serviceIdType, final IdentityKeyPair identityKeyPair + final ServiceIdType serviceIdType, + final IdentityKeyPair identityKeyPair ) throws IOException { OneTimePreKeyCounts preKeyCounts; try { @@ -221,7 +220,8 @@ public class PreKeyHelper { } private List generateKyberPreKeys( - ServiceIdType serviceIdType, final IdentityKeyPair identityKeyPair + ServiceIdType serviceIdType, + final IdentityKeyPair identityKeyPair ) { final var accountData = account.getAccountData(serviceIdType); final var offset = accountData.getPreKeyMetadata().getNextKyberPreKeyId(); @@ -246,7 +246,9 @@ public class PreKeyHelper { } private KyberPreKeyRecord generateLastResortKyberPreKey( - ServiceIdType serviceIdType, IdentityKeyPair identityKeyPair, final int offset + ServiceIdType serviceIdType, + IdentityKeyPair identityKeyPair, + final int offset ) { final var accountData = account.getAccountData(serviceIdType); final var signedPreKeyId = accountData.getPreKeyMetadata().getNextKyberPreKeyId() + offset; diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java index 03d277f9..184eac6b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java @@ -271,7 +271,9 @@ public final class ProfileHelper { } private Profile decryptProfileAndDownloadAvatar( - final RecipientId recipientId, final ProfileKey profileKey, final SignalServiceProfile encryptedProfile + final RecipientId recipientId, + final ProfileKey profileKey, + final SignalServiceProfile encryptedProfile ) { final var avatarPath = encryptedProfile.getAvatar(); downloadProfileAvatar(recipientId, avatarPath, profileKey); @@ -280,7 +282,9 @@ public final class ProfileHelper { } public void downloadProfileAvatar( - final RecipientId recipientId, final String avatarPath, final ProfileKey profileKey + final RecipientId recipientId, + final String avatarPath, + final ProfileKey profileKey ) { var profile = account.getProfileStore().getProfile(recipientId); if (profile == null || !Objects.equals(avatarPath, profile.getAvatarUrlPath())) { @@ -308,7 +312,8 @@ public final class ProfileHelper { } private Single retrieveProfile( - RecipientId recipientId, SignalServiceProfile.RequestType requestType + RecipientId recipientId, + SignalServiceProfile.RequestType requestType ) { var unidentifiedAccess = getUnidentifiedAccess(recipientId); var profileKey = Optional.ofNullable(account.getProfileStore().getProfileKey(recipientId)); @@ -408,9 +413,7 @@ public final class ProfileHelper { }); } - private void downloadProfileAvatar( - RecipientAddress address, String avatarPath, ProfileKey profileKey - ) { + private void downloadProfileAvatar(RecipientAddress address, String avatarPath, ProfileKey profileKey) { if (avatarPath == null) { try { context.getAvatarStore().deleteProfileAvatar(address); @@ -430,7 +433,9 @@ public final class ProfileHelper { } private void retrieveProfileAvatar( - String avatarPath, ProfileKey profileKey, OutputStream outputStream + String avatarPath, + ProfileKey profileKey, + OutputStream outputStream ) throws IOException { var tmpFile = IOUtils.createTempFile(); try (var input = dependencies.getMessageReceiver() diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java index baa0d583..ab75bd16 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java @@ -83,7 +83,10 @@ public class ReceiveHelper { } public void receiveMessages( - Duration timeout, boolean returnOnTimeout, Integer maxMessages, Manager.ReceiveMessageHandler handler + Duration timeout, + boolean returnOnTimeout, + Integer maxMessages, + Manager.ReceiveMessageHandler handler ) throws IOException { account.setNeedsToRetryFailedMessages(true); hasCaughtUpWithOldMessages = false; @@ -264,7 +267,8 @@ public class ReceiveHelper { } private List retryFailedReceivedMessage( - final Manager.ReceiveMessageHandler handler, final CachedMessage cachedMessage + final Manager.ReceiveMessageHandler handler, + final CachedMessage cachedMessage ) { var envelope = cachedMessage.loadEnvelope(); if (envelope == null) { 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 24b3eb5f..6225b982 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 @@ -97,7 +97,8 @@ public class RecipientHelper { } public RecipientId resolveRecipientByUsernameOrLink( - String username, boolean forceRefresh + String username, + boolean forceRefresh ) throws UnregisteredRecipientException { final Username finalUsername; try { @@ -180,7 +181,8 @@ public class RecipientHelper { } private Map getRegisteredUsers( - final Set numbers, final boolean isPartialRefresh + final Set numbers, + final boolean isPartialRefresh ) throws IOException { Map registeredUsers = getRegisteredUsersV2(numbers, isPartialRefresh); @@ -211,7 +213,8 @@ public class RecipientHelper { } private Map getRegisteredUsersV2( - final Set numbers, boolean isPartialRefresh + final Set numbers, + boolean isPartialRefresh ) throws IOException { final var previousNumbers = isPartialRefresh ? Set.of() : account.getCdsiStore().getAllNumbers(); final var newNumbers = new HashSet<>(numbers) {{ 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 0622b4a8..2fb55003 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 @@ -125,7 +125,8 @@ public class SendHelper { } public SendMessageResult sendReceiptMessage( - final SignalServiceReceiptMessage receiptMessage, final RecipientId recipientId + final SignalServiceReceiptMessage receiptMessage, + final RecipientId recipientId ) { final var messageSendLogStore = account.getMessageSendLogStore(); final var result = handleSendMessage(recipientId, @@ -157,7 +158,9 @@ public class SendHelper { } public SendMessageResult sendRetryReceipt( - DecryptionErrorMessage errorMessage, RecipientId recipientId, Optional groupId + DecryptionErrorMessage errorMessage, + RecipientId recipientId, + Optional groupId ) { logger.debug("Sending retry receipt for {} to {}, device: {}", errorMessage.getTimestamp(), @@ -183,7 +186,8 @@ public class SendHelper { } public SendMessageResult sendSelfMessage( - SignalServiceDataMessage.Builder messageBuilder, Optional editTargetTimestamp + SignalServiceDataMessage.Builder messageBuilder, + Optional editTargetTimestamp ) { final var recipientId = account.getSelfRecipientId(); final var contact = account.getContactStore().getContact(recipientId); @@ -214,9 +218,7 @@ public class SendHelper { } } - public SendMessageResult sendTypingMessage( - SignalServiceTypingMessage message, RecipientId recipientId - ) { + public SendMessageResult sendTypingMessage(SignalServiceTypingMessage message, RecipientId recipientId) { final var result = handleSendMessage(recipientId, (messageSender, address, unidentifiedAccess, includePniSignature) -> messageSender.sendTyping(List.of( address), List.of(unidentifiedAccess), message, null).getFirst()); @@ -225,7 +227,8 @@ public class SendHelper { } public List sendGroupTypingMessage( - SignalServiceTypingMessage message, GroupId groupId + SignalServiceTypingMessage message, + GroupId groupId ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { final var g = getGroupForSending(groupId); if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) { @@ -238,7 +241,9 @@ public class SendHelper { } public SendMessageResult resendMessage( - final RecipientId recipientId, final long timestamp, final MessageSendLogEntry messageSendLogEntry + final RecipientId recipientId, + final long timestamp, + final MessageSendLogEntry messageSendLogEntry ) { logger.trace("Resending message {} to {}", timestamp, recipientId); if (messageSendLogEntry.groupId().isEmpty()) { @@ -552,7 +557,9 @@ public class SendHelper { } private List sendGroupMessageInternalWithLegacy( - final LegacySenderHandler sender, final Set recipientIds, final boolean isRecipientUpdate + final LegacySenderHandler sender, + final Set recipientIds, + final boolean isRecipientUpdate ) throws IOException { final var recipientIdList = new ArrayList<>(recipientIds); final var addresses = recipientIdList.stream() @@ -644,7 +651,9 @@ public class SendHelper { } private SendMessageResult sendMessage( - SignalServiceDataMessage message, RecipientId recipientId, Optional editTargetTimestamp + SignalServiceDataMessage message, + RecipientId recipientId, + Optional editTargetTimestamp ) { final var messageSendLogStore = account.getMessageSendLogStore(); final var urgent = true; diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/StickerHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/StickerHelper.java index 4a0d25fe..b6d90ff9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/StickerHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/StickerHelper.java @@ -30,7 +30,9 @@ public class StickerHelper { } public StickerPack addOrUpdateStickerPack( - final StickerPackId stickerPackId, final byte[] stickerPackKey, final boolean installed + final StickerPackId stickerPackId, + final byte[] stickerPackKey, + final boolean installed ) { final var sticker = account.getStickerStore().getStickerPack(stickerPackId); if (sticker != null) { @@ -50,7 +52,8 @@ public class StickerHelper { } public JsonStickerPack getOrRetrieveStickerPack( - StickerPackId packId, byte[] packKey + StickerPackId packId, + byte[] packKey ) throws InvalidStickerException { try { retrieveStickerPack(packId, packKey); 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 c18e8df3..8c6b3c52 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 @@ -218,7 +218,9 @@ public class StorageHelper { } private boolean writeToStorage( - final StorageKey storageKey, final SignalStorageManifest remoteManifest, final boolean needsForcePush + final StorageKey storageKey, + final SignalStorageManifest remoteManifest, + final boolean needsForcePush ) throws IOException, RetryLaterException { final WriteOperationResult remoteWriteOperation; try (final var connection = account.getAccountDatabase().getConnection()) { @@ -408,7 +410,8 @@ public class StorageHelper { } private List getSignalStorageRecords( - final StorageKey storageKey, final List storageIds + final StorageKey storageKey, + final List storageIds ) throws IOException { List records; try { @@ -430,7 +433,8 @@ public class StorageHelper { } private List buildLocalStorageRecords( - final Connection connection, final List storageIds + final Connection connection, + final List storageIds ) throws SQLException { final var records = new ArrayList(); for (final var storageId : storageIds) { @@ -443,7 +447,8 @@ public class StorageHelper { } private SignalStorageRecord buildLocalStorageRecord( - Connection connection, StorageId storageId + Connection connection, + StorageId storageId ) throws SQLException { return switch (ManifestRecord.Identifier.Type.fromValue(storageId.getType())) { case ManifestRecord.Identifier.Type.CONTACT -> { @@ -484,7 +489,8 @@ public class StorageHelper { * exclusive to the local data set. */ private static IdDifferenceResult findIdDifference( - Collection remoteIds, Collection localIds + Collection remoteIds, + Collection localIds ) { final var base64Encoder = Base64.getEncoder(); final var remoteByRawId = remoteIds.stream() @@ -520,7 +526,8 @@ public class StorageHelper { } private List processKnownRecords( - final Connection connection, List records + final Connection connection, + List records ) throws SQLException { final var unknownRecords = new ArrayList(); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index 56665fda..70427f00 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -217,7 +217,9 @@ public class SyncHelper { @NotNull private DeviceContact getDeviceContact( - final RecipientAddress address, final RecipientId recipientId, final Contact contact + final RecipientAddress address, + final RecipientId recipientId, + final Contact contact ) throws IOException { var currentIdentity = address.serviceId().isEmpty() ? null @@ -262,7 +264,9 @@ public class SyncHelper { } public SendMessageResult sendVerifiedMessage( - SignalServiceAddress destination, IdentityKey identityKey, TrustLevel trustLevel + SignalServiceAddress destination, + IdentityKey identityKey, + TrustLevel trustLevel ) { var verifiedMessage = new VerifiedMessage(destination, identityKey, @@ -278,7 +282,8 @@ public class SyncHelper { } public SendMessageResult sendStickerOperationsMessage( - List installStickers, List removeStickers + List installStickers, + List removeStickers ) { var installStickerMessages = installStickers.stream().map(s -> getStickerPackOperationMessage(s, true)); var removeStickerMessages = removeStickers.stream().map(s -> getStickerPackOperationMessage(s, false)); @@ -288,7 +293,8 @@ public class SyncHelper { } private static StickerPackOperationMessage getStickerPackOperationMessage( - final StickerPack s, final boolean installed + final StickerPack s, + final boolean installed ) { return new StickerPackOperationMessage(s.packId().serialize(), s.packKey(), @@ -414,15 +420,14 @@ public class SyncHelper { } } - public SendMessageResult sendMessageRequestResponse( - final MessageRequestResponse.Type type, final GroupId groupId - ) { + public SendMessageResult sendMessageRequestResponse(final MessageRequestResponse.Type type, final GroupId groupId) { final var response = MessageRequestResponseMessage.forGroup(groupId.serialize(), localToRemoteType(type)); return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response)); } public SendMessageResult sendMessageRequestResponse( - final MessageRequestResponse.Type type, final RecipientId recipientId + final MessageRequestResponse.Type type, + final RecipientId recipientId ) { final var address = account.getRecipientAddressResolver().resolveRecipientAddress(recipientId); if (address.serviceId().isEmpty()) { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java index 3899179c..fb39789e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java @@ -158,7 +158,8 @@ public class UnidentifiedAccessHelper { } private static byte[] getTargetUnidentifiedAccessKey( - final Profile targetProfile, final ProfileKey theirProfileKey + final Profile targetProfile, + final ProfileKey theirProfileKey ) { return switch (targetProfile.getUnidentifiedAccessMode()) { case ENABLED -> theirProfileKey == null ? null : UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey); 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 27bda583..2f2ba3db 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 @@ -417,7 +417,9 @@ public class ManagerImpl implements Manager { @Override public void startChangeNumber( - String newNumber, boolean voiceVerification, String captcha + String newNumber, + boolean voiceVerification, + String captcha ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethodNotAvailableException { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); @@ -427,7 +429,9 @@ public class ManagerImpl implements Manager { @Override public void finishChangeNumber( - String newNumber, String verificationCode, String pin + String newNumber, + String verificationCode, + String pin ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); @@ -447,7 +451,8 @@ public class ManagerImpl implements Manager { @Override public void submitRateLimitRecaptchaChallenge( - String challenge, String captcha + String challenge, + String captcha ) throws IOException, CaptchaRejectedException { captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); @@ -527,7 +532,8 @@ public class ManagerImpl implements Manager { @Override public SendGroupMessageResults quitGroup( - GroupId groupId, Set groupAdmins + GroupId groupId, + Set groupAdmins ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException, UnregisteredRecipientException { final var newAdmins = context.getRecipientHelper().resolveRecipients(groupAdmins); return context.getGroupHelper().quitGroup(groupId, newAdmins); @@ -545,7 +551,9 @@ public class ManagerImpl implements Manager { @Override public Pair createGroup( - String name, Set members, String avatarFile + String name, + Set members, + String avatarFile ) throws IOException, AttachmentInvalidException, UnregisteredRecipientException { return context.getGroupHelper() .createGroup(name, @@ -555,7 +563,8 @@ public class ManagerImpl implements Manager { @Override public SendGroupMessageResults updateGroup( - final GroupId groupId, final UpdateGroup updateGroup + final GroupId groupId, + final UpdateGroup updateGroup ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException, UnregisteredRecipientException { return context.getGroupHelper() .updateGroup(groupId, @@ -596,7 +605,9 @@ public class ManagerImpl implements Manager { } private SendMessageResults sendMessage( - SignalServiceDataMessage.Builder messageBuilder, Set recipients, boolean notifySelf + SignalServiceDataMessage.Builder messageBuilder, + Set recipients, + boolean notifySelf ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { return sendMessage(messageBuilder, recipients, notifySelf, Optional.empty()); } @@ -644,7 +655,8 @@ public class ManagerImpl implements Manager { } private SendMessageResults sendTypingMessage( - SignalServiceTypingMessage.Action action, Set recipients + SignalServiceTypingMessage.Action action, + Set recipients ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var results = new HashMap>(); final var timestamp = System.currentTimeMillis(); @@ -671,15 +683,14 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendTypingMessage( - TypingAction action, Set recipients + TypingAction action, + Set recipients ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { return sendTypingMessage(action.toSignalService(), recipients); } @Override - public SendMessageResults sendReadReceipt( - RecipientIdentifier.Single sender, List messageIds - ) { + public SendMessageResults sendReadReceipt(RecipientIdentifier.Single sender, List messageIds) { final var timestamp = System.currentTimeMillis(); var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, @@ -689,9 +700,7 @@ public class ManagerImpl implements Manager { } @Override - public SendMessageResults sendViewedReceipt( - RecipientIdentifier.Single sender, List messageIds - ) { + public SendMessageResults sendViewedReceipt(RecipientIdentifier.Single sender, List messageIds) { final var timestamp = System.currentTimeMillis(); var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED, messageIds, @@ -724,7 +733,9 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendMessage( - Message message, Set recipients, boolean notifySelf + Message message, + Set recipients, + boolean notifySelf ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException { final var selfProfile = context.getProfileHelper().getSelfProfile(); if (selfProfile == null || selfProfile.getDisplayName().isEmpty()) { @@ -738,7 +749,9 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendEditMessage( - Message message, Set recipients, long editTargetTimestamp + Message message, + Set recipients, + long editTargetTimestamp ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException { final var messageBuilder = SignalServiceDataMessage.newBuilder(); applyMessage(messageBuilder, message); @@ -746,7 +759,8 @@ public class ManagerImpl implements Manager { } private void applyMessage( - final SignalServiceDataMessage.Builder messageBuilder, final Message message + final SignalServiceDataMessage.Builder messageBuilder, + final Message message ) throws AttachmentInvalidException, IOException, UnregisteredRecipientException, InvalidStickerException { final var additionalAttachments = new ArrayList(); if (message.messageText().length() > ServiceConfig.MAX_MESSAGE_BODY_SIZE) { @@ -863,7 +877,8 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendRemoteDeleteMessage( - long targetSentTimestamp, Set recipients + long targetSentTimestamp, + Set recipients ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var delete = new SignalServiceDataMessage.RemoteDelete(targetSentTimestamp); final var messageBuilder = SignalServiceDataMessage.newBuilder().withRemoteDelete(delete); @@ -915,7 +930,9 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendPaymentNotificationMessage( - byte[] receipt, String note, RecipientIdentifier.Single recipient + byte[] receipt, + String note, + RecipientIdentifier.Single recipient ) throws IOException { final var paymentNotification = new SignalServiceDataMessage.PaymentNotification(receipt, note); final var payment = new SignalServiceDataMessage.Payment(paymentNotification, null); @@ -958,7 +975,8 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendMessageRequestResponse( - final MessageRequestResponse.Type type, final Set recipients + final MessageRequestResponse.Type type, + final Set recipients ) { var results = new HashMap>(); for (final var recipient : recipients) { @@ -1021,7 +1039,9 @@ public class ManagerImpl implements Manager { @Override public void setContactName( - RecipientIdentifier.Single recipient, String givenName, final String familyName + RecipientIdentifier.Single recipient, + String givenName, + final String familyName ) throws NotPrimaryDeviceException, UnregisteredRecipientException { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); @@ -1033,7 +1053,8 @@ public class ManagerImpl implements Manager { @Override public void setContactsBlocked( - Collection recipients, boolean blocked + Collection recipients, + boolean blocked ) throws IOException, UnregisteredRecipientException { if (recipients.isEmpty()) { return; @@ -1067,7 +1088,8 @@ public class ManagerImpl implements Manager { @Override public void setGroupsBlocked( - final Collection groupIds, final boolean blocked + final Collection groupIds, + final boolean blocked ) throws GroupNotFoundException, IOException { if (groupIds.isEmpty()) { return; @@ -1093,7 +1115,8 @@ public class ManagerImpl implements Manager { @Override public void setExpirationTimer( - RecipientIdentifier.Single recipient, int messageExpirationTimer + RecipientIdentifier.Single recipient, + int messageExpirationTimer ) throws IOException, UnregisteredRecipientException { var recipientId = context.getRecipientHelper().resolveRecipient(recipient); context.getContactHelper().setExpirationTimer(recipientId, messageExpirationTimer); @@ -1255,7 +1278,9 @@ public class ManagerImpl implements Manager { @Override public void receiveMessages( - Optional timeout, Optional maxMessages, ReceiveMessageHandler handler + Optional timeout, + Optional maxMessages, + ReceiveMessageHandler handler ) throws IOException, AlreadyReceivingException { receiveMessages(timeout.orElse(Duration.ofMinutes(1)), timeout.isPresent(), maxMessages.orElse(null), handler); } @@ -1275,7 +1300,10 @@ public class ManagerImpl implements Manager { } private void receiveMessages( - Duration timeout, boolean returnOnTimeout, Integer maxMessages, ReceiveMessageHandler handler + Duration timeout, + boolean returnOnTimeout, + Integer maxMessages, + ReceiveMessageHandler handler ) throws IOException, AlreadyReceivingException { synchronized (messageHandlers) { if (isReceiving()) { @@ -1431,7 +1459,8 @@ public class ManagerImpl implements Manager { @Override public boolean trustIdentityVerified( - RecipientIdentifier.Single recipient, IdentityVerificationCode verificationCode + RecipientIdentifier.Single recipient, + IdentityVerificationCode verificationCode ) throws UnregisteredRecipientException { return switch (verificationCode) { case IdentityVerificationCode.Fingerprint fingerprint -> trustIdentity(recipient, @@ -1450,7 +1479,8 @@ public class ManagerImpl implements Manager { } private boolean trustIdentity( - RecipientIdentifier.Single recipient, Function trustMethod + RecipientIdentifier.Single recipient, + Function trustMethod ) throws UnregisteredRecipientException { final var recipientId = context.getRecipientHelper().resolveRecipient(recipient); final var updated = trustMethod.apply(recipientId); 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 543d9a99..5faf1371 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 @@ -105,7 +105,9 @@ public class RegistrationManagerImpl implements RegistrationManager { @Override public void register( - boolean voiceVerification, String captcha, final boolean forceRegister + boolean voiceVerification, + String captcha, + final boolean forceRegister ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException { if (account.isRegistered() && account.getServiceEnvironment() != null @@ -145,7 +147,8 @@ public class RegistrationManagerImpl implements RegistrationManager { @Override public void verifyAccount( - String verificationCode, String pin + String verificationCode, + String pin ) throws IOException, PinLockedException, IncorrectPinException { if (account.isRegistered()) { throw new IOException("Account is already registered"); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java index 63ba5a37..9e1c6303 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java @@ -611,7 +611,8 @@ public class AccountDatabase extends Database { } private static void createUuidMappingTable( - final Connection connection, final Statement statement + final Connection connection, + final Statement statement ) throws SQLException { statement.executeUpdate(""" CREATE TABLE tmp_mapping_table ( diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/AttachmentStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/AttachmentStore.java index 5e734174..d25f1a72 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/AttachmentStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/AttachmentStore.java @@ -22,7 +22,8 @@ public class AttachmentStore { } public void storeAttachmentPreview( - final SignalServiceAttachmentPointer pointer, final AttachmentStorer storer + final SignalServiceAttachmentPointer pointer, + final AttachmentStorer storer ) throws IOException { storeAttachment(getAttachmentPreviewFile(pointer.getRemoteId(), pointer.getFileName(), @@ -30,7 +31,8 @@ public class AttachmentStore { } public void storeAttachment( - final SignalServiceAttachmentPointer pointer, final AttachmentStorer storer + final SignalServiceAttachmentPointer pointer, + final AttachmentStorer storer ) throws IOException { storeAttachment(getAttachmentFile(pointer), storer); } @@ -54,22 +56,24 @@ public class AttachmentStore { } private File getAttachmentPreviewFile( - SignalServiceAttachmentRemoteId attachmentId, Optional filename, Optional contentType + SignalServiceAttachmentRemoteId attachmentId, + Optional filename, + Optional contentType ) { final var extension = getAttachmentExtension(filename, contentType); return new File(attachmentsPath, attachmentId.toString() + extension + ".preview"); } private File getAttachmentFile( - SignalServiceAttachmentRemoteId attachmentId, Optional filename, Optional contentType + SignalServiceAttachmentRemoteId attachmentId, + Optional filename, + Optional contentType ) { final var extension = getAttachmentExtension(filename, contentType); return new File(attachmentsPath, attachmentId.toString() + extension); } - private static String getAttachmentExtension( - final Optional filename, final Optional contentType - ) { + private static String getAttachmentExtension(final Optional filename, final Optional contentType) { return filename.filter(f -> f.contains(".")) .map(f -> f.substring(f.lastIndexOf(".") + 1)) .or(() -> contentType.flatMap(MimeUtils::guessExtensionFromMimeType)) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/Database.java b/lib/src/main/java/org/asamk/signal/manager/storage/Database.java index 71704698..2fb09a4f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/Database.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/Database.java @@ -24,7 +24,8 @@ public abstract class Database implements AutoCloseable { } public static T initDatabase( - File databaseFile, Function newDatabase + File databaseFile, + Function newDatabase ) throws SQLException { HikariDataSource dataSource = null; 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 87cf3aa3..5c8a5530 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 @@ -189,7 +189,10 @@ public class SignalAccount implements Closeable { } public static SignalAccount load( - File dataPath, String accountPath, boolean waitForLock, final Settings settings + File dataPath, + String accountPath, + boolean waitForLock, + final Settings settings ) throws IOException { logger.trace("Opening account file"); final var fileName = getFileName(dataPath, accountPath); @@ -316,7 +319,9 @@ public class SignalAccount implements Closeable { } public void finishLinking( - final int deviceId, final PreKeyCollection aciPreKeys, final PreKeyCollection pniPreKeys + final int deviceId, + final PreKeyCollection aciPreKeys, + final PreKeyCollection pniPreKeys ) { this.registered = true; this.deviceId = deviceId; @@ -375,7 +380,9 @@ public class SignalAccount implements Closeable { } private void mergeRecipients( - final Connection connection, RecipientId recipientId, RecipientId toBeMergedRecipientId + final Connection connection, + RecipientId recipientId, + RecipientId toBeMergedRecipientId ) throws SQLException { getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId); getGroupStore().mergeRecipients(connection, recipientId, toBeMergedRecipientId); @@ -438,9 +445,7 @@ public class SignalAccount implements Closeable { return f.exists() && !f.isDirectory() && f.length() > 0L; } - private void load( - File dataPath, String accountPath, final Settings settings - ) throws IOException { + private void load(File dataPath, String accountPath, final Settings settings) throws IOException { logger.trace("Loading account file {}", accountPath); this.dataPath = dataPath; this.accountPath = accountPath; @@ -786,7 +791,8 @@ public class SignalAccount implements Closeable { } private void loadLegacyStores( - final JsonNode rootNode, final LegacyJsonSignalProtocolStore legacySignalProtocolStore + final JsonNode rootNode, + final LegacyJsonSignalProtocolStore legacySignalProtocolStore ) { var legacyRecipientStoreNode = rootNode.get("recipientStore"); if (legacyRecipientStoreNode != null) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/UnknownStorageIdStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/UnknownStorageIdStore.java index 75389751..5ea5eff0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/UnknownStorageIdStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/UnknownStorageIdStore.java @@ -41,9 +41,7 @@ public class UnknownStorageIdStore { } } - public List getUnknownStorageIds( - Connection connection, Collection types - ) throws SQLException { + public List getUnknownStorageIds(Connection connection, Collection types) throws SQLException { final var typesCommaSeparated = types.stream().map(String::valueOf).collect(Collectors.joining(",")); final var sql = ( """ diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/Utils.java b/lib/src/main/java/org/asamk/signal/manager/storage/Utils.java index fa2e8916..e4858f21 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/Utils.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/Utils.java @@ -72,7 +72,8 @@ public class Utils { } public static T executeQuerySingleRow( - PreparedStatement statement, ResultSetMapper mapper + PreparedStatement statement, + ResultSetMapper mapper ) throws SQLException { final var resultSet = statement.executeQuery(); if (!resultSet.next()) { @@ -82,7 +83,8 @@ public class Utils { } public static Optional executeQueryForOptional( - PreparedStatement statement, ResultSetMapper mapper + PreparedStatement statement, + ResultSetMapper mapper ) throws SQLException { final var resultSet = statement.executeQuery(); if (!resultSet.next()) { @@ -92,7 +94,8 @@ public class Utils { } public static Stream executeQueryForStream( - PreparedStatement statement, ResultSetMapper mapper + PreparedStatement statement, + ResultSetMapper mapper ) throws SQLException { final var resultSet = statement.executeQuery(); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java index 02379941..a0fa3012 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java @@ -41,7 +41,9 @@ public class AccountsStore { private final AccountLoader accountLoader; public AccountsStore( - final File dataPath, final ServiceEnvironment serviceEnvironment, final AccountLoader accountLoader + final File dataPath, + final ServiceEnvironment serviceEnvironment, + final AccountLoader accountLoader ) throws IOException { this.dataPath = dataPath; this.serviceEnvironment = getServiceEnvironmentString(serviceEnvironment); @@ -202,7 +204,9 @@ public class AccountsStore { } private AccountsStorage upgradeAccountsFile( - final FileChannel fileChannel, final AccountsStorage storage, final int accountsVersion + final FileChannel fileChannel, + final AccountsStorage storage, + final int accountsVersion ) { try { List newAccounts = storage.accounts(); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/configuration/ConfigurationStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/configuration/ConfigurationStore.java index 558d4f86..ef4a4545 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/configuration/ConfigurationStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/configuration/ConfigurationStore.java @@ -59,7 +59,8 @@ public class ConfigurationStore { } public void setUnidentifiedDeliveryIndicators( - final Connection connection, final boolean value + final Connection connection, + final boolean value ) throws SQLException { if (keyValueStore.storeEntry(connection, unidentifiedDeliveryIndicators, value)) { recipientStore.rotateSelfStorageId(connection); @@ -125,7 +126,8 @@ public class ConfigurationStore { } public void setPhoneNumberSharingMode( - final Connection connection, final PhoneNumberSharingMode value + final Connection connection, + final PhoneNumberSharingMode value ) throws SQLException { if (keyValueStore.storeEntry(connection, phoneNumberSharingMode, value)) { recipientStore.rotateSelfStorageId(connection); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java index 89306ecd..f15b94a8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java @@ -31,7 +31,9 @@ public final class GroupInfoV2 extends GroupInfo { private final RecipientResolver recipientResolver; public GroupInfoV2( - final GroupIdV2 groupId, final GroupMasterKey masterKey, final RecipientResolver recipientResolver + final GroupIdV2 groupId, + final GroupMasterKey masterKey, + final RecipientResolver recipientResolver ) { this.groupId = groupId; this.masterKey = masterKey; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java index c4c075bf..26c4c08d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupStore.java @@ -121,7 +121,10 @@ public class GroupStore { } public void storeStorageRecord( - final Connection connection, final GroupId groupId, final StorageId storageId, final byte[] storageRecord + final Connection connection, + final GroupId groupId, + final StorageId storageId, + final byte[] storageRecord ) throws SQLException { final var groupTable = groupId instanceof GroupIdV1 ? TABLE_GROUP_V1 : TABLE_GROUP_V2; final var deleteSql = ( @@ -250,7 +253,8 @@ public class GroupStore { } public GroupInfoV2 getGroupOrPartialMigrate( - Connection connection, final GroupMasterKey groupMasterKey + Connection connection, + final GroupMasterKey groupMasterKey ) throws SQLException { final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey); final var groupId = GroupUtils.getGroupIdV2(groupSecretParams); @@ -258,9 +262,7 @@ public class GroupStore { return getGroupOrPartialMigrate(connection, groupMasterKey, groupId); } - public GroupInfoV2 getGroupOrPartialMigrate( - final GroupMasterKey groupMasterKey, final GroupIdV2 groupId - ) { + public GroupInfoV2 getGroupOrPartialMigrate(final GroupMasterKey groupMasterKey, final GroupIdV2 groupId) { try (final var connection = database.getConnection()) { return getGroupOrPartialMigrate(connection, groupMasterKey, groupId); } catch (SQLException e) { @@ -269,7 +271,9 @@ public class GroupStore { } private GroupInfoV2 getGroupOrPartialMigrate( - Connection connection, final GroupMasterKey groupMasterKey, final GroupIdV2 groupId + Connection connection, + final GroupMasterKey groupMasterKey, + final GroupIdV2 groupId ) throws SQLException { switch (getGroup(connection, (GroupId) groupId)) { case GroupInfoV1 groupInfoV1 -> { @@ -325,7 +329,9 @@ public class GroupStore { } public void mergeRecipients( - final Connection connection, final RecipientId recipientId, final RecipientId toBeMergedRecipientId + final Connection connection, + final RecipientId recipientId, + final RecipientId toBeMergedRecipientId ) throws SQLException { final var sql = ( """ @@ -360,7 +366,9 @@ public class GroupStore { } public void updateStorageIds( - Connection connection, Map storageIdV1Map, Map storageIdV2Map + Connection connection, + Map storageIdV1Map, + Map storageIdV2Map ) throws SQLException { final var sql = ( """ @@ -385,9 +393,7 @@ public class GroupStore { } } - public void updateStorageId( - Connection connection, GroupId groupId, StorageId storageId - ) throws SQLException { + public void updateStorageId(Connection connection, GroupId groupId, StorageId storageId) throws SQLException { final var sqlV1 = ( """ UPDATE %s @@ -460,7 +466,9 @@ public class GroupStore { } private void insertOrReplaceGroup( - final Connection connection, Long internalId, final GroupInfo group + final Connection connection, + Long internalId, + final GroupInfo group ) throws SQLException { if (group instanceof GroupInfoV1 groupV1) { if (internalId != null) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java index 1d8c8799..383912dd 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java @@ -151,7 +151,8 @@ public class LegacyGroupStore { @Override public List deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext + JsonParser jsonParser, + DeserializationContext deserializationContext ) throws IOException { var addresses = new ArrayList(); JsonNode node = jsonParser.getCodec().readTree(jsonParser); @@ -184,7 +185,8 @@ public class LegacyGroupStore { @Override public List deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext + JsonParser jsonParser, + DeserializationContext deserializationContext ) throws IOException { var groups = new ArrayList<>(); JsonNode node = jsonParser.getCodec().readTree(jsonParser); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityInfo.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityInfo.java index d00d455c..0b9b8c17 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityInfo.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityInfo.java @@ -11,9 +11,7 @@ public class IdentityInfo { private final TrustLevel trustLevel; private final long addedTimestamp; - IdentityInfo( - final String address, IdentityKey identityKey, TrustLevel trustLevel, long addedTimestamp - ) { + IdentityInfo(final String address, IdentityKey identityKey, TrustLevel trustLevel, long addedTimestamp) { this.address = address; this.identityKey = identityKey; this.trustLevel = trustLevel; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java index a4b355ea..5e32ba12 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java @@ -49,7 +49,9 @@ public class IdentityKeyStore { } public IdentityKeyStore( - final Database database, final TrustNewIdentity trustNewIdentity, RecipientStore recipientStore + final Database database, + final TrustNewIdentity trustNewIdentity, + RecipientStore recipientStore ) { this.database = database; this.trustNewIdentity = trustNewIdentity; @@ -65,7 +67,9 @@ public class IdentityKeyStore { } public boolean saveIdentity( - final Connection connection, final ServiceId serviceId, final IdentityKey identityKey + final Connection connection, + final ServiceId serviceId, + final IdentityKey identityKey ) throws SQLException { return saveIdentity(connection, serviceId.toString(), identityKey); } @@ -82,7 +86,9 @@ public class IdentityKeyStore { } private boolean saveIdentity( - final Connection connection, final String address, final IdentityKey identityKey + final Connection connection, + final String address, + final IdentityKey identityKey ) throws SQLException { final var identityInfo = loadIdentity(connection, address); if (identityInfo != null && identityInfo.getIdentityKey().equals(identityKey)) { @@ -230,9 +236,7 @@ public class IdentityKeyStore { logger.debug("Complete identities migration took {}ms", (System.nanoTime() - start) / 1000000); } - private IdentityInfo loadIdentity( - final Connection connection, final String address - ) throws SQLException { + private IdentityInfo loadIdentity(final Connection connection, final String address) throws SQLException { final var sql = ( """ SELECT i.address, i.identity_key, i.added_timestamp, i.trust_level diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/LegacyIdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/LegacyIdentityKeyStore.java index a7f6a4aa..fc6e93e1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/identities/LegacyIdentityKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/identities/LegacyIdentityKeyStore.java @@ -41,7 +41,9 @@ public class LegacyIdentityKeyStore { static final Pattern identityFileNamePattern = Pattern.compile("(\\d+)"); private static List getIdentities( - final File identitiesPath, final RecipientResolver resolver, final RecipientAddressResolver addressResolver + final File identitiesPath, + final RecipientResolver resolver, + final RecipientAddressResolver addressResolver ) { final var files = identitiesPath.listFiles(); if (files == null) { @@ -66,7 +68,9 @@ public class LegacyIdentityKeyStore { } private static IdentityInfo loadIdentityLocked( - final RecipientId recipientId, RecipientAddressResolver addressResolver, final File identitiesPath + final RecipientId recipientId, + RecipientAddressResolver addressResolver, + final File identitiesPath ) { final var file = getIdentityFile(recipientId, identitiesPath); if (!file.exists()) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/keyValue/KeyValueStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/keyValue/KeyValueStore.java index c51f32f1..e5bbc188 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/keyValue/KeyValueStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/keyValue/KeyValueStore.java @@ -74,7 +74,9 @@ public class KeyValueStore { } public boolean storeEntry( - final Connection connection, final KeyValueEntry key, final T value + final Connection connection, + final KeyValueEntry key, + final T value ) throws SQLException { final var entry = getEntry(key); if (Objects.equals(entry, value)) { @@ -98,7 +100,8 @@ public class KeyValueStore { @SuppressWarnings("unchecked") private static T readValueFromResultSet( - final KeyValueEntry key, final ResultSet resultSet + final KeyValueEntry key, + final ResultSet resultSet ) throws SQLException { Object value; final var clazz = key.clazz(); @@ -134,7 +137,10 @@ public class KeyValueStore { } private static void setParameterValue( - final PreparedStatement statement, final int parameterIndex, final Class clazz, final T value + final PreparedStatement statement, + final int parameterIndex, + final Class clazz, + final T value ) throws SQLException { if (clazz == int.class || clazz == Integer.class) { if (value == null) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacyProfileStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacyProfileStore.java index abb66470..39d6b7fb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacyProfileStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacyProfileStore.java @@ -34,7 +34,8 @@ public class LegacyProfileStore { @Override public List deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext + JsonParser jsonParser, + DeserializationContext deserializationContext ) throws IOException { JsonNode node = jsonParser.getCodec().readTree(jsonParser); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/profiles/ProfileStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/profiles/ProfileStore.java index 5d3a95a8..ec91a628 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/profiles/ProfileStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/profiles/ProfileStore.java @@ -18,6 +18,7 @@ public interface ProfileStore { void storeProfileKey(RecipientId recipientId, ProfileKey profileKey); void storeExpiringProfileKeyCredential( - RecipientId recipientId, ExpiringProfileKeyCredential expiringProfileKeyCredential + RecipientId recipientId, + ExpiringProfileKeyCredential expiringProfileKeyCredential ); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonIdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonIdentityKeyStore.java index 0d12a303..632efc7e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonIdentityKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonIdentityKeyStore.java @@ -32,7 +32,9 @@ public class LegacyJsonIdentityKeyStore { private final int localRegistrationId; private LegacyJsonIdentityKeyStore( - final List identities, IdentityKeyPair identityKeyPair, int localRegistrationId + final List identities, + IdentityKeyPair identityKeyPair, + int localRegistrationId ) { this.identities = identities; this.identityKeyPair = identityKeyPair; @@ -77,7 +79,8 @@ public class LegacyJsonIdentityKeyStore { @Override public LegacyJsonIdentityKeyStore deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext + JsonParser jsonParser, + DeserializationContext deserializationContext ) throws IOException { JsonNode node = jsonParser.getCodec().readTree(jsonParser); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonPreKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonPreKeyStore.java index 7412f95a..feb27e97 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonPreKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonPreKeyStore.java @@ -26,7 +26,8 @@ public class LegacyJsonPreKeyStore { @Override public LegacyJsonPreKeyStore deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext + JsonParser jsonParser, + DeserializationContext deserializationContext ) throws IOException { JsonNode node = jsonParser.getCodec().readTree(jsonParser); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSessionStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSessionStore.java index ee675d78..b971078c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSessionStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSessionStore.java @@ -31,7 +31,8 @@ public class LegacyJsonSessionStore { @Override public LegacyJsonSessionStore deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext + JsonParser jsonParser, + DeserializationContext deserializationContext ) throws IOException { JsonNode node = jsonParser.getCodec().readTree(jsonParser); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSignedPreKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSignedPreKeyStore.java index cdcba916..ed1b5d95 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSignedPreKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/LegacyJsonSignedPreKeyStore.java @@ -26,7 +26,8 @@ public class LegacyJsonSignedPreKeyStore { @Override public LegacyJsonSignedPreKeyStore deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext + JsonParser jsonParser, + DeserializationContext deserializationContext ) throws IOException { JsonNode node = jsonParser.getCodec().readTree(jsonParser); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java index f334a2be..b631f632 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java @@ -172,7 +172,9 @@ public class SignalProtocolStore implements SignalServiceAccountDataStore { @Override public void storeSenderKey( - final SignalProtocolAddress sender, final UUID distributionId, final SenderKeyRecord record + final SignalProtocolAddress sender, + final UUID distributionId, + final SenderKeyRecord record ) { senderKeyStore.storeSenderKey(sender, distributionId, record); } @@ -189,7 +191,8 @@ public class SignalProtocolStore implements SignalServiceAccountDataStore { @Override public void markSenderKeySharedWith( - final DistributionId distributionId, final Collection addresses + final DistributionId distributionId, + final Collection addresses ) { senderKeyStore.markSenderKeySharedWith(distributionId, addresses); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/CdsiStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/CdsiStore.java index 1170e651..4b68adbe 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/CdsiStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/CdsiStore.java @@ -98,9 +98,7 @@ public class CdsiStore { } } - private static void removeNumbers( - final Connection connection, final Set numbers - ) throws SQLException { + private static void removeNumbers(final Connection connection, final Set numbers) throws SQLException { final var sql = ( """ DELETE FROM %s @@ -116,7 +114,9 @@ public class CdsiStore { } private static void addNumbers( - final Connection connection, final Set numbers, final long lastSeen + final Connection connection, + final Set numbers, + final long lastSeen ) throws SQLException { final var sql = ( """ @@ -135,7 +135,9 @@ public class CdsiStore { } private static void updateLastSeen( - final Connection connection, final Set numbers, final long lastSeen + final Connection connection, + final Set numbers, + final long lastSeen ) throws SQLException { final var sql = ( """ diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore.java index fb1e188b..907aafe4 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore.java @@ -27,7 +27,8 @@ public class LegacyRecipientStore { @Override public List deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext + JsonParser jsonParser, + DeserializationContext deserializationContext ) throws IOException { JsonNode node = jsonParser.getCodec().readTree(jsonParser); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java index edc1121e..95c17102 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java @@ -15,7 +15,8 @@ public class MergeRecipientHelper { private static final Logger logger = LoggerFactory.getLogger(MergeRecipientHelper.class); static Pair> resolveRecipientTrustedLocked( - Store store, RecipientAddress address + Store store, + RecipientAddress address ) throws SQLException { // address has at least one of serviceId/pni and optionally number/username 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 2aad97d6..00048560 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 @@ -208,7 +208,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public RecipientId resolveRecipientByNumber( - final String number, Supplier serviceIdSupplier + final String number, + Supplier serviceIdSupplier ) throws UnregisteredRecipientException { final Optional byNumber; try (final var connection = database.getConnection()) { @@ -238,7 +239,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public RecipientId resolveRecipientByUsername( - final String username, Supplier aciSupplier + final String username, + Supplier aciSupplier ) throws UnregisteredRecipientException { final Optional byUsername; try (final var connection = database.getConnection()) { @@ -301,7 +303,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re @Override public RecipientId resolveRecipientTrusted( - final Optional aci, final Optional pni, final Optional number + final Optional aci, + final Optional pni, + final Optional number ) { return resolveRecipientTrusted(new RecipientAddress(aci, pni, number, Optional.empty())); } @@ -392,7 +396,10 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public List getRecipients( - boolean onlyContacts, Optional blocked, Set recipientIds, Optional name + boolean onlyContacts, + Optional blocked, + Set recipientIds, + Optional name ) { final var sqlWhere = new ArrayList(); if (onlyContacts) { @@ -614,14 +621,17 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public void storeProfileKey( - Connection connection, RecipientId recipientId, final ProfileKey profileKey + Connection connection, + RecipientId recipientId, + final ProfileKey profileKey ) throws SQLException { storeProfileKey(connection, recipientId, profileKey, true); } @Override public void storeExpiringProfileKeyCredential( - RecipientId recipientId, final ExpiringProfileKeyCredential profileKeyCredential + RecipientId recipientId, + final ExpiringProfileKeyCredential profileKeyCredential ) { try (final var connection = database.getConnection()) { storeExpiringProfileKeyCredential(connection, recipientId, profileKeyCredential); @@ -661,7 +671,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public void updateStorageId( - Connection connection, RecipientId recipientId, StorageId storageId + Connection connection, + RecipientId recipientId, + StorageId storageId ) throws SQLException { final var sql = ( """ @@ -813,7 +825,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public void storeContact( - final Connection connection, final RecipientId recipientId, final Contact contact + final Connection connection, + final RecipientId recipientId, + final Contact contact ) throws SQLException { final var sql = ( """ @@ -852,7 +866,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public int removeStorageIdsFromLocalOnlyUnregisteredRecipients( - final Connection connection, final List storageIds + final Connection connection, + final List storageIds ) throws SQLException { final var sql = ( """ @@ -965,7 +980,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private void markUnregisteredAndSplitIfNecessary( - final Connection connection, final RecipientId recipientId + final Connection connection, + final RecipientId recipientId ) throws SQLException { markUnregistered(connection, recipientId); final var address = resolveRecipientAddress(connection, recipientId); @@ -977,7 +993,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private void markDiscoverable( - final Connection connection, final RecipientId recipientId, final boolean discoverable + final Connection connection, + final RecipientId recipientId, + final boolean discoverable ) throws SQLException { final var sql = ( """ @@ -993,9 +1011,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } } - private void markRegistered( - final Connection connection, final RecipientId recipientId - ) throws SQLException { + private void markRegistered(final Connection connection, final RecipientId recipientId) throws SQLException { final var sql = ( """ UPDATE %s @@ -1009,9 +1025,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } } - private void markUnregistered( - final Connection connection, final RecipientId recipientId - ) throws SQLException { + private void markUnregistered(final Connection connection, final RecipientId recipientId) throws SQLException { final var sql = ( """ UPDATE %s @@ -1046,7 +1060,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } public void storeProfile( - final Connection connection, final RecipientId recipientId, final Profile profile + final Connection connection, + final RecipientId recipientId, + final Profile profile ) throws SQLException { final var sql = ( """ @@ -1079,7 +1095,10 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private void storeProfileKey( - Connection connection, RecipientId recipientId, final ProfileKey profileKey, boolean resetProfile + Connection connection, + RecipientId recipientId, + final ProfileKey profileKey, + boolean resetProfile ) throws SQLException { if (profileKey != null) { final var recipientProfileKey = getProfileKey(connection, recipientId); @@ -1111,7 +1130,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private RecipientAddress resolveRecipientAddress( - final Connection connection, final RecipientId recipientId + final Connection connection, + final RecipientId recipientId ) throws SQLException { final var sql = ( """ @@ -1150,7 +1170,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private Pair> resolveRecipientTrustedLocked( - final Connection connection, final RecipientAddress address, final boolean isSelf + final Connection connection, + final RecipientAddress address, + final boolean isSelf ) throws SQLException { if (address.hasSingleIdentifier() || ( !isSelf && selfAddressProvider.getSelfAddress().matches(address) @@ -1168,7 +1190,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private void mergeRecipients( - final Connection connection, final RecipientId recipientId, final List toBeMergedRecipientIds + final Connection connection, + final RecipientId recipientId, + final List toBeMergedRecipientIds ) throws SQLException { for (final var toBeMergedRecipientId : toBeMergedRecipientIds) { recipientMergeHandler.mergeRecipients(connection, recipientId, toBeMergedRecipientId); @@ -1177,9 +1201,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } } - private RecipientId resolveRecipientLocked( - Connection connection, RecipientAddress address - ) throws SQLException { + private RecipientId resolveRecipientLocked(Connection connection, RecipientAddress address) throws SQLException { final var byAci = address.aci().isEmpty() ? Optional.empty() : findByServiceId(connection, address.aci().get()); @@ -1236,7 +1258,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private RecipientId addNewRecipient( - final Connection connection, final RecipientAddress address + final Connection connection, + final RecipientAddress address ) throws SQLException { final var sql = ( """ @@ -1277,7 +1300,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private void updateRecipientAddress( - Connection connection, RecipientId recipientId, final RecipientAddress address + Connection connection, + RecipientId recipientId, + final RecipientAddress address ) throws SQLException { recipientAddressCache.entrySet().removeIf(e -> e.getValue().id().equals(recipientId)); final var sql = ( @@ -1312,7 +1337,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private void mergeRecipientsLocked( - Connection connection, RecipientId recipientId, RecipientId toBeMergedRecipientId + Connection connection, + RecipientId recipientId, + RecipientId toBeMergedRecipientId ) throws SQLException { final var contact = getContact(connection, recipientId); if (contact == null) { @@ -1343,7 +1370,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private Optional findByNumber( - final Connection connection, final String number + final Connection connection, + final String number ) throws SQLException { final var sql = """ SELECT r._id, r.number, r.aci, r.pni, r.username @@ -1358,7 +1386,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private Optional findByUsername( - final Connection connection, final String username + final Connection connection, + final String username ) throws SQLException { final var sql = """ SELECT r._id, r.number, r.aci, r.pni, r.username @@ -1373,7 +1402,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private Optional findByServiceId( - final Connection connection, final ServiceId serviceId + final Connection connection, + final ServiceId serviceId ) throws SQLException { var recipientWithAddress = Optional.ofNullable(recipientAddressCache.get(serviceId)); if (recipientWithAddress.isPresent()) { @@ -1394,7 +1424,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private Set findAllByAddress( - final Connection connection, final RecipientAddress address + final Connection connection, + final RecipientAddress address ) throws SQLException { final var sql = """ SELECT r._id, r.number, r.aci, r.pni, r.username @@ -1447,7 +1478,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private ExpiringProfileKeyCredential getExpiringProfileKeyCredential( - final Connection connection, final RecipientId recipientId + final Connection connection, + final RecipientId recipientId ) throws SQLException { final var sql = ( """ @@ -1593,7 +1625,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public interface RecipientMergeHandler { void mergeRecipients( - final Connection connection, RecipientId recipientId, RecipientId toBeMergedRecipientId + final Connection connection, + RecipientId recipientId, + RecipientId toBeMergedRecipientId ) throws SQLException; } @@ -1617,7 +1651,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re @Override public void updateRecipientAddress( - final RecipientId recipientId, final RecipientAddress address + final RecipientId recipientId, + final RecipientAddress address ) throws SQLException { RecipientStore.this.updateRecipientAddress(connection, recipientId, address); } 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 598ef791..06d29060 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 @@ -44,7 +44,9 @@ public interface RecipientTrustedResolver { @Override public RecipientId resolveRecipientTrusted( - final Optional aci, final Optional pni, final Optional number + final Optional aci, + final Optional pni, + final Optional number ) { return recipientTrustedResolverSupplier.get().resolveRecipientTrusted(aci, pni, number); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java index 910da3cf..20bfdb6a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/sendLog/MessageSendLogStore.java @@ -83,7 +83,10 @@ public class MessageSendLogStore implements AutoCloseable { } public List findMessages( - final ServiceId serviceId, final int deviceId, final long timestamp, final boolean isSenderKey + final ServiceId serviceId, + final int deviceId, + final long timestamp, + final boolean isSenderKey ) { final var sql = """ SELECT group_id, content, content_hint, urgent @@ -111,7 +114,10 @@ public class MessageSendLogStore implements AutoCloseable { } public long insertIfPossible( - long sentTimestamp, SendMessageResult sendMessageResult, ContentHint contentHint, boolean urgent + long sentTimestamp, + SendMessageResult sendMessageResult, + ContentHint contentHint, + boolean urgent ) { if (sendLogDisabled) { return -1; @@ -129,7 +135,10 @@ public class MessageSendLogStore implements AutoCloseable { } public long insertIfPossible( - long sentTimestamp, List sendMessageResults, ContentHint contentHint, boolean urgent + long sentTimestamp, + List sendMessageResults, + ContentHint contentHint, + boolean urgent ) { if (sendLogDisabled) { return -1; @@ -164,7 +173,8 @@ public class MessageSendLogStore implements AutoCloseable { } public void addRecipientToExistingEntryIfPossible( - final long contentId, final List sendMessageResults + final long contentId, + final List sendMessageResults ) { if (sendLogDisabled) { return; @@ -322,7 +332,8 @@ public class MessageSendLogStore implements AutoCloseable { } private void insertRecipientsForExistingContent( - final long contentId, final List recipientDevices + final long contentId, + final List recipientDevices ) { try (final var connection = database.getConnection()) { connection.setAutoCommit(false); @@ -334,7 +345,9 @@ public class MessageSendLogStore implements AutoCloseable { } private void insertRecipientsForExistingContent( - final long contentId, final List recipientDevices, final Connection connection + final long contentId, + final List recipientDevices, + final Connection connection ) throws SQLException { final var sql = """ INSERT INTO %s (address, device_id, content_id) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyRecordStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyRecordStore.java index 39e1df97..dd48fcdc 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyRecordStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyRecordStore.java @@ -59,7 +59,9 @@ public class SenderKeyRecordStore implements SenderKeyStore { @Override public void storeSenderKey( - final SignalProtocolAddress address, final UUID distributionId, final SenderKeyRecord record + final SignalProtocolAddress address, + final UUID distributionId, + final SenderKeyRecord record ) { final var key = getKey(address, distributionId); @@ -165,7 +167,9 @@ public class SenderKeyRecordStore implements SenderKeyStore { } private void storeSenderKey( - final Connection connection, final Key key, final SenderKeyRecord senderKeyRecord + final Connection connection, + final Key key, + final SenderKeyRecord senderKeyRecord ) throws SQLException { final var sqlUpdate = """ UPDATE %s diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java index 284ba08f..175524f5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeySharedStore.java @@ -65,7 +65,8 @@ public class SenderKeySharedStore { } public void markSenderKeySharedWith( - final DistributionId distributionId, final Collection addresses + final DistributionId distributionId, + final Collection addresses ) { final var newEntries = addresses.stream() .map(a -> new SenderKeySharedEntry(a.getName(), a.getDeviceId())) @@ -138,9 +139,7 @@ public class SenderKeySharedStore { } } - public void deleteSharedWith( - final ServiceId serviceId, final int deviceId, final DistributionId distributionId - ) { + public void deleteSharedWith(final ServiceId serviceId, final int deviceId, final DistributionId distributionId) { try (final var connection = database.getConnection()) { final var sql = ( """ @@ -192,7 +191,9 @@ public class SenderKeySharedStore { } private void markSenderKeysSharedWith( - final Connection connection, final DistributionId distributionId, final Set newEntries + final Connection connection, + final DistributionId distributionId, + final Set newEntries ) throws SQLException { final var sql = ( """ diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java index d60249b6..5ae2790e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/senderKeys/SenderKeyStore.java @@ -25,7 +25,9 @@ public class SenderKeyStore implements SignalServiceSenderKeyStore { @Override public void storeSenderKey( - final SignalProtocolAddress sender, final UUID distributionId, final SenderKeyRecord record + final SignalProtocolAddress sender, + final UUID distributionId, + final SenderKeyRecord record ) { senderKeyRecordStore.storeSenderKey(sender, distributionId, record); } @@ -42,7 +44,8 @@ public class SenderKeyStore implements SignalServiceSenderKeyStore { @Override public void markSenderKeySharedWith( - final DistributionId distributionId, final Collection addresses + final DistributionId distributionId, + final Collection addresses ) { senderKeySharedStore.markSenderKeySharedWith(distributionId, addresses); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java index 067dc5d3..86fe875e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/sessions/SessionStore.java @@ -342,7 +342,9 @@ public class SessionStore implements SignalServiceSessionStore { } private void storeSession( - final Connection connection, final Key key, final SessionRecord session + final Connection connection, + final Key key, + final SessionRecord session ) throws SQLException { synchronized (cachedSessions) { cachedSessions.put(key, session); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/threads/LegacyJsonThreadStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/threads/LegacyJsonThreadStore.java index 428e9e95..efe65c64 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/threads/LegacyJsonThreadStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/threads/LegacyJsonThreadStore.java @@ -35,7 +35,9 @@ public class LegacyJsonThreadStore { @Override public void serialize( - final Map value, final JsonGenerator jgen, final SerializerProvider provider + final Map value, + final JsonGenerator jgen, + final SerializerProvider provider ) throws IOException { jgen.writeObject(value.values()); } @@ -45,7 +47,8 @@ public class LegacyJsonThreadStore { @Override public Map deserialize( - JsonParser jsonParser, DeserializationContext deserializationContext + JsonParser jsonParser, + DeserializationContext deserializationContext ) throws IOException { var threads = new HashMap(); JsonNode node = jsonParser.getCodec().readTree(jsonParser); diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java index 31e7a90b..18dd1869 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java @@ -33,7 +33,9 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor inserts, RecipientAddress self + SignalStorageManifest manifest, + List inserts, + RecipientAddress self ) { validateManifestAndInserts(manifest, inserts, self); } private static void validateManifestAndInserts( - SignalStorageManifest manifest, List inserts, RecipientAddress self + SignalStorageManifest manifest, + List inserts, + RecipientAddress self ) { int accountCount = 0; for (StorageId id : manifest.getStorageIds()) { diff --git a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java index ac15a30a..bab4c433 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/AttachmentUtils.java @@ -13,7 +13,8 @@ import java.util.UUID; public class AttachmentUtils { public static SignalServiceAttachmentStream createAttachmentStream( - String attachment, ResumableUploadSpec resumableUploadSpec + String attachment, + ResumableUploadSpec resumableUploadSpec ) throws AttachmentInvalidException { try { final var streamDetails = Utils.createStreamDetails(attachment); @@ -25,7 +26,9 @@ public class AttachmentUtils { } public static SignalServiceAttachmentStream createAttachmentStream( - StreamDetails streamDetails, Optional name, ResumableUploadSpec resumableUploadSpec + StreamDetails streamDetails, + Optional name, + ResumableUploadSpec resumableUploadSpec ) throws ResumeLocationInvalidException { // TODO maybe add a parameter to set the voiceNote, borderless, preview, width, height and caption option final var uploadTimestamp = System.currentTimeMillis(); diff --git a/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java index bfcb750c..c3dcf3fe 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java @@ -63,7 +63,8 @@ public class KeyUtils { } public static SignedPreKeyRecord generateSignedPreKeyRecord( - final int signedPreKeyId, final ECPrivateKey privateKey + final int signedPreKeyId, + final ECPrivateKey privateKey ) { var keyPair = Curve.generateKeyPair(); byte[] signature; @@ -75,9 +76,7 @@ public class KeyUtils { return new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature); } - public static List generateKyberPreKeyRecords( - final int offset, final ECPrivateKey privateKey - ) { + public static List generateKyberPreKeyRecords(final int offset, final ECPrivateKey privateKey) { var records = new ArrayList(PREKEY_BATCH_SIZE); for (var i = 0; i < PREKEY_BATCH_SIZE; i++) { var preKeyId = (offset + i) % PREKEY_MAXIMUM_ID; diff --git a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java index 03b1c03a..e27ba980 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java @@ -88,7 +88,9 @@ public class NumberVerificationUtils { } public static void requestVerificationCode( - RegistrationApi registrationApi, String sessionId, boolean voiceVerification + RegistrationApi registrationApi, + String sessionId, + boolean voiceVerification ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException { final var locale = Utils.getDefaultLocale(Locale.US); final var response = registrationApi.requestSmsVerificationCode(sessionId, @@ -108,7 +110,11 @@ public class NumberVerificationUtils { } public static Pair verifyNumber( - String sessionId, String verificationCode, String pin, PinHelper pinHelper, Verifier verifier + String sessionId, + String verificationCode, + String pin, + PinHelper pinHelper, + Verifier verifier ) throws IOException, PinLockedException, IncorrectPinException { verificationCode = verificationCode.replace("-", ""); try { @@ -138,7 +144,8 @@ public class NumberVerificationUtils { } private static RegistrationSessionMetadataResponse validateSession( - final RegistrationApi registrationApi, final String sessionId + final RegistrationApi registrationApi, + final String sessionId ) throws IOException { if (sessionId == null || sessionId.isEmpty()) { throw new NoSuchSessionException(); @@ -153,7 +160,8 @@ public class NumberVerificationUtils { } private static RegistrationSessionMetadataResponse getValidSession( - final RegistrationApi registrationApi, final String sessionId + final RegistrationApi registrationApi, + final String sessionId ) throws IOException { try { return validateSession(registrationApi, sessionId); @@ -164,7 +172,9 @@ public class NumberVerificationUtils { } private static RegistrationSessionMetadataResponse submitCaptcha( - RegistrationApi registrationApi, String sessionId, String captcha + RegistrationApi registrationApi, + String sessionId, + String captcha ) throws IOException, CaptchaRequiredException { captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); try { @@ -184,7 +194,9 @@ public class NumberVerificationUtils { public interface Verifier { VerifyAccountResponse verify( - String sessionId, String verificationCode, String registrationLock + String sessionId, + String verificationCode, + String registrationLock ) throws IOException; } } diff --git a/lib/src/main/java/org/asamk/signal/manager/util/PaymentUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/PaymentUtils.java index fe338e73..36275d8f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/PaymentUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/PaymentUtils.java @@ -20,9 +20,7 @@ public class PaymentUtils { /** * Signs the supplied address bytes with the {@link IdentityKeyPair}'s private key and returns a proto that includes it, and it's signature. */ - public static PaymentAddress signPaymentsAddress( - byte[] publicAddressBytes, ECPrivateKey privateKey - ) { + public static PaymentAddress signPaymentsAddress(byte[] publicAddressBytes, ECPrivateKey privateKey) { byte[] signature = privateKey.calculateSignature(publicAddressBytes); return new PaymentAddress.Builder().mobileCoinAddress(new PaymentAddress.MobileCoinAddress.Builder().address( @@ -34,9 +32,7 @@ public class PaymentUtils { *

    * Returns the validated bytes if so, otherwise returns null. */ - public static byte[] verifyPaymentsAddress( - PaymentAddress paymentAddress, ECPublicKey publicKey - ) { + public static byte[] verifyPaymentsAddress(PaymentAddress paymentAddress, ECPublicKey publicKey) { final var mobileCoinAddress = paymentAddress.mobileCoinAddress; if (mobileCoinAddress == null || mobileCoinAddress.address == null || mobileCoinAddress.signature == null) { logger.debug("Got payment address without mobile coin address, ignoring."); diff --git a/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java index 17e26ee8..def75049 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java @@ -23,9 +23,7 @@ public class ProfileUtils { private static final Logger logger = LoggerFactory.getLogger(ProfileUtils.class); - public static Profile decryptProfile( - final ProfileKey profileKey, final SignalServiceProfile encryptedProfile - ) { + public static Profile decryptProfile(final ProfileKey profileKey, final SignalServiceProfile encryptedProfile) { var profileCipher = new ProfileCipher(profileKey); IdentityKey identityKey = null; try { @@ -64,7 +62,8 @@ public class ProfileUtils { } public static Profile.UnidentifiedAccessMode getUnidentifiedAccessMode( - final SignalServiceProfile encryptedProfile, final ProfileCipher profileCipher + final SignalServiceProfile encryptedProfile, + final ProfileCipher profileCipher ) { if (encryptedProfile.isUnrestrictedUnidentifiedAccess()) { return Profile.UnidentifiedAccessMode.UNRESTRICTED; @@ -90,7 +89,8 @@ public class ProfileUtils { } private static String decryptString( - final String encrypted, final ProfileCipher profileCipher + final String encrypted, + final ProfileCipher profileCipher ) throws InvalidCiphertextException { try { return encrypted == null ? null : profileCipher.decryptString(Base64.getDecoder().decode(encrypted)); @@ -100,7 +100,8 @@ public class ProfileUtils { } private static Optional decryptBoolean( - final String encrypted, final ProfileCipher profileCipher + final String encrypted, + final ProfileCipher profileCipher ) throws InvalidCiphertextException { try { return encrypted == null @@ -112,7 +113,9 @@ public class ProfileUtils { } private static byte[] decryptAndVerifyMobileCoinAddress( - final byte[] encryptedPaymentAddress, final ProfileCipher profileCipher, final ECPublicKey publicKey + final byte[] encryptedPaymentAddress, + final ProfileCipher profileCipher, + final ECPublicKey publicKey ) throws InvalidCiphertextException { byte[] decrypted; try { diff --git a/lib/src/main/java/org/asamk/signal/manager/util/StickerUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/StickerUtils.java index 302535d9..d017f0a5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/StickerUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/StickerUtils.java @@ -100,7 +100,9 @@ public class StickerUtils { } private static Pair getInputStreamAndLength( - final String rootPath, final ZipFile zip, final String subfile + final String rootPath, + final ZipFile zip, + final String subfile ) throws IOException { if (zip != null) { final var entry = zip.getEntry(subfile); @@ -112,7 +114,9 @@ public class StickerUtils { } private static String getContentType( - final String rootPath, final ZipFile zip, final String subfile + final String rootPath, + final ZipFile zip, + final String subfile ) throws IOException { if (zip != null) { final var entry = zip.getEntry(subfile); diff --git a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java index 16aa3275..ee1f608f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java @@ -59,7 +59,10 @@ public class Utils { } public static Fingerprint computeSafetyNumberForNumber( - String ownNumber, IdentityKey ownIdentityKey, String theirNumber, IdentityKey theirIdentityKey + String ownNumber, + IdentityKey ownIdentityKey, + String theirNumber, + IdentityKey theirIdentityKey ) { // Version 1: E164 user final var version = 1; @@ -70,7 +73,10 @@ public class Utils { } public static Fingerprint computeSafetyNumberForUuid( - ServiceId ownServiceId, IdentityKey ownIdentityKey, ServiceId theirServiceId, IdentityKey theirIdentityKey + ServiceId ownServiceId, + IdentityKey ownIdentityKey, + ServiceId theirServiceId, + IdentityKey theirIdentityKey ) { // Version 2: UUID user final var version = 2; diff --git a/lib/src/test/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelperTest.java b/lib/src/test/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelperTest.java index b57dc6fc..0931b008 100644 --- a/lib/src/test/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelperTest.java +++ b/lib/src/test/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelperTest.java @@ -217,9 +217,7 @@ class MergeRecipientHelperTest { } @Override - public void updateRecipientAddress( - final RecipientId recipientId, final RecipientAddress address - ) { + public void updateRecipientAddress(final RecipientId recipientId, final RecipientAddress address) { recipients.removeIf(r -> r.id().equals(recipientId)); recipients.add(new RecipientWithAddress(recipientId, address)); } diff --git a/src/main/java/org/asamk/Signal.java b/src/main/java/org/asamk/Signal.java index 1d715659..ac1f6d50 100644 --- a/src/main/java/org/asamk/Signal.java +++ b/src/main/java/org/asamk/Signal.java @@ -27,39 +27,44 @@ public interface Signal extends DBusInterface { void unsubscribeReceive(); long sendMessage( - String message, List attachments, String recipient + String message, + List attachments, + String recipient ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; long sendMessage( - String message, List attachments, List recipients + String message, + List attachments, + List recipients ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; - void sendTyping( - String recipient, boolean stop - ) throws Error.Failure, Error.UntrustedIdentity; + void sendTyping(String recipient, boolean stop) throws Error.Failure, Error.UntrustedIdentity; - void sendReadReceipt( - String recipient, List messageIds - ) throws Error.Failure, Error.UntrustedIdentity; + void sendReadReceipt(String recipient, List messageIds) throws Error.Failure, Error.UntrustedIdentity; - void sendViewedReceipt( - String recipient, List messageIds - ) throws Error.Failure, Error.UntrustedIdentity; + void sendViewedReceipt(String recipient, List messageIds) throws Error.Failure, Error.UntrustedIdentity; + + long sendRemoteDeleteMessage(long targetSentTimestamp, String recipient) throws Error.Failure, Error.InvalidNumber; long sendRemoteDeleteMessage( - long targetSentTimestamp, String recipient - ) throws Error.Failure, Error.InvalidNumber; - - long sendRemoteDeleteMessage( - long targetSentTimestamp, List recipients + long targetSentTimestamp, + List recipients ) throws Error.Failure, Error.InvalidNumber; long sendMessageReaction( - String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, String recipient + String emoji, + boolean remove, + String targetAuthor, + long targetSentTimestamp, + String recipient ) throws Error.InvalidNumber, Error.Failure; long sendMessageReaction( - String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List recipients + String emoji, + boolean remove, + String targetAuthor, + long targetSentTimestamp, + List recipients ) throws Error.InvalidNumber, Error.Failure; long sendPaymentNotification(byte[] receipt, String note, String recipient) throws Error.Failure; @@ -68,9 +73,7 @@ public interface Signal extends DBusInterface { void sendSyncRequest() throws Error.Failure; - long sendNoteToSelfMessage( - String message, List attachments - ) throws Error.AttachmentInvalid, Error.Failure; + long sendNoteToSelfMessage(String message, List attachments) throws Error.AttachmentInvalid, Error.Failure; void sendEndSessionMessage(List recipients) throws Error.Failure, Error.InvalidNumber, Error.UntrustedIdentity; @@ -79,19 +82,27 @@ public interface Signal extends DBusInterface { void deleteContact(final String recipient) throws Error.Failure; long sendGroupMessage( - String message, List attachments, byte[] groupId + String message, + List attachments, + byte[] groupId ) throws Error.GroupNotFound, Error.Failure, Error.AttachmentInvalid, Error.InvalidGroupId; void sendGroupTyping( - final byte[] groupId, final boolean stop + final byte[] groupId, + final boolean stop ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity; long sendGroupRemoteDeleteMessage( - long targetSentTimestamp, byte[] groupId + long targetSentTimestamp, + byte[] groupId ) throws Error.Failure, Error.GroupNotFound, Error.InvalidGroupId; long sendGroupMessageReaction( - String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, byte[] groupId + String emoji, + boolean remove, + String targetAuthor, + long targetSentTimestamp, + byte[] groupId ) throws Error.GroupNotFound, Error.Failure, Error.InvalidNumber, Error.InvalidGroupId; String getContactName(String number) throws Error.InvalidNumber; @@ -119,12 +130,17 @@ public interface Signal extends DBusInterface { List getGroupMembers(byte[] groupId) throws Error.InvalidGroupId; byte[] createGroup( - String name, List members, String avatar + String name, + List members, + String avatar ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber; @Deprecated byte[] updateGroup( - byte[] groupId, String name, List members, String avatar + byte[] groupId, + String name, + List members, + String avatar ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId; @Deprecated @@ -156,7 +172,11 @@ public interface Signal extends DBusInterface { ) throws Error.Failure; void updateProfile( - String name, String about, String aboutEmoji, String avatarPath, boolean removeAvatar + String name, + String about, + String aboutEmoji, + String avatarPath, + boolean removeAvatar ) throws Error.Failure; void removePin(); diff --git a/src/main/java/org/asamk/SignalControl.java b/src/main/java/org/asamk/SignalControl.java index fe51a7f7..3eea790f 100644 --- a/src/main/java/org/asamk/SignalControl.java +++ b/src/main/java/org/asamk/SignalControl.java @@ -13,11 +13,14 @@ import java.util.List; public interface SignalControl extends DBusInterface { void register( - String number, boolean voiceVerification + String number, + boolean voiceVerification ) throws Error.Failure, Error.InvalidNumber, Error.RequiresCaptcha; void registerWithCaptcha( - String number, boolean voiceVerification, String captcha + String number, + boolean voiceVerification, + String captcha ) throws Error.Failure, Error.InvalidNumber, Error.RequiresCaptcha; void verify(String number, String verificationCode) throws Error.Failure, Error.InvalidNumber; diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index f8585386..d3d74c3e 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -292,7 +292,8 @@ public class App { } private RegistrationManager loadRegistrationManager( - final String account, final SignalAccountFiles signalAccountFiles + final String account, + final SignalAccountFiles signalAccountFiles ) throws UnexpectedErrorException { try { return signalAccountFiles.initRegistrationManager(account); @@ -306,7 +307,8 @@ public class App { } private Manager loadManager( - final String account, final SignalAccountFiles signalAccountFiles + final String account, + final SignalAccountFiles signalAccountFiles ) throws CommandException { logger.trace("Loading account file for {}", account); try { diff --git a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java index d96835a5..d7fd1104 100644 --- a/src/main/java/org/asamk/signal/ReceiveMessageHandler.java +++ b/src/main/java/org/asamk/signal/ReceiveMessageHandler.java @@ -101,9 +101,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { writer.println(); } - private void printDataMessage( - PlainTextWriter writer, MessageEnvelope.Data message - ) { + private void printDataMessage(PlainTextWriter writer, MessageEnvelope.Data message) { writer.println("Message timestamp: {}", DateUtils.formatTimestamp(message.timestamp())); if (message.isViewOnce()) { writer.println("=VIEW ONCE="); @@ -197,16 +195,12 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - private void printEditMessage( - PlainTextWriter writer, MessageEnvelope.Edit message - ) { + private void printEditMessage(PlainTextWriter writer, MessageEnvelope.Edit message) { writer.println("Edit: Target message timestamp: {}", DateUtils.formatTimestamp(message.targetSentTimestamp())); printDataMessage(writer.indentedWriter(), message.dataMessage()); } - private void printStoryMessage( - PlainTextWriter writer, MessageEnvelope.Story message - ) { + private void printStoryMessage(PlainTextWriter writer, MessageEnvelope.Story message) { writer.println("Story: with replies: {}", message.allowsReplies()); if (message.groupId().isPresent()) { writer.println("Group info:"); @@ -226,9 +220,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - private void printTypingMessage( - final PlainTextWriter writer, final MessageEnvelope.Typing typingMessage - ) { + private void printTypingMessage(final PlainTextWriter writer, final MessageEnvelope.Typing typingMessage) { writer.println("Action: {}", typingMessage.type()); writer.println("Timestamp: {}", DateUtils.formatTimestamp(typingMessage.timestamp())); if (typingMessage.groupId().isPresent()) { @@ -238,9 +230,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - private void printReceiptMessage( - final PlainTextWriter writer, final MessageEnvelope.Receipt receiptMessage - ) { + private void printReceiptMessage(final PlainTextWriter writer, final MessageEnvelope.Receipt receiptMessage) { writer.println("When: {}", DateUtils.formatTimestamp(receiptMessage.when())); if (receiptMessage.type() == MessageEnvelope.Receipt.Type.DELIVERY) { writer.println("Is delivery receipt"); @@ -257,9 +247,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - private void printCallMessage( - final PlainTextWriter writer, final MessageEnvelope.Call callMessage - ) { + private void printCallMessage(final PlainTextWriter writer, final MessageEnvelope.Call callMessage) { if (callMessage.destinationDeviceId().isPresent()) { final var deviceId = callMessage.destinationDeviceId().get(); writer.println("Destination device id: {}", deviceId); @@ -302,9 +290,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - private void printSyncMessage( - final PlainTextWriter writer, final MessageEnvelope.Sync syncMessage - ) { + private void printSyncMessage(final PlainTextWriter writer, final MessageEnvelope.Sync syncMessage) { if (syncMessage.contacts().isPresent()) { final var contactsMessage = syncMessage.contacts().get(); var type = contactsMessage.isComplete() ? "complete" : "partial"; @@ -392,9 +378,7 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - private void printPreview( - final PlainTextWriter writer, final MessageEnvelope.Data.Preview preview - ) { + private void printPreview(final PlainTextWriter writer, final MessageEnvelope.Data.Preview preview) { writer.println("Title: {}", preview.title()); writer.println("Description: {}", preview.description()); writer.println("Date: {}", DateUtils.formatTimestamp(preview.date())); @@ -405,25 +389,19 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - private void printSticker( - final PlainTextWriter writer, final MessageEnvelope.Data.Sticker sticker - ) { + private void printSticker(final PlainTextWriter writer, final MessageEnvelope.Data.Sticker sticker) { writer.println("Pack id: {}", Hex.toStringCondensed(sticker.packId().serialize())); writer.println("Sticker id: {}", sticker.stickerId()); } - private void printReaction( - final PlainTextWriter writer, final MessageEnvelope.Data.Reaction reaction - ) { + private void printReaction(final PlainTextWriter writer, final MessageEnvelope.Data.Reaction reaction) { writer.println("Emoji: {}", reaction.emoji()); writer.println("Target author: {}", formatContact(reaction.targetAuthor())); writer.println("Target timestamp: {}", DateUtils.formatTimestamp(reaction.targetSentTimestamp())); writer.println("Is remove: {}", reaction.isRemove()); } - private void printQuote( - final PlainTextWriter writer, final MessageEnvelope.Data.Quote quote - ) { + private void printQuote(final PlainTextWriter writer, final MessageEnvelope.Data.Quote quote) { writer.println("Id: {}", quote.id()); writer.println("Author: {}", formatContact(quote.author())); if (quote.text().isPresent()) { @@ -541,17 +519,13 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - private void printGroupContext( - final PlainTextWriter writer, final MessageEnvelope.Data.GroupContext groupContext - ) { + private void printGroupContext(final PlainTextWriter writer, final MessageEnvelope.Data.GroupContext groupContext) { printGroupInfo(writer, groupContext.groupId()); writer.println("Revision: {}", groupContext.revision()); writer.println("Type: {}", groupContext.isGroupUpdate() ? "UPDATE" : "DELIVER"); } - private void printStoryContext( - final PlainTextWriter writer, final MessageEnvelope.Data.StoryContext storyContext - ) { + private void printStoryContext(final PlainTextWriter writer, final MessageEnvelope.Data.StoryContext storyContext) { writer.println("Sender: {}", formatContact(storyContext.author())); writer.println("Sent timestamp: {}", storyContext.sentTimestamp()); } @@ -567,15 +541,11 @@ public class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { } } - private void printMention( - PlainTextWriter writer, MessageEnvelope.Data.Mention mention - ) { + private void printMention(PlainTextWriter writer, MessageEnvelope.Data.Mention mention) { writer.println("- {}: {} (length: {})", formatContact(mention.recipient()), mention.start(), mention.length()); } - private void printTextStyle( - PlainTextWriter writer, TextStyle textStyle - ) { + private void printTextStyle(PlainTextWriter writer, TextStyle textStyle) { writer.println("- {}: {} (length: {})", textStyle.style().name(), textStyle.start(), textStyle.length()); } diff --git a/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java b/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java index c40e4901..43e1a0b7 100644 --- a/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java +++ b/src/main/java/org/asamk/signal/commands/AddDeviceCommand.java @@ -38,7 +38,9 @@ public class AddDeviceCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final URI linkUri; try { diff --git a/src/main/java/org/asamk/signal/commands/AddStickerPackCommand.java b/src/main/java/org/asamk/signal/commands/AddStickerPackCommand.java index 89c82200..2d720362 100644 --- a/src/main/java/org/asamk/signal/commands/AddStickerPackCommand.java +++ b/src/main/java/org/asamk/signal/commands/AddStickerPackCommand.java @@ -36,7 +36,9 @@ public class AddStickerPackCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var uris = ns.getList("uri"); for (final var uri : uris) { diff --git a/src/main/java/org/asamk/signal/commands/BlockCommand.java b/src/main/java/org/asamk/signal/commands/BlockCommand.java index 1518c53a..e7d0af49 100644 --- a/src/main/java/org/asamk/signal/commands/BlockCommand.java +++ b/src/main/java/org/asamk/signal/commands/BlockCommand.java @@ -35,7 +35,9 @@ public class BlockCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var contacts = ns.getList("recipient"); final var recipients = CommandUtil.getSingleRecipientIdentifiers(contacts, m.getSelfNumber()); diff --git a/src/main/java/org/asamk/signal/commands/CommandHandler.java b/src/main/java/org/asamk/signal/commands/CommandHandler.java index 8e138b3f..86fede61 100644 --- a/src/main/java/org/asamk/signal/commands/CommandHandler.java +++ b/src/main/java/org/asamk/signal/commands/CommandHandler.java @@ -20,13 +20,15 @@ public class CommandHandler { } public void handleProvisioningCommand( - final ProvisioningCommand command, final ProvisioningManager provisioningManager + final ProvisioningCommand command, + final ProvisioningManager provisioningManager ) throws CommandException { command.handleCommand(ns, provisioningManager, outputWriter); } public void handleRegistrationCommand( - final RegistrationCommand command, final RegistrationManager registrationManager + final RegistrationCommand command, + final RegistrationManager registrationManager ) throws CommandException { command.handleCommand(ns, registrationManager); } @@ -36,7 +38,8 @@ public class CommandHandler { } public void handleMultiLocalCommand( - final MultiLocalCommand command, MultiAccountManager multiAccountManager + final MultiLocalCommand command, + MultiAccountManager multiAccountManager ) throws CommandException { command.handleCommand(ns, multiAccountManager, outputWriter); } diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index 59c2245e..197e1286 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -92,7 +92,9 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { Shutdown.installHandler(); logger.info("Starting daemon in single-account mode for " + m.getSelfNumber()); @@ -117,7 +119,9 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { @Override public void handleCommand( - final Namespace ns, final MultiAccountManager c, final OutputWriter outputWriter + final Namespace ns, + final MultiAccountManager c, + final OutputWriter outputWriter ) throws CommandException { Shutdown.installHandler(); logger.info("Starting daemon in multi-account mode"); diff --git a/src/main/java/org/asamk/signal/commands/DeleteLocalAccountDataCommand.java b/src/main/java/org/asamk/signal/commands/DeleteLocalAccountDataCommand.java index 51c8582d..71f8867c 100644 --- a/src/main/java/org/asamk/signal/commands/DeleteLocalAccountDataCommand.java +++ b/src/main/java/org/asamk/signal/commands/DeleteLocalAccountDataCommand.java @@ -60,7 +60,9 @@ public class DeleteLocalAccountDataCommand implements RegistrationCommand, JsonR @Override public void handleCommand( - Map request, RegistrationManager m, JsonWriter jsonWriter + Map request, + RegistrationManager m, + JsonWriter jsonWriter ) throws CommandException { Namespace commandNamespace = new JsonRpcNamespace(request == null ? Map.of() : request); handleCommand(commandNamespace, m); diff --git a/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java b/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java index c2c18dde..8fce4dbf 100644 --- a/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java +++ b/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java @@ -33,7 +33,9 @@ public class FinishChangeNumberCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var newNumber = ns.getString("number"); final var verificationCode = ns.getString("verification-code"); diff --git a/src/main/java/org/asamk/signal/commands/FinishLinkCommand.java b/src/main/java/org/asamk/signal/commands/FinishLinkCommand.java index a0fa0ab7..c9282616 100644 --- a/src/main/java/org/asamk/signal/commands/FinishLinkCommand.java +++ b/src/main/java/org/asamk/signal/commands/FinishLinkCommand.java @@ -32,7 +32,9 @@ public class FinishLinkCommand implements JsonRpcMultiCommand registered; diff --git a/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java b/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java index f9e9f513..1ecc5aca 100644 --- a/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/JoinGroupCommand.java @@ -33,7 +33,9 @@ public class JoinGroupCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final GroupInviteLinkUrl linkUrl; var uri = ns.getString("uri"); diff --git a/src/main/java/org/asamk/signal/commands/JsonRpcDispatcherCommand.java b/src/main/java/org/asamk/signal/commands/JsonRpcDispatcherCommand.java index 0eb99351..5ce9daf4 100644 --- a/src/main/java/org/asamk/signal/commands/JsonRpcDispatcherCommand.java +++ b/src/main/java/org/asamk/signal/commands/JsonRpcDispatcherCommand.java @@ -59,7 +59,9 @@ public class JsonRpcDispatcherCommand implements LocalCommand, MultiLocalCommand @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { Shutdown.installHandler(); final var receiveMode = ns.get("receive-mode"); @@ -79,7 +81,9 @@ public class JsonRpcDispatcherCommand implements LocalCommand, MultiLocalCommand @Override public void handleCommand( - final Namespace ns, final MultiAccountManager c, final OutputWriter outputWriter + final Namespace ns, + final MultiAccountManager c, + final OutputWriter outputWriter ) throws CommandException { Shutdown.installHandler(); final var receiveMode = ns.get("receive-mode"); diff --git a/src/main/java/org/asamk/signal/commands/JsonRpcLocalCommand.java b/src/main/java/org/asamk/signal/commands/JsonRpcLocalCommand.java index d87b97cd..212c3e87 100644 --- a/src/main/java/org/asamk/signal/commands/JsonRpcLocalCommand.java +++ b/src/main/java/org/asamk/signal/commands/JsonRpcLocalCommand.java @@ -18,9 +18,7 @@ public interface JsonRpcLocalCommand extends JsonRpcSingleCommand() {}; } - default void handleCommand( - Map request, Manager m, JsonWriter jsonWriter - ) throws CommandException { + default void handleCommand(Map request, Manager m, JsonWriter jsonWriter) throws CommandException { Namespace commandNamespace = new JsonRpcNamespace(request == null ? Map.of() : request); handleCommand(commandNamespace, m, jsonWriter); } diff --git a/src/main/java/org/asamk/signal/commands/JsonRpcMultiLocalCommand.java b/src/main/java/org/asamk/signal/commands/JsonRpcMultiLocalCommand.java index 6082f6c5..f14edeff 100644 --- a/src/main/java/org/asamk/signal/commands/JsonRpcMultiLocalCommand.java +++ b/src/main/java/org/asamk/signal/commands/JsonRpcMultiLocalCommand.java @@ -19,7 +19,9 @@ public interface JsonRpcMultiLocalCommand extends JsonRpcMultiCommand request, MultiAccountManager c, JsonWriter jsonWriter + Map request, + MultiAccountManager c, + JsonWriter jsonWriter ) throws CommandException { Namespace commandNamespace = new JsonRpcNamespace(request == null ? Map.of() : request); handleCommand(commandNamespace, c, jsonWriter); diff --git a/src/main/java/org/asamk/signal/commands/LinkCommand.java b/src/main/java/org/asamk/signal/commands/LinkCommand.java index ed0cc0ed..7c29a3a9 100644 --- a/src/main/java/org/asamk/signal/commands/LinkCommand.java +++ b/src/main/java/org/asamk/signal/commands/LinkCommand.java @@ -33,7 +33,9 @@ public class LinkCommand implements ProvisioningCommand { @Override public void handleCommand( - final Namespace ns, final ProvisioningManager m, final OutputWriter outputWriter + final Namespace ns, + final ProvisioningManager m, + final OutputWriter outputWriter ) throws CommandException { final var writer = (PlainTextWriter) outputWriter; diff --git a/src/main/java/org/asamk/signal/commands/ListAccountsCommand.java b/src/main/java/org/asamk/signal/commands/ListAccountsCommand.java index b19a1686..b9542934 100644 --- a/src/main/java/org/asamk/signal/commands/ListAccountsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListAccountsCommand.java @@ -23,7 +23,9 @@ public class ListAccountsCommand implements JsonRpcMultiLocalCommand { @Override public void handleCommand( - final Namespace ns, final MultiAccountManager c, final OutputWriter outputWriter + final Namespace ns, + final MultiAccountManager c, + final OutputWriter outputWriter ) throws CommandException { final var accountNumbers = c.getAccountNumbers(); switch (outputWriter) { diff --git a/src/main/java/org/asamk/signal/commands/ListContactsCommand.java b/src/main/java/org/asamk/signal/commands/ListContactsCommand.java index 3865d829..81a1bcb1 100644 --- a/src/main/java/org/asamk/signal/commands/ListContactsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListContactsCommand.java @@ -47,7 +47,9 @@ public class ListContactsCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var allRecipients = Boolean.TRUE.equals(ns.getBoolean("all-recipients")); final var blocked = ns.getBoolean("blocked"); diff --git a/src/main/java/org/asamk/signal/commands/ListDevicesCommand.java b/src/main/java/org/asamk/signal/commands/ListDevicesCommand.java index c4ae6e72..e9b03a17 100644 --- a/src/main/java/org/asamk/signal/commands/ListDevicesCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListDevicesCommand.java @@ -33,7 +33,9 @@ public class ListDevicesCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { List devices; try { diff --git a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java index debe4ad0..62bb4f14 100644 --- a/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListGroupsCommand.java @@ -48,9 +48,7 @@ public class ListGroupsCommand implements JsonRpcLocalCommand { .collect(Collectors.toSet()); } - private static void printGroupPlainText( - PlainTextWriter writer, Group group, boolean detailed - ) { + private static void printGroupPlainText(PlainTextWriter writer, Group group, boolean detailed) { if (detailed) { final var groupInviteLink = group.groupInviteLinkUrl(); @@ -79,7 +77,9 @@ public class ListGroupsCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { var groups = m.getGroups(); diff --git a/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java b/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java index 6b49a812..70b939d6 100644 --- a/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListIdentitiesCommand.java @@ -46,7 +46,9 @@ public class ListIdentitiesCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { var number = ns.getString("number"); diff --git a/src/main/java/org/asamk/signal/commands/ListStickerPacksCommand.java b/src/main/java/org/asamk/signal/commands/ListStickerPacksCommand.java index 233eb4b5..db081211 100644 --- a/src/main/java/org/asamk/signal/commands/ListStickerPacksCommand.java +++ b/src/main/java/org/asamk/signal/commands/ListStickerPacksCommand.java @@ -27,7 +27,9 @@ public class ListStickerPacksCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager c, final OutputWriter outputWriter + final Namespace ns, + final Manager c, + final OutputWriter outputWriter ) throws CommandException { final var stickerPacks = c.getStickerPacks(); switch (outputWriter) { diff --git a/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java index f1b8ef5b..346398a5 100644 --- a/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/QuitGroupCommand.java @@ -44,7 +44,9 @@ public class QuitGroupCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var groupId = CommandUtil.getGroupId(ns.getString("group-id")); diff --git a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java index ba126f7e..37da35e1 100644 --- a/src/main/java/org/asamk/signal/commands/ReceiveCommand.java +++ b/src/main/java/org/asamk/signal/commands/ReceiveCommand.java @@ -66,7 +66,9 @@ public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommandgetList("recipient"); diff --git a/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java b/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java index aca52778..253cb7ad 100644 --- a/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java +++ b/src/main/java/org/asamk/signal/commands/RemoveContactCommand.java @@ -31,7 +31,9 @@ public class RemoveContactCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { var recipientString = ns.getString("recipient"); var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber()); diff --git a/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java b/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java index 68a53616..eab0a6be 100644 --- a/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java +++ b/src/main/java/org/asamk/signal/commands/RemoveDeviceCommand.java @@ -30,7 +30,9 @@ public class RemoveDeviceCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { try { final var deviceId = ns.getInt("device-id"); diff --git a/src/main/java/org/asamk/signal/commands/RemovePinCommand.java b/src/main/java/org/asamk/signal/commands/RemovePinCommand.java index 8085bc65..bc55f9d2 100644 --- a/src/main/java/org/asamk/signal/commands/RemovePinCommand.java +++ b/src/main/java/org/asamk/signal/commands/RemovePinCommand.java @@ -27,7 +27,9 @@ public class RemovePinCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { try { m.setRegistrationLockPin(Optional.empty()); diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 004ba506..9784171a 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -108,7 +108,9 @@ public class SendCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var notifySelf = Boolean.TRUE.equals(ns.getBoolean("notify-self")); final var isNoteToSelf = Boolean.TRUE.equals(ns.getBoolean("note-to-self")); @@ -257,7 +259,8 @@ public class SendCommand implements JsonRpcLocalCommand { } private List parseMentions( - final String selfNumber, final List mentionStrings + final String selfNumber, + final List mentionStrings ) throws UserErrorException { final var mentionPattern = Pattern.compile("(\\d+):(\\d+):(.+)"); final var mentions = new ArrayList(); diff --git a/src/main/java/org/asamk/signal/commands/SendContactsCommand.java b/src/main/java/org/asamk/signal/commands/SendContactsCommand.java index a5128937..a67950c1 100644 --- a/src/main/java/org/asamk/signal/commands/SendContactsCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendContactsCommand.java @@ -24,7 +24,9 @@ public class SendContactsCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { try { m.sendContacts(); diff --git a/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java b/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java index 53cd3ea7..732fd749 100644 --- a/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendMessageRequestResponseCommand.java @@ -31,7 +31,9 @@ public class SendMessageRequestResponseCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var recipientStrings = ns.getList("recipient"); final var groupIdStrings = ns.getList("group-id"); diff --git a/src/main/java/org/asamk/signal/commands/SendPaymentNotificationCommand.java b/src/main/java/org/asamk/signal/commands/SendPaymentNotificationCommand.java index 77181e3e..0814658e 100644 --- a/src/main/java/org/asamk/signal/commands/SendPaymentNotificationCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendPaymentNotificationCommand.java @@ -31,7 +31,9 @@ public class SendPaymentNotificationCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var recipientString = ns.getString("recipient"); final var recipientIdentifier = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber()); diff --git a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java index 56fa81f2..da877d76 100644 --- a/src/main/java/org/asamk/signal/commands/SendReactionCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendReactionCommand.java @@ -53,7 +53,9 @@ public class SendReactionCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var isNoteToSelf = Boolean.TRUE.equals(ns.getBoolean("note-to-self")); final var recipientStrings = ns.getList("recipient"); diff --git a/src/main/java/org/asamk/signal/commands/SendReceiptCommand.java b/src/main/java/org/asamk/signal/commands/SendReceiptCommand.java index adae7c4d..93c8a564 100644 --- a/src/main/java/org/asamk/signal/commands/SendReceiptCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendReceiptCommand.java @@ -35,7 +35,9 @@ public class SendReceiptCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var recipientString = ns.getString("recipient"); final var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber()); diff --git a/src/main/java/org/asamk/signal/commands/SendSyncRequestCommand.java b/src/main/java/org/asamk/signal/commands/SendSyncRequestCommand.java index 1f35ba2b..eda364c0 100644 --- a/src/main/java/org/asamk/signal/commands/SendSyncRequestCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendSyncRequestCommand.java @@ -24,7 +24,9 @@ public class SendSyncRequestCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { try { m.requestAllSyncData(); diff --git a/src/main/java/org/asamk/signal/commands/SendTypingCommand.java b/src/main/java/org/asamk/signal/commands/SendTypingCommand.java index 9293ec6a..a9e699cc 100644 --- a/src/main/java/org/asamk/signal/commands/SendTypingCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendTypingCommand.java @@ -38,7 +38,9 @@ public class SendTypingCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var recipientStrings = ns.getList("recipient"); final var groupIdStrings = ns.getList("group-id"); diff --git a/src/main/java/org/asamk/signal/commands/SetPinCommand.java b/src/main/java/org/asamk/signal/commands/SetPinCommand.java index f6b5f7ad..4d4a458f 100644 --- a/src/main/java/org/asamk/signal/commands/SetPinCommand.java +++ b/src/main/java/org/asamk/signal/commands/SetPinCommand.java @@ -29,7 +29,9 @@ public class SetPinCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { try { var registrationLockPin = ns.getString("pin"); diff --git a/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java b/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java index 84fcf527..c75b3077 100644 --- a/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java +++ b/src/main/java/org/asamk/signal/commands/StartChangeNumberCommand.java @@ -39,7 +39,9 @@ public class StartChangeNumberCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var newNumber = ns.getString("number"); final var voiceVerification = Boolean.TRUE.equals(ns.getBoolean("voice")); diff --git a/src/main/java/org/asamk/signal/commands/StartLinkCommand.java b/src/main/java/org/asamk/signal/commands/StartLinkCommand.java index ac269e95..895b5dae 100644 --- a/src/main/java/org/asamk/signal/commands/StartLinkCommand.java +++ b/src/main/java/org/asamk/signal/commands/StartLinkCommand.java @@ -23,7 +23,9 @@ public class StartLinkCommand implements JsonRpcMultiCommand { @Override public void handleCommand( - final Void request, final MultiAccountManager m, final JsonWriter jsonWriter + final Void request, + final MultiAccountManager m, + final JsonWriter jsonWriter ) throws CommandException { final URI deviceLinkUri; try { diff --git a/src/main/java/org/asamk/signal/commands/TrustCommand.java b/src/main/java/org/asamk/signal/commands/TrustCommand.java index a130c03c..7f18507a 100644 --- a/src/main/java/org/asamk/signal/commands/TrustCommand.java +++ b/src/main/java/org/asamk/signal/commands/TrustCommand.java @@ -33,7 +33,9 @@ public class TrustCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { var recipientString = ns.getString("recipient"); var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber()); diff --git a/src/main/java/org/asamk/signal/commands/UnblockCommand.java b/src/main/java/org/asamk/signal/commands/UnblockCommand.java index 1299d0db..a6b5e6b3 100644 --- a/src/main/java/org/asamk/signal/commands/UnblockCommand.java +++ b/src/main/java/org/asamk/signal/commands/UnblockCommand.java @@ -35,7 +35,9 @@ public class UnblockCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var contacts = ns.getList("recipient"); final var recipients = CommandUtil.getSingleRecipientIdentifiers(contacts, m.getSelfNumber()); diff --git a/src/main/java/org/asamk/signal/commands/UnregisterCommand.java b/src/main/java/org/asamk/signal/commands/UnregisterCommand.java index 83694953..e044f691 100644 --- a/src/main/java/org/asamk/signal/commands/UnregisterCommand.java +++ b/src/main/java/org/asamk/signal/commands/UnregisterCommand.java @@ -28,7 +28,9 @@ public class UnregisterCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { try { if (Boolean.TRUE.equals(ns.getBoolean("delete-account"))) { diff --git a/src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java b/src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java index a750bc88..9c8c18b8 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateAccountCommand.java @@ -47,7 +47,9 @@ public class UpdateAccountCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var deviceName = ns.getString("device-name"); final var unrestrictedUnidentifiedSender = ns.getBoolean("unrestricted-unidentified-sender"); diff --git a/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java b/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java index d1feff8a..03882e25 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateConfigurationCommand.java @@ -38,7 +38,9 @@ public class UpdateConfigurationCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var readReceipts = ns.getBoolean("read-receipts"); final var unidentifiedDeliveryIndicators = ns.getBoolean("unidentified-delivery-indicators"); diff --git a/src/main/java/org/asamk/signal/commands/UpdateContactCommand.java b/src/main/java/org/asamk/signal/commands/UpdateContactCommand.java index 7ff91e9b..815c15da 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateContactCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateContactCommand.java @@ -33,7 +33,9 @@ public class UpdateContactCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { var recipientString = ns.getString("recipient"); var recipient = CommandUtil.getSingleRecipientIdentifier(recipientString, m.getSelfNumber()); diff --git a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java index f29f0732..1231d3d4 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java @@ -102,7 +102,9 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { final var groupIdString = ns.getString("group-id"); var groupId = CommandUtil.getGroupId(groupIdString); @@ -180,7 +182,9 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand { } private void outputResult( - final OutputWriter outputWriter, final SendGroupMessageResults results, final GroupId groupId + final OutputWriter outputWriter, + final SendGroupMessageResults results, + final GroupId groupId ) { switch (outputWriter) { case PlainTextWriter writer -> { diff --git a/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java b/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java index cea2da0d..b122f58c 100644 --- a/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java +++ b/src/main/java/org/asamk/signal/commands/UpdateProfileCommand.java @@ -36,7 +36,9 @@ public class UpdateProfileCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { var givenName = ns.getString("given-name"); var familyName = ns.getString("family-name"); diff --git a/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java b/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java index b4c09b41..710f4d35 100644 --- a/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java +++ b/src/main/java/org/asamk/signal/commands/UploadStickerPackCommand.java @@ -36,7 +36,9 @@ public class UploadStickerPackCommand implements JsonRpcLocalCommand { @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { var path = new File(ns.getString("path")); diff --git a/src/main/java/org/asamk/signal/commands/VerifyCommand.java b/src/main/java/org/asamk/signal/commands/VerifyCommand.java index 54d05a4d..a8543c5a 100644 --- a/src/main/java/org/asamk/signal/commands/VerifyCommand.java +++ b/src/main/java/org/asamk/signal/commands/VerifyCommand.java @@ -55,13 +55,17 @@ public class VerifyCommand implements RegistrationCommand, JsonRpcRegistrationCo @Override public void handleCommand( - final VerifyParams request, final RegistrationManager m, final JsonWriter jsonWriter + final VerifyParams request, + final RegistrationManager m, + final JsonWriter jsonWriter ) throws CommandException { verify(m, request.verificationCode(), request.pin()); } private void verify( - final RegistrationManager m, final String verificationCode, final String pin + final RegistrationManager m, + final String verificationCode, + final String pin ) throws UserErrorException, IOErrorException { try { m.verifyAccount(verificationCode, pin); diff --git a/src/main/java/org/asamk/signal/commands/VersionCommand.java b/src/main/java/org/asamk/signal/commands/VersionCommand.java index 01f1a2d7..498e5e71 100644 --- a/src/main/java/org/asamk/signal/commands/VersionCommand.java +++ b/src/main/java/org/asamk/signal/commands/VersionCommand.java @@ -35,14 +35,18 @@ public class VersionCommand implements JsonRpcLocalCommand, JsonRpcMultiLocalCom @Override public void handleCommand( - final Namespace ns, final Manager m, final OutputWriter outputWriter + final Namespace ns, + final Manager m, + final OutputWriter outputWriter ) throws CommandException { outputVersion(outputWriter); } @Override public void handleCommand( - final Namespace ns, final MultiAccountManager c, final OutputWriter outputWriter + final Namespace ns, + final MultiAccountManager c, + final OutputWriter outputWriter ) throws CommandException { outputVersion(outputWriter); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusCommandHandler.java b/src/main/java/org/asamk/signal/dbus/DbusCommandHandler.java index 5e272895..546a433f 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusCommandHandler.java +++ b/src/main/java/org/asamk/signal/dbus/DbusCommandHandler.java @@ -87,7 +87,8 @@ public class DbusCommandHandler { } private static String tryGetSingleAccountObjectPath( - final DBusConnection dBusConn, final String busname + final DBusConnection dBusConn, + final String busname ) throws DBusException, CommandException { var control = dBusConn.getRemoteObject(busname, DbusConfig.getObjectPath(), SignalControl.class); try { diff --git a/src/main/java/org/asamk/signal/dbus/DbusHandler.java b/src/main/java/org/asamk/signal/dbus/DbusHandler.java index e71d7281..b6799de7 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusHandler.java +++ b/src/main/java/org/asamk/signal/dbus/DbusHandler.java @@ -28,7 +28,10 @@ public class DbusHandler implements AutoCloseable { private final boolean noReceiveOnStart; public DbusHandler( - final boolean isDbusSystem, final String busname, final Manager m, final boolean noReceiveOnStart + final boolean isDbusSystem, + final String busname, + final Manager m, + final boolean noReceiveOnStart ) { this.isDbusSystem = isDbusSystem; this.dbusRunner = (connection) -> { diff --git a/src/main/java/org/asamk/signal/dbus/DbusInterfacePropertiesHandler.java b/src/main/java/org/asamk/signal/dbus/DbusInterfacePropertiesHandler.java index d3c2ca83..73cfab67 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusInterfacePropertiesHandler.java +++ b/src/main/java/org/asamk/signal/dbus/DbusInterfacePropertiesHandler.java @@ -12,9 +12,7 @@ public class DbusInterfacePropertiesHandler { private final String interfaceName; private final List> properties; - public DbusInterfacePropertiesHandler( - final String interfaceName, final List> properties - ) { + public DbusInterfacePropertiesHandler(final String interfaceName, final List> properties) { this.interfaceName = interfaceName; this.properties = properties; } diff --git a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java index bd5bdfb2..159407e1 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java @@ -201,14 +201,18 @@ public class DbusManagerImpl implements Manager { @Override public void startChangeNumber( - final String newNumber, final boolean voiceVerification, final String captcha + final String newNumber, + final boolean voiceVerification, + final String captcha ) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException { throw new UnsupportedOperationException(); } @Override public void finishChangeNumber( - final String newNumber, final String verificationCode, final String pin + final String newNumber, + final String verificationCode, + final String pin ) throws IncorrectPinException, PinLockedException, IOException { throw new UnsupportedOperationException(); } @@ -270,7 +274,8 @@ public class DbusManagerImpl implements Manager { @Override public SendGroupMessageResults quitGroup( - final GroupId groupId, final Set groupAdmins + final GroupId groupId, + final Set groupAdmins ) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException { if (!groupAdmins.isEmpty()) { throw new UnsupportedOperationException(); @@ -296,7 +301,9 @@ public class DbusManagerImpl implements Manager { @Override public Pair createGroup( - final String name, final Set members, final String avatarFile + final String name, + final Set members, + final String avatarFile ) throws IOException, AttachmentInvalidException { final var newGroupId = signal.createGroup(emptyIfNull(name), members.stream().map(RecipientIdentifier.Single::getIdentifier).toList(), @@ -306,7 +313,8 @@ public class DbusManagerImpl implements Manager { @Override public SendGroupMessageResults updateGroup( - final GroupId groupId, final UpdateGroup updateGroup + final GroupId groupId, + final UpdateGroup updateGroup ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException { final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class); if (updateGroup.getName() != null) { @@ -380,7 +388,8 @@ public class DbusManagerImpl implements Manager { @Override public SendMessageResults sendTypingMessage( - final TypingAction action, final Set recipients + final TypingAction action, + final Set recipients ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { return handleMessage(recipients, numbers -> { numbers.forEach(n -> signal.sendTyping(n, action == TypingAction.STOP)); @@ -395,24 +404,22 @@ public class DbusManagerImpl implements Manager { } @Override - public SendMessageResults sendReadReceipt( - final RecipientIdentifier.Single sender, final List messageIds - ) { + public SendMessageResults sendReadReceipt(final RecipientIdentifier.Single sender, final List messageIds) { signal.sendReadReceipt(sender.getIdentifier(), messageIds); return new SendMessageResults(0, Map.of()); } @Override - public SendMessageResults sendViewedReceipt( - final RecipientIdentifier.Single sender, final List messageIds - ) { + public SendMessageResults sendViewedReceipt(final RecipientIdentifier.Single sender, final List messageIds) { signal.sendViewedReceipt(sender.getIdentifier(), messageIds); return new SendMessageResults(0, Map.of()); } @Override public SendMessageResults sendMessage( - final Message message, final Set recipients, final boolean notifySelf + final Message message, + final Set recipients, + final boolean notifySelf ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { return handleMessage(recipients, numbers -> signal.sendMessage(message.messageText(), message.attachments(), numbers), @@ -422,14 +429,17 @@ public class DbusManagerImpl implements Manager { @Override public SendMessageResults sendEditMessage( - final Message message, final Set recipients, final long editTargetTimestamp + final Message message, + final Set recipients, + final long editTargetTimestamp ) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException { throw new UnsupportedOperationException(); } @Override public SendMessageResults sendRemoteDeleteMessage( - final long targetSentTimestamp, final Set recipients + final long targetSentTimestamp, + final Set recipients ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { return handleMessage(recipients, numbers -> signal.sendRemoteDeleteMessage(targetSentTimestamp, numbers), @@ -466,7 +476,9 @@ public class DbusManagerImpl implements Manager { @Override public SendMessageResults sendPaymentNotificationMessage( - final byte[] receipt, final String note, final RecipientIdentifier.Single recipient + final byte[] receipt, + final String note, + final RecipientIdentifier.Single recipient ) throws IOException { final var timestamp = signal.sendPaymentNotification(receipt, note, recipient.getIdentifier()); return new SendMessageResults(timestamp, Map.of()); @@ -502,14 +514,17 @@ public class DbusManagerImpl implements Manager { @Override public void setContactName( - final RecipientIdentifier.Single recipient, final String givenName, final String familyName + final RecipientIdentifier.Single recipient, + final String givenName, + final String familyName ) throws NotPrimaryDeviceException { signal.setContactName(recipient.getIdentifier(), givenName); } @Override public void setContactsBlocked( - final Collection recipients, final boolean blocked + final Collection recipients, + final boolean blocked ) throws NotPrimaryDeviceException, IOException { for (final var recipient : recipients) { signal.setContactBlocked(recipient.getIdentifier(), blocked); @@ -518,7 +533,8 @@ public class DbusManagerImpl implements Manager { @Override public void setGroupsBlocked( - final Collection groupIds, final boolean blocked + final Collection groupIds, + final boolean blocked ) throws GroupNotFoundException, IOException { for (final var groupId : groupIds) { setGroupProperty(groupId, "IsBlocked", blocked); @@ -532,7 +548,8 @@ public class DbusManagerImpl implements Manager { @Override public void setExpirationTimer( - final RecipientIdentifier.Single recipient, final int messageExpirationTimer + final RecipientIdentifier.Single recipient, + final int messageExpirationTimer ) throws IOException { signal.setExpirationTimer(recipient.getIdentifier(), messageExpirationTimer); } @@ -597,7 +614,9 @@ public class DbusManagerImpl implements Manager { @Override public void receiveMessages( - Optional timeout, Optional maxMessages, ReceiveMessageHandler handler + Optional timeout, + Optional maxMessages, + ReceiveMessageHandler handler ) throws IOException, AlreadyReceivingException { if (receiveThread != null) { throw new AlreadyReceivingException("Already receiving message."); @@ -784,7 +803,8 @@ public class DbusManagerImpl implements Manager { @Override public boolean trustIdentityVerified( - final RecipientIdentifier.Single recipient, final IdentityVerificationCode verificationCode + final RecipientIdentifier.Single recipient, + final IdentityVerificationCode verificationCode ) { throw new UnsupportedOperationException(); } @@ -1124,9 +1144,7 @@ public class DbusManagerImpl implements Manager { } @SuppressWarnings("unchecked") - private T getValue( - final Map> stringVariantMap, final String field - ) { + private T getValue(final Map> stringVariantMap, final String field) { return (T) stringVariantMap.get(field).getValue(); } } diff --git a/src/main/java/org/asamk/signal/dbus/DbusMultiAccountManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusMultiAccountManagerImpl.java index 27e28779..2b9a0a77 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusMultiAccountManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusMultiAccountManagerImpl.java @@ -34,7 +34,9 @@ public class DbusMultiAccountManagerImpl implements MultiAccountManager { private final String busname; public DbusMultiAccountManagerImpl( - final SignalControl signalControl, DBusConnection connection, final String busname + final SignalControl signalControl, + DBusConnection connection, + final String busname ) { this.signalControl = signalControl; this.connection = connection; diff --git a/src/main/java/org/asamk/signal/dbus/DbusProvisioningManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusProvisioningManagerImpl.java index 2e026c8a..e3cf4c12 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusProvisioningManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusProvisioningManagerImpl.java @@ -27,7 +27,9 @@ public class DbusProvisioningManagerImpl implements ProvisioningManager { } public DbusProvisioningManagerImpl( - final SignalControl signalControl, DBusConnection connection, URI deviceLinkUri + final SignalControl signalControl, + DBusConnection connection, + URI deviceLinkUri ) { this.signalControl = signalControl; this.connection = connection; diff --git a/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java b/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java index 788e0c91..0c02c680 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusRegistrationManagerImpl.java @@ -27,7 +27,9 @@ public class DbusRegistrationManagerImpl implements RegistrationManager { @Override public void register( - final boolean voiceVerification, final String captcha, final boolean forceRegister + final boolean voiceVerification, + final String captcha, + final boolean forceRegister ) throws IOException, CaptchaRequiredException { if (forceRegister) { throw new UnsupportedOperationException(); @@ -41,7 +43,8 @@ public class DbusRegistrationManagerImpl implements RegistrationManager { @Override public void verifyAccount( - final String verificationCode, final String pin + final String verificationCode, + final String pin ) throws IOException, PinLockedException, IncorrectPinException { if (pin == null) { signalControl.verify(number, verificationCode); diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index def343a9..cf6999a9 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -49,14 +49,17 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { @Override public void register( - final String number, final boolean voiceVerification + final String number, + final boolean voiceVerification ) throws Error.Failure, Error.InvalidNumber { registerWithCaptcha(number, voiceVerification, null); } @Override public void registerWithCaptcha( - final String number, final boolean voiceVerification, final String captcha + final String number, + final boolean voiceVerification, + final String captcha ) throws Error.Failure, Error.InvalidNumber { if (!Manager.isValidNumber(number, null)) { throw new SignalControl.Error.InvalidNumber( @@ -86,7 +89,9 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { @Override public void verifyWithPin( - final String number, final String verificationCode, final String pin + final String number, + final String verificationCode, + final String pin ) throws Error.Failure, Error.InvalidNumber { try (final RegistrationManager registrationManager = c.getNewRegistrationManager(number)) { registrationManager.verifyAccount(verificationCode, pin); diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index 1b8b7825..bbae26b5 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -81,7 +81,10 @@ public class DbusSignalImpl implements Signal, AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(DbusSignalImpl.class); public DbusSignalImpl( - final Manager m, DBusConnection connection, final String objectPath, final boolean noReceiveOnStart + final Manager m, + DBusConnection connection, + final String objectPath, + final boolean noReceiveOnStart ) { this.m = m; this.connection = connection; @@ -258,16 +261,12 @@ public class DbusSignalImpl implements Signal, AutoCloseable { } @Override - public long sendRemoteDeleteMessage( - final long targetSentTimestamp, final String recipient - ) { + public long sendRemoteDeleteMessage(final long targetSentTimestamp, final String recipient) { return sendRemoteDeleteMessage(targetSentTimestamp, List.of(recipient)); } @Override - public long sendRemoteDeleteMessage( - final long targetSentTimestamp, final List recipients - ) { + public long sendRemoteDeleteMessage(final long targetSentTimestamp, final List recipients) { try { final var results = m.sendRemoteDeleteMessage(targetSentTimestamp, getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream() @@ -323,7 +322,9 @@ public class DbusSignalImpl implements Signal, AutoCloseable { @Override public long sendPaymentNotification( - final byte[] receipt, final String note, final String recipient + final byte[] receipt, + final String note, + final String recipient ) throws Error.Failure { try { final var results = m.sendPaymentNotificationMessage(receipt, @@ -338,7 +339,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { @Override public void sendTyping( - final String recipient, final boolean stop + final String recipient, + final boolean stop ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity { try { final var results = m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START, @@ -355,7 +357,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { @Override public void sendReadReceipt( - final String recipient, final List messageIds + final String recipient, + final List messageIds ) throws Error.Failure, Error.UntrustedIdentity { final var results = m.sendReadReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds); checkSendMessageResults(results); @@ -363,7 +366,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { @Override public void sendViewedReceipt( - final String recipient, final List messageIds + final String recipient, + final List messageIds ) throws Error.Failure, Error.UntrustedIdentity { final var results = m.sendViewedReceipt(getSingleRecipientIdentifier(recipient, m.getSelfNumber()), messageIds); checkSendMessageResults(results); @@ -389,7 +393,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { @Override public long sendNoteToSelfMessage( - final String messageText, final List attachments + final String messageText, + final List attachments ) throws Error.AttachmentInvalid, Error.Failure, Error.UntrustedIdentity { try { final var message = new Message(messageText, @@ -461,7 +466,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { @Override public void sendGroupTyping( - final byte[] groupId, final boolean stop + final byte[] groupId, + final boolean stop ) throws Error.Failure, Error.GroupNotFound, Error.UntrustedIdentity { try { final var results = m.sendTypingMessage(stop ? TypingAction.STOP : TypingAction.START, @@ -475,9 +481,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { } @Override - public long sendGroupRemoteDeleteMessage( - final long targetSentTimestamp, final byte[] groupId - ) { + public long sendGroupRemoteDeleteMessage(final long targetSentTimestamp, final byte[] groupId) { try { final var results = m.sendRemoteDeleteMessage(targetSentTimestamp, Set.of(getGroupRecipientIdentifier(groupId))); @@ -621,7 +625,9 @@ public class DbusSignalImpl implements Signal, AutoCloseable { @Override public byte[] createGroup( - final String name, final List members, final String avatar + final String name, + final List members, + final String avatar ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber { return updateGroupInternal(new byte[0], name, members, avatar); } @@ -900,7 +906,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { } private static void checkGroupSendMessageResults( - long timestamp, Collection results + long timestamp, + Collection results ) throws DBusExecutionException { if (results.size() == 1) { checkSendMessageResult(timestamp, results.stream().findFirst().get()); @@ -927,7 +934,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { } private static Set getSingleRecipientIdentifiers( - final Collection recipientStrings, final String localNumber + final Collection recipientStrings, + final String localNumber ) throws DBusExecutionException { final var identifiers = new HashSet(); for (var recipientString : recipientStrings) { @@ -937,7 +945,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable { } private static RecipientIdentifier.Single getSingleRecipientIdentifier( - final String recipientString, final String localNumber + final String recipientString, + final String localNumber ) throws DBusExecutionException { try { return RecipientIdentifier.Single.fromString(recipientString, localNumber); diff --git a/src/main/java/org/asamk/signal/http/HttpServerHandler.java b/src/main/java/org/asamk/signal/http/HttpServerHandler.java index c9c8fa16..e1780665 100644 --- a/src/main/java/org/asamk/signal/http/HttpServerHandler.java +++ b/src/main/java/org/asamk/signal/http/HttpServerHandler.java @@ -240,7 +240,9 @@ public class HttpServerHandler implements AutoCloseable { } private List> subscribeReceiveHandlers( - final List managers, final ServerSentEventSender sender, Callable unsubscribe + final List managers, + final ServerSentEventSender sender, + Callable unsubscribe ) { return managers.stream().map(m1 -> { final var receiveMessageHandler = new JsonReceiveMessageHandler(m1, s -> { diff --git a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java index 7ba4b307..8ab8ea83 100644 --- a/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java +++ b/src/main/java/org/asamk/signal/json/JsonMessageEnvelope.java @@ -28,9 +28,7 @@ public record JsonMessageEnvelope( @JsonInclude(JsonInclude.Include.NON_NULL) JsonTypingMessage typingMessage ) { - public static JsonMessageEnvelope from( - MessageEnvelope envelope, Throwable exception, Manager m - ) { + public static JsonMessageEnvelope from(MessageEnvelope envelope, Throwable exception, Manager m) { final RecipientAddress sourceAddress; final Integer sourceDevice; if (envelope.sourceAddress().isPresent()) { diff --git a/src/main/java/org/asamk/signal/jsonrpc/JsonRpcRequest.java b/src/main/java/org/asamk/signal/jsonrpc/JsonRpcRequest.java index e90d740c..29866c1a 100644 --- a/src/main/java/org/asamk/signal/jsonrpc/JsonRpcRequest.java +++ b/src/main/java/org/asamk/signal/jsonrpc/JsonRpcRequest.java @@ -38,7 +38,9 @@ public final class JsonRpcRequest extends JsonRpcMessage { private ValueNode id; public static JsonRpcRequest forNotification( - final String method, final ContainerNode params, final ValueNode id + final String method, + final ContainerNode params, + final ValueNode id ) { return new JsonRpcRequest("2.0", method, params, id); } @@ -47,7 +49,10 @@ public final class JsonRpcRequest extends JsonRpcMessage { } private JsonRpcRequest( - final String jsonrpc, final String method, final ContainerNode params, final ValueNode id + final String jsonrpc, + final String method, + final ContainerNode params, + final ValueNode id ) { this.jsonrpc = jsonrpc; this.method = method; diff --git a/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java b/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java index 96456a4e..48eb4a45 100644 --- a/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java +++ b/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcCommandHandler.java @@ -56,7 +56,9 @@ public class SignalJsonRpcCommandHandler { } public JsonNode handleRequest( - final ObjectMapper objectMapper, final String method, ContainerNode params + final ObjectMapper objectMapper, + final String method, + ContainerNode params ) throws JsonRpcException { var command = getCommand(method); if (c != null) { @@ -197,7 +199,9 @@ public class SignalJsonRpcCommandHandler { } private JsonNode runCommand( - final ObjectMapper objectMapper, final ContainerNode params, final CommandRunner command + final ObjectMapper objectMapper, + final ContainerNode params, + final CommandRunner command ) throws JsonRpcException { final Object[] result = {null}; final JsonWriter commandJsonWriter = s -> { diff --git a/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcDispatcherHandler.java b/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcDispatcherHandler.java index c8b4bc15..5d3fa261 100644 --- a/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcDispatcherHandler.java +++ b/src/main/java/org/asamk/signal/jsonrpc/SignalJsonRpcDispatcherHandler.java @@ -43,7 +43,9 @@ public class SignalJsonRpcDispatcherHandler { private SignalJsonRpcCommandHandler commandHandler; public SignalJsonRpcDispatcherHandler( - final JsonWriter jsonWriter, final Supplier lineSupplier, final boolean noReceiveOnStart + final JsonWriter jsonWriter, + final Supplier lineSupplier, + final boolean noReceiveOnStart ) { this.noReceiveOnStart = noReceiveOnStart; this.objectMapper = Util.createJsonObjectMapper(); @@ -167,7 +169,9 @@ public class SignalJsonRpcDispatcherHandler { @Override public void handleCommand( - final Void request, final Manager m, final JsonWriter jsonWriter + final Void request, + final Manager m, + final JsonWriter jsonWriter ) throws CommandException { final var subscriptionId = subscribeReceive(m, false); jsonWriter.write(subscriptionId); @@ -175,7 +179,9 @@ public class SignalJsonRpcDispatcherHandler { @Override public void handleCommand( - final Void request, final MultiAccountManager c, final JsonWriter jsonWriter + final Void request, + final MultiAccountManager c, + final JsonWriter jsonWriter ) throws CommandException { final var subscriptionId = subscribeReceive(c.getManagers(), false); jsonWriter.write(subscriptionId); @@ -196,7 +202,9 @@ public class SignalJsonRpcDispatcherHandler { @Override public void handleCommand( - final JsonNode request, final Manager m, final JsonWriter jsonWriter + final JsonNode request, + final Manager m, + final JsonWriter jsonWriter ) throws CommandException { final var subscriptionId = getSubscriptionId(request); if (subscriptionId == null) { @@ -210,7 +218,9 @@ public class SignalJsonRpcDispatcherHandler { @Override public void handleCommand( - final JsonNode request, final MultiAccountManager c, final JsonWriter jsonWriter + final JsonNode request, + final MultiAccountManager c, + final JsonWriter jsonWriter ) throws CommandException { final var subscriptionId = getSubscriptionId(request); if (subscriptionId == null) { diff --git a/src/main/java/org/asamk/signal/jsonrpc/SocketHandler.java b/src/main/java/org/asamk/signal/jsonrpc/SocketHandler.java index dc620bc8..2e0d50e4 100644 --- a/src/main/java/org/asamk/signal/jsonrpc/SocketHandler.java +++ b/src/main/java/org/asamk/signal/jsonrpc/SocketHandler.java @@ -39,7 +39,9 @@ public class SocketHandler implements AutoCloseable { } public SocketHandler( - final ServerSocketChannel serverChannel, final MultiAccountManager c, final boolean noReceiveOnStart + final ServerSocketChannel serverChannel, + final MultiAccountManager c, + final boolean noReceiveOnStart ) { this.serverChannel = serverChannel; this.socketHandler = channel -> getSignalJsonRpcDispatcherHandler(channel).handleConnection(c); diff --git a/src/main/java/org/asamk/signal/logging/LogConfigurator.java b/src/main/java/org/asamk/signal/logging/LogConfigurator.java index f6e23b70..2ee7b5f0 100644 --- a/src/main/java/org/asamk/signal/logging/LogConfigurator.java +++ b/src/main/java/org/asamk/signal/logging/LogConfigurator.java @@ -83,7 +83,8 @@ public class LogConfigurator extends ContextAwareBase implements Configurator { } private ConsoleAppender createLoggingConsoleAppender( - final LoggerContext lc, final LayoutWrappingEncoder layoutEncoder + final LoggerContext lc, + final LayoutWrappingEncoder layoutEncoder ) { return new ConsoleAppender<>() {{ setContext(lc); @@ -95,7 +96,8 @@ public class LogConfigurator extends ContextAwareBase implements Configurator { } private FileAppender createLoggingFileAppender( - final LoggerContext lc, final LayoutWrappingEncoder layoutEncoder + final LoggerContext lc, + final LayoutWrappingEncoder layoutEncoder ) { return new FileAppender<>() {{ setContext(lc); diff --git a/src/main/java/org/asamk/signal/logging/Scrubber.java b/src/main/java/org/asamk/signal/logging/Scrubber.java index 6078c6ba..9d796a64 100644 --- a/src/main/java/org/asamk/signal/logging/Scrubber.java +++ b/src/main/java/org/asamk/signal/logging/Scrubber.java @@ -249,9 +249,7 @@ public final class Scrubber { return scrub(in, IPV4_PATTERN, (matcher, output) -> output.append(IPV4_CENSOR)); } - private static CharSequence scrub( - CharSequence in, Pattern pattern, ProcessMatch processMatch - ) { + private static CharSequence scrub(CharSequence in, Pattern pattern, ProcessMatch processMatch) { final StringBuilder output = new StringBuilder(in.length()); final Matcher matcher = pattern.matcher(in); diff --git a/src/main/java/org/asamk/signal/util/CommandUtil.java b/src/main/java/org/asamk/signal/util/CommandUtil.java index 039ac2a7..61f5013e 100644 --- a/src/main/java/org/asamk/signal/util/CommandUtil.java +++ b/src/main/java/org/asamk/signal/util/CommandUtil.java @@ -84,7 +84,8 @@ public class CommandUtil { } public static Set getSingleRecipientIdentifiers( - final Collection recipientStrings, final String localNumber + final Collection recipientStrings, + final String localNumber ) throws UserErrorException { if (recipientStrings == null) { return Set.of(); @@ -97,7 +98,8 @@ public class CommandUtil { } public static RecipientIdentifier.Single getSingleRecipientIdentifier( - final String recipientString, final String localNumber + final String recipientString, + final String localNumber ) throws UserErrorException { try { return RecipientIdentifier.Single.fromString(recipientString, localNumber); diff --git a/src/main/java/org/asamk/signal/util/SendMessageResultUtils.java b/src/main/java/org/asamk/signal/util/SendMessageResultUtils.java index 76932402..a23e3c54 100644 --- a/src/main/java/org/asamk/signal/util/SendMessageResultUtils.java +++ b/src/main/java/org/asamk/signal/util/SendMessageResultUtils.java @@ -41,7 +41,8 @@ public class SendMessageResultUtils { } public static void outputResult( - final OutputWriter outputWriter, final SendMessageResults sendMessageResults + final OutputWriter outputWriter, + final SendMessageResults sendMessageResults ) throws CommandException { switch (outputWriter) { case PlainTextWriter writer -> { From ff6cb5262ad3d987bd113fac18afd8722fca38a4 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 23 Nov 2024 22:29:01 +0100 Subject: [PATCH 140/279] Update libsignal-service Support for storage encryption v2 and account entropy pool Fixes #1632 --- graalvm-config-dir/reflect-config.json | 81 +++- gradle/libs.versions.toml | 2 +- .../signal/manager/api/MessageEnvelope.java | 11 +- .../org/asamk/signal/manager/api/Profile.java | 3 +- .../signal/manager/config/LiveConfig.java | 5 +- .../signal/manager/config/ServiceConfig.java | 3 +- .../signal/manager/config/StagingConfig.java | 5 +- .../signal/manager/helper/AccountHelper.java | 40 +- .../signal/manager/helper/GroupV2Helper.java | 4 +- .../helper/IncomingMessageHandler.java | 29 +- .../signal/manager/helper/PreKeyHelper.java | 2 +- .../signal/manager/helper/ProfileHelper.java | 19 +- .../manager/helper/RecipientHelper.java | 1 - .../signal/manager/helper/StorageHelper.java | 365 +++++++++++------- .../signal/manager/helper/SyncHelper.java | 16 +- .../manager/internal/SignalDependencies.java | 18 + .../signal/manager/jobs/SyncStorageJob.java | 16 +- .../signal/manager/storage/SignalAccount.java | 89 ++++- .../syncStorage/AccountRecordProcessor.java | 183 ++++----- .../syncStorage/ContactRecordProcessor.java | 323 ++++++++-------- .../syncStorage/GroupV1RecordProcessor.java | 54 ++- .../syncStorage/GroupV2RecordProcessor.java | 78 ++-- .../syncStorage/StorageSyncModels.java | 133 ++++--- .../syncStorage/StorageSyncValidations.java | 52 +-- .../syncStorage/WriteOperationResult.java | 4 +- .../asamk/signal/manager/util/KeyUtils.java | 5 + .../manager/util/NumberVerificationUtils.java | 2 +- .../signal/manager/util/ProfileUtils.java | 3 + .../org/asamk/signal/manager/util/Utils.java | 45 +++ 29 files changed, 952 insertions(+), 639 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index abe02ac4..230ede2f 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -124,6 +124,13 @@ "name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"com.squareup.wire.Message", + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"com.squareup.wire.ProtoAdapter" +}, { "name":"com.squareup.wire.internal.ImmutableList", "allDeclaredFields":true, @@ -209,9 +216,14 @@ { "name":"java.io.FilePermission" }, +{ + "name":"java.io.OutputStream" +}, { "name":"java.io.Serializable", - "allDeclaredMethods":true + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredClasses":true }, { "name":"java.lang.Boolean", @@ -577,6 +589,9 @@ { "name":"kotlin.String" }, +{ + "name":"kotlin.Unit" +}, { "name":"kotlin.collections.AbstractCollection", "allDeclaredFields":true, @@ -629,6 +644,9 @@ { "name":"long[]" }, +{ + "name":"okio.BufferedSink" +}, { "name":"okio.ByteString" }, @@ -1025,7 +1043,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "methods":[{"name":"groupId","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] + "methods":[{"name":"groupId","parameterTypes":[] }, {"name":"groupName","parameterTypes":[] }, {"name":"revision","parameterTypes":[] }, {"name":"type","parameterTypes":[] }] }, { "name":"org.asamk.signal.json.JsonMention", @@ -1247,7 +1265,7 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":["int","long","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"aciAccountData","parameterTypes":[] }, {"name":"deviceId","parameterTypes":[] }, {"name":"encryptedDeviceName","parameterTypes":[] }, {"name":"isMultiDevice","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"password","parameterTypes":[] }, {"name":"pinMasterKey","parameterTypes":[] }, {"name":"pniAccountData","parameterTypes":[] }, {"name":"profileKey","parameterTypes":[] }, {"name":"registered","parameterTypes":[] }, {"name":"registrationLockPin","parameterTypes":[] }, {"name":"serviceEnvironment","parameterTypes":[] }, {"name":"storageKey","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"usernameLinkEntropy","parameterTypes":[] }, {"name":"usernameLinkServerId","parameterTypes":[] }, {"name":"version","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":["int","long","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","long","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["int","java.lang.String","boolean","java.lang.String","java.lang.String","java.lang.String","int","boolean","java.lang.String","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"accountEntropyPool","parameterTypes":[] }, {"name":"aciAccountData","parameterTypes":[] }, {"name":"deviceId","parameterTypes":[] }, {"name":"encryptedDeviceName","parameterTypes":[] }, {"name":"isMultiDevice","parameterTypes":[] }, {"name":"mediaRootBackupKey","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"password","parameterTypes":[] }, {"name":"pinMasterKey","parameterTypes":[] }, {"name":"pniAccountData","parameterTypes":[] }, {"name":"profileKey","parameterTypes":[] }, {"name":"registered","parameterTypes":[] }, {"name":"registrationLockPin","parameterTypes":[] }, {"name":"serviceEnvironment","parameterTypes":[] }, {"name":"storageKey","parameterTypes":[] }, {"name":"timestamp","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"usernameLinkEntropy","parameterTypes":[] }, {"name":"usernameLinkServerId","parameterTypes":[] }, {"name":"version","parameterTypes":[] }] }, { "name":"org.asamk.signal.manager.storage.SignalAccount$Storage$AccountData", @@ -2257,7 +2275,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "methods":[{"name":"getAnnouncementGroup","parameterTypes":[] }, {"name":"getChangeNumber","parameterTypes":[] }, {"name":"getDeleteSync","parameterTypes":[] }, {"name":"getGiftBadges","parameterTypes":[] }, {"name":"getPaymentActivation","parameterTypes":[] }, {"name":"getPni","parameterTypes":[] }, {"name":"getSenderKey","parameterTypes":[] }, {"name":"getStorage","parameterTypes":[] }, {"name":"getStories","parameterTypes":[] }, {"name":"getVersionedExpirationTimer","parameterTypes":[] }] + "methods":[{"name":"getAnnouncementGroup","parameterTypes":[] }, {"name":"getChangeNumber","parameterTypes":[] }, {"name":"getDeleteSync","parameterTypes":[] }, {"name":"getGiftBadges","parameterTypes":[] }, {"name":"getPaymentActivation","parameterTypes":[] }, {"name":"getPni","parameterTypes":[] }, {"name":"getSenderKey","parameterTypes":[] }, {"name":"getStorage","parameterTypes":[] }, {"name":"getStorageServiceEncryptionV2","parameterTypes":[] }, {"name":"getStories","parameterTypes":[] }, {"name":"getVersionedExpirationTimer","parameterTypes":[] }] }, { "name":"org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest", @@ -2284,6 +2302,13 @@ { "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]" }, +{ + "name":"org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }, {"name":"","parameterTypes":["java.lang.String","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, { "name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage", "allDeclaredFields":true, @@ -2891,7 +2916,17 @@ }, { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord", - "allDeclaredFields":true + "allDeclaredFields":true, + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$Builder" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$Companion" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PhoneNumberSharingMode" }, { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PinnedConversation", @@ -2907,7 +2942,17 @@ }, { "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord", - "allDeclaredFields":true + "allDeclaredFields":true, + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Builder" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Companion" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$IdentityState" }, { "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord$Name", @@ -2915,11 +2960,28 @@ }, { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record", - "allDeclaredFields":true + "allDeclaredFields":true, + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record$Builder" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV1Record$Companion" }, { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record", - "allDeclaredFields":true + "allDeclaredFields":true, + "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record$Builder" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record$Companion" +}, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record$StorySendMode" }, { "name":"org.whispersystems.signalservice.internal.storage.protos.ManifestRecord", @@ -2929,6 +2991,9 @@ "name":"org.whispersystems.signalservice.internal.storage.protos.ManifestRecord$Identifier", "fields":[{"name":"raw_"}, {"name":"type_"}] }, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.OptionalBool" +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.Payments", "allDeclaredFields":true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d7d56d71..e52a4ae7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.12" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_110" +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_111" sqlite = "org.xerial:sqlite-jdbc:3.47.0.0" hikari = "com.zaxxer:HikariCP:6.2.1" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.11.3" diff --git a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java index 5598d438..d8438f5d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/MessageEnvelope.java @@ -611,11 +611,12 @@ public record MessageEnvelope( RecipientResolver recipientResolver, RecipientAddressResolver addressResolver ) { - return new Blocked(blockedListMessage.getAddresses() - .stream() - .map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)) - .toApiRecipientAddress()) - .toList(), blockedListMessage.getGroupIds().stream().map(GroupId::unknownVersion).toList()); + return new Blocked(blockedListMessage.individuals.stream() + .map(d -> new RecipientAddress(d.getAci() == null ? null : d.getAci().toString(), + null, + d.getE164(), + null)) + .toList(), blockedListMessage.groupIds.stream().map(GroupId::unknownVersion).toList()); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Profile.java b/lib/src/main/java/org/asamk/signal/manager/api/Profile.java index 5b1ecf3a..acbaac11 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Profile.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Profile.java @@ -161,7 +161,8 @@ public class Profile { } public enum Capability { - storage; + storage, + storageServiceEncryptionV2Capability; public static Capability valueOfOrNull(String value) { try { diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index f7859dae..8e844392 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -51,7 +51,7 @@ class LiveConfig { private static final byte[] backupServerPublicParams = Base64.getDecoder() .decode("AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O"); - private static Environment LIBSIGNAL_NET_ENV = Environment.PRODUCTION; + private static final Environment LIBSIGNAL_NET_ENV = Environment.PRODUCTION; static SignalServiceConfiguration createDefaultServiceConfiguration( final List interceptors @@ -71,7 +71,8 @@ class LiveConfig { proxy, zkGroupServerPublicParams, genericServerPublicParams, - backupServerPublicParams); + backupServerPublicParams, + false); } static ECPublicKey getUnidentifiedSenderTrustRoot() { 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 482e0f60..24365cfe 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,7 +29,8 @@ public class ServiceConfig { public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) { final var deleteSync = !isPrimaryDevice; - return new AccountAttributes.Capabilities(true, deleteSync, true); + final var storageEncryptionV2 = !isPrimaryDevice; + return new AccountAttributes.Capabilities(true, deleteSync, true, storageEncryptionV2); } public static ServiceEnvironmentConfig getServiceEnvironmentConfig( diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index a3212c07..741afa16 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -51,7 +51,7 @@ class StagingConfig { private static final byte[] backupServerPublicParams = Base64.getDecoder() .decode("AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8"); - private static Network.Environment LIBSIGNAL_NET_ENV = Network.Environment.STAGING; + private static final Network.Environment LIBSIGNAL_NET_ENV = Network.Environment.STAGING; static SignalServiceConfiguration createDefaultServiceConfiguration( final List interceptors @@ -71,7 +71,8 @@ class StagingConfig { proxy, zkGroupServerPublicParams, genericServerPublicParams, - backupServerPublicParams); + backupServerPublicParams, + false); } static ECPublicKey getUnidentifiedSenderTrustRoot() { 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 d5e8581e..3eb2051d 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 @@ -3,7 +3,6 @@ package org.asamk.signal.manager.helper; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.DeviceLinkUrl; import org.asamk.signal.manager.api.IncorrectPinException; -import org.asamk.signal.manager.api.InvalidDeviceLinkException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; @@ -27,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; +import org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse; import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.ServiceIdType; @@ -56,6 +56,7 @@ import java.util.concurrent.TimeUnit; import okio.ByteString; import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID; +import static org.asamk.signal.manager.util.Utils.handleResponseException; import static org.whispersystems.signalservice.internal.util.Util.isEmpty; public class AccountHelper { @@ -289,12 +290,11 @@ public class AccountHelper { (sessionId1, verificationCode1, registrationLock) -> { final var registrationApi = dependencies.getRegistrationApi(); try { - Utils.handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1)); + handleResponseException(registrationApi.verifyAccount(sessionId1, verificationCode1)); } catch (AlreadyVerifiedException e) { // Already verified so can continue changing number } - return Utils.handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest( - sessionId1, + return handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest(sessionId1, null, newNumber, registrationLock, @@ -482,26 +482,28 @@ public class AccountHelper { dependencies.getAccountManager().setAccountAttributes(account.getAccountAttributes(null)); } - public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, InvalidDeviceLinkException, org.asamk.signal.manager.api.DeviceLimitExceededException { - String verificationCode; + public void addDevice(DeviceLinkUrl deviceLinkInfo) throws IOException, org.asamk.signal.manager.api.DeviceLimitExceededException { + final var linkDeviceApi = dependencies.getLinkDeviceApi(); + final LinkedDeviceVerificationCodeResponse verificationCode; try { - verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode(); + verificationCode = handleResponseException(linkDeviceApi.getDeviceVerificationCode()); } catch (DeviceLimitExceededException e) { throw new org.asamk.signal.manager.api.DeviceLimitExceededException("Too many linked devices", e); } - try { - dependencies.getAccountManager() - .addDevice(deviceLinkInfo.deviceIdentifier(), - deviceLinkInfo.deviceKey(), - account.getAciIdentityKeyPair(), - account.getPniIdentityKeyPair(), - account.getProfileKey(), - account.getOrCreatePinMasterKey(), - verificationCode); - } catch (InvalidKeyException e) { - throw new InvalidDeviceLinkException("Invalid device link", e); - } + handleResponseException(dependencies.getLinkDeviceApi() + .linkDevice(account.getNumber(), + account.getAci(), + account.getPni(), + deviceLinkInfo.deviceIdentifier(), + deviceLinkInfo.deviceKey(), + account.getAciIdentityKeyPair(), + account.getPniIdentityKeyPair(), + account.getProfileKey(), + account.getOrCreatePinMasterKey(), + account.getOrCreateMediaRootBackupKey(), + verificationCode.getVerificationCode(), + null)); account.setMultiDevice(true); context.getJobExecutor().enqueueJob(new SyncStorageJob()); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java index 05bc876a..a3330d5c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java @@ -82,7 +82,7 @@ class GroupV2Helper { final var groupsV2AuthorizationString = getGroupAuthForToday(groupSecretParams); return dependencies.getGroupsV2Api().getGroup(groupSecretParams, groupsV2AuthorizationString); } catch (NonSuccessfulResponseCodeException e) { - if (e.getCode() == 403) { + if (e.code == 403) { throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null); } logger.warn("Failed to retrieve Group V2 info, ignoring: {}", e.getMessage()); @@ -119,7 +119,7 @@ class GroupV2Helper { false, sendEndorsementsExpirationMs); } catch (NonSuccessfulResponseCodeException e) { - if (e.getCode() == 403) { + if (e.code == 403) { throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null); } logger.warn("Failed to retrieve Group V2 history, ignoring: {}", e.getMessage()); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 376a671b..5deffb3e 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -31,6 +31,7 @@ import org.asamk.signal.manager.internal.SignalDependencies; import org.asamk.signal.manager.jobs.RetrieveStickerPackJob; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.groups.GroupInfoV1; +import org.asamk.signal.manager.storage.recipients.RecipientAddress; import org.asamk.signal.manager.storage.recipients.RecipientId; import org.asamk.signal.manager.storage.stickers.StickerPack; import org.signal.libsignal.metadata.ProtocolInvalidKeyException; @@ -525,12 +526,12 @@ public final class IncomingMessageHandler { } if (syncMessage.getBlockedList().isPresent()) { final var blockedListMessage = syncMessage.getBlockedList().get(); - for (var address : blockedListMessage.getAddresses()) { - context.getContactHelper() - .setContactBlocked(account.getRecipientResolver().resolveRecipient(address), true); + for (var individual : blockedListMessage.individuals) { + final var address = new RecipientAddress(individual.getAci(), individual.getE164()); + final var recipientId = account.getRecipientResolver().resolveRecipient(address); + context.getContactHelper().setContactBlocked(recipientId, true); } - for (var groupId : blockedListMessage.getGroupIds() - .stream() + for (var groupId : blockedListMessage.groupIds.stream() .map(GroupId::unknownVersion) .collect(Collectors.toSet())) { try { @@ -585,14 +586,22 @@ public final class IncomingMessageHandler { } if (syncMessage.getKeys().isPresent()) { final var keysMessage = syncMessage.getKeys().get(); - if (keysMessage.getStorageService().isPresent()) { - final var storageKey = keysMessage.getStorageService().get(); + if (keysMessage.getAccountEntropyPool() != null) { + final var aep = keysMessage.getAccountEntropyPool(); + account.setAccountEntropyPool(aep); + actions.add(SyncStorageDataAction.create()); + } else if (keysMessage.getMaster() != null) { + final var masterKey = keysMessage.getMaster(); + account.setMasterKey(masterKey); + actions.add(SyncStorageDataAction.create()); + } else if (keysMessage.getStorageService() != null) { + final var storageKey = keysMessage.getStorageService(); account.setStorageKey(storageKey); actions.add(SyncStorageDataAction.create()); } - if (keysMessage.getMaster().isPresent()) { - final var masterKey = keysMessage.getMaster().get(); - account.setMasterKey(masterKey); + if (keysMessage.getMediaRootBackupKey() != null) { + final var mrb = keysMessage.getMediaRootBackupKey(); + account.setMediaRootBackupKey(mrb); actions.add(SyncStorageDataAction.create()); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java index 339425c1..b17a9206 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java @@ -172,7 +172,7 @@ public class PreKeyHelper { // This can happen when the primary device has changed phone number logger.warn("Failed to updated pre keys: {}", e.getMessage()); } catch (NonSuccessfulResponseCodeException e) { - if (serviceIdType != ServiceIdType.PNI || e.getCode() != 422) { + if (serviceIdType != ServiceIdType.PNI || e.code != 422) { throw e; } logger.warn("Failed to set PNI pre keys, ignoring for now. Account needs to be reregistered to fix this."); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java index 184eac6b..0905bb30 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java @@ -336,13 +336,6 @@ public final class ProfileHelper { final var profile = account.getProfileStore().getProfile(recipientId); - if (recipientId.equals(account.getSelfRecipientId())) { - final var isUnrestricted = encryptedProfile.isUnrestrictedUnidentifiedAccess(); - if (account.isUnrestrictedUnidentifiedAccess() != isUnrestricted) { - account.setUnrestrictedUnidentifiedAccess(isUnrestricted); - } - } - Profile newProfile = null; if (profileKey.isPresent()) { logger.trace("Decrypting profile"); @@ -358,6 +351,18 @@ public final class ProfileHelper { .build(); } + if (recipientId.equals(account.getSelfRecipientId())) { + final var isUnrestricted = encryptedProfile.isUnrestrictedUnidentifiedAccess(); + if (account.isUnrestrictedUnidentifiedAccess() != isUnrestricted) { + account.setUnrestrictedUnidentifiedAccess(isUnrestricted); + } + if (account.isPrimaryDevice() && profile != null && newProfile.getCapabilities() + .contains(Profile.Capability.storageServiceEncryptionV2Capability) && !profile.getCapabilities() + .contains(Profile.Capability.storageServiceEncryptionV2Capability)) { + context.getJobExecutor().enqueueJob(new SyncStorageJob(true)); + } + } + try { logger.trace("Storing identity"); final var identityKey = new IdentityKey(Base64.getDecoder().decode(encryptedProfile.getIdentityKey())); 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 6225b982..5da66b7e 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 @@ -239,7 +239,6 @@ public class RecipientHelper { newNumbers, account.getRecipientStore().getServiceIdToProfileKeyMap(), token, - dependencies.getServiceEnvironmentConfig().cdsiMrenclave(), null, dependencies.getLibSignalNetwork(), newToken -> { 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 8c6b3c52..740c0b5e 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 @@ -2,6 +2,7 @@ package org.asamk.signal.manager.helper; import org.asamk.signal.manager.api.GroupIdV1; import org.asamk.signal.manager.api.GroupIdV2; +import org.asamk.signal.manager.api.Profile; import org.asamk.signal.manager.internal.SignalDependencies; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.storage.recipients.RecipientId; @@ -17,11 +18,17 @@ import org.signal.core.util.SetUtil; import org.signal.libsignal.protocol.InvalidKeyException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.storage.RecordIkm; import org.whispersystems.signalservice.api.storage.SignalStorageManifest; import org.whispersystems.signalservice.api.storage.SignalStorageRecord; import org.whispersystems.signalservice.api.storage.StorageId; import org.whispersystems.signalservice.api.storage.StorageKey; +import org.whispersystems.signalservice.api.storage.StorageRecordConvertersKt; +import org.whispersystems.signalservice.api.storage.StorageServiceRepository; +import org.whispersystems.signalservice.api.storage.StorageServiceRepository.ManifestIfDifferentVersionResult; +import org.whispersystems.signalservice.api.storage.StorageServiceRepository.WriteStorageRecordsResult; import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord; +import org.whispersystems.signalservice.internal.storage.protos.StorageRecord; import java.io.IOException; import java.sql.Connection; @@ -32,9 +39,10 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.stream.Collectors; +import static org.asamk.signal.manager.util.Utils.handleResponseException; + public class StorageHelper { private static final Logger logger = LoggerFactory.getLogger(StorageHelper.class); @@ -54,7 +62,7 @@ public class StorageHelper { } public void syncDataWithStorage() throws IOException { - final var storageKey = account.getOrCreateStorageKey(); + var storageKey = account.getOrCreateStorageKey(); if (storageKey == null) { if (!account.isPrimaryDevice()) { logger.debug("Storage key unknown, requesting from primary device."); @@ -65,52 +73,76 @@ public class StorageHelper { logger.trace("Reading manifest from remote storage"); final var localManifestVersion = account.getStorageManifestVersion(); - final var localManifest = account.getStorageManifest().orElse(SignalStorageManifest.EMPTY); - SignalStorageManifest remoteManifest; - try { - remoteManifest = dependencies.getAccountManager() - .getStorageManifestIfDifferentVersion(storageKey, localManifestVersion) - .orElse(localManifest); - } catch (InvalidKeyException e) { - logger.warn("Manifest couldn't be decrypted."); - if (account.isPrimaryDevice()) { - try { - forcePushToStorage(storageKey); - } catch (RetryLaterException rle) { - // TODO retry later - return; - } - } - return; - } - - logger.trace("Manifest versions: local {}, remote {}", localManifestVersion, remoteManifest.getVersion()); + final var localManifest = account.getStorageManifest().orElse(SignalStorageManifest.Companion.getEMPTY()); + final var storageServiceRepository = dependencies.getStorageServiceRepository(); + final var result = storageServiceRepository.getStorageManifestIfDifferentVersion(storageKey, + localManifestVersion); var needsForcePush = false; - if (remoteManifest.getVersion() > localManifestVersion) { - logger.trace("Remote version was newer, reading records."); - needsForcePush = readDataFromStorage(storageKey, localManifest, remoteManifest); - } else if (remoteManifest.getVersion() < localManifest.getVersion()) { - logger.debug("Remote storage manifest version was older. User might have switched accounts."); - } - logger.trace("Done reading data from remote storage"); + final var remoteManifest = switch (result) { + case ManifestIfDifferentVersionResult.DifferentVersion diff -> { + final var manifest = diff.getManifest(); + storeManifestLocally(manifest); + yield manifest; + } + case ManifestIfDifferentVersionResult.DecryptionError ignore -> { + logger.warn("Manifest couldn't be decrypted."); + if (account.isPrimaryDevice()) { + needsForcePush = true; + } else { + context.getSyncHelper().requestSyncKeys(); + } + yield null; + } + case ManifestIfDifferentVersionResult.SameVersion ignored -> localManifest; + case ManifestIfDifferentVersionResult.NetworkError e -> throw e.getException(); + case ManifestIfDifferentVersionResult.StatusCodeError e -> throw e.getException(); + default -> throw new RuntimeException("Unhandled ManifestIfDifferentVersionResult type"); + }; - if (localManifest != remoteManifest) { - storeManifestLocally(remoteManifest); - } + if (remoteManifest != null) { + logger.trace("Manifest versions: local {}, remote {}", localManifestVersion, remoteManifest.version); - readRecordsWithPreviouslyUnknownTypes(storageKey); + if (remoteManifest.version > localManifestVersion) { + logger.trace("Remote version was newer, reading records."); + needsForcePush = readDataFromStorage(storageKey, localManifest, remoteManifest); + } else if (remoteManifest.version < localManifest.version) { + logger.debug("Remote storage manifest version was older. User might have switched accounts."); + } + logger.trace("Done reading data from remote storage"); + + readRecordsWithPreviouslyUnknownTypes(storageKey, remoteManifest); + } logger.trace("Adding missing storageIds to local data"); account.getRecipientStore().setMissingStorageIds(); account.getGroupStore().setMissingStorageIds(); var needsMultiDeviceSync = false; - try { - needsMultiDeviceSync = writeToStorage(storageKey, remoteManifest, needsForcePush); - } catch (RetryLaterException e) { - // TODO retry later - return; + + if (account.needsStorageKeyMigration()) { + logger.debug("Storage needs force push due to new account entropy pool"); + // Set new aep and reset previous master key and storage key + account.setAccountEntropyPool(account.getOrCreateAccountEntropyPool()); + storageKey = account.getOrCreateStorageKey(); + context.getSyncHelper().sendKeysMessage(); + needsForcePush = true; + } else if (remoteManifest == null) { + if (account.isPrimaryDevice()) { + needsForcePush = true; + } + } else if (remoteManifest.recordIkm == null && account.getSelfRecipientProfile() + .getCapabilities() + .contains(Profile.Capability.storageServiceEncryptionV2Capability)) { + logger.debug("The SSRE2 capability is supported, but no recordIkm is set! Force pushing."); + needsForcePush = true; + } else { + try { + needsMultiDeviceSync = writeToStorage(storageKey, remoteManifest, needsForcePush); + } catch (RetryLaterException e) { + // TODO retry later + return; + } } if (needsForcePush) { @@ -131,6 +163,23 @@ public class StorageHelper { logger.debug("Done syncing data with remote storage"); } + public void forcePushToStorage() throws IOException { + if (!account.isPrimaryDevice()) { + return; + } + + final var storageKey = account.getOrCreateStorageKey(); + if (storageKey == null) { + return; + } + + try { + forcePushToStorage(storageKey); + } catch (RetryLaterException e) { + // TODO retry later + } + } + private boolean readDataFromStorage( final StorageKey storageKey, final SignalStorageManifest localManifest, @@ -140,14 +189,14 @@ public class StorageHelper { try (final var connection = account.getAccountDatabase().getConnection()) { connection.setAutoCommit(false); - var idDifference = findIdDifference(remoteManifest.getStorageIds(), localManifest.getStorageIds()); + var idDifference = findIdDifference(remoteManifest.storageIds, localManifest.storageIds); if (idDifference.hasTypeMismatches() && account.isPrimaryDevice()) { logger.debug("Found type mismatches in the ID sets! Scheduling a force push after this sync completes."); needsForcePush = true; } - logger.debug("Pre-Merge ID Difference :: " + idDifference); + logger.debug("Pre-Merge ID Difference :: {}", idDifference); if (!idDifference.localOnlyIds().isEmpty()) { final var updated = account.getRecipientStore() @@ -161,15 +210,15 @@ public class StorageHelper { } if (!idDifference.isEmpty()) { - final var remoteOnlyRecords = getSignalStorageRecords(storageKey, idDifference.remoteOnlyIds()); + final var remoteOnlyRecords = getSignalStorageRecords(storageKey, + remoteManifest, + idDifference.remoteOnlyIds()); if (remoteOnlyRecords.size() != idDifference.remoteOnlyIds().size()) { - logger.debug("Could not find all remote-only records! Requested: " - + idDifference.remoteOnlyIds() - .size() - + ", Found: " - + remoteOnlyRecords.size() - + ". These stragglers should naturally get deleted during the sync."); + logger.debug( + "Could not find all remote-only records! Requested: {}, Found: {}. These stragglers should naturally get deleted during the sync.", + idDifference.remoteOnlyIds().size(), + remoteOnlyRecords.size()); } final var unknownInserts = processKnownRecords(connection, remoteOnlyRecords); @@ -194,18 +243,21 @@ public class StorageHelper { return needsForcePush; } - private void readRecordsWithPreviouslyUnknownTypes(final StorageKey storageKey) throws IOException { + private void readRecordsWithPreviouslyUnknownTypes( + final StorageKey storageKey, + final SignalStorageManifest remoteManifest + ) throws IOException { try (final var connection = account.getAccountDatabase().getConnection()) { connection.setAutoCommit(false); final var knownUnknownIds = account.getUnknownStorageIdStore() .getUnknownStorageIds(connection, KNOWN_TYPES); if (!knownUnknownIds.isEmpty()) { - logger.debug("We have " + knownUnknownIds.size() + " unknown records that we can now process."); + logger.debug("We have {} unknown records that we can now process.", knownUnknownIds.size()); - final var remote = getSignalStorageRecords(storageKey, knownUnknownIds); + final var remote = getSignalStorageRecords(storageKey, remoteManifest, knownUnknownIds); - logger.debug("Found " + remote.size() + " of the known-unknowns remotely."); + logger.debug("Found {} of the known-unknowns remotely.", remote.size()); processKnownRecords(connection, remote); account.getUnknownStorageIdStore() @@ -227,15 +279,16 @@ public class StorageHelper { connection.setAutoCommit(false); final var localStorageIds = getAllLocalStorageIds(connection); - final var idDifference = findIdDifference(remoteManifest.getStorageIds(), localStorageIds); - logger.debug("ID Difference :: " + idDifference); + final var idDifference = findIdDifference(remoteManifest.storageIds, localStorageIds); + logger.debug("ID Difference :: {}", idDifference); final var remoteDeletes = idDifference.remoteOnlyIds().stream().map(StorageId::getRaw).toList(); final var remoteInserts = buildLocalStorageRecords(connection, idDifference.localOnlyIds()); // TODO check if local storage record proto matches remote, then reset to remote storage_id - remoteWriteOperation = new WriteOperationResult(new SignalStorageManifest(remoteManifest.getVersion() + 1, + remoteWriteOperation = new WriteOperationResult(new SignalStorageManifest(remoteManifest.version + 1, account.getDeviceId(), + remoteManifest.recordIkm, localStorageIds), remoteInserts, remoteDeletes); connection.commit(); @@ -244,39 +297,37 @@ public class StorageHelper { } if (remoteWriteOperation.isEmpty()) { - logger.debug("No remote writes needed. Still at version: " + remoteManifest.getVersion()); + logger.debug("No remote writes needed. Still at version: {}", remoteManifest.version); return false; } logger.debug("We have something to write remotely."); - logger.debug("WriteOperationResult :: " + remoteWriteOperation); + logger.debug("WriteOperationResult :: {}", remoteWriteOperation); StorageSyncValidations.validate(remoteWriteOperation, remoteManifest, needsForcePush, account.getSelfRecipientAddress()); - final Optional conflict; - try { - conflict = dependencies.getAccountManager() - .writeStorageRecords(storageKey, - remoteWriteOperation.manifest(), - remoteWriteOperation.inserts(), - remoteWriteOperation.deletes()); - } catch (InvalidKeyException e) { - logger.warn("Failed to decrypt conflicting storage manifest: {}", e.getMessage()); - throw new IOException(e); + final var result = dependencies.getStorageServiceRepository() + .writeStorageRecords(storageKey, + remoteWriteOperation.manifest(), + remoteWriteOperation.inserts(), + remoteWriteOperation.deletes()); + switch (result) { + case WriteStorageRecordsResult.ConflictError ignored -> { + logger.debug("Hit a conflict when trying to resolve the conflict! Retrying."); + throw new RetryLaterException(); + } + case WriteStorageRecordsResult.NetworkError networkError -> throw networkError.getException(); + case WriteStorageRecordsResult.StatusCodeError statusCodeError -> throw statusCodeError.getException(); + case WriteStorageRecordsResult.Success ignored -> { + logger.debug("Saved new manifest. Now at version: {}", remoteWriteOperation.manifest().version); + storeManifestLocally(remoteWriteOperation.manifest()); + return true; + } + default -> throw new IllegalStateException("Unexpected value: " + result); } - - if (conflict.isPresent()) { - logger.debug("Hit a conflict when trying to resolve the conflict! Retrying."); - throw new RetryLaterException(); - } - - logger.debug("Saved new manifest. Now at version: " + remoteWriteOperation.manifest().getVersion()); - storeManifestLocally(remoteWriteOperation.manifest()); - - return true; } private void forcePushToStorage( @@ -284,7 +335,8 @@ public class StorageHelper { ) throws IOException, RetryLaterException { logger.debug("Force pushing local state to remote storage"); - final var currentVersion = dependencies.getAccountManager().getStorageManifestVersion(); + final var currentVersion = handleResponseException(dependencies.getStorageServiceRepository() + .getManifestVersion()); final var newVersion = currentVersion + 1; final var newStorageRecords = new ArrayList(); final Map newContactStorageIds; @@ -302,15 +354,16 @@ public class StorageHelper { final var recipient = account.getRecipientStore().getRecipient(connection, recipientId); final var accountRecord = StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(), recipient, - account.getUsernameLink(), - storageId.getRaw()); - newStorageRecords.add(accountRecord); + account.getUsernameLink()); + newStorageRecords.add(new SignalStorageRecord(storageId, + new StorageRecord.Builder().account(accountRecord).build())); } else { final var recipient = account.getRecipientStore().getRecipient(connection, recipientId); final var address = recipient.getAddress().getIdentifier(); final var identity = account.getIdentityKeyStore().getIdentityInfo(connection, address); - final var record = StorageSyncModels.localToRemoteRecord(recipient, identity, storageId.getRaw()); - newStorageRecords.add(record); + final var record = StorageSyncModels.localToRemoteRecord(recipient, identity); + newStorageRecords.add(new SignalStorageRecord(storageId, + new StorageRecord.Builder().contact(record).build())); } } @@ -319,8 +372,9 @@ public class StorageHelper { for (final var groupId : groupV1Ids) { final var storageId = newGroupV1StorageIds.get(groupId); final var group = account.getGroupStore().getGroup(connection, groupId); - final var record = StorageSyncModels.localToRemoteRecord(group, storageId.getRaw()); - newStorageRecords.add(record); + final var record = StorageSyncModels.localToRemoteRecord(group); + newStorageRecords.add(new SignalStorageRecord(storageId, + new StorageRecord.Builder().groupV1(record).build())); } final var groupV2Ids = account.getGroupStore().getGroupV2Ids(connection); @@ -328,8 +382,9 @@ public class StorageHelper { for (final var groupId : groupV2Ids) { final var storageId = newGroupV2StorageIds.get(groupId); final var group = account.getGroupStore().getGroup(connection, groupId); - final var record = StorageSyncModels.localToRemoteRecord(group, storageId.getRaw()); - newStorageRecords.add(record); + final var record = StorageSyncModels.localToRemoteRecord(group); + newStorageRecords.add(new SignalStorageRecord(storageId, + new StorageRecord.Builder().groupV2(record).build())); } connection.commit(); @@ -338,34 +393,46 @@ public class StorageHelper { } final var newStorageIds = newStorageRecords.stream().map(SignalStorageRecord::getId).toList(); - final var manifest = new SignalStorageManifest(newVersion, account.getDeviceId(), newStorageIds); + final RecordIkm recordIkm; + if (account.getSelfRecipientProfile() + .getCapabilities() + .contains(Profile.Capability.storageServiceEncryptionV2Capability)) { + logger.debug("Generating and including a new recordIkm."); + recordIkm = RecordIkm.Companion.generate(); + } else { + logger.debug("SSRE2 not yet supported. Not including recordIkm."); + recordIkm = null; + } + + final var manifest = new SignalStorageManifest(newVersion, account.getDeviceId(), recordIkm, newStorageIds); StorageSyncValidations.validateForcePush(manifest, newStorageRecords, account.getSelfRecipientAddress()); - final Optional conflict; - try { - if (newVersion > 1) { - logger.trace("Force-pushing data. Inserting {} IDs.", newStorageRecords.size()); - conflict = dependencies.getAccountManager() - .resetStorageRecords(storageServiceKey, manifest, newStorageRecords); - } else { - logger.trace("First version, normal push. Inserting {} IDs.", newStorageRecords.size()); - conflict = dependencies.getAccountManager() - .writeStorageRecords(storageServiceKey, manifest, newStorageRecords, Collections.emptyList()); + final WriteStorageRecordsResult result; + if (newVersion > 1) { + logger.trace("Force-pushing data. Inserting {} IDs.", newStorageRecords.size()); + result = dependencies.getStorageServiceRepository() + .resetAndWriteStorageRecords(storageServiceKey, manifest, newStorageRecords); + } else { + logger.trace("First version, normal push. Inserting {} IDs.", newStorageRecords.size()); + result = dependencies.getStorageServiceRepository() + .writeStorageRecords(storageServiceKey, manifest, newStorageRecords, Collections.emptyList()); + } + + switch (result) { + case WriteStorageRecordsResult.ConflictError ignored -> { + logger.debug("Hit a conflict. Trying again."); + throw new RetryLaterException(); } - } catch (InvalidKeyException e) { - logger.debug("Hit an invalid key exception, which likely indicates a conflict.", e); - throw new RetryLaterException(); + case WriteStorageRecordsResult.NetworkError networkError -> throw networkError.getException(); + case WriteStorageRecordsResult.StatusCodeError statusCodeError -> throw statusCodeError.getException(); + case WriteStorageRecordsResult.Success ignored -> { + logger.debug("Force push succeeded. Updating local manifest version to: {}", manifest.version); + storeManifestLocally(manifest); + } + default -> throw new IllegalStateException("Unexpected value: " + result); } - if (conflict.isPresent()) { - logger.debug("Hit a conflict. Trying again."); - throw new RetryLaterException(); - } - - logger.debug("Force push succeeded. Updating local manifest version to: " + manifest.getVersion()); - storeManifestLocally(manifest); - try (final var connection = account.getAccountDatabase().getConnection()) { connection.setAutoCommit(false); account.getRecipientStore().updateStorageIds(connection, newContactStorageIds); @@ -405,22 +472,35 @@ public class StorageHelper { private void storeManifestLocally( final SignalStorageManifest remoteManifest ) { - account.setStorageManifestVersion(remoteManifest.getVersion()); + account.setStorageManifestVersion(remoteManifest.version); account.setStorageManifest(remoteManifest); } private List getSignalStorageRecords( final StorageKey storageKey, + final SignalStorageManifest manifest, final List storageIds ) throws IOException { - List records; - try { - records = dependencies.getAccountManager().readStorageRecords(storageKey, storageIds); - } catch (InvalidKeyException e) { - logger.warn("Failed to read storage records, ignoring."); - return List.of(); - } - return records; + final var result = dependencies.getStorageServiceRepository() + .readStorageRecords(storageKey, manifest.recordIkm, storageIds); + return switch (result) { + case StorageServiceRepository.StorageRecordResult.DecryptionError decryptionError -> { + if (decryptionError.getException() instanceof InvalidKeyException) { + logger.warn("Failed to read storage records, ignoring."); + yield List.of(); + } else if (decryptionError.getException() instanceof IOException ioe) { + throw ioe; + } else { + throw new IOException(decryptionError.getException()); + } + } + case StorageServiceRepository.StorageRecordResult.NetworkError networkError -> + throw networkError.getException(); + case StorageServiceRepository.StorageRecordResult.StatusCodeError statusCodeError -> + throw statusCodeError.getException(); + case StorageServiceRepository.StorageRecordResult.Success success -> success.getRecords(); + default -> throw new IllegalStateException("Unexpected value: " + result); + }; } private List getAllLocalStorageIds(final Connection connection) throws SQLException { @@ -436,12 +516,10 @@ public class StorageHelper { final Connection connection, final List storageIds ) throws SQLException { - final var records = new ArrayList(); + final var records = new ArrayList(storageIds.size()); for (final var storageId : storageIds) { final var record = buildLocalStorageRecord(connection, storageId); - if (record != null) { - records.add(record); - } + records.add(record); } return records; } @@ -455,25 +533,31 @@ public class StorageHelper { final var recipient = account.getRecipientStore().getRecipient(connection, storageId); final var address = recipient.getAddress().getIdentifier(); final var identity = account.getIdentityKeyStore().getIdentityInfo(connection, address); - yield StorageSyncModels.localToRemoteRecord(recipient, identity, storageId.getRaw()); + final var record = StorageSyncModels.localToRemoteRecord(recipient, identity); + yield new SignalStorageRecord(storageId, new StorageRecord.Builder().contact(record).build()); } case ManifestRecord.Identifier.Type.GROUPV1 -> { final var groupV1 = account.getGroupStore().getGroupV1(connection, storageId); - yield StorageSyncModels.localToRemoteRecord(groupV1, storageId.getRaw()); + final var record = StorageSyncModels.localToRemoteRecord(groupV1); + yield new SignalStorageRecord(storageId, new StorageRecord.Builder().groupV1(record).build()); } case ManifestRecord.Identifier.Type.GROUPV2 -> { final var groupV2 = account.getGroupStore().getGroupV2(connection, storageId); - yield StorageSyncModels.localToRemoteRecord(groupV2, storageId.getRaw()); + final var record = StorageSyncModels.localToRemoteRecord(groupV2); + yield new SignalStorageRecord(storageId, new StorageRecord.Builder().groupV2(record).build()); } case ManifestRecord.Identifier.Type.ACCOUNT -> { final var selfRecipient = account.getRecipientStore() .getRecipient(connection, account.getSelfRecipientId()); - yield StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(), + + final var record = StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(), selfRecipient, - account.getUsernameLink(), - storageId.getRaw()); + account.getUsernameLink()); + yield new SignalStorageRecord(storageId, new StorageRecord.Builder().account(record).build()); + } + case null, default -> { + throw new AssertionError("Got unknown local storage record type: " + storageId); } - case null, default -> throw new AssertionError("Got unknown local storage record type: " + storageId); }; } @@ -537,13 +621,24 @@ public class StorageHelper { final var groupV2RecordProcessor = new GroupV2RecordProcessor(account, connection); for (final var record : records) { - logger.debug("Reading record of type {}", record.getType()); - switch (ManifestRecord.Identifier.Type.fromValue(record.getType())) { - case ACCOUNT -> accountRecordProcessor.process(record.getAccount().get()); - case GROUPV1 -> groupV1RecordProcessor.process(record.getGroupV1().get()); - case GROUPV2 -> groupV2RecordProcessor.process(record.getGroupV2().get()); - case CONTACT -> contactRecordProcessor.process(record.getContact().get()); - case null, default -> unknownRecords.add(record.getId()); + if (record.getProto().account != null) { + logger.debug("Reading record {} of type account", record.getId()); + accountRecordProcessor.process(StorageRecordConvertersKt.toSignalAccountRecord(record.getProto().account, + record.getId())); + } else if (record.getProto().groupV1 != null) { + logger.debug("Reading record {} of type groupV1", record.getId()); + groupV1RecordProcessor.process(StorageRecordConvertersKt.toSignalGroupV1Record(record.getProto().groupV1, + record.getId())); + } else if (record.getProto().groupV2 != null) { + logger.debug("Reading record {} of type groupV2", record.getId()); + groupV2RecordProcessor.process(StorageRecordConvertersKt.toSignalGroupV2Record(record.getProto().groupV2, + record.getId())); + } else if (record.getProto().contact != null) { + logger.debug("Reading record {} of type contact", record.getId()); + contactRecordProcessor.process(StorageRecordConvertersKt.toSignalContactRecord(record.getProto().contact, + record.getId())); + } else { + unknownRecords.add(record.getId()); } } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java index 70427f00..39726df8 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/SyncHelper.java @@ -247,10 +247,14 @@ public class SyncHelper { } public SendMessageResult sendBlockedList() { - var addresses = new ArrayList(); + var addresses = new ArrayList(); for (var record : account.getContactStore().getContacts()) { if (record.second().isBlocked()) { - addresses.add(context.getRecipientHelper().resolveSignalServiceAddress(record.first())); + final var address = account.getRecipientAddressResolver().resolveRecipientAddress(record.first()); + if (address.aci().isPresent() || address.number().isPresent()) { + addresses.add(new BlockedListMessage.Individual(address.aci().orElse(null), + address.number().orElse(null))); + } } } var groupIds = new ArrayList(); @@ -276,8 +280,10 @@ public class SyncHelper { } public SendMessageResult sendKeysMessage() { - var keysMessage = new KeysMessage(Optional.ofNullable(account.getOrCreateStorageKey()), - Optional.ofNullable(account.getOrCreatePinMasterKey())); + var keysMessage = new KeysMessage(account.getOrCreateStorageKey(), + account.getOrCreatePinMasterKey(), + account.getOrCreateAccountEntropyPool(), + account.getOrCreateMediaRootBackupKey()); return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forKeys(keysMessage)); } @@ -405,7 +411,7 @@ public class SyncHelper { builder.withMessageExpirationTimeVersion(c.getExpirationTimerVersion().get()); } else { logger.debug( - "[ContactSync] {} was synced with an old expiration timer. Ignoring. Received: {} Current: ${}", + "[ContactSync] {} was synced with an old expiration timer. Ignoring. Received: {} Current: {}", recipientId, c.getExpirationTimerVersion(), contact == null ? 1 : contact.messageExpirationTimeVersion()); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index 7ee2b39e..90e3b159 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -15,10 +15,13 @@ import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; +import org.whispersystems.signalservice.api.link.LinkDeviceApi; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.registration.RegistrationApi; import org.whispersystems.signalservice.api.services.ProfileService; +import org.whispersystems.signalservice.api.storage.StorageServiceApi; +import org.whispersystems.signalservice.api.storage.StorageServiceRepository; import org.whispersystems.signalservice.api.svr.SecureValueRecovery; import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; @@ -49,6 +52,8 @@ public class SignalDependencies { private SignalServiceAccountManager accountManager; private GroupsV2Api groupsV2Api; private RegistrationApi registrationApi; + private LinkDeviceApi linkDeviceApi; + private StorageServiceApi storageServiceApi; private GroupsV2Operations groupsV2Operations; private ClientZkOperations clientZkOperations; @@ -155,6 +160,19 @@ public class SignalDependencies { return getOrCreate(() -> registrationApi, () -> registrationApi = getAccountManager().getRegistrationApi()); } + public LinkDeviceApi getLinkDeviceApi() { + return getOrCreate(() -> linkDeviceApi, () -> linkDeviceApi = new LinkDeviceApi(getPushServiceSocket())); + } + + private StorageServiceApi getStorageServiceApi() { + return getOrCreate(() -> storageServiceApi, + () -> storageServiceApi = new StorageServiceApi(getPushServiceSocket())); + } + + public StorageServiceRepository getStorageServiceRepository() { + return new StorageServiceRepository(getStorageServiceApi()); + } + public GroupsV2Operations getGroupsV2Operations() { return getOrCreate(() -> groupsV2Operations, () -> groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()), diff --git a/lib/src/main/java/org/asamk/signal/manager/jobs/SyncStorageJob.java b/lib/src/main/java/org/asamk/signal/manager/jobs/SyncStorageJob.java index 85cfbe2c..d695c7b1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/jobs/SyncStorageJob.java +++ b/lib/src/main/java/org/asamk/signal/manager/jobs/SyncStorageJob.java @@ -8,13 +8,27 @@ import java.io.IOException; public class SyncStorageJob implements Job { + private final boolean forcePush; + private static final Logger logger = LoggerFactory.getLogger(SyncStorageJob.class); + public SyncStorageJob() { + this.forcePush = false; + } + + public SyncStorageJob(final boolean forcePush) { + this.forcePush = forcePush; + } + @Override public void run(Context context) { logger.trace("Running storage sync job"); try { - context.getStorageHelper().syncDataWithStorage(); + if (forcePush) { + context.getStorageHelper().forcePushToStorage(); + } else { + context.getStorageHelper().syncDataWithStorage(); + } } catch (IOException e) { logger.warn("Failed to sync storage data", e); } 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 5c8a5530..2529ad4a 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 @@ -65,10 +65,12 @@ import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.AccountEntropyPool; import org.whispersystems.signalservice.api.SignalServiceAccountDataStore; import org.whispersystems.signalservice.api.SignalServiceDataStore; import org.whispersystems.signalservice.api.account.AccountAttributes; import org.whispersystems.signalservice.api.account.PreKeyCollection; +import org.whispersystems.signalservice.api.backup.MediaRootBackupKey; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.push.ServiceId; @@ -138,6 +140,8 @@ public class SignalAccount implements Closeable { private String registrationLockPin; private MasterKey pinMasterKey; private StorageKey storageKey; + private AccountEntropyPool accountEntropyPool; + private MediaRootBackupKey mediaRootBackupKey; private ProfileKey profileKey; private Settings settings; @@ -305,6 +309,7 @@ public class SignalAccount implements Closeable { this.isMultiDevice = true; setLastReceiveTimestamp(0L); this.pinMasterKey = masterKey; + this.accountEntropyPool = null; getKeyValueStore().storeEntry(storageManifestVersion, -1L); this.setStorageManifest(null); this.storageKey = null; @@ -339,6 +344,7 @@ public class SignalAccount implements Closeable { final PreKeyCollection pniPreKeys ) { this.pinMasterKey = masterKey; + this.accountEntropyPool = null; getKeyValueStore().storeEntry(storageManifestVersion, -1L); this.setStorageManifest(null); this.storageKey = null; @@ -499,6 +505,12 @@ public class SignalAccount implements Closeable { if (storage.storageKey != null) { storageKey = new StorageKey(base64.decode(storage.storageKey)); } + if (storage.accountEntropyPool != null) { + accountEntropyPool = new AccountEntropyPool(storage.accountEntropyPool); + } + if (storage.mediaRootBackupKey != null) { + mediaRootBackupKey = new MediaRootBackupKey(base64.decode(storage.mediaRootBackupKey)); + } if (storage.profileKey != null) { try { profileKey = new ProfileKey(base64.decode(storage.profileKey)); @@ -981,6 +993,8 @@ public class SignalAccount implements Closeable { registrationLockPin, pinMasterKey == null ? null : base64.encodeToString(pinMasterKey.serialize()), storageKey == null ? null : base64.encodeToString(storageKey.serialize()), + accountEntropyPool == null ? null : accountEntropyPool.getValue(), + mediaRootBackupKey == null ? null : base64.encodeToString(mediaRootBackupKey.getValue()), profileKey == null ? null : base64.encodeToString(profileKey.serialize()), usernameLink == null ? null : base64.encodeToString(usernameLink.getEntropy()), usernameLink == null ? null : usernameLink.getServerId().toString()); @@ -1442,6 +1456,10 @@ public class SignalAccount implements Closeable { return selfRecipientId; } + public Profile getSelfRecipientProfile() { + return recipientStore.getProfile(selfRecipientId); + } + public String getSessionId(final String forNumber) { final var keyValueStore = getKeyValueStore(); final var sessionNumber = keyValueStore.getEntry(verificationSessionNumber); @@ -1512,31 +1530,50 @@ public class SignalAccount implements Closeable { public MasterKey getPinBackedMasterKey() { if (registrationLockPin == null) { return null; + } else if (!isPrimaryDevice()) { + return getMasterKey(); } - return pinMasterKey; + return getOrCreatePinMasterKey(); } public MasterKey getOrCreatePinMasterKey() { - if (pinMasterKey == null) { - pinMasterKey = KeyUtils.createMasterKey(); - save(); + final var key = getMasterKey(); + if (key != null) { + return key; } + + pinMasterKey = KeyUtils.createMasterKey(); + save(); return pinMasterKey; } + private MasterKey getMasterKey() { + if (pinMasterKey != null) { + return pinMasterKey; + } else if (accountEntropyPool != null) { + return accountEntropyPool.deriveMasterKey(); + } + return null; + } + public void setMasterKey(MasterKey masterKey) { if (isPrimaryDevice()) { return; } this.pinMasterKey = masterKey; + if (masterKey != null) { + this.storageKey = null; + } save(); } public StorageKey getOrCreateStorageKey() { - if (pinMasterKey != null) { - return pinMasterKey.deriveStorageServiceKey(); - } else if (storageKey != null) { + if (storageKey != null) { return storageKey; + } else if (pinMasterKey != null) { + return pinMasterKey.deriveStorageServiceKey(); + } else if (accountEntropyPool != null) { + return accountEntropyPool.deriveMasterKey().deriveStorageServiceKey(); } else if (!isPrimaryDevice() || !isMultiDevice()) { // Only upload storage, if a pin master key already exists or linked devices exist return null; @@ -1553,6 +1590,40 @@ public class SignalAccount implements Closeable { save(); } + public AccountEntropyPool getOrCreateAccountEntropyPool() { + if (accountEntropyPool == null) { + accountEntropyPool = AccountEntropyPool.Companion.generate(); + save(); + } + return accountEntropyPool; + } + + public void setAccountEntropyPool(final AccountEntropyPool accountEntropyPool) { + this.accountEntropyPool = accountEntropyPool; + if (accountEntropyPool != null) { + this.storageKey = null; + this.pinMasterKey = null; + } + save(); + } + + public boolean needsStorageKeyMigration() { + return isPrimaryDevice() && (storageKey != null || pinMasterKey != null); + } + + public MediaRootBackupKey getOrCreateMediaRootBackupKey() { + if (mediaRootBackupKey == null) { + mediaRootBackupKey = KeyUtils.createMediaRootBackupKey(); + save(); + } + return mediaRootBackupKey; + } + + public void setMediaRootBackupKey(final MediaRootBackupKey mediaRootBackupKey) { + this.mediaRootBackupKey = mediaRootBackupKey; + save(); + } + public String getRecoveryPassword() { final var masterKey = getPinBackedMasterKey(); if (masterKey == null) { @@ -1575,7 +1646,7 @@ public class SignalAccount implements Closeable { return Optional.empty(); } try (var inputStream = new FileInputStream(storageManifestFile)) { - return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes())); + return Optional.of(SignalStorageManifest.Companion.deserialize(inputStream.readAllBytes())); } catch (IOException e) { logger.warn("Failed to read local storage manifest.", e); return Optional.empty(); @@ -1882,6 +1953,8 @@ public class SignalAccount implements Closeable { String registrationLockPin, String pinMasterKey, String storageKey, + String accountEntropyPool, + String mediaRootBackupKey, String profileKey, String usernameLinkEntropy, String usernameLinkServerId diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java index 18dd1869..b2f7dd29 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java @@ -12,8 +12,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.push.UsernameLinkComponents; import org.whispersystems.signalservice.api.storage.SignalAccountRecord; -import org.whispersystems.signalservice.api.util.OptionalUtil; +import org.whispersystems.signalservice.api.storage.StorageId; import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.internal.storage.protos.AccountRecord; import org.whispersystems.signalservice.internal.storage.protos.OptionalBool; import java.sql.Connection; @@ -21,6 +22,9 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Optional; +import static org.asamk.signal.manager.util.Utils.firstNonEmpty; +import static org.asamk.signal.manager.util.Utils.firstNonNull; + /** * Processes {@link SignalAccountRecord}s. */ @@ -43,10 +47,10 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor update) throws SQLException { final var accountRecord = update.newRecord(); + final var accountProto = accountRecord.getProto(); - if (!accountRecord.getE164().equals(account.getNumber())) { + if (!accountProto.e164.equals(account.getNumber())) { jobExecutor.enqueueJob(new CheckWhoAmIJob()); } - account.getConfigurationStore().setReadReceipts(connection, accountRecord.isReadReceiptsEnabled()); - account.getConfigurationStore().setTypingIndicators(connection, accountRecord.isTypingIndicatorsEnabled()); + account.getConfigurationStore().setReadReceipts(connection, accountProto.readReceipts); + account.getConfigurationStore().setTypingIndicators(connection, accountProto.typingIndicators); account.getConfigurationStore() - .setUnidentifiedDeliveryIndicators(connection, accountRecord.isSealedSenderIndicatorsEnabled()); - account.getConfigurationStore().setLinkPreviews(connection, accountRecord.isLinkPreviewsEnabled()); + .setUnidentifiedDeliveryIndicators(connection, accountProto.sealedSenderIndicators); + account.getConfigurationStore().setLinkPreviews(connection, accountProto.linkPreviews); account.getConfigurationStore() .setPhoneNumberSharingMode(connection, - StorageSyncModels.remoteToLocal(accountRecord.getPhoneNumberSharingMode())); - account.getConfigurationStore().setPhoneNumberUnlisted(connection, accountRecord.isPhoneNumberUnlisted()); + StorageSyncModels.remoteToLocal(accountProto.phoneNumberSharingMode)); + account.getConfigurationStore().setPhoneNumberUnlisted(connection, accountProto.unlistedPhoneNumber); - account.setUsername(accountRecord.getUsername() != null && !accountRecord.getUsername().isEmpty() - ? accountRecord.getUsername() - : null); - if (accountRecord.getUsernameLink() != null) { - final var usernameLink = accountRecord.getUsernameLink(); + account.setUsername(!accountProto.username.isEmpty() ? accountProto.username : null); + if (accountProto.usernameLink != null) { + final var usernameLink = accountProto.usernameLink; account.setUsernameLink(new UsernameLinkComponents(usernameLink.entropy.toByteArray(), UuidUtil.parseOrThrow(usernameLink.serverId.toByteArray()))); account.getConfigurationStore().setUsernameLinkColor(connection, usernameLink.color.name()); } - if (accountRecord.getProfileKey().isPresent()) { + if (accountProto.profileKey.size() > 0) { ProfileKey profileKey; try { - profileKey = new ProfileKey(accountRecord.getProfileKey().get()); + profileKey = new ProfileKey(accountProto.profileKey.toByteArray()); } catch (InvalidInputException e) { logger.debug("Received invalid profile key from storage"); profileKey = null; } if (profileKey != null) { account.setProfileKey(profileKey); - final var avatarPath = accountRecord.getAvatarUrlPath().orElse(null); + final var avatarPath = accountProto.avatarUrlPath.isEmpty() ? null : accountProto.avatarUrlPath; jobExecutor.enqueueJob(new DownloadProfileAvatarJob(avatarPath)); } } final var profile = account.getRecipientStore().getProfile(connection, account.getSelfRecipientId()); final var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile); - builder.withGivenName(accountRecord.getGivenName().orElse(null)); - builder.withFamilyName(accountRecord.getFamilyName().orElse(null)); + builder.withGivenName(accountProto.givenName); + builder.withFamilyName(accountProto.familyName); account.getRecipientStore().storeProfile(connection, account.getSelfRecipientId(), builder.build()); account.getRecipientStore() .storeStorageRecord(connection, account.getSelfRecipientId(), accountRecord.getId(), - accountRecord.toProto().encode()); + accountProto.encode()); } @Override @@ -221,7 +198,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor { private static final Logger logger = LoggerFactory.getLogger(ContactRecordProcessor.class); @@ -55,20 +61,24 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor getMatching(SignalContactRecord remote) throws SQLException { - final var address = getRecipientAddress(remote); + final var address = getRecipientAddress(remote.getProto()); final var recipientId = account.getRecipientStore().resolveRecipient(connection, address); final var recipient = account.getRecipientStore().getRecipient(connection, recipientId); @@ -85,141 +95,120 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor 0 && ( + !account.isPrimaryDevice() + || remote.identityState != local.identityState + || local.identityKey.size() == 0 )) { - identityState = remote.getIdentityState(); - identityKey = remote.getIdentityKey().get(); + identityState = remote.identityState; + identityKey = remote.identityKey; } else { - identityState = local.getIdentityState(); - identityKey = local.getIdentityKey().orElse(null); + identityState = local.identityState; + identityKey = local.identityKey.size() > 0 ? local.identityKey : ByteString.EMPTY; } - if (local.getAci().isPresent() - && local.getIdentityKey().isPresent() - && remote.getIdentityKey().isPresent() - && !Arrays.equals(local.getIdentityKey().get(), remote.getIdentityKey().get())) { + if (!local.aci.isEmpty() + && local.identityKey.size() > 0 + && remote.identityKey.size() > 0 + && !local.identityKey.equals(remote.identityKey)) { logger.debug("The local and remote identity keys do not match for {}. Enqueueing a profile fetch.", - local.getAci().orElse(null)); + local.aci); final var address = getRecipientAddress(local); jobExecutor.enqueueJob(new DownloadProfileJob(address)); } - final var e164sMatchButPnisDont = local.getNumber().isPresent() - && local.getNumber() - .get() - .equals(remote.getNumber().orElse(null)) - && local.getPni().isPresent() - && remote.getPni().isPresent() - && !local.getPni().get().equals(remote.getPni().get()); - - final var pnisMatchButE164sDont = local.getPni().isPresent() - && local.getPni() - .get() - .equals(remote.getPni().orElse(null)) - && local.getNumber().isPresent() - && remote.getNumber().isPresent() - && !local.getNumber().get().equals(remote.getNumber().get()); - - PNI pni; + String pni; String e164; - if (!account.isPrimaryDevice() && (e164sMatchButPnisDont || pnisMatchButE164sDont)) { - if (e164sMatchButPnisDont) { - logger.debug("Matching E164s, but the PNIs differ! Trusting our local pair."); - } else if (pnisMatchButE164sDont) { - logger.debug("Matching PNIs, but the E164s differ! Trusting our local pair."); + if (account.isPrimaryDevice()) { + final var e164sMatchButPnisDont = !local.e164.isEmpty() + && local.e164.equals(remote.e164) + && !local.pni.isEmpty() + && !remote.pni.isEmpty() + && !local.pni.equals(remote.pni); + + final var pnisMatchButE164sDont = !local.pni.isEmpty() + && local.pni.equals(remote.pni) + && !local.e164.isEmpty() + && !remote.e164.isEmpty() + && !local.e164.equals(remote.e164); + + if (e164sMatchButPnisDont || pnisMatchButE164sDont) { + if (e164sMatchButPnisDont) { + logger.debug("Matching E164s, but the PNIs differ! Trusting our local pair."); + } else if (pnisMatchButE164sDont) { + logger.debug("Matching PNIs, but the E164s differ! Trusting our local pair."); + } + jobExecutor.enqueueJob(new RefreshRecipientsJob()); + pni = local.pni; + e164 = local.e164; + } else { + pni = firstNonEmpty(remote.pni, local.pni); + e164 = firstNonEmpty(remote.e164, local.e164); } - jobExecutor.enqueueJob(new RefreshRecipientsJob()); - pni = local.getPni().get(); - e164 = local.getNumber().get(); } else { - pni = OptionalUtil.or(remote.getPni(), local.getPni()).orElse(null); - e164 = OptionalUtil.or(remote.getNumber(), local.getNumber()).orElse(null); + pni = firstNonEmpty(remote.pni, local.pni); + e164 = firstNonEmpty(remote.e164, local.e164); } - final var unknownFields = remote.serializeUnknownFields(); - final var aci = local.getAci().isEmpty() ? remote.getAci().orElse(null) : local.getAci().get(); - final var profileKey = OptionalUtil.or(remote.getProfileKey(), local.getProfileKey()).orElse(null); - final var username = OptionalUtil.or(remote.getUsername(), local.getUsername()).orElse(""); - final var blocked = remote.isBlocked(); - final var profileSharing = remote.isProfileSharingEnabled(); - final var archived = remote.isArchived(); - final var forcedUnread = remote.isForcedUnread(); - final var muteUntil = remote.getMuteUntil(); - final var hideStory = remote.shouldHideStory(); - final var unregisteredTimestamp = remote.getUnregisteredTimestamp(); - final var hidden = remote.isHidden(); - final var systemGivenName = account.isPrimaryDevice() - ? local.getSystemGivenName().orElse("") - : remote.getSystemGivenName().orElse(""); - final var systemFamilyName = account.isPrimaryDevice() - ? local.getSystemFamilyName().orElse("") - : remote.getSystemFamilyName().orElse(""); - final var systemNickname = remote.getSystemNickname().orElse(""); - final var nicknameGivenName = remote.getNicknameGivenName().orElse(""); - final var nicknameFamilyName = remote.getNicknameFamilyName().orElse(""); - final var pniSignatureVerified = remote.isPniSignatureVerified() || local.isPniSignatureVerified(); - final var note = remote.getNote().or(local::getNote).orElse(""); - - final var mergedBuilder = new SignalContactRecord.Builder(remote.getId().getRaw(), aci, unknownFields).setE164( - e164) - .setPni(pni) - .setProfileGivenName(profileGivenName) - .setProfileFamilyName(profileFamilyName) - .setSystemGivenName(systemGivenName) - .setSystemFamilyName(systemFamilyName) - .setSystemNickname(systemNickname) - .setProfileKey(profileKey) - .setUsername(username) - .setIdentityState(identityState) - .setIdentityKey(identityKey) - .setBlocked(blocked) - .setProfileSharingEnabled(profileSharing) - .setArchived(archived) - .setForcedUnread(forcedUnread) - .setMuteUntil(muteUntil) - .setHideStory(hideStory) - .setUnregisteredTimestamp(unregisteredTimestamp) - .setHidden(hidden) - .setPniSignatureVerified(pniSignatureVerified) - .setNicknameGivenName(nicknameGivenName) - .setNicknameFamilyName(nicknameFamilyName) - .setNote(note); + final var mergedBuilder = SignalContactRecord.Companion.newBuilder(remote.unknownFields().toByteArray()) + .aci(local.aci.isEmpty() ? remote.aci : local.aci) + .e164(e164) + .pni(pni) + .givenName(profileGivenName) + .familyName(profileFamilyName) + .systemGivenName(account.isPrimaryDevice() ? local.systemGivenName : remote.systemGivenName) + .systemFamilyName(account.isPrimaryDevice() ? local.systemFamilyName : remote.systemFamilyName) + .systemNickname(remote.systemNickname) + .profileKey(firstNonEmpty(remote.profileKey, local.profileKey)) + .username(firstNonEmpty(remote.username, local.username)) + .identityState(identityState) + .identityKey(identityKey) + .blocked(remote.blocked) + .whitelisted(remote.whitelisted) + .archived(remote.archived) + .markedUnread(remote.markedUnread) + .mutedUntilTimestamp(remote.mutedUntilTimestamp) + .hideStory(remote.hideStory) + .unregisteredAtTimestamp(remote.unregisteredAtTimestamp) + .hidden(remote.hidden) + .pniSignatureVerified(remote.pniSignatureVerified || local.pniSignatureVerified) + .nickname(remote.nickname) + .note(remote.note); final var merged = mergedBuilder.build(); final var matchesRemote = doProtosMatch(merged, remote); if (matchesRemote) { - return remote; + return remoteRecord; } final var matchesLocal = doProtosMatch(merged, local); if (matchesLocal) { - return local; + return localRecord; } - return mergedBuilder.setId(KeyUtils.createRawStorageId()).build(); + return new SignalContactRecord(StorageId.forContact(KeyUtils.createRawStorageId()), mergedBuilder.build()); } @Override @@ -231,7 +220,8 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor update) throws SQLException { final var contactRecord = update.newRecord(); - final var address = getRecipientAddress(contactRecord); + final var contactProto = contactRecord.getProto(); + final var address = getRecipientAddress(contactProto); final var recipientId = account.getRecipientStore().resolveRecipientTrusted(connection, address); final var recipient = account.getRecipientStore().getRecipient(connection, recipientId); @@ -251,95 +241,92 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor

    - * The monitor is also responsible for sending heartbeats/keep-alive messages to prevent - * timeouts. - */ final class SignalWebSocketHealthMonitor implements HealthMonitor { private static final Logger logger = LoggerFactory.getLogger(SignalWebSocketHealthMonitor.class); + /** + * This is the amount of time in between sent keep alives. Must be greater than [KEEP_ALIVE_TIMEOUT] + */ private static final long KEEP_ALIVE_SEND_CADENCE = TimeUnit.SECONDS.toMillis(OkHttpWebSocketConnection.KEEPALIVE_FREQUENCY_SECONDS); - private static final long MAX_TIME_SINCE_SUCCESSFUL_KEEP_ALIVE = KEEP_ALIVE_SEND_CADENCE * 3; - private SignalWebSocket signalWebSocket; + /** + * This is the amount of time we will wait for a response to the keep alive before we consider the websockets dead. + * It is required that this value be less than [KEEP_ALIVE_SEND_CADENCE] + */ + private static final long KEEP_ALIVE_TIMEOUT = TimeUnit.SECONDS.toMillis(20); + + private final Executor executor = Executors.newSingleThreadExecutor(); private final SleepTimer sleepTimer; - - private volatile KeepAliveSender keepAliveSender; - - private final HealthState identified = new HealthState(); - private final HealthState unidentified = new HealthState(); + private SignalWebSocket webSocket = null; + private volatile KeepAliveSender keepAliveSender = null; + private boolean needsKeepAlive = false; + private long lastKeepAliveReceived = 0; public SignalWebSocketHealthMonitor(SleepTimer sleepTimer) { this.sleepTimer = sleepTimer; } - public void monitor(SignalWebSocket signalWebSocket) { - Preconditions.checkNotNull(signalWebSocket); - Preconditions.checkArgument(this.signalWebSocket == null, "monitor can only be called once"); + void monitor(SignalWebSocket webSocket) { + Preconditions.checkNotNull(webSocket); + Preconditions.checkArgument(this.webSocket == null, "monitor can only be called once"); - this.signalWebSocket = signalWebSocket; + executor.execute(() -> { - //noinspection ResultOfMethodCallIgnored - signalWebSocket.getWebSocketState() - .subscribeOn(Schedulers.computation()) - .observeOn(Schedulers.computation()) - .distinctUntilChanged() - .subscribe(s -> onStateChange(s, identified)); + this.webSocket = webSocket; - //noinspection ResultOfMethodCallIgnored - signalWebSocket.getUnidentifiedWebSocketState() - .subscribeOn(Schedulers.computation()) - .observeOn(Schedulers.computation()) - .distinctUntilChanged() - .subscribe(s -> onStateChange(s, unidentified)); + webSocket.getState() + .subscribeOn(Schedulers.computation()) + .observeOn(Schedulers.computation()) + .distinctUntilChanged() + .subscribe(this::onStateChanged); + + webSocket.setKeepAliveChangedListener(this::updateKeepAliveSenderStatus); + }); } - private synchronized void onStateChange(WebSocketConnectionState connectionState, HealthState healthState) { - switch (connectionState) { - case CONNECTED -> logger.debug("WebSocket is now connected"); - case AUTHENTICATION_FAILED -> logger.debug("WebSocket authentication failed"); - case FAILED -> logger.debug("WebSocket connection failed"); - } + private void onStateChanged(WebSocketConnectionState connectionState) { + executor.execute(() -> { + needsKeepAlive = connectionState == WebSocketConnectionState.CONNECTED; - healthState.needsKeepAlive = connectionState == WebSocketConnectionState.CONNECTED; - - if (keepAliveSender == null && isKeepAliveNecessary()) { - keepAliveSender = new KeepAliveSender(); - keepAliveSender.start(); - } else if (keepAliveSender != null && !isKeepAliveNecessary()) { - keepAliveSender.shutdown(); - keepAliveSender = null; - } + updateKeepAliveSenderStatus(); + }); } @Override public void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket) { - if (isIdentifiedWebSocket) { - identified.lastKeepAliveReceived = System.currentTimeMillis(); - } else { - unidentified.lastKeepAliveReceived = System.currentTimeMillis(); - } + final var keepAliveTime = System.currentTimeMillis(); + executor.execute(() -> lastKeepAliveReceived = keepAliveTime); } @Override public void onMessageError(int status, boolean isIdentifiedWebSocket) { - if (status == 409) { - HealthState healthState = (isIdentifiedWebSocket ? identified : unidentified); - if (healthState.mismatchErrorTracker.addSample(System.currentTimeMillis())) { - logger.warn("Received too many mismatch device errors, forcing new websockets."); - signalWebSocket.forceNewWebSockets(); - signalWebSocket.connect(); - } + } + + private Unit updateKeepAliveSenderStatus() { + if (keepAliveSender == null && sendKeepAlives()) { + keepAliveSender = new KeepAliveSender(); + keepAliveSender.start(); + } else if (keepAliveSender != null && !sendKeepAlives()) { + keepAliveSender.shutdown(); + keepAliveSender = null; } + return Unit.INSTANCE; } - private boolean isKeepAliveNecessary() { - return identified.needsKeepAlive || unidentified.needsKeepAlive; - } - - private static class HealthState { - - private final HttpErrorTracker mismatchErrorTracker = new HttpErrorTracker(5, TimeUnit.MINUTES.toMillis(1)); - - private volatile boolean needsKeepAlive; - private volatile long lastKeepAliveReceived; + private boolean sendKeepAlives() { + return needsKeepAlive && webSocket != null && webSocket.getShouldSendKeepAlives(); } /** - * Sends periodic heartbeats/keep-alives over both WebSockets to prevent connection timeouts. If - * either WebSocket fails 3 times to get a return heartbeat both are forced to be recreated. + * Sends periodic heartbeats/keep-alives over the WebSocket to prevent connection timeouts. If + * the WebSocket fails to get a return heartbeat after [KEEP_ALIVE_TIMEOUT] seconds, it is forced to be recreated. */ - private class KeepAliveSender extends Thread { + private final class KeepAliveSender extends Thread { private volatile boolean shouldKeepRunning = true; + @Override public void run() { - identified.lastKeepAliveReceived = System.currentTimeMillis(); - unidentified.lastKeepAliveReceived = System.currentTimeMillis(); + logger.debug("[KeepAliveSender({})] started", this.threadId()); + lastKeepAliveReceived = System.currentTimeMillis(); - while (shouldKeepRunning && isKeepAliveNecessary()) { + var keepAliveSendTime = System.currentTimeMillis(); + while (shouldKeepRunning && sendKeepAlives()) { try { - sleepTimer.sleep(KEEP_ALIVE_SEND_CADENCE); + final var nextKeepAliveSendTime = keepAliveSendTime + KEEP_ALIVE_SEND_CADENCE; + sleepUntil(nextKeepAliveSendTime); - if (shouldKeepRunning && isKeepAliveNecessary()) { - long keepAliveRequiredSinceTime = System.currentTimeMillis() - - MAX_TIME_SINCE_SUCCESSFUL_KEEP_ALIVE; + if (shouldKeepRunning && sendKeepAlives()) { + keepAliveSendTime = System.currentTimeMillis(); + webSocket.sendKeepAlive(); + } - if (identified.lastKeepAliveReceived < keepAliveRequiredSinceTime - || unidentified.lastKeepAliveReceived < keepAliveRequiredSinceTime) { - logger.warn("Missed keep alives, identified last: " - + identified.lastKeepAliveReceived - + " unidentified last: " - + unidentified.lastKeepAliveReceived - + " needed by: " - + keepAliveRequiredSinceTime); - signalWebSocket.forceNewWebSockets(); - signalWebSocket.connect(); - } else { - signalWebSocket.sendKeepAlive(); + final var responseRequiredTime = keepAliveSendTime + KEEP_ALIVE_TIMEOUT; + sleepUntil(responseRequiredTime); + + if (shouldKeepRunning && sendKeepAlives()) { + if (lastKeepAliveReceived < keepAliveSendTime) { + logger.debug("Missed keep alive, last: {} needed by: {}", + lastKeepAliveReceived, + responseRequiredTime); + webSocket.forceNewWebSocket(); } } } catch (Throwable e) { - logger.warn("Error occurred in KeepAliveSender, ignoring ...", e); + logger.warn("Keep alive sender failed", e); + } + } + logger.debug("[KeepAliveSender({})] ended", threadId()); + } + + void sleepUntil(long timeMillis) { + while (System.currentTimeMillis() < timeMillis) { + final var waitTime = timeMillis - System.currentTimeMillis(); + if (waitTime > 0) { + try { + sleepTimer.sleep(waitTime); + } catch (InterruptedException e) { + logger.warn("WebSocket health monitor interrupted", e); + } } } } - public void shutdown() { + void shutdown() { shouldKeepRunning = false; } } - - private static final class HttpErrorTracker { - - private final long[] timestamps; - private final long errorTimeRange; - - public HttpErrorTracker(int samples, long errorTimeRange) { - this.timestamps = new long[samples]; - this.errorTimeRange = errorTimeRange; - } - - public synchronized boolean addSample(long now) { - long errorsMustBeAfter = now - errorTimeRange; - int count = 1; - int minIndex = 0; - - for (int i = 0; i < timestamps.length; i++) { - if (timestamps[i] < errorsMustBeAfter) { - timestamps[i] = 0; - } else if (timestamps[i] != 0) { - count++; - } - - if (timestamps[i] < timestamps[minIndex]) { - minIndex = i; - } - } - - timestamps[minIndex] = now; - - if (count >= timestamps.length) { - Arrays.fill(timestamps, 0); - return true; - } - return false; - } - } } + diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java index a0fa3012..7be4a401 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/accounts/AccountsStore.java @@ -1,6 +1,7 @@ package org.asamk.signal.manager.storage.accounts; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.i18n.phonenumbers.PhoneNumberUtil; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.ServiceEnvironment; @@ -10,7 +11,6 @@ import org.asamk.signal.manager.util.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.push.ServiceId.ACI; -import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -181,7 +181,7 @@ public class AccountsStore { return Arrays.stream(files) .filter(File::isFile) .map(File::getName) - .filter(file -> PhoneNumberFormatter.isValidNumber(file, null)) + .filter(file -> PhoneNumberUtil.getInstance().isPossibleNumber(file, null)) .collect(Collectors.toSet()); } diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java index 2863874b..d4841c7a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java @@ -195,7 +195,8 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor localNumber.length()) return "+" + number; + + int difference = localNumber.length() - number.length(); + + return "+" + localNumber.substring(0, difference) + number; + } + + public static String formatNumber(String number, String localNumber) throws InvalidNumberException { + if (number == null) { + throw new InvalidNumberException("Null String passed as number."); + } + + if (number.contains("@")) { + throw new InvalidNumberException("Possible attempt to use email address."); + } + + number = number.replaceAll("[^0-9+]", ""); + + if (number.isEmpty()) { + throw new InvalidNumberException("No valid characters found."); + } + + try { + PhoneNumberUtil util = PhoneNumberUtil.getInstance(); + PhoneNumber localNumberObject = util.parse(localNumber, null); + + String localCountryCode = util.getRegionCodeForNumber(localNumberObject); + logger.trace("Got local CC: {}", localCountryCode); + + PhoneNumber numberObject = util.parse(number, localCountryCode); + return util.format(numberObject, PhoneNumberFormat.E164); + } catch (NumberParseException e) { + logger.debug("{}: {}", e.getClass().getSimpleName(), e.getMessage()); + return impreciseFormatNumber(number, localNumber); + } + } +} diff --git a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java index b90a7873..9d97ed7a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java @@ -162,7 +162,11 @@ public class Utils { throw new IOException(throwableOptional); } } - return response.successOrThrow(); + try { + return response.successOrThrow(); + } catch (Throwable e) { + throw new AssertionError(e); + } } public static ByteString firstNonEmpty(ByteString... strings) { From 1295ef69ca8a794b40abd0dd6290fd2ca250713a Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 16 Mar 2025 22:07:29 +0100 Subject: [PATCH 214/279] Use record patterns --- .../manager/helper/RecipientHelper.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) 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 58e7ba59..5a3524ec 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 @@ -25,6 +25,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; import static org.asamk.signal.manager.config.ServiceConfig.MAXIMUM_ONE_OFF_REQUEST_SIZE; import static org.asamk.signal.manager.util.Utils.handleResponseException; @@ -77,12 +78,11 @@ public class RecipientHelper { } public RecipientId resolveRecipient(final RecipientIdentifier.Single recipient) throws UnregisteredRecipientException { - if (recipient instanceof RecipientIdentifier.Uuid uuidRecipient) { - return account.getRecipientResolver().resolveRecipient(ACI.from(uuidRecipient.uuid())); - } else if (recipient instanceof RecipientIdentifier.Pni pniRecipient) { - return account.getRecipientResolver().resolveRecipient(PNI.from(pniRecipient.pni())); - } else if (recipient instanceof RecipientIdentifier.Number numberRecipient) { - final var number = numberRecipient.number(); + if (recipient instanceof RecipientIdentifier.Uuid(UUID uuid)) { + return account.getRecipientResolver().resolveRecipient(ACI.from(uuid)); + } else if (recipient instanceof RecipientIdentifier.Pni(UUID pni)) { + return account.getRecipientResolver().resolveRecipient(PNI.from(pni)); + } else if (recipient instanceof RecipientIdentifier.Number(String number)) { return account.getRecipientStore().resolveRecipientByNumber(number, () -> { try { return getRegisteredUserByNumber(number); @@ -90,8 +90,7 @@ public class RecipientHelper { return null; } }); - } else if (recipient instanceof RecipientIdentifier.Username usernameRecipient) { - var username = usernameRecipient.username(); + } else if (recipient instanceof RecipientIdentifier.Username(String username)) { return resolveRecipientByUsernameOrLink(username, false); } throw new AssertionError("Unexpected RecipientIdentifier: " + recipient); @@ -145,8 +144,8 @@ public class RecipientHelper { try { return Optional.of(resolveRecipient(recipient)); } catch (UnregisteredRecipientException e) { - if (recipient instanceof RecipientIdentifier.Number r) { - return account.getRecipientStore().resolveRecipientByNumberOptional(r.number()); + if (recipient instanceof RecipientIdentifier.Number(String number)) { + return account.getRecipientStore().resolveRecipientByNumberOptional(number); } else { return Optional.empty(); } From f18015ff2ef10d49f8ab0365253e8cece9577dc2 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 18 Mar 2025 18:13:38 +0100 Subject: [PATCH 215/279] f --- graalvm-config-dir/reflect-config.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 481999d3..50e17987 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -2991,6 +2991,9 @@ "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$UsernameLink", "allDeclaredFields":true }, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AvatarColor" +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord", "allDeclaredFields":true, From 67302eb9c31e3bcce4c39ea90d5bdb1f77306a02 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 18 Mar 2025 18:20:44 +0100 Subject: [PATCH 216/279] Replace cached envelopes when moving Fixes #1730 --- .../signal/manager/storage/messageCache/MessageCache.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java b/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java index 51882a44..e4fd8c01 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/messageCache/MessageCache.java @@ -10,6 +10,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Collections; import java.util.Objects; @@ -75,7 +76,7 @@ public class MessageCache { return cachedMessage; } logger.debug("Moving cached message {} to {}", cachedMessage.getFile().toPath(), cacheFile.toPath()); - Files.move(cachedMessage.getFile().toPath(), cacheFile.toPath()); + Files.move(cachedMessage.getFile().toPath(), cacheFile.toPath(), StandardCopyOption.REPLACE_EXISTING); return new CachedMessage(cacheFile); } From 6befda7ef1dabc760db516be5080f1c0bb1c657c Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 22 Mar 2025 10:55:16 +0100 Subject: [PATCH 217/279] Update graalvm build tools --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 67a180fb..38c3e265 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ plugins { application eclipse `check-lib-versions` - id("org.graalvm.buildtools.native") version "0.10.5" + id("org.graalvm.buildtools.native") version "0.10.6" } allprojects { From 14b07be0dcc9d61eeebc857338367515a3e338b3 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 31 Mar 2025 09:11:05 +0200 Subject: [PATCH 218/279] Always renew session when failing to decrypt message --- .../signal/manager/actions/RenewSessionAction.java | 4 +--- .../actions/SendRetryMessageRequestAction.java | 11 +---------- .../signal/manager/helper/IncomingMessageHandler.java | 7 +++---- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/RenewSessionAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/RenewSessionAction.java index 40117246..35058940 100644 --- a/lib/src/main/java/org/asamk/signal/manager/actions/RenewSessionAction.java +++ b/lib/src/main/java/org/asamk/signal/manager/actions/RenewSessionAction.java @@ -19,9 +19,7 @@ public class RenewSessionAction implements HandleAction { @Override public void execute(Context context) throws Throwable { context.getAccount().getAccountData(accountId).getSessionStore().archiveSessions(serviceId); - if (!recipientId.equals(context.getAccount().getSelfRecipientId())) { - context.getSendHelper().sendNullMessage(recipientId); - } + context.getSendHelper().sendNullMessage(recipientId); } @Override diff --git a/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java b/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java index 865f062f..96a94128 100644 --- a/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java +++ b/lib/src/main/java/org/asamk/signal/manager/actions/SendRetryMessageRequestAction.java @@ -7,7 +7,6 @@ import org.signal.libsignal.metadata.ProtocolException; import org.signal.libsignal.protocol.message.CiphertextMessage; import org.signal.libsignal.protocol.message.DecryptionErrorMessage; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.internal.push.Envelope; import java.util.Optional; @@ -15,29 +14,21 @@ import java.util.Optional; public class SendRetryMessageRequestAction implements HandleAction { private final RecipientId recipientId; - private final ServiceId serviceId; private final ProtocolException protocolException; private final SignalServiceEnvelope envelope; - private final ServiceId accountId; public SendRetryMessageRequestAction( final RecipientId recipientId, - final ServiceId serviceId, final ProtocolException protocolException, - final SignalServiceEnvelope envelope, - final ServiceId accountId + final SignalServiceEnvelope envelope ) { this.recipientId = recipientId; - this.serviceId = serviceId; this.protocolException = protocolException; this.envelope = envelope; - this.accountId = accountId; } @Override public void execute(Context context) throws Throwable { - context.getAccount().getAccountData(accountId).getSessionStore().archiveSessions(serviceId); - int senderDevice = protocolException.getSenderDevice(); Optional groupId = protocolException.getGroupId().isPresent() ? Optional.of(GroupId.unknownVersion( protocolException.getGroupId().get())) : Optional.empty(); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 90d67a36..1e808e64 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -165,12 +165,11 @@ public final class IncomingMessageHandler { if (serviceId != null) { final var isSelf = sender.equals(account.getSelfRecipientId()) && e.getSenderDevice() == account.getDeviceId(); + logger.debug("Received invalid message, queuing renew session action."); + actions.add(new RenewSessionAction(sender, serviceId, destination)); if (!isSelf) { logger.debug("Received invalid message, requesting message resend."); - actions.add(new SendRetryMessageRequestAction(sender, serviceId, e, envelope, destination)); - } else { - logger.debug("Received invalid message, queuing renew session action."); - actions.add(new RenewSessionAction(sender, serviceId, destination)); + actions.add(new SendRetryMessageRequestAction(sender, e, envelope)); } } else { logger.debug("Received invalid message from invalid sender: {}", e.getSender()); From adb6787d5b3d1dae30669e38ca6cdfaf753f6578 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 31 Mar 2025 09:11:28 +0200 Subject: [PATCH 219/279] Refresh prekeys when receiving message with invalid key id --- .../asamk/signal/manager/helper/IncomingMessageHandler.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 1e808e64..3bb7479a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -157,6 +157,9 @@ public final class IncomingMessageHandler { } catch (ProtocolInvalidKeyIdException | ProtocolInvalidKeyException | ProtocolNoSessionException | ProtocolInvalidMessageException e) { logger.debug("Failed to decrypt incoming message", e); + if (e instanceof ProtocolInvalidKeyIdException) { + actions.add(RefreshPreKeysAction.create()); + } final var sender = account.getRecipientResolver().resolveRecipient(e.getSender()); if (context.getContactHelper().isContactBlocked(sender)) { logger.debug("Received invalid message from blocked contact, ignoring."); From 3dc8844cb4e1feb916e5af37cb7817312e033cb8 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 31 Mar 2025 09:19:20 +0200 Subject: [PATCH 220/279] Update libraries --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b8d57a12..5d633271 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,15 +3,15 @@ slf4j = "2.0.17" [libraries] bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.80" -jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.18.2" +jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.18.3" argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0" dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0" slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } -logback = "ch.qos.logback:logback-classic:1.5.17" +logback = "ch.qos.logback:logback-classic:1.5.18" signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_119" sqlite = "org.xerial:sqlite-jdbc:3.49.1.0" -hikari = "com.zaxxer:HikariCP:6.2.1" +hikari = "com.zaxxer:HikariCP:6.3.0" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.12.0" junit-launcher = "org.junit.platform:junit-platform-launcher:1.12.0" From 69a9b30732aac5ff79012e98f844527074f391a1 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 31 Mar 2025 14:45:02 +0200 Subject: [PATCH 221/279] Update libsignal-service --- gradle/libs.versions.toml | 2 +- .../java/org/asamk/signal/manager/helper/ReceiveHelper.java | 2 ++ .../asamk/signal/manager/internal/SignalDependencies.java | 5 +++-- .../manager/internal/SignalWebSocketHealthMonitor.java | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5d633271..49f54ff8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.18" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_119" +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_120" sqlite = "org.xerial:sqlite-jdbc:3.49.1.0" hikari = "com.zaxxer:HikariCP:6.3.0" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.12.0" diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java index eb935188..71f69081 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ReceiveHelper.java @@ -100,6 +100,7 @@ public class ReceiveHelper { .distinctUntilChanged() .subscribe(this::onWebSocketStateChange); signalWebSocket.connect(); + signalWebSocket.registerKeepAliveToken("receive"); try { receiveMessagesInternal(signalWebSocket, timeout, returnOnTimeout, maxMessages, handler, queuedActions); @@ -107,6 +108,7 @@ public class ReceiveHelper { hasCaughtUpWithOldMessages = false; handleQueuedActions(queuedActions.keySet()); queuedActions.clear(); + signalWebSocket.removeKeepAliveToken("receive"); signalWebSocket.disconnect(); webSocketStateDisposable.dispose(); shouldStop = false; diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index 972b422f..c19e6b66 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -41,6 +41,7 @@ import java.net.Proxy; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; public class SignalDependencies { @@ -257,7 +258,7 @@ public class SignalDependencies { Optional.of(credentialsProvider), userAgent, healthMonitor, - allowStories)); + allowStories), timer, TimeUnit.SECONDS.toMillis(10)); healthMonitor.monitor(authenticatedSignalWebSocket); }); } @@ -273,7 +274,7 @@ public class SignalDependencies { Optional.empty(), userAgent, healthMonitor, - allowStories)); + allowStories), timer, TimeUnit.SECONDS.toMillis(10)); healthMonitor.monitor(unauthenticatedSignalWebSocket); }); } diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java index b5b855ac..dfae5985 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java @@ -90,7 +90,7 @@ final class SignalWebSocketHealthMonitor implements HealthMonitor { } private boolean sendKeepAlives() { - return needsKeepAlive && webSocket != null && webSocket.getShouldSendKeepAlives(); + return needsKeepAlive && webSocket != null && webSocket.shouldSendKeepAlives(); } /** From db2182aa7d8b44a49691f7e6d49bac9961f841eb Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 6 Apr 2025 19:37:18 +0200 Subject: [PATCH 222/279] Update libsignal-service --- graalvm-config-dir/jni-config.json | 4 ++ graalvm-config-dir/reflect-config.json | 29 +++++++++++- gradle/libs.versions.toml | 2 +- .../signal/manager/config/ServiceConfig.java | 3 +- .../signal/manager/helper/PreKeyHelper.java | 8 ++-- .../signal/manager/helper/ProfileHelper.java | 10 +++-- .../helper/UnidentifiedAccessHelper.java | 7 ++- .../manager/internal/SignalDependencies.java | 45 +++++++++++++++++-- .../org/asamk/signal/manager/util/Utils.java | 15 +------ run_tests.sh | 1 + 10 files changed, 94 insertions(+), 30 deletions(-) diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index 7c68c6d6..0284d58f 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -90,6 +90,10 @@ "name":"org.signal.libsignal.internal.CompletableFuture", "methods":[{"name":"","parameterTypes":[] }, {"name":"complete","parameterTypes":["java.lang.Object"] }, {"name":"completeExceptionally","parameterTypes":["java.lang.Throwable"] }] }, +{ + "name":"org.signal.libsignal.internal.NativeHandleGuard$SimpleOwner", + "methods":[{"name":"unsafeNativeHandleWithoutGuard","parameterTypes":[] }] +}, { "name":"org.signal.libsignal.net.CdsiLookupResponse", "methods":[{"name":"","parameterTypes":["java.util.Map","int"] }] diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 50e17987..cdf38732 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -39,6 +39,9 @@ { "name":"[Ljava.sql.Statement;" }, +{ + "name":"[Lorg.asamk.signal.commands.ListStickerPacksCommand$JsonStickerPack$JsonSticker;" +}, { "name":"[Lorg.asamk.signal.json.JsonAttachment;" }, @@ -2302,7 +2305,7 @@ "allDeclaredFields":true, "allDeclaredMethods":true, "allDeclaredConstructors":true, - "methods":[{"name":"getAnnouncementGroup","parameterTypes":[] }, {"name":"getChangeNumber","parameterTypes":[] }, {"name":"getDeleteSync","parameterTypes":[] }, {"name":"getGiftBadges","parameterTypes":[] }, {"name":"getPaymentActivation","parameterTypes":[] }, {"name":"getPni","parameterTypes":[] }, {"name":"getSenderKey","parameterTypes":[] }, {"name":"getStorage","parameterTypes":[] }, {"name":"getStorageServiceEncryptionV2","parameterTypes":[] }, {"name":"getStories","parameterTypes":[] }, {"name":"getVersionedExpirationTimer","parameterTypes":[] }] + "methods":[{"name":"getAnnouncementGroup","parameterTypes":[] }, {"name":"getAttachmentBackfill","parameterTypes":[] }, {"name":"getChangeNumber","parameterTypes":[] }, {"name":"getDeleteSync","parameterTypes":[] }, {"name":"getGiftBadges","parameterTypes":[] }, {"name":"getPaymentActivation","parameterTypes":[] }, {"name":"getPni","parameterTypes":[] }, {"name":"getSenderKey","parameterTypes":[] }, {"name":"getStorage","parameterTypes":[] }, {"name":"getStorageServiceEncryptionV2","parameterTypes":[] }, {"name":"getStories","parameterTypes":[] }, {"name":"getVersionedExpirationTimer","parameterTypes":[] }] }, { "name":"org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest", @@ -2329,6 +2332,13 @@ { "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]" }, +{ + "name":"org.whispersystems.signalservice.api.keys.OneTimePreKeyCounts", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse", "allDeclaredFields":true, @@ -2396,7 +2406,14 @@ "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["java.lang.String","byte[]","byte[]","byte[]","byte[]","byte[]","boolean","boolean","byte[]","java.util.List"] }, {"name":"getAbout","parameterTypes":[] }, {"name":"getAboutEmoji","parameterTypes":[] }, {"name":"getAvatar","parameterTypes":[] }, {"name":"getBadgeIds","parameterTypes":[] }, {"name":"getCommitment","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"getPaymentAddress","parameterTypes":[] }, {"name":"getPhoneNumberSharing","parameterTypes":[] }, {"name":"getSameAvatar","parameterTypes":[] }, {"name":"getVersion","parameterTypes":[] }] +}, +{ + "name":"org.whispersystems.signalservice.api.provisioning.ProvisioningMessage", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true }, { "name":"org.whispersystems.signalservice.api.push.ServiceId", @@ -2441,6 +2458,12 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String"] }] }, +{ + "name":"org.whispersystems.signalservice.api.ratelimit.SubmitRecaptchaChallengePayload", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, { "name":"org.whispersystems.signalservice.api.storage.StorageAuthResponse", "allDeclaredFields":true, @@ -2965,6 +2988,7 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord", "allDeclaredFields":true, + "fields":[{"name":"avatarColor"}, {"name":"avatarUrlPath"}, {"name":"backupSubscriberData"}, {"name":"backupTier"}, {"name":"displayBadgesOnProfile"}, {"name":"e164"}, {"name":"familyName"}, {"name":"givenName"}, {"name":"hasBackup"}, {"name":"hasCompletedUsernameOnboarding"}, {"name":"hasSeenGroupStoryEducationSheet"}, {"name":"hasSetMyStoriesPrivacy"}, {"name":"hasViewedOnboardingStory"}, {"name":"keepMutedChatsArchived"}, {"name":"linkPreviews"}, {"name":"noteToSelfArchived"}, {"name":"noteToSelfMarkedUnread"}, {"name":"payments"}, {"name":"phoneNumberSharingMode"}, {"name":"pinnedConversations"}, {"name":"preferContactAvatars"}, {"name":"preferredReactionEmoji"}, {"name":"primarySendsSms"}, {"name":"profileKey"}, {"name":"readReceipts"}, {"name":"sealedSenderIndicators"}, {"name":"storiesDisabled"}, {"name":"storyViewReceiptsEnabled"}, {"name":"subscriberCurrencyCode"}, {"name":"subscriberId"}, {"name":"subscriptionManuallyCancelled"}, {"name":"typingIndicators"}, {"name":"universalExpireTimer"}, {"name":"unlistedPhoneNumber"}, {"name":"username"}, {"name":"usernameLink"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { @@ -3027,6 +3051,7 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record", "allDeclaredFields":true, + "fields":[{"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"dontNotifyForMentionsIfMuted"}, {"name":"hideStory"}, {"name":"markedUnread"}, {"name":"masterKey"}, {"name":"mutedUntilTimestamp"}, {"name":"storySendMode"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 49f54ff8..74f97a76 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.18" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_120" +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_121" sqlite = "org.xerial:sqlite-jdbc:3.49.1.0" hikari = "com.zaxxer:HikariCP:6.3.0" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.12.0" 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 fed6f457..587fc89b 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 @@ -30,7 +30,8 @@ public class ServiceConfig { public static AccountAttributes.Capabilities getCapabilities(boolean isPrimaryDevice) { final var deleteSync = !isPrimaryDevice; final var storageEncryptionV2 = !isPrimaryDevice; - return new AccountAttributes.Capabilities(true, deleteSync, true, storageEncryptionV2); + final var attachmentBackfill = !isPrimaryDevice; + return new AccountAttributes.Capabilities(true, deleteSync, true, storageEncryptionV2, attachmentBackfill); } public static ServiceEnvironmentConfig getServiceEnvironmentConfig( diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java index b17a9206..bf7ad580 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/PreKeyHelper.java @@ -11,17 +11,19 @@ import org.signal.libsignal.protocol.state.PreKeyRecord; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.NetworkResultUtil; import org.whispersystems.signalservice.api.account.PreKeyUpload; +import org.whispersystems.signalservice.api.keys.OneTimePreKeyCounts; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; -import org.whispersystems.signalservice.internal.push.OneTimePreKeyCounts; import java.io.IOException; import java.util.List; import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_STALE_AGE; import static org.asamk.signal.manager.config.ServiceConfig.SIGNED_PREKEY_ROTATE_AGE; +import static org.asamk.signal.manager.util.Utils.handleResponseException; public class PreKeyHelper { @@ -82,7 +84,7 @@ public class PreKeyHelper { ) throws IOException { OneTimePreKeyCounts preKeyCounts; try { - preKeyCounts = dependencies.getAccountManager().getPreKeyCounts(serviceIdType); + preKeyCounts = handleResponseException(dependencies.getKeysApi().getAvailablePreKeyCounts(serviceIdType)); } catch (AuthorizationFailedException e) { logger.debug("Failed to get pre key count, ignoring: " + e.getClass().getSimpleName()); preKeyCounts = new OneTimePreKeyCounts(0, 0); @@ -143,7 +145,7 @@ public class PreKeyHelper { kyberPreKeyRecords); var needsReset = false; try { - dependencies.getAccountManager().setPreKeys(preKeyUpload); + NetworkResultUtil.toPreKeysLegacy(dependencies.getKeysApi().setPreKeys(preKeyUpload)); try { if (preKeyRecords != null) { account.addPreKeys(serviceIdType, preKeyRecords); diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java index 0905bb30..af16ed1d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java @@ -23,6 +23,7 @@ import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.NetworkResultUtil; import org.whispersystems.signalservice.api.crypto.SealedSenderAccess; import org.whispersystems.signalservice.api.profiles.AvatarUploadParams; import org.whispersystems.signalservice.api.profiles.ProfileAndCredential; @@ -196,9 +197,10 @@ public final class ProfileHelper { : avatar == null ? AvatarUploadParams.unchanged(true) : AvatarUploadParams.unchanged(false); final var paymentsAddress = Optional.ofNullable(newProfile.getMobileCoinAddress()) .map(address -> PaymentUtils.signPaymentsAddress(address, - account.getAciIdentityKeyPair().getPrivateKey())); + account.getAciIdentityKeyPair().getPrivateKey())) + .orElse(null); logger.debug("Uploading new profile"); - final var avatarPath = dependencies.getAccountManager() + final var avatarPath = NetworkResultUtil.toSetProfileLegacy(dependencies.getProfileApi() .setVersionedProfile(account.getAci(), account.getProfileKey(), newProfile.getInternalServiceName(), @@ -208,9 +210,9 @@ public final class ProfileHelper { avatarUploadParams, List.of(/* TODO implement support for badges */), account.getConfigurationStore().getPhoneNumberSharingMode() - == PhoneNumberSharingMode.EVERYBODY); + == PhoneNumberSharingMode.EVERYBODY)); if (!avatarUploadParams.keepTheSame) { - builder.withAvatarUrlPath(avatarPath.orElse(null)); + builder.withAvatarUrlPath(avatarPath); } newProfile = builder.build(); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java index fb39789e..cd2719a1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/UnidentifiedAccessHelper.java @@ -18,6 +18,8 @@ import java.io.IOException; import java.util.List; import java.util.concurrent.TimeUnit; +import static org.asamk.signal.manager.util.Utils.handleResponseException; + public class UnidentifiedAccessHelper { private static final Logger logger = LoggerFactory.getLogger(UnidentifiedAccessHelper.class); @@ -109,7 +111,8 @@ public class UnidentifiedAccessHelper { return privacySenderCertificate.getSerialized(); } try { - final var certificate = dependencies.getAccountManager().getSenderCertificateForPhoneNumberPrivacy(); + final var certificate = handleResponseException(dependencies.getCertificateApi() + .getSenderCertificateForPhoneNumberPrivacy()); privacySenderCertificate = new SenderCertificate(certificate); return certificate; } catch (IOException | InvalidCertificateException e) { @@ -125,7 +128,7 @@ public class UnidentifiedAccessHelper { return senderCertificate.getSerialized(); } try { - final var certificate = dependencies.getAccountManager().getSenderCertificate(); + final var certificate = handleResponseException(dependencies.getCertificateApi().getSenderCertificate()); this.senderCertificate = new SenderCertificate(certificate); return certificate; } catch (IOException | InvalidCertificateException e) { diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index c19e6b66..6470d4e9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -14,12 +14,17 @@ import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.account.AccountApi; +import org.whispersystems.signalservice.api.attachment.AttachmentApi; import org.whispersystems.signalservice.api.cds.CdsApi; +import org.whispersystems.signalservice.api.certificate.CertificateApi; import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; +import org.whispersystems.signalservice.api.keys.KeysApi; import org.whispersystems.signalservice.api.link.LinkDeviceApi; +import org.whispersystems.signalservice.api.message.MessageApi; +import org.whispersystems.signalservice.api.profiles.ProfileApi; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.ratelimit.RateLimitChallengeApi; @@ -68,6 +73,10 @@ public class SignalDependencies { private RegistrationApi registrationApi; private LinkDeviceApi linkDeviceApi; private StorageServiceApi storageServiceApi; + private CertificateApi certificateApi; + private AttachmentApi attachmentApi; + private MessageApi messageApi; + private KeysApi keysApi; private GroupsV2Operations groupsV2Operations; private ClientZkOperations clientZkOperations; @@ -80,6 +89,7 @@ public class SignalDependencies { private List secureValueRecovery; private ProfileService profileService; + private ProfileApi profileApi; SignalDependencies( final ServiceEnvironmentConfig serviceEnvironmentConfig, @@ -174,7 +184,8 @@ public class SignalDependencies { public SignalServiceAccountManager getAccountManager() { return getOrCreate(() -> accountManager, - () -> accountManager = new SignalServiceAccountManager(getAccountApi(), + () -> accountManager = new SignalServiceAccountManager(getAuthenticatedSignalWebSocket(), + getAccountApi(), getPushServiceSocket(), getGroupsV2Operations())); } @@ -231,6 +242,27 @@ public class SignalDependencies { return new StorageServiceRepository(getStorageServiceApi()); } + public CertificateApi getCertificateApi() { + return getOrCreate(() -> certificateApi, + () -> certificateApi = new CertificateApi(getAuthenticatedSignalWebSocket())); + } + + public AttachmentApi getAttachmentApi() { + return getOrCreate(() -> attachmentApi, + () -> attachmentApi = new AttachmentApi(getAuthenticatedSignalWebSocket(), getPushServiceSocket())); + } + + public MessageApi getMessageApi() { + return getOrCreate(() -> messageApi, + () -> messageApi = new MessageApi(getAuthenticatedSignalWebSocket(), + getUnauthenticatedSignalWebSocket())); + } + + public KeysApi getKeysApi() { + return getOrCreate(() -> keysApi, + () -> keysApi = new KeysApi(getAuthenticatedSignalWebSocket(), getUnauthenticatedSignalWebSocket())); + } + public GroupsV2Operations getGroupsV2Operations() { return getOrCreate(() -> groupsV2Operations, () -> groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()), @@ -289,8 +321,9 @@ public class SignalDependencies { () -> messageSender = new SignalServiceMessageSender(getPushServiceSocket(), dataStore, sessionLock, - getAuthenticatedSignalWebSocket(), - getUnauthenticatedSignalWebSocket(), + getAttachmentApi(), + getMessageApi(), + getKeysApi(), Optional.empty(), executor, ServiceConfig.MAX_ENVELOPE_SIZE)); @@ -304,10 +337,14 @@ public class SignalDependencies { .toList()); } + public ProfileApi getProfileApi() { + return getOrCreate(() -> profileApi, + () -> profileApi = new ProfileApi(getAuthenticatedSignalWebSocket(), getPushServiceSocket())); + } + public ProfileService getProfileService() { return getOrCreate(() -> profileService, () -> profileService = new ProfileService(getClientZkProfileOperations(), - getMessageReceiver(), getAuthenticatedSignalWebSocket(), getUnauthenticatedSignalWebSocket())); } diff --git a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java index 9d97ed7a..daee1d77 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java @@ -7,6 +7,7 @@ import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.NetworkResult; +import org.whispersystems.signalservice.api.NetworkResultUtil; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.StreamDetails; @@ -154,19 +155,7 @@ public class Utils { } public static T handleResponseException(final NetworkResult response) throws IOException { - final var throwableOptional = response.getCause(); - if (throwableOptional != null) { - if (throwableOptional instanceof IOException ioException) { - throw ioException; - } else { - throw new IOException(throwableOptional); - } - } - try { - return response.successOrThrow(); - } catch (Throwable e) { - throw new AssertionError(e); - } + return NetworkResultUtil.toBasicLegacy(response); } public static ByteString firstNonEmpty(ByteString... strings) { diff --git a/run_tests.sh b/run_tests.sh index 253532ec..02ab0286 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -141,6 +141,7 @@ exec 3> "$FIFO_FILE" echo '{"jsonrpc":"2.0","id":"id","method":"listGroups"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"listDevices"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"listIdentities"}' >&3 + echo '{"jsonrpc":"2.0","id":"id","method":"listStickerPacks"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"sendSyncRequest"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"sendContacts"}' >&3 echo '{"jsonrpc":"2.0","id":"id","method":"version"}' >&3 From b998f322f58fb20c6e545918ee5ec9af80d3aae5 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 6 Apr 2025 20:10:46 +0200 Subject: [PATCH 223/279] Bump version to 0.13.14 --- CHANGELOG.md | 13 ++++++++++++- build.gradle.kts | 2 +- data/org.asamk.SignalCli.metainfo.xml | 3 +++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ff89f8..3474b123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Changelog -## [Unreleased] +## [0.13.14] - 2025-04-06 + +Requires libsignal-client version 0.68.1. + +### Fixed + +- Fix pre key import from old data files + +### Changed + +- Use websocket connection instead of HTTP for more requests +- Improve handling of messages with decryption error ## [0.13.13] - 2025-02-28 diff --git a/build.gradle.kts b/build.gradle.kts index 38c3e265..bfc09725 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.14-SNAPSHOT" + version = "0.13.14" } java { diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index a0af7892..b6f81a4d 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -45,6 +45,9 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.14 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.13 From bd7948e24657e1f324e423eb43b13aeedf6f3637 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 224/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3474b123..16899ff1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.14] - 2025-04-06 Requires libsignal-client version 0.68.1. diff --git a/build.gradle.kts b/build.gradle.kts index bfc09725..17c632af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.14" + version = "0.13.15-SNAPSHOT" } java { From ce813e4529811b7e97c46e13bf716f08a4139785 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 8 Apr 2025 16:24:57 +0200 Subject: [PATCH 225/279] Update client dependencies --- client/Cargo.lock | 435 +++++++++++++++++++++++----------------------- 1 file changed, 218 insertions(+), 217 deletions(-) diff --git a/client/Cargo.lock b/client/Cargo.lock index b85be350..d0cd01f4 100644 --- a/client/Cargo.lock +++ b/client/Cargo.lock @@ -69,15 +69,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "async-trait" -version = "0.1.85" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", @@ -108,7 +108,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -119,21 +119,21 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.10" +version = "1.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" dependencies = [ "shlex", ] @@ -152,9 +152,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.26" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" dependencies = [ "clap_builder", "clap_derive", @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.26" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" dependencies = [ "anstream", "anstyle", @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -209,9 +209,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" dependencies = [ "core-foundation-sys", "libc", @@ -236,15 +236,15 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -343,9 +343,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ "atomic-waker", "bytes", @@ -374,9 +374,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -395,12 +395,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -408,15 +408,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -452,9 +452,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", @@ -462,6 +462,7 @@ dependencies = [ "http", "http-body", "hyper", + "libc", "pin-project-lite", "socket2", "tokio", @@ -510,9 +511,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -534,9 +535,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -555,9 +556,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -610,9 +611,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", @@ -626,22 +627,24 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", + "cfg-if", "combine", "jni-sys", "log", "thiserror 1.0.69", "walkdir", + "windows-sys 0.45.0", ] [[package]] @@ -652,9 +655,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jsonrpsee" -version = "0.24.7" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843" +checksum = "37b26c20e2178756451cfeb0661fb74c47dd5988cb7e3939de7e9241fd604d42" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -665,9 +668,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.7" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2882f6f8acb9fdaec7cefc4fd607119a9bd709831df7d7672a1d3b644628280" +checksum = "456196007ca3a14db478346f58c7238028d55ee15c1df15115596e411ff27925" dependencies = [ "async-trait", "bytes", @@ -689,9 +692,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.24.7" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3638bc4617f96675973253b3a45006933bde93c2fd8a6170b33c777cc389e5b" +checksum = "c872b6c9961a4ccc543e321bb5b89f6b2d2c7fe8b61906918273a3333c95400c" dependencies = [ "async-trait", "base64", @@ -714,9 +717,9 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.24.7" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d" +checksum = "5e65763c942dfc9358146571911b0cd1c361c2d63e2d2305622d40d36376ca80" dependencies = [ "heck", "proc-macro-crate", @@ -727,9 +730,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.24.7" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1" +checksum = "08a8e70baf945b6b5752fc8eb38c918a48f1234daf11355e07106d963f860089" dependencies = [ "http", "serde", @@ -739,27 +742,27 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" [[package]] name = "litemap" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "log" -version = "0.4.25" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -769,9 +772,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" dependencies = [ "adler2", ] @@ -787,34 +790,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "object" version = "0.36.7" @@ -826,15 +801,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "percent-encoding" @@ -844,18 +819,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", @@ -876,42 +851,41 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] @@ -924,15 +898,15 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "0.38.43" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ "bitflags", "errno", @@ -943,9 +917,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.21" +version = "0.23.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" dependencies = [ "log", "once_cell", @@ -958,37 +932,27 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-platform-verifier" -version = "0.3.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +checksum = "4a5467026f437b4cb2a533865eaa73eb840019a0916f4b9ec563c6e617e086c9" dependencies = [ "core-foundation", "core-foundation-sys", @@ -1001,8 +965,8 @@ dependencies = [ "rustls-webpki", "security-framework", "security-framework-sys", - "webpki-roots", - "winapi", + "webpki-root-certs", + "windows-sys 0.59.0", ] [[package]] @@ -1013,9 +977,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "ring", "rustls-pki-types", @@ -1024,9 +988,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -1048,15 +1012,14 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", - "num-bigint", "security-framework-sys", ] @@ -1072,18 +1035,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -1092,9 +1055,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.135" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -1120,7 +1083,7 @@ dependencies = [ "log", "serde", "serde_json", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tokio-util", ] @@ -1136,26 +1099,20 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1176,9 +1133,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.96" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -1198,9 +1155,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" dependencies = [ "rustix", "windows-sys 0.59.0", @@ -1217,11 +1174,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -1237,9 +1194,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -1258,9 +1215,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -1285,9 +1242,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", @@ -1306,9 +1263,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -1325,9 +1282,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "toml_datetime", @@ -1401,9 +1358,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "untrusted" @@ -1466,30 +1423,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "webpki-roots" -version = "0.26.7" +name = "webpki-root-certs" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" dependencies = [ "rustls-pki-types", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.9" @@ -1500,10 +1441,13 @@ dependencies = [ ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] [[package]] name = "windows-sys" @@ -1511,7 +1455,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1520,7 +1464,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -1529,28 +1488,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1563,24 +1540,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1589,9 +1590,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.24" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] @@ -1634,18 +1635,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", From 1e685c7cab19b2cd57d79cf5d54999b5549164fd Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 9 Apr 2025 20:44:10 +0200 Subject: [PATCH 226/279] Extend merge/split logging --- .../manager/storage/recipients/MergeRecipientHelper.java | 7 ++++--- .../signal/manager/storage/recipients/RecipientStore.java | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java index 0a96785b..1454c8a7 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java @@ -83,10 +83,11 @@ public class MergeRecipientHelper { recipientsToBeStripped.add(recipient); } - logger.debug("Got separate recipients for high trust identifiers {}, need to merge ({}) and strip ({})", + logger.debug("Got separate recipients for high trust identifiers {}, need to merge ({}, {}) and strip ({})", address, - recipientsToBeMerged.stream().map(r -> r.id().toString()).collect(Collectors.joining(", ")), - recipientsToBeStripped.stream().map(r -> r.id().toString()).collect(Collectors.joining(", "))); + resultingRecipient.map(RecipientWithAddress::address), + recipientsToBeMerged.stream().map(r -> r.address().toString()).collect(Collectors.joining(", ")), + recipientsToBeStripped.stream().map(r -> r.address().toString()).collect(Collectors.joining(", "))); RecipientAddress finalAddress = resultingRecipient.map(RecipientWithAddress::address).orElse(null); for (final var recipient : recipientsToBeMerged) { 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 8d006416..442cbf4e 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 @@ -994,7 +994,12 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re ) throws SQLException { markUnregistered(connection, recipientId); final var address = resolveRecipientAddress(connection, recipientId); - if (address.aci().isPresent() && address.pni().isPresent()) { + final var needSplit = address.aci().isPresent() && address.pni().isPresent(); + logger.trace("Marking unregistered recipient {} as unregistered (and split={}): {}", + recipientId, + needSplit, + address); + if (needSplit) { final var numberAddress = new RecipientAddress(address.pni().get(), address.number().orElse(null)); updateRecipientAddress(connection, recipientId, address.removeIdentifiersFrom(numberAddress)); addNewRecipient(connection, numberAddress); From 4e455d85d6e311cf229efae7f4d4acc9208d6931 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 26 Apr 2025 09:04:05 +0200 Subject: [PATCH 227/279] Add more logging to register --- .../asamk/signal/manager/internal/RegistrationManagerImpl.java | 3 +++ 1 file changed, 3 insertions(+) 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 e103fec3..0d20cc0b 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 @@ -129,12 +129,15 @@ public class RegistrationManagerImpl implements RegistrationManager { } final var registrationApi = unauthenticatedAccountManager.getRegistrationApi(); + logger.trace("Creating verification session"); String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi, account.getSessionId(account.getNumber()), id -> account.setSessionId(account.getNumber(), id), voiceVerification, captcha); + logger.trace("Requesting verification code"); NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification); + logger.debug("Successfully requested verification code"); account.setRegistered(false); } catch (DeprecatedVersionException e) { logger.debug("Signal-Server returned deprecated version exception", e); From 5e11cf1c508810388de9dce18034723a66a8b453 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 8 May 2025 19:37:41 +0200 Subject: [PATCH 228/279] Update gradle --- gradle/wrapper/gradle-wrapper.jar | Bin 43705 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 4 ++-- gradlew.bat | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9bbc975c742b298b441bfb90dbc124400a3751b9..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 642 zcmdmamFde>rVZJA^}0Q$xegf!xPEW^+5YDM%iT2bEgct9o+jH~+sJas#HZ=szO|** z=Pj=X_vx?W&DSwKck|WWn~hffsvnQ+42*W$b7b0$SCcOoZ`{W{^$^pk;4>8-A*-)$ z?n(Po`1$6Jn_u?t-L+tsPyZ2#X}8T6OS8pAU;kdgd+_Hw4z4TW0p9E!T+=f7-c&O% zFic^X{7^$?^Ho04eona9n#mGMxKhA=~8B%JN`M zMhm5wc-2v)$``sY$!Q`9xiU@DhI73ZxiGEKg>yIPs)NmWwMdF-ngLXpZSqV5ez36n zVkxF2rjrjWR+_xr6e6@_u@s~2uv{9vi*1pj2)BjFD+-%@&pRVP1f{O1glxTOp2-62Ph;v z`N1+vCd)9ea)af*Ol1*JCfnp$%Uu}%OuoN7g2}3C@`L5FlP#(sA=|h@iixuZC?qp^ z=L$=v$ZoI}|87Wh=&h7udff{aieKr*l+zDp?pf)_bbRvUf>kn;HCDMXNlgbbo!QRK I1x7am0No)LiU0rr delta 584 zcmexzm1*ZyrVZJAexH5Moc8h7)w{^+t*dqJ%=yhh23L$9JpFV=_k`zJ-?Q4DI*eSe z+ES)HSrVnWLtJ&)lO%hRkV9zl5qqWRt0e;bb zPPo`)y?HTAyZI&u&X<|2$FDHCf4;!v8}p=?Tm`^F0`u(|1ttf~&t$qP3KUSD>@TJQ zRwJ}Pim6NzEc8KA6)e;S6gs8=7IIL8sQL*MYEuRYO;Uj<%3UbMbV&^&!Zvx+LKmjT z8Zch6rYP7Tw?$Hn(UTJwWiS=$f{lB(C=e*%usDV})0AQIK~sat=ND@+Gg*Pyij!rR z*fa02W|%BsV++>4W{DKDGSIUEHd2$P+8ct!RF+CHDowUuTEZOZ%rJSQv*qOXOSPDN zT|sP-$p*_3ncsWB*qoD7JQcyZ9xan%cJP6Tb4-?AZpr*F6v98hoNaPJm@HV`yya5N z))6pqFXn@}P(3T0nEzM8*c_9KtE9o|_pFd&K35GBXP^9Kg(b6GH-z8S4GDzIl~T+b zdLd#meKKHu$5u))8cu$=GKINkGDPOUD)!0$C(BH(U!}!-e;Q0ok8Sc?V1zRO04>ts AA^-pY diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b1..ca025c83 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index faf93008..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c..5eed7ee8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From a2900085c9fa85c86ccabd00e843593a9f480816 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 8 May 2025 19:37:57 +0200 Subject: [PATCH 229/279] Use java 24 in CI --- .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 4f27b4e1..ad4dbf15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '21', '23' ] + java: [ '21', '24' ] steps: - uses: actions/checkout@v4 From 4fd9e55c3c13f5be80d6abdd21f2c608e65903d0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 8 May 2025 20:04:49 +0200 Subject: [PATCH 230/279] Enable native access required by Java 24 Fixes #1765 --- build.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 17c632af..bd091026 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ java { application { mainClass.set("org.asamk.signal.Main") + applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED") } graalvmNative { @@ -112,7 +113,8 @@ tasks.withType { attributes( "Implementation-Title" to project.name, "Implementation-Version" to project.version, - "Main-Class" to application.mainClass.get() + "Main-Class" to application.mainClass.get(), + "Enable-Native-Access" to "ALL-UNNAMED", ) } } From bcc1eadc7de7320d12246a4958d83e7f167d9195 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 8 May 2025 20:36:27 +0200 Subject: [PATCH 231/279] Remove unused e164 field from account record --- .../signal/manager/syncStorage/AccountRecordProcessor.java | 7 +------ .../signal/manager/syncStorage/StorageSyncModels.java | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java index 656297bf..21c0d97a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java @@ -2,7 +2,6 @@ package org.asamk.signal.manager.syncStorage; import org.asamk.signal.manager.api.Profile; import org.asamk.signal.manager.internal.JobExecutor; -import org.asamk.signal.manager.jobs.CheckWhoAmIJob; import org.asamk.signal.manager.jobs.DownloadProfileAvatarJob; import org.asamk.signal.manager.storage.SignalAccount; import org.asamk.signal.manager.util.KeyUtils; @@ -146,7 +145,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor Date: Thu, 8 May 2025 20:42:13 +0200 Subject: [PATCH 232/279] Add compatibility flag for graalvm native build --- build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle.kts b/build.gradle.kts index bd091026..6feb1b8b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,7 @@ graalvmNative { buildArgs.add("--install-exit-handlers") buildArgs.add("-Dfile.encoding=UTF-8") buildArgs.add("-J-Dfile.encoding=UTF-8") + buildArgs.add("-march=compatibility") resources.autodetect() configurationFileDirectories.from(file("graalvm-config-dir")) if (System.getenv("GRAALVM_HOME") == null) { From e594f3b237d2aa3dfe2383301398d8c9fed86ac0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 8 May 2025 21:18:25 +0200 Subject: [PATCH 233/279] Update libsignal-service --- graalvm-config-dir/reflect-config.json | 1 + gradle/libs.versions.toml | 2 +- .../java/org/asamk/signal/manager/config/LiveConfig.java | 3 +++ .../java/org/asamk/signal/manager/config/StagingConfig.java | 3 +++ .../java/org/asamk/signal/manager/helper/AccountHelper.java | 4 ++-- .../org/asamk/signal/manager/helper/RecipientHelper.java | 1 - .../signal/manager/internal/ProvisioningManagerImpl.java | 3 --- .../asamk/signal/manager/internal/SignalDependencies.java | 5 ++--- src/main/java/org/asamk/signal/BaseConfig.java | 2 +- 9 files changed, 13 insertions(+), 11 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index cdf38732..d9b07c03 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -3021,6 +3021,7 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord", "allDeclaredFields":true, + "fields":[{"name":"aci"}, {"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"e164"}, {"name":"familyName"}, {"name":"givenName"}, {"name":"hidden"}, {"name":"hideStory"}, {"name":"identityKey"}, {"name":"identityState"}, {"name":"markedUnread"}, {"name":"mutedUntilTimestamp"}, {"name":"nickname"}, {"name":"note"}, {"name":"pni"}, {"name":"pniSignatureVerified"}, {"name":"profileKey"}, {"name":"systemFamilyName"}, {"name":"systemGivenName"}, {"name":"systemNickname"}, {"name":"unregisteredAtTimestamp"}, {"name":"username"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 74f97a76..3182d4bf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.18" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_121" +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_122" sqlite = "org.xerial:sqlite-jdbc:3.49.1.0" hikari = "com.zaxxer:HikariCP:6.3.0" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.12.0" diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index 6a707fa5..7085132d 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -5,6 +5,7 @@ import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.whispersystems.signalservice.api.push.TrustStore; +import org.whispersystems.signalservice.internal.configuration.HttpProxy; import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl; import org.whispersystems.signalservice.internal.configuration.SignalProxy; @@ -41,6 +42,7 @@ class LiveConfig { private static final Optional dns = Optional.empty(); private static final Optional proxy = Optional.empty(); + private static final Optional systemProxy = Optional.empty(); private static final byte[] zkGroupServerPublicParams = Base64.getDecoder() .decode("AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0LUlT9vALgh/f2DPVOOmR0RW6bgRvc7DSF20V/omg+YBw=="); @@ -68,6 +70,7 @@ class LiveConfig { interceptors, dns, proxy, + systemProxy, zkGroupServerPublicParams, genericServerPublicParams, backupServerPublicParams, diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index 7eb04b3c..6b6ae9b5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -5,6 +5,7 @@ import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.whispersystems.signalservice.api.push.TrustStore; +import org.whispersystems.signalservice.internal.configuration.HttpProxy; import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl; import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl; import org.whispersystems.signalservice.internal.configuration.SignalProxy; @@ -41,6 +42,7 @@ class StagingConfig { private static final Optional dns = Optional.empty(); private static final Optional proxy = Optional.empty(); + private static final Optional systemProxy = Optional.empty(); private static final byte[] zkGroupServerPublicParams = Base64.getDecoder() .decode("ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ=="); @@ -68,6 +70,7 @@ class StagingConfig { interceptors, dns, proxy, + systemProxy, zkGroupServerPublicParams, genericServerPublicParams, backupServerPublicParams, 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 7d40f99c..f94a3dca 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 @@ -535,9 +535,9 @@ public class AccountHelper { account.getAciIdentityKeyPair(), account.getPniIdentityKeyPair(), account.getProfileKey(), + account.getOrCreateAccountEntropyPool(), account.getOrCreatePinMasterKey(), account.getOrCreateMediaRootBackupKey(), - account.getOrCreateAccountEntropyPool(), verificationCode.getVerificationCode(), null)); account.setMultiDevice(true); @@ -595,7 +595,7 @@ public class AccountHelper { } account.setRegistrationLockPin(null); - dependencies.getAccountManager().deleteAccount(); + handleResponseException(dependencies.getAccountApi().deleteAccount()); account.setRegistered(false); unregisteredListener.call(); 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 5a3524ec..ecb85254 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 @@ -241,7 +241,6 @@ public class RecipientHelper { token, null, dependencies.getLibSignalNetwork(), - false, newToken -> { if (isPartialRefresh) { account.getCdsiStore().updateAfterPartialCdsQuery(newNumbers); 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 e5a381e1..eeca842a 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 @@ -29,7 +29,6 @@ import org.asamk.signal.manager.util.KeyUtils; import org.signal.libsignal.protocol.IdentityKeyPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; @@ -76,7 +75,6 @@ public class ProvisioningManagerImpl implements ProvisioningManager { tempIdentityKey = KeyUtils.generateIdentityKeyPair(); password = KeyUtils.createPassword(); - final var clientZkOperations = ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()); final var credentialsProvider = new DynamicCredentialsProvider(null, null, null, @@ -85,7 +83,6 @@ public class ProvisioningManagerImpl implements ProvisioningManager { final var pushServiceSocket = new PushServiceSocket(serviceEnvironmentConfig.signalServiceConfiguration(), credentialsProvider, userAgent, - clientZkOperations.getProfileOperations(), ServiceConfig.AUTOMATIC_NETWORK_RETRY); final var provisioningSocket = new ProvisioningSocket(serviceEnvironmentConfig.signalServiceConfiguration(), userAgent); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index 6470d4e9..abdebd9a 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -147,7 +147,6 @@ public class SignalDependencies { () -> pushServiceSocket = new PushServiceSocket(serviceEnvironmentConfig.signalServiceConfiguration(), credentialsProvider, userAgent, - getClientZkProfileOperations(), ServiceConfig.AUTOMATIC_NETWORK_RETRY)); } @@ -290,7 +289,7 @@ public class SignalDependencies { Optional.of(credentialsProvider), userAgent, healthMonitor, - allowStories), timer, TimeUnit.SECONDS.toMillis(10)); + allowStories), () -> true, timer, TimeUnit.SECONDS.toMillis(10)); healthMonitor.monitor(authenticatedSignalWebSocket); }); } @@ -306,7 +305,7 @@ public class SignalDependencies { Optional.empty(), userAgent, healthMonitor, - allowStories), timer, TimeUnit.SECONDS.toMillis(10)); + allowStories), () -> true, timer, TimeUnit.SECONDS.toMillis(10)); healthMonitor.monitor(unauthenticatedSignalWebSocket); }); } diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 9218fe5f..101b0e3a 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.35.0"); + .orElse("Signal-Android/7.41.3"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From 7aafb05995ee09305fa36bcb79a03dc1d057a0eb Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 8 May 2025 21:19:47 +0200 Subject: [PATCH 234/279] Update dependencies --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3182d4bf..9986e329 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ slf4j = "2.0.17" [libraries] bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.80" -jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.18.3" +jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.19.0" argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0" dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0" slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } From d0d0021f57a4f94749597a5324ee6eefbc54d5a3 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 8 May 2025 21:49:51 +0200 Subject: [PATCH 235/279] Bump version to 0.13.15 --- CHANGELOG.md | 13 ++++++++++++- build.gradle.kts | 2 +- data/org.asamk.SignalCli.metainfo.xml | 3 +++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16899ff1..86fb8178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # Changelog -## [Unreleased] +## [0.13.15] - 2025-05-08 + +Requires libsignal-client version 0.70.0. + +### Fixed + +- Fix native access warning with Java 24 +- Fix storage sync loop due to old removed e164 field + +### Changed + +- Increased compatibility of native build with older/virtual CPUs ## [0.13.14] - 2025-04-06 diff --git a/build.gradle.kts b/build.gradle.kts index 6feb1b8b..9ec777c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.15-SNAPSHOT" + version = "0.13.15" } java { diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index b6f81a4d..5e80335e 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -45,6 +45,9 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.15 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.14 From 56e11d0857c6131667695b95c9c2805c8a938150 Mon Sep 17 00:00:00 2001 From: AsamK Date: Thu, 8 May 2025 22:55:26 +0200 Subject: [PATCH 236/279] Update codeql v3 --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ff13469a..f778268a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,7 +35,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java @@ -43,7 +43,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -57,4 +57,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From bb124a922defe76565386d767ff318db28d897bd Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 237/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86fb8178..2d306b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.15] - 2025-05-08 Requires libsignal-client version 0.70.0. diff --git a/build.gradle.kts b/build.gradle.kts index 9ec777c1..267673d6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.15" + version = "0.13.16-SNAPSHOT" } java { From 74909408c4d56fd8ee05c4720d1229a040b46c3b Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 10 May 2025 10:18:03 +0200 Subject: [PATCH 238/279] Add missing reflect config Fixes #1768 --- graalvm-config-dir/reflect-config.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index d9b07c03..e27f68d9 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -1409,6 +1409,12 @@ "name":"org.asamk.signal.manager.storage.profiles.LegacyProfileStore$ProfileStoreDeserializer", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"org.asamk.signal.manager.storage.profiles.LegacySignalProfile", + "allDeclaredFields":true, + "allDeclaredMethods":true, + "allDeclaredConstructors":true +}, { "name":"org.asamk.signal.manager.storage.profiles.LegacySignalProfileEntry", "allDeclaredFields":true, From a9bb8d9aaef94bf1e251b822532da8a91f870dd2 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 1 Jun 2025 16:11:21 +0200 Subject: [PATCH 239/279] Update gradle --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c83..002b867c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From e89803464b0e1fd284045bd4100c05ec856c7011 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 1 Jun 2025 21:51:03 +0200 Subject: [PATCH 240/279] Update libsignal-service --- graalvm-config-dir/jni-config.json | 13 ++++++++++- graalvm-config-dir/reflect-config.json | 3 --- gradle/libs.versions.toml | 2 +- .../signal/manager/config/LiveConfig.java | 5 ++-- .../signal/manager/config/StagingConfig.java | 5 ++-- .../signal/manager/helper/AccountHelper.java | 2 +- .../manager/internal/SignalDependencies.java | 3 ++- .../signal/manager/storage/SignalAccount.java | 2 +- .../storage/identities/IdentityKeyStore.java | 23 +++++++++++-------- .../identities/SignalIdentityKeyStore.java | 2 +- .../storage/protocol/SignalProtocolStore.java | 2 +- 11 files changed, 39 insertions(+), 23 deletions(-) diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index 0284d58f..23a2160c 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -27,6 +27,10 @@ { "name":"java.lang.ClassNotFoundException" }, +{ + "name":"java.lang.Enum", + "methods":[{"name":"ordinal","parameterTypes":[] }] +}, { "name":"java.lang.IllegalArgumentException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] @@ -48,6 +52,10 @@ { "name":"java.lang.String" }, +{ + "name":"java.lang.Thread", + "methods":[{"name":"currentThread","parameterTypes":[] }, {"name":"getStackTrace","parameterTypes":[] }] +}, { "name":"java.lang.Throwable", "methods":[{"name":"getMessage","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] @@ -88,7 +96,7 @@ }, { "name":"org.signal.libsignal.internal.CompletableFuture", - "methods":[{"name":"","parameterTypes":[] }, {"name":"complete","parameterTypes":["java.lang.Object"] }, {"name":"completeExceptionally","parameterTypes":["java.lang.Throwable"] }] + "methods":[{"name":"","parameterTypes":[] }, {"name":"complete","parameterTypes":["java.lang.Object"] }, {"name":"completeExceptionally","parameterTypes":["java.lang.Throwable"] }, {"name":"setCancellationId","parameterTypes":["long"] }] }, { "name":"org.signal.libsignal.internal.NativeHandleGuard$SimpleOwner", @@ -195,6 +203,9 @@ "name":"org.signal.libsignal.protocol.state.IdentityKeyStore$Direction", "fields":[{"name":"RECEIVING"}, {"name":"SENDING"}] }, +{ + "name":"org.signal.libsignal.protocol.state.IdentityKeyStore$IdentityChange" +}, { "name":"org.signal.libsignal.protocol.state.KyberPreKeyRecord", "fields":[{"name":"unsafeHandle"}] diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index e27f68d9..cbec1c86 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -2994,7 +2994,6 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord", "allDeclaredFields":true, - "fields":[{"name":"avatarColor"}, {"name":"avatarUrlPath"}, {"name":"backupSubscriberData"}, {"name":"backupTier"}, {"name":"displayBadgesOnProfile"}, {"name":"e164"}, {"name":"familyName"}, {"name":"givenName"}, {"name":"hasBackup"}, {"name":"hasCompletedUsernameOnboarding"}, {"name":"hasSeenGroupStoryEducationSheet"}, {"name":"hasSetMyStoriesPrivacy"}, {"name":"hasViewedOnboardingStory"}, {"name":"keepMutedChatsArchived"}, {"name":"linkPreviews"}, {"name":"noteToSelfArchived"}, {"name":"noteToSelfMarkedUnread"}, {"name":"payments"}, {"name":"phoneNumberSharingMode"}, {"name":"pinnedConversations"}, {"name":"preferContactAvatars"}, {"name":"preferredReactionEmoji"}, {"name":"primarySendsSms"}, {"name":"profileKey"}, {"name":"readReceipts"}, {"name":"sealedSenderIndicators"}, {"name":"storiesDisabled"}, {"name":"storyViewReceiptsEnabled"}, {"name":"subscriberCurrencyCode"}, {"name":"subscriberId"}, {"name":"subscriptionManuallyCancelled"}, {"name":"typingIndicators"}, {"name":"universalExpireTimer"}, {"name":"unlistedPhoneNumber"}, {"name":"username"}, {"name":"usernameLink"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { @@ -3027,7 +3026,6 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord", "allDeclaredFields":true, - "fields":[{"name":"aci"}, {"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"e164"}, {"name":"familyName"}, {"name":"givenName"}, {"name":"hidden"}, {"name":"hideStory"}, {"name":"identityKey"}, {"name":"identityState"}, {"name":"markedUnread"}, {"name":"mutedUntilTimestamp"}, {"name":"nickname"}, {"name":"note"}, {"name":"pni"}, {"name":"pniSignatureVerified"}, {"name":"profileKey"}, {"name":"systemFamilyName"}, {"name":"systemGivenName"}, {"name":"systemNickname"}, {"name":"unregisteredAtTimestamp"}, {"name":"username"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { @@ -3058,7 +3056,6 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record", "allDeclaredFields":true, - "fields":[{"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"dontNotifyForMentionsIfMuted"}, {"name":"hideStory"}, {"name":"markedUnread"}, {"name":"masterKey"}, {"name":"mutedUntilTimestamp"}, {"name":"storySendMode"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9986e329..a90ecb53 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.18" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_122" +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_123" sqlite = "org.xerial:sqlite-jdbc:3.49.1.0" hikari = "com.zaxxer:HikariCP:6.3.0" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.12.0" diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index 7085132d..111695f6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -29,7 +29,8 @@ class LiveConfig { private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder() .decode("BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF"); private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"; - private static final String SVR2_MRENCLAVE = "9314436a9a144992bb3680770ea5fd7934a7ffd29257844a33763a238903d570"; + private static final String SVR2_MRENCLAVE_LEGACY = "9314436a9a144992bb3680770ea5fd7934a7ffd29257844a33763a238903d570"; + private static final String SVR2_MRENCLAVE = "093be9ea32405e85ae28dbb48eb668aebeb7dbe29517b9b86ad4bec4dfe0e6a6"; private static final String URL = "https://chat.signal.org"; private static final String CDN_URL = "https://cdn.signal.org"; @@ -91,7 +92,7 @@ class LiveConfig { createDefaultServiceConfiguration(interceptors), getUnidentifiedSenderTrustRoot(), CDSI_MRENCLAVE, - List.of(SVR2_MRENCLAVE)); + List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY)); } private LiveConfig() { diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index 6b6ae9b5..e72c66b5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -29,7 +29,8 @@ class StagingConfig { private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder() .decode("BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"); private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"; - private static final String SVR2_MRENCLAVE = "38e01eff4fe357dc0b0e8ef7a44b4abc5489fbccba3a78780f3872c277f62bf3"; + private static final String SVR2_MRENCLAVE_LEGACY = "38e01eff4fe357dc0b0e8ef7a44b4abc5489fbccba3a78780f3872c277f62bf3"; + private static final String SVR2_MRENCLAVE = "2e8cefe6e3f389d8426adb24e9b7fb7adf10902c96f06f7bbcee36277711ed91"; private static final String URL = "https://chat.staging.signal.org"; private static final String CDN_URL = "https://cdn-staging.signal.org"; @@ -91,7 +92,7 @@ class StagingConfig { createDefaultServiceConfiguration(interceptors), getUnidentifiedSenderTrustRoot(), CDSI_MRENCLAVE, - List.of(SVR2_MRENCLAVE)); + List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY)); } private StagingConfig() { 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 f94a3dca..1d5849c3 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 @@ -105,7 +105,7 @@ public class AccountHelper { if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) { throw new IOException("Missing PNI identity key, relinking required"); } - if (account.getPreviousStorageVersion() < 4 + if (account.getPreviousStorageVersion() < 10 && account.isPrimaryDevice() && account.getRegistrationLockPin() != null) { migrateRegistrationPin(); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index abdebd9a..9a8e1fd5 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -325,7 +325,8 @@ public class SignalDependencies { getKeysApi(), Optional.empty(), executor, - ServiceConfig.MAX_ENVELOPE_SIZE)); + ServiceConfig.MAX_ENVELOPE_SIZE, + () -> true)); } public List getSecureValueRecovery() { 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 8d2fbd0f..83040906 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 @@ -116,7 +116,7 @@ public class SignalAccount implements Closeable { private static final Logger logger = LoggerFactory.getLogger(SignalAccount.class); private static final int MINIMUM_STORAGE_VERSION = 1; - private static final int CURRENT_STORAGE_VERSION = 9; + private static final int CURRENT_STORAGE_VERSION = 10; private final Object LOCK = new Object(); diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java index 5e32ba12..a2d03a21 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/identities/IdentityKeyStore.java @@ -8,6 +8,7 @@ import org.asamk.signal.manager.storage.recipients.RecipientStore; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.state.IdentityKeyStore.Direction; +import org.signal.libsignal.protocol.state.IdentityKeyStore.IdentityChange; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.signalservice.api.push.ServiceId; @@ -62,11 +63,11 @@ public class IdentityKeyStore { return identityChanges; } - public boolean saveIdentity(final ServiceId serviceId, final IdentityKey identityKey) { + public IdentityChange saveIdentity(final ServiceId serviceId, final IdentityKey identityKey) { return saveIdentity(serviceId.toString(), identityKey); } - public boolean saveIdentity( + public IdentityChange saveIdentity( final Connection connection, final ServiceId serviceId, final IdentityKey identityKey @@ -74,9 +75,9 @@ public class IdentityKeyStore { return saveIdentity(connection, serviceId.toString(), identityKey); } - boolean saveIdentity(final String address, final IdentityKey identityKey) { + IdentityChange saveIdentity(final String address, final IdentityKey identityKey) { if (isRetryingDecryption) { - return false; + return IdentityChange.NEW_OR_UNCHANGED; } try (final var connection = database.getConnection()) { return saveIdentity(connection, address, identityKey); @@ -85,20 +86,24 @@ public class IdentityKeyStore { } } - private boolean saveIdentity( + private IdentityChange saveIdentity( final Connection connection, final String address, final IdentityKey identityKey ) throws SQLException { final var identityInfo = loadIdentity(connection, address); - if (identityInfo != null && identityInfo.getIdentityKey().equals(identityKey)) { + if (identityInfo == null) { + saveNewIdentity(connection, address, identityKey, true); + return IdentityChange.NEW_OR_UNCHANGED; + } + if (identityInfo.getIdentityKey().equals(identityKey)) { // Identity already exists, not updating the trust level logger.trace("Not storing new identity for recipient {}, identity already stored", address); - return false; + return IdentityChange.NEW_OR_UNCHANGED; } - saveNewIdentity(connection, address, identityKey, identityInfo == null); - return true; + saveNewIdentity(connection, address, identityKey, false); + return IdentityChange.REPLACED_EXISTING; } public void setRetryingDecryption(final boolean retryingDecryption) { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/identities/SignalIdentityKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/identities/SignalIdentityKeyStore.java index 5a1a676e..1ac7c9a6 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/identities/SignalIdentityKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/identities/SignalIdentityKeyStore.java @@ -33,7 +33,7 @@ public class SignalIdentityKeyStore implements org.signal.libsignal.protocol.sta } @Override - public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { + public IdentityChange saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { return identityKeyStore.saveIdentity(address.getName(), identityKey); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java index b631f632..e1cf9a88 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/protocol/SignalProtocolStore.java @@ -65,7 +65,7 @@ public class SignalProtocolStore implements SignalServiceAccountDataStore { } @Override - public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { + public IdentityChange saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { return identityKeyStore.saveIdentity(address, identityKey); } From 6b46314eabaa769c16415baf62c8e2f4697286ae Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 3 Jun 2025 19:24:21 +0200 Subject: [PATCH 241/279] Update dependencies --- graalvm-config-dir/reflect-config.json | 1 + gradle/libs.versions.toml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index cbec1c86..c1c5a63d 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -3056,6 +3056,7 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record", "allDeclaredFields":true, + "fields":[{"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"dontNotifyForMentionsIfMuted"}, {"name":"hideStory"}, {"name":"markedUnread"}, {"name":"masterKey"}, {"name":"mutedUntilTimestamp"}, {"name":"storySendMode"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a90ecb53..d712041f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,5 +13,5 @@ logback = "ch.qos.logback:logback-classic:1.5.18" signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_123" sqlite = "org.xerial:sqlite-jdbc:3.49.1.0" hikari = "com.zaxxer:HikariCP:6.3.0" -junit-jupiter = "org.junit.jupiter:junit-jupiter:5.12.0" -junit-launcher = "org.junit.platform:junit-platform-launcher:1.12.0" +junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.0" +junit-launcher = "org.junit.platform:junit-platform-launcher:1.13.0" From bf87fcc652bc37a9843b4dc79f985005452a1c41 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 3 Jun 2025 22:22:51 +0200 Subject: [PATCH 242/279] Ensure messages are created with a unique timestamp Fixes #1783 --- .../signal/manager/internal/ManagerImpl.java | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) 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 c5a7552f..0a67e0a0 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 @@ -131,6 +131,7 @@ 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.atomic.AtomicLong; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -160,6 +161,7 @@ public class ManagerImpl implements Manager { private final List closedListeners = new ArrayList<>(); private final List addressChangedListeners = new ArrayList<>(); private final CompositeDisposable disposable = new CompositeDisposable(); + private final AtomicLong lastMessageTimestamp = new AtomicLong(); public ManagerImpl( SignalAccount account, @@ -598,6 +600,24 @@ public class ManagerImpl implements Manager { return context.getGroupHelper().joinGroup(inviteLinkUrl); } + private long getNextMessageTimestamp() { + while (true) { + final var last = lastMessageTimestamp.get(); + final var timestamp = System.currentTimeMillis(); + if (last == timestamp) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + continue; + } + if (lastMessageTimestamp.compareAndSet(last, timestamp)) { + return timestamp; + } + } + } + private SendMessageResults sendMessage( SignalServiceDataMessage.Builder messageBuilder, Set recipients, @@ -613,7 +633,7 @@ public class ManagerImpl implements Manager { Optional editTargetTimestamp ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var results = new HashMap>(); - long timestamp = System.currentTimeMillis(); + long timestamp = getNextMessageTimestamp(); messageBuilder.withTimestamp(timestamp); for (final var recipient : recipients) { if (recipient instanceof RecipientIdentifier.NoteToSelf || ( @@ -653,7 +673,7 @@ public class ManagerImpl implements Manager { Set recipients ) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException { var results = new HashMap>(); - final var timestamp = System.currentTimeMillis(); + final var timestamp = getNextMessageTimestamp(); for (var recipient : recipients) { if (recipient instanceof RecipientIdentifier.Single single) { final var message = new SignalServiceTypingMessage(action, timestamp, Optional.empty()); @@ -685,7 +705,7 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendReadReceipt(RecipientIdentifier.Single sender, List messageIds) { - final var timestamp = System.currentTimeMillis(); + final var timestamp = getNextMessageTimestamp(); var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.READ, messageIds, timestamp); @@ -695,7 +715,7 @@ public class ManagerImpl implements Manager { @Override public SendMessageResults sendViewedReceipt(RecipientIdentifier.Single sender, List messageIds) { - final var timestamp = System.currentTimeMillis(); + final var timestamp = getNextMessageTimestamp(); var receiptMessage = new SignalServiceReceiptMessage(SignalServiceReceiptMessage.Type.VIEWED, messageIds, timestamp); From 7e9727aa382d746b9eb12c772c7251947793b68a Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 7 Jun 2025 16:14:20 +0200 Subject: [PATCH 243/279] Update tests --- run_tests.sh | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/run_tests.sh b/run_tests.sh index 02ab0286..339d9d7b 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -176,6 +176,17 @@ run_main -a "$NUMBER_2" receive run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi run_main -a "$NUMBER_1" receive run_main -a "$NUMBER_2" receive +run_main -a "$NUMBER_1" updateAccount --discoverable-by-number=true +run_main -a "$NUMBER_2" removeContact --forget "$NUMBER_1" +run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi +run_main -a "$NUMBER_2" send "$NUMBER_1" -m hii +run_main -a "$NUMBER_1" updateAccount --discoverable-by-number=false +run_main -a "$NUMBER_1" receive +run_main -a "$NUMBER_2" receive +run_main -a "$NUMBER_2" send "$NUMBER_1" -m hi +run_main -a "$NUMBER_2" send "$NUMBER_1" -m hii +run_main -a "$NUMBER_1" receive +run_main -a "$NUMBER_2" receive ## Groups GROUP_ID=$(run_main -a "$NUMBER_1" --output=json updateGroup -n GRUPPE -a LICENSE -m "$NUMBER_1" | jq -r '.groupId') run_main -a "$NUMBER_1" send "$NUMBER_2" -m first @@ -238,7 +249,9 @@ for OUTPUT in "plain-text" "json"; do run_linked -a "$NUMBER_1" --output="$OUTPUT" receive done -run_main -a "$NUMBER_1" removeDevice -d 2 +run_main -a "$NUMBER_1" --output="$OUTPUT" receive +run_main -a "$NUMBER_1" removeDevice -d 2 || true +run_main -a "$NUMBER_1" removeDevice -d 2 || true ## Unregister if [ "$TEST_REGISTER" -eq 1 ]; then From 2f8328847c32e1908e6dcc2d188a28509627d302 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 7 Jun 2025 16:13:09 +0200 Subject: [PATCH 244/279] Update dependencies --- graalvm-config-dir/reflect-config.json | 11 +++++++++++ gradle/libs.versions.toml | 8 ++++---- .../signal/manager/internal/SignalDependencies.java | 5 ++++- src/main/java/org/asamk/signal/BaseConfig.java | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index c1c5a63d..d1c2075b 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -1626,6 +1626,10 @@ "name":"org.bouncycastle.jcajce.provider.asymmetric.NTRU$Mappings", "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"org.bouncycastle.jcajce.provider.asymmetric.NoSig$Mappings", + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings", "methods":[{"name":"","parameterTypes":[] }] @@ -2996,6 +3000,9 @@ "allDeclaredFields":true, "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$BackupTierHistory" +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$Builder" }, @@ -3005,6 +3012,9 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$IAPSubscriberData" }, +{ + "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$NotificationProfileManualOverride" +}, { "name":"org.whispersystems.signalservice.internal.storage.protos.AccountRecord$PhoneNumberSharingMode" }, @@ -3026,6 +3036,7 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord", "allDeclaredFields":true, + "fields":[{"name":"aci"}, {"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"e164"}, {"name":"familyName"}, {"name":"givenName"}, {"name":"hidden"}, {"name":"hideStory"}, {"name":"identityKey"}, {"name":"identityState"}, {"name":"markedUnread"}, {"name":"mutedUntilTimestamp"}, {"name":"nickname"}, {"name":"note"}, {"name":"pni"}, {"name":"pniSignatureVerified"}, {"name":"profileKey"}, {"name":"systemFamilyName"}, {"name":"systemGivenName"}, {"name":"systemNickname"}, {"name":"unregisteredAtTimestamp"}, {"name":"username"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d712041f..a6b9fda1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ slf4j = "2.0.17" [libraries] -bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.80" +bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.81" jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.19.0" argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0" dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0" @@ -10,8 +10,8 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.18" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_123" +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_124" sqlite = "org.xerial:sqlite-jdbc:3.49.1.0" hikari = "com.zaxxer:HikariCP:6.3.0" -junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.0" -junit-launcher = "org.junit.platform:junit-platform-launcher:1.13.0" +junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.1" +junit-launcher = "org.junit.platform:junit-platform-launcher:1.13.1" diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index 9a8e1fd5..f954588f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -339,7 +339,10 @@ public class SignalDependencies { public ProfileApi getProfileApi() { return getOrCreate(() -> profileApi, - () -> profileApi = new ProfileApi(getAuthenticatedSignalWebSocket(), getPushServiceSocket())); + () -> profileApi = new ProfileApi(getAuthenticatedSignalWebSocket(), + getUnauthenticatedSignalWebSocket(), + getPushServiceSocket(), + getClientZkProfileOperations())); } public ProfileService getProfileService() { diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 101b0e3a..8a38c36a 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.41.3"); + .orElse("Signal-Android/7.44.1"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From 17cd99be5967419c5c518ba444c8c58fdaa65b0f Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 7 Jun 2025 16:58:00 +0200 Subject: [PATCH 245/279] Bump version to 0.13.16 --- CHANGELOG.md | 8 +++++++- build.gradle.kts | 2 +- data/org.asamk.SignalCli.metainfo.xml | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d306b4b..f29e3d93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog -## [Unreleased] +## [0.13.16] - 2025-06-07 + +Requires libsignal-client version 0.73.2. + +### Changed + +- Ensure every sent message gets a unique timestamp ## [0.13.15] - 2025-05-08 diff --git a/build.gradle.kts b/build.gradle.kts index 267673d6..3be525f1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.16-SNAPSHOT" + version = "0.13.16" } java { diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index 5e80335e..b7483db9 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -45,6 +45,9 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.16 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.15 From 02573449402003871f44f153b06bec7a9e47d521 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 246/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f29e3d93..3ea3c01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.16] - 2025-06-07 Requires libsignal-client version 0.73.2. diff --git a/build.gradle.kts b/build.gradle.kts index 3be525f1..8fadb883 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.16" + version = "0.13.17-SNAPSHOT" } java { From 6b60a6d5a5aacb579cbd64074f25d1014bc93e00 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 8 Jun 2025 14:48:25 +0200 Subject: [PATCH 247/279] Fix NPR when loading an inactive group Fixes #1786 --- .../main/java/org/asamk/signal/manager/helper/GroupHelper.java | 3 +++ .../java/org/asamk/signal/manager/helper/GroupV2Helper.java | 3 +++ 2 files changed, 6 insertions(+) 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 0d838204..682bd996 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 @@ -551,6 +551,9 @@ public class GroupHelper { while (true) { final var page = context.getGroupV2Helper() .getDecryptedGroupHistoryPage(groupSecretParams, fromRevision, sendEndorsementsExpirationMs); + if (page == null) { + break; + } page.getChangeLogs() .stream() .map(DecryptedGroupChangeLog::getChange) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java index 900d7ea9..8eb66843 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java @@ -44,6 +44,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.util.UuidUtil; +import org.whispersystems.signalservice.internal.push.exceptions.NotInGroupException; import java.io.IOException; import java.util.ArrayList; @@ -119,6 +120,8 @@ class GroupV2Helper { groupsV2AuthorizationString, false, sendEndorsementsExpirationMs); + } catch (NotInGroupException e) { + throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null); } catch (NonSuccessfulResponseCodeException e) { if (e.code == 403) { throw new NotAGroupMemberException(GroupUtils.getGroupIdV2(groupSecretParams), null); From a0d5744c4945791eb57436d0f1288b09bd41132a Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 8 Jun 2025 16:22:03 +0200 Subject: [PATCH 248/279] Improve behavior when pin data doesn't exist on the server --- lib/src/main/java/org/asamk/signal/manager/Manager.java | 3 ++- .../java/org/asamk/signal/manager/RegistrationManager.java | 3 ++- .../asamk/signal/manager/api/PinLockMissingException.java | 3 +++ .../java/org/asamk/signal/manager/helper/AccountHelper.java | 5 +++-- .../java/org/asamk/signal/manager/helper/PinHelper.java | 6 +++++- .../java/org/asamk/signal/manager/internal/ManagerImpl.java | 3 ++- .../signal/manager/internal/RegistrationManagerImpl.java | 3 ++- .../asamk/signal/manager/util/NumberVerificationUtils.java | 5 +++-- .../asamk/signal/commands/FinishChangeNumberCommand.java | 3 +++ src/main/java/org/asamk/signal/commands/VerifyCommand.java | 3 +++ .../java/org/asamk/signal/dbus/DbusSignalControlImpl.java | 3 +++ 11 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 lib/src/main/java/org/asamk/signal/manager/api/PinLockMissingException.java diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index affbaa9d..238084e7 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -30,6 +30,7 @@ import org.asamk.signal.manager.api.NotAGroupMemberException; import org.asamk.signal.manager.api.NotPrimaryDeviceException; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.PendingAdminApprovalException; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.ReceiveConfig; @@ -140,7 +141,7 @@ public interface Manager extends Closeable { String newNumber, String verificationCode, String pin - ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException; + ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException, PinLockMissingException; void unregister() throws IOException; diff --git a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java index 7d358df6..5f4f4463 100644 --- a/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java +++ b/lib/src/main/java/org/asamk/signal/manager/RegistrationManager.java @@ -3,6 +3,7 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; @@ -21,7 +22,7 @@ public interface RegistrationManager extends Closeable { void verifyAccount( String verificationCode, String pin - ) throws IOException, PinLockedException, IncorrectPinException; + ) throws IOException, PinLockedException, IncorrectPinException, PinLockMissingException; void deleteLocalAccountData() throws IOException; diff --git a/lib/src/main/java/org/asamk/signal/manager/api/PinLockMissingException.java b/lib/src/main/java/org/asamk/signal/manager/api/PinLockMissingException.java new file mode 100644 index 00000000..7887d45f --- /dev/null +++ b/lib/src/main/java/org/asamk/signal/manager/api/PinLockMissingException.java @@ -0,0 +1,3 @@ +package org.asamk.signal.manager.api; + +public class PinLockMissingException extends Exception {} 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 1d5849c3..de3e2402 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 @@ -4,6 +4,7 @@ import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.DeviceLinkUrl; import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; @@ -185,7 +186,7 @@ public class AccountHelper { String newNumber, String verificationCode, String pin - ) throws IncorrectPinException, PinLockedException, IOException { + ) throws IncorrectPinException, PinLockedException, IOException, PinLockMissingException { for (var attempts = 0; attempts < 5; attempts++) { try { finishChangeNumberInternal(newNumber, verificationCode, pin); @@ -205,7 +206,7 @@ public class AccountHelper { String newNumber, String verificationCode, String pin - ) throws IncorrectPinException, PinLockedException, IOException { + ) throws IncorrectPinException, PinLockedException, IOException, PinLockMissingException { final var pniIdentity = KeyUtils.generateIdentityKeyPair(); final var encryptedDeviceMessages = new ArrayList(); final var devicePniSignedPreKeys = new HashMap(); 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 a95635cf..40878647 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 @@ -88,7 +88,11 @@ public class PinHelper { IOException exception = null; for (final var secureValueRecovery : secureValueRecoveries) { try { - return getRegistrationLockData(secureValueRecovery, svr2Credentials, pin); + final var lockData = getRegistrationLockData(secureValueRecovery, svr2Credentials, pin); + if (lockData == null) { + continue; + } + return lockData; } catch (IOException e) { exception = 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 0a67e0a0..ffb74de2 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 @@ -48,6 +48,7 @@ import org.asamk.signal.manager.api.NotPrimaryDeviceException; import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.PendingAdminApprovalException; import org.asamk.signal.manager.api.PhoneNumberSharingMode; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.Profile; import org.asamk.signal.manager.api.RateLimitException; @@ -428,7 +429,7 @@ public class ManagerImpl implements Manager { String newNumber, String verificationCode, String pin - ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException { + ) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException, PinLockMissingException { if (!account.isPrimaryDevice()) { throw new NotPrimaryDeviceException(); } 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 0d20cc0b..72c4b63f 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 @@ -21,6 +21,7 @@ import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.UpdateProfile; @@ -149,7 +150,7 @@ public class RegistrationManagerImpl implements RegistrationManager { public void verifyAccount( String verificationCode, String pin - ) throws IOException, PinLockedException, IncorrectPinException { + ) throws IOException, PinLockedException, IncorrectPinException, PinLockMissingException { if (account.isRegistered()) { throw new IOException("Account is already registered"); } diff --git a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java index 921d7fc1..659a8c67 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java @@ -4,6 +4,7 @@ import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; import org.asamk.signal.manager.api.Pair; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.VerificationMethodNotAvailableException; @@ -114,7 +115,7 @@ public class NumberVerificationUtils { String pin, PinHelper pinHelper, Verifier verifier - ) throws IOException, PinLockedException, IncorrectPinException { + ) throws IOException, PinLockedException, IncorrectPinException, PinLockMissingException { verificationCode = verificationCode.replace("-", ""); try { final var response = verifier.verify(sessionId, verificationCode, null); @@ -127,7 +128,7 @@ public class NumberVerificationUtils { final var registrationLockData = pinHelper.getRegistrationLockData(pin, e); if (registrationLockData == null) { - throw e; + throw new PinLockMissingException(); } var registrationLock = registrationLockData.getMasterKey().deriveRegistrationLock(); diff --git a/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java b/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java index 8fce4dbf..1242c194 100644 --- a/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java +++ b/src/main/java/org/asamk/signal/commands/FinishChangeNumberCommand.java @@ -9,6 +9,7 @@ import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.NotPrimaryDeviceException; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.output.OutputWriter; @@ -50,6 +51,8 @@ public class FinishChangeNumberCommand implements JsonRpcLocalCommand { + "\nUse '--pin PIN_CODE' to specify the registration lock PIN"); } catch (IncorrectPinException e) { throw new UserErrorException("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining()); + } catch (PinLockMissingException e) { + throw new UserErrorException("Account is pin locked, but pin data has been deleted on the server."); } catch (NotPrimaryDeviceException e) { throw new UserErrorException("This command doesn't work on linked devices."); } catch (IOException e) { diff --git a/src/main/java/org/asamk/signal/commands/VerifyCommand.java b/src/main/java/org/asamk/signal/commands/VerifyCommand.java index a8543c5a..6a5046bc 100644 --- a/src/main/java/org/asamk/signal/commands/VerifyCommand.java +++ b/src/main/java/org/asamk/signal/commands/VerifyCommand.java @@ -11,6 +11,7 @@ import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.api.IncorrectPinException; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.output.JsonWriter; import org.slf4j.Logger; @@ -76,6 +77,8 @@ public class VerifyCommand implements RegistrationCommand, JsonRpcRegistrationCo + "\nUse '--pin PIN_CODE' to specify the registration lock PIN"); } catch (IncorrectPinException e) { throw new UserErrorException("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining()); + } catch (PinLockMissingException e) { + throw new UserErrorException("Account is pin locked, but pin data has been deleted on the server."); } catch (IOException e) { throw new IOErrorException("Verify error: " + e.getMessage(), e); } diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java index cf6999a9..50150a98 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalControlImpl.java @@ -10,6 +10,7 @@ import org.asamk.signal.manager.RegistrationManager; import org.asamk.signal.manager.api.CaptchaRequiredException; import org.asamk.signal.manager.api.IncorrectPinException; import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException; +import org.asamk.signal.manager.api.PinLockMissingException; import org.asamk.signal.manager.api.PinLockedException; import org.asamk.signal.manager.api.RateLimitException; import org.asamk.signal.manager.api.UserAlreadyExistsException; @@ -105,6 +106,8 @@ public class DbusSignalControlImpl implements org.asamk.SignalControl { + (e.getTimeRemaining() / 1000 / 60 / 60)); } catch (IncorrectPinException e) { throw new Error.Failure("Verification failed! Invalid pin, tries remaining: " + e.getTriesRemaining()); + } catch (PinLockMissingException e) { + throw new Error.Failure("Account is pin locked, but pin data has been deleted on the server."); } } From 5dc66f839d3792fbd32f830f9a82d5fbc4223711 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 10 Jun 2025 19:36:43 +0200 Subject: [PATCH 249/279] Close attachment input streams after upload Fixes #1790 --- .../manager/helper/AttachmentHelper.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java index 0ffa62ed..b55153f0 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java @@ -44,14 +44,20 @@ public class AttachmentHelper { } public List uploadAttachments(final List attachments) throws AttachmentInvalidException, IOException { - var attachmentStreams = createAttachmentStreams(attachments); + final var attachmentStreams = createAttachmentStreams(attachments); - // Upload attachments here, so we only upload once even for multiple recipients - var attachmentPointers = new ArrayList(attachmentStreams.size()); - for (var attachmentStream : attachmentStreams) { - attachmentPointers.add(uploadAttachment(attachmentStream)); + try { + // Upload attachments here, so we only upload once even for multiple recipients + final var attachmentPointers = new ArrayList(attachmentStreams.size()); + for (final var attachmentStream : attachmentStreams) { + attachmentPointers.add(uploadAttachment(attachmentStream)); + } + return attachmentPointers; + } finally { + for (final var attachmentStream : attachmentStreams) { + attachmentStream.close(); + } } - return attachmentPointers; } private List createAttachmentStreams(List attachments) throws AttachmentInvalidException, IOException { From 70c79eac01304cd2bae5da2973a33e3253d400dc Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 24 Jun 2025 23:13:00 +0200 Subject: [PATCH 250/279] Keep all unhandled fields of remote storage record Fixes #1792 --- .../signal/manager/syncStorage/AccountRecordProcessor.java | 2 +- .../signal/manager/syncStorage/ContactRecordProcessor.java | 2 +- .../signal/manager/syncStorage/GroupV1RecordProcessor.java | 2 +- .../signal/manager/syncStorage/GroupV2RecordProcessor.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java index 21c0d97a..30816060 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java @@ -111,7 +111,7 @@ public class AccountRecordProcessor extends DefaultStorageRecordProcessor Date: Wed, 25 Jun 2025 00:20:42 +0200 Subject: [PATCH 251/279] Update dependencies --- CHANGELOG.md | 2 ++ graalvm-config-dir/reflect-config.json | 5 ++++- gradle/libs.versions.toml | 10 +++++----- src/main/java/org/asamk/signal/BaseConfig.java | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ea3c01b..ecb19833 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +Requires libsignal-client version 0.74.0. + ## [0.13.16] - 2025-06-07 Requires libsignal-client version 0.73.2. diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index d1c2075b..0bfd1cdf 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -671,6 +671,10 @@ { "name":"long[]" }, +{ + "name":"okhttp3.internal.connection.RealConnectionPool", + "fields":[{"name":"addressStates"}] +}, { "name":"okio.BufferedSink" }, @@ -3036,7 +3040,6 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.ContactRecord", "allDeclaredFields":true, - "fields":[{"name":"aci"}, {"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"e164"}, {"name":"familyName"}, {"name":"givenName"}, {"name":"hidden"}, {"name":"hideStory"}, {"name":"identityKey"}, {"name":"identityState"}, {"name":"markedUnread"}, {"name":"mutedUntilTimestamp"}, {"name":"nickname"}, {"name":"note"}, {"name":"pni"}, {"name":"pniSignatureVerified"}, {"name":"profileKey"}, {"name":"systemFamilyName"}, {"name":"systemGivenName"}, {"name":"systemNickname"}, {"name":"unregisteredAtTimestamp"}, {"name":"username"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a6b9fda1..4ff08539 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,15 +3,15 @@ slf4j = "2.0.17" [libraries] bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.81" -jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.19.0" +jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.19.1" argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0" dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0" slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.18" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_124" -sqlite = "org.xerial:sqlite-jdbc:3.49.1.0" +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_125" +sqlite = "org.xerial:sqlite-jdbc:3.50.1.0" hikari = "com.zaxxer:HikariCP:6.3.0" -junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.1" -junit-launcher = "org.junit.platform:junit-platform-launcher:1.13.1" +junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.2" +junit-launcher = "org.junit.platform:junit-platform-launcher:1.13.2" diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 8a38c36a..1f0922fe 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.44.1"); + .orElse("Signal-Android/7.46.1"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From e6113d4d96f7677422d348b16408d2a4990fb934 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 28 Jun 2025 14:35:56 +0200 Subject: [PATCH 252/279] Update libsignal-service-java --- graalvm-config-dir/reflect-config.json | 1 - gradle/libs.versions.toml | 2 +- .../signal/manager/api/DeviceLinkUrl.java | 3 +-- .../signal/manager/config/LiveConfig.java | 3 +-- .../signal/manager/config/StagingConfig.java | 3 +-- .../manager/helper/AttachmentHelper.java | 9 ++++++++- .../internal/SignalWebSocketHealthMonitor.java | 8 +++++--- .../manager/storage/prekeys/PreKeyStore.java | 7 ++++--- .../storage/prekeys/SignedPreKeyStore.java | 7 ++++--- .../asamk/signal/manager/util/KeyUtils.java | 18 +++++++----------- src/main/java/org/asamk/signal/BaseConfig.java | 2 +- 11 files changed, 33 insertions(+), 30 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 0bfd1cdf..c5459804 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -3070,7 +3070,6 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record", "allDeclaredFields":true, - "fields":[{"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"dontNotifyForMentionsIfMuted"}, {"name":"hideStory"}, {"name":"markedUnread"}, {"name":"masterKey"}, {"name":"mutedUntilTimestamp"}, {"name":"storySendMode"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ff08539..680ca54a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.18" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_125" +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_126" sqlite = "org.xerial:sqlite-jdbc:3.50.1.0" hikari = "com.zaxxer:HikariCP:6.3.0" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.2" diff --git a/lib/src/main/java/org/asamk/signal/manager/api/DeviceLinkUrl.java b/lib/src/main/java/org/asamk/signal/manager/api/DeviceLinkUrl.java index 54271dac..2bb63507 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/DeviceLinkUrl.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/DeviceLinkUrl.java @@ -2,7 +2,6 @@ package org.asamk.signal.manager.api; import org.asamk.signal.manager.util.Utils; import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import java.net.URI; @@ -37,7 +36,7 @@ public record DeviceLinkUrl(String deviceIdentifier, ECPublicKey deviceKey) { } ECPublicKey deviceKey; try { - deviceKey = Curve.decodePoint(publicKeyBytes, 0); + deviceKey = new ECPublicKey(publicKeyBytes); } catch (InvalidKeyException e) { throw new InvalidDeviceLinkException("Invalid device link", e); } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index 111695f6..890d7081 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -2,7 +2,6 @@ package org.asamk.signal.manager.config; import org.signal.libsignal.net.Network.Environment; import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.whispersystems.signalservice.api.push.TrustStore; import org.whispersystems.signalservice.internal.configuration.HttpProxy; @@ -80,7 +79,7 @@ class LiveConfig { static ECPublicKey getUnidentifiedSenderTrustRoot() { try { - return Curve.decodePoint(UNIDENTIFIED_SENDER_TRUST_ROOT, 0); + return new ECPublicKey(UNIDENTIFIED_SENDER_TRUST_ROOT); } catch (InvalidKeyException e) { throw new AssertionError(e); } diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index e72c66b5..6d8f08c7 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -2,7 +2,6 @@ package org.asamk.signal.manager.config; import org.signal.libsignal.net.Network; import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.whispersystems.signalservice.api.push.TrustStore; import org.whispersystems.signalservice.internal.configuration.HttpProxy; @@ -80,7 +79,7 @@ class StagingConfig { static ECPublicKey getUnidentifiedSenderTrustRoot() { try { - return Curve.decodePoint(UNIDENTIFIED_SENDER_TRUST_ROOT, 0); + return new ECPublicKey(UNIDENTIFIED_SENDER_TRUST_ROOT); } catch (InvalidKeyException e) { throw new AssertionError(e); } diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java index b55153f0..08526c72 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AttachmentHelper.java @@ -9,6 +9,7 @@ import org.asamk.signal.manager.util.IOUtils; import org.signal.libsignal.protocol.InvalidMessageException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream; @@ -138,9 +139,15 @@ public class AttachmentHelper { SignalServiceAttachmentPointer pointer, File tmpFile ) throws IOException { + if (pointer.getDigest().isEmpty()) { + throw new IOException("Attachment pointer has no digest."); + } try { return dependencies.getMessageReceiver() - .retrieveAttachment(pointer, tmpFile, ServiceConfig.MAX_ATTACHMENT_SIZE); + .retrieveAttachment(pointer, + tmpFile, + ServiceConfig.MAX_ATTACHMENT_SIZE, + AttachmentCipherInputStream.IntegrityCheck.forEncryptedDigest(pointer.getDigest().get())); } catch (MissingConfigurationException | InvalidMessageException e) { throw new IOException(e); } diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java index dfae5985..7d927ddd 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalWebSocketHealthMonitor.java @@ -56,7 +56,10 @@ final class SignalWebSocketHealthMonitor implements HealthMonitor { .distinctUntilChanged() .subscribe(this::onStateChanged); - webSocket.setKeepAliveChangedListener(this::updateKeepAliveSenderStatus); + webSocket.addKeepAliveChangeListener(() -> { + executor.execute(this::updateKeepAliveSenderStatus); + return Unit.INSTANCE; + }); }); } @@ -78,7 +81,7 @@ final class SignalWebSocketHealthMonitor implements HealthMonitor { public void onMessageError(int status, boolean isIdentifiedWebSocket) { } - private Unit updateKeepAliveSenderStatus() { + private void updateKeepAliveSenderStatus() { if (keepAliveSender == null && sendKeepAlives()) { keepAliveSender = new KeepAliveSender(); keepAliveSender.start(); @@ -86,7 +89,6 @@ final class SignalWebSocketHealthMonitor implements HealthMonitor { keepAliveSender.shutdown(); keepAliveSender = null; } - return Unit.INSTANCE; } private boolean sendKeepAlives() { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java index d2d55710..8e8bd2ab 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/PreKeyStore.java @@ -4,8 +4,9 @@ import org.asamk.signal.manager.storage.Database; import org.asamk.signal.manager.storage.Utils; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyIdException; -import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; +import org.signal.libsignal.protocol.ecc.ECPrivateKey; +import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.signal.libsignal.protocol.state.PreKeyRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -176,8 +177,8 @@ public class PreKeyStore implements SignalServicePreKeyStore { private PreKeyRecord getPreKeyRecordFromResultSet(ResultSet resultSet) throws SQLException { try { final var keyId = resultSet.getInt("key_id"); - final var publicKey = Curve.decodePoint(resultSet.getBytes("public_key"), 0); - final var privateKey = Curve.decodePrivatePoint(resultSet.getBytes("private_key")); + final var publicKey = new ECPublicKey(resultSet.getBytes("public_key")); + final var privateKey = new ECPrivateKey(resultSet.getBytes("private_key")); return new PreKeyRecord(keyId, new ECKeyPair(publicKey, privateKey)); } catch (InvalidKeyException e) { return null; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java index d62e939d..4644c3ba 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/prekeys/SignedPreKeyStore.java @@ -4,8 +4,9 @@ import org.asamk.signal.manager.storage.Database; import org.asamk.signal.manager.storage.Utils; import org.signal.libsignal.protocol.InvalidKeyException; import org.signal.libsignal.protocol.InvalidKeyIdException; -import org.signal.libsignal.protocol.ecc.Curve; import org.signal.libsignal.protocol.ecc.ECKeyPair; +import org.signal.libsignal.protocol.ecc.ECPrivateKey; +import org.signal.libsignal.protocol.ecc.ECPublicKey; import org.signal.libsignal.protocol.state.SignedPreKeyRecord; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -238,8 +239,8 @@ public class SignedPreKeyStore implements org.signal.libsignal.protocol.state.Si private SignedPreKeyRecord getSignedPreKeyRecordFromResultSet(ResultSet resultSet) throws SQLException { try { final var keyId = resultSet.getInt("key_id"); - final var publicKey = Curve.decodePoint(resultSet.getBytes("public_key"), 0); - final var privateKey = Curve.decodePrivatePoint(resultSet.getBytes("private_key")); + final var publicKey = new ECPublicKey(resultSet.getBytes("public_key")); + final var privateKey = new ECPrivateKey(resultSet.getBytes("private_key")); final var signature = resultSet.getBytes("signature"); final var timestamp = resultSet.getLong("timestamp"); return new SignedPreKeyRecord(keyId, timestamp, new ECKeyPair(publicKey, privateKey), signature); diff --git a/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java index 37a0bf82..31c3cd39 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/KeyUtils.java @@ -4,7 +4,7 @@ import org.asamk.signal.manager.storage.SignalAccount; import org.signal.libsignal.protocol.IdentityKey; import org.signal.libsignal.protocol.IdentityKeyPair; import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.ecc.Curve; +import org.signal.libsignal.protocol.ecc.ECKeyPair; import org.signal.libsignal.protocol.ecc.ECPrivateKey; import org.signal.libsignal.protocol.kem.KEMKeyPair; import org.signal.libsignal.protocol.kem.KEMKeyType; @@ -33,8 +33,8 @@ public class KeyUtils { public static IdentityKeyPair getIdentityKeyPair(byte[] publicKeyBytes, byte[] privateKeyBytes) { try { - IdentityKey publicKey = new IdentityKey(publicKeyBytes); - ECPrivateKey privateKey = Curve.decodePrivatePoint(privateKeyBytes); + final var publicKey = new IdentityKey(publicKeyBytes); + final var privateKey = new ECPrivateKey(privateKeyBytes); return new IdentityKeyPair(publicKey, privateKey); } catch (InvalidKeyException e) { @@ -43,7 +43,7 @@ public class KeyUtils { } public static IdentityKeyPair generateIdentityKeyPair() { - var djbKeyPair = Curve.generateKeyPair(); + var djbKeyPair = ECKeyPair.generate(); var djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey()); var djbPrivateKey = djbKeyPair.getPrivateKey(); @@ -54,7 +54,7 @@ public class KeyUtils { var records = new ArrayList(PREKEY_BATCH_SIZE); for (var i = 0; i < PREKEY_BATCH_SIZE; i++) { var preKeyId = (offset + i) % PREKEY_MAXIMUM_ID; - var keyPair = Curve.generateKeyPair(); + var keyPair = ECKeyPair.generate(); var record = new PreKeyRecord(preKeyId, keyPair); records.add(record); @@ -66,13 +66,9 @@ public class KeyUtils { final int signedPreKeyId, final ECPrivateKey privateKey ) { - var keyPair = Curve.generateKeyPair(); + var keyPair = ECKeyPair.generate(); byte[] signature; - try { - signature = Curve.calculateSignature(privateKey, keyPair.getPublicKey().serialize()); - } catch (InvalidKeyException e) { - throw new AssertionError(e); - } + signature = privateKey.calculateSignature(keyPair.getPublicKey().serialize()); return new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature); } diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index 1f0922fe..730485bc 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.46.1"); + .orElse("Signal-Android/7.47.1"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION; From fa9bb3c21030fb0ef89a440859b796a1e5ebd64a Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 28 Jun 2025 14:57:20 +0200 Subject: [PATCH 253/279] Bump version to 0.13.17 --- CHANGELOG.md | 14 ++++++++++++-- build.gradle.kts | 2 +- data/org.asamk.SignalCli.metainfo.xml | 3 +++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb19833..62abfe70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,18 @@ # Changelog -## [Unreleased] +## [0.13.17] - 2025-06-28 -Requires libsignal-client version 0.74.0. +Requires libsignal-client version 0.76.0. + +### Fixed + +- Fix issue when loading an older inactive group +- Close attachment input streams after upload +- Fix storage sync behavior with unhandled fields + +### Changed + +- Improve behavior when pin data doesn't exist on the server ## [0.13.16] - 2025-06-07 diff --git a/build.gradle.kts b/build.gradle.kts index 8fadb883..11715e36 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.17-SNAPSHOT" + version = "0.13.17" } java { diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index b7483db9..95646534 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -45,6 +45,9 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.17 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.16 From e7ca02f1fb1a06caf712a3316648947e99fed5da Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 254/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62abfe70..5437bd63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.17] - 2025-06-28 Requires libsignal-client version 0.76.0. diff --git a/build.gradle.kts b/build.gradle.kts index 11715e36..c3568444 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.17" + version = "0.13.18-SNAPSHOT" } java { From 069325af4715b63725ac4a41874dd453dea563cf Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 29 Jun 2025 11:16:32 +0200 Subject: [PATCH 255/279] Extend shutdown request with optional error --- src/main/java/org/asamk/signal/Shutdown.java | 15 ++++++++++++--- .../org/asamk/signal/commands/DaemonCommand.java | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/asamk/signal/Shutdown.java b/src/main/java/org/asamk/signal/Shutdown.java index a59497c9..c51c12e4 100644 --- a/src/main/java/org/asamk/signal/Shutdown.java +++ b/src/main/java/org/asamk/signal/Shutdown.java @@ -1,5 +1,6 @@ package org.asamk.signal; +import org.asamk.signal.commands.exceptions.CommandException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,7 +12,7 @@ import sun.misc.Signal; public class Shutdown { private static final Logger logger = LoggerFactory.getLogger(Shutdown.class); - private static final CompletableFuture shutdown = new CompletableFuture<>(); + private static final CompletableFuture shutdown = new CompletableFuture<>(); private static final CompletableFuture shutdownComplete = new CompletableFuture<>(); private static boolean initialized = false; @@ -43,9 +44,17 @@ public class Shutdown { shutdown.complete(null); } - public static void waitForShutdown() throws InterruptedException { + public static void triggerShutdown(CommandException exception) { + logger.debug("Triggering shutdown with exception.", exception); + shutdown.complete(exception); + } + + public static void waitForShutdown() throws InterruptedException, CommandException { try { - shutdown.get(); + final var result = shutdown.get(); + if (result instanceof CommandException e) { + throw e; + } } catch (ExecutionException e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/asamk/signal/commands/DaemonCommand.java b/src/main/java/org/asamk/signal/commands/DaemonCommand.java index 197e1286..6790c65d 100644 --- a/src/main/java/org/asamk/signal/commands/DaemonCommand.java +++ b/src/main/java/org/asamk/signal/commands/DaemonCommand.java @@ -97,7 +97,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand { final OutputWriter outputWriter ) throws CommandException { Shutdown.installHandler(); - logger.info("Starting daemon in single-account mode for " + m.getSelfNumber()); + logger.info("Starting daemon in single-account mode for {}", m.getSelfNumber()); final var noReceiveStdOut = Boolean.TRUE.equals(ns.getBoolean("no-receive-stdout")); final var receiveMode = ns.get("receive-mode"); final var receiveConfig = getReceiveConfig(ns); From cb06cbdcca718e7a2f5e9360f3eb6b319a9ea933 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 29 Jun 2025 11:21:31 +0200 Subject: [PATCH 256/279] Shut down when dbus daemon connection goes away unexpectedly Fixes #1800 --- .../java/org/asamk/signal/dbus/DbusHandler.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/dbus/DbusHandler.java b/src/main/java/org/asamk/signal/dbus/DbusHandler.java index b6799de7..2ed6d11f 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusHandler.java +++ b/src/main/java/org/asamk/signal/dbus/DbusHandler.java @@ -1,17 +1,21 @@ package org.asamk.signal.dbus; import org.asamk.signal.DbusConfig; +import org.asamk.signal.Shutdown; import org.asamk.signal.commands.exceptions.CommandException; +import org.asamk.signal.commands.exceptions.IOErrorException; import org.asamk.signal.commands.exceptions.UnexpectedErrorException; import org.asamk.signal.commands.exceptions.UserErrorException; import org.asamk.signal.manager.Manager; import org.asamk.signal.manager.MultiAccountManager; +import org.freedesktop.dbus.connections.IDisconnectCallback; import org.freedesktop.dbus.connections.impl.DBusConnection; import org.freedesktop.dbus.connections.impl.DBusConnectionBuilder; import org.freedesktop.dbus.exceptions.DBusException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -94,7 +98,9 @@ public class DbusHandler implements AutoCloseable { final var busType = isDbusSystem ? DBusConnection.DBusBusType.SYSTEM : DBusConnection.DBusBusType.SESSION; logger.debug("Starting DBus server on {} bus: {}", busType, busname); try { - dBusConnection = DBusConnectionBuilder.forType(busType).build(); + dBusConnection = DBusConnectionBuilder.forType(busType) + .withDisconnectCallback(new DisconnectCallback()) + .build(); dbusRunner.run(dBusConnection); } catch (DBusException e) { throw new UnexpectedErrorException("Dbus command failed: " + e.getMessage(), e); @@ -141,4 +147,13 @@ public class DbusHandler implements AutoCloseable { void run(DBusConnection connection) throws DBusException; } + + private static final class DisconnectCallback implements IDisconnectCallback { + + @Override + public void disconnectOnError(IOException ex) { + logger.debug("DBus daemon disconnected unexpectedly, shutting down"); + Shutdown.triggerShutdown(new IOErrorException("Unexpected dbus daemon disconnect", ex)); + } + } } From 3180eba83694cf24d23cf9f3e4d73fa5e8791533 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 8 Jul 2025 17:34:04 +0200 Subject: [PATCH 257/279] Exit if account check fails at startup Fixes #1804 --- .../signal/manager/SignalAccountFiles.java | 22 ++++++++++++++----- src/main/java/org/asamk/signal/App.java | 2 ++ 2 files changed, 18 insertions(+), 6 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 4072a9df..cd0b5a21 100644 --- a/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java +++ b/lib/src/main/java/org/asamk/signal/manager/SignalAccountFiles.java @@ -2,6 +2,7 @@ package org.asamk.signal.manager; import org.asamk.signal.manager.api.AccountCheckException; import org.asamk.signal.manager.api.NotRegisteredException; +import org.asamk.signal.manager.api.Pair; import org.asamk.signal.manager.api.ServiceEnvironment; import org.asamk.signal.manager.config.ServiceConfig; import org.asamk.signal.manager.config.ServiceEnvironmentConfig; @@ -63,19 +64,28 @@ public class SignalAccountFiles { return accountsStore.getAllNumbers(); } - public MultiAccountManager initMultiAccountManager() throws IOException { - final var managers = accountsStore.getAllAccounts().parallelStream().map(a -> { + public MultiAccountManager initMultiAccountManager() throws IOException, AccountCheckException { + final var managerPairs = accountsStore.getAllAccounts().parallelStream().map(a -> { try { - return initManager(a.number(), a.path()); - } catch (NotRegisteredException | IOException | AccountCheckException e) { + return new Pair(initManager(a.number(), a.path()), null); + } catch (NotRegisteredException e) { logger.warn("Ignoring {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName()); return null; - } catch (Throwable e) { + } catch (AccountCheckException | IOException e) { logger.error("Failed to load {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName()); - throw e; + return new Pair(null, e); } }).filter(Objects::nonNull).toList(); + for (final var pair : managerPairs) { + if (pair.second() instanceof IOException e) { + throw e; + } else if (pair.second() instanceof AccountCheckException e) { + throw e; + } + } + + final var managers = managerPairs.stream().map(Pair::first).toList(); return new MultiAccountManagerImpl(managers, this); } diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index e494d201..cf731ea9 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -291,6 +291,8 @@ public class App { commandHandler.handleMultiLocalCommand(command, multiAccountManager); } catch (IOException e) { throw new IOErrorException("Failed to load local accounts file", e); + } catch (AccountCheckException e) { + throw new UnexpectedErrorException("Failed to load local accounts file", e); } } From 887ed3bb448624095f950674931cf60924c5f219 Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 8 Jul 2025 17:35:17 +0200 Subject: [PATCH 258/279] Show better error message when sending fails due to missing pre keys --- .../java/org/asamk/signal/commands/SendCommand.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index 39be74ee..cce1d21e 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -250,8 +250,14 @@ public class SendCommand implements JsonRpcLocalCommand { : m.sendMessage(message, recipientIdentifiers, notifySelf); outputResult(outputWriter, results); } catch (AttachmentInvalidException | IOException e) { - throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() - .getSimpleName() + ")", e); + if (e instanceof IOException io && io.getMessage().contains("No prekeys available")) { + throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() + .getSimpleName() + "), maybe one of the devices of the recipient wasn't online for a while.", + e); + } else { + throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass() + .getSimpleName() + ")", e); + } } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) { throw new UserErrorException(e.getMessage()); } catch (UnregisteredRecipientException e) { From 1b7f75559008ea2dc557bc3e5228bd0db7a30b3f Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 12 Jul 2025 10:05:14 +0200 Subject: [PATCH 259/279] Update dependencies --- client/Cargo.lock | 512 ++++++++++++++++++++--------------- client/Cargo.toml | 2 +- client/src/transports/mod.rs | 7 +- 3 files changed, 295 insertions(+), 226 deletions(-) diff --git a/client/Cargo.lock b/client/Cargo.lock index d0cd01f4..ed225c51 100644 --- a/client/Cargo.lock +++ b/client/Cargo.lock @@ -13,15 +13,15 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -34,44 +34,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "async-trait" @@ -90,17 +90,11 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -119,9 +113,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bytes" @@ -131,9 +125,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.18" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "shlex", ] @@ -146,15 +140,15 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "clap" -version = "4.5.35" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -162,9 +156,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", @@ -175,9 +169,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", @@ -187,15 +181,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "combine" @@ -209,9 +203,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -242,12 +236,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -326,9 +320,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -343,9 +337,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -362,9 +356,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heck" @@ -434,11 +428,10 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", @@ -452,12 +445,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", @@ -472,21 +466,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -495,31 +490,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -527,67 +502,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" version = "1.0.3" @@ -601,9 +563,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -611,14 +573,25 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -655,9 +628,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jsonrpsee" -version = "0.24.9" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b26c20e2178756451cfeb0661fb74c47dd5988cb7e3939de7e9241fd604d42" +checksum = "1fba77a59c4c644fd48732367624d1bcf6f409f9c9a286fbc71d2f1fc0b2ea16" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -668,9 +641,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.24.9" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456196007ca3a14db478346f58c7238028d55ee15c1df15115596e411ff27925" +checksum = "693c93cbb7db25f4108ed121304b671a36002c2db67dff2ee4391a688c738547" dependencies = [ "async-trait", "bytes", @@ -684,19 +657,19 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", "tokio-stream", + "tower", "tracing", ] [[package]] name = "jsonrpsee-http-client" -version = "0.24.9" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c872b6c9961a4ccc543e321bb5b89f6b2d2c7fe8b61906918273a3333c95400c" +checksum = "6962d2bd295f75e97dd328891e58fce166894b974c1f7ce2e7597f02eeceb791" dependencies = [ - "async-trait", "base64", "http-body", "hyper", @@ -708,18 +681,17 @@ dependencies = [ "rustls-platform-verifier", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", "tower", - "tracing", "url", ] [[package]] name = "jsonrpsee-proc-macros" -version = "0.24.9" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e65763c942dfc9358146571911b0cd1c361c2d63e2d2305622d40d36376ca80" +checksum = "2fa4f5daed39f982a1bb9d15449a28347490ad42b212f8eaa2a2a344a0dce9e9" dependencies = [ "heck", "proc-macro-crate", @@ -730,33 +702,33 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.24.9" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a8e70baf945b6b5752fc8eb38c918a48f1234daf11355e07106d963f860089" +checksum = "66df7256371c45621b3b7d2fb23aea923d577616b9c0e9c0b950a6ea5c2be0ca" dependencies = [ "http", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.12", ] [[package]] name = "libc" -version = "0.2.171" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "log" @@ -766,28 +738,28 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "miniz_oxide" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "wasi", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -805,6 +777,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "openssl-probe" version = "0.1.6" @@ -849,6 +827,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "proc-macro-crate" version = "3.3.0" @@ -860,9 +847,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -892,9 +879,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -904,9 +891,9 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", @@ -917,9 +904,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ "log", "once_cell", @@ -944,15 +931,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-platform-verifier" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5467026f437b4cb2a533865eaa73eb840019a0916f4b9ec563c6e617e086c9" +checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ "core-foundation", "core-foundation-sys", @@ -965,7 +955,7 @@ dependencies = [ "rustls-webpki", "security-framework", "security-framework-sys", - "webpki-root-certs", + "webpki-root-certs 0.26.11", "windows-sys 0.59.0", ] @@ -977,9 +967,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -1090,24 +1080,21 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1133,9 +1120,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1143,10 +1130,16 @@ dependencies = [ ] [[package]] -name = "synstructure" -version = "0.13.1" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", @@ -1205,9 +1198,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -1215,15 +1208,17 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.2" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -1263,9 +1258,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -1276,15 +1271,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", @@ -1293,17 +1288,16 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.13" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", + "sync_wrapper", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -1324,7 +1318,6 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1332,9 +1325,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -1343,9 +1336,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] @@ -1379,12 +1372,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -1418,15 +1405,24 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "webpki-root-certs" -version = "0.26.8" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.1", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86138b15b2b7d561bc4469e77027b8dd005a43dc502e9031d1f5afc8ce1f280e" dependencies = [ "rustls-pki-types", ] @@ -1467,6 +1463,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -1491,13 +1496,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -1510,6 +1531,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -1522,6 +1549,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -1534,12 +1567,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -1552,6 +1597,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -1564,6 +1615,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -1576,6 +1633,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -1589,31 +1652,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.7.6" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -1623,9 +1686,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -1661,10 +1724,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] -name = "zerovec" -version = "0.10.4" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -1673,9 +1747,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", diff --git a/client/Cargo.toml b/client/Cargo.toml index c435149f..694ee652 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -12,7 +12,7 @@ log = "0.4" serde = "1" serde_json = "1" tokio = { version = "1", features = ["rt", "macros", "net", "rt-multi-thread"] } -jsonrpsee = { version = "0.24", features = [ +jsonrpsee = { version = "0.25", features = [ "macros", "async-client", "http-client", diff --git a/client/src/transports/mod.rs b/client/src/transports/mod.rs index 04f4390f..3d0c4195 100644 --- a/client/src/transports/mod.rs +++ b/client/src/transports/mod.rs @@ -1,8 +1,5 @@ use futures_util::{stream::StreamExt, Sink, SinkExt, Stream}; -use jsonrpsee::core::{ - async_trait, - client::{ReceivedMessage, TransportReceiverT, TransportSenderT}, -}; +use jsonrpsee::core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT}; use thiserror::Error; pub mod ipc; @@ -21,7 +18,6 @@ struct Sender> { inner: T, } -#[async_trait] impl + Unpin + 'static> TransportSenderT for Sender { @@ -48,7 +44,6 @@ struct Receiver { inner: T, } -#[async_trait] impl> + Unpin + 'static> TransportReceiverT for Receiver { From ff846bc6784f1245bc2b1c83651fea3b673c563a Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 12 Jul 2025 10:05:57 +0200 Subject: [PATCH 260/279] Fix clippy warnings --- client/src/main.rs | 2 +- client/src/transports/mod.rs | 6 +++--- client/src/transports/stream_codec.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index 35c1ab22..68de03d4 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -70,7 +70,7 @@ async fn handle_command( .await .map_err(|e| RpcError::Custom(format!("JSON-RPC command startLink failed: {e:?}")))? .device_link_uri; - println!("{}", url); + println!("{url}"); client.finish_link(url, name).await } CliCommands::ListAccounts => client.list_accounts().await, diff --git a/client/src/transports/mod.rs b/client/src/transports/mod.rs index 3d0c4195..ad552cbf 100644 --- a/client/src/transports/mod.rs +++ b/client/src/transports/mod.rs @@ -27,7 +27,7 @@ impl + Unpin + 'static> T self.inner .send(body) .await - .map_err(|e| Errors::Other(format!("{:?}", e)))?; + .map_err(|e| Errors::Other(format!("{e:?}")))?; Ok(()) } @@ -35,7 +35,7 @@ impl + Unpin + 'static> T self.inner .close() .await - .map_err(|e| Errors::Other(format!("{:?}", e)))?; + .map_err(|e| Errors::Other(format!("{e:?}")))?; Ok(()) } } @@ -53,7 +53,7 @@ impl> + Unpin + 'static> match self.inner.next().await { None => Err(Errors::Closed), Some(Ok(msg)) => Ok(ReceivedMessage::Text(msg)), - Some(Err(e)) => Err(Errors::Other(format!("{:?}", e))), + Some(Err(e)) => Err(Errors::Other(format!("{e:?}"))), } } } diff --git a/client/src/transports/stream_codec.rs b/client/src/transports/stream_codec.rs index 6f77306f..e46233cb 100644 --- a/client/src/transports/stream_codec.rs +++ b/client/src/transports/stream_codec.rs @@ -41,7 +41,7 @@ impl Decoder for StreamCodec { match str::from_utf8(line.as_ref()) { Ok(s) => Ok(Some(s.to_string())), - Err(_) => Err(io::Error::new(io::ErrorKind::Other, "invalid UTF-8")), + Err(_) => Err(io::Error::other("invalid UTF-8")), } } else { Ok(None) From d54be747da7fec55686eae27a26dbd266e4fbb6d Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 12 Jul 2025 10:15:27 +0200 Subject: [PATCH 261/279] Remove unused dependency --- client/Cargo.lock | 1 - client/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/client/Cargo.lock b/client/Cargo.lock index ed225c51..01f28e0c 100644 --- a/client/Cargo.lock +++ b/client/Cargo.lock @@ -1070,7 +1070,6 @@ dependencies = [ "clap", "futures-util", "jsonrpsee", - "log", "serde", "serde_json", "thiserror 2.0.12", diff --git a/client/Cargo.toml b/client/Cargo.toml index 694ee652..85daf1e2 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" [dependencies] anyhow = "1" clap = { version = "4", features = ["cargo", "derive", "wrap_help"] } -log = "0.4" serde = "1" serde_json = "1" tokio = { version = "1", features = ["rt", "macros", "net", "rt-multi-thread"] } From a96626c4685cfaf589deea45ff2d46e5d6883f3c Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 12 Jul 2025 10:16:57 +0200 Subject: [PATCH 262/279] Update to rust 2024 edition --- client/Cargo.toml | 2 +- client/src/jsonrpc.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/Cargo.toml b/client/Cargo.toml index 85daf1e2..aff9ede4 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "signal-cli-client" version = "0.0.1" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/client/src/jsonrpc.rs b/client/src/jsonrpc.rs index b085cde5..6598d525 100644 --- a/client/src/jsonrpc.rs +++ b/client/src/jsonrpc.rs @@ -417,6 +417,6 @@ pub async fn connect_unix( Ok(ClientBuilder::default().build_with_tokio(sender, receiver)) } -pub async fn connect_http(uri: &str) -> Result { +pub async fn connect_http(uri: &str) -> Result, Error> { HttpClientBuilder::default().build(uri) } From ca33249170118be0d2fe3e9deed4ad23b34ac875 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 12 Jul 2025 11:03:28 +0200 Subject: [PATCH 263/279] Handle rate limit exception correctly when querying usernames Fixes #1797 --- graalvm-config-dir/jni-config.json | 6 ++++- .../org/asamk/signal/manager/Manager.java | 2 +- .../manager/helper/RecipientHelper.java | 25 +++++++++++++------ .../signal/manager/internal/ManagerImpl.java | 2 +- .../signal/commands/GetUserStatusCommand.java | 13 +++++++--- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/graalvm-config-dir/jni-config.json b/graalvm-config-dir/jni-config.json index 23a2160c..523a6c03 100644 --- a/graalvm-config-dir/jni-config.json +++ b/graalvm-config-dir/jni-config.json @@ -58,7 +58,7 @@ }, { "name":"java.lang.Throwable", - "methods":[{"name":"getMessage","parameterTypes":[] }, {"name":"toString","parameterTypes":[] }] + "methods":[{"name":"getMessage","parameterTypes":[] }, {"name":"setStackTrace","parameterTypes":["java.lang.StackTraceElement[]"] }, {"name":"toString","parameterTypes":[] }] }, { "name":"java.lang.UnsatisfiedLinkError", @@ -126,6 +126,10 @@ "name":"org.signal.libsignal.net.NetworkException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] }, +{ + "name":"org.signal.libsignal.net.RetryLaterException", + "methods":[{"name":"","parameterTypes":["long"] }] +}, { "name":"org.signal.libsignal.protocol.DuplicateMessageException", "methods":[{"name":"","parameterTypes":["java.lang.String"] }] diff --git a/lib/src/main/java/org/asamk/signal/manager/Manager.java b/lib/src/main/java/org/asamk/signal/manager/Manager.java index 238084e7..ccb661e1 100644 --- a/lib/src/main/java/org/asamk/signal/manager/Manager.java +++ b/lib/src/main/java/org/asamk/signal/manager/Manager.java @@ -96,7 +96,7 @@ public interface Manager extends Closeable { */ Map getUserStatus(Set numbers) throws IOException, RateLimitException; - Map getUsernameStatus(Set usernames); + Map getUsernameStatus(Set usernames) throws IOException; void updateAccountAttributes( String deviceName, 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 ecb85254..a73bb741 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 @@ -17,6 +17,7 @@ import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidArgumentException; import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException; +import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import java.io.IOException; import java.util.Collection; @@ -68,7 +69,7 @@ public class RecipientHelper { .toSignalServiceAddress(); } - public Set resolveRecipients(Collection recipients) throws UnregisteredRecipientException { + public Set resolveRecipients(Collection recipients) throws UnregisteredRecipientException, IOException { final var recipientIds = new HashSet(recipients.size()); for (var number : recipients) { final var recipientId = resolveRecipient(number); @@ -91,7 +92,11 @@ public class RecipientHelper { } }); } else if (recipient instanceof RecipientIdentifier.Username(String username)) { - return resolveRecipientByUsernameOrLink(username, false); + try { + return resolveRecipientByUsernameOrLink(username, false); + } catch (Exception e) { + return null; + } } throw new AssertionError("Unexpected RecipientIdentifier: " + recipient); } @@ -99,7 +104,7 @@ public class RecipientHelper { public RecipientId resolveRecipientByUsernameOrLink( String username, boolean forceRefresh - ) throws UnregisteredRecipientException { + ) throws UnregisteredRecipientException, IOException { final Username finalUsername; try { finalUsername = getUsernameFromUsernameOrLink(username); @@ -110,11 +115,15 @@ public class RecipientHelper { try { final var aci = handleResponseException(dependencies.getUsernameApi().getAciByUsername(finalUsername)); return account.getRecipientStore().resolveRecipientTrusted(aci, finalUsername.getUsername()); - } catch (IOException e) { - throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, - null, - null, - username)); + } catch (NonSuccessfulResponseCodeException e) { + if (e.code == 404) { + throw new UnregisteredRecipientException(new org.asamk.signal.manager.api.RecipientAddress(null, + null, + null, + username)); + } + logger.debug("Failed to get uuid for username: {}", username, e); + throw e; } } return account.getRecipientStore().resolveRecipientByUsername(finalUsername.getUsername(), () -> { 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 ffb74de2..eb672d24 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 @@ -285,7 +285,7 @@ public class ManagerImpl implements Manager { } @Override - public Map getUsernameStatus(Set usernames) { + public Map getUsernameStatus(Set usernames) throws IOException { final var registeredUsers = new HashMap(); for (final var username : usernames) { try { diff --git a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java index e3d36d85..eab4155c 100644 --- a/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java +++ b/src/main/java/org/asamk/signal/commands/GetUserStatusCommand.java @@ -64,9 +64,16 @@ public class GetUserStatusCommand implements JsonRpcLocalCommand { } final var usernames = ns.getList("username"); - final var registeredUsernames = usernames == null - ? Map.of() - : m.getUsernameStatus(new HashSet<>(usernames)); + final Map registeredUsernames; + try { + registeredUsernames = usernames == null ? Map.of() : m.getUsernameStatus(new HashSet<>(usernames)); + } catch (IOException e) { + throw new IOErrorException("Unable to check if users are registered: " + + e.getMessage() + + " (" + + e.getClass().getSimpleName() + + ")", e); + } // Output switch (outputWriter) { From 4ce194afe28e8918d066b267dec8ea9cf6b4b2a0 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 12 Jul 2025 11:03:54 +0200 Subject: [PATCH 264/279] Add missing username parameter to getUserStatus command in json-rpc client --- client/src/cli.rs | 2 ++ client/src/jsonrpc.rs | 1 + client/src/main.rs | 9 +++++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/client/src/cli.rs b/client/src/cli.rs index 4ff9d458..e0abb717 100644 --- a/client/src/cli.rs +++ b/client/src/cli.rs @@ -84,6 +84,8 @@ pub enum CliCommands { }, GetUserStatus { recipient: Vec, + #[arg(long)] + username: Vec, }, JoinGroup { #[arg(long)] diff --git a/client/src/jsonrpc.rs b/client/src/jsonrpc.rs index 6598d525..3b6d583b 100644 --- a/client/src/jsonrpc.rs +++ b/client/src/jsonrpc.rs @@ -70,6 +70,7 @@ pub trait Rpc { &self, account: Option, recipients: Vec, + usernames: Vec, ) -> Result; #[method(name = "joinGroup", param_kind = map)] diff --git a/client/src/main.rs b/client/src/main.rs index 68de03d4..6466b2e6 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -60,8 +60,13 @@ async fn handle_command( .delete_local_account_data(cli.account, ignore_registered) .await } - CliCommands::GetUserStatus { recipient } => { - client.get_user_status(cli.account, recipient).await + CliCommands::GetUserStatus { + recipient, + username, + } => { + client + .get_user_status(cli.account, recipient, username) + .await } CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await, CliCommands::Link { name } => { From dbdff831323a3242527eb4e3e4a823322e1c9262 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 12 Jul 2025 11:09:24 +0200 Subject: [PATCH 265/279] Update README Fixes #1803 --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e6d6e5ff..c7228c09 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,12 @@ of all country codes.) signal-cli -a ACCOUNT send -m "This is a message" RECIPIENT ``` +* Send a message to a username, usernames need to be prefixed with `u:` + + ```sh + signal-cli -a ACCOUNT send -m "This is a message" u:USERNAME.000 + ``` + * Pipe the message content from another process. uname -a | signal-cli -a ACCOUNT send --message-from-stdin RECIPIENT From 3d4070a13907152b312c863a2ea885ac57935365 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 12 Jul 2025 11:41:52 +0200 Subject: [PATCH 266/279] Compile UnixStream support only on unix systems --- client/src/cli.rs | 1 + client/src/jsonrpc.rs | 1 + client/src/main.rs | 37 +++++++++++++++++++++--------------- client/src/transports/mod.rs | 1 + 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/client/src/cli.rs b/client/src/cli.rs index e0abb717..90809e1b 100644 --- a/client/src/cli.rs +++ b/client/src/cli.rs @@ -15,6 +15,7 @@ pub struct Cli { pub json_rpc_tcp: Option>, /// UNIX socket address and port of signal-cli daemon + #[cfg(unix)] #[arg(long, conflicts_with = "json_rpc_tcp")] pub json_rpc_socket: Option>, diff --git a/client/src/jsonrpc.rs b/client/src/jsonrpc.rs index 3b6d583b..6874652d 100644 --- a/client/src/jsonrpc.rs +++ b/client/src/jsonrpc.rs @@ -410,6 +410,7 @@ pub async fn connect_tcp( Ok(ClientBuilder::default().build_with_tokio(sender, receiver)) } +#[cfg(unix)] pub async fn connect_unix( socket_path: impl AsRef, ) -> Result { diff --git a/client/src/main.rs b/client/src/main.rs index 6466b2e6..f82ee237 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -482,23 +482,30 @@ async fn connect(cli: Cli) -> Result { handle_command(cli, client).await } else { - let socket_path = cli - .json_rpc_socket - .clone() - .unwrap_or(None) - .or_else(|| { - std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| { - PathBuf::from(runtime_dir) - .join(DEFAULT_SOCKET_SUFFIX) - .into() + #[cfg(windows)] + { + Err(RpcError::Custom("Invalid socket".into())) + } + #[cfg(unix)] + { + let socket_path = cli + .json_rpc_socket + .clone() + .unwrap_or(None) + .or_else(|| { + std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| { + PathBuf::from(runtime_dir) + .join(DEFAULT_SOCKET_SUFFIX) + .into() + }) }) - }) - .unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into()); - let client = jsonrpc::connect_unix(socket_path) - .await - .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?; + .unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into()); + let client = jsonrpc::connect_unix(socket_path) + .await + .map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?; - handle_command(cli, client).await + handle_command(cli, client).await + } } } diff --git a/client/src/transports/mod.rs b/client/src/transports/mod.rs index ad552cbf..ed1963a0 100644 --- a/client/src/transports/mod.rs +++ b/client/src/transports/mod.rs @@ -2,6 +2,7 @@ use futures_util::{stream::StreamExt, Sink, SinkExt, Stream}; use jsonrpsee::core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT}; use thiserror::Error; +#[cfg(unix)] pub mod ipc; mod stream_codec; pub mod tcp; From dc787be17ba3ec2396c992b77fff3aa7d98d259c Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 12 Jul 2025 11:31:38 +0200 Subject: [PATCH 267/279] Build rust json-rpc client in CI --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad4dbf15..23e19225 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,3 +69,28 @@ jobs: with: name: signal-cli-native path: build/native/nativeCompile/signal-cli + + build-client: + strategy: + matrix: + os: + - ubuntu + - macos + - windows + runs-on: ${{ matrix.os }}-latest + defaults: + run: + working-directory: ./client + steps: + - uses: actions/checkout@v4 + - name: Install rust + run: rustup default stable + - name: Build client + run: cargo build --release --verbose + - name: Archive production artifacts + uses: actions/upload-artifact@v4 + with: + name: signal-cli-client-${{ matrix.os }} + path: | + client/target/release/signal-cli-client + client/target/release/signal-cli-client.exe From c924d5c03a318d8c7d4dd7be287eaf0d523ecd91 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 14 Jul 2025 15:59:26 +0200 Subject: [PATCH 268/279] Update libsignal-service-java --- graalvm-config-dir/reflect-config.json | 1 + gradle/libs.versions.toml | 2 +- .../asamk/signal/manager/helper/IncomingMessageHandler.java | 5 +++-- .../asamk/signal/manager/internal/SignalDependencies.java | 4 +++- .../signal/manager/syncStorage/ContactRecordProcessor.java | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index c5459804..0bfd1cdf 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -3070,6 +3070,7 @@ { "name":"org.whispersystems.signalservice.internal.storage.protos.GroupV2Record", "allDeclaredFields":true, + "fields":[{"name":"archived"}, {"name":"avatarColor"}, {"name":"blocked"}, {"name":"dontNotifyForMentionsIfMuted"}, {"name":"hideStory"}, {"name":"markedUnread"}, {"name":"masterKey"}, {"name":"mutedUntilTimestamp"}, {"name":"storySendMode"}, {"name":"whitelisted"}], "methods":[{"name":"adapter","parameterTypes":[] }, {"name":"unknownFields","parameterTypes":[] }] }, { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 680ca54a..4edfe40a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.18" -signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_126" +signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_127" sqlite = "org.xerial:sqlite-jdbc:3.50.1.0" hikari = "com.zaxxer:HikariCP:6.3.0" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.2" diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java index 3bb7479a..dd9cb38f 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/IncomingMessageHandler.java @@ -41,6 +41,7 @@ import org.signal.libsignal.metadata.ProtocolNoSessionException; import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; import org.signal.libsignal.metadata.SelfSendException; import org.signal.libsignal.protocol.InvalidMessageException; +import org.signal.libsignal.protocol.UsePqRatchet; import org.signal.libsignal.protocol.groups.GroupSessionBuilder; import org.signal.libsignal.protocol.message.DecryptionErrorMessage; import org.signal.libsignal.zkgroup.InvalidInputException; @@ -105,7 +106,7 @@ public final class IncomingMessageHandler { try { final var cipherResult = dependencies.getCipher(destination == null || destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI) - .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp()); + .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp(), UsePqRatchet.NO); content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp()); if (content == null) { return new Pair<>(List.of(), null); @@ -143,7 +144,7 @@ public final class IncomingMessageHandler { try { final var cipherResult = dependencies.getCipher(destination == null || destination.equals(account.getAci()) ? ServiceIdType.ACI : ServiceIdType.PNI) - .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp()); + .decrypt(envelope.getProto(), envelope.getServerDeliveredTimestamp(), UsePqRatchet.NO); content = validate(envelope.getProto(), cipherResult, envelope.getServerDeliveredTimestamp()); if (content == null) { return new Pair<>(List.of(), null); diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index f954588f..0bc895cb 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -5,6 +5,7 @@ import org.asamk.signal.manager.config.ServiceEnvironmentConfig; import org.asamk.signal.manager.util.Utils; import org.signal.libsignal.metadata.certificate.CertificateValidator; import org.signal.libsignal.net.Network; +import org.signal.libsignal.protocol.UsePqRatchet; import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -326,7 +327,8 @@ public class SignalDependencies { Optional.empty(), executor, ServiceConfig.MAX_ENVELOPE_SIZE, - () -> true)); + () -> true, + UsePqRatchet.NO)); } public List getSecureValueRecovery() { diff --git a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java index 3858bd06..e1fee99b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java +++ b/lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java @@ -37,7 +37,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor Date: Mon, 14 Jul 2025 16:42:06 +0200 Subject: [PATCH 269/279] Add support for sending view once messages Closes #1812 --- client/src/cli.rs | 3 +++ client/src/jsonrpc.rs | 1 + client/src/main.rs | 2 ++ lib/src/main/java/org/asamk/signal/manager/api/Message.java | 1 + .../java/org/asamk/signal/manager/internal/ManagerImpl.java | 1 + man/signal-cli.1.adoc | 5 +++++ src/main/java/org/asamk/signal/commands/SendCommand.java | 5 +++++ src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java | 3 +++ 8 files changed, 21 insertions(+) diff --git a/client/src/cli.rs b/client/src/cli.rs index 90809e1b..7fa5d1c5 100644 --- a/client/src/cli.rs +++ b/client/src/cli.rs @@ -179,6 +179,9 @@ pub enum CliCommands { #[arg(short = 'a', long)] attachment: Vec, + #[arg(long)] + view_once: bool, + #[arg(long)] mention: Vec, diff --git a/client/src/jsonrpc.rs b/client/src/jsonrpc.rs index 6874652d..fe0dc668 100644 --- a/client/src/jsonrpc.rs +++ b/client/src/jsonrpc.rs @@ -183,6 +183,7 @@ pub trait Rpc { endSession: bool, message: String, attachments: Vec, + view_once: bool, mentions: Vec, textStyle: Vec, quoteTimestamp: Option, diff --git a/client/src/main.rs b/client/src/main.rs index f82ee237..ac12331d 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -144,6 +144,7 @@ async fn handle_command( end_session, message, attachment, + view_once, mention, text_style, quote_timestamp, @@ -170,6 +171,7 @@ async fn handle_command( end_session, message.unwrap_or_default(), attachment, + view_once, mention, text_style, quote_timestamp, diff --git a/lib/src/main/java/org/asamk/signal/manager/api/Message.java b/lib/src/main/java/org/asamk/signal/manager/api/Message.java index 48277e6d..9b372451 100644 --- a/lib/src/main/java/org/asamk/signal/manager/api/Message.java +++ b/lib/src/main/java/org/asamk/signal/manager/api/Message.java @@ -6,6 +6,7 @@ import java.util.Optional; public record Message( String messageText, List attachments, + boolean viewOnce, List mentions, Optional quote, Optional sticker, 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 eb672d24..726d0aaf 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 @@ -810,6 +810,7 @@ public class ManagerImpl implements Manager { } else if (!additionalAttachments.isEmpty()) { messageBuilder.withAttachments(additionalAttachments); } + messageBuilder.withViewOnce(message.viewOnce()); if (!message.mentions().isEmpty()) { messageBuilder.withMentions(resolveMentions(message.mentions())); } diff --git a/man/signal-cli.1.adoc b/man/signal-cli.1.adoc index 32321095..73286aac 100644 --- a/man/signal-cli.1.adoc +++ b/man/signal-cli.1.adoc @@ -316,6 +316,11 @@ Data URI encoded attachments must follow the RFC 2397. Additionally a file name can be added: e.g.: `data:;filename=;base64,` +*--view-once*:: +Send the message as a view once message. +A conformant client will only allow the receiver to view the message once. +View Once is only supported for messages that include an image attachment. + *--sticker* STICKER:: Send a sticker of a locally known sticker pack (syntax: stickerPackId:stickerId). Shouldn't be used together with `-m` as the official clients don't support this. diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index cce1d21e..e022723e 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -66,6 +66,9 @@ public class SendCommand implements JsonRpcLocalCommand { .help("Add an attachment. " + "Can be either a file path or a data URI. Data URI encoded attachments must follow the RFC 2397. Additionally a file name can be added, e.g. " + "data:;filename=;base64,."); + subparser.addArgument("--view-once") + .action(Arguments.storeTrue()) + .help("Send the message as a view once message"); subparser.addArgument("-e", "--end-session", "--endsession") .help("Clear session state and send end session message.") .action(Arguments.storeTrue()); @@ -164,6 +167,7 @@ public class SendCommand implements JsonRpcLocalCommand { if (attachments == null) { attachments = List.of(); } + final var viewOnce = ns.getBoolean("view-once"); final var selfNumber = m.getSelfNumber(); @@ -239,6 +243,7 @@ public class SendCommand implements JsonRpcLocalCommand { try { final var message = new Message(messageText, attachments, + viewOnce, mentions, Optional.ofNullable(quote), Optional.ofNullable(sticker), diff --git a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java index ac77df99..725df8b1 100644 --- a/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java +++ b/src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java @@ -236,6 +236,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { try { final var message = new Message(messageText, attachments, + false, List.of(), Optional.empty(), Optional.empty(), @@ -399,6 +400,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { try { final var message = new Message(messageText, attachments, + false, List.of(), Optional.empty(), Optional.empty(), @@ -444,6 +446,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable { try { final var message = new Message(messageText, attachments, + false, List.of(), Optional.empty(), Optional.empty(), From 3e981d66e9534db61953078b3ca8faf16ed9dd2d Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 14 Jul 2025 18:52:41 +0200 Subject: [PATCH 270/279] Fix null pointer regression --- src/main/java/org/asamk/signal/commands/SendCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/commands/SendCommand.java b/src/main/java/org/asamk/signal/commands/SendCommand.java index e022723e..98051b23 100644 --- a/src/main/java/org/asamk/signal/commands/SendCommand.java +++ b/src/main/java/org/asamk/signal/commands/SendCommand.java @@ -167,7 +167,7 @@ public class SendCommand implements JsonRpcLocalCommand { if (attachments == null) { attachments = List.of(); } - final var viewOnce = ns.getBoolean("view-once"); + final var viewOnce = Boolean.TRUE.equals(ns.getBoolean("view-once")); final var selfNumber = m.getSelfNumber(); From 783201d12e7ef621554fe16640ddfd7f0534ff37 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 16 Jul 2025 19:17:21 +0200 Subject: [PATCH 271/279] Fix incorrect error message --- src/main/java/org/asamk/signal/App.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/asamk/signal/App.java b/src/main/java/org/asamk/signal/App.java index cf731ea9..6352aa13 100644 --- a/src/main/java/org/asamk/signal/App.java +++ b/src/main/java/org/asamk/signal/App.java @@ -292,7 +292,7 @@ public class App { } catch (IOException e) { throw new IOErrorException("Failed to load local accounts file", e); } catch (AccountCheckException e) { - throw new UnexpectedErrorException("Failed to load local accounts file", e); + throw new UnexpectedErrorException("Failed to load account file", e); } } From 2225e69277a72926fd201269c554553f95af9163 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 16 Jul 2025 19:37:03 +0200 Subject: [PATCH 272/279] Update sqlite-jdbc --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4edfe40a..fec44a43 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } logback = "ch.qos.logback:logback-classic:1.5.18" signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_127" -sqlite = "org.xerial:sqlite-jdbc:3.50.1.0" +sqlite = "org.xerial:sqlite-jdbc:3.50.2.0" hikari = "com.zaxxer:HikariCP:6.3.0" junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.2" junit-launcher = "org.junit.platform:junit-platform-launcher:1.13.2" From dbc454ba9e71b6ed98e8b3a9682fefe849773263 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 16 Jul 2025 19:40:10 +0200 Subject: [PATCH 273/279] Bump version to 0.13.18 --- CHANGELOG.md | 18 +++++++++++++++++- build.gradle.kts | 2 +- data/org.asamk.SignalCli.metainfo.xml | 3 +++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5437bd63..bb795535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Changelog -## [Unreleased] +## [0.13.18] - 2025-07-16 + +Requires libsignal-client version 0.76.3. + +### Added + +- Added `--view-once` parameter to send command to send view once images + +### Fixed + +- Handle rate limit exception correctly when querying usernames + +### Improved + +- Shut down when dbus daemon connection goes away unexpectedly +- In daemon mode, exit immediately if account check fails at startup +- Improve behavior when sending to devices that have no available prekeys ## [0.13.17] - 2025-06-28 diff --git a/build.gradle.kts b/build.gradle.kts index c3568444..97853d19 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.18-SNAPSHOT" + version = "0.13.18" } java { diff --git a/data/org.asamk.SignalCli.metainfo.xml b/data/org.asamk.SignalCli.metainfo.xml index 95646534..c6c07411 100644 --- a/data/org.asamk.SignalCli.metainfo.xml +++ b/data/org.asamk.SignalCli.metainfo.xml @@ -45,6 +45,9 @@ intense + + https://github.com/AsamK/signal-cli/releases/tag/v0.13.18 + https://github.com/AsamK/signal-cli/releases/tag/v0.13.17 From a0960fcabd4b4a81e84feb83773e01160acec7e4 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 274/279] Prepare next release --- CHANGELOG.md | 2 ++ build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb795535..74d0bdc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## [Unreleased] + ## [0.13.18] - 2025-07-16 Requires libsignal-client version 0.76.3. diff --git a/build.gradle.kts b/build.gradle.kts index 97853d19..ea69d97e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "org.asamk" - version = "0.13.18" + version = "0.13.19-SNAPSHOT" } java { From be48afb2b5b4b138f09dcb112541fa56106a16d6 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 16 Jul 2025 20:56:02 +0200 Subject: [PATCH 275/279] Fix container build --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1f4027d..b7ce03f1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -182,7 +182,7 @@ jobs: tar xf ./"${ARCHIVE_DIR}"/*.tar.gz rm -r signal-cli-archive-* signal-cli-native mkdir -p build/install/ - mv ./signal-cli-*/ build/install/signal-cli + mv ./signal-cli-"${GITHUB_REF_NAME#v}"/ build/install/signal-cli - name: Build Image id: build_image From f9a36c6e0404d06bd396b24b5ea699e49ed29b89 Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 16 Jul 2025 20:59:26 +0200 Subject: [PATCH 276/279] Fix send parameters to be all camel case Fixes #1814 --- client/src/jsonrpc.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/jsonrpc.rs b/client/src/jsonrpc.rs index fe0dc668..66ef9d9d 100644 --- a/client/src/jsonrpc.rs +++ b/client/src/jsonrpc.rs @@ -183,7 +183,7 @@ pub trait Rpc { endSession: bool, message: String, attachments: Vec, - view_once: bool, + viewOnce: bool, mentions: Vec, textStyle: Vec, quoteTimestamp: Option, @@ -192,10 +192,10 @@ pub trait Rpc { quoteMention: Vec, quoteTextStyle: Vec, quoteAttachment: Vec, - preview_url: Option, - preview_title: Option, - preview_description: Option, - preview_image: Option, + previewUrl: Option, + previewTitle: Option, + previewDescription: Option, + previewImage: Option, sticker: Option, storyTimestamp: Option, storyAuthor: Option, From b453d7a0b9c6617d44d802c8ed310c17c1fe9257 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 2 Aug 2025 12:05:01 +0200 Subject: [PATCH 277/279] Add new svr2 mrenclave --- .../java/org/asamk/signal/manager/config/LiveConfig.java | 7 ++++--- .../org/asamk/signal/manager/config/StagingConfig.java | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java index 890d7081..c76da017 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/LiveConfig.java @@ -28,8 +28,9 @@ class LiveConfig { private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder() .decode("BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF"); private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"; - private static final String SVR2_MRENCLAVE_LEGACY = "9314436a9a144992bb3680770ea5fd7934a7ffd29257844a33763a238903d570"; - private static final String SVR2_MRENCLAVE = "093be9ea32405e85ae28dbb48eb668aebeb7dbe29517b9b86ad4bec4dfe0e6a6"; + private static final String SVR2_MRENCLAVE_LEGACY_LEGACY = "9314436a9a144992bb3680770ea5fd7934a7ffd29257844a33763a238903d570"; + private static final String SVR2_MRENCLAVE_LEGACY = "093be9ea32405e85ae28dbb48eb668aebeb7dbe29517b9b86ad4bec4dfe0e6a6"; + private static final String SVR2_MRENCLAVE = "29cd63c87bea751e3bfd0fbd401279192e2e5c99948b4ee9437eafc4968355fb"; private static final String URL = "https://chat.signal.org"; private static final String CDN_URL = "https://cdn.signal.org"; @@ -91,7 +92,7 @@ class LiveConfig { createDefaultServiceConfiguration(interceptors), getUnidentifiedSenderTrustRoot(), CDSI_MRENCLAVE, - List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY)); + List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY, SVR2_MRENCLAVE_LEGACY_LEGACY)); } private LiveConfig() { diff --git a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java index 6d8f08c7..e25fe21b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java +++ b/lib/src/main/java/org/asamk/signal/manager/config/StagingConfig.java @@ -28,8 +28,9 @@ class StagingConfig { private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT = Base64.getDecoder() .decode("BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx"); private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57"; - private static final String SVR2_MRENCLAVE_LEGACY = "38e01eff4fe357dc0b0e8ef7a44b4abc5489fbccba3a78780f3872c277f62bf3"; - private static final String SVR2_MRENCLAVE = "2e8cefe6e3f389d8426adb24e9b7fb7adf10902c96f06f7bbcee36277711ed91"; + private static final String SVR2_MRENCLAVE_LEGACY_LEGACY = "38e01eff4fe357dc0b0e8ef7a44b4abc5489fbccba3a78780f3872c277f62bf3"; + private static final String SVR2_MRENCLAVE_LEGACY = "2e8cefe6e3f389d8426adb24e9b7fb7adf10902c96f06f7bbcee36277711ed91"; + private static final String SVR2_MRENCLAVE = "a75542d82da9f6914a1e31f8a7407053b99cc99a0e7291d8fbd394253e19b036"; private static final String URL = "https://chat.staging.signal.org"; private static final String CDN_URL = "https://cdn-staging.signal.org"; @@ -91,7 +92,7 @@ class StagingConfig { createDefaultServiceConfiguration(interceptors), getUnidentifiedSenderTrustRoot(), CDSI_MRENCLAVE, - List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY)); + List.of(SVR2_MRENCLAVE, SVR2_MRENCLAVE_LEGACY, SVR2_MRENCLAVE_LEGACY_LEGACY)); } private StagingConfig() { From 42f10670b654b2314e05fee142ec6b18a8196362 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 17 Aug 2025 17:35:14 +0200 Subject: [PATCH 278/279] Replace deprecated groovy utils --- buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt b/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt index eb74acb2..ae8ae4de 100644 --- a/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt +++ b/buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt @@ -1,12 +1,10 @@ @file:Suppress("DEPRECATION") -import groovy.util.XmlSlurper -import groovy.util.slurpersupport.GPathResult -import org.codehaus.groovy.runtime.ResourceGroovyMethods import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Dependency +import javax.xml.parsers.DocumentBuilderFactory class CheckLibVersionsPlugin : Plugin { override fun apply(project: Project) { @@ -28,10 +26,10 @@ class CheckLibVersionsPlugin : Plugin { val name = dependency.name val metaDataUrl = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml" try { - val url = ResourceGroovyMethods.toURL(metaDataUrl) - val metaDataText = ResourceGroovyMethods.getText(url) - val metadata = XmlSlurper().parseText(metaDataText) - val newest = (metadata.getProperty("versioning") as GPathResult).getProperty("latest") + val dbf = DocumentBuilderFactory.newInstance() + val db = dbf.newDocumentBuilder() + val doc = db.parse(metaDataUrl); + val newest = doc.getElementsByTagName("latest").item(0).textContent if (version != newest.toString()) { println("UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}") } From f6d81e3c056cb42dd8e21b6ebd46d7cc119be221 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 17 Aug 2025 17:35:59 +0200 Subject: [PATCH 279/279] Update gradle --- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..8bdaf60c75ab801e22807dde59e12a8735a34077 100644 GIT binary patch delta 37256 zcmXVXV`E)y({>tT2aRppNn_h+Y}>|ev}4@T^BTF zt*UbFk22?fVj8UBV<>NN?oj)e%q3;ANZn%w$&6vqe{^I;QY|jWDMG5ZEZRBH(B?s8 z#P8OsAZjB^hSJcmj0htMiurSj*&pTVc4Q?J8pM$O*6ZGZT*uaKX|LW}Zf>VRnC5;1 zSCWN+wVs*KP6h)5YXeKX;l)oxK^6fH2%+TI+348tQ+wXDQZ>noe$eDa5Q{7FH|_d$ zq!-(Ga2avI1+K!}Fz~?<`hpS3Wc|u#W4`{F+&Nx(g8|DLU<^u~GRNe<35m05WFc~C zJM?2zO{8IPPG0XVWI?@BD!7)~mw6VdR;u4HGN~g^lH|h}=DgO$ec8G3#Dt?Lfc6k3v*{%viJm3wtS3c`aA;J< z(RqusS%t%}c#2l@(X#MCoIQR?Y3d#=zx#Htg_B4Z`ziM-Yui|#6&+YD^=T?@ZJ=Q! z7X;7vYNp%yy01j=nt5jfk%Ab9gFk=quaas)6_6)er_Ks2Qh&>!>f&1U`fyq-TmJot z_`m-)A=X+#_6-coG4Yz0AhDL2FcBpe18AnYp@620t{2)2unUz%5Wf!O*0+?E{bOwx z&NPT1{oMo(@?he0(ujvS+seFH%;Zq;9>!Ol43(Wl;Emujm}x&JU>#L|x_ffl=Az*- z-2mA00ap9V4D*kZ+!4FEEERo9KUG6hZNzZpu`xR zCT(HG$m%9BO;66C-({?7Y(ECD43@i3C=ZbhpaT+{3$R>6ZHlQ&i3pzF>(4O}8@gYB&wID6mkHHFf2O_edpaHIMV3E)&;(0bLUyGf(6&=B*)37Tubx zHB;CkwoF#&_%LCS1Z*Zb3L|n5dIIY!N;GMpEC7OFUVdYiJc=!tt2vh+nB)X?L(Oa@nCM zl-Bb`R~({aYF$Ra(UKd97mfin1l~*Gb=WWk^92POcsy+`D=Z~3OIqqKV5^))b_q;? zWBLW8oTQ)h>o_oRyIm3jvoS(7PH0%~HTbc)qm&v@^@;bii|1$&9ivbs@f*{wQd-OVj> zEX>{AAD?oGdcgR^a`qPH<|g)G3i_)cNbF38YRiWMjiCIe9y|}B=kFnO;`HDYua)9l zVnd68O;nXZwU?p8GRZ!9n#|TQr*|2roF-~1si~E3v9J{pCGXZ-ccUnmPA=iiB0SaT zB5m^|Hln3*&hcHX&xUoD>-k2$_~0h9EkW(|gP=1wXf`E4^2MK3TArmO)3vjy^OzgoV}n6JNYQbgAZF~MYA}XYKgLN~(fx3`trMC7 z+h#$&mI0I*fticKJhCd$0Y_X>DN2^G?;zz|qMwk-1^JIZuqo?{{I++YVr5He2{?S3 zGd9eykq!l0w+LGaCofT%nhOc8bxls9V&CfZCm?V-6R}2dDY3$wk@te znGy2pS$=3|wz!fmujPu+FRUD+c7r}#duG$YH>n$rKZ|}O1#y=(+3kdF`bP3J{+iAM zmK@PKt=WU}a%@pgV3y3-#+%I@(1sQDOqF5K#L+mDe_JDc*p<%i$FU_c#BG;9B9v-8 zhtRMK^5##f*yb&Vr6Lon$;53^+*QMDjeeQZ8pLE1vwa~J7|gv7pY$w#Gn3*JhNzn% z*x_dM@O4QdmT*3#qMUd!iJI=2%H92&`g0n;3NE4S=ci5UHpw4eEw&d{mKZ0CPu`>L zEGO4nq=X#uG3`AVlsAO`HQvhWL9gz=#%qTB?{&c=p-5E3qynmL{6yi$(uItGt%;M& zq?CXHG>1Tt$Mjj@64xL>@;LQJoyxJT+z$Pm9UvQu_ zOgARy33XHSDAhd8-{CQHxxFO#)$ND8OWSSc`FXxJ&_81xa)#GmUEWaMU2U$uRfh{2 z^Bbt+m?(qq*8>{CU&3iux+pH3iR@fwq?AloyDXq-H7PI9Z_h^cN>b$JE|ye(Utu_3 zui=tU1gn{DlJ-V-pQ;UUMC_0_DR$&vkG$?5ycZL$h>(9sRbYm0J7m|>+vJezi}Tpj zu0Fagr*Uq#I>f}E*mrje=kpuUQ*0f$Gv0Cvzwq`i(*jym$x1Qn#y06$L3$rIw{D2Y z2t0)ZBY}{5>^%oGuosKCxx|fkm~97o#vC2!bNu7J_b>5x?mw3YD!97su~EaDW+jm9 zv5U5ts0LRP4NcW@Hs2>X+-8kkXjdP?lra!W44a5rQy42ENhP|AR9IrceE`Z5hZ=A# zdB{w_f`EXrRy*=6lM|=@uFjWSQYrvM{6VopTHD)Zh2U;L8Jq!Y z<4W)hb34~;^0;c=TT-!TT;PP%cx!N;$wAaD@g7}7L}qcr!|HZzHUn=zKXh}kA!LED zDGexnb?~xbXC?grP;wvpPPTsM$VD?sydh3d2xJK>phZ6;=?-{oR#4l?ief)`Hx;ns zJzma8sr}#;{F|TLPXpQxGK+IeHY!a{G?nc#PY5zy#28x)OU*bD^UuApH^4mcoDZwz zUh+GFec2(}foDhw)Iv9#+=U+4{jN_s$7LpWkeL{jGo*;_8M7z;4p{TJkD*f>e9M*T z1QMGNw&0*5uwPs8%w=>7!(4o?fo$lYV%E3U#@GYFzFOu;-{Ts0`Sp1g0PPI_ec$xF zd1BpP!DZUBUJ$p^&pEyINuKZXQmexrV0hww?-0%NVpB80R5sMiec)m>^oV{S4E%us zn(z>anDpcWVNO~3& zrdL}9J$`}x4{=FZ?eJ<4U|@+b{~>MyM-FJCgKvS;ZJ>#*Su9OLHJZ0(t5AC`;$kWD z%_N}MZXBG2xYf#*_Z(>=crE*4l0JBua>;s8J9dfo#&%&)w8|=EC`0ywO7L0l>zDo~ zSk1&)d1%BFZwCV2s?_zwB=5`{-;9solZ)pu^4H6Q!#8|Mh26hJvKG8K$T2oIH2lD9 zSa;|Hv_3~>`yy6QSsN%hrm!+tp{**j{pe&fYcWg8S0z^Q$66BFdDg6)Br*)!n3T+f z7~s_8eK4HtrT|%K<&t_`(NsPW+(IQ1f3GA*0oO{eCE7J%-fGL;6Y~#&-N-r*DV!hA zvj}4FFW~Cd9z#EaR@nx`bW z48Tg|k5nzV-I*vIoC0a)@?_;DtZk(JY;n_LrA^uee{j#$h3}fNY*15` zl2wj>M{PmUHB3KRXBP2GWW|B7RZW({nuZJGN2O-u=#BA(@vG^ow3n$e7u=+dSJo%+ zF)UA%K8xA+r94&p-?FYx+LqfW)RrjSnFBj{B;6(5co4rV6V#XI75BFVh*?at%%o6j$5)u2|TE&BCB`euH0!jNz z5(Lf$;>D3VQP||uintqX8WPrn*?+)6mD`K=Txz+5gD>2GE zk!IdlA{A#%`Ll-BJj08U>fA!r6S02S^dX(izeGM4LcY>~g^U$)vw% zdV@b2g#?}*)+*iDWmOHR`-VCd(rD_1PSCs(b~8Qr69bhp8>?*1qdrRZCA|m@3{+tW zQyre2^zuuMI6PZ0R9!Ql_Aws+fjw68TGiR%jK(IzwVTEvUZ`9~SQ_RVJiVHHcO_mgr5 z9H|@8GY4tUvG3DNTjSb~kv-P$F03=Cz+u6nW_AlsxpZ4xg~w3!#g}`r_j0 z13GpvKRIs?B&h=op~7Uj?qKy19pd+{>E+8^0+v2g1$NZ-xTn zJ4$dp9pdQ7%qaPC?N<1@tQC+7uL#of)%e3l>Yx4D5#Cl6XQNp9h0XZDULW-sj`9-D z3CtoYO*jY0X-GVdAz1}9N%DcyYnA(fSSQO zK{a}k4~XXsiA^I#~52amxe4@gMu*wKLS>TvYXUagd*_35z z>6%E?8_dAs2hN;s-nHDRO?Cgg5)aebjwl7r`)r{!~?JECl!xiYr+P}B4Zwr zdOmbCd<-2k`nIs9F#}u;+-FE0a&2T;YbUu)1S^!r3)DNr(+8fvzuzy2oJlVtLnEdF zE8NQJ0W#O+F<$|RG3pNI1V1a*r_M&b`pi2HLJ)v|s;GTci%_ItdssFmUAmPi<9zLCJR60QB!W zv+(O(NpSnRy_Uh2#;ko|eWNWMk1Dhm7xV7q!=uPIT+hO2+2KU*-#)1itWE(L6tH&A zGhHP!cUcQA(;qKqZ^&S>%-90>_??#B3+tPkX!G+a94?X-R>fCt_^FaHOo%frkS`E> z@PzQMtrMaHn;1v>s}CYTJFn1=yizNIjcd;lN8@Psf;vOSZ3^4j^E;3BYS|daR6GP% z^m+F}lmIfj+sjDeLd`>m>78^3+?3Uo?btw;L#_{d!w9MvI&55j!1ZJGwz+UsAo^BQo?GdP^G*6=p&BL-`U1i#!DO>F=UztubL7A~l6wQKufoz!z|qq>)y!yvC?!cww9 zsN?(kvGVUGnGzaPX0c`^uk05P+fog+pTv9A0&jevIjlNrP}1MQHo{^-N^cJB22-tk z`5~#kg~Buvol0Nfve2_7ZDcNiqKt+#S);@IaC1w69Z4GR0lxxV6?~3BgH2>aAxTI|0-FcbzV01b9Ppiur#_!#Y zjY<41$oTWx?dbfsvix`{xE$*OVqrf=%ay$&4J}yK2<{S|6|=SC6bhJk)j_eLZgIEi zEH1*&%$`YPSzHsJoq@YFLK#k{s`2@fVD^0%vz1duXAirWESQ}jXjYU&FGAeY+S8Z2 z=+9u@YuUFbl143hX}wNPhCXJ!B#HSrK8x@|`}DD*d^;Da78#i{-F6YAN`mJfC4!D# z;kMqJXz_P<{=fWLnk0$BMypYBtXR*ZyGH|R5=mbzCY+&I@jo67#GS_jm?fkPa)JpGZ5&uc^>dPC^oW@oY zaxVTa-6P{GoTQU{yamt!qNk953k|$?n6XRjQ6J&~NxR62I1#X^`ouJ1I{CTcZLs2} z?+0J0*2mIcjoF!5`WU{kg?Z|={u^D|O4Rnl^q;H@6oUF3dJc>LjF~{sh;N`rA6WPt zHb_rKj|w)MHU2!G#dPNUu#jtTQ4h8b)$l;b5G|b@ZLNuO^Ld9#*1 zv{4vY`NUnYD>ZP)h&*VP*}32*8Gs(e!j9dqQ{O79-YjXdQcoX5&Kxj?GR!jcTiwo` zM^Tv$=7?5`1+bky_D01RwT5CYM5WdtrjeaD#APPq{&SQerwMYaizh?qH}rQPY`}7u zU`a4!?`Ti>a%$t5CQ2}!kkk?-}8_CjS|b3n7IoVIft*o$!U~yM&_@FToop( zr8!`nZ>CgUP{J8yVGll;5+l_$*8dv5a3(%}`Cr4!K>asPsi-7@@``vYC3 zS*?}cQYaIc>-n%KsKg|+;=iPZ0y0;4*RVUclP{uaNuEhQu(D_$dXZ0JMWRG$y+t4T zX708p?)DY%(m?5y?7zo;uYWGL zS&B^c=(JH19VlFfZg9~ADPAaCEpdKY8HSpVawMnVSdZ-f-tsvuzIq3D|JjG#RrNdhlof{loQVHL~Nt5_OJhCO6z)h z%}+h1yoKLmTolWBVht(^hv^z?fj|NiHL z`z6MU5+ow>A^*=^Ody9&G@-!;I-m-p^FzR*W6{h;G+VprFeqWF2;$D;64~ynHc7}K zcBdKPq}V;tH6Snzehvmlssi z8y{UmbEFNwe-Qg4C3P-ITAE>sRRpVrlLcJbJA83gcg020 zEylMTgg5^SQl#5eZsc$;s3=9ob<{>x$?FDG4P2FUi@L}k+=1)5MVe3Tb-CBoOax?` z+xlo{I%+m}4sRR$Mbz=`tvwPXe>JVe=-lMi1lE(hmAmWO>(;Ny&V9Jhda;wVi!GoC zr9%LJhlho2y$YF8WT0UvrCVb%#9jyNBHaHhHL~UyeILeAWAw^}i8$ltMr2Yp6{lvV zK9^=_@Plr%z5x2-QX1Anic_;-*AT8u%f@;5Q|x_-kS9$kbl9T;Fw3Wq_32zfcdGQ5 zsqsFFE{(;u!m_6vYVP3QUCZ>KRV8wyg@_%Ds`oA$S%wPo65gLLYhLnyP zhK{0!Ha52RV4CQ^+&a3%%Ob};CA+=XzwNEcPnc3ZouzDBxHb#WSWog z6vF+G-6b?>jfUO8f%*V2oSPN_!R6?kzr8|c+Fo*tt-C&MyzV zT>M65Pa)4#)7ao^6Jj_{`^jb;T@hb{neRGTuMwj~SD9U}q;=niF!g78n!Y0jEXRlT zrSw;qZiU2rtnnEMvN);}=q2Ww&2bA5PV9^W|0f30Zk7Ust-%Q#F!V~jy33y^($hsQ zh@n}s$T7sZUzn69tccDf-a;lg4UWYYI|2?*Lms2$ZW)GI-yaymOBZq!&aOm4 zg4iuvQM|}-y=U>fOaLFvu(`K}T5BANqjBpqrY+RxviWLz<wNld3Q zOBi{x%;Dka>Yc!KK(3mP@37jmo@Mz0cH(Rqg|+z2!Th&@QRP$Zlhz@#qUVwNe+&<| z*r@@F%Q4dEBnm;=G#@xvANE`CUE53}ZBNBrRuqYi#x%afta6su7&}a?a=G)rKmkK) zfjZ$n!{l&|aa2~)$69+Gbq!LA1^Pti_X2wMfoZ6VO{Rm1AT#$uuVZ(BazVh&l@OW- zT&hmX+Zb!T-c3!_KhLAl`Sd4aJnvwWL)ATcbxTo)LJ8GZ-c{m0EPu+zW~Ir!S2p^R z)7utF6qj3+BpAq8RU~RXZ#vwr6fQzM@c$4CPixQ3Z%q~(Alx$As{Y5{Cbp0;11^${C_}W!KX=~W!zReTO z?aa+Pn73jCR%p?&9s643`gJ$-OuXOBFgbk78U`PTq*5GyBOEGeW2FOdY!hji?{7H` zRjP4h^JZ8T0%?nBNA2PC9Cc=m(>G{}=##WMe%2j)u<5pldvt2csC#l0wc#&V%;cyk zWRp}bwR8iEi_c7JC-~eFiuoiUu+mE;l12%pk|UO09_2 z>eE1B&MK95QzvySEAf?itp=4n5RZtQ$!2{B1<9x*@cLWsfmJqMk*oh}fD%5O4^GCN z37Y83rWzv~4>w0jdKxzV49lPdpX1creItd8F$w=Lfu!az*ai2r-M*`MZH*OY?sCX@ z?U*kR}2ccC4KCV_h!awS%0cY($fD>sPlU`(3S4OKo!ffovsG`JkUc7-2 z+}NOCASI}n03S7Dz*1Nh^82}i7z7eqFyri!Um!##*VNy`%3$mPBlXn`ip9zHJE%}z zjt$;Rdq|?+3{hmT35bHJV`Xj#uR;re^f zVF>~hbu#vv>)49SP@HCVD>4wm#-7fGzH~Z-9-*WcYooVzz{or zHO^zLrYU#h5{)1kv@V6piPMn0s+=lG*1O{VbBXjx5ulO4{>LN16ph1ywnupD^sa3h z{9pWV8PrlGDV-}pwGz5rxpW)Z(q30FkGDvx1W6VP!)@%IFF_mSnV1O`ZQ$AS zV)FekW4=%FoffthfbITk2Cog9DeIOG7_#t?iBD)|IpeTaI7hjKs;ifz&LZkngi5Wr zq)SCWvFU4}GhS1suQ|iWl!Y^~AE{Q=B1LN-Yso3?Mq1awyiJKEQNP)DY_us6|1NE7 z@F1QJFadv}7N2~GY3Sm`2%flyD#nF-`4clNI)PeTwqS{Fc$tuL_Pdys03a zLfHbhkh#b2K=}JRhlBUBrTb(i5Ms{M31^PWk_L(CKf4i|xOFA=L1 z2SGxSA@2%mUXb(@mx-R_4nKMaa&=-!aEDk2@CjeWjUNVuFxPho4@zMH-fnRE*kiq| z7W?IE;$LX@ZJBKX5xaxurB-HUadHl%5+u|?J5D^3F-7gEyPIBZuNqHJhp&W_b9eBC zJ#)RQwBB6^@slM1%ggGG#<9WBa0k7#8Q-rdGsMQE@7z%_x3TZ;k?!c2MQ7u^jDu4ZI;T9Fnv^rB~;`xB+I-fZa&&=T>N@GuNZd-jiU%R`> zdg41iOzr9Z`rfOKj-A8r=gst5Bv@tY-j?$)^TPH6IGW1>FRrd?y9AsafFhfac5sfS z!z_v2h`^Y(y_>97r`7yy%gWc{J7hW2&B`p#p}HXCVi*^HJvp2-WzYKK^I4;72ymXKPRH?=UE&U!VZMv+EHmXG9J91O ztTxu>>##+KkI0EuT}Sq zm1AnDS6&3GWLaQSXKe1bcPXaJ;Cpn1(2ZpSgh-+t8pu7ACtHW-w z<%tjAl1TPw3()A?%a1aRDEusI&LO}cTlZJv#_Wah0tMU9+=ab6I>onMsi!pR?C8Qi5hBK zz~WZrR}JHGK$y_~ryEaJGbP-M9fs{8KKm|Oo5bMEcgeL%l-iZiSFYCuq@`3!w!#Yr zyuV`jA#slqYf5hz*}vq-Jjk;>@MVJEG$gD>268u)mQ?UX5_cq>+I9Gg=_XKP8SSI# zm9^(40#wZfS(o{m6fCDHa@iWB9K#B^&xd3Yd%)Z;i8n9=i54mA7VAyT<~E*Q{aT*% z>qGD?#Y6ot;FivJ6HSn$Px^aWo!iJ*j@fA8l#tVL{}|ZWe)`UXEmhPU<5(Wmr}hqO z5x8Si8g(bqEp+Rc$fq(aPVy$*?HhLEd5uAd1MD6Ghg$&DI5kDBsqMpF5gO+JmIpY3 z#vKA2w~URZy?*7nOwW>Fa^-6H1BJ1%*}Y?Wm4yL%!Ls>9fr5L9%(BKIDLKy%@Q+J- zK+!+kCvuSEn$lGSdns&>@c#nqJf7k*gglAyXSUIASL-C4oMoCYoJ4-@)SNK9mW)SsFda!>q`@Vq;j9o6kQcuH( z41;6DW{~4lbk1Ug=5gfQLld^uo+$*@YA}!bN}ekTEtA3B=6-ztZ9^KDzT#S7BUr#& zYXGhILp+T`lKFHBX7me|SCAm+5~iY87Hb=_z8oEE5o+W=4-*xQBPrada%)U72lD)Fm8Xpm0}{*^f>JwiSpjvoLD#q#n@nTuW!I4?JUPJ1AjXgc!au&1fu zo+XX`WjA*dTfSjj)_M5wrVFz?6r2)$`Hr){4FK{m7Eh1Mm<=PBV3=*yl_^UNfO z6)R`HRf7)be9|yAPbcC5(Q*gZm#o zt7hlICpCLq(o&n`0gy2Qnt->2DdUH$g*Zcp^05HspJd7idiX14g>j&@ROzf%K=6EGx<> z%L$cau&Jb&x^VE1z}9jo{_lJ$L1I59^a$x#uI>l4``?WWR>Z$t(*p+*j0#c^W}pw`7oI1R9MI?&A37S03`}wlOp_CBmD~javahP%)DcMTJMSDph`RPAvUaWgQo-L;&Ag)hZsl zl;s>Lq?@9lJI=cSo(K)Y^Z7{cQAo0GXA+zc0iwhzC07UV^X_0(CRx|h96VB!R3e+B z0g(jHwBdryOVB5jtt>yrYsRdLU-%G_vUv1JU>Z)CKUNy&7lyb#bDn&t{_KJx+H*i)ia<4j*Tru1+K zHg8V11BJ*|KFH>(B&-T&fc>~VYEE#1>W<%1amEqb;Cx7lTKzpD1Ltn_;l1=%z>2OyrQ=%ByoQnP`;Y zP?U`ye<0gnxlJ~8ulNd&7IC%B6y_+)3TZi+BD2+0PjA0V7J<>wYjxO#bM8kp!qfOy zZ|e$u8^hUt8J6Z7f`)!#Ad7Cn6ZiPSNC`GYMq>`S-JwwZ4Yn1-9@020LZ#Ya>i-!O zG4rl1X#e(NTK_Ll@f1`9D$6UP3#0f=U9z6nlhIReA4B4S;HWbZvC%~D$yp-$TofHH zY#aEAPIK0T!roE7epx6;AmQ^r7c6GL4F~y^UV2|GRmeQd{M!r#%Q-0PP0h?iJ~$&z zu~t|k=Z0ToUqw{Q!CW6zIo3)$LNne>AUO>iOLxu7h|lPtb?ci0s^Lm@2*(GP(TnK$ z3>M6F^KhG15qwqU{v2lBHD}#CPO2BP5c_EXSAb9-s^2dhkwi&j!H)bBF#=VWwXksQH>v4%Bsp=NgY>HV9E&8kcoFGVNHb7LbeNdKxm7L zkFWH_GKiz)r$?X%_ROX;8o)O;drZG+3b()@^9Kmi))@1!v=uxh7tia$+1mBk$+;48 z1V`@<9-9K>&np9#xsaOg` z>wl~mcXr=877@BzV*93nP^h^U0@UwC@K8%jIAe_IctQCA3zYNWWSLTET@9=gqXH{! z4ek8YxI1;`Wb)i>s(eY1M;?EaBqS)E?#sJmf#Y6jsG2G!^E73>AAgVPgi4f^yXsza zwq3<{qW`cY#YMU|8*oCt3z{IC1(Z?o%w3iV6}=*V=nx5*Po(u_^{%DqCLXU_6htol z={XfRa_S~F;4Zsw;6RSl-A(OGkDu48`uD*3(noV(L0!J@%sPptPL%FO^cKplLC;iq zTaTB<+O+D&*~2DrK6^u%XT})Jrc7>+Hj@xOlJlVxz4fy*1?b@Oi^8FG!bqlBH8o!n z>~F#%7}Poj%beNU1S&5x!B+k`Ca=z5lnsMj@seyz#H( zBmYWn0(6TaaS}moWyC)pJxlfy`-$oV7Oskdn!-)Yc;V#3KYe*_ZGMhVdQ0L9fyF4c z-wSiCOl=1PDWzMyw4}bo!6xYM|Aw?nLrCr0-s!v16Bb%Hvl_Espc#9hP&tv$`U6UJ zy^vaxzV#q$tN}oEh{kW^cVrO~8#|ojb2+G<0z_A%FyCY0<2yecnF&67?RhxR%0bwr zO1dvJ%fy*DkD7waZn&$Lz4m{SZpn@EBm`Cp(=5XLnY8jZbN*?W$|%bwS@18_msB5O z^ixjhgR#<2tP2uito2!ptSztQDEd+KV~yUAEvp{s`!dF3N-51kNJ)|L9zzB!N5})3 z2~gg%x^~{W$L4p;hMSn>=&!~jT53Mq?9VDefsY0g6wH<%_B|S_J#guV>7?S+x6XC>d?#MLnx+j~p-a?O2PWCkw%M$X&jl*xmluhFy(z79P;5Y|x!^O`&yOpw?&mCBxakmlR07DAM zRKSK)gruDZtjP-;Vx;=Gn^iT?OiB&G4uqX;G{a(>XF9;n%3+=X3NV{`kG@klzsL`M zWx^4-d7^~n9gOVl;0ud;e}}M95=h0L2^TQr*7uYZ8A1f9<+bLS;AnnuDu$&T@j{>!r3Ytg>hxTM*Uy13Vi)!1oH?iC1C2m=wdh8b%2p`n&3zYo) z4OH-=jYTC1udKOaeuVSp#60OwD!vyCRY{Fk?2`xa9NN<_w%%DGfe5?g#KahJyn6?%AwY{L&=pPJZj?FaEXqYa29=8TUx^^gTZ_L0x2tI&!QN-Jy^qVvtg z98&rSm50IM)&OVeW7$c1)yh7`RPp(`f~=Z@M9T;!`J~BnlcYPzzXHC$1~A>FOYZD0 z%s+A8EeGmXA&j-+NVD;*hLrAb&m><5a1r^wEEPV~O{9&oT&XQFn* zSI0G0vXOaD`|zKYld3NhDff?|p#EP1E+#Ds)cN0A_iy7vCxro14W*N*bVEc(xzAa- zk5s=`2rN1p*?bl0V%)uD+Ftm7=NY>NGnS2F@==Nz|2Rs6uAGisqqK*`^vm>*oga5o zpU*F+2*2pk%siXg+T#54m|R@cxqtYnacSIt+j5Phm^kYG!xNsLiDsJGkGY9Ql)DSIe$RC;4mV*-foNZg$JC$AX`+)tBlw zp|Eva!~!~Uny7m}0}x1LGd;$Um<|$JE9I3bq0FI3$RcDohUM`xy?b4HomEe&Cl_<# zct@|E6X^qCl>bnhX`;-G_mlO@;!$M$QYO$`P%=PtmK!j_hvOzNJ9*26h0+58UYc zChyB)J`r^Y>V3XqNQ?_W?_oRBY+@RYXAOZCAa-&H9>VfzCc%Ls&)0{~dXtWEQFS;qps^H_eaWb63T%Jmdq=132qfOJj; z^o!D$8dRA3XPaeB3}}qvc%-aXuob>UCE)F6P5ro3cb!#ay8C7=2MI0M<@Spslua!Y zfH*S;lhxG@Wof;QAa_?t7?03?HrKqeQ}NtxoW(0tgJ!6g%uz&UZQvZiZ*_<&^~U)- z!V4a&9U%vfoGl5RFBq{M(&r|a^e5(;xiFM2v(CV25AGXix*J<43);ewr!ap|`~|Q+ zS`#Wf2A!X__5S-QwC|AR<0n_t;F<7&+wb%%%ga`QI~+7ES{4qW)(xE-yUne2BLUGF zLiYE5v|w~x`RfrTF`QoXzl=h`?yvA4(EnqD8EIz(F#ixD{C@~ZmSX~H!g=bdV|+TW zB|h;G$gmZKoUwdtC5;IqG(~hz_Q#1&Af@26lr)YiCcPcwmxS+8ZxE$V%bPuiBw zA~$U}Fp1)kwt;jZ{+_Zrt|`kt6?#^q+=mSgS7BK4EI~GblcEW9r_8B)a7`JJwB^q| zcK7Y#Fg9o4uj(DCHB1$#9BF7z4>w?~jV#fHY63KA(IxJ2j(Mmn&r(orNO3#p;AHYD zr0%tDqJtl6piy77+VT@EB51Y9Jx!xv(Pp!}PR{}0+MzwL70welF?GrCu9oi_ExX6I zzE5m#Ssb>iJJJAY2>?_j^ogDOl;$*+)|Io4uK9LeP(BTp0I%^ga~6!?QHo=n;ywLd zrG-{s8x$%dWiW)gw7o*>c8sk4-_8q7BdA$`N}I~fC`~)ztO$y4!A`gXa0|ugSqk-_ z3A?SP(W1zbG54hBLZN|)<2|!d3)ra~joK(-lEa5y+08P57Aaw*;FsN-whG_mRCX_AxC%{gOp!hzWL&%q_W2e#Y<$R!6rv^!siuqhAa@0It`#*?lO zbBF~rIau~T>n$sgYaKlMkd8b@bvT6s>v*YIq!F@9D|}ZuJFIfX37Sb#-wB-92wI zp6&n&FXp-hxYAVVf@P!=P**GZyQ#!Mg3g+ z^51krxe`VAv-L}OC9J&}ndx%_-ek%vwpfAk&fgfw-Ao%jMm104avlW`Z}&9^IqCI{7K>-}u>Hat;!vgwmJ9T3l$o@^nn>Ua`9s;MQ`(w-+g10mim*e5 zxlQXo{h%Vfx^0A{E!?>xTlB>8Z04xGDa?68hp-sQOkWQA-p(Wt#tUIN5Q<&B(d-VC zRg|2etlG(wZ<_M+>&m!qCmX-I?*cH?hiINamr#w|+kms1= zgoZbkmpe<=OGI%2@TC1rTW9{Rdh;E04XjLu7mz3|*)|&vr>%cIXr=qr^(;p5Tr4cq zx0NKfuash^OEFWpuX;##)kymY2e|{J$a=>aPb$c4w17i_zbv{ZpOGz(M54{ezi!;9 zHIB&tIp_%n<7jaD7#Xe>KBw>dK#TFTAY2Yl`;4z{z9%(iYWd7mnlNG60du1ShP-Pe z!(8til%B7jxcdQBGwtER!)bJ%PrKecGyk(}=O{?a*>H0~2#-Hda;S~agxd^w)RrP| z_eSB2nJQ*b=B9MRJ&<*AhVI)$t|i|SSfeTia9LfKm%q%QJ=yZl62HQGHV0GO)k(to z@WU%$pv}3hE_O4iJ|V!;xI1&VhUgBuidgh)-y|J_!Z7=K17xIOM@Jvk*L@q18(BW9 zzKr?f)v;0v5A*&@dw`F|jeiDM$tJf&sCq+IE~56;tmN-J!qAj#0GupAa%ucNK)@p*ffr-`???~*)~kK<6qjrpyNjhUvc+9h;xo!t{&Y<( zKwnT7J*x=^wfL26KtPUTCO_!2eo=c+1{n*ZhtW*YmfIugMdvRDJ(W4|?~m&JCrB02 zV#==*`M>VgQbW1o8YGHr`TI5ZklZ>$J151Kj{Ar)%d5MMV?BQ`a%n$>OK}>{vo5EF zO=nnE~;1JIL)smt2q ztjvq09vBFtO5B2}3sjcZ+Hyg$!A24`+wyS|X($ZaA_(Wia@uR|N{khIjMoOGo^V0$ zkc*@h80LxC3EJT+qiD=>N;g0AF)H7~;8S8gJhhgZ{yzYFK!m^G*<`RVa9MvOxnsvT z);1kLd-DNon82oFXVW+?jvPSO(gWxz;?n&P|K?%~5+&)Ii4tzPa02~Fp`nP&I$2i{ z+q;X{c|j2at-d07tG|e$*4ju@^U|;{><`zDWB0z!30TR{m636{4@o8S=zWnRFV@L1 zghg^(Om8ePF2U(?)NqCz8?b*uj-CsGV3S0WM-<}KiRQUvVuB*TXl#nyiw&XSgLw5E z@@t)>_DJe6)J@>pq~MI>_4na=an3nXZ7t@Uc7(z^N#6nDEhAND(O8GK;H};U>}gt6 zOXGa0@@-P(!)QzPNctURy4Cj>8p8CWP2k34bmutURm3d|T8p?XOg?|QrHI>m_Cjqc z;{83*L-6gVuggLo*jdDfZ%2@HwTC`h#3w_a?iBJ}q5b3dY>51NFqv%ig(iyleCUfc z58yx%hg$uiFAMrBKBAK~p|2%~8TK=pR*HC%xJoiwv)Ui}b`jrOt z-if>AxS#wY#z(1s&!O=ts=8u)2G7dzIXo{%FBW}JU%-YJ1)$pq?~4R%72G3HJ&DUv zBO!hxu>=SR`!(=SvE;`CV&a)2h)>Fl6@-lJVoGlDUqijLlTCkOhv8!+Oi}&?R+V6M zD*_UvHwcuA!2YTn*iJ$Hrc8AS>UU+TTTp)}Q$2$E(@{VO@-I`Qe}O8zOzL;E*4Bic zPxwNAPxzyW+ORL7g#8IMl2}mNlvtoNCqjqAwfEu0eKH@ZWs-QU`8QBY2MFdV&OX@* z008C^002-+0|b-zI~J2vdKZ(=rv{U7Rw92<5IvUy-F~20QBYKLRVWGD4StXYi3v)9 zhZ;<4O?+x@cc`<1)9HN?md@n0AdG@AGW{87f)qA`jOzT7)=X3or+x%b=m&tCyN zz_P%*ikOEuZ)UCe0rdy#Oxt>hiFfjbkCdL(cBxB;>K*okOAZr+>eyo3Q z_N5oonjSfZFC)XvYVJ6)}Y z>+B`rX{x|n^`Fg`a5H1xDnmn|fGOM-n0(5Q&AXpMoKq$e8j2|KeV4rzOt1wk ze!OhyP@r)+S3lBd^ zM5~n>nC`mirk!hFQ_*2We~y@m&Wd0~q^qL3B4WjRqcI~LwGx52)oEfqX~s+=Wn#0( zNChH2X5>gJ6HiqHyNp=Mtgh(o4#bV#KvdA^sHuo9nU zqC1)}&15vujn$)OGKI6SzP9GdnzeyW^JvBEG-4*b-O3~*=B8-Oe`H#0CA(|8lSXIE ztUZ=AdV9@e?PmG8*ZyiXq6w9pOw(^LjvBQwBhg*Ez2gQml2*yhsz@8brWilV#JWs9a{#NSTpLGMetI9S^hKLmrx< zQz=blT5xe#m8LUIf5AbGP?jw*)BFiXjP8QCm&$aSK{J`=Oa`UWET&SB4OtOsOeiK# zG-0M|ckc{=&>ZsVG@Ir!dB*OjG@r?pws!AqnSj;;v<0+Kr_0D+h}NP~1yc#mY=@7; zA;!!+>R4@iXfZ9(X%Srkt8~G*8dVlp&4yEHIg{JGF#{iCe=4sGjW_H1W&1o-O#z*% zs0OyOIf+`ef@bXwBi#cdu3&P2A^1;ap%8hQ#=?WORdl6JD`_>8cjCTEbzmuN*&aEf z7l4QrV6UZhrL=~E;HHS1sdRPT8{~4EB|WXl?Al~y5}nP-q?J@@V_vB_vMOE6qzXp_ z2Oes$b=L?+f3A)uqUnv}bTi`89%`mdI@Qx=+a^1Vq?t&2s6`N{r>!>8HY09&C}gj- zg6M&o8;s;)jkd#kYI>6vA}bv=QyRSrd?n4^m?0uEnSx5!7CE;FC&fIVopuSc?Pgkf zX+)$rdj*r%+0kN)BNXJJeY8&O>}T?i$r6!R6!8#`e;bL;5b_NWQYQ3!5FSx!(>tWo z^>i4YbOE;E~MM*G! zqed{8f9u9f)J$u16e~>{9fyfieW|n=4+ukR^lGN5l1wHYjn#&tDWuNVLa25#?Y9B_ zIgjY`TV4KikLlmKr`2C+)^ykS15NQhvAZGOchrbw%w;ti-Gmc5%~T{A&FRNm%o%Q` zTLhoC=97Rty*`;V`Vhcxgm#UT;Du>Pfp+s*e;`!IG6=qj-mKFJx^1E^r4w|H(Wpvq zh4MxzY%x+j5LczQp(NN=O*Qn{tin-3g^;aAFOGXVy+b(3J0}prwo3m60i;6UQgbTD za@%OdVs<3}kvr+#I-R8VF!?Hr!`MFiKArBMQ=*WCCUBhtdB0A#)7?yUuM`Z68_X^% ze`$wvd!{3|uhIvZHdkK6X>IKF;~^#}H^yT?f?9IxP|wHd6Q%Sq>SwBcMXBsZd)i2Y{-^Ti7En~_)5w45X4=f-X_*iZ?4P0g zOX)s(0A(p5mkY~R&fh%rIeJjQeIEWAe>eI%Oq`TVZ_jyn(PRwbXDF-Fy)?k21Ogg8 z#1wc%LF&7}ZZ03GG$aDxQg!}_PG6u$A!8u0|N0FFt2BBHA8{j%%AE4hmjpLe^ktNW zRHh@9bMNxXmZI7Et8`94KaR|6B?_e7cZnt76-BiPjR(`ZiP=O>~;ax1%yRp}ZCk zeV4u`boG7V%Po_s^M?ZDN9b^^M13xeGc^?Rod1;DAJemf+y6m++gr{_g$;ug(&0tGfuRQyTEK+-?ap9P7( zAb+GSd(%TNibm#n`WuXe9sy}FuU-%RgYFla`KQ!6)Yuy{)94*uvd#N4e>jO@FiH2w zYyd+J1CXj1b4aO`XtQ#CfrlMJ!}qcnG$ft8Ihqrl9(IeK;$Bt@`&n5!RW8YOE+b9V z_<}IHv);p{?9o~0DMF!8^wpQ*9TT#_XnVoaQ5ARw(-oJ7qjDJ%LTFq;&K1}@xx9pD z@~nKSO4$ykjeLd3xxyi(+cRCByH-RI#e;eYI7Ocu^m^wp+^F-wSre>D^G?nt3o#p?tF z#)*YvN+%kEZX+fGzWI2>%vlSg#XOr;Kgyavo{6QSaB;ugdemsVQRfXJ;1=efIxREh zPgrSyA2t0(qR$2eWIej_NvG}I$OBu@_l7L%NTye13?g%ynm5(&4(&R$d1rl7sQJ+D z_U4_3wrp>0_HZ*=e>-mCO(TtSjcA-}WaG?R>;X0B8GUfgOG*Jy`c~d1Vj~2y=^P(OPz7>}GN5xN9VS3%^yE<#rgUR^vO6e-1FYrd#Ze%ERxlivZ>-MpnWc zrKXH7b9XYzv|y6koDtG@^1FqCF-}cMTlMXYEiJhgf!`-DP#7bWqqXTOjo%LsEWAW( zHB%|0+iZ$nw{r3{Rh$O+`4E3t=MOTbAlL3)n*wV!7K0DSHuR;1 z_suFse{+9>hd<7r5K2HXb!U1zk@G>Ja({!URiEN}1nytap4x_JcS|B|$^`Kl zAazO(M5d7B9^lUkoX=sWvPF`Cy*{t={d`(bkHj*m=uvs& zTOWx)g{?*cT0~fH80&jc2$)P5G5cmNW<`!bUA4`VqC@|W^Aja-%C9lapFH3euT&Y+ zM)IP;ROo5NLLx`4=w8umXj|bMI-ln!ZLg45IH(^518DAEhrh|+(n;l~Vbq#f;Xad-!{H-pBk=8bz0%L?>Y-(SH2UUdPZeca-AJOd^duIi`*HF=nJjD--LK ztwAJd!sGnC@~+L_nWyIOvXXwGcE2!yUt^3L)4+9oN6Lz2(xz?MpUO)`{+Z6tioQcj z7zs;cW!YeF_3$tGSE4rm+C}2uw1#UPf5hK;EI)NX-8)f9t+;JTc@xSQEG`?lmW}in ziG&$TNwYNCA1ePoFW>}_5ExeZ4;a9c$29(<&d-U0t_yA3U`&@+j=2^tMjzV$3;$K1 zz6d8yC;J3Zk&Y(A6Z=5=JO4xH=NZGt`u~R?tNaog8F}Z>7_(C5tHgC)tZy`Xf8cbv zAx1md&R*bQonKa{U>@1k1G9Fjih@*u&gw)h0!a1v616Brr4FL z;?UA`;j$}ISsGCMzf=6=hNQ4>P>g8mer zxF`1Ke%lCnl=qr+jW=Gu9O$bhV3%p#eROpIdS>&M>`)!Gk zWq;w%FOy))Y@jUFmAOhK$`=ZXh(6nB&Nm8*mv>NE^= z^7n{VGu>lBplgc|*gt{5SdvMzOWcXp+7v*0of6ckR9RneV^IjDDjSd_qlu%|5hS2> zMFz>qua*mjGUXcOT3y+we_%**MMSK5lt%bHjMc={JeoRV;%7Hg-jUnd^XIkc-&()Z zA5G+!$Cgh2(j}>-HJXBX$&DO~fDlnFMi)RlB#k+gemG-1yfXY zuI&0pr$4)N34M=F!g6-PK^UwyHX?~*sS|@_G9FEs{)q6yUQ{+Ie=eE%w;D-*SJI06 zBUY!`0ip9IJe+SUe{-EedtV}L93LZZhq(Q@2=ASOclfGP{HBXMfJ_-Vf&pTefI+<# zS2b;!c!!ykD@gG!Qe`Pce36F#Sm`F3au{!=L|VDmm8EG}D$mlqEL|QBWofB*S(a)~ zsn1jm(p3);;wRKk-n~OqA8xJ6Qqur!sSYi#%71Uee{J3!f8L#0+A~1mEFG}_LPKSWr%JM2c1K7M>uer-j${I4$xf#^noGzP&nuc_?!cD&qMS{rl8yBeuzHHbc)aU zT;lyS(_k&J#ZMP?pYT z>FJ=WfA~J^e@E`ui2dmsvh;&G0ay;uXKc`Nm-DcEdm>9e5lF{?^fQU%7f8-gP@n1^ z1>5l;{qioF1K?jvV0S;24$*JJ1N6UV13&|0P=nMye=SSTouZk7mUz$eHa(D|9V`)0 zB@*flKGzUEANG|T^1d)Yf6UTfv-EedcOF7#>0hU)EH9|d#)Yr>@NpsNa@A?&norHL za?gb`K3BQsJS-$F*QBUHO_J3L$lAitsI{r3z}98FAj_AB>$JORhM-r*i?Y0Q zZ~ySqJ}HV%b(CvD8r69?XKK0qd7m>J5Jy&dyM>_NeC=8LwL!c-$eZ_;amygL z;;eI2EOTe`Y~d*iSpnLm&jz$~>U^T)~olxCvGs5i81_ zRl$;gPxF-sN&!LWG(R>%3(hHtL8pRR$!Y#_IH>2TmH1pCA*G%tc15+Xq-qSIbA^O* zukI0=r}^tcd_ElVK~kTy8Y+D%%ioq+INU1Y+Oev&pIqEpeU93Pl)2#pAwbN_DhpbjkI-ddM|Jz4vN)?; zF`z6PR0248WtnniR#}7H(s0P(-Oyg9ti|%xSWvOByq)pYus5qTe@>`Pe=cuxQ~_-B z@bclf=lcOJrbnou!#*7^Z5aN`&UoVydKToDVq9 zs81@_IR~BR=_91tAM)>dm2Ow*UX|`6dWq^(s#>`Eied7Ke+Fq7jgnRr7GMH= zF`mP;sR+=Md7xpmRV9BE_lA& zI4Q}#Oe+L~f2Re*v_~jIA10k#@tDJ)NC8QAYpQOJ;Gg;`O zIE>`-WlCty7o|$4e~gGb0ZxKQLv9oY7XVRSXZ4z^Nz(kM;QKam2t7%p`8H)fFTcgV z+(x-=Cb^;Vb1FaYRQZMcZUZ`H0n5*e|2+r4Qc8x&U4Zj~jq_X{M4D-NjNTa+D=M-cednUESgQS3}zW!9}%Ytwo*z)e>a5nN@?WZh}Y;7mq<{) z?gDuvF>$hBVv)^++>9tuJZos1oFdj?e+NX{M@}*!a};{%1IFvY@w;I1dvFLESNaqv z-Urh@fOve0rqRuu+!to+4ayn?SQ>7)&X>^6tOG}-VROzgyWzN;K z+_{FTob^=gyp96SgH+>;P_6R>t#E#fRyzA>mGc3*()lA=?R=50a{i0zTuf_Ri)pPZ zK=2Pz^UisA!x zyaW`6iVE1Jh4K(}o1mg7_(a7Az7R!3MMUcVd`Z@{w1xhD>AC0o&UfD5Ip=%qwfi3e zaI9)qxc<^hH?4g~eXkX}$WDL7>m&8CzWS#6n427Q5|-zMzGKIO@tsPcN!bC0`4I2+LCnHz`8qU+IhZS7 zhbj0Qykl|r)Hf*+)f*43}A(bH^{EjO4^e($di*<7|p`0g`O54q~Z$UhSw9m z{%k=MS**fpk#-D?Z+0&-u|~o4+&onf$BBRySgUa4lo6aDMY}E{3Q1l%8D=CM<)$yu zjy*q!ldw*9Po{smPDZ!{u|B_as=^!^yS_K$CbFJ=w&e{3u_15WX$p&`PYDBW;f1tf zF+0PIT*;j5Z4lgahHYqgpT|3?y!09+c;pjJc$iSJ@HcxoEo1_EIl7#HU z*%Qh{*CiRxP8!%m&)I3->)L~ApG_@2>S|j_YOonwD$#$1b9u-6EGLmo+h@`bRzFjw zda8su4^feJJ}bo(3=M2!(hbT&f)$~5s#Ic-FGNoO7vOCSW1I!pqZPgRFvgfX3}aiu z%48^FLelC*s$io}Zdd=*PMhj78*r#hX;teQuvV{W?aC&DxJWG8jzsY~7OIGW)I^VJ z^$iTt{e6F~6mQ#$4JaHwWm*?Ykyx8XMuP0oT6-6D$ON$?Z|zQMHD1Kq+(d%uPVF)V znDUi&a?rb^gC`h^q9-(^tkDtgz&itYJKjao1Xn~noi?vw`PRubH>D?O-j2SH&ikjH`3}2l6wqlUA$Ol>P*}$HK<2w)-4L5X*n6Vjh>;%AU-GL zpT&Re3`0Jfbt9cODKErVdvK>@!snT4rO6n?7p0YK$6agyp1Z!Qt-ZZiKff#`%*9ve zKaLYl-z6K|ovDOt#oG$Aio%*HZrPhDwfEp&(dMg6=xplk&R~bk3DYI?K{I%8FLH8l zm}PZ5U}Vt3A>*`NF?%q7=kCk*pL{7E&D($R0N0u``tq50h)CLI!QR1YQ$Ky%DPE=^ zzJ^DH%h&0RqE@G7`}*v(9p7YIy7hgNQ7i7Xrv|fy%2eFmUu>HNgGxvYd~1rZ>7Mjh z0FUC^3gufiZw#+B@m+<+al#TF({{D*1#kf0my&kySYD;V{tp7!had97kW0LSLu7vt zPl?O+;YSo3OSl=X{6yx8efVkd#%eJo9{>4-jm-mTcV~VS`~{uT=4KP|x|HkH^-1Nb zky-jZe^UD7bA#!ZgWZ}GbTeuHNx%@W0;G2<-p z2f2BFR8Y+({!Dk!Nf|d4p^|@*zGr`Xh4vK0U&TGY#NVizn`usQ$}#bGjt!D>X_xwY ztf5D}sbPka|AChR?1TR-*8F@KlN&+z{aeAerR!ivEZO79|KOEMyo~=+wC8rXJK1~q zq8JxlN?#_&<_(m`}UVE04Vo5)=)QYwNE8S&ZoV9;bF=PfjXnPr5~^sRiLD1XZn?FO&;-(O$Q0sF1k8a=eYw zFF5hF2i2i!aX>9n9Ian^0 zvn*w*qu4z9^sd5*QzXpRX_I&&V@hsN%gI|c@|KLBX-{!8ogMV-`1oa2O(i2#`&lI$ z&7$4f3Bw1kGRuOYRmxTx;P^hj&dE@pI=(EOcpck`-fK411_r8)&uuEvdW8?Ra!!V{8Rc{5$)gP*3>F|CY#Q>prXinq0DPpc!6AH> zZzR^p^A&_k8l&5`h069~{))X=*t8dm!h5keRK6EWhH=C_kiU7T$C3GS=5op;cmK7G zqgWR0XdJ@A9F~t_MYOSJ7)=^onZvQwt^Ak6@xwTA2#az!WjBA;tjM8lH=227K7Wg% zIcyw3NA%1goD=QbkBUA1IVRTR6b_Z;kPVgRu zU`P}jp&5Jd+wR)Rid*r$kZ}NyHEF77#L(;vac~X~ig$k>E^_=v#2nR9LuM!tE`%bS zr(9V=$vDsA4kj_eikw##vXKv!zx3v@NiSK zXpzxV{R}M{!S8eUQ}uHP%_{DjJ=M=^i(fdnr6NXIt65v=dt0=%@@92Ht$F=x-Nh8( zZ?R@}cS(ODs4CfxM#?0>)h~|VU-#nG9Ftf1a;joCV~3}-&E?@5WzsO!IjREDiU)CV zG#V=JiTZ0)u&b;_&F(61t;nf)wG};G!|ITnTFA7?sU^FS5l3{28zM%COZC-{_t0lg zgbX@jR4paluv$iU{+I;&(GaSrQAbD2vIk*ABb9&tkkLhVSLW0T2J`98J($biB4M;7sqLVLmW{BejNuid<>6k_%jYf z0%d=M5%@0+SLG=utRu`+QG`w0}qv5sc z1`TgiBN{%Sp3v|K^`v?hP(M;X)%dgOIf1@weAoGBs}>CdD(t(_cZ`1^Q z^1ZBafr9_nU!ie<#QoL&1%hix96t3Hmfb5+_dlF#V3~o=S1@~wb6>zfxn4M3|9AEO z?FNS%1&pzZPfNfWjtavVV~wAd#=zyIdJS_8T%pwBG4_h8>G_dJWcp{~XK1y|nMi*= zu1SucS@ZJ^+&_jZrzLVpM1`InL)r8+2KH&HUy5NfP(7_RI(cS|#@IC9AR4F1Zl0hs zPbRBz7$vLw3Wqt+aPKIFsJMsx4i#46Hbb?%3O}jDnd3CvDo{ZJTe{IQzEM`XAui8v zyo@8p*rChVrwfD}DdoE}pGpTe6!mH5+k27t7-w)C=qBA(?q5hhUdCbI3etUyirv8$ z|0)7%J*w0O1XVv~sU&9m)?tosGv@j(z&u|J)xLhz_%6jE{w~z|FT{L*91Hvo7Wxwi z`3JQezaBgM{|8V@2MF_%Q9{HF006QWlkqzolT>;|e_B^->*2<`Rq)hx@kmkeMi2!> zP!POKx6^Gjdm!1?3$YL4TX-RY7e0UwCC*kwLlJ}3-Hvn6h6?p9RF6#Gg zLk71LH{D$~Xt^~vNTO6}nW-f9qNGWz8`2~#@n&0EFKAP6Ydev3cUw|hs<~5z*XmxAy6(dWgh1&s z>6n0ylqP}2#DsomWK)xWXJnd^@lRr#Nv#*Y^I?9mA_fH}Z)8{cTE?M&-ngM4D`J@a zzQ&J}i2Wu``;1Eb+<%XSmQ=c9=!~qDArsZpZeN$nEWa&N!}}^$*@3|P(qDuB@bZ;F zVQKlwfrE(>iYPl6!RRQ4P;pSgSYAyD3?A|;p~6j(e`bIyrnsu)3}?aNV4T+(?&eV7 z0Lm-Z*Dsh{eMYtRjOiz!j~4nCg-=jR2MDI8gO6$f008Hc@H-uoBYZD^3w&GWRX?94 z`N}uS!*=Y%c{I0n+{lt;=dswS(wFU|tz+fsJfgBf1?)j2Ma2b}nT%Mu+sIZL~IKh9fCG6ERuFKu5=>#OAG7o84C0Ka@)* zF<_7Akxl3t>0vW%7+EttjL|bj*2Y;F-`2LJZChl}IMet6KM6s9YQL4sCX74Hq#f`kHr03aTWQfK0tn|;;)qfQfU!?t%5ssxoiE# zjT;3G&wIh5L$}AIGfk_V4=eVhYx^BW&Gwe-Y+he%dl;sF?Au|(=}GD~0ACwyDU&4! zw+HA3TE|w<1O>{ERj3gTG0vH`V@rb_4bXaOR;h_@ngKUgCxwE7>f~t7F_Y~*Rx$|` z0@=1gAwg9}D&vgCAWcwBNe{V_$Dl?lMN|q?8R`*UnbruJ3l^qSx&F+PwxS&1=^w$Mrv*TzxU;Gxj zmG=XgOJ*vr&>eyl)85Iq3s5&TFQP8$5p?fe(mUE97G=$W99u%$&}?te1}($Z(w3to zthA$>X-!X$VwtOxY1nPr&T|=bj6uz@v>`J+s2S&f^n{Zf)izD78*TH`PWWfY%BFOf z^yc7PlpLGqE^}7}=q|cjr55THwBd(@l|p@jnu6~MQyF8sRf^FbL0;Ru-;hY^4bVQ? z&xSgHP+!ncMf=z=gQcbZuU0yUBM}1Z+uoMB775T{I>M^FAM29lfS-;sBA{=}JjUp@ zEC*_T>Y3e8tl!bIpo;aI6uL*H6O68wnKnu5Ddr1@S!W&?-^(ZIf_A+(R`_^5%U7L3 zjW*9N+&3Yp9y!Gv8ZB{RPcdN$+By$P-rI=)c>mp9k{4|VIBA3`kB9}Ft(e~Zo zG|=DsH7q@d4J%*nS3p#1~@T7d+O@kUU4DDxIbK5mmX&pzc6-1yjAf zEcQp}1FX@5C2{gL2S>8jS$%-H@}IfL>-I0-D)9iWHl$5_aJ zkC(1hW|HolnH=O?@{=k(!bqx~UeSw$B=gKq!M2Wdw{gzhGY8UB5&bjt5tV+LewGUW zR2$AnfIde1ImkbbA;wY~7he{lLp>FsrpAv2rOoDto@kD+ZS-`qc!Zs?or#an~aNv-#VXZiE*tAVY8*!YB9c?dCWE-<(u~42a zk=vQETsD%bPff6QtReWy#0lkp<^!?!4!PDEU_fa(8|Klq1TKl|mM?A9Y{QUF(M-o? zYo9RzKycu%piZ5}+JRi!F;fOAI3vUR6#BJUnSMsT`ix4?(eo%nT=1b`cn6eI0$eiYO&qsrQu&ZUg3bUT!rq%ZLL-Y>7g@gHXe3XSbC#b|#G! zq#`nZm&=v~kWUPRx$&sm%H%`aNF$3Nq3ht#?ArQH8z?jS8oIz1?zE+`GZ-VUroAyTZ}L>ehtN|tq(~?U|E80`k^=rO8yc3u}XhPf5IoD4y;U_ zM)iQZ{<%vze*vB>IiWi@G{i)(H|LaPlD`tPvfNEGXa8EI*V!)()1EC~P{iEdsPr2B zEvieII;Um@wFhJKo33=3nRyNOd4s;muKhcBWxfLy`g_3bEYdE24E~Rt)&7CL%|9RJ zT}WE0gd$T!GC-fBD~!;8DbJ#N%L3_N@e=5Q1PKJ? zf58X~KI#;DhwCqEI6(iy5%}NqePoXVU=yY(KNX-DY*Q>00(cz*Di4VY45I|bBiV2g zBMZe(+Hl$r9q5&R@v|6G_JLK?j{B}&7HpYSn2AcE!1Kb-?gtiqZ5h;gez6D`+fhcv zez6$E&~@ITidYJCGb|5fQ5M}0oTbgoZa`Fv8dWS4wX+iLf~9*|!WDHexu`Ea;fgX9 zu@dS#)}aHjvWvQtF&wx`tX4&XSTl25Oc6H#iAYVH>C*0hBMyW*Yyb2dBx&MCRjdi`xeXzJ9Ahx?xx1cr* zE*RS4HePc(oH;DdaB%OKTi}T<6nL2Ip7AzEg=#PmcL4aPwHfyA&}`0jN8!mk#a*h{ zDelGw)8@)Eo6TiV9R$QK5F%#!e8m5j5#c1{+~F*LVv?W2MtaVlfM!R;`W?oQo=ZBV z{=Qk;asFPhkL|dB=HF!gw}KSWkJMHwobXU{a(2%ME^5evf7dSd#vyT76$ix;(8d&O z`Yj}slHaC@PQ*c8Q}xqX-PX)$)3o`;F_qq;=b<a&fg1oZw`FGF?2%YnMlNbOt z$_Ye&)^C0RjcSTjX;gFEleM5<3~_}%Pkmn=_9Gnj;1*BHZt;uLfU*viPO9F%t2m*3Ls{tjXk;4fRU9WRE=by!22G2`KbzD)%+JO*#>Aa zS_QCJLQ6@A40;=|-ivm1D1LmLYOc`oc;7gG)rDT572y}Cq4fn?eM!Qpiq_Ctca!)M zwp5~B6b|L-#v^&!aFNsrYVRAP+rxR<67PGND#r@n4PBwmcx;@uUAxWG;jQzoeVW#W z>b#rdQD2_6Um!KyfREdcocD^c!W-ef(2ImPxImisDkbp`mQ z0wXbaBnt&XaCjv)?!)K^gq?x6J_4~%U~~-Y-T*M(!kz-wRgpnMMX&NaL+2~4FO&CD z&Bz3$_gtY&Jn9XPlU==xKJSnE8ocbX2jU%-Pf$&y!RM)~%+m+Q;BNYOU1i08lkE4` zBMsg>ozK%xVE-f7KTeN&I(&7$$hD`bEmG&(QcZ;iC+MT`C^kO^gD-0EF58%=Pac7I z3_X72ybp-@S}V(WGQKBIPhWsa;dq{&0otC8DeRT_@u=4m>i35GeXaeKk^Y)rZScA- zdM*wJ{raTTViFdpqg60D0l`gwvTecd)+vX5j8xydRIkt}g)$1|3bc|Wg`!JBp@#}= zURd09;?z30>uvHEAic6|GN&Nm2{jUTiw-VMLf|9p(!}gGb2~kH#0y%=_1;+1s&#i01u<{y)d?>tTGY~&PFJ2^npXa&r6|m_y zvGSScuv5spFDB3TsYao3vGQ$*tm1mI2#05jO!D*9;vXU*;G+kB{FM z2(MS;d-yP*B$B5;n4mwELH1`CXerzOFOQ5BzB)$7S|eBJHD398oIx~BUvKb@(>L<; zt*E!!I}2Km)6x>OzB5*T_;w^-#M7JjKUVlqUkE3?IoX=0f4am!lVCFySLv2UTQ1ub zq{+6Cnq?cL4%yyJx5;)V?UHSb_R97E9hdEKIthal=?DvMN63=uee1Eugg1&nxz9$sFObr}{;gdE0K2G05_#nV) z{u4i~#qYQAgE-66yTzrElPGa{t?*1uP2w;DBr3rjE_T2%cPi*r3$O6G$9oNJJnL)&cya?5b){}X$`LgK9i>Um)H81Xn z`l^G#-tN5U>F`!{`l~wC24AZLVE|m_Oo-mRh+U+6>(zRHe_i0=eP>fqJ#h`|x8IX+@--2aQhuWpMyQ^=e+czd>pB)Zx0{VF{gTr+=*QR9}M<^^TEU zY@=7`t$3|CJ}&N=3^ynZzQ|>9qE_6C>z7cEl;sbzsX{Pk;>aZ=+O2)OjqL`z)(Qg_ z1$BxQwPF~5pAmV*Q?(-LS~@f?tjTi8FOi?4?RC>{$E%%?L&&WQv+<%@f$v(H-e~~6-pIh#~L|>MDZn^&r z`j+f-%YD2tWuII0g$Hji^kvKaR#fcV=a%~k@tD+q(+$h-(UJm=Qe}8GF*l=d(nR&OQ{7OL_2E=Vm2~MJX9`-SZSXeEFD}Wr5B5U8nD2AgzO2JB1RsOKwrp| zQ9+&%9{^BG2MBjW_x58D003kklkqzolXHtTe}Te6DU?D%5Kvqd+tTd+0E=b=XuYWoSE;xzkUO- ziY11l!^7w0w`!dmd%|s~>#DJ%7FEM@e9PvM<++;UH3aE_umukVEjD?m8BJmAg|QQ= zf9pHk4n|^y zT)JB-YYlOrz8e5zNY=bKFvKIv77Wu~VCrVT8@AA22i*5XpjSQ96oG;S!{{zQ;JVFS zQ-50D6-K0>pCNmuJ|x0z@VYG&3^4TVf5(=H7}z#L|9#7~q6Z9#+;)D8p*NS`N+E@j zBow4mNMdLZeaO&??U@V{x$2p3Et31FNbXz>wKriT90e1^croRfXd#xTKco1FD8Zdd z3Rf^Sh)GN{jCTl7FvFnuQn1|==8#Qd7T2g`ezF~grSr9HG}8hQOQ?3e{H_P zpkIdkQ{+5UnfE5cN>_GsvuncT%b^Y_7i7vi)cD*+SLdm}YaI*<(qNIgxCMQd(>>{iBFSw8J6KV=ooCr>Y&{ zbUK#D6MxFu;BS6WYE8f;!W)xC6Dxygm5GV2(K>pIcrZE{1zv<}{@ez}p!1NGR^qkN z$lx%uu^(FzY4jhh$aA#*ohXt^=P(U5+7{Fq>@USy_*$6QzYUitixxB)G|!b$#RY?d z{>@K7Wq!5w?7th#8PxiNc^BHy=|Bs17}T%m3o6iq2HC0@oi=P!-zC>0t&uj4-k|&X z8>qk*)V={wO9u$HjWB8?0RRAMlkhtolZKB&e-2P4PC`p5lv2gUpcq0zq!*0Pi!D;Y z2B-v!sTZ6~PLhGi%y?!7%2K=92Y*ESppSj+Q_{*>_Q5yb{SE#GUyS<2}pIOwBWFD^<0NoaBO= ze_V4pDJzw?!{iKcTa?pfp%qP@-V~bS zaFM<%YAoUf2mpJ^kQL+>z;y6hBIaE<+fapSDT&;7vkB# z+OX3SW@=>T=zE5lp4XfyhDfVkfy&TnxI1aJ$4Bl*5J8uUFitY`HGQXT)1=5$o2#Ik zA;hbWw?&8yr{jl%M9_mXDo&%9p|`1O=BeN;g}rK6hIc&(doO}>7*NrV^9=p1e;LkM zj_>6>!L_P_H)OO!1qQBfsu;uth7Qx#iVWwPMlJqe5_&yvkb4f ze!<;Mp)WpnY!08`j^c}0f;a2U(H!(9PtC~579LsrF zLUeP0&xd)~lsq;NIVi^14|c^ac}6=}p5!k~Q2%v}7lsErGUTnvA$f5&XasePPJ_sg z6hwO2?$YipnbOVRboPAd-8-(a?jjcxrEaP=73lUf=x_LpwkWxrOtgUq2iuJf27CDI z$Zo!&;JFpGF;C}KyUq56H9w}UsDoGCm~uO-bmp~{q}<>S6#vc^sy<<)K_NX?&~$+# zSpV|%XBcFILUM~0EhMqI6MYf0HD`iqU8Mrn0^)^REIRsgKJYE%DE&TzM-V{|BR5(o-FtXIUIdAvAp_2i%4*$iNCzjVTipiOx8IZ6E?+t$V#^sGm;;^uj zWpcCr=t@o85&cLcr`~n_G8R`gHLdoW15WR=V+IriwkY!f;}gQ}^mt6qnyH>1LFMr-$to}%T!%YB^nUi- zk0IWBMZdM27T5(8(V^vBtn5beZtk-T#2}wu zwXtVIXPL+5JVO?DGbgg&?X3UmF$bNGGNs6smHpPp;+AyU>&)@kzIGhdER2 zUn9LuaFny*!&Q#r0h*&$wdn@Z|^T$|5vZPCZGYKVMbd-*A-OTE2$aT zvElV9QO9#Wb-!~c>Ro$^i1^IP>tk_F$`b2aCqAlbefKEalH)n0E_>0zY@?%Kd8!Vb z)eh6~UhMYI;pL5&H(fQ*-vU?Ogn$gF!R_& zG*`?yg&5hECwPSDBgezFU0OYchl>aZ_O#1As$3DLs?6DVQ{+Bgf)qXOt?i!a-QsZ%Qyak$I+*LVKW3LN868lw&Abn1?M8woaWLO$jR z$1o+N+loH#L^Er>=GCPgsT1^R0=X}s#h!PvnZFcfc zPt^$bFspHAPSw5*d+fTlT0DcKG-OCmeGp&5%#xVc(qXh_!{LV4Fy&pGr2278^s7Hd zG0OA~n))|Zn3$VO=t^_#qRjpIIm&kCB^Mks z5%5*{`o~*6j@yuj;WK9LU!7(f7@qD&a9f}U_ezFf?*k~2TwalyDA{Me7+?!XX85W8~2Gkn7tkMi(Y#9wua=HjEN6b!4F;~fq2 zN+=n_OYt$sP&~H8bAIx}a8=fAeC)y3XSNNE)@wvGrmw_A2?_6(5dH4Ay$$3eKnpls zQ9p2NjNR;IS2XA*j@uavp?DKu^d$E794+V23Ft`Vk@33@+vnrt10H+~EM|8CvEjZ0 zsbjngycb@L8_MfVT`Xnnuk>x^`U%`CUB!Uzxi*3x3TY=eP}a67_st`3LM%MRB2@IF z--lqT%Cn#eoc*(yV-@o_=s>T9rI^|8Sn#Mxp@^^<0&VtemQx&)8jQ7o21p%?cZhY= z2$L+PviXU>b&m1-87KE7;kWh`u#fdL$UD*xi>MUO^=5ux-13*`xP76LtA@2zUB^ms zSP{pq)Oc4=?5KT7jGFsk9qwwUux!x@N8#C3{jzMRcrJ}`@d6sRivaGYm`CCXmL6|fuFcBWxDev6Dq94<*BsW}T zUkMa>wwY(#q>&x))jD6u=f}0nXH*SBq(iHCV2gJ)&{Y3)R1aG6HdSi6xrrL+dp_=o zTnPHdBA;++kh;9JI$dVv-Z^nm2UM>VT`TKi3#7P}DGpQ3hHyot_%Ga5v(0Q0Xw^BQ zrB9sE+=kH-nx;d_Bwn5&zP(`iND^1RUcgx6*Ieq^p5Ygbprub6b$UW5=&;iph_RJX zv<=!^MO&MGLRP?LAeXM#O}yx{*)e_8fczM2xhtfJUEEenScK&7Hm`>;^Z!hT>)+_| zotD^E!|*`-9xk8Mw9oTqyVn;=CubXG)F|FKXuGWzYg<+^{7hV|$;^Yn&0ElR`rJL} z@vE~it;yE0dG*)jM%UBw6e>Tu^*xu9&HUkCUX1ntJ{WCAJasOvA3ufatZs5*DI-p- zxNA`D)n(2siM^MSVtP0)tHIk@)Xyyz(ho#&Rr)o@W(78Dad7&wf4-@MOtE?N z?#5=EP9XfsK%DG|mFk0QoA#XR{LtbZ@XFbt-?!L<9(NTEGPBG}T`ZcX-L#^jM zq2;S+?;XXN4s!~p7D#pnf~~zMgH`2|dUL}P=UuB`{<@O=I98hMSI++L66r4FY2r<< z%0Bf0xHUihoNG6;)RcCV(`@{S-4gawQv?%S?=6Wh<;jH!587HZv1BDpGAo@Ha#KkB zjix+Lg`FvSr!`ja1%F;iIbo1XspRa=d+)|5G{2lHURUXkxe35IPELIvv7a zc|*l*t#Q=As}vi>RC7aRxdsm%)g@4h`#6*)7T$V$Dlxt=ej+c%c-+ArC9|ex{2@7| zu4c+$vYSIihTmODqeJ{JH$%> z-CFQ!lh+{2vP;+tewX9brpOL9Ne7)_0gn)ROwklwW4VTNQqE#prrjg3HjNst&{(RS| zGk*}mpX;P2#HZfT)Hx8EbQ~u0Zdek{Znhq#>yfJt;^%*@YT~1O1FKn5tErRueVR-L@n%;Fhr|EP^GW)F`mDjn z=f0ShV<4J&+CF9AoFQJ zAblnPmu*LPX`s(O6$An`00LxqfK$b-aNX%sw zpzWo1N+A9djuA~ekCB0ytR#>%SDb(3=lj+RM5vxPT~s84Fn~p_xj;(RQ+jKn06+}e zhLfE?!%Y+s1X%=LHV4X#WPK~b_KXgOb1;2;_b{P*DdDF8YJI?#iBmj46lRX{+Svix3yprmvW z;urmpc*u~|x~H*62?NkVap+;Z!rxsq(F6gka7~idft^3G?K)&yFSPe4J|I;~fiw&U zF7QP16d5_83uqVFK}lZZ#3mgj0&-*k3;_aa^iGlr9(pSOT~O3;kKzR6iw&WNzOo>Y z5}DTG=|2=5;9)FG()?c!GGQ{>&g>5j2KY+^srL=5v`V-r2#k#CzWIj&1J}a%NtF+GV?iJxGCC#V z4^0cKl?p-+x6(i$K{C=TX`hV4l76?)gN-9%3&=0^U0|OSNDv@ZKU^AuK(b_-5vluR tb|UG5rrMiG19Iiulsp;xC-#?+`!a`jC=f`JOy*MdA6k~?a^c>+=|A-;lequ@ delta 35551 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 002b867c..2a84e188 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a93..ef07e016 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License.