From 3357475fc40b3d940876d4e6e3270e6fdbcbe6e0 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 19 Oct 2020 17:16:40 -0400 Subject: [PATCH] Move capabilities into a single column. --- .../securesms/database/RecipientDatabase.java | 53 ++++--- .../database/helpers/SQLCipherOpenHelper.java | 10 +- .../logsubmit/LogSectionCapabilities.java | 6 +- .../securesms/recipients/Recipient.java | 27 +--- .../recipients/RecipientDetails.java | 3 - .../thoughtcrime/securesms/util/Bitmask.java | 74 +++++++++ .../securesms/util/CursorUtil.java | 8 + .../securesms/util/BitmaskTest.java | 148 ++++++++++++++++++ .../api/profiles/SignalServiceProfile.java | 7 - 9 files changed, 276 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/Bitmask.java create mode 100644 app/src/test/java/org/thoughtcrime/securesms/util/BitmaskTest.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index dc2186e7d5..afbdf76584 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.groups.v2.ProfileKeySet; import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob; +import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.ProfileName; @@ -40,6 +41,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate; import org.thoughtcrime.securesms.storage.StorageSyncModels; import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.Bitmask; import org.thoughtcrime.securesms.util.CursorUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.IdentityUtil; @@ -114,8 +116,7 @@ public class RecipientDatabase extends Database { private static final String LAST_PROFILE_FETCH = "last_profile_fetch"; private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode"; private static final String FORCE_SMS_SELECTION = "force_sms_selection"; - private static final String UUID_CAPABILITY = "uuid_supported"; - private static final String GROUPS_V2_CAPABILITY = "gv2_capability"; + private static final String CAPABILITIES = "capabilities"; private static final String STORAGE_SERVICE_ID = "storage_service_key"; private static final String DIRTY = "dirty"; private static final String PROFILE_GIVEN_NAME = "signal_profile_name"; @@ -129,6 +130,11 @@ public class RecipientDatabase extends Database { private static final String IDENTITY_STATUS = "identity_status"; private static final String IDENTITY_KEY = "identity_key"; + private static final class Capabilities { + static final int BIT_LENGTH = 2; + static final int GROUPS_V2 = 0; + } + private static final String[] RECIPIENT_PROJECTION = new String[] { ID, UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE, BLOCKED, MESSAGE_RINGTONE, CALL_RINGTONE, MESSAGE_VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED, @@ -138,7 +144,7 @@ public class RecipientDatabase extends Database { NOTIFICATION_CHANNEL, UNIDENTIFIED_ACCESS_MODE, FORCE_SMS_SELECTION, - UUID_CAPABILITY, GROUPS_V2_CAPABILITY, + CAPABILITIES, STORAGE_SERVICE_ID, DIRTY, MENTION_SETTING }; @@ -329,12 +335,11 @@ public class RecipientDatabase extends Database { LAST_PROFILE_FETCH + " INTEGER DEFAULT 0, " + UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " + FORCE_SMS_SELECTION + " INTEGER DEFAULT 0, " + - UUID_CAPABILITY + " INTEGER DEFAULT " + Recipient.Capability.UNKNOWN.serialize() + ", " + - GROUPS_V2_CAPABILITY + " INTEGER DEFAULT " + Recipient.Capability.UNKNOWN.serialize() + ", " + STORAGE_SERVICE_ID + " TEXT UNIQUE DEFAULT NULL, " + DIRTY + " INTEGER DEFAULT " + DirtyState.CLEAN.getId() + ", " + MENTION_SETTING + " INTEGER DEFAULT " + MentionSetting.ALWAYS_NOTIFY.getId() + ", " + - STORAGE_PROTO + " TEXT DEFAULT NULL);"; + STORAGE_PROTO + " TEXT DEFAULT NULL, " + + CAPABILITIES + " INTEGER DEFAULT 0);"; private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID + " FROM " + TABLE_NAME + @@ -503,6 +508,7 @@ public class RecipientDatabase extends Database { if (transactionSuccessful) { if (recipientNeedingRefresh != null) { Recipient.live(recipientNeedingRefresh).refresh(); + RetrieveProfileJob.enqueue(recipientNeedingRefresh); } if (remapped != null) { @@ -1173,8 +1179,7 @@ public class RecipientDatabase extends Database { String notificationChannel = CursorUtil.requireString(cursor, NOTIFICATION_CHANNEL); int unidentifiedAccessMode = CursorUtil.requireInt(cursor, UNIDENTIFIED_ACCESS_MODE); boolean forceSmsSelection = CursorUtil.requireBoolean(cursor, FORCE_SMS_SELECTION); - int uuidCapabilityValue = CursorUtil.requireInt(cursor, UUID_CAPABILITY); - int groupsV2CapabilityValue = CursorUtil.requireInt(cursor, GROUPS_V2_CAPABILITY); + long capabilities = CursorUtil.requireLong(cursor, CAPABILITIES); String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID); int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING); @@ -1240,8 +1245,7 @@ public class RecipientDatabase extends Database { notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), forceSmsSelection, - Recipient.Capability.deserialize(uuidCapabilityValue), - Recipient.Capability.deserialize(groupsV2CapabilityValue), + capabilities, InsightsBannerTier.fromId(insightsBannerTier), storageKey, MentionSetting.fromId(mentionSettingId), @@ -1404,9 +1408,13 @@ public class RecipientDatabase extends Database { } public void setCapabilities(@NonNull RecipientId id, @NonNull SignalServiceProfile.Capabilities capabilities) { - ContentValues values = new ContentValues(2); - values.put(UUID_CAPABILITY, Recipient.Capability.fromBoolean(capabilities.isUuid()).serialize()); - values.put(GROUPS_V2_CAPABILITY, Recipient.Capability.fromBoolean(capabilities.isGv2()).serialize()); + long value = 0; + + value = Bitmask.update(value, Capabilities.GROUPS_V2, Capabilities.BIT_LENGTH, Recipient.Capability.fromBoolean(capabilities.isGv2()).serialize()); + + ContentValues values = new ContentValues(1); + values.put(CAPABILITIES, value); + if (update(id, values)) { Recipient.live(id).refresh(); } @@ -2372,7 +2380,7 @@ public class RecipientDatabase extends Database { uuidValues.put(SYSTEM_PHONE_LABEL, e164Settings.getSystemPhoneLabel()); uuidValues.put(SYSTEM_CONTACT_URI, e164Settings.getSystemContactUri()); uuidValues.put(PROFILE_SHARING, uuidSettings.isProfileSharing() || e164Settings.isProfileSharing()); - uuidValues.put(GROUPS_V2_CAPABILITY, uuidSettings.getGroupsV2Capability() != Recipient.Capability.UNKNOWN ? uuidSettings.getGroupsV2Capability().serialize() : e164Settings.getGroupsV2Capability().serialize()); + uuidValues.put(CAPABILITIES, Math.max(uuidSettings.getCapabilities(), e164Settings.getCapabilities())); uuidValues.put(MENTION_SETTING, uuidSettings.getMentionSetting() != MentionSetting.ALWAYS_NOTIFY ? uuidSettings.getMentionSetting().getId() : e164Settings.getMentionSetting().getId()); if (uuidSettings.getProfileKey() != null) { updateProfileValuesForMerge(uuidValues, uuidSettings); @@ -2589,7 +2597,7 @@ public class RecipientDatabase extends Database { private final String notificationChannel; private final UnidentifiedAccessMode unidentifiedAccessMode; private final boolean forceSmsSelection; - private final Recipient.Capability uuidCapability; + private final long capabilities; private final Recipient.Capability groupsV2Capability; private final InsightsBannerTier insightsBannerTier; private final byte[] storageId; @@ -2627,8 +2635,7 @@ public class RecipientDatabase extends Database { @Nullable String notificationChannel, @NonNull UnidentifiedAccessMode unidentifiedAccessMode, boolean forceSmsSelection, - Recipient.Capability uuidCapability, - Recipient.Capability groupsV2Capability, + long capabilities, @NonNull InsightsBannerTier insightsBannerTier, @Nullable byte[] storageId, @NonNull MentionSetting mentionSetting, @@ -2665,8 +2672,8 @@ public class RecipientDatabase extends Database { this.notificationChannel = notificationChannel; this.unidentifiedAccessMode = unidentifiedAccessMode; this.forceSmsSelection = forceSmsSelection; - this.uuidCapability = uuidCapability; - this.groupsV2Capability = groupsV2Capability; + this.capabilities = capabilities; + this.groupsV2Capability = Recipient.Capability.deserialize((int) Bitmask.read(capabilities, Capabilities.GROUPS_V2, Capabilities.BIT_LENGTH)); this.insightsBannerTier = insightsBannerTier; this.storageId = storageId; this.mentionSetting = mentionSetting; @@ -2801,10 +2808,6 @@ public class RecipientDatabase extends Database { return forceSmsSelection; } - public Recipient.Capability getUuidCapability() { - return uuidCapability; - } - public Recipient.Capability getGroupsV2Capability() { return groupsV2Capability; } @@ -2821,6 +2824,10 @@ public class RecipientDatabase extends Database { return syncExtras; } + long getCapabilities() { + return capabilities; + } + /** * A bundle of data that's only necessary when syncing to storage service, not for a * {@link Recipient}. diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index df2228e43d..47d749fef8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -157,8 +157,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int MENTION_CLEANUP = 76; private static final int MENTION_CLEANUP_V2 = 77; private static final int REACTION_CLEANUP = 78; + private static final int CAPABILITIES_REFACTOR = 79; - private static final int DATABASE_VERSION = 78; + private static final int DATABASE_VERSION = 79; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -1131,6 +1132,13 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.update("sms", values, "remote_deleted = ?", new String[] { "1" }); } + if (oldVersion < CAPABILITIES_REFACTOR) { + db.execSQL("ALTER TABLE recipient ADD COLUMN capabilities INTEGER DEFAULT 0"); + + db.execSQL("UPDATE recipient SET capabilities = 1 WHERE gv2_capability = 1"); + db.execSQL("UPDATE recipient SET capabilities = 2 WHERE gv2_capability = -1"); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionCapabilities.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionCapabilities.java index ec7329c35f..7a7a8b4377 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionCapabilities.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/LogSectionCapabilities.java @@ -30,9 +30,7 @@ public final class LogSectionCapabilities implements LogSection { AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(false); - return new StringBuilder().append("Local device UUID : ").append(capabilities.isUuid()).append("\n") - .append("Global UUID : ").append(self.getUuidCapability()).append("\n") - .append("Local device GV2 : ").append(capabilities.isGv2()).append("\n") - .append("Global GV2 : ").append(self.getGroupsV2Capability()).append("\n"); + return new StringBuilder().append("Local device GV2: ").append(capabilities.isGv2()).append("\n") + .append("Global GV2 : ").append(self.getGroupsV2Capability()).append("\n"); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index 1dd46df3cc..852676d22a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -97,7 +97,6 @@ public class Recipient { private final String notificationChannel; private final UnidentifiedAccessMode unidentifiedAccessMode; private final boolean forceSmsSelection; - private final Capability uuidCapability; private final Capability groupsV2Capability; private final InsightsBannerTier insightsBannerTier; private final byte[] storageId; @@ -311,7 +310,6 @@ public class Recipient { this.notificationChannel = null; this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED; this.forceSmsSelection = false; - this.uuidCapability = Capability.UNKNOWN; this.groupsV2Capability = Capability.UNKNOWN; this.storageId = null; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; @@ -353,7 +351,6 @@ public class Recipient { this.notificationChannel = details.notificationChannel; this.unidentifiedAccessMode = details.unidentifiedAccessMode; this.forceSmsSelection = details.forceSmsSelection; - this.uuidCapability = details.uuidCapability; this.groupsV2Capability = details.groupsV2Capability; this.storageId = details.storageId; this.mentionSetting = details.mentionSetting; @@ -740,25 +737,10 @@ public class Recipient { return forceSmsSelection; } - /** - * @return True if this recipient can support receiving UUID-only messages, otherwise false. - */ - public boolean isUuidSupported() { - if (FeatureFlags.usernames()) { - return true; - } else { - return uuidCapability == Capability.SUPPORTED; - } - } - public Capability getGroupsV2Capability() { return groupsV2Capability; } - public Capability getUuidCapability() { - return uuidCapability; - } - public @Nullable byte[] getProfileKey() { return profileKey; } @@ -825,7 +807,7 @@ public class Recipient { public enum Capability { UNKNOWN(0), SUPPORTED(1), - NOT_SUPPORTED(-1); + NOT_SUPPORTED(2); private final int value; @@ -839,9 +821,10 @@ public class Recipient { public static Capability deserialize(int value) { switch (value) { - case 1 : return SUPPORTED; - case -1 : return NOT_SUPPORTED; - default : return UNKNOWN; + case 0: return UNKNOWN; + case 1: return SUPPORTED; + case 2: return NOT_SUPPORTED; + default: throw new IllegalArgumentException(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java index 6adafba8c1..b2cc7152db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -59,7 +59,6 @@ public class RecipientDetails { final String notificationChannel; final UnidentifiedAccessMode unidentifiedAccessMode; final boolean forceSmsSelection; - final Recipient.Capability uuidCapability; final Recipient.Capability groupsV2Capability; final InsightsBannerTier insightsBannerTier; final byte[] storageId; @@ -104,7 +103,6 @@ public class RecipientDetails { this.notificationChannel = settings.getNotificationChannel(); this.unidentifiedAccessMode = settings.getUnidentifiedAccessMode(); this.forceSmsSelection = settings.isForceSmsSelection(); - this.uuidCapability = settings.getUuidCapability(); this.groupsV2Capability = settings.getGroupsV2Capability(); this.insightsBannerTier = settings.getInsightsBannerTier(); this.storageId = settings.getStorageId(); @@ -152,7 +150,6 @@ public class RecipientDetails { this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN; this.forceSmsSelection = false; this.name = null; - this.uuidCapability = Recipient.Capability.UNKNOWN; this.groupsV2Capability = Recipient.Capability.UNKNOWN; this.storageId = null; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Bitmask.java b/app/src/main/java/org/thoughtcrime/securesms/util/Bitmask.java new file mode 100644 index 0000000000..176931348b --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Bitmask.java @@ -0,0 +1,74 @@ +package org.thoughtcrime.securesms.util; + +import org.whispersystems.libsignal.util.guava.Preconditions; + +import java.util.Locale; + +/** + * A set of utilities to make working with Bitmasks easier. + */ +public final class Bitmask { + + /** + * Reads a bitmasked boolean from a long at the requested position. + */ + public static boolean read(long value, int position) { + return read(value, position, 1) > 0; + } + + /** + * Reads a bitmasked value from a long at the requested position. + * + * @param value The value your are reading state from + * @param position The position you'd like to read from + * @param flagBitSize How many bits are in each flag + * @return The value at the requested position + */ + public static long read(long value, int position, int flagBitSize) { + Preconditions.checkArgument(flagBitSize >= 0, "Must have a positive bit size! size: " + flagBitSize); + + int bitsToShift = position * flagBitSize; + Preconditions.checkArgument(bitsToShift + flagBitSize <= 64 && position >= 0, String.format(Locale.US, "Your position is out of bounds! position: %d, flagBitSize: %d", position, flagBitSize)); + + long shifted = value >>> bitsToShift; + long mask = twoToThe(flagBitSize) - 1; + + return shifted & mask; + } + + /** + * Sets the value at the specified position in a single-bit bitmasked long. + */ + public static long update(long existing, int position, boolean value) { + return update(existing, position, 1, value ? 1 : 0); + } + + /** + * Updates the value in a bitmasked long. + * + * @param existing The existing state of the bitmask + * @param position The position you'd like to update + * @param flagBitSize How many bits are in each flag + * @param value The value you'd like to set at the specified position + * @return The updated bitmask + */ + public static long update(long existing, int position, int flagBitSize, long value) { + Preconditions.checkArgument(flagBitSize >= 0, "Must have a positive bit size! size: " + flagBitSize); + Preconditions.checkArgument(value >= 0, "Value must be positive! value: " + value); + Preconditions.checkArgument(value < twoToThe(flagBitSize), String.format(Locale.US, "Value is larger than you can hold for the given bitsize! value: %d, flagBitSize: %d", value, flagBitSize)); + + int bitsToShift = position * flagBitSize; + Preconditions.checkArgument(bitsToShift + flagBitSize <= 64 && position >= 0, String.format(Locale.US, "Your position is out of bounds! position: %d, flagBitSize: %d", position, flagBitSize)); + + long clearMask = ~((twoToThe(flagBitSize) - 1) << bitsToShift); + long cleared = existing & clearMask; + long shiftedValue = value << bitsToShift; + + return cleared | shiftedValue; + } + + /** Simple method to do 2^n. Giving it a name just so it's clear what's happening. */ + private static long twoToThe(long n) { + return 1 << n; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java index c5a87af8ae..67fcc16255 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java @@ -30,6 +30,14 @@ public final class CursorUtil { return cursor.getBlob(cursor.getColumnIndexOrThrow(column)); } + public static boolean requireMaskedBoolean(@NonNull Cursor cursor, @NonNull String column, int position) { + return Bitmask.read(requireLong(cursor, column), position); + } + + public static int requireMaskedInt(@NonNull Cursor cursor, @NonNull String column, int position, int flagBitSize) { + return Util.toIntExact(Bitmask.read(requireLong(cursor, column), position, flagBitSize)); + } + public static Optional getString(@NonNull Cursor cursor, @NonNull String column) { if (cursor.getColumnIndex(column) < 0) { return Optional.absent(); diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/BitmaskTest.java b/app/src/test/java/org/thoughtcrime/securesms/util/BitmaskTest.java new file mode 100644 index 0000000000..4965ddc497 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/util/BitmaskTest.java @@ -0,0 +1,148 @@ +package org.thoughtcrime.securesms.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class BitmaskTest { + + @Test + public void read_singleBit() { + assertFalse(Bitmask.read(0b00000000, 0)); + assertFalse(Bitmask.read(0b11111101, 1)); + assertFalse(Bitmask.read(0b11111011, 2)); + assertFalse(Bitmask.read(0b01111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L, 63)); + + assertTrue(Bitmask.read(0b00000001, 0)); + assertTrue(Bitmask.read(0b00000010, 1)); + assertTrue(Bitmask.read(0b00000100, 2)); + assertTrue(Bitmask.read(0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 63)); + } + + @Test + public void read_twoBits() { + assertEquals(0, Bitmask.read(0b11111100, 0, 2)); + assertEquals(1, Bitmask.read(0b11111101, 0, 2)); + assertEquals(2, Bitmask.read(0b11111110, 0, 2)); + assertEquals(3, Bitmask.read(0b11111111, 0, 2)); + + assertEquals(0, Bitmask.read(0b11110011, 1, 2)); + assertEquals(1, Bitmask.read(0b11110111, 1, 2)); + assertEquals(2, Bitmask.read(0b11111011, 1, 2)); + assertEquals(3, Bitmask.read(0b11111111, 1, 2)); + + assertEquals(0, Bitmask.read(0b00000000, 2, 2)); + assertEquals(1, Bitmask.read(0b00010000, 2, 2)); + assertEquals(2, Bitmask.read(0b00100000, 2, 2)); + assertEquals(3, Bitmask.read(0b00110000, 2, 2)); + + assertEquals(0, Bitmask.read(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 31, 2)); + assertEquals(1, Bitmask.read(0b01000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 31, 2)); + assertEquals(2, Bitmask.read(0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 31, 2)); + assertEquals(3, Bitmask.read(0b11000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 31, 2)); + } + + @Test + public void read_fourBits() { + assertEquals(0, Bitmask.read(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 15, 4)); + assertEquals(4, Bitmask.read(0b01000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 15, 4)); + assertEquals(8, Bitmask.read(0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 15, 4)); + assertEquals(15, Bitmask.read(0b11110000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 15, 4)); + } + + @Test(expected = IllegalArgumentException.class) + public void read_error_negativeIndex() { + Bitmask.read(0b0000000, -1); + } + + @Test(expected = IllegalArgumentException.class) + public void read_error_indexTooLarge_singleBit() { + Bitmask.read(0b0000000, 64); + } + + @Test(expected = IllegalArgumentException.class) + public void read_error_indexTooLarge_twoBits() { + Bitmask.read(0b0000000, 32, 2); + } + + @Test + public void update_singleBit() { + assertEquals(0b00000001, Bitmask.update(0b00000000, 0, true)); + assertEquals(0b00000010, Bitmask.update(0b00000000, 1, true)); + assertEquals(0b00000100, Bitmask.update(0b00000000, 2, true)); + assertEquals(0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, + Bitmask.update(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 63, true)); + + assertEquals(0b11111110, Bitmask.update(0b11111111, 0, false)); + assertEquals(0b11111101, Bitmask.update(0b11111111, 1, false)); + assertEquals(0b11111011, Bitmask.update(0b11111111, 2, false)); + assertEquals(0b01111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L, + Bitmask.update(0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L, 63, false)); + + assertEquals(0b11111111, Bitmask.update(0b11111111, 0, true)); + assertEquals(0b11111111, Bitmask.update(0b11111111, 1, true)); + assertEquals(0b11111111, Bitmask.update(0b11111111, 2, true)); + assertEquals(0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L, + Bitmask.update(0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111L, 63, true)); + + assertEquals(0b00000000, Bitmask.update(0b00000000, 0, false)); + assertEquals(0b00000000, Bitmask.update(0b00000000, 1, false)); + assertEquals(0b00000000, Bitmask.update(0b00000000, 2, false)); + assertEquals(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, + Bitmask.update(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 63, false)); + } + + @Test + public void update_twoBits() { + assertEquals(0b00000000, Bitmask.update(0b00000000, 0, 2, 0)); + assertEquals(0b00000001, Bitmask.update(0b00000000, 0, 2, 1)); + assertEquals(0b00000010, Bitmask.update(0b00000000, 0, 2, 2)); + assertEquals(0b00000011, Bitmask.update(0b00000000, 0, 2, 3)); + + assertEquals(0b00000000, Bitmask.update(0b00000000, 1, 2, 0)); + assertEquals(0b00000100, Bitmask.update(0b00000000, 1, 2, 1)); + assertEquals(0b00001000, Bitmask.update(0b00000000, 1, 2, 2)); + assertEquals(0b00001100, Bitmask.update(0b00000000, 1, 2, 3)); + + assertEquals(0b11111100, Bitmask.update(0b11111111, 0, 2, 0)); + assertEquals(0b11111101, Bitmask.update(0b11111111, 0, 2, 1)); + assertEquals(0b11111110, Bitmask.update(0b11111111, 0, 2, 2)); + assertEquals(0b11111111, Bitmask.update(0b11111111, 0, 2, 3)); + + assertEquals(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, + Bitmask.update(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 31, 2, 0)); + assertEquals(0b01000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, + Bitmask.update(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 31, 2, 1)); + assertEquals(0b10000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, + Bitmask.update(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 31, 2, 2)); + assertEquals(0b11000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, + Bitmask.update(0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L, 31, 2, 3)); + } + + @Test(expected = IllegalArgumentException.class) + public void update_error_negativeIndex() { + Bitmask.update(0b0000000, -1, true); + } + + @Test(expected = IllegalArgumentException.class) + public void update_error_indexTooLarge_singleBit() { + Bitmask.update(0b0000000, 64, true); + } + + @Test(expected = IllegalArgumentException.class) + public void update_error_indexTooLarge_twoBits() { + Bitmask.update(0b0000000, 32, 2, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void update_error_negativeValue() { + Bitmask.update(0b0000000, 0, 2, -1); + } + + @Test(expected = IllegalArgumentException.class) + public void update_error_valueTooLarge() { + Bitmask.update(0b0000000, 0, 2, 4); + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java index 219e4ca323..210a89e37a 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/profiles/SignalServiceProfile.java @@ -91,9 +91,6 @@ public class SignalServiceProfile { } public static class Capabilities { - @JsonProperty - private boolean uuid; - @JsonProperty private boolean gv2; @@ -103,10 +100,6 @@ public class SignalServiceProfile { @JsonCreator public Capabilities() {} - public boolean isUuid() { - return uuid; - } - public boolean isGv2() { return gv2; }