Skip to content

Commit

Permalink
Replace prekey jobs with one overall sync job.
Browse files Browse the repository at this point in the history
  • Loading branch information
cody-signal committed Aug 18, 2022
1 parent 2740b5e commit 3252871
Show file tree
Hide file tree
Showing 21 changed files with 433 additions and 418 deletions.
@@ -0,0 +1,212 @@
package org.thoughtcrime.securesms.jobs

import androidx.test.ext.junit.runners.AndroidJUnit4
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.libsignal.protocol.ecc.Curve
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.parsedRequestBody
import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
import org.whispersystems.signalservice.internal.push.PreKeyState
import org.whispersystems.signalservice.internal.push.PreKeyStatus

@RunWith(AndroidJUnit4::class)
class PreKeysSyncJobTest {

@get:Rule
val harness = SignalActivityRule()

private val aciPreKeyMeta: PreKeyMetadataStore
get() = SignalStore.account().aciPreKeys

private val pniPreKeyMeta: PreKeyMetadataStore
get() = SignalStore.account().pniPreKeys

private lateinit var job: PreKeysSyncJob

@Before
fun setUp() {
job = PreKeysSyncJob()
}

@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
}

/**
* Create signed prekeys for both identities when both do not have registered prekeys according
* to our local state.
*/
@Test
fun runWithoutRegisteredKeysForBothIdentities() {
// GIVEN
aciPreKeyMeta.isSignedPreKeyRegistered = false
pniPreKeyMeta.isSignedPreKeyRegistered = false

lateinit var aciSignedPreKey: SignedPreKeyEntity
lateinit var pniSignedPreKey: SignedPreKeyEntity

InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v2/keys/signed?identity=aci") { r ->
aciSignedPreKey = r.parsedRequestBody()
MockResponse().success()
},
Put("/v2/keys/signed?identity=pni") { r ->
pniSignedPreKey = r.parsedRequestBody()
MockResponse().success()
},
)

// WHEN
val result: Job.Result = job.run()

// THEN
result.isSuccess assertIs true

aciPreKeyMeta.isSignedPreKeyRegistered assertIs true
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true

val aciVerifySignatureResult = Curve.verifySignature(
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.publicKey,
aciSignedPreKey.publicKey.serialize(),
aciSignedPreKey.signature
)
aciVerifySignatureResult assertIs true

val pniVerifySignatureResult = Curve.verifySignature(
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.publicKey,
pniSignedPreKey.publicKey.serialize(),
pniSignedPreKey.signature
)
pniVerifySignatureResult assertIs true
}

/**
* With 100 prekeys registered for each identity, do nothing.
*/
@Test
fun runWithRegisteredKeysForBothIdentities() {
// GIVEN
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId

InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(100)) },
)

// WHEN
val result: Job.Result = job.run()

// THEN
result.isSuccess assertIs true

aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIs currentPniKeyId
}

/**
* With 100 prekeys registered for ACI, but no PNI prekeys registered according to local state,
* do nothing for ACI but create PNI prekeys and update local state.
*/
@Test
fun runWithRegisteredKeysForAciIdentityOnly() {
// GIVEN
pniPreKeyMeta.isSignedPreKeyRegistered = false

val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId

InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
Put("/v2/keys/signed?identity=pni") { MockResponse().success() },
)

// WHEN
val result: Job.Result = job.run()

// THEN
result.isSuccess assertIs true

pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
}

/**
* With <10 prekeys registered for each identity, upload new.
*/
@Test
fun runWithLowNumberOfRegisteredKeysForBothIdentities() {
// GIVEN
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId

val currentNextAciPreKeyId = aciPreKeyMeta.nextOneTimePreKeyId
val currentNextPniPreKeyId = pniPreKeyMeta.nextOneTimePreKeyId

lateinit var aciPreKeyStateRequest: PreKeyState
lateinit var pniPreKeyStateRequest: PreKeyState

InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(5)) },
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(5)) },
Put("/v2/keys/?identity=aci") { r ->
aciPreKeyStateRequest = r.parsedRequestBody()
MockResponse().success()
},
Put("/v2/keys/?identity=pni") { r ->
pniPreKeyStateRequest = r.parsedRequestBody()
MockResponse().success()
},
)

// WHEN
val result: Job.Result = job.run()

// THEN
result.isSuccess assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIsNot currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId

aciPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextAciPreKeyId
pniPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextPniPreKeyId

ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.let { aciIdentityKey ->
aciPreKeyStateRequest.identityKey assertIs aciIdentityKey

val verifySignatureResult = Curve.verifySignature(
aciIdentityKey.publicKey,
aciPreKeyStateRequest.signedPreKey.publicKey.serialize(),
aciPreKeyStateRequest.signedPreKey.signature
)
verifySignatureResult assertIs true
}

ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.let { pniIdentityKey ->
pniPreKeyStateRequest.identityKey assertIs pniIdentityKey

val verifySignatureResult = Curve.verifySignature(
pniIdentityKey.publicKey,
pniPreKeyStateRequest.signedPreKey.publicKey.serialize(),
pniPreKeyStateRequest.signedPreKey.signature
)
verifySignatureResult assertIs true
}
}
}
Expand Up @@ -50,16 +50,15 @@
import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.gcm.FcmJobService;
import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
Expand Down Expand Up @@ -180,13 +179,12 @@ public void onCreate() {
.addNonBlocking(this::initializeRevealableMessageManager)
.addNonBlocking(this::initializePendingRetryReceiptManager)
.addNonBlocking(this::initializeFcmCheck)
.addNonBlocking(CreateSignedPreKeyJob::enqueueIfNeeded)
.addNonBlocking(PreKeysSyncJob::enqueueIfNeeded)
.addNonBlocking(this::initializePeriodicTasks)
.addNonBlocking(this::initializeCircumvention)
.addNonBlocking(this::initializePendingMessages)
.addNonBlocking(this::initializeCleanup)
.addNonBlocking(this::initializeGlideCodecs)
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
.addNonBlocking(EmojiSource::refresh)
Expand Down
Expand Up @@ -217,7 +217,7 @@ class ChangeNumberRepository(private val accountManager: SignalServiceAccountMan

// Signed Prekeys
val signedPreKeyRecord = if (deviceId == primaryDeviceId) {
PreKeyUtil.generateAndStoreSignedPreKey(pniProtocolStore, pniMetadataStore, pniIdentity.privateKey, false)
PreKeyUtil.generateAndStoreSignedPreKey(pniProtocolStore, pniMetadataStore, pniIdentity.privateKey)
} else {
PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), pniIdentity.privateKey)
}
Expand Down
Expand Up @@ -64,14 +64,13 @@ public class PreKeyUtil {
return records;
}

public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore, boolean setAsActive) {
return generateAndStoreSignedPreKey(protocolStore, metadataStore, protocolStore.getIdentityKeyPair().getPrivateKey(), setAsActive);
public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore, @NonNull PreKeyMetadataStore metadataStore) {
return generateAndStoreSignedPreKey(protocolStore, metadataStore, protocolStore.getIdentityKeyPair().getPrivateKey());
}

public synchronized static @NonNull SignedPreKeyRecord generateAndStoreSignedPreKey(@NonNull SignalProtocolStore protocolStore,
@NonNull PreKeyMetadataStore metadataStore,
@NonNull ECPrivateKey privateKey,
boolean setAsActive)
@NonNull ECPrivateKey privateKey)
{
Log.i(TAG, "Generating signed prekeys...");

Expand All @@ -81,10 +80,6 @@ public class PreKeyUtil {
protocolStore.storeSignedPreKey(signedPreKeyId, record);
metadataStore.setNextSignedPreKeyId((signedPreKeyId + 1) % Medium.MAX_VALUE);

if (setAsActive) {
metadataStore.setActiveSignedPreKeyId(signedPreKeyId);
}

return record;
}

Expand Down
Expand Up @@ -16,8 +16,7 @@ import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations
import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations.migrate
import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations.migratePostTransaction
import org.thoughtcrime.securesms.database.model.AvatarPickerDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob.DatabaseUpgradeListener
import org.thoughtcrime.securesms.service.KeyCachingService
Expand Down Expand Up @@ -145,7 +144,7 @@ open class SignalDatabase(private val context: Application, databaseSecret: Data
val masterSecret = KeyCachingService.getMasterSecret(context)
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db, null) else TextSecurePreferences.setNeedsSqlCipherMigration(context, true)
if (!PreKeyMigrationHelper.migratePreKeys(context, db)) {
ApplicationDependencies.getJobManager().add(RefreshPreKeysJob())
PreKeysSyncJob.enqueue()
}
SessionStoreMigrationHelper.migrateSessions(context, db)
PreKeyMigrationHelper.cleanUpPreKeys(context)
Expand Down
Expand Up @@ -30,9 +30,8 @@ import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.helpers.migration.MyStoryMigration
import org.thoughtcrime.securesms.database.helpers.migration.UrgentMslFlagMigration
import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
Expand Down Expand Up @@ -224,7 +223,7 @@ object SignalDatabaseMigrations {
db.execSQL("CREATE TABLE one_time_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL)")

if (!PreKeyMigrationHelper.migratePreKeys(context, db)) {
ApplicationDependencies.getJobManager().add(RefreshPreKeysJob())
PreKeysSyncJob.enqueue()
}
}

Expand Down
Expand Up @@ -31,11 +31,11 @@
import org.thoughtcrime.securesms.jobmanager.JobMigrator;
import org.thoughtcrime.securesms.jobmanager.impl.FactoryJobPredicate;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.FastJobStorage;
import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob;
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
import org.thoughtcrime.securesms.jobs.MarkerJob;
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
Expand Down Expand Up @@ -317,7 +317,7 @@ public ApplicationDependencyProvider(@NonNull Application context) {
}

if (needsPreKeyJob) {
CreateSignedPreKeyJob.enqueueIfNeeded();
PreKeysSyncJob.enqueueIfNeeded();
}

SignalBaseIdentityKeyStore baseIdentityStore = new SignalBaseIdentityKeyStore(context);
Expand Down
12 changes: 9 additions & 3 deletions app/src/main/java/org/thoughtcrime/securesms/jobmanager/Job.java
Expand Up @@ -4,6 +4,7 @@

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;

import org.signal.core.util.logging.Log;
Expand All @@ -14,6 +15,8 @@
import java.util.Objects;
import java.util.UUID;

import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;

/**
* A durable unit of work.
*
Expand Down Expand Up @@ -193,15 +196,18 @@ public static Result fatalFailure(@NonNull RuntimeException runtimeException) {
return new Result(ResultType.FAILURE, runtimeException, null, INVALID_BACKOFF);
}

boolean isSuccess() {
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
public boolean isSuccess() {
return resultType == ResultType.SUCCESS;
}

boolean isRetry() {
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
public boolean isRetry() {
return resultType == ResultType.RETRY;
}

boolean isFailure() {
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
public boolean isFailure() {
return resultType == ResultType.FAILURE;
}

Expand Down
Expand Up @@ -92,8 +92,8 @@ protected void warn(@NonNull String tag, @NonNull String message) {
warn(tag, "", message, null);
}

protected void warn(@NonNull String tag, @NonNull String event, @NonNull String message) {
warn(tag, event, message, null);
protected void warn(@NonNull String tag, @NonNull Object extra, @NonNull String message) {
warn(tag, extra.toString(), message, null);
}

protected void warn(@NonNull String tag, @Nullable Throwable t) {
Expand Down

0 comments on commit 3252871

Please sign in to comment.