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