]> nmode's Git Repositories - signal-cli/commitdiff
Store profile phone number sharing mode and discoverable state
authorAsamK <asamk@gmx.de>
Tue, 16 Apr 2024 19:55:50 +0000 (21:55 +0200)
committerAsamK <asamk@gmx.de>
Wed, 17 Apr 2024 19:08:09 +0000 (21:08 +0200)
12 files changed:
lib/src/main/java/org/asamk/signal/manager/api/PhoneNumberSharingMode.java
lib/src/main/java/org/asamk/signal/manager/api/Profile.java
lib/src/main/java/org/asamk/signal/manager/api/Recipient.java
lib/src/main/java/org/asamk/signal/manager/helper/ProfileHelper.java
lib/src/main/java/org/asamk/signal/manager/helper/RecipientHelper.java
lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.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/recipients/LegacyRecipientStore2.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/Recipient.java
lib/src/main/java/org/asamk/signal/manager/storage/recipients/RecipientStore.java
lib/src/main/java/org/asamk/signal/manager/util/ProfileUtils.java

index 9efa01be17f82caff563bdc99a918f16606733f3..fce5630500b4aab8fde483462c5caa8f94614e2e 100644 (file)
@@ -3,5 +3,16 @@ package org.asamk.signal.manager.api;
 public enum PhoneNumberSharingMode {
     EVERYBODY,
     CONTACTS,
 public enum PhoneNumberSharingMode {
     EVERYBODY,
     CONTACTS,
-    NOBODY,
+    NOBODY;
+
+    public static PhoneNumberSharingMode valueOfOrNull(String value) {
+        if (value == null) {
+            return null;
+        }
+        try {
+            return valueOf(value);
+        } catch (IllegalArgumentException ignored) {
+            return null;
+        }
+    }
 }
 }
index 33117188687675c4799642a36219cc4769f80779..5b1ecf3a07e76e5b6b251cfe5884f8c0a4c4f388 100644 (file)
@@ -26,6 +26,8 @@ public class Profile {
 
     private final Set<Capability> capabilities;
 
 
     private final Set<Capability> capabilities;
 
+    private final PhoneNumberSharingMode phoneNumberSharingMode;
+
     public Profile(
             final long lastUpdateTimestamp,
             final String givenName,
     public Profile(
             final long lastUpdateTimestamp,
             final String givenName,
@@ -35,7 +37,8 @@ public class Profile {
             final String avatarUrlPath,
             final byte[] mobileCoinAddress,
             final UnidentifiedAccessMode unidentifiedAccessMode,
             final String avatarUrlPath,
             final byte[] mobileCoinAddress,
             final UnidentifiedAccessMode unidentifiedAccessMode,
-            final Set<Capability> capabilities
+            final Set<Capability> capabilities,
+            final PhoneNumberSharingMode phoneNumberSharingMode
     ) {
         this.lastUpdateTimestamp = lastUpdateTimestamp;
         this.givenName = givenName;
     ) {
         this.lastUpdateTimestamp = lastUpdateTimestamp;
         this.givenName = givenName;
@@ -46,6 +49,7 @@ public class Profile {
         this.mobileCoinAddress = mobileCoinAddress;
         this.unidentifiedAccessMode = unidentifiedAccessMode;
         this.capabilities = capabilities;
         this.mobileCoinAddress = mobileCoinAddress;
         this.unidentifiedAccessMode = unidentifiedAccessMode;
         this.capabilities = capabilities;
+        this.phoneNumberSharingMode = phoneNumberSharingMode;
     }
 
     private Profile(final Builder builder) {
     }
 
     private Profile(final Builder builder) {
@@ -58,6 +62,7 @@ public class Profile {
         mobileCoinAddress = builder.mobileCoinAddress;
         unidentifiedAccessMode = builder.unidentifiedAccessMode;
         capabilities = builder.capabilities;
         mobileCoinAddress = builder.mobileCoinAddress;
         unidentifiedAccessMode = builder.unidentifiedAccessMode;
         capabilities = builder.capabilities;
+        phoneNumberSharingMode = builder.phoneNumberSharingMode;
     }
 
     public static Builder newBuilder() {
     }
 
     public static Builder newBuilder() {
@@ -136,6 +141,10 @@ public class Profile {
         return capabilities;
     }
 
         return capabilities;
     }
 
+    public PhoneNumberSharingMode getPhoneNumberSharingMode() {
+        return phoneNumberSharingMode;
+    }
+
     public enum UnidentifiedAccessMode {
         UNKNOWN,
         DISABLED,
     public enum UnidentifiedAccessMode {
         UNKNOWN,
         DISABLED,
@@ -200,6 +209,7 @@ public class Profile {
         private byte[] mobileCoinAddress;
         private UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
         private Set<Capability> capabilities = Collections.emptySet();
         private byte[] mobileCoinAddress;
         private UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
         private Set<Capability> capabilities = Collections.emptySet();
+        private PhoneNumberSharingMode phoneNumberSharingMode;
         private long lastUpdateTimestamp = 0;
 
         private Builder() {
         private long lastUpdateTimestamp = 0;
 
         private Builder() {
@@ -240,6 +250,11 @@ public class Profile {
             return this;
         }
 
             return this;
         }
 
+        public Builder withPhoneNumberSharingMode(final PhoneNumberSharingMode val) {
+            phoneNumberSharingMode = val;
+            return this;
+        }
+
         public Profile build() {
             return new Profile(this);
         }
         public Profile build() {
             return new Profile(this);
         }
index 52b68e58803aaaafe69d85c23972ce82714817b9..9685bcab28d667e8e68cf6b8f12672a05fbe5834 100644 (file)
@@ -20,13 +20,16 @@ public class Recipient {
 
     private final Profile profile;
 
 
     private final Profile profile;
 
+    private final Boolean discoverable;
+
     public Recipient(
             final RecipientId recipientId,
             final RecipientAddress address,
             final Contact contact,
             final ProfileKey profileKey,
             final ExpiringProfileKeyCredential expiringProfileKeyCredential,
     public Recipient(
             final RecipientId recipientId,
             final RecipientAddress address,
             final Contact contact,
             final ProfileKey profileKey,
             final ExpiringProfileKeyCredential expiringProfileKeyCredential,
-            final Profile profile
+            final Profile profile,
+            final Boolean discoverable
     ) {
         this.recipientId = recipientId;
         this.address = address;
     ) {
         this.recipientId = recipientId;
         this.address = address;
@@ -34,6 +37,7 @@ public class Recipient {
         this.profileKey = profileKey;
         this.expiringProfileKeyCredential = expiringProfileKeyCredential;
         this.profile = profile;
         this.profileKey = profileKey;
         this.expiringProfileKeyCredential = expiringProfileKeyCredential;
         this.profile = profile;
+        this.discoverable = discoverable;
     }
 
     private Recipient(final Builder builder) {
     }
 
     private Recipient(final Builder builder) {
@@ -41,8 +45,9 @@ public class Recipient {
         address = builder.address;
         contact = builder.contact;
         profileKey = builder.profileKey;
         address = builder.address;
         contact = builder.contact;
         profileKey = builder.profileKey;
-        expiringProfileKeyCredential = builder.expiringProfileKeyCredential1;
+        expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
         profile = builder.profile;
         profile = builder.profile;
+        discoverable = builder.discoverable;
     }
 
     public static Builder newBuilder() {
     }
 
     public static Builder newBuilder() {
@@ -55,7 +60,7 @@ public class Recipient {
         builder.address = copy.getAddress();
         builder.contact = copy.getContact();
         builder.profileKey = copy.getProfileKey();
         builder.address = copy.getAddress();
         builder.contact = copy.getContact();
         builder.profileKey = copy.getProfileKey();
-        builder.expiringProfileKeyCredential1 = copy.getExpiringProfileKeyCredential();
+        builder.expiringProfileKeyCredential = copy.getExpiringProfileKeyCredential();
         builder.profile = copy.getProfile();
         return builder;
     }
         builder.profile = copy.getProfile();
         return builder;
     }
@@ -84,6 +89,10 @@ public class Recipient {
         return profile;
     }
 
         return profile;
     }
 
+    public Boolean getDiscoverable() {
+        return discoverable;
+    }
+
     @Override
     public boolean equals(final Object o) {
         if (this == o) return true;
     @Override
     public boolean equals(final Object o) {
         if (this == o) return true;
@@ -108,8 +117,9 @@ public class Recipient {
         private RecipientAddress address;
         private Contact contact;
         private ProfileKey profileKey;
         private RecipientAddress address;
         private Contact contact;
         private ProfileKey profileKey;
-        private ExpiringProfileKeyCredential expiringProfileKeyCredential1;
+        private ExpiringProfileKeyCredential expiringProfileKeyCredential;
         private Profile profile;
         private Profile profile;
+        private Boolean discoverable;
 
         private Builder() {
         }
 
         private Builder() {
         }
@@ -135,7 +145,7 @@ public class Recipient {
         }
 
         public Builder withExpiringProfileKeyCredential(final ExpiringProfileKeyCredential val) {
         }
 
         public Builder withExpiringProfileKeyCredential(final ExpiringProfileKeyCredential val) {
-            expiringProfileKeyCredential1 = val;
+            expiringProfileKeyCredential = val;
             return this;
         }
 
             return this;
         }
 
@@ -144,6 +154,11 @@ public class Recipient {
             return this;
         }
 
             return this;
         }
 
+        public Builder withDiscoverable(final Boolean val) {
+            discoverable = val;
+            return this;
+        }
+
         public Recipient build() {
             return new Recipient(this);
         }
         public Recipient build() {
             return new Recipient(this);
         }
index 5d6b90d453316729ddf95359432ab273bfb56483..4a2b79bd99d5951e4c5d5b35b2418b678940b00a 100644 (file)
@@ -363,6 +363,7 @@ public final class ProfileHelper {
 
             logger.trace("Storing profile");
             account.getProfileStore().storeProfile(recipientId, newProfile);
 
             logger.trace("Storing profile");
             account.getProfileStore().storeProfile(recipientId, newProfile);
+            account.getRecipientStore().markRegistered(recipientId, true);
 
             logger.trace("Done handling retrieved profile");
         }).doOnError(e -> {
 
             logger.trace("Done handling retrieved profile");
         }).doOnError(e -> {
@@ -374,6 +375,10 @@ public final class ProfileHelper {
                     .withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN)
                     .withCapabilities(Set.of())
                     .build();
                     .withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN)
                     .withCapabilities(Set.of())
                     .build();
+            if (e instanceof NotFoundException) {
+                logger.debug("Marking recipient {} as unregistered after 404 profile fetch.", recipientId);
+                account.getRecipientStore().markRegistered(recipientId, false);
+            }
 
             account.getProfileStore().storeProfile(recipientId, newProfile);
         });
 
             account.getProfileStore().storeProfile(recipientId, newProfile);
         });
index 3bdd822768f64aaec4370613c8d28b799ea2bd6f..092cff6f46b2ba39de9fc0706b2dbd45932535ef 100644 (file)
@@ -187,7 +187,8 @@ public class RecipientHelper {
 
         final var unregisteredUsers = new HashSet<>(numbers);
         unregisteredUsers.removeAll(registeredUsers.keySet());
 
         final var unregisteredUsers = new HashSet<>(numbers);
         unregisteredUsers.removeAll(registeredUsers.keySet());
-        account.getRecipientStore().markUnregistered(unregisteredUsers);
+        account.getRecipientStore().markUndiscoverablePossiblyUnregistered(unregisteredUsers);
+        account.getRecipientStore().markDiscoverable(registeredUsers.keySet());
 
         return registeredUsers;
     }
 
         return registeredUsers;
     }
index b43dfba45c0f91efb7cff2259abace3103beb28c..8b32b19bd9aa0755725b44468645c287b4092575 100644 (file)
@@ -1310,7 +1310,8 @@ public class ManagerImpl implements Manager {
                         s.getContact(),
                         s.getProfileKey(),
                         s.getExpiringProfileKeyCredential(),
                         s.getContact(),
                         s.getProfileKey(),
                         s.getExpiringProfileKeyCredential(),
-                        s.getProfile()))
+                        s.getProfile(),
+                        s.getDiscoverable()))
                 .toList();
     }
 
                 .toList();
     }
 
index bf751fbca1b226f72eaccf5df8a9ad4c8deae92c..3d9fad9c90a7cad9b53cb76cf32d75eeed317299 100644 (file)
@@ -33,7 +33,7 @@ import java.util.UUID;
 public class AccountDatabase extends Database {
 
     private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
 public class AccountDatabase extends Database {
 
     private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
-    private static final long DATABASE_VERSION = 25;
+    private static final long DATABASE_VERSION = 26;
 
     private AccountDatabase(final HikariDataSource dataSource) {
         super(logger, DATABASE_VERSION, dataSource);
 
     private AccountDatabase(final HikariDataSource dataSource) {
         super(logger, DATABASE_VERSION, dataSource);
@@ -591,6 +591,15 @@ public class AccountDatabase extends Database {
                                         """);
             }
         }
                                         """);
             }
         }
+        if (oldVersion < 26) {
+            logger.debug("Updating database: Create discoverabel and profile_phone_number_sharing columns");
+            try (final var statement = connection.createStatement()) {
+                statement.executeUpdate("""
+                                        ALTER TABLE recipient ADD discoverable INTEGER;
+                                        ALTER TABLE recipient ADD profile_phone_number_sharing TEXT;
+                                        """);
+            }
+        }
     }
 
     private static void createUuidMappingTable(
     }
 
     private static void createUuidMappingTable(
index a3a98c6c1fd9d6aec492eaecd8155c473af49ee2..a65d157d431e0800ad8891951e557c1c088f4f33 100644 (file)
@@ -914,7 +914,8 @@ public class SignalAccount implements Closeable {
                                     : profile.getUnidentifiedAccess() != null
                                             ? Profile.UnidentifiedAccessMode.ENABLED
                                             : Profile.UnidentifiedAccessMode.DISABLED,
                                     : profile.getUnidentifiedAccess() != null
                                             ? Profile.UnidentifiedAccessMode.ENABLED
                                             : Profile.UnidentifiedAccessMode.DISABLED,
-                            capabilities);
+                            capabilities,
+                            null);
                     getProfileStore().storeProfile(recipientId, newProfile);
                 }
             }
                     getProfileStore().storeProfile(recipientId, newProfile);
                 }
             }
index 186ac517773bbbceb38285a59ba8f04cab5cbc73..e7aff6405ebd52fdebeda33bad7a5ec898352d09 100644 (file)
@@ -87,7 +87,8 @@ public class LegacyRecipientStore2 {
                             r.profile.capabilities.stream()
                                     .map(Profile.Capability::valueOfOrNull)
                                     .filter(Objects::nonNull)
                             r.profile.capabilities.stream()
                                     .map(Profile.Capability::valueOfOrNull)
                                     .filter(Objects::nonNull)
-                                    .collect(Collectors.toSet()));
+                                    .collect(Collectors.toSet()),
+                            null);
                 }
 
                 return new Recipient(recipientId,
                 }
 
                 return new Recipient(recipientId,
@@ -96,6 +97,7 @@ public class LegacyRecipientStore2 {
                         profileKey,
                         expiringProfileKeyCredential,
                         profile,
                         profileKey,
                         expiringProfileKeyCredential,
                         profile,
+                        null,
                         null);
             }).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
 
                         null);
             }).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
 
index 1d5fb9c81094bfcca0d74f74cc15a116a0410b19..c5e1181b8165dae401d4176dae0784dad3afed0e 100644 (file)
@@ -21,6 +21,8 @@ public class Recipient {
 
     private final Profile profile;
 
 
     private final Profile profile;
 
+    private final Boolean discoverable;
+
     private final byte[] storageRecord;
 
     public Recipient(
     private final byte[] storageRecord;
 
     public Recipient(
@@ -30,6 +32,7 @@ public class Recipient {
             final ProfileKey profileKey,
             final ExpiringProfileKeyCredential expiringProfileKeyCredential,
             final Profile profile,
             final ProfileKey profileKey,
             final ExpiringProfileKeyCredential expiringProfileKeyCredential,
             final Profile profile,
+            final Boolean discoverable,
             final byte[] storageRecord
     ) {
         this.recipientId = recipientId;
             final byte[] storageRecord
     ) {
         this.recipientId = recipientId;
@@ -38,6 +41,7 @@ public class Recipient {
         this.profileKey = profileKey;
         this.expiringProfileKeyCredential = expiringProfileKeyCredential;
         this.profile = profile;
         this.profileKey = profileKey;
         this.expiringProfileKeyCredential = expiringProfileKeyCredential;
         this.profile = profile;
+        this.discoverable = discoverable;
         this.storageRecord = storageRecord;
     }
 
         this.storageRecord = storageRecord;
     }
 
@@ -48,6 +52,7 @@ public class Recipient {
         profileKey = builder.profileKey;
         expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
         profile = builder.profile;
         profileKey = builder.profileKey;
         expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
         profile = builder.profile;
+        discoverable = builder.discoverable;
         storageRecord = builder.storageRecord;
     }
 
         storageRecord = builder.storageRecord;
     }
 
@@ -91,6 +96,10 @@ public class Recipient {
         return profile;
     }
 
         return profile;
     }
 
+    public Boolean getDiscoverable() {
+        return discoverable;
+    }
+
     public byte[] getStorageRecord() {
         return storageRecord;
     }
     public byte[] getStorageRecord() {
         return storageRecord;
     }
@@ -121,6 +130,7 @@ public class Recipient {
         private ProfileKey profileKey;
         private ExpiringProfileKeyCredential expiringProfileKeyCredential;
         private Profile profile;
         private ProfileKey profileKey;
         private ExpiringProfileKeyCredential expiringProfileKeyCredential;
         private Profile profile;
+        private Boolean discoverable;
         private byte[] storageRecord;
 
         private Builder() {
         private byte[] storageRecord;
 
         private Builder() {
@@ -156,6 +166,11 @@ public class Recipient {
             return this;
         }
 
             return this;
         }
 
+        public Builder withDiscoverable(final Boolean val) {
+            discoverable = val;
+            return this;
+        }
+
         public Builder withStorageRecord(final byte[] val) {
             storageRecord = val;
             return this;
         public Builder withStorageRecord(final byte[] val) {
             storageRecord = val;
             return this;
index 5c12a1ec66fcd5e25e05f46e66ae65c3474b2be7..6428d20216187cb61a60522748b9378998bd7066 100644 (file)
@@ -2,6 +2,7 @@ package org.asamk.signal.manager.storage.recipients;
 
 import org.asamk.signal.manager.api.Contact;
 import org.asamk.signal.manager.api.Pair;
 
 import org.asamk.signal.manager.api.Contact;
 import org.asamk.signal.manager.api.Pair;
+import org.asamk.signal.manager.api.PhoneNumberSharingMode;
 import org.asamk.signal.manager.api.Profile;
 import org.asamk.signal.manager.api.UnregisteredRecipientException;
 import org.asamk.signal.manager.storage.Database;
 import org.asamk.signal.manager.api.Profile;
 import org.asamk.signal.manager.api.UnregisteredRecipientException;
 import org.asamk.signal.manager.storage.Database;
@@ -64,6 +65,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                                       aci TEXT UNIQUE,
                                       pni TEXT UNIQUE,
                                       unregistered_timestamp INTEGER,
                                       aci TEXT UNIQUE,
                                       pni TEXT UNIQUE,
                                       unregistered_timestamp INTEGER,
+                                      discoverable INTEGER,
                                       profile_key BLOB,
                                       profile_key_credential BLOB,
                                       needs_pni_signature INTEGER NOT NULL DEFAULT FALSE,
                                       profile_key BLOB,
                                       profile_key_credential BLOB,
                                       needs_pni_signature INTEGER NOT NULL DEFAULT FALSE,
@@ -92,7 +94,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                                       profile_avatar_url_path TEXT,
                                       profile_mobile_coin_address BLOB,
                                       profile_unidentified_access_mode TEXT,
                                       profile_avatar_url_path TEXT,
                                       profile_mobile_coin_address BLOB,
                                       profile_unidentified_access_mode TEXT,
-                                      profile_capabilities TEXT
+                                      profile_capabilities TEXT,
+                                      profile_phone_number_sharing TEXT
                                     ) STRICT;
                                     """);
         }
                                     ) STRICT;
                                     """);
         }
@@ -354,7 +357,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                        r.number, r.aci, r.pni, r.username,
                        r.profile_key, r.profile_key_credential,
                        r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
                        r.number, r.aci, r.pni, r.username,
                        r.profile_key, r.profile_key_credential,
                        r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
-                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities,
+                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
+                       r.discoverable,
                        r.storage_record
                 FROM %s r
                 WHERE r._id = ?
                        r.storage_record
                 FROM %s r
                 WHERE r._id = ?
@@ -373,7 +377,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                        r.number, r.aci, r.pni, r.username,
                        r.profile_key, r.profile_key_credential,
                        r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
                        r.number, r.aci, r.pni, r.username,
                        r.profile_key, r.profile_key_credential,
                        r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
-                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities,
+                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
+                       r.discoverable,
                        r.storage_record
                 FROM %s r
                 WHERE r.storage_id = ?
                        r.storage_record
                 FROM %s r
                 WHERE r.storage_id = ?
@@ -409,7 +414,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                        r.number, r.aci, r.pni, r.username,
                        r.profile_key, r.profile_key_credential,
                        r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
                        r.number, r.aci, r.pni, r.username,
                        r.profile_key, r.profile_key_credential,
                        r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
-                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities,
+                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
+                       r.discoverable,
                        r.storage_record
                 FROM %s r
                 WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s
                        r.storage_record
                 FROM %s r
                 WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s
@@ -898,15 +904,36 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
         }
     }
 
         }
     }
 
-    public void markUnregistered(final Set<String> unregisteredUsers) {
-        logger.debug("Marking {} numbers as unregistered", unregisteredUsers.size());
+    public void markUndiscoverablePossiblyUnregistered(final Set<String> numbers) {
+        logger.debug("Marking {} numbers as unregistered", numbers.size());
         try (final var connection = database.getConnection()) {
             connection.setAutoCommit(false);
         try (final var connection = database.getConnection()) {
             connection.setAutoCommit(false);
-            for (final var number : unregisteredUsers) {
-                final var recipient = findByNumber(connection, number);
-                if (recipient.isPresent()) {
-                    final var recipientId = recipient.get().id();
-                    markUnregisteredAndSplitIfNecessary(connection, recipientId);
+            for (final var number : numbers) {
+                final var recipientAddress = findByNumber(connection, number);
+                if (recipientAddress.isPresent()) {
+                    final var recipientId = recipientAddress.get().id();
+                    markDiscoverable(connection, recipientId, false);
+                    final var contact = getContact(connection, recipientId);
+                    if (recipientAddress.get().address().aci().isEmpty() || contact.unregisteredTimestamp() != null) {
+                        markUnregisteredAndSplitIfNecessary(connection, recipientId);
+                    }
+                }
+            }
+            connection.commit();
+        } catch (SQLException e) {
+            throw new RuntimeException("Failed update recipient store", e);
+        }
+    }
+
+    public void markDiscoverable(final Set<String> numbers) {
+        logger.debug("Marking {} numbers as discoverable", numbers.size());
+        try (final var connection = database.getConnection()) {
+            connection.setAutoCommit(false);
+            for (final var number : numbers) {
+                final var recipientAddress = findByNumber(connection, number);
+                if (recipientAddress.isPresent()) {
+                    final var recipientId = recipientAddress.get().id();
+                    markDiscoverable(connection, recipientId, true);
                 }
             }
             connection.commit();
                 }
             }
             connection.commit();
@@ -915,6 +942,21 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
         }
     }
 
         }
     }
 
+    public void markRegistered(final RecipientId recipientId, final boolean registered) {
+        logger.debug("Marking {} as registered={}", recipientId, registered);
+        try (final var connection = database.getConnection()) {
+            connection.setAutoCommit(false);
+            if (registered) {
+                markRegistered(connection, recipientId);
+            } else {
+                markUnregistered(connection, recipientId);
+            }
+            connection.commit();
+        } catch (SQLException e) {
+            throw new RuntimeException("Failed update recipient store", e);
+        }
+    }
+
     private void markUnregisteredAndSplitIfNecessary(
             final Connection connection, final RecipientId recipientId
     ) throws SQLException {
     private void markUnregisteredAndSplitIfNecessary(
             final Connection connection, final RecipientId recipientId
     ) throws SQLException {
@@ -927,6 +969,23 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
         }
     }
 
         }
     }
 
+    private void markDiscoverable(
+            final Connection connection, final RecipientId recipientId, final boolean discoverable
+    ) throws SQLException {
+        final var sql = (
+                """
+                UPDATE %s
+                SET discoverable = ?
+                WHERE _id = ?
+                """
+        ).formatted(TABLE_RECIPIENT);
+        try (final var statement = connection.prepareStatement(sql)) {
+            statement.setBoolean(1, discoverable);
+            statement.setLong(2, recipientId.id());
+            statement.executeUpdate();
+        }
+    }
+
     private void markRegistered(
             final Connection connection, final RecipientId recipientId
     ) throws SQLException {
     private void markRegistered(
             final Connection connection, final RecipientId recipientId
     ) throws SQLException {
@@ -949,8 +1008,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
         final var sql = (
                 """
                 UPDATE %s
         final var sql = (
                 """
                 UPDATE %s
-                SET unregistered_timestamp = ?
-                WHERE _id = ? AND unregistered_timestamp IS NULL
+                SET unregistered_timestamp = ?, discoverable = FALSE
+                WHERE _id = ?
                 """
         ).formatted(TABLE_RECIPIENT);
         try (final var statement = connection.prepareStatement(sql)) {
                 """
         ).formatted(TABLE_RECIPIENT);
         try (final var statement = connection.prepareStatement(sql)) {
@@ -985,7 +1044,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
         final var sql = (
                 """
                 UPDATE %s
         final var sql = (
                 """
                 UPDATE %s
-                SET profile_last_update_timestamp = ?, profile_given_name = ?, profile_family_name = ?, profile_about = ?, profile_about_emoji = ?, profile_avatar_url_path = ?, profile_mobile_coin_address = ?, profile_unidentified_access_mode = ?, profile_capabilities = ?
+                SET profile_last_update_timestamp = ?, profile_given_name = ?, profile_family_name = ?, profile_about = ?, profile_about_emoji = ?, profile_avatar_url_path = ?, profile_mobile_coin_address = ?, profile_unidentified_access_mode = ?, profile_capabilities = ?, profile_phone_number_sharing = ?
                 WHERE _id = ?
                 """
         ).formatted(TABLE_RECIPIENT);
                 WHERE _id = ?
                 """
         ).formatted(TABLE_RECIPIENT);
@@ -1002,7 +1061,11 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                     profile == null
                             ? null
                             : profile.getCapabilities().stream().map(Enum::name).collect(Collectors.joining(",")));
                     profile == null
                             ? null
                             : profile.getCapabilities().stream().map(Enum::name).collect(Collectors.joining(",")));
-            statement.setLong(10, recipientId.id());
+            statement.setString(10,
+                    profile == null || profile.getPhoneNumberSharingMode() == null
+                            ? null
+                            : profile.getPhoneNumberSharingMode().name());
+            statement.setLong(11, recipientId.id());
             statement.executeUpdate();
         }
         rotateStorageId(connection, recipientId);
             statement.executeUpdate();
         }
         rotateStorageId(connection, recipientId);
@@ -1396,7 +1459,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
     public Profile getProfile(final Connection connection, final RecipientId recipientId) throws SQLException {
         final var sql = (
                 """
     public Profile getProfile(final Connection connection, final RecipientId recipientId) throws SQLException {
         final var sql = (
                 """
-                SELECT r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities
+                SELECT r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing
                 FROM %s r
                 WHERE r._id = ? AND r.profile_capabilities IS NOT NULL
                 """
                 FROM %s r
                 WHERE r._id = ? AND r.profile_capabilities IS NOT NULL
                 """
@@ -1431,6 +1494,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                 getProfileKeyFromResultSet(resultSet),
                 getExpiringProfileKeyCredentialFromResultSet(resultSet),
                 getProfileFromResultSet(resultSet),
                 getProfileKeyFromResultSet(resultSet),
                 getExpiringProfileKeyCredentialFromResultSet(resultSet),
                 getProfileFromResultSet(resultSet),
+                getDiscoverableFromResultSet(resultSet),
                 getStorageRecordFromResultSet(resultSet));
     }
 
                 getStorageRecordFromResultSet(resultSet));
     }
 
@@ -1453,6 +1517,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                 unregisteredTimestamp == 0 ? null : unregisteredTimestamp);
     }
 
                 unregisteredTimestamp == 0 ? null : unregisteredTimestamp);
     }
 
+    private static Boolean getDiscoverableFromResultSet(final ResultSet resultSet) throws SQLException {
+        final var discoverable = resultSet.getBoolean("discoverable");
+        if (resultSet.wasNull()) {
+            return null;
+        }
+        return discoverable;
+    }
+
     private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException {
         final var profileCapabilities = resultSet.getString("profile_capabilities");
         final var profileUnidentifiedAccessMode = resultSet.getString("profile_unidentified_access_mode");
     private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException {
         final var profileCapabilities = resultSet.getString("profile_capabilities");
         final var profileUnidentifiedAccessMode = resultSet.getString("profile_unidentified_access_mode");
@@ -1471,7 +1543,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
                         : Arrays.stream(profileCapabilities.split(","))
                                 .map(Profile.Capability::valueOfOrNull)
                                 .filter(Objects::nonNull)
                         : Arrays.stream(profileCapabilities.split(","))
                                 .map(Profile.Capability::valueOfOrNull)
                                 .filter(Objects::nonNull)
-                                .collect(Collectors.toSet()));
+                                .collect(Collectors.toSet()),
+                PhoneNumberSharingMode.valueOfOrNull(resultSet.getString("profile_phone_number_sharing")));
     }
 
     private ProfileKey getProfileKeyFromResultSet(ResultSet resultSet) throws SQLException {
     }
 
     private ProfileKey getProfileKeyFromResultSet(ResultSet resultSet) throws SQLException {
index 82ffc8617b4139f49a6438138e406a96c4edfdb6..17e26ee89a80d3ff8c60a75ecbc87edee94df6d7 100644 (file)
@@ -1,6 +1,7 @@
 package org.asamk.signal.manager.util;
 
 import org.asamk.signal.manager.api.Pair;
 package org.asamk.signal.manager.util;
 
 import org.asamk.signal.manager.api.Pair;
+import org.asamk.signal.manager.api.PhoneNumberSharingMode;
 import org.asamk.signal.manager.api.Profile;
 import org.signal.libsignal.protocol.IdentityKey;
 import org.signal.libsignal.protocol.InvalidKeyException;
 import org.asamk.signal.manager.api.Profile;
 import org.signal.libsignal.protocol.IdentityKey;
 import org.signal.libsignal.protocol.InvalidKeyException;
@@ -16,6 +17,7 @@ import org.whispersystems.signalservice.internal.push.PaymentAddress;
 import java.io.IOException;
 import java.util.Base64;
 import java.util.HashSet;
 import java.io.IOException;
 import java.util.Base64;
 import java.util.HashSet;
+import java.util.Optional;
 
 public class ProfileUtils {
 
 
 public class ProfileUtils {
 
@@ -33,11 +35,14 @@ public class ProfileUtils {
         }
 
         try {
         }
 
         try {
-            var name = decrypt(encryptedProfile.getName(), profileCipher);
-            var about = trimZeros(decrypt(encryptedProfile.getAbout(), profileCipher));
-            var aboutEmoji = trimZeros(decrypt(encryptedProfile.getAboutEmoji(), profileCipher));
+            var name = decryptString(encryptedProfile.getName(), profileCipher);
+            var about = decryptString(encryptedProfile.getAbout(), profileCipher);
+            var aboutEmoji = decryptString(encryptedProfile.getAboutEmoji(), profileCipher);
 
             final var nameParts = splitName(name);
 
             final var nameParts = splitName(name);
+            final var remotePhoneNumberSharing = decryptBoolean(encryptedProfile.getPhoneNumberSharing(),
+                    profileCipher).map(v -> v ? PhoneNumberSharingMode.EVERYBODY : PhoneNumberSharingMode.NOBODY)
+                    .orElse(null);
             return new Profile(System.currentTimeMillis(),
                     nameParts.first(),
                     nameParts.second(),
             return new Profile(System.currentTimeMillis(),
                     nameParts.first(),
                     nameParts.second(),
@@ -50,7 +55,8 @@ public class ProfileUtils {
                                     profileCipher,
                                     identityKey.getPublicKey()),
                     getUnidentifiedAccessMode(encryptedProfile, profileCipher),
                                     profileCipher,
                                     identityKey.getPublicKey()),
                     getUnidentifiedAccessMode(encryptedProfile, profileCipher),
-                    getCapabilities(encryptedProfile));
+                    getCapabilities(encryptedProfile),
+                    remotePhoneNumberSharing);
         } catch (InvalidCiphertextException e) {
             logger.debug("Failed to decrypt profile for {}", encryptedProfile.getServiceId(), e);
             return null;
         } catch (InvalidCiphertextException e) {
             logger.debug("Failed to decrypt profile for {}", encryptedProfile.getServiceId(), e);
             return null;
@@ -83,18 +89,28 @@ public class ProfileUtils {
         return capabilities;
     }
 
         return capabilities;
     }
 
-    private static String decrypt(
-            final String encryptedName, final ProfileCipher profileCipher
+    private static String decryptString(
+            final String encrypted, final ProfileCipher profileCipher
     ) throws InvalidCiphertextException {
         try {
     ) throws InvalidCiphertextException {
         try {
-            return encryptedName == null
-                    ? null
-                    : new String(profileCipher.decrypt(Base64.getDecoder().decode(encryptedName)));
+            return encrypted == null ? null : profileCipher.decryptString(Base64.getDecoder().decode(encrypted));
         } catch (IllegalArgumentException e) {
             return null;
         }
     }
 
         } catch (IllegalArgumentException e) {
             return null;
         }
     }
 
+    private static Optional<Boolean> decryptBoolean(
+            final String encrypted, final ProfileCipher profileCipher
+    ) throws InvalidCiphertextException {
+        try {
+            return encrypted == null
+                    ? Optional.empty()
+                    : profileCipher.decryptBoolean(Base64.getDecoder().decode(encrypted));
+        } catch (IllegalArgumentException e) {
+            return Optional.empty();
+        }
+    }
+
     private static byte[] decryptAndVerifyMobileCoinAddress(
             final byte[] encryptedPaymentAddress, final ProfileCipher profileCipher, final ECPublicKey publicKey
     ) throws InvalidCiphertextException {
     private static byte[] decryptAndVerifyMobileCoinAddress(
             final byte[] encryptedPaymentAddress, final ProfileCipher profileCipher, final ECPublicKey publicKey
     ) throws InvalidCiphertextException {
@@ -129,13 +145,4 @@ public class ProfileUtils {
             default -> new Pair<>(parts[0], parts[1]);
         };
     }
             default -> new Pair<>(parts[0], parts[1]);
         };
     }
-
-    static String trimZeros(String str) {
-        if (str == null) {
-            return null;
-        }
-
-        int pos = str.indexOf(0);
-        return pos == -1 ? str : str.substring(0, pos);
-    }
 }
 }