use std::iter;
use matrix_sdk_base::{
crypto::types::events::room_key_bundle::RoomKeyBundleContent,
media::{MediaFormat, MediaRequestParameters},
};
use ruma::{OwnedUserId, UserId, events::room::MediaSource};
use tracing::{info, instrument, warn};
use crate::{Error, Result, Room};
#[instrument(skip(room), fields(room_id = ?room.room_id()))]
pub(super) async fn share_room_history(room: &Room, user_id: OwnedUserId) -> Result<()> {
let client = &room.client;
let own_identity = match client.user_id() {
Some(own_user) => client.encryption().get_user_identity(own_user).await?,
None => None,
};
if own_identity.is_none() {
warn!("Not sharing message history as cross-signing is not set up");
return Ok(());
}
info!("Sharing message history");
let olm_machine = client.olm_machine().await;
let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
let bundle = olm_machine.store().build_room_key_bundle(room.room_id()).await?;
if bundle.is_empty() {
info!("No keys to share");
return Ok(());
}
let json = serde_json::to_vec(&bundle)?;
let upload = client.upload_encrypted_file(&mut (json.as_slice())).await?;
info!(
media_url = ?upload.url,
shared_keys = bundle.room_keys.len(),
withheld_keys = bundle.withheld.len(),
"Uploaded encrypted key blob"
);
let (req_id, request) = olm_machine.query_keys_for_users(iter::once(user_id.as_ref()));
if !request.device_keys.is_empty() {
room.client.keys_query(&req_id, request.device_keys).await?;
}
client.claim_one_time_keys(iter::once(user_id.as_ref())).await?;
let content = RoomKeyBundleContent { room_id: room.room_id().to_owned(), file: upload };
let requests = {
let olm_machine = client.olm_machine().await;
let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
olm_machine
.share_room_key_bundle_data(
&user_id,
&client.base_client().room_key_recipient_strategy,
content,
)
.await?
};
for request in requests {
let response = client.send_to_device(&request).await?;
client.mark_request_as_sent(&request.txn_id, &response).await?;
}
Ok(())
}
#[instrument(skip(room), fields(room_id = ?room.room_id(), bundle_sender))]
pub(crate) async fn maybe_accept_key_bundle(room: &Room, inviter: &UserId) -> Result<()> {
let client = &room.client;
let olm_machine = client.olm_machine().await;
let Some(olm_machine) = olm_machine.as_ref() else {
warn!("Not fetching room key bundle as the Olm machine is not available");
return Ok(());
};
let Some(bundle_info) =
olm_machine.store().get_received_room_key_bundle_data(room.room_id(), inviter).await?
else {
info!("No room key bundle from inviter found");
return Ok(());
};
tracing::Span::current().record("bundle_sender", bundle_info.sender_user.as_str());
let (req_id, request) =
olm_machine.query_keys_for_users(iter::once(bundle_info.sender_user.as_ref()));
if !request.device_keys.is_empty() {
room.client.keys_query(&req_id, request.device_keys).await?;
}
let bundle_content = client
.media()
.get_media_content(
&MediaRequestParameters {
source: MediaSource::Encrypted(Box::new(bundle_info.bundle_data.file.clone())),
format: MediaFormat::File,
},
false,
)
.await?;
match serde_json::from_slice(&bundle_content) {
Ok(bundle) => {
olm_machine
.store()
.receive_room_key_bundle(
&bundle_info,
bundle,
|_, _| {},
)
.await?;
}
Err(err) => {
warn!("Failed to deserialize room key bundle: {err}");
}
}
Ok(())
}