Skip to content

Commit e3c6044

Browse files
committed
Integ test for merging megolm sessions with history sharing
Add an integration test that checks that, when we receive a copy of a megolm session directly after having previously received it via history sharing, we get the best bits of both.
1 parent 794e623 commit e3c6044

File tree

1 file changed

+151
-2
lines changed

1 file changed

+151
-2
lines changed

testing/matrix-sdk-integration-testing/src/tests/e2ee/shared_history.rs

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ use matrix_sdk::{
2525
};
2626
use matrix_sdk_base::crypto::types::events::UtdCause;
2727
use matrix_sdk_common::deserialized_responses::{
28-
ProcessedToDeviceEvent, UnableToDecryptReason::MissingMegolmSession, WithheldCode,
28+
DeviceLinkProblem, ProcessedToDeviceEvent, UnableToDecryptReason::MissingMegolmSession,
29+
VerificationLevel, VerificationState, WithheldCode,
2930
};
3031
use matrix_sdk_ui::{
3132
Timeline,
@@ -553,6 +554,148 @@ async fn test_transitive_history_share_with_withhelds() -> Result<()> {
553554
Ok(())
554555
}
555556

557+
/// Test megolm session merging with history sharing
558+
///
559+
/// 1. Alice and Bob share a room
560+
/// 2. Bob sends a message
561+
/// 3. Alice invites Charlie, sharing the history
562+
/// 4. Charlie can see Bob's message, but the sender is unauthenticated.
563+
/// 5. Bob sends another message (on the same session)
564+
/// 6. Charlie can now decrypt both of Bob's messages, with authenticated
565+
/// sender.
566+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
567+
async fn test_history_sharing_session_merging() -> Result<()> {
568+
let alice_span = tracing::info_span!("alice");
569+
let bob_span = tracing::info_span!("bob");
570+
let charlie_span = tracing::info_span!("charlie");
571+
572+
let alice = create_encryption_enabled_client("alice").instrument(alice_span.clone()).await?;
573+
let bob = create_encryption_enabled_client("bob").instrument(bob_span.clone()).await?;
574+
let charlie =
575+
create_encryption_enabled_client("charlie").instrument(charlie_span.clone()).await?;
576+
577+
// 1. Alice creates a room, and enables encryption
578+
let alice_room = alice
579+
.create_room(assign!(CreateRoomRequest::new(), {
580+
preset: Some(RoomPreset::PublicChat),
581+
}))
582+
.instrument(alice_span.clone())
583+
.await?;
584+
let alice_timeline = alice_room.timeline().await?;
585+
586+
alice_room.enable_encryption().instrument(alice_span.clone()).await?;
587+
info!(room_id = ?alice_room.room_id(), "Alice has created and enabled encryption in the room");
588+
589+
// ... and invites Bob to the room
590+
alice_room.invite_user_by_id(bob.user_id().unwrap()).instrument(alice_span.clone()).await?;
591+
592+
// Bob joins
593+
bob.sync_once().instrument(bob_span.clone()).await?;
594+
595+
let bob_room = bob
596+
.join_room_by_id(alice_room.room_id())
597+
.instrument(bob_span.clone())
598+
.await
599+
.expect("Bob should be able to accept the invitation from Alice");
600+
601+
// 2. Bob sends a message, which Alice should receive
602+
let bob_send_test_event = async |event_content: &str| {
603+
let bob_event_id = bob_room
604+
.send(RoomMessageEventContent::text_plain(event_content))
605+
.into_future()
606+
.instrument(bob_span.clone())
607+
.await
608+
.expect("We should be able to send a message to the room")
609+
.event_id;
610+
611+
alice
612+
.sync_once()
613+
.instrument(alice_span.clone())
614+
.await
615+
.expect("Alice should be able to sync");
616+
617+
assert_event_received(&alice_timeline, &bob_event_id, event_content).await;
618+
619+
bob_event_id
620+
};
621+
622+
let event_id_1 = bob_send_test_event("Event 1").await;
623+
624+
// 3. Alice invites Charlie.
625+
alice_room.invite_user_by_id(charlie.user_id().unwrap()).instrument(alice_span.clone()).await?;
626+
627+
// Workaround for https://github.com/matrix-org/matrix-rust-sdk/issues/5770: Charlie needs a copy of
628+
// Alice's identity.
629+
charlie
630+
.encryption()
631+
.request_user_identity(alice.user_id().unwrap())
632+
.instrument(charlie_span.clone())
633+
.await?;
634+
635+
charlie.sync_once().instrument(charlie_span.clone()).await?;
636+
let charlie_room = charlie
637+
.join_room_by_id(alice_room.room_id())
638+
.instrument(charlie_span.clone())
639+
.await
640+
.expect("Charlie should be able to accept the invitation from Alice");
641+
642+
// 4. Charlie can see Bob's message, but the sender is unauthenticated.
643+
let charlie_timeline = charlie_room.timeline().await?;
644+
charlie.sync_once().instrument(charlie_span.clone()).await?;
645+
let received_event = assert_event_received(&charlie_timeline, &event_id_1, "Event 1").await;
646+
assert_eq!(
647+
received_event
648+
.as_event()
649+
.unwrap()
650+
.encryption_info()
651+
.expect("Received event should be encrypted")
652+
.verification_state,
653+
VerificationState::Unverified(VerificationLevel::None(DeviceLinkProblem::InsecureSource))
654+
);
655+
656+
// 5. Bob sends another message (on the same session)
657+
bob.sync_once().instrument(bob_span.clone()).await?;
658+
// Sanity: make sure Bob knows that Charlie has joined
659+
bob_room
660+
.get_member_no_sync(charlie.user_id().unwrap())
661+
.instrument(bob_span.clone())
662+
.await?
663+
.expect("Bob should see Charlie in the room");
664+
let event_id_2 = bob_send_test_event("Event 2").await;
665+
666+
// 6. Charlie can now decrypt both of Bob's messages, with authenticated sender
667+
let mut charlie_room_stream = charlie.encryption().room_keys_received_stream().await.unwrap();
668+
charlie.sync_once().instrument(charlie_span.clone()).await?;
669+
670+
// Make sure we're decrypting with the newly-received keys.
671+
assert_next_with_timeout!(&mut charlie_room_stream).expect("charlie should receive room keys");
672+
673+
let received_event = assert_event_received(&charlie_timeline, &event_id_2, "Event 2").await;
674+
assert_eq!(
675+
received_event
676+
.as_event()
677+
.unwrap()
678+
.encryption_info()
679+
.expect("Received event should be encrypted")
680+
.verification_state,
681+
VerificationState::Unverified(VerificationLevel::UnverifiedIdentity)
682+
);
683+
684+
// The earlier event should now have a better verification status.
685+
let received_event = assert_event_received(&charlie_timeline, &event_id_1, "Event 1").await;
686+
assert_eq!(
687+
received_event
688+
.as_event()
689+
.unwrap()
690+
.encryption_info()
691+
.expect("Received event should be encrypted")
692+
.verification_state,
693+
VerificationState::Unverified(VerificationLevel::UnverifiedIdentity)
694+
);
695+
696+
Ok(())
697+
}
698+
556699
async fn create_encryption_enabled_client(username: &str) -> Result<SyncTokenAwareClient> {
557700
let encryption_settings =
558701
EncryptionSettings { auto_enable_cross_signing: true, ..Default::default() };
@@ -624,7 +767,11 @@ async fn wait_for_timeline_event(
624767
* Wait for the given event to arrive in the timeline, and assert that its
625768
* content matches that given.
626769
*/
627-
async fn assert_event_received(timeline: &Timeline, event_id: &EventId, expected_content: &str) {
770+
async fn assert_event_received(
771+
timeline: &Timeline,
772+
event_id: &EventId,
773+
expected_content: &str,
774+
) -> Arc<TimelineItem> {
628775
let timeline_item = wait_for_timeline_event(timeline, event_id).await.unwrap_or_else(|| {
629776
panic!("Timeout waiting for event {event_id} with content {expected_content} to arrive")
630777
});
@@ -639,6 +786,8 @@ async fn assert_event_received(timeline: &Timeline, event_id: &EventId, expected
639786
expected_content,
640787
"The decrypted event should match the message Bob has sent"
641788
);
789+
790+
timeline_item
642791
}
643792

644793
/**

0 commit comments

Comments
 (0)