diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts
index b1462135b5..22b2f84ee3 100644
--- a/benchmark/build.gradle.kts
+++ b/benchmark/build.gradle.kts
@@ -55,7 +55,7 @@ android {
testOptions {
managedDevices {
devices {
- create ("api31", ManagedVirtualDevice::class) {
+ create("api31", ManagedVirtualDevice::class) {
device = "Pixel 6"
apiLevel = 31
systemImageSource = "aosp"
diff --git a/build-logic/plugins/src/main/java/ktlint.gradle.kts b/build-logic/plugins/src/main/java/ktlint.gradle.kts
new file mode 100644
index 0000000000..228c78f6dd
--- /dev/null
+++ b/build-logic/plugins/src/main/java/ktlint.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ id("org.jlleitschuh.gradle.ktlint")
+}
+
+ktlint {
+ // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
+ version.set("0.47.1")
+}
diff --git a/build-logic/plugins/src/main/java/signal-library.gradle.kts b/build-logic/plugins/src/main/java/signal-library.gradle.kts
index 36ac68df48..f88bc83ca8 100644
--- a/build-logic/plugins/src/main/java/signal-library.gradle.kts
+++ b/build-logic/plugins/src/main/java/signal-library.gradle.kts
@@ -17,8 +17,8 @@ val signalJavaVersion: JavaVersion by extra
plugins {
id("com.android.library")
id("kotlin-android")
- id("org.jlleitschuh.gradle.ktlint")
id("android-constants")
+ id("ktlint")
}
android {
@@ -46,11 +46,6 @@ android {
}
}
-ktlint {
- // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
- version.set("0.47.1")
-}
-
dependencies {
lintChecks(project(":lintchecks"))
diff --git a/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts b/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts
index 2990fe16c0..556432f23c 100644
--- a/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts
+++ b/build-logic/plugins/src/main/java/signal-sample-app.gradle.kts
@@ -19,7 +19,7 @@ val signalJavaVersion: JavaVersion by extra
plugins {
id("com.android.application")
id("kotlin-android")
- id("org.jlleitschuh.gradle.ktlint")
+ id("ktlint")
id("android-constants")
}
@@ -47,11 +47,6 @@ android {
}
}
-ktlint {
- // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
- version.set("0.47.1")
-}
-
dependencies {
coreLibraryDesugaring(libs.android.tools.desugar)
diff --git a/build.gradle b/build.gradle
index e0df44805c..a825500cdb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -31,6 +31,7 @@ buildscript {
exclude group: 'com.squareup.wire', module: 'wire-grpc-server-generator'
exclude group: 'io.outfoxx', module: 'swiftpoet'
}
+ classpath 'androidx.benchmark:benchmark-gradle-plugin:1.1.0-beta04'
}
}
diff --git a/dependencies.gradle b/dependencies.gradle
index 450f586062..1a463e27a9 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -159,6 +159,7 @@ dependencyResolutionManagement {
alias('espresso-core').to('androidx.test.espresso:espresso-core:3.4.0')
alias('uiautomator').to('androidx.test.uiautomator:uiautomator:2.2.0')
alias('androidx-benchmark-macro').to('androidx.benchmark:benchmark-macro-junit4:1.1.1')
+ alias('androidx-benchmark-micro').to('androidx.benchmark:benchmark-junit4:1.1.0-beta04')
}
testLibs {
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index b3149f67a4..7a22980a78 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -102,6 +102,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
@@ -180,6 +185,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -204,6 +217,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1184,6 +1213,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
@@ -1267,6 +1301,9 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
@@ -3404,6 +3441,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -3481,6 +3526,14 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
+
+
+
@@ -4506,6 +4559,11 @@ https://docs.gradle.org/current/userguide/dependency_verification.html
+
+
+
+
+
diff --git a/microbenchmark/.gitignore b/microbenchmark/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/microbenchmark/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/microbenchmark/benchmark-proguard-rules.pro b/microbenchmark/benchmark-proguard-rules.pro
new file mode 100644
index 0000000000..e4061d2224
--- /dev/null
+++ b/microbenchmark/benchmark-proguard-rules.pro
@@ -0,0 +1,37 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-dontobfuscate
+
+-ignorewarnings
+
+-keepattributes *Annotation*
+
+-dontnote junit.framework.**
+-dontnote junit.runner.**
+
+-dontwarn androidx.test.**
+-dontwarn org.junit.**
+-dontwarn org.hamcrest.**
+-dontwarn com.squareup.javawriter.JavaWriter
+
+-keepclasseswithmembers @org.junit.runner.RunWith public class *
\ No newline at end of file
diff --git a/microbenchmark/build.gradle.kts b/microbenchmark/build.gradle.kts
new file mode 100644
index 0000000000..b2ae0d58f6
--- /dev/null
+++ b/microbenchmark/build.gradle.kts
@@ -0,0 +1,63 @@
+@file:Suppress("UnstableApiUsage")
+
+plugins {
+ id("com.android.library")
+ id("androidx.benchmark")
+ id("org.jetbrains.kotlin.android")
+ id("android-constants")
+ id("ktlint")
+}
+
+val signalBuildToolsVersion: String by extra
+val signalCompileSdkVersion: String by extra
+val signalTargetSdkVersion: Int by extra
+val signalMinSdkVersion: Int by extra
+val signalJavaVersion: JavaVersion by extra
+
+android {
+ namespace = "org.signal.microbenchmark"
+ compileSdkVersion = signalCompileSdkVersion
+
+ compileOptions {
+ sourceCompatibility = signalJavaVersion
+ targetCompatibility = signalJavaVersion
+ }
+
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+
+ defaultConfig {
+ minSdk = signalMinSdkVersion
+ targetSdk = signalTargetSdkVersion
+
+ testInstrumentationRunner = "androidx.benchmark.junit4.AndroidBenchmarkRunner"
+ }
+
+ testBuildType = "release"
+ buildTypes {
+ debug {
+ // Since isDebuggable can't be modified by gradle for library modules,
+ // it must be done in a manifest - see src/androidTest/AndroidManifest.xml
+ isMinifyEnabled = true
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "benchmark-proguard-rules.pro")
+ }
+ release {
+ isDefault = true
+ }
+ }
+}
+
+dependencies {
+ lintChecks(project(":lintchecks"))
+
+ // Base dependencies
+ androidTestImplementation(testLibs.junit.junit)
+ androidTestImplementation(benchmarkLibs.androidx.test.ext.junit)
+ androidTestImplementation(benchmarkLibs.androidx.benchmark.micro)
+
+ // Dependencies of modules being tested
+ androidTestImplementation(project(":libsignal-service"))
+ androidTestImplementation(libs.libsignal.android)
+ androidTestImplementation(libs.google.protobuf.javalite)
+}
diff --git a/microbenchmark/src/androidTest/AndroidManifest.xml b/microbenchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000000..405595ca6b
--- /dev/null
+++ b/microbenchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/microbenchmark/src/androidTest/java/org/signal/microbenchmark/ProtocolBenchmarks.kt b/microbenchmark/src/androidTest/java/org/signal/microbenchmark/ProtocolBenchmarks.kt
new file mode 100644
index 0000000000..3b96c52189
--- /dev/null
+++ b/microbenchmark/src/androidTest/java/org/signal/microbenchmark/ProtocolBenchmarks.kt
@@ -0,0 +1,89 @@
+package org.signal.microbenchmark
+
+import android.util.Log
+import androidx.benchmark.junit4.BenchmarkRule
+import androidx.benchmark.junit4.measureRepeated
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.signal.libsignal.protocol.logging.SignalProtocolLogger
+import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider
+import org.signal.util.SignalClient
+
+/**
+ * Benchmarks for decrypting messages.
+ *
+ * Note that in order to isolate all costs to just the process of decryption itself,
+ * all operations are performed in in-memory stores.
+ */
+@RunWith(AndroidJUnit4::class)
+class ProtocolBenchmarks {
+
+ @get:Rule
+ val benchmarkRule = BenchmarkRule()
+
+ @Before
+ fun setup() {
+ SignalProtocolLoggerProvider.setProvider { priority, tag, message ->
+ when (priority) {
+ SignalProtocolLogger.VERBOSE -> Log.v(tag, message)
+ SignalProtocolLogger.DEBUG -> Log.d(tag, message)
+ SignalProtocolLogger.INFO -> Log.i(tag, message)
+ SignalProtocolLogger.WARN -> Log.w(tag, message)
+ SignalProtocolLogger.ERROR -> Log.w(tag, message)
+ SignalProtocolLogger.ASSERT -> Log.e(tag, message)
+ }
+ }
+ }
+
+ @Test
+ fun decrypt_unsealedSender() {
+ val (alice, bob) = buildAndInitializeClients()
+
+ benchmarkRule.measureRepeated {
+ val envelope = runWithTimingDisabled {
+ alice.encryptUnsealedSender(bob)
+ }
+
+ bob.decryptMessage(envelope)
+
+ // Respond so that the session ratchets
+ runWithTimingDisabled {
+ alice.decryptMessage(bob.encryptUnsealedSender(alice))
+ }
+ }
+ }
+
+ @Test
+ fun decrypt_sealedSender() {
+ val (alice, bob) = buildAndInitializeClients()
+
+ benchmarkRule.measureRepeated {
+ val envelope = runWithTimingDisabled {
+ alice.encryptSealedSender(bob)
+ }
+
+ bob.decryptMessage(envelope)
+
+ // Respond so that the session ratchets
+ runWithTimingDisabled {
+ alice.decryptMessage(bob.encryptSealedSender(alice))
+ }
+ }
+ }
+
+ private fun buildAndInitializeClients(): Pair {
+ val alice = SignalClient()
+ val bob = SignalClient()
+
+ // Do initial prekey dance
+ alice.initializeSession(bob)
+ bob.initializeSession(alice)
+ alice.decryptMessage(bob.encryptUnsealedSender(alice))
+ bob.decryptMessage(alice.encryptUnsealedSender(bob))
+
+ return alice to bob
+ }
+}
diff --git a/microbenchmark/src/androidTest/java/org/signal/util/InMemorySignalServiceAccountDataStore.kt b/microbenchmark/src/androidTest/java/org/signal/util/InMemorySignalServiceAccountDataStore.kt
new file mode 100644
index 0000000000..9beec9f1fd
--- /dev/null
+++ b/microbenchmark/src/androidTest/java/org/signal/util/InMemorySignalServiceAccountDataStore.kt
@@ -0,0 +1,157 @@
+package org.signal.util
+
+import org.signal.libsignal.protocol.IdentityKey
+import org.signal.libsignal.protocol.IdentityKeyPair
+import org.signal.libsignal.protocol.SignalProtocolAddress
+import org.signal.libsignal.protocol.groups.state.SenderKeyRecord
+import org.signal.libsignal.protocol.message.CiphertextMessage
+import org.signal.libsignal.protocol.state.IdentityKeyStore
+import org.signal.libsignal.protocol.state.PreKeyRecord
+import org.signal.libsignal.protocol.state.SessionRecord
+import org.signal.libsignal.protocol.state.SignedPreKeyRecord
+import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
+import org.whispersystems.signalservice.api.push.DistributionId
+import java.util.UUID
+
+/**
+ * An in-memory datastore specifically designed for tests.
+ */
+class InMemorySignalServiceAccountDataStore : SignalServiceAccountDataStore {
+
+ private val identityKey: IdentityKeyPair = IdentityKeyPair.generate()
+ private val identities: MutableMap = mutableMapOf()
+ private val oneTimePreKeys: MutableMap = mutableMapOf()
+ private val signedPreKeys: MutableMap = mutableMapOf()
+ private var sessions: MutableMap = mutableMapOf()
+ private val senderKeys: MutableMap = mutableMapOf()
+
+ override fun getIdentityKeyPair(): IdentityKeyPair {
+ return identityKey
+ }
+
+ override fun getLocalRegistrationId(): Int {
+ return 1
+ }
+
+ override fun saveIdentity(address: SignalProtocolAddress, identityKey: IdentityKey): Boolean {
+ val hadPrevious = identities.containsKey(address)
+ identities[address] = identityKey
+ return hadPrevious
+ }
+
+ override fun isTrustedIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?, direction: IdentityKeyStore.Direction?): Boolean {
+ return true
+ }
+
+ override fun getIdentity(address: SignalProtocolAddress): IdentityKey? {
+ return identities[address]
+ }
+
+ override fun loadPreKey(preKeyId: Int): PreKeyRecord {
+ return oneTimePreKeys[preKeyId]!!
+ }
+
+ override fun storePreKey(preKeyId: Int, record: PreKeyRecord) {
+ oneTimePreKeys[preKeyId] = record
+ }
+
+ override fun containsPreKey(preKeyId: Int): Boolean {
+ return oneTimePreKeys.containsKey(preKeyId)
+ }
+
+ override fun removePreKey(preKeyId: Int) {
+ oneTimePreKeys.remove(preKeyId)
+ }
+
+ override fun loadSession(address: SignalProtocolAddress): SessionRecord {
+ return sessions.getOrPut(address) { SessionRecord() }
+ }
+
+ override fun loadExistingSessions(addresses: List): List {
+ return addresses.map { sessions[it]!! }
+ }
+
+ override fun getSubDeviceSessions(name: String): List {
+ return sessions
+ .filter { it.key.name == name && it.key.deviceId != 1 && it.value.isValid() }
+ .map { it.key.deviceId }
+ }
+
+ override fun storeSession(address: SignalProtocolAddress, record: SessionRecord) {
+ sessions[address] = record
+ }
+
+ override fun containsSession(address: SignalProtocolAddress): Boolean {
+ return sessions[address]?.isValid() ?: false
+ }
+
+ override fun deleteSession(address: SignalProtocolAddress) {
+ sessions -= address
+ }
+
+ override fun deleteAllSessions(name: String) {
+ sessions = sessions.filter { it.key.name == name }.toMutableMap()
+ }
+
+ override fun loadSignedPreKey(signedPreKeyId: Int): SignedPreKeyRecord {
+ return signedPreKeys[signedPreKeyId]!!
+ }
+
+ override fun loadSignedPreKeys(): List {
+ return signedPreKeys.values.toList()
+ }
+
+ override fun storeSignedPreKey(signedPreKeyId: Int, record: SignedPreKeyRecord) {
+ signedPreKeys[signedPreKeyId] = record
+ }
+
+ override fun containsSignedPreKey(signedPreKeyId: Int): Boolean {
+ return signedPreKeys.containsKey(signedPreKeyId)
+ }
+
+ override fun removeSignedPreKey(signedPreKeyId: Int) {
+ signedPreKeys -= signedPreKeyId
+ }
+
+ override fun storeSenderKey(sender: SignalProtocolAddress, distributionId: UUID, record: SenderKeyRecord) {
+ senderKeys[SenderKeyLocator(sender, distributionId)] = record
+ }
+
+ override fun loadSenderKey(sender: SignalProtocolAddress, distributionId: UUID): SenderKeyRecord {
+ return senderKeys[SenderKeyLocator(sender, distributionId)]!!
+ }
+
+ override fun archiveSession(address: SignalProtocolAddress) {
+ sessions[address]!!.archiveCurrentState()
+ }
+
+ override fun getAllAddressesWithActiveSessions(addressNames: MutableList): Set {
+ return sessions
+ .filter { it.key.name in addressNames }
+ .filter { it.value.isValid() }
+ .map { it.key }
+ .toSet()
+ }
+
+ override fun getSenderKeySharedWith(distributionId: DistributionId): Set {
+ error("Not used")
+ }
+
+ override fun markSenderKeySharedWith(distributionId: DistributionId, addresses: Collection) {
+ // Not used
+ }
+
+ override fun clearSenderKeySharedWith(addresses: Collection) {
+ // Not used
+ }
+
+ override fun isMultiDevice(): Boolean {
+ return false
+ }
+
+ private fun SessionRecord.isValid(): Boolean {
+ return this.hasSenderChain() && this.sessionVersion == CiphertextMessage.CURRENT_VERSION
+ }
+
+ private data class SenderKeyLocator(val address: SignalProtocolAddress, val distributionId: UUID)
+}
diff --git a/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt b/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt
new file mode 100644
index 0000000000..cf13a291ff
--- /dev/null
+++ b/microbenchmark/src/androidTest/java/org/signal/util/SignalClient.kt
@@ -0,0 +1,175 @@
+package org.signal.util
+
+import com.google.protobuf.ByteString
+import org.signal.libsignal.internal.Native
+import org.signal.libsignal.internal.NativeHandleGuard
+import org.signal.libsignal.metadata.certificate.CertificateValidator
+import org.signal.libsignal.metadata.certificate.SenderCertificate
+import org.signal.libsignal.metadata.certificate.ServerCertificate
+import org.signal.libsignal.protocol.SessionBuilder
+import org.signal.libsignal.protocol.SignalProtocolAddress
+import org.signal.libsignal.protocol.ecc.Curve
+import org.signal.libsignal.protocol.ecc.ECKeyPair
+import org.signal.libsignal.protocol.ecc.ECPublicKey
+import org.signal.libsignal.protocol.state.PreKeyBundle
+import org.signal.libsignal.protocol.state.PreKeyRecord
+import org.signal.libsignal.protocol.state.SignedPreKeyRecord
+import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
+import org.whispersystems.signalservice.api.SignalSessionLock
+import org.whispersystems.signalservice.api.crypto.ContentHint
+import org.whispersystems.signalservice.api.crypto.EnvelopeContent
+import org.whispersystems.signalservice.api.crypto.SignalServiceCipher
+import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
+import org.whispersystems.signalservice.api.push.ServiceId
+import org.whispersystems.signalservice.api.push.SignalServiceAddress
+import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
+import org.whispersystems.signalservice.internal.push.SignalServiceProtos
+import org.whispersystems.signalservice.internal.util.Util
+import org.whispersystems.util.Base64
+import java.util.Optional
+import java.util.UUID
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.random.Random
+
+/**
+ * An in-memory signal client that can encrypt and decrypt messages.
+ *
+ * Has a single prekey bundle that can be used to initialize a session with another client.
+ */
+class SignalClient {
+ companion object {
+ private val trustRoot: ECKeyPair = Curve.generateKeyPair()
+ }
+
+ private val serviceId: ServiceId = ServiceId.from(UUID.randomUUID())
+
+ private val store: SignalServiceAccountDataStore = InMemorySignalServiceAccountDataStore()
+
+ private val preKeyBundle: PreKeyBundle = let {
+ val preKeyRecord = PreKeyRecord(1, Curve.generateKeyPair())
+ val signedPreKeyPair = Curve.generateKeyPair()
+ val signedPreKeySignature = Curve.calculateSignature(store.identityKeyPair.privateKey, signedPreKeyPair.publicKey.serialize())
+
+ store.storePreKey(1, preKeyRecord)
+ store.storeSignedPreKey(1, SignedPreKeyRecord(1, System.currentTimeMillis(), signedPreKeyPair, signedPreKeySignature))
+
+ PreKeyBundle(1, 1, 1, preKeyRecord.keyPair.publicKey, 1, signedPreKeyPair.publicKey, signedPreKeySignature, store.identityKeyPair.publicKey)
+ }
+
+ private val unidentifiedAccessKey: ByteArray = Util.getSecretBytes(32)
+
+ private val senderCertificate: SenderCertificate = createCertificateFor(
+ trustRoot = trustRoot,
+ uuid = serviceId.uuid(),
+ e164 = "+${Random.nextLong(1111111111L, 9999999999L)}",
+ deviceId = 1,
+ identityKey = store.identityKeyPair.publicKey.publicKey,
+ expires = Long.MAX_VALUE
+ )
+
+ private val cipher = SignalServiceCipher(SignalServiceAddress(serviceId), 1, store, TestSessionLock(), CertificateValidator(trustRoot.publicKey))
+
+ /**
+ * Sets up sessions using the [to] client's [preKeyBundle]. Note that you can only initialize a client once
+ * since we currently only make a single prekey bundle.
+ */
+ fun initializeSession(to: SignalClient) {
+ val address = SignalProtocolAddress(to.serviceId.toString(), 1)
+ SessionBuilder(store, address).process(to.preKeyBundle)
+ }
+
+ fun encryptUnsealedSender(to: SignalClient): SignalServiceProtos.Envelope {
+ val sentTimestamp = System.currentTimeMillis()
+
+ val message = SignalServiceProtos.DataMessage.newBuilder()
+ .setBody("Test Message")
+ .setTimestamp(sentTimestamp)
+ .build()
+
+ val content = SignalServiceProtos.Content.newBuilder()
+ .setDataMessage(message)
+ .build()
+
+ val outgoingPushMessage: OutgoingPushMessage = cipher.encrypt(
+ SignalProtocolAddress(to.serviceId.toString(), 1),
+ Optional.empty(),
+ EnvelopeContent.encrypted(content, ContentHint.RESENDABLE, Optional.empty())
+ )
+
+ val encryptedContent: ByteArray = Base64.decode(outgoingPushMessage.content)
+
+ return SignalServiceProtos.Envelope.newBuilder()
+ .setSourceUuid(serviceId.toString())
+ .setSourceDevice(1)
+ .setDestinationUuid(to.serviceId.toString())
+ .setTimestamp(sentTimestamp)
+ .setServerTimestamp(sentTimestamp)
+ .setServerGuid(UUID.randomUUID().toString())
+ .setType(SignalServiceProtos.Envelope.Type.valueOf(outgoingPushMessage.type))
+ .setUrgent(true)
+ .setContent(ByteString.copyFrom(encryptedContent))
+ .build()
+ }
+
+ fun encryptSealedSender(to: SignalClient): SignalServiceProtos.Envelope {
+ val sentTimestamp = System.currentTimeMillis()
+
+ val message = SignalServiceProtos.DataMessage.newBuilder()
+ .setBody("Test Message")
+ .setTimestamp(sentTimestamp)
+ .build()
+
+ val content = SignalServiceProtos.Content.newBuilder()
+ .setDataMessage(message)
+ .build()
+
+ val outgoingPushMessage: OutgoingPushMessage = cipher.encrypt(
+ SignalProtocolAddress(to.serviceId.toString(), 1),
+ Optional.of(UnidentifiedAccess(to.unidentifiedAccessKey, senderCertificate.serialized)),
+ EnvelopeContent.encrypted(content, ContentHint.RESENDABLE, Optional.empty())
+ )
+
+ val encryptedContent: ByteArray = Base64.decode(outgoingPushMessage.content)
+
+ return SignalServiceProtos.Envelope.newBuilder()
+ .setSourceUuid(serviceId.toString())
+ .setSourceDevice(1)
+ .setDestinationUuid(to.serviceId.toString())
+ .setTimestamp(sentTimestamp)
+ .setServerTimestamp(sentTimestamp)
+ .setServerGuid(UUID.randomUUID().toString())
+ .setType(SignalServiceProtos.Envelope.Type.valueOf(outgoingPushMessage.type))
+ .setUrgent(true)
+ .setContent(ByteString.copyFrom(encryptedContent))
+ .build()
+ }
+
+ fun decryptMessage(envelope: SignalServiceProtos.Envelope) {
+ cipher.decrypt(envelope, System.currentTimeMillis())
+ }
+}
+
+private fun createCertificateFor(trustRoot: ECKeyPair, uuid: UUID, e164: String, deviceId: Int, identityKey: ECPublicKey, expires: Long): SenderCertificate {
+ val serverKey: ECKeyPair = Curve.generateKeyPair()
+ NativeHandleGuard(serverKey.publicKey).use { serverPublicGuard ->
+ NativeHandleGuard(trustRoot.privateKey).use { trustRootPrivateGuard ->
+ val serverCertificate = ServerCertificate(Native.ServerCertificate_New(1, serverPublicGuard.nativeHandle(), trustRootPrivateGuard.nativeHandle()))
+ NativeHandleGuard(identityKey).use { identityGuard ->
+ NativeHandleGuard(serverCertificate).use { serverCertificateGuard ->
+ NativeHandleGuard(serverKey.privateKey).use { serverPrivateGuard ->
+ return SenderCertificate(Native.SenderCertificate_New(uuid.toString(), e164, deviceId, identityGuard.nativeHandle(), expires, serverCertificateGuard.nativeHandle(), serverPrivateGuard.nativeHandle()))
+ }
+ }
+ }
+ }
+ }
+}
+
+private class TestSessionLock : SignalSessionLock {
+ val lock = ReentrantLock()
+
+ override fun acquire(): SignalSessionLock.Lock {
+ lock.lock()
+ return SignalSessionLock.Lock { lock.unlock() }
+ }
+}
diff --git a/microbenchmark/src/main/AndroidManifest.xml b/microbenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..568741e54f
--- /dev/null
+++ b/microbenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 59aa5e625f..feb7a38ab9 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -88,3 +88,4 @@ project(':qr-app').projectDir = file('qr/app')
rootProject.name='Signal'
apply from: 'dependencies.gradle'
+include ':microbenchmark'