]> nmode's Git Repositories - signal-cli/commitdiff
Recreate recipient database with aci column
authorAsamK <asamk@gmx.de>
Sun, 28 Jan 2024 16:57:40 +0000 (17:57 +0100)
committerAsamK <asamk@gmx.de>
Sun, 28 Jan 2024 21:38:41 +0000 (22:38 +0100)
17 files changed:
graalvm-config-dir/reflect-config.json
graalvm-config-dir/resource-config.json
lib/src/main/java/org/asamk/signal/manager/api/Contact.java
lib/src/main/java/org/asamk/signal/manager/storage/AccountDatabase.java
lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
lib/src/main/java/org/asamk/signal/manager/storage/groups/LegacyGroupStore.java
lib/src/main/java/org/asamk/signal/manager/storage/profiles/LegacyProfileStore.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelper.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientAddress.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java
lib/src/main/java/org/asamk/signal/manager/syncStorage/ContactRecordProcessor.java
lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncModels.java
lib/src/main/java/org/asamk/signal/manager/syncStorage/StorageSyncValidations.java
lib/src/test/java/org/asamk/signal/manager/storage/recipients/MergeRecipientHelperTest.java
src/main/java/org/asamk/signal/dbus/DbusManagerImpl.java

index ea6babe42989f5a98854ec9bb4f8aa2e0051406d..186f507f859ff2dc85153115342df387f3f6dccc 100644 (file)
   "allDeclaredFields":true,
   "queryAllDeclaredMethods":true,
   "queryAllDeclaredConstructors":true,
-  "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }, {"name":"getUsernameLinkEncryptedValue","parameterTypes":[] }]
+  "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }, {"name":"<init>","parameterTypes":["java.lang.String","boolean"] }, {"name":"getKeepLinkHandle","parameterTypes":[] }, {"name":"getUsernameLinkEncryptedValue","parameterTypes":[] }]
 },
 {
   "name":"org.whispersystems.signalservice.internal.push.SetUsernameLinkResponseBody",
   "queryAllDeclaredConstructors":true,
   "methods":[{"name":"<init>","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":"<init>","parameterTypes":["boolean","java.lang.String"] }, {"name":"<init>","parameterTypes":["boolean","java.lang.String","int","kotlin.jvm.internal.DefaultConstructorMarker"] }]
+},
 {
   "name":"org.whispersystems.signalservice.internal.push.VerificationSessionMetadataRequestBody",
   "allDeclaredFields":true,
index 6902124a84e2e3dc393fe66f9eb666bf62830a70..2c5463f7cfe1d32c68c6d5481c95d95a0f734b07 100644 (file)
   }]},
   "bundles":[{
     "name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl",
-    "locales":["", "en", "und"]
+    "locales":["", "de", "en", "und"]
   }]
 }
index f84c667b9d3ddcfa1dd58059962c1b0753544bd4..cfe3f89492ffd06b46ea947a433b10b1a64a578a 100644 (file)
@@ -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);
         }
index 9e9eebf8aabd3ca27ce6e0c68b08aec905b616a2..bedf43329e59e6e8c26d76a06541959e958f90fc 100644 (file)
@@ -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<UUID, ServiceId>();
-                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.<ServiceId>map(p -> p)
-                                : serviceIdUuid.<ServiceId>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<UUID, ServiceId>();
+        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.<ServiceId>map(p -> p)
+                        : serviceIdUuid.<ServiceId>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();
+            }
+        }
     }
 }
index fa5496d2e21681f4d9448948093f9b68dd2d9207..c324b01238eab3badc26c41431b7a8140dcabc7e 100644 (file)
@@ -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;
index a35d4513a92fe8cbae492f6c8e9c65b104dd0336..b5698b51b38abe6136e323d31566493ea5a0c181 100644 (file)
@@ -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));
                     }
 
index cf1002576a6ceca604db7379dde6f932f55cd31b..abb66470dad3e1cc5df8d7b11667ac4ffc1e37a3 100644 (file)
@@ -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 {
index 039e8471a5d1034d1945a2bf26b3f229d7529f6e..fb1e188b76962cf1760a07a437518fa104c2eb3e 100644 (file)
@@ -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));
                 }
             }
index 02061a66f4e95702b70f2639ca6b620710c0fe3f..17f136ec90fda7bd0b968729e5cafb89bbbb5b37 100644 (file)
@@ -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;
index dd18f401ce30e493ab7d8dd183d04f8533917972..2fa91a5994d28c81033374588449dc9f4fc673b0 100644 (file)
@@ -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<RecipientWithAddress> remainingRecipients;
index e4c24c99eeeb81d613504d86d3055a1032c3128d..33b8a5f98514568df18466de6d18fd50e3fce544 100644 (file)
@@ -8,59 +8,60 @@ import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import java.util.Optional;
 
 public record RecipientAddress(
-        Optional<ServiceId> serviceId, Optional<PNI> pni, Optional<String> number, Optional<String> username
+        Optional<ACI> aci, Optional<PNI> pni, Optional<String> number, Optional<String> 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> serviceId, Optional<String> 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> aci() {
-        return serviceId.map(s -> s instanceof ServiceId.ACI aci ? aci : null);
+    public Optional<ServiceId> 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());
     }
index 285d5cf6c0d35423c1291fecf13bf6d1e309528e..6be22754a33f0163d72960cd97e0cd4671079bcd 100644 (file)
@@ -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> aci, final Optional<PNI> pni, final Optional<String> 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<Pair<RecipientId, Contact>> 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<ServiceId, ProfileKey> 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<StorageId> 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.<RecipientWithAddress>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.<ServiceId>map(p -> p) : serviceIdUuid.<ServiceId>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 {
index 6114a0184561208388b6e1b068c93e185efcc361..b79cb957500667584a34b33ef3554716ed5c412e 100644 (file)
@@ -233,22 +233,38 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
         final var profileShared = contact != null && contact.isProfileSharingEnabled();
         final var archived = contact != null && contact.isArchived();
         final var hidden = contact != null && contact.isHidden();
+        final var hideStory = contact != null && contact.hideStory();
+        final var muteUntil = contact == null ? 0 : contact.muteUntil();
+        final var unregisteredTimestamp = contact == null || contact.unregisteredTimestamp() == null
+                ? 0
+                : contact.unregisteredTimestamp();
         final var contactGivenName = contact == null ? null : contact.givenName();
         final var contactFamilyName = contact == null ? null : contact.familyName();
+        final var contactNickName = contact == null ? null : contact.nickName();
         if (blocked != contactRecord.isBlocked()
                 || profileShared != contactRecord.isProfileSharingEnabled()
                 || archived != contactRecord.isArchived()
                 || hidden != contactRecord.isHidden()
+                || hideStory != contactRecord.shouldHideStory()
+                || muteUntil != contactRecord.getMuteUntil()
+                || unregisteredTimestamp != contactRecord.getUnregisteredTimestamp()
                 || !Objects.equals(contactRecord.getSystemGivenName().orElse(null), contactGivenName)
-                || !Objects.equals(contactRecord.getSystemFamilyName().orElse(null), contactFamilyName)) {
+                || !Objects.equals(contactRecord.getSystemFamilyName().orElse(null), contactFamilyName)
+                || !Objects.equals(contactRecord.getSystemNickname().orElse(null), contactNickName)) {
             logger.debug("Storing new or updated contact {}", recipientId);
             final var contactBuilder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
             final var newContact = contactBuilder.withIsBlocked(contactRecord.isBlocked())
                     .withIsProfileSharingEnabled(contactRecord.isProfileSharingEnabled())
                     .withIsArchived(contactRecord.isArchived())
                     .withIsHidden(contactRecord.isHidden())
+                    .withMuteUntil(contactRecord.getMuteUntil())
+                    .withHideStory(contactRecord.shouldHideStory())
                     .withGivenName(contactRecord.getSystemGivenName().orElse(null))
-                    .withFamilyName(contactRecord.getSystemFamilyName().orElse(null));
+                    .withFamilyName(contactRecord.getSystemFamilyName().orElse(null))
+                    .withNickName(contactRecord.getSystemNickname().orElse(null))
+                    .withUnregisteredTimestamp(contactRecord.getUnregisteredTimestamp() == 0
+                            ? null
+                            : contactRecord.getUnregisteredTimestamp());
             account.getRecipientStore().storeContact(connection, recipientId, newContact.build());
         }
 
index c18434c3f7eda356ed5eecce11f5c4651280d3c8..c438570c2a4f6c11644f8f899c8cea4865cd962d 100644 (file)
@@ -94,9 +94,15 @@ public final class StorageSyncModels {
         }
         if (recipient.getContact() != null) {
             builder.setSystemGivenName(recipient.getContact().givenName())
-                    .setSystemFamilyName((recipient.getContact().familyName()))
+                    .setSystemFamilyName(recipient.getContact().familyName())
+                    .setSystemNickname(recipient.getContact().nickName())
                     .setBlocked(recipient.getContact().isBlocked())
                     .setProfileSharingEnabled(recipient.getContact().isProfileSharingEnabled())
+                    .setMuteUntil(recipient.getContact().muteUntil())
+                    .setHideStory(recipient.getContact().hideStory())
+                    .setUnregisteredTimestamp(recipient.getContact().unregisteredTimestamp() == null
+                            ? 0
+                            : recipient.getContact().unregisteredTimestamp())
                     .setArchived(recipient.getContact().isArchived())
                     .setHidden(recipient.getContact().isHidden());
         }
index 2168a2ef9b784b90de520393028699650e7329be..e69481edc41333358cbb2804a981d79f970c4396 100644 (file)
@@ -5,7 +5,6 @@ import org.signal.core.util.Base64;
 import org.signal.core.util.SetUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.whispersystems.signalservice.api.push.ServiceId;
 import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
 import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
 import org.whispersystems.signalservice.api.storage.StorageId;
@@ -181,11 +180,11 @@ public final class StorageSyncValidations {
 
             if (insert.getContact().isPresent()) {
                 final var contact = insert.getContact().get();
-                final var serviceId = contact.getServiceId().map(ServiceId.class::cast);
+                final var aci = contact.getAci();
                 final var pni = contact.getPni();
                 final var number = contact.getNumber();
                 final var username = contact.getUsername();
-                final var address = new RecipientAddress(serviceId, pni, number, username);
+                final var address = new RecipientAddress(aci, pni, number, username);
                 if (self.matches(address)) {
                     throw new SelfAddedAsContactError();
                 }
index eae8958f21915205e7041283c935a416bdc29642..b57dc6fcd2cfb9c2513480a06fa46c88d182025f 100644 (file)
@@ -3,7 +3,7 @@ package org.asamk.signal.manager.storage.recipients;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
-import org.whispersystems.signalservice.api.push.ServiceId;
+import org.whispersystems.signalservice.api.push.ServiceId.ACI;
 import org.whispersystems.signalservice.api.push.ServiceId.PNI;
 
 import java.util.Arrays;
@@ -17,8 +17,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 
 class MergeRecipientHelperTest {
 
-    static final ServiceId SERVICE_ID_A = ServiceId.ACI.from(UUID.randomUUID());
-    static final ServiceId SERVICE_ID_B = ServiceId.ACI.from(UUID.randomUUID());
+    static final ACI ACI_A = ACI.from(UUID.randomUUID());
+    static final ACI ACI_B = ACI.from(UUID.randomUUID());
     static final PNI PNI_A = PNI.from(UUID.randomUUID());
     static final PNI PNI_B = PNI.from(UUID.randomUUID());
     static final String NUMBER_A = "+AAA";
@@ -26,8 +26,8 @@ class MergeRecipientHelperTest {
     static final String USERNAME_A = "USER.1";
     static final String USERNAME_B = "USER.2";
 
-    static final PartialAddresses ADDR_A = new PartialAddresses(SERVICE_ID_A, PNI_A, NUMBER_A, USERNAME_A);
-    static final PartialAddresses ADDR_B = new PartialAddresses(SERVICE_ID_B, PNI_B, NUMBER_B, USERNAME_B);
+    static final PartialAddresses ADDR_A = new PartialAddresses(ACI_A, PNI_A, NUMBER_A, USERNAME_A);
+    static final PartialAddresses ADDR_B = new PartialAddresses(ACI_B, PNI_B, NUMBER_B, USERNAME_B);
 
     static final T[] testInstancesNone = new T[]{
             new T(Set.of(), ADDR_A.FULL, Set.of(rec(1000000, ADDR_A.FULL))),
@@ -53,9 +53,7 @@ class MergeRecipientHelperTest {
             new T(Set.of(rec(1, ADDR_A.PNI)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.PNI), rec(1000000, ADDR_A.ACI_NUM))),
             new T(Set.of(rec(1, ADDR_A.NUM)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.ACI_NUM))),
             new T(Set.of(rec(1, ADDR_A.ACI_NUM)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.ACI_NUM))),
-            new T(Set.of(rec(1, ADDR_A.PNI_NUM)),
-                    ADDR_A.ACI_NUM,
-                    Set.of(rec(1, ADDR_A.PNI), rec(1000000, ADDR_A.ACI_NUM))),
+            new T(Set.of(rec(1, ADDR_A.PNI_NUM)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.FULL))),
             new T(Set.of(rec(1, ADDR_A.ACI_PNI)), ADDR_A.ACI_NUM, Set.of(rec(1, ADDR_A.FULL))),
 
             new T(Set.of(rec(1, ADDR_A.FULL)), ADDR_A.PNI_NUM, Set.of(rec(1, ADDR_A.FULL))),
@@ -186,7 +184,7 @@ class MergeRecipientHelperTest {
         @Override
         public String toString() {
             return "T{#input=%s, request=%s_%s_%s, #output=%s}".formatted(input.size(),
-                    request.serviceId().isPresent() ? "SVI" : "",
+                    request.aci().isPresent() ? "ACI" : "",
                     request.pni().isPresent() ? "PNI" : "",
                     request.number().isPresent() ? "NUM" : "",
                     output.size());
@@ -245,17 +243,17 @@ class MergeRecipientHelperTest {
             RecipientAddress ACI_USERNAME
     ) {
 
-        PartialAddresses(ServiceId serviceId, PNI pni, String number, String username) {
-            this(new RecipientAddress(serviceId, pni, number),
-                    new RecipientAddress(serviceId, pni, number, username),
-                    new RecipientAddress(serviceId, null, null),
+        PartialAddresses(ACI aci, PNI pni, String number, String username) {
+            this(new RecipientAddress(aci, pni, number),
+                    new RecipientAddress(aci, pni, number, username),
+                    new RecipientAddress(aci, null, null),
                     new RecipientAddress(null, pni, null),
                     new RecipientAddress(null, null, number),
-                    new RecipientAddress(serviceId, null, number),
-                    new RecipientAddress(serviceId, null, number, username),
+                    new RecipientAddress(aci, null, number),
+                    new RecipientAddress(aci, null, number, username),
                     new RecipientAddress(null, pni, number),
-                    new RecipientAddress(serviceId, pni, null),
-                    new RecipientAddress(serviceId, null, null, username));
+                    new RecipientAddress(aci, pni, null),
+                    new RecipientAddress(aci, null, null, username));
         }
     }
 }
index 9ba193489cc1713c3b8daf1c7cd8447da76343f5..3d0e2263d02a886be6e7479dae03c8c6a812b6ec 100644 (file)
@@ -670,7 +670,18 @@ public class DbusManagerImpl implements Manager {
             }
             return Recipient.newBuilder()
                     .withAddress(new RecipientAddress(null, n))
-                    .withContact(new Contact(contactName, null, null, 0, contactBlocked, false, false, false))
+                    .withContact(new Contact(contactName,
+                            null,
+                            null,
+                            null,
+                            0,
+                            0,
+                            false,
+                            contactBlocked,
+                            false,
+                            false,
+                            false,
+                            null))
                     .build();
         }).filter(Objects::nonNull).toList();
     }