mirror of
https://gitlab.computer.surgery/matrix/grapevine.git
synced 2025-12-18 00:01:24 +01:00
SSS: split up sync_events_v5_route()
This commit is contained in:
parent
9405e5f16c
commit
7558d3456b
1 changed files with 446 additions and 415 deletions
|
|
@ -111,269 +111,30 @@ pub(crate) async fn sync_events_v5_route(
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Users that have left any encrypted rooms the sender was in
|
#[allow(clippy::if_then_some_else_none)]
|
||||||
let mut left_encrypted_users = HashSet::new();
|
let device_lists = if body.extensions.e2ee.enabled.unwrap_or(false) {
|
||||||
let mut device_list_changes = HashSet::new();
|
Some(get_e2ee_data(&sender_user, globalsince, &all_joined_rooms).await?)
|
||||||
let mut device_list_left = HashSet::new();
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
if body.extensions.e2ee.enabled.unwrap_or(false) {
|
|
||||||
// Look for device list updates of this account
|
|
||||||
device_list_changes.extend(
|
|
||||||
services()
|
|
||||||
.users
|
|
||||||
.keys_changed(sender_user.as_ref(), globalsince, None)
|
|
||||||
.filter_map(Result::ok),
|
|
||||||
);
|
|
||||||
|
|
||||||
for room_id in &all_joined_rooms {
|
|
||||||
let Some(current_shortstatehash) =
|
|
||||||
services().rooms.state.get_room_shortstatehash(room_id)?
|
|
||||||
else {
|
|
||||||
error!(%room_id, "Room has no state");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let since_shortstatehash = services()
|
|
||||||
.rooms
|
|
||||||
.user
|
|
||||||
.get_token_shortstatehash(room_id, globalsince)?;
|
|
||||||
|
|
||||||
let since_sender_member: Option<RoomMemberEventContent> =
|
|
||||||
since_shortstatehash
|
|
||||||
.and_then(|shortstatehash| {
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.state_get(
|
|
||||||
shortstatehash,
|
|
||||||
&StateEventType::RoomMember,
|
|
||||||
sender_user.as_str(),
|
|
||||||
)
|
|
||||||
.transpose()
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.and_then(|pdu| {
|
|
||||||
serde_json::from_str(pdu.content.get())
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::bad_database("Invalid PDU in database.")
|
|
||||||
})
|
|
||||||
.ok()
|
|
||||||
});
|
|
||||||
|
|
||||||
let encrypted_room = services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.state_get(
|
|
||||||
current_shortstatehash,
|
|
||||||
&StateEventType::RoomEncryption,
|
|
||||||
"",
|
|
||||||
)?
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
if let Some(since_shortstatehash) = since_shortstatehash {
|
|
||||||
// Skip if there are only timeline changes
|
|
||||||
if since_shortstatehash == current_shortstatehash {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let since_encryption =
|
|
||||||
services().rooms.state_accessor.state_get(
|
|
||||||
since_shortstatehash,
|
|
||||||
&StateEventType::RoomEncryption,
|
|
||||||
"",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let joined_since_last_sync =
|
|
||||||
since_sender_member.is_none_or(|member| {
|
|
||||||
member.membership != MembershipState::Join
|
|
||||||
});
|
|
||||||
|
|
||||||
let new_encrypted_room =
|
|
||||||
encrypted_room && since_encryption.is_none();
|
|
||||||
if encrypted_room {
|
|
||||||
let current_state_ids = services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.state_full_ids(current_shortstatehash)
|
|
||||||
.await?;
|
|
||||||
let since_state_ids = services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.state_full_ids(since_shortstatehash)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for (key, event_id) in current_state_ids {
|
|
||||||
if since_state_ids.get(&key) != Some(&event_id) {
|
|
||||||
let Some(pdu) =
|
|
||||||
services().rooms.timeline.get_pdu(&event_id)?
|
|
||||||
else {
|
|
||||||
error!(%event_id, "Event in state not found");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if pdu.kind == TimelineEventType::RoomMember {
|
|
||||||
if let Some(state_key) = &pdu.state_key {
|
|
||||||
let user_id =
|
|
||||||
UserId::parse(state_key.clone())
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::bad_database(
|
|
||||||
"Invalid UserId in member \
|
|
||||||
PDU.",
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if user_id == sender_user {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_membership =
|
|
||||||
serde_json::from_str::<
|
|
||||||
RoomMemberEventContent,
|
|
||||||
>(
|
|
||||||
pdu.content.get()
|
|
||||||
)
|
|
||||||
.map_err(|_| {
|
|
||||||
Error::bad_database(
|
|
||||||
"Invalid PDU in database.",
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.membership;
|
|
||||||
|
|
||||||
match new_membership {
|
|
||||||
MembershipState::Join => {
|
|
||||||
// A new user joined an encrypted
|
|
||||||
// room
|
|
||||||
if !share_encrypted_room(
|
|
||||||
&sender_user,
|
|
||||||
&user_id,
|
|
||||||
room_id,
|
|
||||||
)? {
|
|
||||||
device_list_changes
|
|
||||||
.insert(user_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MembershipState::Leave => {
|
|
||||||
// Write down users that have left
|
|
||||||
// encrypted rooms we are in
|
|
||||||
left_encrypted_users
|
|
||||||
.insert(user_id);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if joined_since_last_sync || new_encrypted_room {
|
|
||||||
// If the user is in a new encrypted room, give them all
|
|
||||||
// joined users
|
|
||||||
device_list_changes.extend(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.room_members(room_id)
|
|
||||||
.flatten()
|
|
||||||
.filter(|user_id| {
|
|
||||||
// Don't send key updates from the sender to
|
|
||||||
// the sender
|
|
||||||
&sender_user != user_id
|
|
||||||
})
|
|
||||||
.filter(|user_id| {
|
|
||||||
// Only send keys if the sender doesn't
|
|
||||||
// share an encrypted room with the target
|
|
||||||
// already
|
|
||||||
!share_encrypted_room(
|
|
||||||
&sender_user,
|
|
||||||
user_id,
|
|
||||||
room_id,
|
|
||||||
)
|
|
||||||
.unwrap_or(false)
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Look for device list updates in this room
|
|
||||||
device_list_changes.extend(
|
|
||||||
services()
|
|
||||||
.users
|
|
||||||
.keys_changed(room_id.as_ref(), globalsince, None)
|
|
||||||
.filter_map(Result::ok),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for user_id in left_encrypted_users {
|
|
||||||
let dont_share_encrypted_room = services()
|
|
||||||
.rooms
|
|
||||||
.user
|
|
||||||
.get_shared_rooms(vec![sender_user.clone(), user_id.clone()])?
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.filter_map(|other_room_id| {
|
|
||||||
Some(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(
|
|
||||||
&other_room_id,
|
|
||||||
&StateEventType::RoomEncryption,
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
.ok()?
|
|
||||||
.is_some(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.all(|encrypted| !encrypted);
|
|
||||||
// If the user doesn't share an encrypted room with the target
|
|
||||||
// anymore, we need to tell them
|
|
||||||
if dont_share_encrypted_room {
|
|
||||||
device_list_left.insert(user_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut lists = BTreeMap::new();
|
|
||||||
// and required state
|
// and required state
|
||||||
let mut todo_rooms: BTreeMap<OwnedRoomId, TodoRoom> = BTreeMap::new();
|
let mut todo_rooms: BTreeMap<OwnedRoomId, TodoRoom> = BTreeMap::new();
|
||||||
|
|
||||||
for (list_id, list) in body.lists {
|
let lists = body
|
||||||
trace!(list_id, ?list, "Collecting rooms in list");
|
.lists
|
||||||
|
.into_iter()
|
||||||
if list.filters.and_then(|f| f.is_invite).unwrap_or(false) {
|
.filter_map(|(list_id, list)| {
|
||||||
continue;
|
let rooms = rooms_in_list(
|
||||||
}
|
&list_id,
|
||||||
|
list,
|
||||||
let mut list_room_ids = BTreeSet::new();
|
&all_joined_rooms,
|
||||||
for (mut from, mut to) in list.ranges {
|
|
||||||
from = from.clamp(
|
|
||||||
uint!(0),
|
|
||||||
UInt::try_from(all_joined_rooms.len() - 1).unwrap_or(UInt::MAX),
|
|
||||||
);
|
|
||||||
to = to.clamp(
|
|
||||||
from,
|
|
||||||
UInt::try_from(all_joined_rooms.len() - 1).unwrap_or(UInt::MAX),
|
|
||||||
);
|
|
||||||
let room_ids =
|
|
||||||
all_joined_rooms[from.try_into().unwrap_or(usize::MAX)
|
|
||||||
..=to.try_into().unwrap_or(usize::MAX)]
|
|
||||||
.to_vec();
|
|
||||||
list_room_ids.extend(room_ids);
|
|
||||||
}
|
|
||||||
for room_id in &list_room_ids {
|
|
||||||
todo_rooms.entry(room_id.clone()).or_default().update(
|
|
||||||
list.room_details.required_state.clone(),
|
|
||||||
list.room_details.timeline_limit,
|
|
||||||
&known_rooms,
|
&known_rooms,
|
||||||
room_id,
|
&mut todo_rooms,
|
||||||
);
|
)?;
|
||||||
}
|
Some((list_id, rooms))
|
||||||
let num_rooms = list_room_ids.len();
|
})
|
||||||
trace!(list_id, num_rooms, "Done collecting rooms");
|
.collect();
|
||||||
|
|
||||||
lists.insert(
|
|
||||||
list_id.clone(),
|
|
||||||
sync_events::v5::response::List {
|
|
||||||
count: UInt::try_from(num_rooms).unwrap_or(UInt::MAX),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (room_id, room) in &body.room_subscriptions {
|
for (room_id, room) in &body.room_subscriptions {
|
||||||
if !services().rooms.metadata.exists(room_id)? {
|
if !services().rooms.metadata.exists(room_id)? {
|
||||||
|
|
@ -395,160 +156,10 @@ pub(crate) async fn sync_events_v5_route(
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut rooms = BTreeMap::new();
|
let mut rooms = BTreeMap::new();
|
||||||
for (room_id, todo_room) in &todo_rooms {
|
for (room_id, todo_room) in todo_rooms {
|
||||||
trace!(
|
if let Some(room) = process_room(&sender_user, &room_id, &todo_room)? {
|
||||||
room_id = room_id.as_str(),
|
rooms.insert(room_id.clone(), room);
|
||||||
?todo_room,
|
|
||||||
"Processing matched room"
|
|
||||||
);
|
|
||||||
let roomsincecount = PduCount::Normal(todo_room.roomsince);
|
|
||||||
|
|
||||||
let (timeline_pdus, limited) = load_timeline(
|
|
||||||
&sender_user,
|
|
||||||
room_id,
|
|
||||||
roomsincecount,
|
|
||||||
todo_room.timeline_limit,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if todo_room.roomsince != 0 && timeline_pdus.is_empty() {
|
|
||||||
trace!("No new timeline events, skipping");
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev_batch = timeline_pdus
|
|
||||||
.first()
|
|
||||||
.map(|(pdu_count, _)| match pdu_count {
|
|
||||||
PduCount::Backfilled(_) => {
|
|
||||||
error!("Timeline in backfill state?!");
|
|
||||||
"0".to_owned()
|
|
||||||
}
|
|
||||||
PduCount::Normal(c) => c.to_string(),
|
|
||||||
})
|
|
||||||
.or_else(|| {
|
|
||||||
(todo_room.roomsince != 0)
|
|
||||||
.then(|| todo_room.roomsince.to_string())
|
|
||||||
});
|
|
||||||
|
|
||||||
let room_events: Vec<_> = timeline_pdus
|
|
||||||
.iter()
|
|
||||||
.map(|(_, pdu)| pdu.to_sync_room_event())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let required_state = todo_room
|
|
||||||
.required_state_request
|
|
||||||
.iter()
|
|
||||||
.filter_map(|state| {
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.room_state_get(room_id, &state.0, &state.1)
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.map(|state| state.to_sync_state_event())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Heroes
|
|
||||||
let heroes = services()
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.room_members(room_id)
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.filter(|member| member != &sender_user)
|
|
||||||
.filter_map(|member| {
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_accessor
|
|
||||||
.get_member(room_id, &member)
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.map(|memberevent| {
|
|
||||||
(
|
|
||||||
memberevent
|
|
||||||
.displayname
|
|
||||||
.unwrap_or_else(|| member.to_string()),
|
|
||||||
memberevent.avatar_url,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.take(5)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let name = match &*heroes {
|
|
||||||
[] => None,
|
|
||||||
[(only, _)] => Some(only.clone()),
|
|
||||||
[firsts @ .., (last, _)] => Some({
|
|
||||||
let firsts = firsts
|
|
||||||
.iter()
|
|
||||||
.map(|(name, _)| name.clone())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ");
|
|
||||||
|
|
||||||
format!("{firsts} and {last}")
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let room = sync_events::v5::response::Room {
|
|
||||||
name: services().rooms.state_accessor.get_name(room_id)?.or(name),
|
|
||||||
avatar: if let [(_name, Some(avatar))] = &*heroes {
|
|
||||||
JsOption::Some(avatar.clone())
|
|
||||||
} else {
|
|
||||||
match services().rooms.state_accessor.get_avatar(room_id)? {
|
|
||||||
JsOption::Some(avatar) => JsOption::from_option(avatar.url),
|
|
||||||
JsOption::Null => JsOption::Null,
|
|
||||||
JsOption::Undefined => JsOption::Undefined,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initial: Some(todo_room.roomsince == 0),
|
|
||||||
is_dm: None,
|
|
||||||
invite_state: None,
|
|
||||||
unread_notifications: UnreadNotificationsCount {
|
|
||||||
highlight_count: Some(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.user
|
|
||||||
.highlight_count(&sender_user, room_id)?
|
|
||||||
.try_into()
|
|
||||||
.expect("notification count can't go that high"),
|
|
||||||
),
|
|
||||||
notification_count: Some(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.user
|
|
||||||
.notification_count(&sender_user, room_id)?
|
|
||||||
.try_into()
|
|
||||||
.expect("notification count can't go that high"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
timeline: room_events,
|
|
||||||
required_state,
|
|
||||||
prev_batch,
|
|
||||||
limited,
|
|
||||||
joined_count: Some(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.room_joined_count(room_id)?
|
|
||||||
.map(UInt::new_saturating)
|
|
||||||
.unwrap_or(uint!(0)),
|
|
||||||
),
|
|
||||||
invited_count: Some(
|
|
||||||
services()
|
|
||||||
.rooms
|
|
||||||
.state_cache
|
|
||||||
.room_invited_count(room_id)?
|
|
||||||
.map(UInt::new_saturating)
|
|
||||||
.unwrap_or(uint!(0)),
|
|
||||||
),
|
|
||||||
// Count events in timeline greater than global sync counter
|
|
||||||
num_live: None,
|
|
||||||
// TODO
|
|
||||||
bump_stamp: None,
|
|
||||||
// TODO
|
|
||||||
heroes: None,
|
|
||||||
};
|
|
||||||
trace!(room_id = room_id.as_str(), ?room, "Built room data");
|
|
||||||
|
|
||||||
rooms.insert(room_id.clone(), room);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rooms
|
if rooms
|
||||||
|
|
@ -589,10 +200,7 @@ pub(crate) async fn sync_events_v5_route(
|
||||||
})
|
})
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
e2ee: sync_events::v5::response::E2EE {
|
e2ee: sync_events::v5::response::E2EE {
|
||||||
device_lists: DeviceLists {
|
device_lists: device_lists.unwrap_or_default(),
|
||||||
changed: device_list_changes.into_iter().collect(),
|
|
||||||
left: device_list_left.into_iter().collect(),
|
|
||||||
},
|
|
||||||
device_one_time_keys_count: services()
|
device_one_time_keys_count: services()
|
||||||
.users
|
.users
|
||||||
.count_one_time_keys(&sender_user, &sender_device)?,
|
.count_one_time_keys(&sender_user, &sender_device)?,
|
||||||
|
|
@ -627,3 +235,426 @@ pub(crate) async fn sync_events_v5_route(
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn get_e2ee_data(
|
||||||
|
sender_user: &UserId,
|
||||||
|
globalsince: u64,
|
||||||
|
all_joined_rooms: &[OwnedRoomId],
|
||||||
|
) -> Result<DeviceLists> {
|
||||||
|
// Users that have left any encrypted rooms the sender was in
|
||||||
|
let mut left_encrypted_users = HashSet::new();
|
||||||
|
|
||||||
|
// Look for device list updates of this account
|
||||||
|
let mut device_list_changes: HashSet<_> = services()
|
||||||
|
.users
|
||||||
|
.keys_changed(sender_user.as_ref(), globalsince, None)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for room_id in all_joined_rooms {
|
||||||
|
let Some(current_shortstatehash) =
|
||||||
|
services().rooms.state.get_room_shortstatehash(room_id)?
|
||||||
|
else {
|
||||||
|
error!(%room_id, "Room has no state");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let since_shortstatehash = services()
|
||||||
|
.rooms
|
||||||
|
.user
|
||||||
|
.get_token_shortstatehash(room_id, globalsince)?;
|
||||||
|
|
||||||
|
let since_sender_member: Option<RoomMemberEventContent> =
|
||||||
|
since_shortstatehash
|
||||||
|
.and_then(|shortstatehash| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.state_get(
|
||||||
|
shortstatehash,
|
||||||
|
&StateEventType::RoomMember,
|
||||||
|
sender_user.as_str(),
|
||||||
|
)
|
||||||
|
.transpose()
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.and_then(|pdu| {
|
||||||
|
serde_json::from_str(pdu.content.get())
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database("Invalid PDU in database.")
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
});
|
||||||
|
|
||||||
|
let encrypted_room = services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.state_get(
|
||||||
|
current_shortstatehash,
|
||||||
|
&StateEventType::RoomEncryption,
|
||||||
|
"",
|
||||||
|
)?
|
||||||
|
.is_some();
|
||||||
|
|
||||||
|
if let Some(since_shortstatehash) = since_shortstatehash {
|
||||||
|
// Skip if there are only timeline changes
|
||||||
|
if since_shortstatehash == current_shortstatehash {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let since_encryption = services().rooms.state_accessor.state_get(
|
||||||
|
since_shortstatehash,
|
||||||
|
&StateEventType::RoomEncryption,
|
||||||
|
"",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let joined_since_last_sync =
|
||||||
|
since_sender_member.is_none_or(|member| {
|
||||||
|
member.membership != MembershipState::Join
|
||||||
|
});
|
||||||
|
|
||||||
|
let new_encrypted_room =
|
||||||
|
encrypted_room && since_encryption.is_none();
|
||||||
|
if encrypted_room {
|
||||||
|
let current_state_ids = services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.state_full_ids(current_shortstatehash)
|
||||||
|
.await?;
|
||||||
|
let since_state_ids = services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.state_full_ids(since_shortstatehash)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for (key, event_id) in current_state_ids {
|
||||||
|
if since_state_ids.get(&key) != Some(&event_id) {
|
||||||
|
let Some(pdu) =
|
||||||
|
services().rooms.timeline.get_pdu(&event_id)?
|
||||||
|
else {
|
||||||
|
error!(%event_id, "Event in state not found");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if pdu.kind == TimelineEventType::RoomMember {
|
||||||
|
if let Some(state_key) = &pdu.state_key {
|
||||||
|
let user_id = UserId::parse(state_key.clone())
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database(
|
||||||
|
"Invalid UserId in member PDU.",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if user_id == sender_user {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_membership = serde_json::from_str::<
|
||||||
|
RoomMemberEventContent,
|
||||||
|
>(
|
||||||
|
pdu.content.get()
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::bad_database(
|
||||||
|
"Invalid PDU in database.",
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.membership;
|
||||||
|
|
||||||
|
match new_membership {
|
||||||
|
MembershipState::Join => {
|
||||||
|
// A new user joined an encrypted
|
||||||
|
// room
|
||||||
|
if !share_encrypted_room(
|
||||||
|
sender_user,
|
||||||
|
&user_id,
|
||||||
|
room_id,
|
||||||
|
)? {
|
||||||
|
device_list_changes.insert(user_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MembershipState::Leave => {
|
||||||
|
// Write down users that have left
|
||||||
|
// encrypted rooms we are in
|
||||||
|
left_encrypted_users.insert(user_id);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if joined_since_last_sync || new_encrypted_room {
|
||||||
|
// If the user is in a new encrypted room, give them all
|
||||||
|
// joined users
|
||||||
|
device_list_changes.extend(
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.room_members(room_id)
|
||||||
|
.flatten()
|
||||||
|
.filter(|user_id| {
|
||||||
|
// Don't send key updates from the sender to
|
||||||
|
// the sender
|
||||||
|
sender_user != user_id
|
||||||
|
})
|
||||||
|
.filter(|user_id| {
|
||||||
|
// Only send keys if the sender doesn't
|
||||||
|
// share an encrypted room with the target
|
||||||
|
// already
|
||||||
|
!share_encrypted_room(
|
||||||
|
sender_user,
|
||||||
|
user_id,
|
||||||
|
room_id,
|
||||||
|
)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Look for device list updates in this room
|
||||||
|
device_list_changes.extend(
|
||||||
|
services()
|
||||||
|
.users
|
||||||
|
.keys_changed(room_id.as_ref(), globalsince, None)
|
||||||
|
.filter_map(Result::ok),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut device_list_left = HashSet::new();
|
||||||
|
for user_id in left_encrypted_users {
|
||||||
|
let dont_share_encrypted_room = services()
|
||||||
|
.rooms
|
||||||
|
.user
|
||||||
|
.get_shared_rooms(vec![sender_user.to_owned(), user_id.clone()])?
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.filter_map(|other_room_id| {
|
||||||
|
Some(
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(
|
||||||
|
&other_room_id,
|
||||||
|
&StateEventType::RoomEncryption,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
.ok()?
|
||||||
|
.is_some(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.all(|encrypted| !encrypted);
|
||||||
|
// If the user doesn't share an encrypted room with the target
|
||||||
|
// anymore, we need to tell them
|
||||||
|
if dont_share_encrypted_room {
|
||||||
|
device_list_left.insert(user_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DeviceLists {
|
||||||
|
changed: device_list_changes.into_iter().collect(),
|
||||||
|
left: device_list_left.into_iter().collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(
|
||||||
|
skip_all,
|
||||||
|
fields(list_id = list_id, ?list),
|
||||||
|
)]
|
||||||
|
fn rooms_in_list(
|
||||||
|
list_id: &str,
|
||||||
|
list: sync_events::v5::request::List,
|
||||||
|
all_joined_rooms: &[OwnedRoomId],
|
||||||
|
known_rooms: &BTreeMap<OwnedRoomId, u64>,
|
||||||
|
todo_rooms: &mut BTreeMap<OwnedRoomId, TodoRoom>,
|
||||||
|
) -> Option<sync_events::v5::response::List> {
|
||||||
|
trace!(list_id, ?list, "Collecting rooms in list");
|
||||||
|
|
||||||
|
if list.filters.and_then(|f| f.is_invite).unwrap_or(false) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut list_room_ids: BTreeSet<OwnedRoomId> = BTreeSet::new();
|
||||||
|
for (mut from, mut to) in list.ranges {
|
||||||
|
from = from.clamp(
|
||||||
|
uint!(0),
|
||||||
|
UInt::try_from(all_joined_rooms.len() - 1).unwrap_or(UInt::MAX),
|
||||||
|
);
|
||||||
|
to = to.clamp(
|
||||||
|
from,
|
||||||
|
UInt::try_from(all_joined_rooms.len() - 1).unwrap_or(UInt::MAX),
|
||||||
|
);
|
||||||
|
let room_ids = all_joined_rooms[from.try_into().unwrap_or(usize::MAX)
|
||||||
|
..=to.try_into().unwrap_or(usize::MAX)]
|
||||||
|
.to_vec();
|
||||||
|
list_room_ids.extend(room_ids);
|
||||||
|
}
|
||||||
|
for room_id in &list_room_ids {
|
||||||
|
todo_rooms.entry(room_id.clone()).or_default().update(
|
||||||
|
list.room_details.required_state.clone(),
|
||||||
|
list.room_details.timeline_limit,
|
||||||
|
known_rooms,
|
||||||
|
room_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let num_rooms = list_room_ids.len();
|
||||||
|
trace!(list_id, num_rooms, "Done collecting rooms");
|
||||||
|
|
||||||
|
Some(sync_events::v5::response::List {
|
||||||
|
count: UInt::try_from(num_rooms).unwrap_or(UInt::MAX),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
#[tracing::instrument(skip(sender_user))]
|
||||||
|
fn process_room(
|
||||||
|
sender_user: &UserId,
|
||||||
|
room_id: &RoomId,
|
||||||
|
todo_room: &TodoRoom,
|
||||||
|
) -> Result<Option<sync_events::v5::response::Room>> {
|
||||||
|
let roomsincecount = PduCount::Normal(todo_room.roomsince);
|
||||||
|
|
||||||
|
let (timeline_pdus, limited) = load_timeline(
|
||||||
|
sender_user,
|
||||||
|
room_id,
|
||||||
|
roomsincecount,
|
||||||
|
todo_room.timeline_limit,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if todo_room.roomsince != 0 && timeline_pdus.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let prev_batch = timeline_pdus
|
||||||
|
.first()
|
||||||
|
.map(|(pdu_count, _)| match pdu_count {
|
||||||
|
PduCount::Backfilled(_) => {
|
||||||
|
error!("Timeline in backfill state?!");
|
||||||
|
"0".to_owned()
|
||||||
|
}
|
||||||
|
PduCount::Normal(c) => c.to_string(),
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
(todo_room.roomsince != 0).then(|| todo_room.roomsince.to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
let room_events: Vec<_> =
|
||||||
|
timeline_pdus.iter().map(|(_, pdu)| pdu.to_sync_room_event()).collect();
|
||||||
|
|
||||||
|
let required_state = todo_room
|
||||||
|
.required_state_request
|
||||||
|
.iter()
|
||||||
|
.filter_map(|state| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get(room_id, &state.0, &state.1)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.map(|state| state.to_sync_state_event())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Heroes
|
||||||
|
let heroes = services()
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.room_members(room_id)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.filter(|member| member != sender_user)
|
||||||
|
.filter_map(|member| {
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.get_member(room_id, &member)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.map(|memberevent| {
|
||||||
|
(
|
||||||
|
memberevent
|
||||||
|
.displayname
|
||||||
|
.unwrap_or_else(|| member.to_string()),
|
||||||
|
memberevent.avatar_url,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.take(5)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let name = match &*heroes {
|
||||||
|
[] => None,
|
||||||
|
[(only, _)] => Some(only.clone()),
|
||||||
|
[firsts @ .., (last, _)] => Some({
|
||||||
|
let firsts = firsts
|
||||||
|
.iter()
|
||||||
|
.map(|(name, _)| name.clone())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
format!("{firsts} and {last}")
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let room = sync_events::v5::response::Room {
|
||||||
|
name: services().rooms.state_accessor.get_name(room_id)?.or(name),
|
||||||
|
avatar: if let [(_name, Some(avatar))] = &*heroes {
|
||||||
|
JsOption::Some(avatar.clone())
|
||||||
|
} else {
|
||||||
|
match services().rooms.state_accessor.get_avatar(room_id)? {
|
||||||
|
JsOption::Some(avatar) => JsOption::from_option(avatar.url),
|
||||||
|
JsOption::Null => JsOption::Null,
|
||||||
|
JsOption::Undefined => JsOption::Undefined,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initial: Some(todo_room.roomsince == 0),
|
||||||
|
is_dm: None,
|
||||||
|
invite_state: None,
|
||||||
|
unread_notifications: UnreadNotificationsCount {
|
||||||
|
highlight_count: Some(
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.user
|
||||||
|
.highlight_count(sender_user, room_id)?
|
||||||
|
.try_into()
|
||||||
|
.expect("notification count can't go that high"),
|
||||||
|
),
|
||||||
|
notification_count: Some(
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.user
|
||||||
|
.notification_count(sender_user, room_id)?
|
||||||
|
.try_into()
|
||||||
|
.expect("notification count can't go that high"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
timeline: room_events,
|
||||||
|
required_state,
|
||||||
|
prev_batch,
|
||||||
|
limited,
|
||||||
|
joined_count: Some(
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.room_joined_count(room_id)?
|
||||||
|
.map(UInt::new_saturating)
|
||||||
|
.unwrap_or(uint!(0)),
|
||||||
|
),
|
||||||
|
invited_count: Some(
|
||||||
|
services()
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.room_invited_count(room_id)?
|
||||||
|
.map(UInt::new_saturating)
|
||||||
|
.unwrap_or(uint!(0)),
|
||||||
|
),
|
||||||
|
// Count events in timeline greater than global sync counter
|
||||||
|
num_live: None,
|
||||||
|
// TODO
|
||||||
|
bump_stamp: None,
|
||||||
|
// TODO
|
||||||
|
heroes: None,
|
||||||
|
};
|
||||||
|
trace!(?room, "Built room data");
|
||||||
|
|
||||||
|
Ok(Some(room))
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue