Add test for Unwedging (before implementing it)

This commit is contained in:
Benoit Marty 2020-03-03 18:23:31 +01:00
parent 41a8f40241
commit 0cb43eef51
4 changed files with 203 additions and 46 deletions

View File

@ -25,6 +25,7 @@
<w>signup</w> <w>signup</w>
<w>ssss</w> <w>ssss</w>
<w>threepid</w> <w>threepid</w>
<w>unwedging</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

View File

@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
@ -40,7 +41,6 @@ import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import java.util.HashMap import java.util.HashMap
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@ -140,64 +140,38 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
* @return Alice, Bob and Sam session * @return Alice, Bob and Sam session
*/ */
fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData { fun doE2ETestWithAliceAndBobAndSamInARoom(): CryptoTestData {
val statuses = HashMap<String, String>()
val cryptoTestData = doE2ETestWithAliceAndBobInARoom() val cryptoTestData = doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId val aliceRoomId = cryptoTestData.roomId
val room = aliceSession.getRoom(aliceRoomId)!! val room = aliceSession.getRoom(aliceRoomId)!!
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams) val samSession = createSamAccountAndInviteToTheRoom(room)
val lock1 = CountDownLatch(2)
// val samEventListener = object : MXEventListener() {
// override fun onNewRoom(roomId: String) {
// if (TextUtils.equals(roomId, aliceRoomId)) {
// if (!statuses.containsKey("onNewRoom")) {
// statuses["onNewRoom"] = "onNewRoom"
// lock1.countDown()
// }
// }
// }
// }
//
// samSession.dataHandler.addListener(samEventListener)
room.invite(samSession.myUserId, null, object : TestMatrixCallback<Unit>(lock1) {
override fun onSuccess(data: Unit) {
statuses["invite"] = "invite"
super.onSuccess(data)
}
})
mTestHelper.await(lock1)
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
// samSession.dataHandler.removeListener(samEventListener)
val lock2 = CountDownLatch(1)
samSession.joinRoom(aliceRoomId, null, object : TestMatrixCallback<Unit>(lock2) {
override fun onSuccess(data: Unit) {
statuses["joinRoom"] = "joinRoom"
super.onSuccess(data)
}
})
mTestHelper.await(lock2)
assertTrue(statuses.containsKey("joinRoom"))
// wait the initial sync // wait the initial sync
SystemClock.sleep(1000) SystemClock.sleep(1000)
// samSession.dataHandler.removeListener(samEventListener)
return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession) return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
} }
/**
* Create Sam account and invite him in the room. He will accept the invitation
* @Return Sam session
*/
fun createSamAccountAndInviteToTheRoom(room: Room): Session {
val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
mTestHelper.doSync<Unit> {
room.invite(samSession.myUserId, null, it)
}
mTestHelper.doSync<Unit> {
samSession.joinRoom(room.roomId, null, it)
}
return samSession
}
/** /**
* @return Alice and Bob sessions * @return Alice and Bob sessions
*/ */

View File

@ -0,0 +1,174 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import java.util.concurrent.CountDownLatch
/**
* Ref:
* - https://github.com/matrix-org/matrix-doc/pull/1719
* - https://matrix.org/docs/spec/client_server/latest#recovering-from-undecryptable-messages
* - https://github.com/matrix-org/matrix-js-sdk/pull/780
* - https://github.com/matrix-org/matrix-ios-sdk/pull/778
* - https://github.com/matrix-org/matrix-ios-sdk/pull/784
*/
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class UnwedgingTest : InstrumentedTest {
private lateinit var messagesReceivedByBob: List<TimelineEvent>
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
@Before
fun init() {
messagesReceivedByBob = emptyList()
}
/**
* - Alice & Bob in a e2e room
* - Alice sends a 1st message with a 1st megolm session
* - Store the olm session between A&B devices
* - Alice sends a 2nd message with a 2nd megolm session
* - Simulate Alice using a backup of her OS and make her crypto state like after the first message
* - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
*
* What Bob must see:
* -> No issue with the 2 first messages
* -> The third event must fail to decrypt at first because Bob the olm session is wedged
* -> This is automatically fixed after SDKs restarted the olm session
*/
@Test
fun testUnwedging() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId
val bobSession = cryptoTestData.secondSession!!
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
bobSession.cryptoService().setWarnOnUnknownDevices(false)
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
bobTimeline.start()
var latch = CountDownLatch(1)
var bobEventsListener = createEventListener(latch, 1)
bobTimeline.addListener(bobEventsListener)
messagesReceivedByBob = emptyList()
// - Alice sends a 1st message with a 1st megolm session
roomFromAlicePOV.sendTextMessage("First message")
// Wait for the message to be received by Bob
mTestHelper.await(latch)
bobTimeline.removeListener(bobEventsListener)
messagesReceivedByBob.size shouldBe 1
// - Store the olm session between A&B devices
// Let us pickle our session with bob here so we can later unpickle it
// and wedge our session.
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!)
sessionIdsForBob!!.size shouldBe 1
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!!
// Sam join the room
val samSession = mCryptoTestHelper.createSamAccountAndInviteToTheRoom(roomFromAlicePOV)
latch = CountDownLatch(1)
bobEventsListener = createEventListener(latch, 2)
bobTimeline.addListener(bobEventsListener)
messagesReceivedByBob = emptyList()
// - Alice sends a 2nd message with a 2nd megolm session
roomFromAlicePOV.sendTextMessage("Second message")
// Wait for the message to be received by Bob
mTestHelper.await(latch)
bobTimeline.removeListener(bobEventsListener)
messagesReceivedByBob.size shouldBe 2
// Let us wedge the session now. Set crypto state like after the first message
aliceCryptoStore.storeSession(olmSession, bobSession.cryptoService().getMyDevice().identityKey()!!)
latch = CountDownLatch(1)
bobEventsListener = createEventListener(latch, 3)
bobTimeline.addListener(bobEventsListener)
messagesReceivedByBob = emptyList()
// - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
roomFromAlicePOV.sendTextMessage("Third message")
// Wait for the message to be received by Bob
mTestHelper.await(latch)
bobTimeline.removeListener(bobEventsListener)
messagesReceivedByBob.size shouldBe 3
messagesReceivedByBob[0].root.getClearType() shouldBeEqualTo EventType.ENCRYPTED
messagesReceivedByBob[1].root.getClearType() shouldBeEqualTo EventType.MESSAGE
messagesReceivedByBob[2].root.getClearType() shouldBeEqualTo EventType.MESSAGE
bobTimeline.dispose()
cryptoTestData.cleanUp(mTestHelper)
mTestHelper.signOutAndClose(samSession)
}
private fun createEventListener(latch: CountDownLatch, expectedNumberOfMessages: Int): Timeline.Listener {
return object : Timeline.Listener {
override fun onTimelineFailure(throwable: Throwable) {
// noop
}
override fun onNewTimelineEvents(eventIds: List<String>) {
// noop
}
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
messagesReceivedByBob = snapshot.filter { it.root.type == EventType.ENCRYPTED }
if (messagesReceivedByBob.size == expectedNumberOfMessages) {
latch.countDown()
}
}
}
}
}

View File

@ -21,6 +21,7 @@ package im.vector.matrix.android.internal.crypto
import android.content.Context import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.squareup.moshi.Types import com.squareup.moshi.Types
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
@ -1192,4 +1193,11 @@ internal class DefaultCryptoService @Inject constructor(
override fun getGossipingEventsTrail(): List<Event> { override fun getGossipingEventsTrail(): List<Event> {
return cryptoStore.getGossipingEventsTrail() return cryptoStore.getGossipingEventsTrail()
} }
/* ==========================================================================================
* For test only
* ========================================================================================== */
@VisibleForTesting
val cryptoStoreForTesting = cryptoStore
} }