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