Done transcribing all logic is filled in no more unimplemented!
This commit is contained in:
parent
b2cbc3cd5d
commit
954fe5e51e
@ -6,6 +6,8 @@ edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
itertools = "0.9.0"
|
||||
js_int = "0.1.8"
|
||||
petgraph = "0.5.1"
|
||||
serde = { version = "1.0.114", features = ["derive"] }
|
||||
serde_json = "1.0.56"
|
||||
|
567
src/event_auth.rs
Normal file
567
src/event_auth.rs
Normal file
@ -0,0 +1,567 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use ruma::{
|
||||
events::{
|
||||
room::{self, join_rules::JoinRule, member::MembershipState},
|
||||
EventType,
|
||||
},
|
||||
identifiers::{RoomVersionId, UserId},
|
||||
};
|
||||
|
||||
use crate::{room_version::RoomVersion, state_event::StateEvent, StateMap};
|
||||
|
||||
/// Represents the 3 event redaction outcomes.
|
||||
pub enum RedactAllowed {
|
||||
/// The event is the users so redaction can take place.
|
||||
OwnEvent,
|
||||
/// The user can easily redact the event.
|
||||
CanRedact,
|
||||
/// The user does not have enough power to redact this event.
|
||||
No,
|
||||
}
|
||||
|
||||
pub(crate) fn auth_types_for_event(event: &StateEvent) -> Vec<(EventType, String)> {
|
||||
if event.kind() == EventType::RoomCreate {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let mut auth_types = vec![
|
||||
(EventType::RoomPowerLevels, "".to_string()),
|
||||
(EventType::RoomMember, event.sender().to_string()),
|
||||
(EventType::RoomCreate, "".to_string()),
|
||||
];
|
||||
|
||||
if event.kind() == EventType::RoomMember {
|
||||
if let Ok(content) = event.deserialize_content::<room::member::MemberEventContent>() {
|
||||
if [MembershipState::Join, MembershipState::Invite].contains(&content.membership) {
|
||||
auth_types.push((EventType::RoomJoinRules, "".into()))
|
||||
}
|
||||
|
||||
// TODO is None the same as "" for state_key, probably NOT
|
||||
auth_types.push((EventType::RoomMember, event.state_key().unwrap_or_default()));
|
||||
|
||||
if content.membership == MembershipState::Invite {
|
||||
if let Some(t_id) = content.third_party_invite {
|
||||
auth_types.push((EventType::RoomThirdPartyInvite, t_id.signed.token))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auth_types
|
||||
}
|
||||
|
||||
pub(crate) fn auth_check(
|
||||
room_version: &RoomVersionId,
|
||||
event: &StateEvent,
|
||||
auth_events: StateMap<StateEvent>,
|
||||
) -> Option<bool> {
|
||||
tracing::debug!("auth_check begingin");
|
||||
for auth_event in auth_events.values() {
|
||||
if auth_event.room_id() != event.room_id() {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO sig_check is false when called by `iterative_auth_check`
|
||||
|
||||
// Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules
|
||||
//
|
||||
// 1. If type is m.room.create:
|
||||
if event.kind() == EventType::RoomCreate {
|
||||
// domain of room_id must match domain of sender.
|
||||
if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) {
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
// if content.room_version is present and is not a valid version
|
||||
// TODO check this out (what event has this as content?)
|
||||
if serde_json::from_value::<RoomVersionId>(
|
||||
event
|
||||
.content()
|
||||
.get("room_version")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::json!({})),
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
tracing::debug!("m.room.create event was allowed");
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
// 3. If event does not have m.room.create in auth_events reject.
|
||||
if auth_events
|
||||
.get(&(EventType::RoomCreate, "".into()))
|
||||
.is_none()
|
||||
{
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
// check for m.federate
|
||||
if event.room_id().map(|id| id.server_name()) != Some(event.sender().server_name()) {
|
||||
if !can_federate(&auth_events) {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. if type is m.room.aliases
|
||||
if event.kind() == EventType::RoomAliases {
|
||||
// TODO && room_version "special case aliases auth" ??
|
||||
if event.state_key().is_none() {
|
||||
return Some(false); // must have state_key
|
||||
}
|
||||
if event.state_key().unwrap().is_empty() {
|
||||
return Some(false); // and be non-empty state_key (point to a user_id)
|
||||
}
|
||||
|
||||
if event.state_key() != Some(event.sender().to_string()) {
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
tracing::debug!("m.room.aliases event was allowed");
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
if event.kind() == EventType::RoomMember {
|
||||
if is_membership_change_allowed(event, &auth_events)? {
|
||||
tracing::debug!("m.room.member event was allowed");
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
|
||||
if !check_event_sender_in_room(event, &auth_events)? {
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
// Special case to allow m.room.third_party_invite events where ever
|
||||
// a user is allowed to issue invites
|
||||
if event.kind() == EventType::RoomThirdPartyInvite {
|
||||
// TODO impl this
|
||||
unimplemented!("third party invite")
|
||||
}
|
||||
|
||||
if !can_send_event(event, &auth_events)? {
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
if event.kind() == EventType::RoomPowerLevels {
|
||||
if !check_power_levels(room_version, event, &auth_events)? {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
if event.kind() == EventType::RoomRedaction {
|
||||
if let RedactAllowed::No = check_redaction(room_version, event, &auth_events)? {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("allowing event passed all checks");
|
||||
Some(true)
|
||||
}
|
||||
|
||||
// synapse has an `event: &StateEvent` param but it's never used
|
||||
/// Can this room federate based on its m.room.create event.
|
||||
fn can_federate(auth_events: &StateMap<StateEvent>) -> bool {
|
||||
let creation_event = auth_events.get(&(EventType::RoomCreate, "".into()));
|
||||
if let Some(ev) = creation_event {
|
||||
if let Some(fed) = ev.content().get("m.federate") {
|
||||
fed.to_string() == "true"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Dose the user who sent this member event have required power levels to do so.
|
||||
fn is_membership_change_allowed(
|
||||
event: &StateEvent,
|
||||
auth_events: &StateMap<StateEvent>,
|
||||
) -> Option<bool> {
|
||||
let content = event
|
||||
.deserialize_content::<room::member::MemberEventContent>()
|
||||
.ok()?;
|
||||
let membership = content.membership;
|
||||
|
||||
// check if this is the room creator joining
|
||||
if event.prev_event_ids().len() == 1 && membership == MembershipState::Join {
|
||||
if let Some(create) = auth_events.get(&(EventType::RoomCreate, "".into())) {
|
||||
if let Ok(create_ev) = create.deserialize_content::<room::create::CreateEventContent>()
|
||||
{
|
||||
if event.state_key() == Some(create_ev.creator.to_string()) {
|
||||
tracing::debug!("m.room.member event allowed via m.room.create");
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let target_user_id = UserId::try_from(event.state_key()?).ok()?;
|
||||
// if the server_names are different and federation is NOT allowed
|
||||
if event.room_id()?.server_name() != target_user_id.server_name() {
|
||||
if !can_federate(auth_events) {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO according to
|
||||
// https://github.com/matrix-org/synapse/blob/f2af3e4fc550e7e93be1b0f425c3e9c484b96293/synapse/events/__init__.py#L240
|
||||
// sender is the `user_id`?
|
||||
let key = (EventType::RoomMember, event.sender().to_string());
|
||||
let caller = auth_events.get(&key);
|
||||
|
||||
let caller_in_room = caller.is_some() && check_membership(caller, MembershipState::Join);
|
||||
let caller_invited = caller.is_some() && check_membership(caller, MembershipState::Invite);
|
||||
|
||||
let key = (EventType::RoomMember, target_user_id.to_string());
|
||||
let target = auth_events.get(&key);
|
||||
|
||||
let target_in_room = target.is_some() && check_membership(target, MembershipState::Join);
|
||||
let target_banned = target.is_some() && check_membership(target, MembershipState::Ban);
|
||||
|
||||
let key = (EventType::RoomJoinRules, "".to_string());
|
||||
let join_rules_event = auth_events.get(&key);
|
||||
let mut join_rule = JoinRule::Invite;
|
||||
if let Some(jr) = join_rules_event {
|
||||
join_rule = jr
|
||||
.deserialize_content::<room::join_rules::JoinRulesEventContent>()
|
||||
.ok()? // TODO these are errors? and should be treated as a DB failure?
|
||||
.join_rule;
|
||||
}
|
||||
|
||||
let user_level = get_user_power_level(event.sender(), auth_events);
|
||||
let target_level = get_user_power_level(event.sender(), auth_events);
|
||||
|
||||
// synapse has a not "what to do for default here ##"
|
||||
let ban_level = get_named_level(auth_events, "ban", 50);
|
||||
|
||||
// TODO clean this up
|
||||
tracing::debug!(
|
||||
"_is_membership_change_allowed: {}",
|
||||
serde_json::json!({
|
||||
"caller_in_room": caller_in_room,
|
||||
"caller_invited": caller_invited,
|
||||
"target_banned": target_banned,
|
||||
"target_in_room": target_in_room,
|
||||
"membership": membership,
|
||||
"join_rule": join_rule,
|
||||
"target_user_id": target_user_id,
|
||||
"event.user_id": event.sender(),
|
||||
}),
|
||||
);
|
||||
|
||||
if membership == MembershipState::Invite && content.third_party_invite.is_some() {
|
||||
// TODO impl this
|
||||
unimplemented!("third party invite")
|
||||
}
|
||||
|
||||
if membership != MembershipState::Join {
|
||||
if caller_invited
|
||||
&& membership == MembershipState::Leave
|
||||
&& &target_user_id == event.sender()
|
||||
{
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
|
||||
if membership == MembershipState::Invite {
|
||||
if target_banned {
|
||||
return Some(false);
|
||||
} else if target_in_room {
|
||||
return Some(false);
|
||||
} else {
|
||||
let invite_level = get_named_level(auth_events, "invite", 0);
|
||||
if user_level < invite_level {
|
||||
return Some(false);
|
||||
}
|
||||
}
|
||||
} else if membership == MembershipState::Join {
|
||||
if event.sender() != &target_user_id {
|
||||
return Some(false); // cannot force another user to join
|
||||
} else if target_banned {
|
||||
return Some(false); // cannot joined when banned
|
||||
} else if join_rule == JoinRule::Public {
|
||||
// pass
|
||||
} else if join_rule == JoinRule::Invite {
|
||||
if !caller_in_room && !caller_invited {
|
||||
return Some(false); // you are not invited to this room
|
||||
}
|
||||
} else {
|
||||
// synapse has 2 TODO's may_join list and private rooms
|
||||
return Some(false);
|
||||
}
|
||||
} else if membership == MembershipState::Leave {
|
||||
if target_banned && user_level < ban_level {
|
||||
return Some(false); // you cannot unban this user
|
||||
} else if &target_user_id != event.sender() {
|
||||
let kick_level = get_named_level(auth_events, "kick", 50);
|
||||
|
||||
if user_level < kick_level || user_level <= target_level {
|
||||
return Some(false); // you do not have the power to kick user
|
||||
}
|
||||
}
|
||||
} else if membership == MembershipState::Ban {
|
||||
if user_level < ban_level || user_level <= target_level {
|
||||
return Some(false);
|
||||
}
|
||||
} else {
|
||||
// Unknown membership status
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
Some(false)
|
||||
}
|
||||
|
||||
/// Is the event's sender in the room that they sent the event to.
|
||||
fn check_event_sender_in_room(
|
||||
event: &StateEvent,
|
||||
auth_events: &StateMap<StateEvent>,
|
||||
) -> Option<bool> {
|
||||
let mem = auth_events.get(&(EventType::RoomMember, event.sender().to_string()))?;
|
||||
// TODO this is check_membership
|
||||
Some(
|
||||
mem.deserialize_content::<room::member::MemberEventContent>()
|
||||
.ok()?
|
||||
.membership
|
||||
== MembershipState::Join,
|
||||
)
|
||||
}
|
||||
|
||||
/// Is the user allowed to send a specific event.
|
||||
fn can_send_event(event: &StateEvent, auth_events: &StateMap<StateEvent>) -> Option<bool> {
|
||||
let ple = auth_events.get(&(EventType::RoomPowerLevels, "".into()));
|
||||
|
||||
let send_level = get_send_level(event.kind(), event.state_key(), ple);
|
||||
let user_level = get_user_power_level(event.sender(), auth_events);
|
||||
|
||||
if user_level < send_level {
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
if let Some(sk) = event.state_key() {
|
||||
if sk.starts_with("@") {
|
||||
if sk != event.sender().to_string() {
|
||||
return Some(false); // permission required to post in this room
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(true)
|
||||
}
|
||||
|
||||
/// Confirm that the event sender has the required power levels.
|
||||
fn check_power_levels(
|
||||
room_version: &RoomVersionId,
|
||||
power_event: &StateEvent,
|
||||
auth_events: &StateMap<StateEvent>,
|
||||
) -> Option<bool> {
|
||||
use itertools::Itertools;
|
||||
|
||||
let key = (power_event.kind(), power_event.state_key()?);
|
||||
let current_state = auth_events.get(&key)?;
|
||||
|
||||
let user_content = power_event
|
||||
.deserialize_content::<room::power_levels::PowerLevelsEventContent>()
|
||||
.ok()?;
|
||||
let current_content = current_state
|
||||
.deserialize_content::<room::power_levels::PowerLevelsEventContent>()
|
||||
.ok()?;
|
||||
|
||||
// validation of users is done in Ruma, synapse for loops validating user_ids and integers here
|
||||
|
||||
let user_level = get_user_power_level(power_event.sender(), auth_events);
|
||||
|
||||
let mut user_levels_to_check = vec![];
|
||||
let old_list = ¤t_content.users;
|
||||
let user_list = &user_content.users;
|
||||
for user in old_list.keys().chain(user_list.keys()).dedup() {
|
||||
let user: &UserId = user;
|
||||
user_levels_to_check.push(user);
|
||||
}
|
||||
|
||||
let mut event_levels_to_check = vec![];
|
||||
let old_list = ¤t_content.events;
|
||||
let new_list = &user_content.events;
|
||||
for ev_id in old_list.keys().chain(new_list.keys()).dedup() {
|
||||
let ev_id: &EventType = ev_id;
|
||||
event_levels_to_check.push(ev_id);
|
||||
}
|
||||
|
||||
// TODO validate MSC2209 depending on room version check "notifications".
|
||||
// synapse does this very differently with the loops (see comments below)
|
||||
// but since we have a validated JSON event we can check the levels directly
|
||||
// I hope...
|
||||
if RoomVersion::new(room_version).limit_notifications_power_levels {
|
||||
let old_level: i64 = current_content.notifications.room.into();
|
||||
let new_level: i64 = user_content.notifications.room.into();
|
||||
|
||||
let old_level_too_big = old_level > user_level;
|
||||
let new_level_too_big = new_level > user_level;
|
||||
if old_level_too_big || new_level_too_big {
|
||||
return Some(false); // cannot add ops greater than own
|
||||
}
|
||||
}
|
||||
|
||||
let old_state = ¤t_content;
|
||||
let new_state = &user_content;
|
||||
|
||||
// synapse does not have to split up these checks since we can't combine UserIds and
|
||||
// EventTypes we do 2 loops
|
||||
|
||||
// UserId loop
|
||||
for user in user_levels_to_check {
|
||||
let old_level = old_state.users.get(user);
|
||||
let new_level = new_state.users.get(user);
|
||||
if old_level.is_some() && new_level.is_some() {
|
||||
if old_level == new_level {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if user != power_event.sender() {
|
||||
if old_level.map(|int| (*int).into()) == Some(user_level) {
|
||||
return Some(false); // cannot remove ops level == to own
|
||||
}
|
||||
}
|
||||
|
||||
let old_level_too_big = old_level.map(|int| (*int).into()) > Some(user_level);
|
||||
let new_level_too_big = new_level.map(|int| (*int).into()) > Some(user_level);
|
||||
if old_level_too_big || new_level_too_big {
|
||||
return Some(false); // cannot add ops greater than own
|
||||
}
|
||||
}
|
||||
|
||||
// EventType loop
|
||||
for ev_type in event_levels_to_check {
|
||||
let old_level = old_state.events.get(ev_type);
|
||||
let new_level = new_state.events.get(ev_type);
|
||||
if old_level.is_some() && new_level.is_some() {
|
||||
if old_level == new_level {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let old_level_too_big = old_level.map(|int| (*int).into()) > Some(user_level);
|
||||
let new_level_too_big = new_level.map(|int| (*int).into()) > Some(user_level);
|
||||
if old_level_too_big || new_level_too_big {
|
||||
return Some(false); // cannot add ops greater than own
|
||||
}
|
||||
}
|
||||
|
||||
Some(true)
|
||||
}
|
||||
|
||||
/// Does the event redacting come from a user with enough power to redact the given event.
|
||||
fn check_redaction(
|
||||
room_version: &RoomVersionId,
|
||||
redaction_event: &StateEvent,
|
||||
auth_events: &StateMap<StateEvent>,
|
||||
) -> Option<RedactAllowed> {
|
||||
let user_level = get_user_power_level(redaction_event.sender(), auth_events);
|
||||
let redact_level = get_named_level(auth_events, "redact", 50);
|
||||
|
||||
if user_level >= redact_level {
|
||||
return Some(RedactAllowed::CanRedact);
|
||||
}
|
||||
|
||||
if room_version.is_version_1() {
|
||||
if redaction_event.event_id() == redaction_event.redacts() {
|
||||
return Some(RedactAllowed::OwnEvent);
|
||||
}
|
||||
} else {
|
||||
// TODO synapse has this line also
|
||||
// event.internal_metadata.recheck_redaction = True
|
||||
return Some(RedactAllowed::OwnEvent);
|
||||
}
|
||||
Some(RedactAllowed::No)
|
||||
}
|
||||
|
||||
/// Check that the member event matches `state`.
|
||||
///
|
||||
/// This function returns false instead of failing when deserialization fails.
|
||||
fn check_membership(member_event: Option<&StateEvent>, state: MembershipState) -> bool {
|
||||
if let Some(event) = member_event {
|
||||
if let Ok(content) =
|
||||
serde_json::from_value::<room::member::MemberEventContent>(event.content())
|
||||
{
|
||||
content.membership == state
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn get_named_level(auth_events: &StateMap<StateEvent>, name: &str, default: i64) -> i64 {
|
||||
let power_level_event = auth_events.get(&(EventType::RoomPowerLevels, "".into()));
|
||||
if let Some(pl) = power_level_event {
|
||||
// TODO do this the right way and deserialize
|
||||
if let Some(level) = pl.content().get(name) {
|
||||
level.to_string().parse().unwrap_or(default)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
fn get_user_power_level(user_id: &UserId, auth_events: &StateMap<StateEvent>) -> i64 {
|
||||
if let Some(pl) = auth_events.get(&(EventType::RoomPowerLevels, "".into())) {
|
||||
if let Ok(content) = pl.deserialize_content::<room::power_levels::PowerLevelsEventContent>()
|
||||
{
|
||||
if let Some(level) = content.users.get(user_id) {
|
||||
(*level).into()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0 // TODO if this fails DB error?
|
||||
}
|
||||
} else {
|
||||
// if no power level event found the creator gets 100 everyone else gets 0
|
||||
let key = (EventType::RoomCreate, "".into());
|
||||
if let Some(create) = auth_events.get(&key) {
|
||||
if let Ok(c) = create.deserialize_content::<room::create::CreateEventContent>() {
|
||||
if &c.creator == user_id {
|
||||
100
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_send_level(
|
||||
e_type: EventType,
|
||||
state_key: Option<String>,
|
||||
power_lvl: Option<&StateEvent>,
|
||||
) -> i64 {
|
||||
if let Some(ple) = power_lvl {
|
||||
if state_key.is_some() {
|
||||
if let Ok(content) =
|
||||
serde_json::from_value::<room::power_levels::PowerLevelsEventContent>(ple.content())
|
||||
{
|
||||
if let Some(_specific_ev) = content.events.get(&e_type) {
|
||||
// this is done differently in synapse the `specific_ev` is set and if `users_default` is
|
||||
// found it is used
|
||||
}
|
||||
content.users_default.into()
|
||||
} else {
|
||||
return 50;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
238
src/lib.rs
238
src/lib.rs
@ -1,15 +1,16 @@
|
||||
use std::{collections::BTreeMap, time::SystemTime};
|
||||
use std::{
|
||||
collections::{BTreeMap, BinaryHeap},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use petgraph::Graph;
|
||||
use ruma::{
|
||||
events::{
|
||||
room::{self},
|
||||
AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType,
|
||||
},
|
||||
events::EventType,
|
||||
identifiers::{EventId, RoomId, RoomVersionId},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod event_auth;
|
||||
mod room_version;
|
||||
mod state_event;
|
||||
mod state_store;
|
||||
|
||||
@ -78,10 +79,11 @@ impl StateResolution {
|
||||
|
||||
// add the auth_diff to conflicting now we have a full set of conflicting events
|
||||
auth_diff.extend(conflicting.values().cloned().flatten());
|
||||
let all_conflicted = auth_diff;
|
||||
let mut all_conflicted = auth_diff;
|
||||
|
||||
tracing::debug!("full conflicted set is {} events", all_conflicted.len());
|
||||
|
||||
// gather missing events for the event_map
|
||||
let events = store
|
||||
.get_events(
|
||||
&all_conflicted
|
||||
@ -92,6 +94,12 @@ impl StateResolution {
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.unwrap();
|
||||
// update event_map to include the fetched events
|
||||
event_map.extend(
|
||||
events
|
||||
.into_iter()
|
||||
.flat_map(|ev| Some((ev.event_id()?.clone(), ev))),
|
||||
);
|
||||
|
||||
for event in event_map.values() {
|
||||
if event.room_id() != Some(room_id) {
|
||||
@ -106,9 +114,11 @@ impl StateResolution {
|
||||
));
|
||||
}
|
||||
}
|
||||
// TODO throw error if event is not for this room
|
||||
// TODO make sure each conflicting event is in?? event_map `{eid for eid in full_conflicted_set if eid in event_map}`
|
||||
|
||||
// TODO make sure each conflicting event is in?? event_map `{eid for eid in full_conflicted_set if eid in event_map}`
|
||||
all_conflicted.retain(|id| event_map.contains_key(id));
|
||||
|
||||
// get only the power events with a state_key: "" or ban/kick event (sender != state_key)
|
||||
let power_events = all_conflicted
|
||||
.iter()
|
||||
.filter(|id| is_power_event(id, store))
|
||||
@ -116,7 +126,7 @@ impl StateResolution {
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// sort the power events based on power_level/clock/event_id and outgoing/incoming edges
|
||||
let mut sorted_power_levels = self.revers_topological_power_sort(
|
||||
let mut sorted_power_levels = self.reverse_topological_power_sort(
|
||||
room_id,
|
||||
&power_events,
|
||||
&mut event_map,
|
||||
@ -132,13 +142,14 @@ impl StateResolution {
|
||||
&clean,
|
||||
&mut event_map,
|
||||
store,
|
||||
);
|
||||
)?;
|
||||
|
||||
// At this point the power_events have been resolved we now have to
|
||||
// sort the remaining events using the mainline of the resolved power level.
|
||||
sorted_power_levels.dedup();
|
||||
let deduped_power_ev = sorted_power_levels;
|
||||
|
||||
// we have resolved the power events so remove them, I'm sure theres other reasons to do so
|
||||
let events_to_resolve = all_conflicted
|
||||
.iter()
|
||||
.filter(|id| deduped_power_ev.contains(id))
|
||||
@ -157,7 +168,7 @@ impl StateResolution {
|
||||
&resolved,
|
||||
&mut event_map,
|
||||
store,
|
||||
);
|
||||
)?;
|
||||
|
||||
// add unconflicted state to the resolved state
|
||||
resolved_state.extend(clean);
|
||||
@ -197,23 +208,153 @@ impl StateResolution {
|
||||
fn get_auth_chain_diff(
|
||||
&mut self,
|
||||
state_sets: &[StateMap<EventId>],
|
||||
event_map: &EventMap<StateEvent>,
|
||||
_event_map: &EventMap<StateEvent>,
|
||||
store: &dyn StateStore,
|
||||
) -> Result<Vec<EventId>, String> {
|
||||
use itertools::Itertools;
|
||||
|
||||
tracing::debug!("calculating auth chain difference");
|
||||
panic!()
|
||||
store.auth_chain_diff(
|
||||
&state_sets
|
||||
.iter()
|
||||
.flat_map(|map| map.values())
|
||||
.dedup()
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn revers_topological_power_sort(
|
||||
fn reverse_topological_power_sort(
|
||||
&mut self,
|
||||
room_id: &RoomId,
|
||||
power_events: &[EventId],
|
||||
event_map: &EventMap<StateEvent>,
|
||||
store: &dyn StateStore,
|
||||
conflicted_set: &[EventId],
|
||||
auth_diff: &[EventId],
|
||||
) -> Vec<EventId> {
|
||||
tracing::debug!("reverse topological sort of power events");
|
||||
panic!()
|
||||
|
||||
let mut graph = BTreeMap::new();
|
||||
for (idx, event_id) in power_events.iter().enumerate() {
|
||||
self.add_event_and_auth_chain_to_graph(room_id, &mut graph, event_id, store, auth_diff);
|
||||
|
||||
// We yield occasionally when we're working with large data sets to
|
||||
// ensure that we don't block the reactor loop for too long.
|
||||
if idx % _YIELD_AFTER_ITERATIONS == 0 {
|
||||
// yield clock.sleep(0)
|
||||
}
|
||||
}
|
||||
|
||||
// this is used in the `key_fn` passed to the lexico_topo_sort fn
|
||||
let mut event_to_pl = BTreeMap::new();
|
||||
for (idx, event_id) in graph.keys().enumerate() {
|
||||
let pl = self.get_power_level_for_sender(room_id, &event_id, event_map, store);
|
||||
|
||||
event_to_pl.insert(event_id.clone(), pl);
|
||||
|
||||
// We yield occasionally when we're working with large data sets to
|
||||
// ensure that we don't block the reactor loop for too long.
|
||||
if idx % _YIELD_AFTER_ITERATIONS == 0 {
|
||||
// yield clock.sleep(0)
|
||||
}
|
||||
}
|
||||
|
||||
self.lexicographical_topological_sort(&mut graph, |event_id| {
|
||||
let ev = event_map.get(event_id).cloned().unwrap();
|
||||
let pl = event_to_pl.get(event_id).unwrap();
|
||||
|
||||
(*pl, ev.origin_server_ts().clone(), ev.event_id().cloned())
|
||||
})
|
||||
}
|
||||
|
||||
fn lexicographical_topological_sort<F>(
|
||||
&mut self,
|
||||
graph: &BTreeMap<EventId, Vec<EventId>>,
|
||||
key_fn: F,
|
||||
) -> Vec<EventId>
|
||||
where
|
||||
F: Fn(&EventId) -> (i64, SystemTime, Option<EventId>),
|
||||
{
|
||||
// Note, this is basically Kahn's algorithm except we look at nodes with no
|
||||
// outgoing edges, c.f.
|
||||
// https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm
|
||||
let outdegree_map = graph;
|
||||
let mut reverse_graph = BTreeMap::new();
|
||||
|
||||
// Vec of nodes that have zero out degree.
|
||||
let mut zero_outdegree = vec![];
|
||||
|
||||
for (node, edges) in graph.iter() {
|
||||
if edges.is_empty() {
|
||||
zero_outdegree.push((key_fn(node), node));
|
||||
}
|
||||
|
||||
reverse_graph.insert(node, vec![]);
|
||||
for edge in edges {
|
||||
reverse_graph.entry(edge).or_insert(vec![]).push(node);
|
||||
}
|
||||
}
|
||||
|
||||
let mut heap = BinaryHeap::from(zero_outdegree);
|
||||
|
||||
while let Some((_, node)) = heap.pop() {
|
||||
for parent in reverse_graph.get(node).unwrap() {
|
||||
let out = outdegree_map.get(parent).unwrap();
|
||||
if out.iter().filter(|id| *id == node).count() == 0 {
|
||||
heap.push((key_fn(parent), parent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
heap.into_iter().map(|(_, id)| id).cloned().collect()
|
||||
}
|
||||
|
||||
fn get_power_level_for_sender(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
event_id: &EventId,
|
||||
event_map: &EventMap<StateEvent>,
|
||||
store: &dyn StateStore,
|
||||
) -> i64 {
|
||||
let mut pl = None;
|
||||
for aid in store.auth_event_ids(room_id, event_id).unwrap() {
|
||||
if let Ok(aev) = store.get_event(&aid) {
|
||||
if aev.is_type_and_key(EventType::RoomPowerLevels, "") {
|
||||
pl = Some(aev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pl.is_none() {
|
||||
for aid in store.auth_event_ids(room_id, event_id).unwrap() {
|
||||
if let Ok(aev) = store.get_event(&aid) {
|
||||
if aev.is_type_and_key(EventType::RoomCreate, "") {
|
||||
if let Ok(content) = aev
|
||||
.deserialize_content::<ruma::events::room::create::CreateEventContent>()
|
||||
{
|
||||
if &content.creator == aev.sender() {
|
||||
return 100;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if let Some(content) = pl
|
||||
.map(|pl| {
|
||||
pl.deserialize_content::<ruma::events::room::power_levels::PowerLevelsEventContent>(
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
.flatten()
|
||||
{
|
||||
content.users_default.into()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn iterative_auth_check(
|
||||
@ -224,9 +365,41 @@ impl StateResolution {
|
||||
unconflicted_state: &StateMap<EventId>,
|
||||
event_map: &EventMap<StateEvent>,
|
||||
store: &dyn StateStore,
|
||||
) -> StateMap<EventId> {
|
||||
) -> Result<StateMap<EventId>, String> {
|
||||
tracing::debug!("starting iter auth check");
|
||||
panic!()
|
||||
let resolved_state = unconflicted_state.clone();
|
||||
for (idx, event_id) in power_events.iter().enumerate() {
|
||||
let event = store.get_event(event_id).unwrap();
|
||||
|
||||
let mut auth_events = BTreeMap::new();
|
||||
for aid in store.auth_event_ids(room_id, event_id).unwrap() {
|
||||
if let Ok(ev) = store.get_event(&aid) {
|
||||
// TODO is None the same as "" for state_key, pretty sure it is NOT
|
||||
auth_events.insert((ev.kind(), ev.state_key().unwrap_or_default()), ev);
|
||||
} else {
|
||||
tracing::warn!("auth event id for {} is missing {}", aid, event_id);
|
||||
}
|
||||
}
|
||||
|
||||
for key in event_auth::auth_types_for_event(&event) {
|
||||
if let Some(ev_id) = resolved_state.get(&key) {
|
||||
// TODO synapse gets the event from the store then checks its not None
|
||||
// then pulls the same `ev_id` event from the event_map??
|
||||
if let Ok(event) = store.get_event(ev_id) {
|
||||
auth_events.insert(key.clone(), event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !event_auth::auth_check(room_version, &event, auth_events).ok_or("".to_string())? {}
|
||||
|
||||
// We yield occasionally when we're working with large data sets to
|
||||
// ensure that we don't block the reactor loop for too long.
|
||||
if idx % _YIELD_AFTER_ITERATIONS == 0 {
|
||||
// yield clock.sleep(0)
|
||||
}
|
||||
}
|
||||
Ok(resolved_state)
|
||||
}
|
||||
|
||||
/// Returns the sorted `to_sort` list of `EventId`s based on a mainline sort using
|
||||
@ -337,6 +510,33 @@ impl StateResolution {
|
||||
// Did not find a power level event so we default to zero
|
||||
0
|
||||
}
|
||||
|
||||
fn add_event_and_auth_chain_to_graph(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
graph: &mut BTreeMap<EventId, Vec<EventId>>,
|
||||
event_id: &EventId,
|
||||
store: &dyn StateStore,
|
||||
auth_diff: &[EventId],
|
||||
) {
|
||||
let mut state = vec![event_id.clone()];
|
||||
while !state.is_empty() {
|
||||
// we just checked if it was empty so unwrap is fine
|
||||
let eid = state.pop().unwrap();
|
||||
graph.insert(eid.clone(), vec![]);
|
||||
|
||||
for aid in store.auth_event_ids(room_id, &eid).unwrap() {
|
||||
if auth_diff.contains(&aid) {
|
||||
if !graph.contains_key(&aid) {
|
||||
state.push(aid.clone());
|
||||
}
|
||||
|
||||
// we just inserted this at the start of the while loop
|
||||
graph.get_mut(&eid).unwrap().push(aid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_power_event(event_id: &EventId, store: &dyn StateStore) -> bool {
|
||||
|
147
src/room_version.rs
Normal file
147
src/room_version.rs
Normal file
@ -0,0 +1,147 @@
|
||||
use ruma::identifiers::RoomVersionId;
|
||||
|
||||
pub enum RoomDisposition {
|
||||
/// A room version that has a stable specification.
|
||||
Stable,
|
||||
/// A room version that is not yet fully specified.
|
||||
#[allow(dead_code)]
|
||||
Unstable,
|
||||
}
|
||||
|
||||
pub enum EventFormatVersion {
|
||||
/// $id:server event id format
|
||||
V1,
|
||||
/// MSC1659-style $hash event id format: introduced for room v3
|
||||
V2,
|
||||
/// MSC1884-style $hash format: introduced for room v4
|
||||
V3,
|
||||
}
|
||||
|
||||
pub enum StateResolutionVersion {
|
||||
/// State resolution for rooms at version 1.
|
||||
V1,
|
||||
/// State resolution for room at version 2 or later.
|
||||
V2,
|
||||
}
|
||||
|
||||
pub struct RoomVersion {
|
||||
/// The version this room is set to.
|
||||
pub version: RoomVersionId,
|
||||
/// The stability of this room.
|
||||
pub disposition: RoomDisposition,
|
||||
/// The format of the EventId.
|
||||
pub event_format: EventFormatVersion,
|
||||
/// Which state resolution algorithm is used.
|
||||
pub state_res: StateResolutionVersion,
|
||||
/// not sure
|
||||
pub enforce_key_validity: bool,
|
||||
|
||||
// bool: before MSC2261/MSC2432, m.room.aliases had special auth rules and redaction rules
|
||||
pub special_case_aliases_auth: bool,
|
||||
// Strictly enforce canonicaljson, do not allow:
|
||||
// * Integers outside the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]
|
||||
// * Floats
|
||||
// * NaN, Infinity, -Infinity
|
||||
pub strict_canonicaljson: bool,
|
||||
// bool: MSC2209: Check 'notifications' key while verifying
|
||||
// m.room.power_levels auth rules.
|
||||
pub limit_notifications_power_levels: bool,
|
||||
}
|
||||
|
||||
impl RoomVersion {
|
||||
pub fn new(version: &RoomVersionId) -> Self {
|
||||
if version.is_version_1() {
|
||||
Self::version_1()
|
||||
} else if version.is_version_2() {
|
||||
Self::version_2()
|
||||
} else if version.is_version_3() {
|
||||
Self::version_3()
|
||||
} else if version.is_version_4() {
|
||||
Self::version_4()
|
||||
} else if version.is_version_5() {
|
||||
Self::version_5()
|
||||
} else if version.is_version_6() {
|
||||
Self::version_6()
|
||||
} else {
|
||||
panic!("this crate needs to be updated with ruma")
|
||||
}
|
||||
}
|
||||
|
||||
fn version_1() -> Self {
|
||||
Self {
|
||||
version: RoomVersionId::version_1(),
|
||||
disposition: RoomDisposition::Stable,
|
||||
event_format: EventFormatVersion::V1,
|
||||
state_res: StateResolutionVersion::V1,
|
||||
enforce_key_validity: false,
|
||||
special_case_aliases_auth: true,
|
||||
strict_canonicaljson: false,
|
||||
limit_notifications_power_levels: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn version_2() -> Self {
|
||||
Self {
|
||||
version: RoomVersionId::version_2(),
|
||||
disposition: RoomDisposition::Stable,
|
||||
event_format: EventFormatVersion::V1,
|
||||
state_res: StateResolutionVersion::V2,
|
||||
enforce_key_validity: false,
|
||||
special_case_aliases_auth: true,
|
||||
strict_canonicaljson: false,
|
||||
limit_notifications_power_levels: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn version_3() -> Self {
|
||||
Self {
|
||||
version: RoomVersionId::version_3(),
|
||||
disposition: RoomDisposition::Stable,
|
||||
event_format: EventFormatVersion::V2,
|
||||
state_res: StateResolutionVersion::V2,
|
||||
enforce_key_validity: false,
|
||||
special_case_aliases_auth: true,
|
||||
strict_canonicaljson: false,
|
||||
limit_notifications_power_levels: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn version_4() -> Self {
|
||||
Self {
|
||||
version: RoomVersionId::version_4(),
|
||||
disposition: RoomDisposition::Stable,
|
||||
event_format: EventFormatVersion::V3,
|
||||
state_res: StateResolutionVersion::V2,
|
||||
enforce_key_validity: false,
|
||||
special_case_aliases_auth: true,
|
||||
strict_canonicaljson: false,
|
||||
limit_notifications_power_levels: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn version_5() -> Self {
|
||||
Self {
|
||||
version: RoomVersionId::version_5(),
|
||||
disposition: RoomDisposition::Stable,
|
||||
event_format: EventFormatVersion::V3,
|
||||
state_res: StateResolutionVersion::V2,
|
||||
enforce_key_validity: true,
|
||||
special_case_aliases_auth: true,
|
||||
strict_canonicaljson: false,
|
||||
limit_notifications_power_levels: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn version_6() -> Self {
|
||||
Self {
|
||||
version: RoomVersionId::version_6(),
|
||||
disposition: RoomDisposition::Stable,
|
||||
event_format: EventFormatVersion::V3,
|
||||
state_res: StateResolutionVersion::V2,
|
||||
enforce_key_validity: true,
|
||||
special_case_aliases_auth: false,
|
||||
strict_canonicaljson: true,
|
||||
limit_notifications_power_levels: true,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
use ruma::{
|
||||
events::{
|
||||
from_raw_json_value,
|
||||
pdu::{Pdu, PduStub, RoomV1Pdu, RoomV1PduStub, RoomV3Pdu, RoomV3PduStub},
|
||||
pdu::{Pdu, PduStub},
|
||||
room::member::{MemberEventContent, MembershipState},
|
||||
AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventDeHelper, EventType,
|
||||
EventDeHelper, EventType,
|
||||
},
|
||||
identifiers::{EventId, RoomId},
|
||||
identifiers::{EventId, RoomId, UserId},
|
||||
};
|
||||
use serde::{de, Serialize};
|
||||
use serde_json::value::RawValue as RawJsonValue;
|
||||
use std::{convert::TryFrom, time::SystemTime};
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
@ -72,6 +72,20 @@ impl StateEvent {
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn deserialize_content<C: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
) -> Result<C, serde_json::Error> {
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
Pdu::RoomV1Pdu(ev) => serde_json::from_value(ev.content.clone()),
|
||||
Pdu::RoomV3Pdu(ev) => serde_json::from_value(ev.content.clone()),
|
||||
},
|
||||
Self::Sync(ev) => match ev {
|
||||
PduStub::RoomV1PduStub(ev) => serde_json::from_value(ev.content.clone()),
|
||||
PduStub::RoomV3PduStub(ev) => serde_json::from_value(ev.content.clone()),
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn origin_server_ts(&self) -> &SystemTime {
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
@ -88,9 +102,35 @@ impl StateEvent {
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
Pdu::RoomV1Pdu(ev) => Some(&ev.event_id),
|
||||
Pdu::RoomV3Pdu(ev) => None,
|
||||
Pdu::RoomV3Pdu(_) => None,
|
||||
},
|
||||
Self::Sync(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sender(&self) -> &UserId {
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
Pdu::RoomV1Pdu(ev) => &ev.sender,
|
||||
Pdu::RoomV3Pdu(ev) => &ev.sender,
|
||||
},
|
||||
Self::Sync(ev) => match ev {
|
||||
PduStub::RoomV1PduStub(ev) => &ev.sender,
|
||||
PduStub::RoomV3PduStub(ev) => &ev.sender,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redacts(&self) -> Option<&EventId> {
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
Pdu::RoomV1Pdu(ev) => ev.redacts.as_ref(),
|
||||
Pdu::RoomV3Pdu(ev) => ev.redacts.as_ref(),
|
||||
},
|
||||
Self::Sync(ev) => match ev {
|
||||
PduStub::RoomV1PduStub(ev) => ev.redacts.as_ref(),
|
||||
PduStub::RoomV3PduStub(ev) => ev.redacts.as_ref(),
|
||||
},
|
||||
Self::Sync(ev) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,12 +140,81 @@ impl StateEvent {
|
||||
Pdu::RoomV1Pdu(ev) => Some(&ev.room_id),
|
||||
Pdu::RoomV3Pdu(ev) => Some(&ev.room_id),
|
||||
},
|
||||
Self::Sync(ev) => None,
|
||||
Self::Sync(_) => None,
|
||||
}
|
||||
}
|
||||
pub fn kind(&self) -> EventType {
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
Pdu::RoomV1Pdu(ev) => ev.kind.clone(),
|
||||
Pdu::RoomV3Pdu(ev) => ev.kind.clone(),
|
||||
},
|
||||
Self::Sync(ev) => match ev {
|
||||
PduStub::RoomV1PduStub(ev) => ev.kind.clone(),
|
||||
PduStub::RoomV3PduStub(ev) => ev.kind.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn state_key(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
Pdu::RoomV1Pdu(ev) => ev.state_key.clone(),
|
||||
Pdu::RoomV3Pdu(ev) => ev.state_key.clone(),
|
||||
},
|
||||
Self::Sync(ev) => match ev {
|
||||
PduStub::RoomV1PduStub(ev) => ev.state_key.clone(),
|
||||
PduStub::RoomV3PduStub(ev) => ev.state_key.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev_event_ids(&self) -> Vec<EventId> {
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
Pdu::RoomV1Pdu(ev) => ev.prev_events.iter().map(|(id, _)| id).cloned().collect(),
|
||||
Pdu::RoomV3Pdu(ev) => ev.prev_events.clone(),
|
||||
},
|
||||
Self::Sync(ev) => match ev {
|
||||
PduStub::RoomV1PduStub(ev) => {
|
||||
ev.prev_events.iter().map(|(id, _)| id).cloned().collect()
|
||||
}
|
||||
PduStub::RoomV3PduStub(ev) => ev.prev_events.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content(&self) -> serde_json::Value {
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
Pdu::RoomV1Pdu(ev) => ev.content.clone(),
|
||||
Pdu::RoomV3Pdu(ev) => ev.content.clone(),
|
||||
},
|
||||
Self::Sync(ev) => match ev {
|
||||
PduStub::RoomV1PduStub(ev) => ev.content.clone(),
|
||||
PduStub::RoomV3PduStub(ev) => ev.content.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_type_and_key(&self, ev_type: EventType, state_key: &str) -> bool {
|
||||
true
|
||||
match self {
|
||||
Self::Full(ev) => match ev {
|
||||
Pdu::RoomV1Pdu(ev) => {
|
||||
ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key)
|
||||
}
|
||||
Pdu::RoomV3Pdu(ev) => {
|
||||
ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key)
|
||||
}
|
||||
},
|
||||
Self::Sync(ev) => match ev {
|
||||
PduStub::RoomV1PduStub(ev) => {
|
||||
ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key)
|
||||
}
|
||||
PduStub::RoomV3PduStub(ev) => {
|
||||
ev.kind == ev_type && ev.state_key.as_deref() == Some(state_key)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,14 +225,10 @@ impl<'de> de::Deserialize<'de> for StateEvent {
|
||||
{
|
||||
let json = Box::<RawJsonValue>::deserialize(deserializer)?;
|
||||
let EventDeHelper {
|
||||
state_key,
|
||||
event_id,
|
||||
room_id,
|
||||
unsigned,
|
||||
..
|
||||
room_id, unsigned, ..
|
||||
} = from_raw_json_value(&json)?;
|
||||
|
||||
// Determine whether the event is a full, sync, or stripped
|
||||
// Determine whether the event is a full or sync
|
||||
// based on the fields present.
|
||||
if room_id.is_some() {
|
||||
Ok(match unsigned {
|
||||
|
@ -1,13 +1,4 @@
|
||||
use std::{collections::BTreeMap, time::SystemTime};
|
||||
|
||||
use petgraph::Graph;
|
||||
use ruma::{
|
||||
events::{
|
||||
room::{self},
|
||||
AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, EventType,
|
||||
},
|
||||
identifiers::{EventId, RoomId, RoomVersionId},
|
||||
};
|
||||
use ruma::identifiers::{EventId, RoomId, RoomVersionId};
|
||||
|
||||
use crate::StateEvent;
|
||||
|
||||
@ -21,6 +12,9 @@ pub trait StateStore {
|
||||
/// Returns a Vec of the related auth events to the given `event`.
|
||||
fn auth_event_ids(&self, room_id: &RoomId, event_id: &EventId) -> Result<Vec<EventId>, String>;
|
||||
|
||||
/// Returns a Vec<EventId> representing the difference in auth chains of the given `events`.
|
||||
fn auth_chain_diff(&self, event_id: &[&EventId]) -> Result<Vec<EventId>, String>;
|
||||
|
||||
/// Returns a tuple of requested state events from `event_id` and the auth chain events that
|
||||
/// relate to the.
|
||||
fn get_remote_state_for_room(
|
||||
|
@ -155,6 +155,10 @@ impl StateStore for TestStore {
|
||||
])
|
||||
}
|
||||
|
||||
fn auth_chain_diff(&self, event_id: &[&EventId]) -> Result<Vec<EventId>, String> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn get_remote_state_for_room(
|
||||
&self,
|
||||
room_id: &RoomId,
|
||||
@ -176,11 +180,11 @@ fn it_works() {
|
||||
let room_version = RoomVersionId::version_6();
|
||||
|
||||
let initial_state = btreemap! {
|
||||
(EventType::RoomCreate, "".into()) => EventId::try_from("").unwrap(),
|
||||
(EventType::RoomCreate, "".into()) => EventId::try_from("$aaa:example.org").unwrap(),
|
||||
};
|
||||
|
||||
let state_to_resolve = btreemap! {
|
||||
(EventType::RoomCreate, "".into()) => EventId::try_from("").unwrap(),
|
||||
(EventType::RoomCreate, "".into()) => EventId::try_from("$bbb:example.org").unwrap(),
|
||||
};
|
||||
|
||||
let mut resolver = StateResolution::default();
|
||||
|
Loading…
x
Reference in New Issue
Block a user