Add an automatic session reset interval.

This commit is contained in:
Greyson Parrelli
2021-01-16 14:17:32 -05:00
parent 2c1c6fab35
commit a2dc781840
5 changed files with 101 additions and 8 deletions

View File

@@ -13,6 +13,7 @@ import com.annimon.stream.Stream;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import net.sqlcipher.SQLException;
import net.sqlcipher.database.SQLiteConstraintException; import net.sqlcipher.database.SQLiteConstraintException;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
@@ -28,6 +29,7 @@ import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime;
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileKeyCredentialColumnData; import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileKeyCredentialColumnData;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
@@ -128,6 +130,7 @@ public class RecipientDatabase extends Database {
private static final String MENTION_SETTING = "mention_setting"; private static final String MENTION_SETTING = "mention_setting";
private static final String STORAGE_PROTO = "storage_proto"; private static final String STORAGE_PROTO = "storage_proto";
private static final String LAST_GV1_MIGRATE_REMINDER = "last_gv1_migrate_reminder"; private static final String LAST_GV1_MIGRATE_REMINDER = "last_gv1_migrate_reminder";
private static final String LAST_SESSION_RESET = "last_session_reset";
public static final String SEARCH_PROFILE_NAME = "search_signal_profile"; public static final String SEARCH_PROFILE_NAME = "search_signal_profile";
private static final String SORT_NAME = "sort_name"; private static final String SORT_NAME = "sort_name";
@@ -346,7 +349,8 @@ public class RecipientDatabase extends Database {
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, " + CAPABILITIES + " INTEGER DEFAULT 0, " +
LAST_GV1_MIGRATE_REMINDER + " INTEGER DEFAULT 0);"; LAST_GV1_MIGRATE_REMINDER + " INTEGER DEFAULT 0, " +
LAST_SESSION_RESET + " BLOB DEFAULT NULL);";
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 +
@@ -1512,6 +1516,30 @@ public class RecipientDatabase extends Database {
return 0; return 0;
} }
public void setLastSessionResetTime(@NonNull RecipientId id, DeviceLastResetTime lastResetTime) {
ContentValues values = new ContentValues(1);
values.put(LAST_SESSION_RESET, lastResetTime.toByteArray());
update(id, values);
}
public @NonNull DeviceLastResetTime getLastSessionResetTimes(@NonNull RecipientId id) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
try (Cursor cursor = db.query(TABLE_NAME, new String[] {LAST_SESSION_RESET}, ID_WHERE, SqlUtil.buildArgs(id), null, null, null)) {
if (cursor.moveToFirst()) {
try {
return DeviceLastResetTime.parseFrom(CursorUtil.requireBlob(cursor, LAST_SESSION_RESET));
} catch (InvalidProtocolBufferException | SQLException e) {
Log.w(TAG, e);
return DeviceLastResetTime.newBuilder().build();
}
}
}
return DeviceLastResetTime.newBuilder().build();
}
public void setCapabilities(@NonNull RecipientId id, @NonNull SignalServiceProfile.Capabilities capabilities) { public void setCapabilities(@NonNull RecipientId id, @NonNull SignalServiceProfile.Capabilities capabilities) {
long value = 0; long value = 0;

View File

@@ -168,8 +168,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private static final int CLEAN_UP_GV1_IDS = 84; private static final int CLEAN_UP_GV1_IDS = 84;
private static final int GV1_MIGRATION_REFACTOR = 85; private static final int GV1_MIGRATION_REFACTOR = 85;
private static final int CLEAR_PROFILE_KEY_CREDENTIALS = 86; private static final int CLEAR_PROFILE_KEY_CREDENTIALS = 86;
private static final int LAST_RESET_SESSION_TIME = 87;
private static final int DATABASE_VERSION = 86; private static final int DATABASE_VERSION = 87;
private static final String DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal.db";
private final Context context; private final Context context;
@@ -1241,6 +1242,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
Log.i(TAG, "Cleared profile key credentials for " + count + " rows"); Log.i(TAG, "Cleared profile key credentials for " + count + " rows");
} }
if (oldVersion < LAST_RESET_SESSION_TIME) {
db.execSQL("ALTER TABLE recipient ADD COLUMN last_session_reset BLOB DEFAULT NULL");
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@@ -7,6 +7,8 @@ import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageDatabase; import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.Job;
@@ -23,6 +25,7 @@ import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit;
/** /**
* - Archives the session associated with the specified device * - Archives the session associated with the specified device
@@ -83,18 +86,35 @@ public class AutomaticSessionResetJob extends BaseJob {
@Override @Override
protected void onRun() throws Exception { protected void onRun() throws Exception {
if (FeatureFlags.automaticSessionReset()) {
SessionUtil.archiveSession(context, recipientId, deviceId); SessionUtil.archiveSession(context, recipientId, deviceId);
insertLocalMessage(); insertLocalMessage();
if (FeatureFlags.automaticSessionReset()) {
long resetInterval = TimeUnit.SECONDS.toMillis(FeatureFlags.automaticSessionResetIntervalSeconds());
DeviceLastResetTime resetTimes = DatabaseFactory.getRecipientDatabase(context).getLastSessionResetTimes(recipientId);
long timeSinceLastReset = System.currentTimeMillis() - getLastResetTime(resetTimes, deviceId);
Log.i(TAG, "DeviceId: " + deviceId + ", Reset interval: " + resetInterval + ", Time since last reset: " + timeSinceLastReset);
if (timeSinceLastReset > resetInterval) {
Log.i(TAG, "We're good! Sending a null message.");
DatabaseFactory.getRecipientDatabase(context).setLastSessionResetTime(recipientId, setLastResetTime(resetTimes, deviceId, System.currentTimeMillis()));
Log.i(TAG, "Marked last reset time: " + System.currentTimeMillis());
sendNullMessage(); sendNullMessage();
Log.i(TAG, "Successfully sent!");
} else { } else {
insertLocalMessage(); Log.w(TAG, "Too soon! Time since last reset: " + timeSinceLastReset);
}
} else {
Log.w(TAG, "Automatic session reset send disabled!");
} }
} }
@Override @Override
protected boolean onShouldRetry(@NonNull Exception e) { protected boolean onShouldRetry(@NonNull Exception e) {
return e instanceof RetryLaterException; return false;
} }
@Override @Override
@@ -119,6 +139,29 @@ public class AutomaticSessionResetJob extends BaseJob {
} }
} }
private long getLastResetTime(@NonNull DeviceLastResetTime resetTimes, int deviceId) {
for (DeviceLastResetTime.Pair pair : resetTimes.getResetTimeList()) {
if (pair.getDeviceId() == deviceId) {
return pair.getLastResetTime();
}
}
return 0;
}
private @NonNull DeviceLastResetTime setLastResetTime(@NonNull DeviceLastResetTime resetTimes, int deviceId, long time) {
DeviceLastResetTime.Builder builder = DeviceLastResetTime.newBuilder();
for (DeviceLastResetTime.Pair pair : resetTimes.getResetTimeList()) {
if (pair.getDeviceId() != deviceId) {
builder.addResetTime(pair);
}
}
builder.addResetTime(DeviceLastResetTime.Pair.newBuilder().setDeviceId(deviceId).setLastResetTime(time));
return builder.build();
}
public static final class Factory implements Job.Factory<AutomaticSessionResetJob> { public static final class Factory implements Job.Factory<AutomaticSessionResetJob> {
@Override @Override
public @NonNull AutomaticSessionResetJob create(@NonNull Parameters parameters, @NonNull Data data) { public @NonNull AutomaticSessionResetJob create(@NonNull Parameters parameters, @NonNull Data data) {

View File

@@ -67,7 +67,8 @@ public final class FeatureFlags {
private static final String SEND_VIEWED_RECEIPTS = "android.sendViewedReceipts"; private static final String SEND_VIEWED_RECEIPTS = "android.sendViewedReceipts";
private static final String CUSTOM_VIDEO_MUXER = "android.customVideoMuxer"; private static final String CUSTOM_VIDEO_MUXER = "android.customVideoMuxer";
private static final String CDS_REFRESH_INTERVAL = "cds.syncInterval.seconds"; private static final String CDS_REFRESH_INTERVAL = "cds.syncInterval.seconds";
private static final String AUTOMATIC_SESSION_RESET = "android.automaticSessionReset"; private static final String AUTOMATIC_SESSION_RESET = "android.automaticSessionReset.2";
private static final String AUTOMATIC_SESSION_INTERVAL = "android.automaticSessionResetInterval";
private static final String DEFAULT_MAX_BACKOFF = "android.defaultMaxBackoff"; private static final String DEFAULT_MAX_BACKOFF = "android.defaultMaxBackoff";
/** /**
@@ -94,6 +95,7 @@ public final class FeatureFlags {
CDS_REFRESH_INTERVAL, CDS_REFRESH_INTERVAL,
GROUP_NAME_MAX_LENGTH, GROUP_NAME_MAX_LENGTH,
AUTOMATIC_SESSION_RESET, AUTOMATIC_SESSION_RESET,
AUTOMATIC_SESSION_INTERVAL,
DEFAULT_MAX_BACKOFF DEFAULT_MAX_BACKOFF
); );
@@ -130,6 +132,7 @@ public final class FeatureFlags {
CDS_REFRESH_INTERVAL, CDS_REFRESH_INTERVAL,
GROUP_NAME_MAX_LENGTH, GROUP_NAME_MAX_LENGTH,
AUTOMATIC_SESSION_RESET, AUTOMATIC_SESSION_RESET,
AUTOMATIC_SESSION_INTERVAL,
DEFAULT_MAX_BACKOFF DEFAULT_MAX_BACKOFF
); );
@@ -296,6 +299,11 @@ public final class FeatureFlags {
return getBoolean(AUTOMATIC_SESSION_RESET, true); return getBoolean(AUTOMATIC_SESSION_RESET, true);
} }
/** How often we allow an automatic session reset. */
public static int automaticSessionResetIntervalSeconds() {
return getInteger(AUTOMATIC_SESSION_RESET, (int) TimeUnit.HOURS.toSeconds(1));
}
public static int getDefaultMaxBackoffSeconds() { public static int getDefaultMaxBackoffSeconds() {
return getInteger(DEFAULT_MAX_BACKOFF, 60); return getInteger(DEFAULT_MAX_BACKOFF, 60);
} }

View File

@@ -82,3 +82,12 @@ message ProfileKeyCredentialColumnData {
bytes profileKey = 1; bytes profileKey = 1;
bytes profileKeyCredential = 2; bytes profileKeyCredential = 2;
} }
message DeviceLastResetTime {
message Pair {
int32 deviceId = 1;
int64 lastResetTime = 2;
}
repeated Pair resetTime = 1;
}