From 76fe6ad7999dee19ddbe80f518c62c4685ef95b4 Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 28 Jan 2024 17:57:40 +0100 Subject: [PATCH 1/1] Recreate recipient database with aci column --- graalvm-config-dir/reflect-config.json | 9 +- graalvm-config-dir/resource-config.json | 2 +- .../org/asamk/signal/manager/api/Contact.java | 40 +++- .../manager/storage/AccountDatabase.java | 171 ++++++++++++------ .../signal/manager/storage/SignalAccount.java | 17 +- .../storage/groups/LegacyGroupStore.java | 2 +- .../storage/profiles/LegacyProfileStore.java | 4 +- .../recipients/LegacyRecipientStore.java | 2 +- .../recipients/LegacyRecipientStore2.java | 6 +- .../recipients/MergeRecipientHelper.java | 25 +-- .../storage/recipients/RecipientAddress.java | 115 ++++++------ .../storage/recipients/RecipientStore.java | 143 ++++++++------- .../syncStorage/ContactRecordProcessor.java | 20 +- .../syncStorage/StorageSyncModels.java | 8 +- .../syncStorage/StorageSyncValidations.java | 5 +- .../recipients/MergeRecipientHelperTest.java | 32 ++-- .../asamk/signal/dbus/DbusManagerImpl.java | 13 +- 17 files changed, 375 insertions(+), 239 deletions(-) diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index ea6babe4..186f507f 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -2619,7 +2619,7 @@ "allDeclaredFields":true, "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true, - "methods":[{"name":"","parameterTypes":["java.lang.String"] }, {"name":"getUsernameLinkEncryptedValue","parameterTypes":[] }] + "methods":[{"name":"","parameterTypes":["java.lang.String"] }, {"name":"","parameterTypes":["java.lang.String","boolean"] }, {"name":"getKeepLinkHandle","parameterTypes":[] }, {"name":"getUsernameLinkEncryptedValue","parameterTypes":[] }] }, { "name":"org.whispersystems.signalservice.internal.push.SetUsernameLinkResponseBody", @@ -2648,6 +2648,13 @@ "queryAllDeclaredConstructors":true, "methods":[{"name":"","parameterTypes":["java.lang.String","java.lang.String","java.lang.String","java.lang.String","java.lang.String"] }, {"name":"getCaptcha","parameterTypes":[] }, {"name":"getMcc","parameterTypes":[] }, {"name":"getMnc","parameterTypes":[] }, {"name":"getPushChallenge","parameterTypes":[] }, {"name":"getPushToken","parameterTypes":[] }, {"name":"getPushTokenType","parameterTypes":[] }] }, +{ + "name":"org.whispersystems.signalservice.internal.push.VerificationCodeFailureResponseBody", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":["boolean","java.lang.String"] }, {"name":"","parameterTypes":["boolean","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }] +}, { "name":"org.whispersystems.signalservice.internal.push.VerificationSessionMetadataRequestBody", "allDeclaredFields":true, diff --git a/graalvm-config-dir/resource-config.json b/graalvm-config-dir/resource-config.json index 6902124a..2c5463f7 100644 --- a/graalvm-config-dir/resource-config.json +++ b/graalvm-config-dir/resource-config.json @@ -215,6 +215,6 @@ }]}, "bundles":[{ "name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl", - "locales":["", "en", "und"] + "locales":["", "de", "en", "und"] }] } 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 f84c667b..cfe3f894 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 @@ -5,23 +5,31 @@ import org.whispersystems.signalservice.internal.util.Util; public record Contact( String givenName, String familyName, + String nickName, String color, int messageExpirationTime, + long muteUntil, + boolean hideStory, boolean isBlocked, boolean isArchived, boolean isProfileSharingEnabled, - boolean isHidden + boolean isHidden, + Long unregisteredTimestamp ) { private Contact(final Builder builder) { this(builder.givenName, builder.familyName, + builder.nickName, builder.color, builder.messageExpirationTime, + builder.muteUntil, + builder.hideStory, builder.isBlocked, builder.isArchived, builder.isProfileSharingEnabled, - builder.isHidden); + builder.isHidden, + builder.unregisteredTimestamp); } public static Builder newBuilder() { @@ -32,12 +40,16 @@ public record Contact( Builder builder = new Builder(); builder.givenName = copy.givenName(); builder.familyName = copy.familyName(); + builder.nickName = copy.nickName(); builder.color = copy.color(); builder.messageExpirationTime = copy.messageExpirationTime(); + builder.muteUntil = copy.muteUntil(); + builder.hideStory = copy.hideStory(); builder.isBlocked = copy.isBlocked(); builder.isArchived = copy.isArchived(); builder.isProfileSharingEnabled = copy.isProfileSharingEnabled(); builder.isHidden = copy.isHidden(); + builder.unregisteredTimestamp = copy.unregisteredTimestamp(); return builder; } @@ -60,12 +72,16 @@ public record Contact( private String givenName; private String familyName; + private String nickName; private String color; private int messageExpirationTime; + private long muteUntil; + private boolean hideStory; private boolean isBlocked; private boolean isArchived; private boolean isProfileSharingEnabled; private boolean isHidden; + private Long unregisteredTimestamp; private Builder() { } @@ -84,6 +100,11 @@ public record Contact( return this; } + public Builder withNickName(final String val) { + nickName = val; + return this; + } + public Builder withColor(final String val) { color = val; return this; @@ -94,6 +115,16 @@ public record Contact( return this; } + public Builder withMuteUntil(final long val) { + muteUntil = val; + return this; + } + + public Builder withHideStory(final boolean val) { + hideStory = val; + return this; + } + public Builder withIsBlocked(final boolean val) { isBlocked = val; return this; @@ -114,6 +145,11 @@ public record Contact( return this; } + public Builder withUnregisteredTimestamp(final Long val) { + unregisteredTimestamp = val; + return this; + } + public Contact build() { return new Contact(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 9e9eebf8..bedf4332 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 @@ -25,6 +25,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.File; import java.sql.Connection; import java.sql.SQLException; +import java.sql.Statement; import java.util.HashMap; import java.util.Optional; import java.util.UUID; @@ -32,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 = 21; + private static final long DATABASE_VERSION = 22; private AccountDatabase(final HikariDataSource dataSource) { super(logger, DATABASE_VERSION, dataSource); @@ -361,60 +362,7 @@ public class AccountDatabase extends Database { if (oldVersion < 15) { logger.debug("Updating database: Store serviceId as TEXT"); try (final var statement = connection.createStatement()) { - statement.executeUpdate(""" - CREATE TABLE tmp_mapping_table ( - uuid BLOB NOT NULL, - address TEXT NOT NULL - ) STRICT; - """); - - final var sql = ( - """ - SELECT r.uuid, r.pni - FROM recipient r - """ - ); - final var uuidAddressMapping = new HashMap(); - try (final var preparedStatement = connection.prepareStatement(sql)) { - try (var result = Utils.executeQueryForStream(preparedStatement, (resultSet) -> { - final var pni = Optional.ofNullable(resultSet.getBytes("pni")) - .map(UuidUtil::parseOrNull) - .map(ServiceId.PNI::from); - final var serviceIdUuid = Optional.ofNullable(resultSet.getBytes("uuid")) - .map(UuidUtil::parseOrNull); - final var serviceId = serviceIdUuid.isPresent() && pni.isPresent() && serviceIdUuid.get() - .equals(pni.get().getRawUuid()) - ? pni.map(p -> p) - : serviceIdUuid.map(ACI::from); - - return new Pair<>(serviceId, pni); - })) { - result.forEach(p -> { - final var serviceId = p.first(); - final var pni = p.second(); - if (serviceId.isPresent()) { - uuidAddressMapping.put(serviceId.get().getRawUuid(), serviceId.get()); - } - if (pni.isPresent()) { - uuidAddressMapping.put(pni.get().getRawUuid(), pni.get()); - } - }); - } - } - - final var insertSql = """ - INSERT INTO tmp_mapping_table (uuid, address) - VALUES (?,?) - """; - try (final var insertStatement = connection.prepareStatement(insertSql)) { - for (final var entry : uuidAddressMapping.entrySet()) { - final var uuid = entry.getKey(); - final var serviceId = entry.getValue(); - insertStatement.setBytes(1, UuidUtil.toByteArray(uuid)); - insertStatement.setString(2, serviceId.toString()); - insertStatement.execute(); - } - } + createUuidMappingTable(connection, statement); statement.executeUpdate(""" CREATE TABLE identity2 ( @@ -563,9 +511,120 @@ public class AccountDatabase extends Database { try (final var statement = connection.createStatement()) { statement.executeUpdate(""" ALTER TABLE recipient ADD unregistered_timestamp INTEGER; - UPDATE recipient SET pni = NULL WHERE uuid IS NOT NULL; """); } } + if (oldVersion < 22) { + logger.debug("Updating database: Store recipient aci/pni as TEXT"); + try (final var statement = connection.createStatement()) { + createUuidMappingTable(connection, statement); + + statement.executeUpdate(""" + CREATE TABLE recipient2 ( + _id INTEGER PRIMARY KEY AUTOINCREMENT, + storage_id BLOB UNIQUE, + storage_record BLOB, + number TEXT UNIQUE, + username TEXT UNIQUE, + aci TEXT UNIQUE, + pni TEXT UNIQUE, + unregistered_timestamp INTEGER, + profile_key BLOB, + profile_key_credential BLOB, + + given_name TEXT, + family_name TEXT, + nick_name TEXT, + color TEXT, + + expiration_time INTEGER NOT NULL DEFAULT 0, + mute_until INTEGER NOT NULL DEFAULT 0, + blocked INTEGER NOT NULL DEFAULT FALSE, + archived INTEGER NOT NULL DEFAULT FALSE, + profile_sharing INTEGER NOT NULL DEFAULT FALSE, + hide_story INTEGER NOT NULL DEFAULT FALSE, + hidden INTEGER NOT NULL DEFAULT FALSE, + + profile_last_update_timestamp INTEGER NOT NULL DEFAULT 0, + profile_given_name TEXT, + profile_family_name TEXT, + profile_about TEXT, + profile_about_emoji TEXT, + profile_avatar_url_path TEXT, + profile_mobile_coin_address BLOB, + profile_unidentified_access_mode TEXT, + profile_capabilities TEXT + ) STRICT; + INSERT INTO recipient2 (_id, aci, pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities) + SELECT r._id, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.uuid AND t.address not like 'PNI:%') aci, (SELECT t.address FROM tmp_mapping_table t WHERE t.uuid = r.pni AND t.address like 'PNI:%') pni, storage_id, storage_record, number, username, unregistered_timestamp, profile_key, profile_key_credential, given_name, family_name, color, expiration_time, blocked, archived, profile_sharing, hidden, profile_last_update_timestamp, profile_given_name, profile_family_name, profile_about, profile_about_emoji, profile_avatar_url_path, profile_mobile_coin_address, profile_unidentified_access_mode, profile_capabilities + FROM recipient r; + DROP TABLE recipient; + ALTER TABLE recipient2 RENAME TO recipient; + + DROP TABLE tmp_mapping_table; + """); + } + } + } + + private static void createUuidMappingTable( + final Connection connection, final Statement statement + ) throws SQLException { + statement.executeUpdate(""" + CREATE TABLE tmp_mapping_table ( + uuid BLOB NOT NULL, + address TEXT NOT NULL + ) STRICT; + """); + + final var sql = ( + """ + SELECT r.uuid, r.pni + FROM recipient r + """ + ); + final var uuidAddressMapping = new HashMap(); + try (final var preparedStatement = connection.prepareStatement(sql)) { + try (var result = Utils.executeQueryForStream(preparedStatement, (resultSet) -> { + final var pni = Optional.ofNullable(resultSet.getBytes("pni")) + .map(UuidUtil::parseOrNull) + .map(ServiceId.PNI::from); + final var serviceIdUuid = Optional.ofNullable(resultSet.getBytes("uuid")).map(UuidUtil::parseOrNull); + final var serviceId = serviceIdUuid.isPresent() && pni.isPresent() && serviceIdUuid.get() + .equals(pni.get().getRawUuid()) + ? pni.map(p -> p) + : serviceIdUuid.map(ACI::from); + + return new Pair<>(serviceId, pni); + })) { + result.forEach(p -> { + final var serviceId = p.first(); + final var pni = p.second(); + if (serviceId.isPresent()) { + final var rawUuid = serviceId.get().getRawUuid(); + if (!uuidAddressMapping.containsKey(rawUuid)) { + uuidAddressMapping.put(rawUuid, serviceId.get()); + } + } + if (pni.isPresent()) { + uuidAddressMapping.put(pni.get().getRawUuid(), pni.get()); + } + }); + } + } + + final var insertSql = """ + INSERT INTO tmp_mapping_table (uuid, address) + VALUES (?,?) + """; + try (final var insertStatement = connection.prepareStatement(insertSql)) { + for (final var entry : uuidAddressMapping.entrySet()) { + final var uuid = entry.getKey(); + final var serviceId = entry.getValue(); + insertStatement.setBytes(1, UuidUtil.toByteArray(uuid)); + insertStatement.setString(2, serviceId.toString()); + insertStatement.execute(); + } + } } } 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 fa5496d2..c324b012 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 @@ -385,8 +385,15 @@ public class SignalAccount implements Closeable { } getRecipientStore().deleteRecipientData(recipientId); getMessageCache().deleteMessages(recipientId); - if (recipientAddress.serviceId().isPresent()) { - final var serviceId = recipientAddress.serviceId().get(); + if (recipientAddress.aci().isPresent()) { + final var serviceId = recipientAddress.aci().get(); + aciAccountData.getSessionStore().deleteAllSessions(serviceId); + pniAccountData.getSessionStore().deleteAllSessions(serviceId); + getIdentityKeyStore().deleteIdentity(serviceId); + getSenderKeyStore().deleteAll(serviceId); + } + if (recipientAddress.pni().isPresent()) { + final var serviceId = recipientAddress.pni().get(); aciAccountData.getSessionStore().deleteAllSessions(serviceId); pniAccountData.getSessionStore().deleteAllSessions(serviceId); getIdentityKeyStore().deleteIdentity(serviceId); @@ -837,13 +844,17 @@ public class SignalAccount implements Closeable { final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress()); getContactStore().storeContact(recipientId, new Contact(contact.name, + null, null, contact.color, contact.messageExpirationTime, + 0, + false, contact.blocked, contact.archived, false, - false)); + false, + null)); // Store profile keys only in profile store var profileKeyString = contact.profileKey; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java index a35d4513..b5698b51 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java @@ -45,7 +45,7 @@ public class LegacyGroupStore { if (g instanceof Storage.GroupV1 g1) { final var members = g1.members.stream().map(m -> { if (m.recipientId == null) { - return recipientResolver.resolveRecipient(new RecipientAddress(ServiceId.parseOrNull(m.uuid), + return recipientResolver.resolveRecipient(new RecipientAddress(ServiceId.ACI.parseOrNull(m.uuid), m.number)); } diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacyProfileStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacyProfileStore.java index cf100257..abb66470 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacyProfileStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacyProfileStore.java @@ -43,7 +43,9 @@ public class LegacyProfileStore { if (node.isArray()) { for (var entry : node) { var name = entry.hasNonNull("name") ? entry.get("name").asText() : null; - var serviceId = entry.hasNonNull("uuid") ? ServiceId.parseOrNull(entry.get("uuid").asText()) : null; + var serviceId = entry.hasNonNull("uuid") + ? ServiceId.ACI.parseOrNull(entry.get("uuid").asText()) + : null; final var address = new RecipientAddress(serviceId, name); ProfileKey profileKey = null; try { diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore.java index 039e8471..fb1e188b 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore.java @@ -36,7 +36,7 @@ public class LegacyRecipientStore { if (node.isArray()) { for (var recipient : node) { var recipientName = recipient.get("name").asText(); - var serviceId = ServiceId.parseOrThrow(recipient.get("uuid").asText()); + var serviceId = ServiceId.ACI.parseOrThrow(recipient.get("uuid").asText()); addresses.add(new RecipientAddress(serviceId, recipientName)); } } 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 02061a66..17f136ec 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,13 +39,17 @@ public class LegacyRecipientStore2 { Contact contact = null; if (r.contact != null) { contact = new Contact(r.contact.name, + null, null, r.contact.color, r.contact.messageExpirationTime, + 0, + false, r.contact.blocked, r.contact.archived, r.contact.profileSharingEnabled, - false); + false, + null); } ProfileKey profileKey = null; diff --git a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java index dd18f401..2fa91a59 100644 --- a/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java @@ -32,22 +32,19 @@ public class MergeRecipientHelper { return new Pair<>(recipient.id(), List.of()); } - if (recipient.address().serviceId().isEmpty() || ( - recipient.address().serviceId().equals(address.serviceId()) - ) || ( - recipient.address().pni().isPresent() && recipient.address().pni().equals(address.serviceId()) - ) || ( - recipient.address().serviceId().equals(address.pni()) - ) || ( - address.pni().isPresent() && address.pni().equals(recipient.address().pni()) - )) { + if (recipient.address().aci().isEmpty() || ( + address.aci().isEmpty() && ( + address.pni().isEmpty() + || recipient.address().pni().equals(address.pni()) + ) + ) || recipient.address().aci().equals(address.aci())) { logger.debug("Got existing recipient {}, updating with high trust address", recipient.id()); store.updateRecipientAddress(recipient.id(), recipient.address().withIdentifiersFrom(address)); return new Pair<>(recipient.id(), List.of()); } logger.debug( - "Got recipient {} existing with number/pni/username, but different serviceId, so stripping its number and adding new recipient", + "Got recipient {} existing with number/pni/username, but different aci, so stripping its number and adding new recipient", recipient.id()); store.updateRecipientAddress(recipient.id(), recipient.address().removeIdentifiersFrom(address)); @@ -55,14 +52,10 @@ public class MergeRecipientHelper { } var resultingRecipient = recipients.stream() - .filter(r -> r.address().serviceId().equals(address.serviceId()) || r.address() - .pni() - .equals(address.serviceId())) + .filter(r -> r.address().aci().isPresent() && r.address().aci().equals(address.aci())) .findFirst(); if (resultingRecipient.isEmpty() && address.pni().isPresent()) { - resultingRecipient = recipients.stream().filter(r -> r.address().serviceId().equals(address.pni()) || ( - address.serviceId().equals(address.pni()) && r.address().pni().equals(address.pni()) - )).findFirst(); + resultingRecipient = recipients.stream().filter(r -> r.address().pni().equals(address.pni())).findFirst(); } final Set remainingRecipients; 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 e4c24c99..33b8a5f9 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 @@ -8,59 +8,60 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.Optional; public record RecipientAddress( - Optional serviceId, Optional pni, Optional number, Optional username + Optional aci, Optional pni, Optional number, Optional username ) { /** * Construct a RecipientAddress. * - * @param serviceId The ACI or PNI of the user, if available. - * @param number The phone number 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. + * @param username The username of the user, if available. */ public RecipientAddress { - if (serviceId.isPresent() && serviceId.get().isUnknown()) { - serviceId = Optional.empty(); + if (aci.isPresent() && aci.get().isUnknown()) { + aci = Optional.empty(); } if (pni.isPresent() && pni.get().isUnknown()) { pni = Optional.empty(); } - if (serviceId.isEmpty() && pni.isPresent()) { - serviceId = Optional.of(pni.get()); - } - if (serviceId.isPresent() && serviceId.get() instanceof PNI sPNI) { - if (pni.isPresent() && !sPNI.equals(pni.get())) { - throw new AssertionError("Must not have two different PNIs!"); - } - if (pni.isEmpty()) { - pni = Optional.of(sPNI); - } - } - if (serviceId.isEmpty() && number.isEmpty()) { + if (aci.isEmpty() && pni.isEmpty() && number.isEmpty()) { throw new AssertionError("Must have either a ServiceId or E164 number!"); } } public RecipientAddress(Optional serviceId, Optional number) { - this(serviceId, Optional.empty(), number, Optional.empty()); + this(serviceId.filter(s -> s instanceof ACI).map(s -> (ACI) s), + serviceId.filter(s -> s instanceof PNI).map(s -> (PNI) s), + number, + Optional.empty()); + } + + public RecipientAddress(ACI aci, String e164) { + this(Optional.ofNullable(aci), Optional.empty(), Optional.ofNullable(e164), Optional.empty()); } - public RecipientAddress(ServiceId serviceId, String e164) { - this(Optional.ofNullable(serviceId), Optional.empty(), Optional.ofNullable(e164), Optional.empty()); + public RecipientAddress(String e164) { + this(Optional.empty(), Optional.empty(), Optional.ofNullable(e164), Optional.empty()); } - public RecipientAddress(ServiceId serviceId, PNI pni, String e164) { - this(Optional.ofNullable(serviceId), Optional.ofNullable(pni), Optional.ofNullable(e164), Optional.empty()); + public RecipientAddress(ACI aci, PNI pni, String e164) { + this(Optional.ofNullable(aci), Optional.ofNullable(pni), Optional.ofNullable(e164), Optional.empty()); } - public RecipientAddress(ServiceId serviceId, PNI pni, String e164, String username) { - this(Optional.ofNullable(serviceId), + public RecipientAddress(ACI aci, PNI pni, String e164, String username) { + this(Optional.ofNullable(aci), Optional.ofNullable(pni), Optional.ofNullable(e164), Optional.ofNullable(username)); } public RecipientAddress(SignalServiceAddress address) { - this(Optional.of(address.getServiceId()), Optional.empty(), address.getNumber(), Optional.empty()); + this(address.getServiceId() instanceof ACI ? Optional.of((ACI) address.getServiceId()) : Optional.empty(), + address.getServiceId() instanceof PNI ? Optional.of((PNI) address.getServiceId()) : Optional.empty(), + address.getNumber(), + Optional.empty()); } public RecipientAddress(org.asamk.signal.manager.api.RecipientAddress address) { @@ -72,30 +73,28 @@ public record RecipientAddress( } public RecipientAddress withIdentifiersFrom(RecipientAddress address) { - return new RecipientAddress(( - this.serviceId.isEmpty() || this.isServiceIdPNI() || this.serviceId.equals(address.pni) - ) && !address.isServiceIdPNI() ? address.serviceId : this.serviceId, + return new RecipientAddress(address.aci.or(this::aci), address.pni.or(this::pni), address.number.or(this::number), address.username.or(this::username)); } public RecipientAddress removeIdentifiersFrom(RecipientAddress address) { - return new RecipientAddress(address.serviceId.equals(this.serviceId) || address.pni.equals(this.serviceId) - ? Optional.empty() - : this.serviceId, - address.pni.equals(this.pni) || address.serviceId.equals(this.pni) ? Optional.empty() : this.pni, + return new RecipientAddress(address.aci.equals(this.aci) ? Optional.empty() : this.aci, + address.pni.equals(this.pni) ? Optional.empty() : this.pni, address.number.equals(this.number) ? Optional.empty() : this.number, address.username.equals(this.username) ? Optional.empty() : this.username); } - public Optional aci() { - return serviceId.map(s -> s instanceof ServiceId.ACI aci ? aci : null); + public Optional serviceId() { + return aci.map(aci -> (ServiceId) aci).or(this::pni); } public String getIdentifier() { - if (serviceId.isPresent()) { - return serviceId.get().toString(); + if (aci.isPresent()) { + return aci.get().toString(); + } else if (pni.isPresent()) { + return pni.get().toString(); } else if (number.isPresent()) { return number.get(); } else { @@ -106,38 +105,31 @@ public record RecipientAddress( public String getLegacyIdentifier() { if (number.isPresent()) { return number.get(); - } else if (serviceId.isPresent()) { - return serviceId.get().toString(); + } else if (aci.isPresent()) { + return aci.get().toString(); + } else if (pni.isPresent()) { + return pni.get().toString(); } else { throw new AssertionError("Given the checks in the constructor, this should not be possible."); } } public boolean matches(RecipientAddress other) { - return (serviceId.isPresent() && other.serviceId.isPresent() && serviceId.get().equals(other.serviceId.get())) - || ( - pni.isPresent() && other.serviceId.isPresent() && pni.get().equals(other.serviceId.get()) - ) - || ( - serviceId.isPresent() && other.pni.isPresent() && serviceId.get().equals(other.pni.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()) ); } public boolean hasSingleIdentifier() { - final var identifiersCount = serviceId().map(s -> 1).orElse(0) - + number().map(s -> 1).orElse(0) - + username().map(s -> 1).orElse(0); + final var identifiersCount = aci().map(s -> 1).orElse(0) + pni().map(s -> 1).orElse(0) + number().map(s -> 1) + .orElse(0) + username().map(s -> 1).orElse(0); return identifiersCount == 1; } public boolean hasIdentifiersOf(RecipientAddress address) { - return (address.serviceId.isEmpty() || address.serviceId.equals(serviceId) || address.serviceId.equals(pni)) + return (address.aci.isEmpty() || address.aci.equals(aci)) && (address.pni.isEmpty() || address.pni.equals(pni)) && (address.number.isEmpty() || address.number.equals(number)) && (address.username.isEmpty() || address.username.equals(username)); @@ -145,13 +137,12 @@ public record RecipientAddress( public boolean hasAdditionalIdentifiersThan(RecipientAddress address) { return ( - serviceId.isPresent() && ( - address.serviceId.isEmpty() || ( - !address.serviceId.equals(serviceId) && !address.pni.equals(serviceId) - ) + aci.isPresent() && ( + address.aci.isEmpty() || !address.aci.equals(aci) + ) ) || ( - pni.isPresent() && !address.serviceId.equals(pni) && ( + pni.isPresent() && ( address.pni.isEmpty() || !address.pni.equals(pni) ) ) || ( @@ -166,19 +157,15 @@ public record RecipientAddress( } public boolean hasOnlyPniAndNumber() { - return pni.isPresent() && serviceId.equals(pni) && number.isPresent(); - } - - public boolean isServiceIdPNI() { - return serviceId.isPresent() && (pni.isPresent() && serviceId.equals(pni)); + return pni.isPresent() && aci.isEmpty() && number.isPresent(); } public SignalServiceAddress toSignalServiceAddress() { - return new SignalServiceAddress(serviceId.orElse(ACI.UNKNOWN), number); + return new SignalServiceAddress(aci.orElse(ACI.UNKNOWN), number); } 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::getRawUuid), 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 285d5cf6..6be22754 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 @@ -19,7 +19,6 @@ import org.whispersystems.signalservice.api.push.ServiceId.ACI; import org.whispersystems.signalservice.api.push.ServiceId.PNI; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.storage.StorageId; -import org.whispersystems.signalservice.api.util.UuidUtil; import java.sql.Connection; import java.sql.ResultSet; @@ -41,7 +40,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.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.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; @@ -63,20 +62,23 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re storage_record BLOB, number TEXT UNIQUE, username TEXT UNIQUE, - uuid BLOB UNIQUE, - pni BLOB UNIQUE, + aci TEXT UNIQUE, + pni TEXT UNIQUE, unregistered_timestamp INTEGER, profile_key BLOB, profile_key_credential BLOB, given_name TEXT, family_name TEXT, + nick_name TEXT, color TEXT, expiration_time INTEGER NOT NULL DEFAULT 0, + mute_until INTEGER NOT NULL DEFAULT 0, blocked INTEGER NOT NULL DEFAULT FALSE, archived INTEGER NOT NULL DEFAULT FALSE, profile_sharing INTEGER NOT NULL DEFAULT FALSE, + hide_story INTEGER NOT NULL DEFAULT FALSE, hidden INTEGER NOT NULL DEFAULT FALSE, profile_last_update_timestamp INTEGER NOT NULL DEFAULT 0, @@ -108,7 +110,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public RecipientAddress resolveRecipientAddress(RecipientId recipientId) { final var sql = ( """ - SELECT r.number, r.uuid, r.pni, r.username + SELECT r.number, r.aci, r.pni, r.username FROM %s r WHERE r._id = ? """ @@ -310,8 +312,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public RecipientId resolveRecipientTrusted( final Optional aci, final Optional pni, final Optional number ) { - final var serviceId = aci.map(a -> (ServiceId) a).or(() -> pni); - return resolveRecipientTrusted(new RecipientAddress(serviceId, pni, number, Optional.empty())); + return resolveRecipientTrusted(new RecipientAddress(aci, pni, number, Optional.empty())); } @Override @@ -341,9 +342,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public List> getContacts() { final var sql = ( """ - SELECT r._id, r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden + 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 FROM %s r - WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL) AND %s AND r.hidden = FALSE + WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s AND r.hidden = FALSE """ ).formatted(TABLE_RECIPIENT, SQL_IS_CONTACT); try (final var connection = database.getConnection()) { @@ -363,9 +364,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ SELECT r._id, - r.number, r.uuid, r.pni, r.username, + r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, + 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.storage_record FROM %s r @@ -382,9 +383,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ SELECT r._id, - r.number, r.uuid, r.pni, r.username, + r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, + 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.storage_record FROM %s r @@ -417,16 +418,16 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ SELECT r._id, - r.number, r.uuid, r.pni, r.username, + r.number, r.aci, r.pni, r.username, r.profile_key, r.profile_key_credential, - r.given_name, r.family_name, r.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, + 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.storage_record FROM %s r - WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL) AND %s + WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s """ ).formatted(TABLE_RECIPIENT, sqlWhere.isEmpty() ? "TRUE" : String.join(" AND ", sqlWhere)); - final var selfServiceId = selfAddressProvider.getSelfAddress().serviceId(); + final var selfAddress = selfAddressProvider.getSelfAddress(); try (final var connection = database.getConnection()) { try (final var statement = connection.prepareStatement(sql)) { if (blocked.isPresent()) { @@ -436,7 +437,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re return result.filter(r -> name.isEmpty() || ( r.getContact() != null && name.get().equals(r.getContact().getName()) ) || (r.getProfile() != null && name.get().equals(r.getProfile().getDisplayName()))).map(r -> { - if (r.getAddress().serviceId().equals(selfServiceId)) { + if (r.getAddress().matches(selfAddress)) { return Recipient.newBuilder(r) .withProfileKey(selfProfileKeyProvider.getSelfProfileKey()) .build(); @@ -482,21 +483,21 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public Map getServiceIdToProfileKeyMap() { final var sql = ( """ - SELECT r.uuid, r.profile_key + SELECT r.aci, r.profile_key FROM %s r - WHERE r.uuid IS NOT NULL AND r.profile_key IS NOT NULL + WHERE r.aci IS NOT NULL AND r.profile_key IS NOT NULL """ ).formatted(TABLE_RECIPIENT); - final var selfServiceId = selfAddressProvider.getSelfAddress().serviceId().orElse(null); + final var selfAci = selfAddressProvider.getSelfAddress().aci().orElse(null); try (final var connection = database.getConnection()) { try (final var statement = connection.prepareStatement(sql)) { return Utils.executeQueryForStream(statement, resultSet -> { - final var serviceId = ServiceId.parseOrThrow(resultSet.getBytes("uuid")); - if (serviceId.equals(selfServiceId)) { - return new Pair<>(serviceId, selfProfileKeyProvider.getSelfProfileKey()); + final var aci = ACI.parseOrThrow(resultSet.getString("aci")); + if (aci.equals(selfAci)) { + return new Pair<>(aci, selfProfileKeyProvider.getSelfProfileKey()); } final var profileKey = getProfileKeyFromResultSet(resultSet); - return new Pair<>(serviceId, profileKey); + return new Pair<>(aci, profileKey); }).filter(Objects::nonNull).collect(Collectors.toMap(Pair::first, Pair::second)); } } catch (SQLException e) { @@ -509,7 +510,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re """ SELECT r._id FROM %s r - WHERE (r.number IS NOT NULL OR r.uuid IS NOT NULL) + WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) """ ).formatted(TABLE_RECIPIENT); try (final var statement = connection.prepareStatement(sql)) { @@ -657,7 +658,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re public List getStorageIds(Connection connection) throws SQLException { final var sql = """ SELECT r.storage_id - FROM %s r WHERE r.storage_id IS NOT NULL AND r._id != ? AND (r.uuid IS NOT NULL OR r.pni IS NOT NULL) + FROM %s r WHERE r.storage_id IS NOT NULL AND r._id != ? AND (r.aci IS NOT NULL OR r.pni IS NOT NULL) """.formatted(TABLE_RECIPIENT); final var selfRecipientId = resolveRecipient(connection, selfAddressProvider.getSelfAddress()); try (final var statement = connection.prepareStatement(sql)) { @@ -767,7 +768,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re long start = System.nanoTime(); final var sql = ( """ - INSERT INTO %s (_id, number, uuid) + INSERT INTO %s (_id, number, aci) VALUES (?, ?, ?) """ ).formatted(TABLE_RECIPIENT); @@ -780,12 +781,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re for (final var recipient : recipients.values()) { statement.setLong(1, recipient.getRecipientId().id()); statement.setString(2, recipient.getAddress().number().orElse(null)); - statement.setBytes(3, - recipient.getAddress() - .serviceId() - .map(ServiceId::getRawUuid) - .map(UuidUtil::toByteArray) - .orElse(null)); + statement.setString(3, recipient.getAddress().aci().map(ACI::toString).orElse(null)); statement.executeUpdate(); } } @@ -829,19 +825,27 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ UPDATE %s - SET given_name = ?, family_name = ?, expiration_time = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ? + SET given_name = ?, family_name = ?, nick_name = ?, expiration_time = ?, mute_until = ?, hide_story = ?, profile_sharing = ?, color = ?, blocked = ?, archived = ?, unregistered_timestamp = ? WHERE _id = ? """ ).formatted(TABLE_RECIPIENT); try (final var statement = connection.prepareStatement(sql)) { statement.setString(1, contact == null ? null : contact.givenName()); statement.setString(2, contact == null ? null : contact.familyName()); - statement.setInt(3, contact == null ? 0 : contact.messageExpirationTime()); - statement.setBoolean(4, contact != null && contact.isProfileSharingEnabled()); - statement.setString(5, contact == null ? null : contact.color()); - statement.setBoolean(6, contact != null && contact.isBlocked()); - statement.setBoolean(7, contact != null && contact.isArchived()); - statement.setLong(8, recipientId.id()); + statement.setString(3, contact == null ? null : contact.nickName()); + statement.setInt(4, contact == null ? 0 : contact.messageExpirationTime()); + statement.setLong(5, contact == null ? 0 : contact.muteUntil()); + statement.setBoolean(6, contact != null && contact.hideStory()); + statement.setBoolean(7, contact != null && contact.isProfileSharingEnabled()); + statement.setString(8, contact == null ? null : contact.color()); + statement.setBoolean(9, contact != null && contact.isBlocked()); + statement.setBoolean(10, contact != null && contact.isArchived()); + if (contact == null || contact.unregisteredTimestamp() == null) { + statement.setNull(11, Types.INTEGER); + } else { + statement.setLong(11, contact.unregisteredTimestamp()); + } + statement.setLong(12, recipientId.id()); statement.executeUpdate(); } rotateStorageId(connection, recipientId); @@ -1055,12 +1059,12 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re private RecipientId resolveRecipientLocked( Connection connection, RecipientAddress address ) throws SQLException { - final var aci = address.aci().isEmpty() + final var byAci = address.aci().isEmpty() ? Optional.empty() : findByServiceId(connection, address.aci().get()); - if (aci.isPresent()) { - return aci.get().id(); + if (byAci.isPresent()) { + return byAci.get().id(); } final var byPni = address.pni().isEmpty() @@ -1104,7 +1108,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re if (recipient.isEmpty()) { logger.debug("Got new recipient, number is unknown"); - return addNewRecipient(connection, new RecipientAddress(null, number)); + return addNewRecipient(connection, new RecipientAddress(number)); } return recipient.get().id(); @@ -1115,15 +1119,15 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re ) throws SQLException { final var sql = ( """ - INSERT INTO %s (number, uuid, pni, username) + INSERT INTO %s (number, aci, pni, username) VALUES (?, ?, ?, ?) RETURNING _id """ ).formatted(TABLE_RECIPIENT); try (final var statement = connection.prepareStatement(sql)) { statement.setString(1, address.number().orElse(null)); - statement.setBytes(2, address.aci().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); - statement.setBytes(3, address.pni().map(PNI::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); + statement.setString(2, address.aci().map(ACI::toString).orElse(null)); + statement.setString(3, address.pni().map(PNI::toString).orElse(null)); statement.setString(4, address.username().orElse(null)); final var generatedKey = Utils.executeQueryForOptional(statement, Utils::getIdMapper); if (generatedKey.isPresent()) { @@ -1142,7 +1146,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ UPDATE %s - SET number = NULL, uuid = NULL, pni = NULL, username = NULL, storage_id = NULL + SET number = NULL, aci = NULL, pni = NULL, username = NULL, storage_id = NULL WHERE _id = ? """ ).formatted(TABLE_RECIPIENT); @@ -1161,14 +1165,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final var sql = ( """ UPDATE %s - SET number = ?, uuid = ?, pni = ?, username = ? + SET number = ?, aci = ?, pni = ?, username = ? WHERE _id = ? """ ).formatted(TABLE_RECIPIENT); try (final var statement = connection.prepareStatement(sql)) { statement.setString(1, address.number().orElse(null)); - statement.setBytes(2, address.aci().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); - statement.setBytes(3, address.pni().map(PNI::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); + statement.setString(2, address.aci().map(ACI::toString).orElse(null)); + statement.setString(3, address.pni().map(PNI::toString).orElse(null)); statement.setString(4, address.username().orElse(null)); statement.setLong(5, recipientId.id()); statement.executeUpdate(); @@ -1225,7 +1229,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final Connection connection, final String number ) throws SQLException { final var sql = """ - SELECT r._id, r.number, r.uuid, r.pni, r.username + SELECT r._id, r.number, r.aci, r.pni, r.username FROM %s r WHERE r.number = ? LIMIT 1 @@ -1240,7 +1244,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final Connection connection, final String username ) throws SQLException { final var sql = """ - SELECT r._id, r.number, r.uuid, r.pni, r.username + SELECT r._id, r.number, r.aci, r.pni, r.username FROM %s r WHERE r.username = ? LIMIT 1 @@ -1259,13 +1263,13 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re return recipientWithAddress; } final var sql = """ - SELECT r._id, r.number, r.uuid, r.pni, r.username + SELECT r._id, r.number, r.aci, r.pni, r.username FROM %s r WHERE %s = ?1 LIMIT 1 - """.formatted(TABLE_RECIPIENT, serviceId instanceof ACI ? "r.uuid" : "r.pni"); + """.formatted(TABLE_RECIPIENT, serviceId instanceof ACI ? "r.aci" : "r.pni"); try (final var statement = connection.prepareStatement(sql)) { - statement.setBytes(1, UuidUtil.toByteArray(serviceId.getRawUuid())); + statement.setString(1, serviceId.toString()); recipientWithAddress = Utils.executeQueryForOptional(statement, this::getRecipientWithAddressFromResultSet); recipientWithAddress.ifPresent(r -> recipientAddressCache.put(serviceId, r)); return recipientWithAddress; @@ -1276,16 +1280,16 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re final Connection connection, final RecipientAddress address ) throws SQLException { final var sql = """ - SELECT r._id, r.number, r.uuid, r.pni, r.username + SELECT r._id, r.number, r.aci, r.pni, r.username FROM %s r - WHERE r.uuid = ?1 OR + WHERE r.aci = ?1 OR r.pni = ?2 OR r.number = ?3 OR r.username = ?4 """.formatted(TABLE_RECIPIENT); try (final var statement = connection.prepareStatement(sql)) { - statement.setBytes(1, address.aci().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); - statement.setBytes(2, address.pni().map(ServiceId::getRawUuid).map(UuidUtil::toByteArray).orElse(null)); + statement.setString(1, address.aci().map(ServiceId::toString).orElse(null)); + statement.setString(2, address.pni().map(ServiceId::toString).orElse(null)); statement.setString(3, address.number().orElse(null)); statement.setString(4, address.username().orElse(null)); return Utils.executeQueryForStream(statement, this::getRecipientWithAddressFromResultSet) @@ -1296,7 +1300,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.expiration_time, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden + 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 FROM %s r WHERE r._id = ? AND (%s) """ @@ -1357,13 +1361,11 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private RecipientAddress getRecipientAddressFromResultSet(ResultSet resultSet) throws SQLException { - final var pni = Optional.ofNullable(resultSet.getBytes("pni")).map(UuidUtil::parseOrNull).map(PNI::from); - final var serviceIdUuid = Optional.ofNullable(resultSet.getBytes("uuid")).map(UuidUtil::parseOrNull); - final var serviceId = serviceIdUuid.isPresent() && pni.isPresent() && serviceIdUuid.get() - .equals(pni.get().getRawUuid()) ? pni.map(p -> p) : serviceIdUuid.map(ACI::from); + final var aci = Optional.ofNullable(resultSet.getString("aci")).map(ACI::parseOrThrow); + final var pni = Optional.ofNullable(resultSet.getString("pni")).map(PNI::parseOrThrow); final var number = Optional.ofNullable(resultSet.getString("number")); final var username = Optional.ofNullable(resultSet.getString("username")); - return new RecipientAddress(serviceId, pni, number, username); + return new RecipientAddress(aci, pni, number, username); } private RecipientId getRecipientIdFromResultSet(ResultSet resultSet) throws SQLException { @@ -1386,14 +1388,19 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re } private Contact getContactFromResultSet(ResultSet resultSet) throws SQLException { + final var unregisteredTimestamp = resultSet.getLong("unregistered_timestamp"); return new Contact(resultSet.getString("given_name"), resultSet.getString("family_name"), + resultSet.getString("nick_name"), resultSet.getString("color"), resultSet.getInt("expiration_time"), + resultSet.getLong("mute_until"), + resultSet.getBoolean("hide_story"), resultSet.getBoolean("blocked"), resultSet.getBoolean("archived"), resultSet.getBoolean("profile_sharing"), - resultSet.getBoolean("hidden")); + resultSet.getBoolean("hidden"), + unregisteredTimestamp == 0 ? null : unregisteredTimestamp); } private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException { 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 6114a018..b79cb957 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 @@ -233,22 +233,38 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor