Move capabilities into a single column.

This commit is contained in:
Greyson Parrelli 2020-10-19 17:16:40 -04:00 committed by Alan Evans
parent ead64d92a5
commit 3357475fc4
9 changed files with 276 additions and 60 deletions

View file

@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.groups.v2.ProfileKeySet;
import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor; import org.thoughtcrime.securesms.groups.v2.processing.GroupsV2StateProcessor;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob; import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName; 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.StorageSyncHelper.RecordUpdate;
import org.thoughtcrime.securesms.storage.StorageSyncModels; import org.thoughtcrime.securesms.storage.StorageSyncModels;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Bitmask;
import org.thoughtcrime.securesms.util.CursorUtil; import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.IdentityUtil; 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 LAST_PROFILE_FETCH = "last_profile_fetch";
private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode"; private static final String UNIDENTIFIED_ACCESS_MODE = "unidentified_access_mode";
private static final String FORCE_SMS_SELECTION = "force_sms_selection"; private static final String FORCE_SMS_SELECTION = "force_sms_selection";
private static final String UUID_CAPABILITY = "uuid_supported"; private static final String CAPABILITIES = "capabilities";
private static final String GROUPS_V2_CAPABILITY = "gv2_capability";
private static final String STORAGE_SERVICE_ID = "storage_service_key"; private static final String STORAGE_SERVICE_ID = "storage_service_key";
private static final String DIRTY = "dirty"; private static final String DIRTY = "dirty";
private static final String PROFILE_GIVEN_NAME = "signal_profile_name"; 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_STATUS = "identity_status";
private static final String IDENTITY_KEY = "identity_key"; 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[] { private static final String[] RECIPIENT_PROJECTION = new String[] {
ID, UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE, 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, 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, NOTIFICATION_CHANNEL,
UNIDENTIFIED_ACCESS_MODE, UNIDENTIFIED_ACCESS_MODE,
FORCE_SMS_SELECTION, FORCE_SMS_SELECTION,
UUID_CAPABILITY, GROUPS_V2_CAPABILITY, CAPABILITIES,
STORAGE_SERVICE_ID, DIRTY, STORAGE_SERVICE_ID, DIRTY,
MENTION_SETTING MENTION_SETTING
}; };
@ -329,12 +335,11 @@ public class RecipientDatabase extends Database {
LAST_PROFILE_FETCH + " INTEGER DEFAULT 0, " + LAST_PROFILE_FETCH + " INTEGER DEFAULT 0, " +
UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " + UNIDENTIFIED_ACCESS_MODE + " INTEGER DEFAULT 0, " +
FORCE_SMS_SELECTION + " 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, " + STORAGE_SERVICE_ID + " TEXT UNIQUE DEFAULT NULL, " +
DIRTY + " INTEGER DEFAULT " + DirtyState.CLEAN.getId() + ", " + DIRTY + " INTEGER DEFAULT " + DirtyState.CLEAN.getId() + ", " +
MENTION_SETTING + " INTEGER DEFAULT " + MentionSetting.ALWAYS_NOTIFY.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 + private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID +
" FROM " + TABLE_NAME + " FROM " + TABLE_NAME +
@ -503,6 +508,7 @@ public class RecipientDatabase extends Database {
if (transactionSuccessful) { if (transactionSuccessful) {
if (recipientNeedingRefresh != null) { if (recipientNeedingRefresh != null) {
Recipient.live(recipientNeedingRefresh).refresh(); Recipient.live(recipientNeedingRefresh).refresh();
RetrieveProfileJob.enqueue(recipientNeedingRefresh);
} }
if (remapped != null) { if (remapped != null) {
@ -1173,8 +1179,7 @@ public class RecipientDatabase extends Database {
String notificationChannel = CursorUtil.requireString(cursor, NOTIFICATION_CHANNEL); String notificationChannel = CursorUtil.requireString(cursor, NOTIFICATION_CHANNEL);
int unidentifiedAccessMode = CursorUtil.requireInt(cursor, UNIDENTIFIED_ACCESS_MODE); int unidentifiedAccessMode = CursorUtil.requireInt(cursor, UNIDENTIFIED_ACCESS_MODE);
boolean forceSmsSelection = CursorUtil.requireBoolean(cursor, FORCE_SMS_SELECTION); boolean forceSmsSelection = CursorUtil.requireBoolean(cursor, FORCE_SMS_SELECTION);
int uuidCapabilityValue = CursorUtil.requireInt(cursor, UUID_CAPABILITY); long capabilities = CursorUtil.requireLong(cursor, CAPABILITIES);
int groupsV2CapabilityValue = CursorUtil.requireInt(cursor, GROUPS_V2_CAPABILITY);
String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID); String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID);
int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING); int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING);
@ -1240,8 +1245,7 @@ public class RecipientDatabase extends Database {
notificationChannel, notificationChannel,
UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), UnidentifiedAccessMode.fromMode(unidentifiedAccessMode),
forceSmsSelection, forceSmsSelection,
Recipient.Capability.deserialize(uuidCapabilityValue), capabilities,
Recipient.Capability.deserialize(groupsV2CapabilityValue),
InsightsBannerTier.fromId(insightsBannerTier), InsightsBannerTier.fromId(insightsBannerTier),
storageKey, storageKey,
MentionSetting.fromId(mentionSettingId), MentionSetting.fromId(mentionSettingId),
@ -1404,9 +1408,13 @@ public class RecipientDatabase extends Database {
} }
public void setCapabilities(@NonNull RecipientId id, @NonNull SignalServiceProfile.Capabilities capabilities) { public void setCapabilities(@NonNull RecipientId id, @NonNull SignalServiceProfile.Capabilities capabilities) {
ContentValues values = new ContentValues(2); long value = 0;
values.put(UUID_CAPABILITY, Recipient.Capability.fromBoolean(capabilities.isUuid()).serialize());
values.put(GROUPS_V2_CAPABILITY, Recipient.Capability.fromBoolean(capabilities.isGv2()).serialize()); 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)) { if (update(id, values)) {
Recipient.live(id).refresh(); Recipient.live(id).refresh();
} }
@ -2372,7 +2380,7 @@ public class RecipientDatabase extends Database {
uuidValues.put(SYSTEM_PHONE_LABEL, e164Settings.getSystemPhoneLabel()); uuidValues.put(SYSTEM_PHONE_LABEL, e164Settings.getSystemPhoneLabel());
uuidValues.put(SYSTEM_CONTACT_URI, e164Settings.getSystemContactUri()); uuidValues.put(SYSTEM_CONTACT_URI, e164Settings.getSystemContactUri());
uuidValues.put(PROFILE_SHARING, uuidSettings.isProfileSharing() || e164Settings.isProfileSharing()); 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()); uuidValues.put(MENTION_SETTING, uuidSettings.getMentionSetting() != MentionSetting.ALWAYS_NOTIFY ? uuidSettings.getMentionSetting().getId() : e164Settings.getMentionSetting().getId());
if (uuidSettings.getProfileKey() != null) { if (uuidSettings.getProfileKey() != null) {
updateProfileValuesForMerge(uuidValues, uuidSettings); updateProfileValuesForMerge(uuidValues, uuidSettings);
@ -2589,7 +2597,7 @@ public class RecipientDatabase extends Database {
private final String notificationChannel; private final String notificationChannel;
private final UnidentifiedAccessMode unidentifiedAccessMode; private final UnidentifiedAccessMode unidentifiedAccessMode;
private final boolean forceSmsSelection; private final boolean forceSmsSelection;
private final Recipient.Capability uuidCapability; private final long capabilities;
private final Recipient.Capability groupsV2Capability; private final Recipient.Capability groupsV2Capability;
private final InsightsBannerTier insightsBannerTier; private final InsightsBannerTier insightsBannerTier;
private final byte[] storageId; private final byte[] storageId;
@ -2627,8 +2635,7 @@ public class RecipientDatabase extends Database {
@Nullable String notificationChannel, @Nullable String notificationChannel,
@NonNull UnidentifiedAccessMode unidentifiedAccessMode, @NonNull UnidentifiedAccessMode unidentifiedAccessMode,
boolean forceSmsSelection, boolean forceSmsSelection,
Recipient.Capability uuidCapability, long capabilities,
Recipient.Capability groupsV2Capability,
@NonNull InsightsBannerTier insightsBannerTier, @NonNull InsightsBannerTier insightsBannerTier,
@Nullable byte[] storageId, @Nullable byte[] storageId,
@NonNull MentionSetting mentionSetting, @NonNull MentionSetting mentionSetting,
@ -2665,8 +2672,8 @@ public class RecipientDatabase extends Database {
this.notificationChannel = notificationChannel; this.notificationChannel = notificationChannel;
this.unidentifiedAccessMode = unidentifiedAccessMode; this.unidentifiedAccessMode = unidentifiedAccessMode;
this.forceSmsSelection = forceSmsSelection; this.forceSmsSelection = forceSmsSelection;
this.uuidCapability = uuidCapability; this.capabilities = capabilities;
this.groupsV2Capability = groupsV2Capability; this.groupsV2Capability = Recipient.Capability.deserialize((int) Bitmask.read(capabilities, Capabilities.GROUPS_V2, Capabilities.BIT_LENGTH));
this.insightsBannerTier = insightsBannerTier; this.insightsBannerTier = insightsBannerTier;
this.storageId = storageId; this.storageId = storageId;
this.mentionSetting = mentionSetting; this.mentionSetting = mentionSetting;
@ -2801,10 +2808,6 @@ public class RecipientDatabase extends Database {
return forceSmsSelection; return forceSmsSelection;
} }
public Recipient.Capability getUuidCapability() {
return uuidCapability;
}
public Recipient.Capability getGroupsV2Capability() { public Recipient.Capability getGroupsV2Capability() {
return groupsV2Capability; return groupsV2Capability;
} }
@ -2821,6 +2824,10 @@ public class RecipientDatabase extends Database {
return syncExtras; return syncExtras;
} }
long getCapabilities() {
return capabilities;
}
/** /**
* A bundle of data that's only necessary when syncing to storage service, not for a * A bundle of data that's only necessary when syncing to storage service, not for a
* {@link Recipient}. * {@link Recipient}.

View file

@ -157,8 +157,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int MENTION_CLEANUP = 76; private static final int MENTION_CLEANUP = 76;
private static final int MENTION_CLEANUP_V2 = 77; private static final int MENTION_CLEANUP_V2 = 77;
private static final int REACTION_CLEANUP = 78; 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 static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@ -1131,6 +1132,13 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.update("sms", values, "remote_deleted = ?", new String[] { "1" }); 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(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View file

@ -30,9 +30,7 @@ public final class LogSectionCapabilities implements LogSection {
AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(false); AccountAttributes.Capabilities capabilities = AppCapabilities.getCapabilities(false);
return new StringBuilder().append("Local device UUID : ").append(capabilities.isUuid()).append("\n") return new StringBuilder().append("Local device GV2: ").append(capabilities.isGv2()).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"); .append("Global GV2 : ").append(self.getGroupsV2Capability()).append("\n");
} }
} }

View file

@ -97,7 +97,6 @@ public class Recipient {
private final String notificationChannel; private final String notificationChannel;
private final UnidentifiedAccessMode unidentifiedAccessMode; private final UnidentifiedAccessMode unidentifiedAccessMode;
private final boolean forceSmsSelection; private final boolean forceSmsSelection;
private final Capability uuidCapability;
private final Capability groupsV2Capability; private final Capability groupsV2Capability;
private final InsightsBannerTier insightsBannerTier; private final InsightsBannerTier insightsBannerTier;
private final byte[] storageId; private final byte[] storageId;
@ -311,7 +310,6 @@ public class Recipient {
this.notificationChannel = null; this.notificationChannel = null;
this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED; this.unidentifiedAccessMode = UnidentifiedAccessMode.DISABLED;
this.forceSmsSelection = false; this.forceSmsSelection = false;
this.uuidCapability = Capability.UNKNOWN;
this.groupsV2Capability = Capability.UNKNOWN; this.groupsV2Capability = Capability.UNKNOWN;
this.storageId = null; this.storageId = null;
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;
@ -353,7 +351,6 @@ public class Recipient {
this.notificationChannel = details.notificationChannel; this.notificationChannel = details.notificationChannel;
this.unidentifiedAccessMode = details.unidentifiedAccessMode; this.unidentifiedAccessMode = details.unidentifiedAccessMode;
this.forceSmsSelection = details.forceSmsSelection; this.forceSmsSelection = details.forceSmsSelection;
this.uuidCapability = details.uuidCapability;
this.groupsV2Capability = details.groupsV2Capability; this.groupsV2Capability = details.groupsV2Capability;
this.storageId = details.storageId; this.storageId = details.storageId;
this.mentionSetting = details.mentionSetting; this.mentionSetting = details.mentionSetting;
@ -740,25 +737,10 @@ public class Recipient {
return forceSmsSelection; 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() { public Capability getGroupsV2Capability() {
return groupsV2Capability; return groupsV2Capability;
} }
public Capability getUuidCapability() {
return uuidCapability;
}
public @Nullable byte[] getProfileKey() { public @Nullable byte[] getProfileKey() {
return profileKey; return profileKey;
} }
@ -825,7 +807,7 @@ public class Recipient {
public enum Capability { public enum Capability {
UNKNOWN(0), UNKNOWN(0),
SUPPORTED(1), SUPPORTED(1),
NOT_SUPPORTED(-1); NOT_SUPPORTED(2);
private final int value; private final int value;
@ -839,9 +821,10 @@ public class Recipient {
public static Capability deserialize(int value) { public static Capability deserialize(int value) {
switch (value) { switch (value) {
case 1 : return SUPPORTED; case 0: return UNKNOWN;
case -1 : return NOT_SUPPORTED; case 1: return SUPPORTED;
default : return UNKNOWN; case 2: return NOT_SUPPORTED;
default: throw new IllegalArgumentException();
} }
} }

View file

@ -59,7 +59,6 @@ public class RecipientDetails {
final String notificationChannel; final String notificationChannel;
final UnidentifiedAccessMode unidentifiedAccessMode; final UnidentifiedAccessMode unidentifiedAccessMode;
final boolean forceSmsSelection; final boolean forceSmsSelection;
final Recipient.Capability uuidCapability;
final Recipient.Capability groupsV2Capability; final Recipient.Capability groupsV2Capability;
final InsightsBannerTier insightsBannerTier; final InsightsBannerTier insightsBannerTier;
final byte[] storageId; final byte[] storageId;
@ -104,7 +103,6 @@ public class RecipientDetails {
this.notificationChannel = settings.getNotificationChannel(); this.notificationChannel = settings.getNotificationChannel();
this.unidentifiedAccessMode = settings.getUnidentifiedAccessMode(); this.unidentifiedAccessMode = settings.getUnidentifiedAccessMode();
this.forceSmsSelection = settings.isForceSmsSelection(); this.forceSmsSelection = settings.isForceSmsSelection();
this.uuidCapability = settings.getUuidCapability();
this.groupsV2Capability = settings.getGroupsV2Capability(); this.groupsV2Capability = settings.getGroupsV2Capability();
this.insightsBannerTier = settings.getInsightsBannerTier(); this.insightsBannerTier = settings.getInsightsBannerTier();
this.storageId = settings.getStorageId(); this.storageId = settings.getStorageId();
@ -152,7 +150,6 @@ public class RecipientDetails {
this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN; this.unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
this.forceSmsSelection = false; this.forceSmsSelection = false;
this.name = null; this.name = null;
this.uuidCapability = Recipient.Capability.UNKNOWN;
this.groupsV2Capability = Recipient.Capability.UNKNOWN; this.groupsV2Capability = Recipient.Capability.UNKNOWN;
this.storageId = null; this.storageId = null;
this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY;

View file

@ -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;
}
}

View file

@ -30,6 +30,14 @@ public final class CursorUtil {
return cursor.getBlob(cursor.getColumnIndexOrThrow(column)); 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<String> getString(@NonNull Cursor cursor, @NonNull String column) { public static Optional<String> getString(@NonNull Cursor cursor, @NonNull String column) {
if (cursor.getColumnIndex(column) < 0) { if (cursor.getColumnIndex(column) < 0) {
return Optional.absent(); return Optional.absent();

View file

@ -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);
}
}

View file

@ -91,9 +91,6 @@ public class SignalServiceProfile {
} }
public static class Capabilities { public static class Capabilities {
@JsonProperty
private boolean uuid;
@JsonProperty @JsonProperty
private boolean gv2; private boolean gv2;
@ -103,10 +100,6 @@ public class SignalServiceProfile {
@JsonCreator @JsonCreator
public Capabilities() {} public Capabilities() {}
public boolean isUuid() {
return uuid;
}
public boolean isGv2() { public boolean isGv2() {
return gv2; return gv2;
} }