From d4e1f9b7f182dce311c07b0c0ab647e639c04d34 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 6 Apr 2024 13:55:40 +0200 Subject: [PATCH 01/16] 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 -> { -- 2.51.0 From 419beee29a495fef141f87c95185407d42cf495f Mon Sep 17 00:00:00 2001 From: AsamK Date: Sat, 6 Apr 2024 13:56:25 +0200 Subject: [PATCH 02/16] 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 03/16] 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."); -- 2.51.0 From e0cd5b987e151222c7486c14d5cebeebc2ac7740 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 12 Apr 2024 16:57:20 +0200 Subject: [PATCH 04/16] 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 05/16] Add aci,pni to API RecipientAddress --- .../signal/manager/api/RecipientAddress.java | 49 ++++++++++--------- .../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, 56 insertions(+), 50 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(String e164) { + this(null, null, e164, null); } - public RecipientAddress(UUID uuid, String e164, String username) { - this(Optional.ofNullable(uuid), Optional.ofNullable(e164), Optional.ofNullable(username)); + public RecipientAddress(UUID uuid) { + this(uuid.toString(), null, null, null); } - public RecipientAddress(SignalServiceAddress address) { - this(Optional.of(address.getServiceId().getRawUuid()), address.getNumber(), Optional.empty()); + public RecipientAddress(String aci, String pni, String e164, String username) { + this(Optional.ofNullable(aci), + Optional.ofNullable(pni), + Optional.ofNullable(e164), + Optional.ofNullable(username)); } - public RecipientAddress(UUID uuid) { - this(Optional.of(uuid), Optional.empty(), Optional.empty()); + 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(); -- 2.51.0 From 71de8e63cce8633b020ee49ed62baf0cf838ca54 Mon Sep 17 00:00:00 2001 From: AsamK Date: Mon, 15 Apr 2024 19:23:41 +0200 Subject: [PATCH 06/16] 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); } -- 2.51.0 From 7e0d4c9b8944b43f140e3b5dbf0ec5c33f19b92d Mon Sep 17 00:00:00 2001 From: AsamK Date: Tue, 16 Apr 2024 21:55:50 +0200 Subject: [PATCH 07/16] 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,36 @@ 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(); + } catch (SQLException e) { + throw new RuntimeException("Failed update recipient store", e); + } + } + + 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(); @@ -915,6 +942,21 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } } + 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); - } } -- 2.51.0 From 8aeaf927e61b3eb2d0e2ac5cddf3a1ffa4ea98ff Mon Sep 17 00:00:00 2001 From: AsamK Date: Wed, 17 Apr 2024 20:50:03 +0200 Subject: [PATCH 08/16] 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 09/16] 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 + ) {} +} -- 2.51.0 From c1775913b9c54c84b45154aa841a0ba475c7c9fd Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 17:07:18 +0200 Subject: [PATCH 10/16] 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), -- 2.51.0 From cef83d962cf5415e01fd65d4e602aaa42850d517 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 17:07:29 +0200 Subject: [PATCH 11/16] 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); } } -- 2.51.0 From 0a82c51b7937df9195501e61266d28756472420b Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 17:13:25 +0200 Subject: [PATCH 12/16] 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; -- 2.51.0 From f0054372b89c2a8372c1cc7b78bad559db092375 Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 17:25:43 +0200 Subject: [PATCH 13/16] 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, -- 2.51.0 From c9f2cca0241f93327ebc77aee895bccc29d3d52e Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:21:07 +0200 Subject: [PATCH 14/16] 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 -- 2.51.0 From 7060faf5d39f79883bfe0e740ad5b58d6681dbbc Mon Sep 17 00:00:00 2001 From: AsamK Date: Fri, 19 Apr 2024 19:25:27 +0200 Subject: [PATCH 15/16] 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 -- 2.51.0 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 16/16] 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", -- 2.51.0