use std::io::Write;
use assert_matches::assert_matches;
use eyeball_im::VectorDiff;
use matrix_sdk::{
assert_next_matches_with_timeout,
linked_chunk::{ChunkIdentifier, LinkedChunkId, Position, Update},
test_utils::mocks::MatrixMockServer,
};
use matrix_sdk_test::{BOB, async_test, event_factory::EventFactory};
use matrix_sdk_ui::timeline::RoomExt;
use ruma::{
event_id,
events::room::encrypted::{
EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent,
},
room_id,
};
use stream_assert::assert_pending;
use tempfile::NamedTempFile;
#[async_test]
async fn test_an_utd_from_the_event_cache_as_an_initial_item_is_decrypted() {
const SESSION_ID: &str = "gM8i47Xhu0q52xLfgUXzanCMpLinoyVyH7R58cBuVBU";
const SESSION_KEY: &[u8] = b"\
-----BEGIN MEGOLM SESSION DATA-----\n\
ASKcWoiAVUM97482UAi83Avce62hSLce7i5JhsqoF6xeAAAACqt2Cg3nyJPRWTTMXxXH7TXnkfdlmBXbQtq5\
bpHo3LRijcq2Gc6TXilESCmJN14pIsfKRJrWjZ0squ/XsoTFytuVLWwkNaW3QF6obeg2IoVtJXLMPdw3b2vO\
vgwGY3OMP0XafH13j1vcb6YLzvgLkZQLnYvd47hv3yK/9GmKS9tokuaQ7dCVYckYcIOS09EDTs70YdxUd5WG\
rQynATCLFP1p/NAGv70r9MK7Cy/mNpjD0r4qC7UEDIoi1kOWzHgnLo19wtvwsb8Fg8ATxcs3Wmtj8hIUYpDx\
ia4sM10zbytUuaPUAfCDf42IyxdmOnGe1CueXhgI71y+RW0s0argNqUt7jB70JT0o9CyX6UBGRaqLk2MPY9T\
hUu5J8X3UgIa6rcbWigzohzWm9rdbEHFrSWqjpfQYMaAKQQgETrjSy4XTrp2RhC2oNqG/hylI4ab+F4X6fpH\
DYP1NqNMP5g36xNu7LhDnrUB5qsPjYOmWORxGLfudpF3oLYCSlr3DgHqEIB6HjQblLZ3KQuPBse3zxyROTnS\
AhdPH4a/z1wioFtKNVph3hecsiKEdqnz4Y2coSIdhz58mJ9JWNQoFAENE5CSsoEZAGvafYZVpW4C75YY2zq1\
wIeiFi1dT43/jLAUGkslsi1VvnyfUu8qO404RxYO3XHoGLMFoFLOO+lZ+VGci2Vz10AhxJhEBHxRKxw4k2uB\
HztoSJUr/2Y\n\
-----END MEGOLM SESSION DATA-----";
let room_id = room_id!("!DovneieKSTkdHKpIXy:morpheus.localhost");
let event_factory = EventFactory::new().room(room_id).sender(&BOB);
let mock_server = MatrixMockServer::new().await;
let client = mock_server.client_builder().build().await;
{
let event_cache_store = client.event_cache_store().lock().await.unwrap();
event_cache_store
.as_clean()
.unwrap()
.handle_linked_chunk_updates(
LinkedChunkId::Room(room_id),
vec![
Update::NewItemsChunk {
previous: None,
new: ChunkIdentifier::new(0),
next: None,
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(0), 0),
items: vec![event_factory
.event(RoomEncryptedEventContent::new(
EncryptedEventScheme::MegolmV1AesSha2(
MegolmV1AesSha2ContentInit {
ciphertext: "\
AwgAEtABPRMavuZMDJrPo6pGQP4qVmpcuapuXtzKXJyi3YpEsjSWdzuRKIgJzD4P\
cSqJM1A8kzxecTQNJsC5q22+KSFEPxPnI4ltpm7GFowSoPSW9+bFdnlfUzEP1jPq\
YevHAsMJp2fRKkzQQbPordrUk1gNqEpGl4BYFeRqKl9GPdKFwy45huvQCLNNueql\
CFZVoYMuhxrfyMiJJAVNTofkr2um2mKjDTlajHtr39pTG8k0eOjSXkLOSdZvNOMz\
hGhSaFNeERSA2G2YbeknOvU7MvjiO0AKuxaAe1CaVhAI14FCgzrJ8g0y5nly+n7x\
QzL2G2Dn8EoXM5Iqj8W99iokQoVsSrUEnaQ1WnSIfewvDDt4LCaD/w7PGETMCQ"
.to_owned(),
sender_key: "DeHIg4gwhClxzFYcmNntPNF9YtsdZbmMy8+3kzCMXHA"
.to_owned(),
device_id: "NLAZCWIOCO".into(),
session_id: SESSION_ID.into(),
}
.into(),
),
None,
))
.event_id(event_id!("$ev0"))
.into_utd_sync_timeline_event(),
],
},
],
)
.await
.expect("Failed to setup the event cache");
}
{
let mut tempfile =
NamedTempFile::new().expect("Failed to create a temporary file for the keys");
tempfile.write_all(SESSION_KEY).expect("Failed to write the keys in the temporary file");
let tempfile_path = tempfile.into_temp_path();
let room_key_import_result = client
.encryption()
.import_room_keys(tempfile_path.to_path_buf(), "1234")
.await
.expect("Failed to import the keys");
assert_eq!(room_key_import_result.imported_count, 1);
}
let event_cache = client.event_cache();
event_cache.subscribe().unwrap();
let room = mock_server.sync_joined_room(&client, room_id).await;
let timeline = room.timeline().await.unwrap();
let (initial_updates, mut updates_stream) = timeline.subscribe().await;
assert_eq!(initial_updates.len(), 2);
assert!(&initial_updates[0].is_date_divider());
assert_matches!(&initial_updates[1].as_event(), Some(event) => {
assert_eq!(event.event_id().unwrap().as_str(), "$ev0");
assert!(event.content().is_unable_to_decrypt());
});
assert_next_matches_with_timeout!(updates_stream, 250, updates => {
assert_eq!(updates.len(), 1, "Expecting 1 update from the `Timeline`");
assert_matches!(&updates[0], VectorDiff::Set { index: 1, value: event } => {
assert_matches!(event.as_event(), Some(event) => {
assert_eq!(event.event_id().unwrap().as_str(), "$ev0");
assert_matches!(event.content().as_message(), Some(message) => {
assert_eq!(message.body(), "It's a secret to everybody");
});
});
});
});
assert_pending!(updates_stream);
}
#[async_test]
async fn test_an_utd_from_the_event_cache_as_a_paginated_item_is_decrypted() {
const SESSION_ID: &str = "gM8i47Xhu0q52xLfgUXzanCMpLinoyVyH7R58cBuVBU";
const SESSION_KEY: &[u8] = b"\
-----BEGIN MEGOLM SESSION DATA-----\n\
ASKcWoiAVUM97482UAi83Avce62hSLce7i5JhsqoF6xeAAAACqt2Cg3nyJPRWTTMXxXH7TXnkfdlmBXbQtq5\
bpHo3LRijcq2Gc6TXilESCmJN14pIsfKRJrWjZ0squ/XsoTFytuVLWwkNaW3QF6obeg2IoVtJXLMPdw3b2vO\
vgwGY3OMP0XafH13j1vcb6YLzvgLkZQLnYvd47hv3yK/9GmKS9tokuaQ7dCVYckYcIOS09EDTs70YdxUd5WG\
rQynATCLFP1p/NAGv70r9MK7Cy/mNpjD0r4qC7UEDIoi1kOWzHgnLo19wtvwsb8Fg8ATxcs3Wmtj8hIUYpDx\
ia4sM10zbytUuaPUAfCDf42IyxdmOnGe1CueXhgI71y+RW0s0argNqUt7jB70JT0o9CyX6UBGRaqLk2MPY9T\
hUu5J8X3UgIa6rcbWigzohzWm9rdbEHFrSWqjpfQYMaAKQQgETrjSy4XTrp2RhC2oNqG/hylI4ab+F4X6fpH\
DYP1NqNMP5g36xNu7LhDnrUB5qsPjYOmWORxGLfudpF3oLYCSlr3DgHqEIB6HjQblLZ3KQuPBse3zxyROTnS\
AhdPH4a/z1wioFtKNVph3hecsiKEdqnz4Y2coSIdhz58mJ9JWNQoFAENE5CSsoEZAGvafYZVpW4C75YY2zq1\
wIeiFi1dT43/jLAUGkslsi1VvnyfUu8qO404RxYO3XHoGLMFoFLOO+lZ+VGci2Vz10AhxJhEBHxRKxw4k2uB\
HztoSJUr/2Y\n\
-----END MEGOLM SESSION DATA-----";
let room_id = room_id!("!DovneieKSTkdHKpIXy:morpheus.localhost");
let event_factory = EventFactory::new().room(room_id).sender(&BOB);
let mock_server = MatrixMockServer::new().await;
let client = mock_server.client_builder().build().await;
{
let event_cache_store = client.event_cache_store().lock().await.unwrap();
event_cache_store
.as_clean()
.unwrap()
.handle_linked_chunk_updates(
LinkedChunkId::Room(room_id),
vec![
Update::NewItemsChunk {
previous: None,
new: ChunkIdentifier::new(0),
next: None,
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(0), 0),
items: vec![event_factory
.event(RoomEncryptedEventContent::new(
EncryptedEventScheme::MegolmV1AesSha2(
MegolmV1AesSha2ContentInit {
ciphertext: "\
AwgAEtABPRMavuZMDJrPo6pGQP4qVmpcuapuXtzKXJyi3YpEsjSWdzuRKIgJzD4P\
cSqJM1A8kzxecTQNJsC5q22+KSFEPxPnI4ltpm7GFowSoPSW9+bFdnlfUzEP1jPq\
YevHAsMJp2fRKkzQQbPordrUk1gNqEpGl4BYFeRqKl9GPdKFwy45huvQCLNNueql\
CFZVoYMuhxrfyMiJJAVNTofkr2um2mKjDTlajHtr39pTG8k0eOjSXkLOSdZvNOMz\
hGhSaFNeERSA2G2YbeknOvU7MvjiO0AKuxaAe1CaVhAI14FCgzrJ8g0y5nly+n7x\
QzL2G2Dn8EoXM5Iqj8W99iokQoVsSrUEnaQ1WnSIfewvDDt4LCaD/w7PGETMCQ"
.to_owned(),
sender_key: "DeHIg4gwhClxzFYcmNntPNF9YtsdZbmMy8+3kzCMXHA"
.to_owned(),
device_id: "NLAZCWIOCO".into(),
session_id: SESSION_ID.into(),
}
.into(),
),
None,
))
.event_id(event_id!("$ev0"))
.into_utd_sync_timeline_event(),
],
},
Update::NewItemsChunk {
previous: Some(ChunkIdentifier::new(0)),
new: ChunkIdentifier::new(1),
next: None,
},
Update::PushItems {
at: Position::new(ChunkIdentifier::new(1), 0),
items: vec![event_factory
.text_msg("hello")
.event_id(event_id!("$ev1"))
.into_event()
]
}
],
)
.await
.expect("Failed to setup the event cache");
}
{
let mut tempfile =
NamedTempFile::new().expect("Failed to create a temporary file for the keys");
tempfile.write_all(SESSION_KEY).expect("Failed to write the keys in the temporary file");
let tempfile_path = tempfile.into_temp_path();
let room_key_import_result = client
.encryption()
.import_room_keys(tempfile_path.to_path_buf(), "1234")
.await
.expect("Failed to import the keys");
assert_eq!(room_key_import_result.imported_count, 1);
}
let event_cache = client.event_cache();
event_cache.subscribe().unwrap();
let room = mock_server.sync_joined_room(&client, room_id).await;
let timeline = room.timeline().await.unwrap();
let (initial_updates, mut updates_stream) = timeline.subscribe().await;
assert_eq!(initial_updates.len(), 2);
assert!(&initial_updates[0].is_date_divider());
assert_matches!(&initial_updates[1].as_event(), Some(event) => {
assert_eq!(event.event_id().unwrap().as_str(), "$ev1");
assert_matches!(event.content().as_message(), Some(message) => {
assert_eq!(message.body(), "hello");
});
});
assert_pending!(updates_stream);
let reached_start = timeline.paginate_backwards(1).await.unwrap();
assert!(reached_start);
assert_next_matches_with_timeout!(updates_stream, 250, updates => {
assert_eq!(updates.len(), 1, "We get the start of timeline item");
assert_matches!(&updates[0], VectorDiff::PushFront { value } => {
assert!(value.is_timeline_start());
});
});
assert_next_matches_with_timeout!(updates_stream, 250, updates => {
assert_eq!(updates.len(), 2, "Expecting 2 updates from the `Timeline`");
assert_matches!(&updates[0], VectorDiff::Insert { index: 2, value: event } => {
assert_matches!(event.as_event(), Some(event) => {
assert_eq!(event.event_id().unwrap().as_str(), "$ev0");
assert!(event.content().is_unable_to_decrypt());
});
});
assert_matches!(&updates[1], VectorDiff::Set { index: 2, value: event } => {
assert_matches!(event.as_event(), Some(event) => {
assert_eq!(event.event_id().unwrap().as_str(), "$ev0");
assert_matches!(event.content().as_message(), Some(message) => {
assert_eq!(message.body(), "It's a secret to everybody");
});
});
});
});
assert_pending!(updates_stream);
}